Construyendo un asistente meteorológico con IA para pilotos ULM: LEMR Meteo
Índice
El problema
Volar un ultraligero requiere condiciones muy específicas: viento moderado, visibilidad decente, sin tormentas. Pero consultar METAR, AEMET, Windy y Open-Meteo por separado es tedioso. Y para un piloto novato, interpretar “LEAS 151730Z 27016KT 9999 SCT045 BKN100 14/02” al principio cuesta un poco más.

¿Y si una IA pudiera leer todo eso y decirte: “Hoy mejor no, rachas de 45 km/h. Mañana perfecto de 10:00 a 16:00”?
El resultado


contiene todo lo necesario para planificar el vuelo. Todos los recursos que normalmente consultamos en un solo sitio + un análisis IA.
Por último una visión a 7 días gracias a Ogimet:

La arquitectura (o: cómo hacer esto gratis)

Datos meteorológicos (€0)
- Open-Meteo API: Condiciones actuales + pronóstico 3 días. Sin API key, sin límites realistas.
- AEMET OpenData: Mapas sinópticos + predicciones textuales. Gratis con registro.
- Aviation Weather (NOAA): METAR del aeropuerto más cercano (LEAS).
- Windy API: Point forecast con diferentes modelos numéricos (GFS, ICON-EU, AROME).
El cerebro: Cascada de modelos IA (€0)
Aquí está la magia. GitHub Models ofrece acceso gratuito a GPT-4o (50 peticiones/día) y otros modelos.
Con la política establecida de refrescarse los datos a cada hora de 6:00 a 23:00 difícilmente llegaría al límite. Pero juntando las peticiones del entorno de desarrollo durante las pruebas, si que llegué varias veces al límite. La solución: un sistema de cascada automática.
AI_MODEL_CASCADE = ['gpt-4o', 'gpt-4o-mini', 'meta-llama-3.1-405b-instruct', 'phi-4']
Si gpt-4o alcanza el límite diario (error 429), el sistema prueba automáticamente con gpt-4o-mini (150/día). Si ese también falla, pasa a Llama 3.1 405B, y finalmente Phi-4.
Rate-limit protection: El código detecta errores 429 y marca el modelo como “agotado” para ese ciclo, evitando reintentos inútiles.
En 24 horas se restablece automáticamente el bloqueo.
Esto quiere decir que dejaría cierto margen para refrescar más veces los datos, y que siguiera siendo gratis, pero no interesa, ya que también hay límites en las APIs gratuitas de Aemet y de Wyndy; y teniendo en cuenta que los METAR se publican también cada hora o media hora, es un buen compromiso para no gastar solicitudes inútilmente.
El prompt ¿Qué se envía a la IA?
De “METAR” a “No vueles hoy, rachas de 23 kt”

