Cómo funciona

Volver al panel

Cómo funciona esto por dentro

Un rastreador de precios para volar entre Luxemburgo / Saarbrücken / Frankfurt-Hahn y Alicante. Captura tarifas de tres fuentes distintas, las guarda con histórico en SQLite, y las ordena no por precio sino por coste efectivo: lo que cuesta el billete más lo que te cuesta a ti en tiempo, días de vacaciones y kilómetros de coche hasta el aeropuerto.

1 · Arquitectura general

Todo corre en tu máquina. No hay servidor remoto, ni cuenta, ni nada que mantener: un proceso de Python que sirve una página y un fichero SQLite con el histórico de precios.

FUENTES EXTERNAS Ryanair API HHN · SCN, con horas Google Flights vía fast-flights Luxair LUX directo, sin horas fetch normaliza y guarda prices.db fares package_fares favorites Flask + JSON empareja y puntúa Navegador esta interfaz Asistente (Gemini) recibe el snapshot como contexto
El fetch escribe; la web solo lee. Puedes capturar precios con el panel cerrado, y viceversa.

2 · Las tres fuentes de datos

Ninguna fuente cubre todas las rutas, y cada una entrega los datos con una forma distinta. Por eso hay tres proveedores en vez de uno.

Ryanair — la fuente principal

Usa la API pública que alimenta el mapa de tarifas de ryanair.com (farfnd/v4/oneWayFares/.../cheapestPerDay). Devuelve la tarifa más barata de cada día con la hora exacta de salida y llegada, meses vista, y es rápida y estable. Cubre HHN y SCN hacia Alicante y vuelta. Es la única fuente que da datos completos por trayecto suelto.

Google Flights — la red de seguridad

A través de la librería fast-flights, que habla el mismo protocolo protobuf que usa la web de Google. No se conduce un navegador: eso se rompería cada dos semanas. Sirve para ver aerolíneas que Ryanair no conoce y para contrastar precios.

Desde una IP europea, Google responde con la pantalla de consentimiento de cookies en vez de con resultados. El proveedor envía las cookies de consentimiento en la petición para saltar ese muro. Además decodifica la respuesta como UTF-8 de forma explícita, porque requests asume ISO-8859-1 cuando la respuesta no declara codificación, y eso convertía los en galimatías.

Luxair — la única forma de ver LUX directo

Ryanair no vuela LUX–ALC y Google solo enseña itinerarios con una o dos escalas desde 110 €. Pero Luxair sí opera esa ruta todo el año como ruta de LuxairTours (habitualmente miércoles y sábado, más lunes en verano alto). Este proveedor consulta el calendario de precios de api.luxair.lu, que es lo que usa su propia web.

Tiene dos particularidades que condicionan todo lo demás:

  • Cotiza el viaje entero a un precio cerrado, para una fecha de salida más un número de noches. No hay precio de trayecto suelto que emparejar, así que estas tarifas viven en su propia tabla (package_fares) y en la lista salen marcadas paq.
  • No publica horarios. La API de Amadeus que sí los tiene está detrás de protección anti-bots, y no se intenta evadirla. Como la ruta vuela como mucho una vez al día, la fecha sigue siendo accionable: la interfaz muestra sin hora en lugar de inventarse un horario.

3 · El modelo de coste efectivo

Ordenar por precio a secas es engañoso: un vuelo de 22 € un martes a las 10:00 te cuesta además un día de vacaciones, y si sale de Hahn, tres horas y media de coche. La idea central de la app es convertir esa incomodidad en euros y sumarla al billete.

SituaciónAjustePor qué
Sale sábado o domingo−15 €No gastas días libres
Viernes después de las 17:30−25 €El mejor caso: sales al acabar de trabajar
Entre semana (lun–jue)+20 €Escapada más incómoda, trabajas al día siguiente
En horario laboral (09:00–17:30)+60 €Equivale a pedir el día
Antes de las 06:30+15 €Madrugón
Aterriza en casa después de las 22:30+25 €Te queda el viaje en coche de noche

