⚡ UNE Unwrapped - Resumen Eléctrico de La Habana
See the codeUna plataforma interactiva para visualizar y analizar el resumen anual de la situación eléctrica en La Habana, alimentada por el canal oficial de Telegram de la UNE y enriquecida con un pipeline de IA local que clasifica cada mensaje y extrae metadata estructurada.

Si plataformas como Spotify, GitHub o YouTube tienen su propio resumen anual para mostrar estadísticas, siempre me pregunté por qué la UNE no podría tener algo parecido. Con esa idea en mente, decidí crear este proyecto para visualizar los datos del servicio eléctrico en La Habana de una forma más clara y accesible.
El proceso empieza scrapeando el canal de Telegram de la UNE en La Habana, de donde se extrae todo el histórico de mensajes con sus respectivos metadatos. Después, una capa de IA local (sin APIs externas) clasifica cada mensaje en una de 15 categorías y extrae metadata estructurada — bloques afectados, provincias, municipios, MW de déficit, horarios, unidades termoeléctricas mencionadas, severidad del evento, etc. Toda esa información se combina con heurísticas deterministas (regex calibradas) para generar análisis anuales en formato JSON, que son el motor de la web.
Finalmente, una aplicación web con estética neobrutalista presenta esos resultados en más de 30 visualizaciones interactivas — desde un Health Score del año al estilo wrapped, pasando por radares por bloque, calendarios de severidad, mapas de afectación geográfica, hasta un Apagómetro que predice basado en el ritmo histórico. Está disponible en une-unwrapped.vercel.app.
El proyecto está montado como un monorepo donde conviven backend (Python)
y frontend (React). El backend scrapea Telegram, persiste los mensajes en
SQLite, los procesa con IA y genera los .json anuales. El frontend, en React
19 + Vite + Tailwind 4, los consume y los renderiza con animaciones de
framer-motion sobre una identidad visual fuerte y directa.
La aplicación está desplegada y disponible en: une-unwrapped.vercel.app
Sincronización automática cada hora vía GitHub Actions (.github/workflows/sync.yml):
nuevos mensajes → IA incremental (200 msgs/run) → analyzer → commit del JSON +
SQLite → deploy en Vercel.
Telegram (UNE Habana)
│
▼
┌──────────────────┐
│ Telethon scraper │ → core/scrapper.py
└──────────────────┘
│
▼
┌──────────────────┐
│ SQLite (messages) │ → telegram_messages.db
└──────────────────┘
│
▼
┌────────────────────────────────────┐
│ Capa de IA — core/ai/ │
│ • zero-shot mDeBERTa (clasifica) │
│ • NER BETO español (extrae) │
│ • gazetteer (provincias/CTEs) │
│ • regex extractors (MW, horarios) │
│ • severity / event_type / summary │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────┐
│ SQLite (message_ai_analysis) │
└────────────────────────────────┘
│
▼
┌────────────────────────────────┐
│ Analyzer — core/analyzer.py │
│ combina datos legacy + AI │
└────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ app/public/data/analysis_<year>.json│
└─────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ React + Vite (frontend) │ → 30+ visualizaciones
└────────────────────────┘
core/ai/)A diferencia del análisis original (que dependía solo de regex frágiles), ahora cada mensaje pasa por un pipeline de IA 100% local — sin APIs externas, sin costos por uso, sin enviar datos a terceros:
| Etapa | Modelo / técnica | Propósito |
|---|---|---|
| Clasificación | MoritzLaurer/mDeBERTa-v3-base-mnli-xnli (zero-shot) | Asigna 1 de 15 categorías + subcategorías |
| NER | mrm8488/bert-spanish-cased-finetuned-ner (BETO) | Detecta lugares, organizaciones, personas |
| Gazetteer | Catálogos hardcoded | Resuelve provincias cubanas, municipios habaneros, centrales termoeléctricas y sus unidades |
| Regex extractors | Determinista | MW (déficit/demanda/disponibilidad), horarios (10:00 a 14:00), bloques (afectados vs recuperados) |
| Heurísticas | Plantillas | Deriva severity (low/medium/high/critical), event_type y un summary resumido |
sen_failure, sen_recovery, block_affectation, block_recovery,
circuit_failure, zone_outage, zone_recovery, daily_resume,
daily_forecast, daf (Disparado Automático por Frecuencia),
thermal_unit_status, scheduled_maintenance, weather_impact,
apology_communication, general_info.
Definidas en core/ai/taxonomy.py con su hipótesis
en español usada por el zero-shot.
| Backend | Cuándo se usa | Velocidad típica | Memoria |
|---|---|---|---|
| PyTorch + CUDA fp16 | GPU disponible (Colab T4, máquina con CUDA) | ~5–10 ms/msg | ~2 GB VRAM |
| ONNX Runtime int8 | Solo CPU (GitHub Actions, laptops sin GPU) | ~250 ms/msg | ~1.5 GB RAM |
Se puede forzar vía la variable de entorno UNE_AI_BACKEND=gpu|cpu.
La cuantización int8 se hace una vez en el primer arranque y se cachea.
core/analyzer.py genera los JSON anuales con:
Agrupadas en 7 secciones (ver el NavigationHub):
Stack frontend: React 19, Vite 7.3, TypeScript 5, Tailwind 4,
framer-motion 12, Recharts, lucide-react, PWA con vite-plugin-pwa,
sitemap auto-generado en build.
[!WARNING] Los resúmenes anuales no deben tomarse como oficialmente válidos. Son una reconstrucción aproximada basada en un canal de comunicación pública, no en datos operativos reales del SEN. Úsalos con fines informativos / de entretenimiento, no para decisiones técnicas.
Aunque la capa de IA mejora considerablemente la extracción comparada con el parser regex original, la fuente sigue siendo la misma — los mensajes de Telegram de UNE Habana — y arrastra las inconsistencias propias de esa fuente:
En consecuencia: el tiempo total sin luz, el conteo de afectaciones por
bloque, la geografía de afectados y los rankings de centrales termoeléctricas
son estimaciones. Pueden errar tanto por encima como por debajo del valor
real, y algunas categorías (sobre todo las de baja frecuencia, como
weather_impact o daf) tienen mayor incertidumbre que otras.
Si te interesa mejorar la calidad de los datos — afinar la taxonomía, ampliar el gazetteer, añadir reglas de reconciliación, etiquetar manualmente un eval set más robusto — los PRs son bienvenidos.
Si este resumen te ha parecido interesante, útil o simplemente te gusta la iniciativa, ¡tu apoyo significa mucho!
uv ~= 0.9.xClonar el repositorio
git clone https://github.com/EduardoProfe666/une-unwrapped-habana.git
cd une-unwrapped-habana
Instalar dependencias
uv sync
Esto instala telethon, transformers, optimum[onnxruntime],
onnxruntime, torch, huggingface_hub, etc.
Obtener credenciales para Telethon
Sigue la guía oficial para conseguir API_ID y API_HASH.
Generar el API_SESSION
uv run python core/session_manager.py
Introduce el número con prefijo (ej. +53XXXXXXXX). Copia el string que
imprime — ese es tu API_SESSION.
Configurar variables de entorno
Crea un .env en la raíz con las variables de .env.example.
Sincronizar mensajes + IA + análisis
uv run python main.py
El flujo de main.py es:
process_latest_messages() — scraper incremental.process_pending_ai_analysis(max_messages=200) — IA incremental (cap por SLA).analyze_data(year) por cada año — regenera los JSON.Si tu BD está vacía o muy desactualizada, descomenta esta línea en main.py:
# process_all_messages()
La capa de IA vive en core/ai/ y se ejecuta automáticamente al
correr main.py. Si quieres correrla aislada — útil para backfill histórico
o para procesar un año específico:
Backfill local (CPU, ONNX int8):
uv run python scripts/backfill_ai.py --year 2024 --max-messages 500 --batch-size 64
Backfill completo en Google Colab GPU (recomendado para histórico de ~60k mensajes):
notebooks/backfill_ai_colab.ipynb en Colab.Entorno de ejecución → GPU (T4).telegram_messages.db y los JSONs.python scripts/apply_colab_results.py /ruta/a/une_backfill_<ts>.zip
git diff y commitea cuando estés conforme.Backfill desde GitHub Actions:
.github/workflows/backfill-ai.yml (manual via Run workflow). Soporta matrix por año (2022–2026) o un año específico, con cache HuggingFace para evitar re-descargas..github/workflows/sync.yml corre el procesamiento incremental cada hora.Evaluación cualitativa del modelo:
uv run python -m core.ai.eval
Corre un set anotado manualmente y reporta precisión/recall por campo.
Entrar a la app
cd app
Instalar dependencias
bun install
Configurar variables de entorno
Crea un .env con las variables de .env.example.
Iniciar el servidor de desarrollo
bun dev
Acceder http://localhost:5173
Build de producción
bun run build
Esto genera el sitemap, hace el build de Vite y deja todo en dist/.
Las contribuciones son bienvenidas. Para contribuir:
git checkout -b feature/amazing-feature).git commit -m 'Add some amazing feature').git push origin feature/amazing-feature).Áreas con mucho margen de mejora:
Este proyecto está licenciado bajo la Licencia MIT — ver LICENSE.
⚡ UNE Unwrapped - Resumen Eléctrico de La Habana
See the codeUna plataforma interactiva para visualizar y analizar el resumen anual de la situación eléctrica en La Habana, alimentada por el canal oficial de Telegram de la UNE y enriquecida con un pipeline de IA local que clasifica cada mensaje y extrae metadata estructurada.