INPUT
(lo que se envía a la IA): ~7.384 tokens
Hacemos una única llamada, para economizar. El prompt incluye:
SYSTEM_PROMPT (~1.751 tokens)
- USO de LEAS como referencia para LEMR
- Reglas de conversión de unidades (con ejemplo correcto/incorrecto)
- Legislación ULM 2024-2026
- Límites operacionales ULM
- Consideraciones generales ULM
- Información y contexto operativo aeródromo LEMR (pista, horarios, coordenadas)
- Regla de planificación de horarios
user_message (~5.633 tokens = 4.003 estáticos + ~1.630 datos dinámicos)
- ⏰ Hora actual y fecha
- 📍 Datos fijos LEMR (pista, horarios, VFR)
- ✈️ METAR LEAS (raw + clasificación VFR/MVFR/IFR/LIFR)
- ✈️ METAR LEMR estimado (raw + clasificación) — mejorado: dewpoint directo del modelo, visibilidad real, capas de nubes diferenciadas, umbral rachas ICAO Annex 3
- ⚠️ Nota sobre METARs + tabla de categorías de vuelo
- 🌡️ Condiciones actuales Open-Meteo (temp, viento km/h, rachas, precip, CAPE J/kg, techo estimado mín/media, visibilidad mín/media, emoji condición actual)
- ⚡ ANÁLISIS RIESGO CONVECTIVO pre-calculado (NULO/BAJO/MODERADO/ALTO/CRÍTICO + indicadores: CAPE, precip, nubes bajas, weather_code)
- 🕐 Open-Meteo HOY horas pendientes hasta cierre aeródromo — hora a hora: viento/rachas km/h, nube baja %, vis si <10 km
- 📅 Pronóstico 4 días Open-Meteo — enriquecido Phase 4: CAPE máx, precip_hours, sunshine_hours, freezing_level mín (m/ft + tag RIME/exp/ok), turbulencia_max (kt + SEVERA/MOD/lig), nieve cm, nubes BAJA/MED/ALT %, tendencia mañana→tarde rachas y nubes con flechas 📈/📉, hora pico rachas
- 🌪️ Windy resumen 4 días — con tendencia mañana→tarde de rachas
- ⏱️ Windy próximas 24 horas — viento, rachas, nubes %, precip (~500 tokens)
- 🚨 AVISOS AEMET CAP activos (viento/lluvia/nieve/chubascos en vigor)
- 📰 AEMET Asturias HOY (primeros 300 chars en GitHub Models / 1.200 en OpenAI)
- 📰 AEMET Asturias MAÑANA (ídem)
- 📰 AEMET Asturias PASADO MAÑANA (ídem)
- 📰 AEMET Llanera horaria compact — hoy + mañana en franjas operativas hora a hora
- ❌ SIN imágenes de mapas (para GitHub Models)
- ⚙️ Regla de rendimiento del avión (temp/presión → densidad del aire)
- 📋 Instrucciones de análisis (secciones 0–10 + reglas críticas)
OUTPUT
(respuesta de la IA): ~1.600 tokens estimados
- Sección 0: METAR LEAS explicado para novatos (~80 tokens)
- Sección 0.1: METAR LEMR explicado (~60 tokens)
- Sección 0.5: Pronóstico vs Realidad actual (~150 tokens)
- Sección 1: Coincidencias entre fuentes 4 días (~80 tokens)
- Sección 2: Discrepancias entre fuentes + causa probable (~100 tokens)
- Sección 3: Evolución meteorológica por día × 4 días (~120 tokens)
- Sección 4: Análisis de pista (solo HOY, con headwind/crosswind) (~150 tokens)
- Sección 5: Veredicto por día × 4 días + justificación multifactor (~250 tokens)
- Sección 6: Riesgos críticos × 4 días (~180 tokens)
- Sección 7: Franjas horarias recomendadas × 4 días (~100 tokens)
- Sección 8: Mejor día para volar + carácter + tipo de vuelo (~80 tokens)
- Sección 9: ¿Merece la pena? × 4 días (~60 tokens)
- Sección 10: Veredicto final global (~80 tokens)
- Disclaimer automático (~30 tokens)
CONSUMO TOTAL
| Concepto | Tokens |
|---|---|
| SYSTEM_PROMPT | ~1.751 |
| user_message estático | ~4.003 |
| user_message dinámico | ~1.630 |
| API overhead | ~14 |
| INPUT total | ~7.398 |
| OUTPUT (respuesta IA) | ~1.600 |
| TOTAL por ciclo | ~9.000 |
¿Qué es un token?
- Un token es aproximadamente 4 caracteres en inglés o una palabra corta.
- Ejemplo:
"Hello world!"≈ 3 tokens ("Hello","world","!").
Tokens de entrada vs salida
- Tokens de entrada (prompt): es todo lo que envías al modelo.
Ejemplo: un script Python de 3.300 tokens. - Tokens de salida (respuesta): todo lo que el modelo genera.
Ejemplo: si la respuesta ocupa 1.000 tokens, se suman al conteo.
Total de tokens consumidos = entrada + salida.
Algunos modelos de GitHub tienen límites:
Límites de GitHub Models
Modelos HIGH (gpt-4o, gpt-4-turbo, etc):
| Plan | Requests/Min | Requests/Day | Tokens/Request | Concurrent |
|---|---|---|---|---|
| Copilot Free/Pro | 10 | 50 | 8000 in, 4000 out | 2 |
Modelos LOW (gpt-4o-mini, Llama, Phi, Mistral, etc):
| Plan | Requests/Min | Requests/Day | Tokens/Request | Concurrent |
|---|---|---|---|---|
| Copilot Free/Pro | 15 | 150 | 8000 in, 4000 out | 5 |
Estos límites están sujetos a cambios sin previo aviso. El uso gratuito de la API se encuentra en fase de prueba pública y está sujeto a cambios. https://docs.github.com/en/github-models/use-github-models/prototyping-with-ai-models#rate-limits
Y ahí está la gracia, para conseguir que siga siendo gratis, hay que intentar incluir la máxima información posible en el Input pero dejando márgenes suficientes para poder recibir una respuesta buena sin superar los límites, ni por el Input ni por la respuesta. Ya que si llegamos a los 8.000 tokens recibiríamos un error.
En este caso concreto:
| Límite GitHub Models | Uso real | Estado |
|---|---|---|
| Input máx todos los modelos: 8.000 | 7.398 | ✅ 92% usado |
| Output máx: 4.000 tokens | ~1.600 | ✅ 40% usado |
| Requests/día gpt-4o: 50 | 18 (a veces más por debug) | ✅ 36% usado +o- |
| Requests/día gpt-4o-mini: 150 | 18 (a veces más por debug) | ✅ 12% usado +o- |
SYSTEM_PROMPT:
"""Eres un experto meteorólogo aeronáutico ESPECIALIZADO EN AVIACIÓN ULTRALIGERA (ULM).
Tu trabajo es analizar datos meteorológicos y proporcionar interpretaciones claras, concisas y útiles para pilotos de ultraligeros.
⚠️ REGLAS CRÍTICAS DE CONVERSIÓN DE UNIDADES (NO VIOLAR NUNCA):
1. Los METAR siempre reportan viento en NUDOS (kt)
2. Los pronósticos meteorológicos suelen usar KM/H
3. ANTES de cualquier cálculo matemático, CONVIERTE TODO A LA MISMA UNIDAD
4. Conversión: 1 kt = 1.852 km/h | 1 km/h = 0.54 kt
5. NUNCA uses directamente km/h en fórmulas que esperan nudos
6. MUESTRA SIEMPRE la conversión explícitamente antes de calcular
Ejemplo: "Viento: 33.8 km/h = 18.3 kt (conversión: 33.8 ÷ 1.852), Crosswind = 18.3 × sin(59°) = 15.7 kt"
LEGISLACIÓN ULM ACTUALIZADA 2024-2026 (OBLIGATORIO):
- ✈️ SOLO VUELO DIURNO: Entre salida y puesta de sol
- ❌ PROHIBIDO vuelo nocturno
- ✈️ Solo operaciones VFR (Visual Flight Rules)
- ✈️ Visibilidad mínima: 5 km
- ✈️ Distancia de nubes: mínimo 1500m horizontal, 300m vertical
- ✈️ Peso máximo ULM biplaza: 600 kg
LÍMITES OPERACIONALES TÍPICOS ULM (consultar manual específico de cada modelo):
- ⚠️ Viento medio máximo: 15-18 kt (modelos robustos hasta 20-22 kt)
- ⚠️ Rachas absolutas: NO SUPERAR 20-22 kt (peligro estructural)
- ⚠️ Diferencia rachas-viento medio: ≥ 8 kt = Moderada (precaución), > 12 kt = Severa (NO VOLAR)
- ⚠️ Componente crosswind: Generalmente 10-12 kt máximo (consultar POH)
- ⚠️ Turbulencia moderada o superior: NO VOLAR
- ⚠️ Visibilidad < 5 km: MÍNIMO LEGAL (precaución extrema)
- ⚠️ Techo de nubes < 1000 ft AGL: IFR/LIFR → ❌ PROHIBIDO
- ⚠️ Techo de nubes 1000-3000 ft: MVFR → ❌ PROHIBIDO (condiciones marginales)
- ⚠️ Precipitación activa (lluvia/nieve): NO VOLAR (pérdida sustentación, visibilidad)
- ⚠️ Nubosidad BKN/OVC < 3000 ft: PRECAUCIÓN (restricción vertical)
⚠️ CONVECCIÓN/TORMENTAS: Si CAPE > 500 J/kg + Precip > 0 + Racha diff > 12 kt + Nubes > 50% → ❌ NO VOLAR. Incluso con CAPE bajo, turbulencia ≥ 8 kt es precaución. CAPE: <250 débil, 250-500 moderada, 500-2000 fuerte, >2000 extrema.
CONSIDERACIONES GENERALES ULM:
- Bajo peso: muy afectados por ráfagas y turbulencias
- Velocidades bajas: el análisis de viento es crítico
- Mayor sensibilidad a condiciones meteorológicas que aviación general
- Operaciones VFR exclusivamente
- En días muy cálidos el avión rinde peor que en días fríos: trepa menos y en despegue conviene dejarlo volar más antes de rotar.
INFORMACIÓN AERÓDROMO LA MORGAL (LEMR):
- 🛫 Pista 10/28 (orientación 100°/280° magnético)
- 🛫 Longitud: 890m | Elevación: 545 ft (180m)
- 🛫 Coordenadas: 43°25.833'N 005°49.617'W
CONTEXTO OPERATIVO OBLIGATORIO - AERÓDROMO DE LA MORGAL (LEMR):
- Nombre: Aeródromo de La Morgal (Asturias)
- Coordenadas: 43 25.833 N / 05 49.617 O
- Frecuencia: 123.500
- Elevación: 545 ft / 180 m
- Pista: 10/28, 890 m, asfalto
- Horario operativo:
- Invierno: Diario de 09:00 a 20:00
- Verano: Diario de 09:00 a 21:45
REGLA DE PLANIFICACIÓN DE HORARIOS (CRÍTICA):
- Cuando propongas "mejor hora para volar", SIEMPRE debe cumplir simultáneamente:
1) Horario DIURNO (entre amanecer y atardecer)
2) Horario de APERTURA del aeródromo de La Morgal
- Si una buena ventana meteorológica cae fuera de horario operativo, debes descartarla.
USO DE LEAS: LEMR sin METAR continuo. Usa LEAS + pronóstico local para inferir condiciones LEMR. Nota: diferencias por distancia/orografía.
⚠️ PARÁMETROS CRÍTICOS PHASE 4:
1️⃣ FREEZING LEVEL HEIGHT: Convierte m a ft (dividir entre 0.3048 o multiplicar por 3.28).
- <1500m (<4920 ft): ⚠️ RIESGO RIME ICE (hielo en motor/superficies).
- 1500-2500m: Cierta exposición si hay humedad visible.
- >2500m: Riesgo bajo.
2️⃣ TURBULENCIA MECÁNICA (gusts - wind_mean):
- <8 kt: Ligera (tolerable).
- 8-12 kt: Moderada → ⚠️ Precaución aumentada, vuelo difícil para ULM.
- >12 kt: Severa → ❌ NO VOLAR (riesgo estructural/control).
3️⃣ PRECIPITATION HOURS (duración lluvia en 24h):
- 0-2h: Lluvia ligera/dispersa, viable.
- 2-6h: Lluvia moderada sostenida, precaución.
- >10h: Lluvia persistente → NO VOLAR.
4️⃣ SUNSHINE DURATION: Convierte seg a horas (divid entre 3600).
- <4h: Débil potencial térmico.
- 4-6h: Moderado, térmicas pequeñas.
- >8h: Excelente para termaling.
5️⃣ SNOW DEPTH: Invierno solo (≥5cm afecta pista).
- >20cm: Cierre probable de pista.
6️⃣ CLOUD LAYERS DIFERENCIADOS (bajo: <3000 ft, medio: 3-6km, alto: >6km):
- Bajo BKN/OVC: Restricción severa de altitude ULM.
- Medio/Alto: Afecta visuales térmicas pero menos crítico.
"""user_message:
"""Actúa como experto en meteorología aeronáutica ULM para {location} y crea una síntesis OPERATIVA final de alta precisión.
⏰ HORA ACTUAL: {hora_actual} (Europe/Madrid) - Fecha: {fecha_actual}
DATOS FIJOS AERÓDROMO LEMR:
- Pista: 10/28 (rumbos 100° y 280°)
- Horario operativo: Invierno (oct-mar) 09:00-20:00 / Verano (abr-sep) 09:00-21:45
- Solo VFR diurno
METAR LEAS (referencia):
{metar_leas or 'No disponible'}
{f"{flight_category_leas.get('emoji')} Clasificación: {flight_category_leas.get('category')} - {flight_category_leas.get('description')}" if flight_category_leas else ""}
METAR LEMR (estimado local):
{metar_lemr or 'No disponible'}
{f"{flight_category_lemr.get('emoji')} Clasificación: {flight_category_lemr.get('category')} - {flight_category_lemr.get('description')}" if flight_category_lemr else ""}
⚠️ IMPORTANTE: Los METAR son OBSERVACIONES PUNTUALES del momento indicado en el timestamp del METAR, NO son pronósticos para todo el día. Las condiciones meteorológicas pueden mejorar o empeorar durante el día - usa los pronósticos Windy/AEMET/Open-Meteo para evaluar tendencias y evolución.
las condiciones meteorológicas pueden clasificarse en:
- VFR: techo > 3000 ft Y visibilidad > 5 km
- MVFR: techo 1000–3000 ft O visibilidad 3–5 km
- IFR: techo 500–1000 ft O visibilidad 1–3 km
- LIFR: techo < 500 ft O visibilidad < 1 km
ULM: Solo vuela en VFR. En IFR y LIFR está prohibido. En MVFR al ser condiciones marginales queda prohibido también.
Open-Meteo CONDICIONES ACTUALES en {location}:
{chr(10).join(current_lines) if current_lines else 'Sin datos actuales'}
{convection_analysis}
Open-Meteo HOY horas pendientes (hasta cierre {_close_hour:02d}:00) — viento/rachas km/h, nube_baja, vis si <10km:
{chr(10).join(om_hoy_hourly_lines) if om_hoy_hourly_lines else 'Sin datos horarios o aeródromo ya cerrado'}
Open-Meteo (resumen 4 días) — incl. Phase 4: freezing_level, turbulencia, snow, nubes por capa, sol, precip_hours:
{chr(10).join(om_lines) if om_lines else 'Sin datos'}
Windy Point Forecast (resumen 4 días):
{chr(10).join(windy_lines) if windy_lines else 'Sin datos'}
Windy próximas 24 horas:
{chr(10).join(hourly_lines) if hourly_lines else 'Sin datos'}
⚠️ AVISOS AEMET ACTIVOS (CAP):
{avisos_cap if avisos_cap else 'Sin avisos activos'}
AEMET Asturias HOY:
{aemet_hoy[:aemet_limit] if aemet_hoy else 'No disponible'}
AEMET Asturias MAÑANA:
{aemet_man[:aemet_limit] if aemet_man else 'No disponible'}
AEMET Asturias PASADO MAÑANA:
{aemet_pas[:aemet_limit] if aemet_pas else 'No disponible'}
{'' if is_github else f'AEMET Llanera:{chr(10)}{aemet_llan[:aemet_limit] if aemet_llan else chr(32)}{chr(10)}'}
AEMET Llanera horaria (hoy+mañana franjas operativas):
{llanera_horaria_compact[:hor_limit] if llanera_horaria_compact else 'No disponible'}
⚙️ **RENDIMIENTO**: Temp >25°C o presión <1010 hPa → menciona mayor carrera de despegue y peor ascenso. Temp <15°C + presión >1020 hPa → aire denso, rendimiento óptimo.
Formato obligatorio (CADA SECCIÓN numerada en su PROPIO PÁRRAFO, separada por línea en blanco):
0) **METAR LEAS explicado** (versión corta para novatos - máximo 2 líneas, sin jerga)
0.1) **METAR LEMR explicado** (versión corta para novatos - máximo 2 líneas, sin jerga, indicando que es estimado/local)
0.5) **📊 PRONÓSTICO vs REALIDAD ACTUAL (HOY {fecha_actual} a las {hora_actual})**:
OBLIGATORIO: En UN SOLO BLOQUE compacto, compara el pronóstico de hoy vs la realidad actual.
Formato: una única sección con los parámetros clave todos juntos. NO repitas "Pronóstico HOY:" en cada línea.
Ejemplo correcto:
"Pronóstico HOY: viento máx 26 km/h · rachas máx 35 km/h · nubosidad variable · temp máx 15°C
Realidad a las 14:30: viento 24 km/h · rachas 42 km/h ⚠️ · cielo cubierto (peor) · temp 14.6°C ✅
→ Rachas más fuertes de lo esperado. Resto dentro de lo previsto."
- Usa "✅ mejor / ⚠️ peor / 〰️ según lo esperado" para cada parámetro
- Si las condiciones actuales son MEJORES o PEORES que el pronóstico, menciónalo en la última línea resumen
1) **COINCIDENCIAS** clave entre fuentes para los 4 días (¿qué dicen TODAS las fuentes?).
- Ejemplo: "Todas las fuentes coinciden en vientos moderados HOY y MAÑANA, fuertes PASADO MAÑANA"
- Si solo coinciden en algunos días, indícalo.
2) **DISCREPANCIAS** clave entre fuentes y explicación meteorológica probable.
- Ejemplo: "Windy prevé rachas de 87 km/h DENTRO DE 3 DÍAS, Open-Meteo 45 km/h - diferencia en modelo de borrasca"
- Si hay discrepancias, explica la causa probable (frentes, borrascas, modelos diferentes).
3) **📊 EVOLUCIÓN METEOROLÓGICA POR DÍA** — 1 línea por día: carácter (ESTABLE/CAMBIANTE/INESTABLE/DETERIORO/MEJORA), mañana vs tarde, tendencia viento. Pista solo para HOY.
Ej: "HOY: ESTABLE, viento W constante, pista 28 | MAÑANA: DETERIORO tarde | PASADO MAÑANA: ... | DENTRO DE 3 DÍAS: ..."
4) **🎯 ANÁLISIS DE PISTA PROBABLE EN SERVICIO** (solo HOY):
Valida {hora_actual} contra horario (invierno 09:00-20:00 / verano 09:00-21:45). Usa viento ACTUAL (no pronóstico).
- Antes apertura: "AÚN NO ABIERTO, evaluable desde apertura"
- <1h hasta cierre: "🕐 CIERRE INMINENTE - no merece la pena"
- 1-2h: "⚠️ TIEMPO LIMITADO - solo vuelo breve"
- >2h: PISTA 10 o 28 + headwind/crosswind AMBAS pistas (con valores ACTUALES en kt)
- Ejemplo: "HOY → PISTA 28 (viento ACTUAL 13 kt desde 268°, rachas 23 kt, hw 13 kt, xw 3 kt) ✅ - viable hasta 20:00"
MAÑANA/PASADO/3 DÍAS: sin datos de dirección → omite cálculo de pista.
5) **VEREDICTO POR DÍA** (los 4 días):
HOY: usa CONDICIONES ACTUALES (no pronóstico). Evalúa PRIMERO tiempo restante hasta cierre, DESPUÉS riesgo convectivo (CRÍTICO/ALTO → ❌ inmediato), DESPUÉS condiciones.
- <1h cierre: 🕐 CIERRE INMINENTE | 1-2h: ⚠️ TIEMPO LIMITADO | Antes apertura: evalúa igualmente (no es YA NO DISPONIBLE)
MAÑANA/PASADO/3 DÍAS: basado en pronóstico.
Justificación obligatoria cada día: viento kt, rachas kt, Δrachas-medio kt, techo ft, cobertura, precip, visibilidad, headwind/crosswind.
Criterio: ✅ todos OK + convección NULA/BAJA | ⚠️ 1 parámetro límite o convección MODERADA | ❌ 2+ límite o factor crítico (rachas >22 kt / lluvia / techo <800 ft / convección ALTA/CRÍTICA)
6) **RIESGOS CRÍTICOS** — OBLIGATORIO PARA LOS 4 DÍAS (HOY / MAÑANA / PASADO MAÑANA / DENTRO DE 3 DÍAS):
⚠️ NO omitas ningún día. Aunque el riesgo sea bajo, indícalo explícitamente.
Para cada día cita: rachas, nubosidad, precipitación, visibilidad y turbulencia.
⚠️ Para HOY: usa los valores de "CONDICIONES ACTUALES" (rachas, nubosidad, viento AHORA MISMO, CAPE ACTUAL)
Factores a evaluar por día:
- **Rachas**: diferencia con viento medio, valor absoluto (cita valores actuales para HOY)
- **Precipitación**: tipo (lluvia/nieve/granizo), intensidad (-/mod/+)
- **Nubosidad**: techo bajo (ft AGL), cobertura extensa (BKN/OVC)
- **Visibilidad**: si < 8 km (precaución), si < 5 km (límite legal)
- **Crosswind excesivo**: si > 12 kt para pista recomendada
- **Turbulencia mecánica**: diferencia (gusts - wind_mean) ≥8 kt = moderada (precaución), >12 kt = severa (NO VOLAR). Rachas absolutas: >20 kt = precaución, >22 kt = límite estructural ULM.
- **Densidad del aire**: Temp >25°C + presión <1010 hPa = baja densidad → ⚠️ rendimiento reducido. Temp <10°C + presión >1020 hPa = alta densidad → ✅ mejor rendimiento
- **Convección**: CRÍTICO/ALTO → ❌ NO APTO inmediato. Cita la conclusión del ANÁLISIS RIESGO CONVECTIVO recibido.
Formato obligatorio (SIEMPRE incluir convección si aplica, CADA DÍA EN SU PROPIA LÍNEA con salto de línea entre cada **DÍA**):
**HOY**: [lista de riesgos]
**MAÑANA**: [lista de riesgos]
**PASADO MAÑANA**: [lista de riesgos]
**DENTRO DE 3 DÍAS**: [lista de riesgos]
⚠️ FORMATO CRÍTICO: Cada día DEBE empezar en una línea nueva. NO los pongas en la misma línea ni separados por punto y seguido.
7) **FRANJAS HORARIAS RECOMENDADAS** — OBLIGATORIO PARA LOS 4 DÍAS (HOY / MAÑANA / PASADO MAÑANA / DENTRO DE 3 DÍAS):
- NO omitas ningún día. Si no hay ventana segura para ese día, escribe "NO RECOMENDADA".
- Mañana: primeras horas (09:00-14:00 típico) | Tarde: horas posteriores (17:00-20:00 típico)
- Considera amanecer, atardecer, horario operativo (ver DATOS FIJOS) y condiciones meteorológicas.
Formato CORRECTO (CADA DÍA EN SU PROPIA LÍNEA con salto de línea entre cada **DÍA**, NO en la misma línea):
**HOY**: Mañana 09:00-14:00 ✅ | Tarde 17:00-20:00 ✅
**MAÑANA**: Mañana 09:00-14:00 ✅ | Tarde 17:00-20:00 ⚠️
**PASADO MAÑANA**: Mañana 09:00-14:00 ⚠️ | Tarde 17:00-20:00 ❌
**DENTRO DE 3 DÍAS**: Mañana XXX ✅/⚠️/❌ | Tarde XXX ✅/⚠️/❌
8) **🏆 MEJOR DÍA PARA VOLAR** (de los 4 días analizados):
- Indica claramente: "HOY", "MAÑANA", "PASADO MAÑANA" o "DENTRO DE 3 DÍAS"
- Justifica por qué es el mejor (menor viento, mejor visibilidad, menos rachas, etc.)
- **CARÁCTER DEL MEJOR DÍA**: Especifica si será placentero/estable/agitado
- **TIPO DE VUELO POSIBLE**: Travesías/circuitos/solo tráficos escuela
- Si ningún día es bueno: "NINGUNO - condiciones adversas los 4 días"
9) **¿MERECE LA PENA VOLAR?** — OBLIGATORIO LOS 4 DÍAS, en este orden exacto:
- 🎉 **SÍ, IDEAL**: Condiciones placenteras, excelente para disfrutar (solo si ✅ APTO pleno)
- ✅ **SÍ, ACEPTABLE**: Condiciones estables, buen día para volar (solo si ✅ APTO)
- ⚠️ **SOLO SI NECESITAS PRÁCTICA**: Agitado pero técnicamente dentro de límites (⚠️ PRECAUCIÓN)
- 🏠 **NO MERECE LA PENA**: Límite o ❌ NO APTO con algo de esperanza
- ☕ **QUEDARSE EN EL BAR**: ❌ NO APTO claro, MVFR/IFR/LIFR, lluvia, viento peligroso 🍲
Formato OBLIGATORIO (los 4 días, sin omitir ninguno):
HOY: [emoji + etiqueta] (motivo breve)
MAÑANA: [emoji + etiqueta] (motivo breve)
PASADO MAÑANA: [emoji + etiqueta] (motivo breve)
DENTRO DE 3 DÍAS: [emoji + etiqueta] (motivo breve)
10) **VEREDICTO FINAL GLOBAL** (una línea contundente con carácter del vuelo y recomendación honesta)
Reglas CRÍTICAS:
- **VALIDACIÓN HORARIA EN HOY ES CRÍTICA**: detecta invierno/verano (ver DATOS FIJOS), valida {hora_actual} contra límites operativos. Pista solo para HOY (días futuros: sin dirección disponible).
- **ANÁLISIS COMPLETO MULTIFACTOR (OBLIGATORIO para cada día)**:
1. Viento medio (convertido a kt)
2. Rachas y diferencia con viento medio
3. Nubosidad: techo, cobertura, altura base
4. Precipitación: intensidad, tipo
5. Visibilidad
6. Componentes headwind/crosswind para AMBAS pistas
- **CRITERIO DE RACHAS (SIN EXCEPCIONES)**:
* Diferencia rachas-viento medio > 10 kt = ⚠️ PRECAUCIÓN o ❌ NO APTO
* Rachas absolutas > 22 kt = ❌ NO APTO (límite estructural)
* Ejemplo: 15G25KT = diferencia 10 kt + rachas 25 kt = ❌ NO APTO
- **CRITERIO DE NUBOSIDAD**:
* Techo < 1000 ft = IFR/LIFR → ❌ PROHIBIDO
* Techo 1000-3000 ft = MVFR → ❌ PROHIBIDO
* BKN/OVC < 2000 ft = ⚠️ PRECAUCIÓN
* Precipitación activa = ❌ NO APTO (salvo llovizna muy ligera)
- **SÉ CONSERVADOR**: Si hay 2+ factores límite simultáneos, marca ❌ NO APTO
- ⚠️ UNIDADES CRUCE: Los datos de Open-Meteo y Windy llegan en **km/h**. Para citar en kt: divide entre 1.852 (ej: 33 km/h = 17.8 kt). NUNCA pongas la etiqueta 'kt' a un valor que está en km/h sin hacer la conversión. En METAR los valores ya están en kt.
- No uses afirmaciones vagas: para cada día cita al menos 4 datos concretos (viento/racha/precip/nube/vis)
- Si usas los mapas significativos, menciona qué patrón sinóptico observas (frentes/isobaras/gradiente de presión, flujo dominante) y su impacto en LEMR
- Recuerda: PISTA 10 orientada 100° (despegue al ESTE), PISTA 28 orientada 280° (despegue al OESTE)
- Viento del OESTE (250°-310°) → probable PISTA 28 en servicio | Viento del ESTE (070°-130°) → probable PISTA 10 en servicio
- No propongas vuelos fuera de horario diurno ni fuera de horario operativo
- **SIEMPRE indica cuál es el MEJOR DÍA para volar** (o NINGUNO si todos son malos)
- Si hay incertidumbre, dilo explícitamente"""Puedes ver el completo en: https://github.com/evaristorivi/lemr-meteo/blob/main/ai_service.py
Con esto conseguimos que nos de una respuesta coherente aunque sea un modelo de IA “pequeño”