Los ajustes se acumulan y se aplican a cada trayecto por separado, así que un viaje de ida y vuelta suma cuatro posibles ajustes. Todos los valores están en config.yaml: si para ti un día de vacaciones no vale 60 €, cámbialo y el ranking entero se reordena.

Las tarifas de Luxair se puntúan solo por día de la semana, nunca por hora. Sin horarios publicados no se puede saber si sale en horario laboral, y penalizarlas por algo que no podemos ver las hundiría injustamente en el ranking.

El coche también es parte del precio

Un vuelo desde Hahn a 25 € no cuesta 25 €: cuesta además 155 km de ida, otros tantos de vuelta, tres horas y media al volante y el parking de todos los días que el coche se queda allí. Luxemburgo está a 15 minutos. Ignorar eso hace que Ryanair gane siempre, y en la vida real no gana siempre. Por eso el coste efectivo suma también el coste de tierra:

AeropuertoCoche (ida y vuelta)ParkingCoste de tierra
LUX30 min · 16 km7 €/día10 € + parking
SCN2 h 20 · 190 km4 €/día70 € + parking
HHN3 h 30 · 310 km5 €/día110 € + parking
CRL4 h 30 · 400 km7 €/día142 € + parking

Sale de travel en config.yaml: los kilómetros y minutos de cada aeropuerto, lo que te cuesta el kilómetro (eur_per_km, combustible y desgaste) y lo que vale para ti una hora conduciendo (eur_per_hour). El parking se cobra por los días que el coche espera, así que pesa poco en un finde y mucho en dos semanas. La columna Coche de las tablas es exactamente esta cifra, y pasando el ratón por encima verás el desglose.

Si vuelves a un aeropuerto distinto del que saliste, el coche sigue donde lo dejaste: el modelo cobra la conducción de los dos aeropuertos, porque tendrás que volver a por él. Un aeropuerto que no esté en travel.airports simplemente no suma coste de tierra, así que añadir una ruta nueva no rompe nada.

4 · Días libres, la métrica que importa

El precio no basta para decidir. Lo que de verdad marca si un viaje sale a cuenta es cuántos días de vacaciones te come, y eso se calcula exacto, no a ojo:

  • El día de ida cuenta solo si sales antes de las 17:30. Un vuelo el viernes a las 22:00 no gasta día.
  • Todos los días laborables intermedios cuentan.
  • El día de vuelta cuenta siempre que sea laborable, aunque el vuelo llegue de madrugada: ese día ya no trabajas.

Así, viernes 22:00 → domingo son 0 días; jueves 22:00 → domingo es 1; y viernes 09:35 → lunes son 2. Con Luxair, que no publica horarios, el día se cuenta siempre — mejor pasarse que vender como gratis un viaje que no lo es.

Este número explica un patrón que no se ve mirando solo precios: los findes puros escasean y salen caros, mientras que pedir un único día libre (viernes o lunes) suele abrir tarifas mucho mejores. Por eso el filtro «Días libres» y las alertas van juntos.

Y además se pagan

Contarlos no basta: si no cuestan nada, el ranking te sigue proponiendo un miércoles a sábado como su mejor idea mientras se gasta tres días de tus vacaciones. Así que cada día vale day_off_cost euros (45 € por defecto) y ese cargo entra en el coste efectivo.

Con una excepción deliberada: el día de salida no se cobra aquí, porque ya lo pagan las penalizaciones de horario laboral y de entre semana, y cobrarlo dos veces sería facturar el mismo día por partida doble. Por eso un viernes 22:00 → domingo sigue saliendo gratis, y un miércoles → sábado carga los dos días extra que de verdad se lleva. Es también el motivo de que la etiqueta del cargo diga «vacaciones» sin número: cubre menos días que la insignia de la fila.

5 · Alertas

Una alerta es un filtro guardado con nombre. Cada fetch las vuelve a evaluar contra la última foto de precios y recuerda qué viajes ya había visto, de modo que puede decirte qué es nuevo en vez de repetirte lo de siempre. Lo nuevo aparece con punto azul en el panel y con NEW en la consola; el contador del menú se pone a cero cuando abres la vista.

