Case study
Bot de Vuelos — Alertas de precio Flight Price Bot — Price alerts
Un bot que vigila precios de vuelos y avisa por Telegram, corriendo entero en GitHub Actions a costo cero. Estuvo 28 días sin mandar una sola alerta y todas las corridas figuraban en verde. A bot that watches flight prices and pings Telegram, running entirely on GitHub Actions at zero cost. It went 28 days without sending a single alert while every run showed green.
Contexto Context
Estaba organizando un viaje y buscar vuelos baratos se había vuelto un trabajo: mil pestañas abiertas, buscadores lentos, y horas por día sin garantía de encontrar el mejor precio. Vi la idea de un bot de alertas y pensé que lo podía hacer. Es un proyecto personal, sin cliente y sin plazo, que resultó ser el que más me enseñó sobre sistemas que corren solos. I was planning a trip and hunting for cheap flights had turned into a job: a thousand tabs, slow search engines, and hours a day with no guarantee of finding the best price. I saw the idea of an alert bot and figured I could build it. It is a personal project, no client and no deadline, and it turned out to be the one that taught me the most about systems that run unattended.
Problema: casi ninguna aerolínea quiere que le consultes precios Problem: almost no airline wants you querying prices
Arranqué con las herramientas de desarrollador del navegador, interceptando las peticiones que hacen las webs de las aerolíneas al buscar un vuelo. Lo que encontré, y anoté como tabla de veredictos: una aerolínea con la API abierta y sin autenticación; otra con una clave pública que su propio frontend deja a la vista; y una tercera protegida con tokens y un sistema antibot serio, que directamente marqué como no viable en vez de perder días peleándola. Para cubrir el resto usé una librería que raspa Google Flights simulando un navegador. La arquitectura salió de ahí: APIs directas donde se puede, un raspador universal como red de seguridad. I started with the browser developer tools, intercepting the requests airline sites make when you search a flight. What I found, and wrote down as a verdict table: one airline with an open, unauthenticated API; another with a public key its own frontend leaves in plain sight; and a third protected with tokens and a serious anti-bot system, which I marked as not viable rather than lose days fighting it. To cover the rest I used a library that scrapes Google Flights by simulating a browser. The architecture came out of that: direct APIs where possible, a universal scraper as a safety net.
Los 28 días de silencio The 28 days of silence
Un día el bot dejó de avisar. No falló: dejó de avisar. Las corridas programadas seguían apareciendo en verde, terminando sin error, y yo asumía que simplemente no había bajado ningún precio. Pasó un mes hasta que revisé de verdad. La librería que uso como fuente universal había sacado una versión nueva con la interfaz cambiada; el bot obtenía cero precios, no encontraba nada por debajo del umbral —porque no había nada— y terminaba con código de salida cero. Todo el sistema estaba diseñado para avisar cuando pasa algo, y no tenía forma de avisar cuando no pasaba nada. El arreglo tuvo dos partes. Una, fijar la versión exacta de la dependencia en vez de aceptar la última. La otra, y la que importa: una corrida con cero precios ahora manda alerta a Telegram y termina en error, así que la corrida aparece en rojo. Después del fix, la primera corrida trajo 534 precios y 8 alertas. La regla que me llevé: un sistema automático necesita saber gritar cuando no encuentra nada. El silencio no es una señal de éxito. One day the bot stopped alerting. It did not fail: it stopped alerting. The scheduled runs kept showing green, finishing without error, and I assumed no price had simply dropped. A month went by before I actually checked. The library I use as the universal source had shipped a new version with a changed interface; the bot was getting zero prices, finding nothing below the threshold — because there was nothing — and exiting with code zero. The whole system was designed to alert when something happens, and had no way to alert when nothing did. The fix had two parts. One, pin the exact dependency version instead of accepting the latest. The other, the one that matters: a run with zero prices now sends a Telegram alert and exits with an error, so the run shows red. After the fix, the first run brought back 534 prices and 8 alerts. The rule I took away: an unattended system needs to know how to shout when it finds nothing. Silence is not a success signal.
Solución y decisiones técnicas Solution and technical decisions
- Arquitectura de adaptadores: cada fuente traduce su formato propio a un resultado estándar. Sumar una aerolínea nueva es crear un archivo, sin tocar el motor.
- Umbrales configurables por ruta en dólares y en pesos, con comparación entre monedas.
- Deduplicación con estado persistido entre corridas: no repite la misma alerta, pero si el precio baja todavía más, vuelve a avisar. Sin eso el bot se vuelve ruido y lo terminás silenciando.
- Peticiones en paralelo con Python asíncrono, para que las tres fuentes no se esperen entre sí.
- Todo corre en la capa gratuita de GitHub Actions con una tarea programada cada 6 horas, y el panel se publica en GitHub Pages. Costo total de infraestructura: cero.
- Cuando actualizo la librería de raspado, lo hago a mano y con una prueba local antes. Es la dependencia más frágil del sistema y ya me mordió una vez.
Resultado Result
Corriendo desde hace meses, con ocho rutas monitoreadas y más de 2.700 alertas emitidas en los últimos 90 días. El panel público muestra el mejor precio y el promedio por ruta, y la tabla de alertas con aerolínea, escalas y fecha de detección. Running for months, with eight monitored routes and more than 2,700 alerts issued over the last 90 days. The public dashboard shows the best and average price per route, plus the alerts table with airline, stops and detection date.
Aprendizajes Learnings
- Un proceso automático tiene que reportar la ausencia de resultados como un problema, no como una corrida normal.
- Fijar la versión exacta de la dependencia que sostiene el sistema. "La última" es una decisión que tomás una vez y te cobran después.
- Antes de escribir un scraper, mirar si hay una API. Y antes de pelear una API protegida, evaluar si vale la pena: descartar una fuente a tiempo también es una decisión técnica.
- Un panel público que muestra lo que el sistema está haciendo vale más que cualquier log: en un vistazo sé si está vivo.