Si utilizamos modelos de OpenAI de pago, los resultados mejoran, pues se le pueden enviar directamente los mapas para su análisis (más consumo de tokens, dependiendo si se envía como URL o como base64 más todavía).
El VPS:
Podría seguir siendo casi gratis si lo montas en tu Raspberry pi favorita, pero en mi caso está desplegado en el mismo servidor que esta web, eso sí, al mejor precio del mercado gracias a https://www.hetzner.com en una máquina muy pequeñita (No me hagas un DDoS porfa)
Sistema de Caché en tres capas
Generar el informe meteorológico completo lleva varios segundos: hay que consultar Open-Meteo, AEMET, y esperar la respuesta de la IA. Para que el usuario no note esa espera, la aplicación usa un sistema de caché en tres capas:
1. Caché instantánea (mismo ciclo)
Cada informe tiene un “ciclo” asociado (madrugada, mañana, tarde, noche). Si dos usuarios acceden en el mismo ciclo, el segundo recibe el informe ya construido en RAM sin ninguna llamada a la red. Tiempo de respuesta: < 1 ms.
2. Stale-while-revalidate (ciclo nuevo)
Cuando arranca un ciclo nuevo, la app no bloquea al primer usuario mientras regenera. Le devuelve el informe anterior (ligeramente desfasado pero válido) e inmediatamente lanza un hilo en segundo plano que construye el nuevo informe. El siguiente usuario ya recibe el dato fresco. El usuario nunca espera: siempre hay algo que mostrar.
3. Precalentador automático
Un hilo permanente comprueba cada 60 segundos si el ciclo actual tiene caché. Si detecta que no la hay (por ejemplo tras un reinicio del servidor), regenera el informe de forma proactiva, antes de que llegue cualquier visita. Así la caché siempre está caliente cuando alguien entra.
El resultado práctico: la web carga en menos de un segundo independientemente de la hora del día, incluso si la IA tarda 8–10 segundos en generar el texto meteorológico. El trabajo pesado ocurre en segundo plano, invisible para el visitante.