Si plataformas como Spotify, GitHub o YouTube tienen su propio resumen anual para mostrar estadísticas, siempre me pregunté por qué la UNE no podría tener algo parecido. Con esa idea en mente, decidí crear este proyecto para visualizar los datos del servicio eléctrico en La Habana de una forma más clara y accesible.
El proceso empieza scrapeando el canal de Telegram de la UNE en La Habana, de donde se extrae todo el histórico de mensajes con sus respectivos metadatos. Después, una capa de IA local (sin APIs externas) clasifica cada mensaje en una de 15 categorías y extrae metadata estructurada — bloques afectados, provincias, municipios, MW de déficit, horarios, unidades termoeléctricas mencionadas, severidad del evento, etc. Toda esa información se combina con heurísticas deterministas (regex calibradas) para generar análisis anuales en formato JSON, que son el motor de la web.
Finalmente, una aplicación web con estética neobrutalista presenta esos resultados en más de 30 visualizaciones interactivas — desde un Health Score del año al estilo wrapped, pasando por radares por bloque, calendarios de severidad, mapas de afectación geográfica, hasta un Apagómetro que predice basado en el ritmo histórico. Está disponible en une-unwrapped.vercel.app.
El proyecto está montado como un monorepo donde conviven backend (Python)
y frontend (React). El backend scrapea Telegram, persiste los mensajes en
SQLite, los procesa con IA y genera los .json anuales. El frontend, en React
19 + Vite + Tailwind 4, los consume y los renderiza con animaciones de
framer-motion sobre una identidad visual fuerte y directa.
La aplicación está desplegada y disponible en: une-unwrapped.vercel.app
Sincronización automática cada hora vía GitHub Actions (.github/workflows/sync.yml):
nuevos mensajes → IA incremental (200 msgs/run) → analyzer → commit del JSON +
SQLite → deploy en Vercel.
Telegram (UNE Habana)
│
▼
┌──────────────────┐
│ Telethon scraper │ → core/scrapper.py
└──────────────────┘
│
▼
┌──────────────────┐
│ SQLite (messages) │ → telegram_messages.db
└──────────────────┘
│
▼
┌────────────────────────────────────┐
│ Capa de IA — core/ai/ │
│ • zero-shot mDeBERTa (clasifica) │
│ • NER BETO español (extrae) │
│ • gazetteer (provincias/CTEs) │
│ • regex extractors (MW, horarios) │
│ • severity / event_type / summary │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────┐
│ SQLite (message_ai_analysis) │
└────────────────────────────────┘
│
▼
┌────────────────────────────────┐
│ Analyzer — core/analyzer.py │
│ combina datos legacy + AI │
└────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ app/public/data/analysis_<year>.json│
└─────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ React + Vite (frontend) │ → 30+ visualizaciones
└────────────────────────┘
core/ai/)A diferencia del análisis original (que dependía solo de regex frágiles), ahora cada mensaje pasa por un pipeline de IA 100% local — sin APIs externas, sin costos por uso, sin enviar datos a terceros:
| Etapa | Modelo / técnica | Propósito |
|---|---|---|
| Clasificación | MoritzLaurer/mDeBERTa-v3-base-mnli-xnli (zero-shot) | Asigna 1 de 15 categorías + subcategorías |
| NER | mrm8488/bert-spanish-cased-finetuned-ner (BETO) | Detecta lugares, organizaciones, personas |
| Gazetteer | Catálogos hardcoded | Resuelve provincias cubanas, municipios habaneros, centrales termoeléctricas y sus unidades |
| Regex extractors | Determinista | MW (déficit/demanda/disponibilidad), horarios (10:00 a 14:00), bloques (afectados vs recuperados) |
| Heurísticas | Plantillas | Deriva severity (low/medium/high/critical), event_type y un summary resumido |
sen_failure, sen_recovery, block_affectation, block_recovery,
circuit_failure, zone_outage, zone_recovery, daily_resume,
daily_forecast, daf (Disparado Automático por Frecuencia),
thermal_unit_status, scheduled_maintenance, weather_impact,
apology_communication, general_info.
Definidas en core/ai/taxonomy.py con su hipótesis
en español usada por el zero-shot.
| Backend | Cuándo se usa | Velocidad típica | Memoria |
|---|---|---|---|
| PyTorch + CUDA fp16 | GPU disponible (Colab T4, máquina con CUDA) | ~5–10 ms/msg | ~2 GB VRAM |
| ONNX Runtime int8 | Solo CPU (GitHub Actions, laptops sin GPU) | ~250 ms/msg | ~1.5 GB RAM |
Se puede forzar vía la variable de entorno UNE_AI_BACKEND=gpu|cpu.
La cuantización int8 se hace una vez en el primer arranque y se cachea.
core/analyzer.py genera los JSON anuales con:
Agrupadas en 7 secciones (ver el NavigationHub):
Stack frontend: React 19, Vite 7.3, TypeScript 5, Tailwind 4,
framer-motion 12, Recharts, lucide-react, PWA con vite-plugin-pwa,
sitemap auto-generado en build.
[!WARNING] Los resúmenes anuales no deben tomarse como oficialmente válidos. Son una reconstrucción aproximada basada en un canal de comunicación pública, no en datos operativos reales del SEN. Úsalos con fines informativos / de entretenimiento, no para decisiones técnicas.
Aunque la capa de IA mejora considerablemente la extracción comparada con el parser regex original, la fuente sigue siendo la misma — los mensajes de Telegram de UNE Habana — y arrastra las inconsistencias propias de esa fuente:
En consecuencia: el tiempo total sin luz, el conteo de afectaciones por
bloque, la geografía de afectados y los rankings de centrales termoeléctricas
son estimaciones. Pueden errar tanto por encima como por debajo del valor
real, y algunas categorías (sobre todo las de baja frecuencia, como
weather_impact o daf) tienen mayor incertidumbre que otras.
Si te interesa mejorar la calidad de los datos — afinar la taxonomía, ampliar el gazetteer, añadir reglas de reconciliación, etiquetar manualmente un eval set más robusto — los PRs son bienvenidos.
Si este resumen te ha parecido interesante, útil o simplemente te gusta la iniciativa, ¡tu apoyo significa mucho!
uv ~= 0.9.xClonar el repositorio
git clone https://github.com/EduardoProfe666/une-unwrapped-habana.git
cd une-unwrapped-habana
Instalar dependencias
uv sync
Esto instala telethon, transformers, optimum[onnxruntime],
onnxruntime, torch, huggingface_hub, etc.
Obtener credenciales para Telethon
Sigue la guía oficial para conseguir API_ID y API_HASH.
Generar el API_SESSION
uv run python core/session_manager.py
Introduce el número con prefijo (ej. +53XXXXXXXX). Copia el string que
imprime — ese es tu API_SESSION.
Configurar variables de entorno
Crea un .env en la raíz con las variables de .env.example.
Sincronizar mensajes + IA + análisis
uv run python main.py
El flujo de main.py es:
process_latest_messages() — scraper incremental.process_pending_ai_analysis(max_messages=200) — IA incremental (cap por SLA).analyze_data(year) por cada año — regenera los JSON.Si tu BD está vacía o muy desactualizada, descomenta esta línea en main.py:
# process_all_messages()
La capa de IA vive en core/ai/ y se ejecuta automáticamente al
correr main.py. Si quieres correrla aislada — útil para backfill histórico
o para procesar un año específico:
Backfill local (CPU, ONNX int8):
uv run python scripts/backfill_ai.py --year 2024 --max-messages 500 --batch-size 64
Backfill completo en Google Colab GPU (recomendado para histórico de ~60k mensajes):
notebooks/backfill_ai_colab.ipynb en Colab.Entorno de ejecución → GPU (T4).telegram_messages.db y los JSONs.python scripts/apply_colab_results.py /ruta/a/une_backfill_<ts>.zip
git diff y commitea cuando estés conforme.Backfill desde GitHub Actions:
.github/workflows/backfill-ai.yml (manual via Run workflow). Soporta matrix por año (2022–2026) o un año específico, con cache HuggingFace para evitar re-descargas..github/workflows/sync.yml corre el procesamiento incremental cada hora.Evaluación cualitativa del modelo:
uv run python -m core.ai.eval
Corre un set anotado manualmente y reporta precisión/recall por campo.
Entrar a la app
cd app
Instalar dependencias
bun install
Configurar variables de entorno
Crea un .env con las variables de .env.example.
Iniciar el servidor de desarrollo
bun dev
Acceder http://localhost:5173
Build de producción
bun run build
Esto genera el sitemap, hace el build de Vite y deja todo en dist/.
Las contribuciones son bienvenidas. Para contribuir:
git checkout -b feature/amazing-feature).git commit -m 'Add some amazing feature').git push origin feature/amazing-feature).Áreas con mucho margen de mejora:
Este proyecto está licenciado bajo la Licencia MIT — ver LICENSE.