El drama de la ITV
No sé cómo funciona el tema de la ITV en vuestras comunidades autónomas, pero en la mía (Asturias) tienes que estar muy pendiente de cuando te toca la revisión porque si no te despistas y estas perdido.
Tienes que perdir cita con unos cuantos meses de antelación o cuando quieras darte cuenta será demasiado tarde y te darán vez cuando ya te haya caducado tu última ITV.
Por suerte existe un plan “B” que consiste en levantarte a las 6:25 de la mañana y reservar una cita para ese mismo día.
Estas citas están muy cotizadas porque normalmente solo llaman a los primeros, además debido a que estas citas van ocupando huecos sobre la marcha, hay que estar allí en cuanto te avisen.
Como ya os podéis imaginar, conseguir ser el primero en reservar una de estas citas no es fácil.
El primer día que lo intente me puse a alarma a las 6:30 (es cuando abre las web) y cuando fui a pedir vez ya estaban reservadas más de 35 plazas por lo que no me acabo de llegar el turno ese mismo día.
¡Pero no todo está perdido!
Ya que somo gente que desarrolla, vamos a pensar como podemos automatizar la reserva, asegurarnos los primeros números y sobre todo no tener que madrugar xD.
Automatizando, que es gerundio
Lo primero es entender un poco la web en cuestión y el proceso manual que queremos automatizar:
- Ir a la web de ITV Asturias.
- Rellenar el formulario con los datos de la cita.
- Aceptar las cookies
- Sede de la ITV.
- Tipo de vehículo.
- Teléfono.
- Matrícula.
- Aceptar condiciones legales.
- Envío de la solicitud.
- Obtener del número de reserva.
Una vez que sabemos qué queremos hacer, solo faltar plantearse el cómo. Y en esto no tuve ninguna duda: Playwright.
Primero modelamos nuestro dominio con un par de clases que definen tanto el concepto estación como la propia reserva.
from dataclasses import dataclass
from enum import Enum
class Station(Enum):
GIJON = 4
AVILES = 5
PRUVIA = 6
MIERES = 7
SIERO = 14
@dataclass
class Reservation:
station: Station
phone: str
license_plate: str
Después implementamos nuestra lógica que estará basada en la lista de puntos que hablamos antes:
from playwright.sync_api import Page
from src.reservation import Reservation
class ITV:
def __init__(self, page: Page) -> None:
self.page = page
def reserve(self, reservation: Reservation) -> None:
self.page.goto("https://www.itvasa.es:444/sincitas/")
self._make_reservation(reservation)
self._save_reservation_number()
def _make_reservation(self, reservation: Reservation) -> None:
self._accept_cookies()
self._fill_form(reservation)
self._request_appointment()
def _accept_cookies(self) -> None:
self.page.locator("#cookieConsentContainer1 > .cookieButton > a:nth-child(1)").click()
def _fill_form(self, reservation: Reservation) -> None:
self.page.locator("#selEstacion").select_option(reservation.station.value)
self.page.locator("#selListaEspera").select_option("L")
self.page.locator("#telefono").fill(reservation.phone)
self.page.locator("#matricula").fill(reservation.license_plate)
self.page.locator("#woklegal").check()
def _request_appointment(self) -> None:
self.page.locator("#BotonSolicitar").click()
def _save_reservation_number(self) -> None:
self._find_reservation_number()
self._print_reservation_number()
def _find_reservation_number(self) -> None:
reservation_number = self.page.locator("table.citas > tbody > tr:nth-last-child(1)")
reservation_number.scroll_into_view_if_needed()
def _print_reservation_number(self) -> None:
self.page.screenshot(path="images/reservation_number.png")
Por último, en lo que a código Python se refiere, creamos un fichero que hará las veces de punto de entrada donde:
- Creamos una página con Playwright usando
Chrome
como navegador. - Instanciamos nuestra clase
ITV
. - Lanzamos la acción de reservar.
from playwright.sync_api import sync_playwright
from src.itv import ITV
from src.reservation import Reservation, Station
with sync_playwright() as playwright:
browser = playwright.chromium.launch()
page = browser.new_page()
itv = ITV(page)
reservation = Reservation(Station.AVILES, "123456789", "1234ABC")
itv.reserve(reservation)
browser.close()
Ya solo queda dockerizar la aplicación, para que sea más cómoda su ejecución, creando un Dockerfile
.
FROM mcr.microsoft.com/playwright/python:v1.45.1-jammy
RUN pip install playwright
WORKDIR /code
ENV PYTHONPATH /code
COPY . .
ENTRYPOINT ["python", "main.py"]
La idea es ejecutar la reserva de forma desatendida haciendo uso de crontab
, por ellos creamos un script que posteriormente enlazaremos a un cron
#!/bin/bash
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
cd /Users/pmareke/itv
docker run -v .:/code itv
Y ahora ya si por último ejecutamos crontab -e
e incluimos la siguiente línea 30 6 * * * /Users/pmareke/itv/run.sh
Esperamos al día siguiente y… ¡Voilà! ¡Ya tenemos nuestra cita reservada y además como primeros!.