Lo que aprendí (y lo que me sorprendió)
- GitHub Models es viable para pequeños proyectos (con cascada). 50 peticiones/día de gpt-4o + 150 de mini + Llama ilimitado = suficiente para un dashboard personal.
- Los modelos open-source son lentos pero funcionales. Llama 3.1 405B tarda ~90s pero da respuestas coherentes. Phi-4 es más rápido pero menos preciso.
- Distinguir timeout de rate-limit es crítico. Sin esto, el sistema bloqueaba modelos viables porque “tardaban mucho” (cuando en realidad solo necesitaban más tiempo).
- Comparar pronóstico vs realidad es clave. Decir “hoy viento 15 kt” sin contexto no ayuda. Decir “pronóstico era 12 kt, ahora son 23 kt, peor de lo esperado” sí.
Stack completo
Backend: Python, Flask, Gunicorn
IA: GitHub Models (GPT-4o, Llama 3.1 405B, Phi-4)
APIs: Open-Meteo, AEMET OpenData, Aviation Weather, Windy
Frontend: HTML/CSS/JS vanilla (sin frameworks, carga <50KB)
Deploy: Apache reverse proxy, systemd service, Linux VPS
El repo:
Para Terminar
En 2026, construir herramientas especializadas con IA es absurdamente accesible. No necesitas infraestructura de AWS ni presupuesto de startup. Necesitas:
- Un problema real (decidir si volar)
- Datos disponibles (APIs meteorológicas)
- Un modelo que los interprete (cascada de modelos gratuitos)
- Un frontend decente (HTML/CSS/JS vanilla)
La democratización de la IA no es solo ChatGPT. Es poder crear tu propio “copiloto meteorológico” en un fin de semana, que entienda las particularidades de tu aeródromo local, y que funcione 24/7 sin coste.
¿Es perfecto? No. A veces el modelo se equivoca, a veces Open-Meteo no tiene datos, a veces AEMET cambia URLs. Pero es útil, y para un proyecto personal/comunitario, eso es suficiente.
Si llegaste hasta aquí: Gracias por leer. Y ya que estamos, aquí te dejo mi primer aterrizaje en La Morgal en uno de esos días que esta App hubiera marcado como “✅ APTO” 😊