Si un viaje deja de cumplir la regla, se olvida — así, si vuelve a aparecer más adelante, se vuelve a anunciar como nuevo.

6 · Cómo se arman las idas y vueltas

La app no busca "viajes": busca trayectos sueltos y luego los combina. Cada vuelo hacia Alicante se empareja con cada vuelo de vuelta que caiga dentro de la ventana de noches que hayas pedido. Eso incluye aeropuertos mezclados: ir desde Saarbrücken y volver a Hahn suele salir más barato que cerrar el viaje en el mismo sitio, y la casilla «mismo aeropuerto» te deja excluirlo si no te compensa conducir.

El filtrado ocurre en el servidor, antes de recortar resultados, y esto importa: hay decenas de miles de combinaciones posibles, y si se cortaran primero las 2.000 mejores globales, todos los viajes desde LUX desaparecerían detrás de los de Ryanair, que son más baratos. Filtrando antes, cuando pides LUX ves de verdad las mejores opciones de LUX.

7 · Modelo de datos e histórico

Cada captura añade filas, nunca sobreescribe. Por eso se puede dibujar la evolución de un precio: al pulsar cualquier vuelo se consulta todo lo que se ha visto para ese trayecto y esa fecha.

TablaContiene
fares Un trayecto suelto con hora exacta: origen, destino, salida, llegada, aerolínea, escalas, precio y el instante de la captura.
package_fares Un viaje completo a precio cerrado (Luxair): fecha de salida, número de noches y precio. Sin horas.
favorites Lo que has guardado con la estrella, junto al precio que tenía al guardarlo — de ahí sale la flecha de subida o bajada.

Cuando Ryanair y Google listan el mismo vuelo, se toma el más barato de la última captura. Antes se cogía una fila cualquiera, y los favoritos parecían encarecerse solos al saltar de una fuente a otra.

El calendario usa el mínimo por día y lo colorea en escala logarítmica, porque las tarifas se agolpan en la parte barata con una cola cara muy larga; en escala lineal casi todos los días saldrían del mismo color.

8 · El asistente

El panel de chat habla con Gemini a través de su API REST (no del SDK oficial, que arrastra cryptography y no compila en Windows ARM64). En cada pregunta se le envía un resumen del estado actual de la base de datos: rutas, mejores trayectos, mejores combinaciones, tarifas de Luxair y los pesos del scoring. Por eso responde con tus fechas y precios reales en vez de con generalidades.

Cuando recomienda una opción emite además una línea marcadora ([TRIP], [FLIGHT] o [PACKAGE]) que la interfaz convierte en una tarjeta con botón de guardar. Así lo que te propone el asistente entra en Favoritos de un clic. Si el modelo no emite el marcador, simplemente no aparece la tarjeta: el texto se lee igual.

La clave vive en el fichero .env, que está excluido del repositorio.

9 · Operarlo día a día

Capturar precios y levantar el panel son cosas independientes:

python -m flighttracker fetch            # rápido: Ryanair + Luxair
python -m flighttracker fetch --google  # añade Google Flights (lento)
python -m flighttracker.web             # panel en localhost:5010
python -m flighttracker alerts          # coincidencias de cada alerta
python -m flighttracker report          # el mismo ranking, en consola
python -m flighttracker models          # modelos de Gemini disponibles

El histórico solo tiene valor si se alimenta con regularidad. Con una captura diaria en el Programador de tareas de Windows, en un par de semanas los gráficos empiezan a decirte si un precio está subiendo o bajando — que es justo lo que hace falta para decidir cuándo comprar.

10 · Límites conocidos

  • Luxair no trae horarios. Solo fechas. La única forma de obtenerlos sería atravesar la protección anti-bots de su motor de reservas, y eso no se hace.
  • Los precios son los del momento de la captura, no en vivo. Al abrir el panel ves la última foto, no el precio de ahora mismo.
  • Solo tarifas base, un adulto. No se cuentan maletas, asientos ni extras, que en Ryanair pueden cambiar bastante la comparación real frente a Luxair, donde suele ir más incluido.
  • Google no siempre responde. Cuando falla para un día concreto, la captura sigue con el resto en lugar de abortar.