Metodología
Una explicación honesta de cómo se generan los reportes diarios, qué fuentes usamos, cómo decidimos qué destacar, y qué dejamos afuera.
Cadencia: cada 1 hora
El bot corre cada hora desde una agenda local automatizada (launchd en macOS). Cada corrida es un ciclo numerado (ciclo #N). En cada ciclo el bot:
- Scrapea todas las fuentes oficiales con scrapers determinísticos (HTML estable, sin LLM).
- Normaliza los items a un schema único
{source, title, url, published_at, summary_raw, item_type}. - Pasa los items curados a un agente LLM (Gemini 3.5 Flash) que decide qué destacar.
- El LLM genera un reporte estructurado con frontmatter Jekyll y lo escribe en
_posts/. - Jekyll recompila el sitio estático y nginx lo sirve.
La URL /AAAA/MM/DD/cycle-N/ siempre sirve ese ciclo. La home muestra el más reciente.
Arquitectura
El procesamiento está separado en dos máquinas:
- luz (servidor en Uruguay): aloja el sitio estático en
nginx, ejecuta los scrapers determinísticos. Es la fuente única de verdad para los datos crudos. - Mac local: ejecuta el agente LLM (Gemini 3.5 Flash vía
agy) y deploya el reporte curado de vuelta aluz.
Esto desacopla la captura (rápida, determinística, sin necesidad de LLM) de la curaduría (más lenta, semántica, requiere modelo). Si el LLM falla o se atrasa, los scrapers siguen funcionando y se puede regenerar cualquier ciclo después.
Fuentes
La lista canónica vive en data/sources-extended.yml. Hoy cubrimos 22 fuentes agrupadas:
Legales primarias
- IMPO — Diario Oficial (
impo.com.uy) — leyes promulgadas, decretos del Ejecutivo, resoluciones, avisos oficiales. - Poder Judicial (
poderjudicial.gub.uy) — sentencias de la Suprema Corte, fallos de casación, comunicados judiciales. - Tribunal de Cuentas (
tcr.gub.uy) — resoluciones, observaciones del gasto público, auditorías. - Fiscalía General de la Nación (
gub.uy/fiscalia-general-nacion) — comunicados, formalizaciones, dictámenes. - Corte Electoral (
gub.uy/corte-electoral) — padrón, comicios, resoluciones electorales.
Control y transparencia
- JUTEP — Junta de Transparencia y Ética Pública (declaraciones juradas, conflictos de interés).
- ACCE — Compras Estatales — licitaciones, adjudicaciones, RUPE.
- Catálogo Nacional de Datos Abiertos (AGESIC, vía CKAN) — 2.700+ datasets públicos.
Entes y servicios públicos
- BCU — Banco Central (política monetaria, tasa, comunicados).
- INE — Instituto Nacional de Estadística (IPC, IPP, desempleo, censos).
- DGI — tributos, vencimientos, regímenes.
- ANEP — primaria, media, CODICEN.
- UdelaR — Universidad de la República.
- ANCAP, UTE, BPS, INAU, ASSE — entes y servicios.
Gobierno subnacional
- Intendencia de Montevideo (40% de la población).
- Intendencia de Canelones.
- Intendencia de Maldonado.
- Congreso de Intendentes.
Cuando una fuente falla en un ciclo (404, paywall, error de conexión, anti-bot), aparece en el bloque "Transparencia del ciclo" con el motivo. No la escondemos.
Scrapers determinísticos
Cada fuente tiene un scraper Python específico en scripts/scrapers/<key>.py que:
- Hace una request HTTP con
User-Agent: hoy-luz-uy-bot/1.0 (+https://hoy.luz.uy). - Parsea el HTML con
BeautifulSoupusando selectores específicos del sitio. - Emite items normalizados en JSONL al stdout.
- Falla con exit code != 0 si el sitio cambió de estructura.
Cuando un scraper falla, se crea un ticket en data/cache/broken-scrapers/<key>.json. El próximo ciclo de mantenimiento lanza un agente LLM que inspecciona el HTML actual y propone un selector nuevo. El LLM no participa del scrape, solo del fix cuando algo cambia.
Esto garantiza:
- Reproducibilidad: dos corridas con el mismo HTML producen los mismos items.
- Auditabilidad: el código de cada scraper está versionado y es legible.
- Resiliencia: una fuente caída no contamina las demás.
Cómo se elige qué destacar
El prompt de curaduría está en el repositorio local (~/hoy.luz.uy/prompts/curate.md). En resumen, el LLM debe:
- Elegir 3 a 6 hechos por ciclo, no más.
- Priorizar por impacto cívico real — población afectada, plata pública, cambio normativo, urgencia. No por ruido mediático.
- Etiquetar cada hecho con
policy_area,impact,status,confidence,affected_groups,territory,institutions. - No inventar URLs ni declaraciones. Solo usa lo que viene literal del scrape.
- Cita una sola vez por reporte, máximo 15 palabras, con atribución explícita.
Y excluye:
- Items duplicados o reposteos sin información nueva.
- Reacciones de figuras menores a noticias mayores (se cubre la noticia, no las reacciones).
- Comunicación comercial de entes (ofertas, promociones).
Taxonomía de hechos
Cada hecho lleva una clasificación estructurada:
| Dimensión | Valores |
|---|---|
policy_area |
economía, seguridad, salud, educación, vivienda, trabajo, ambiente, transporte, defensa, RREE, desarrollo social, cultura, tecnología, agro, justicia, institucional |
gov_function |
anuncio, ejecución, regulación, presupuesto, compra pública, nombramiento, convenio, fiscalización, crisis, datos |
impact |
alto, medio, bajo |
status |
anunciado, en trámite, aprobado, implementado, observado, desmentido, pendiente |
confidence |
alta, media, baja |
quality de fuente |
oficial-primaria, documento, prensa, redes |
Estos campos habilitan las tres vistas del sitio: Diaria (curaduría editorial), Por tema (agrupado por área de política), Cronología (línea de tiempo).
Verificación cruzada
En cada ciclo el agente elige a un funcionario y verifica una de sus declaraciones contra la fuente primaria oficial (comunicado de Presidencia, gub.uy, página del ente). El resultado aparece en el bloque "Verificación cruzada" del reporte con uno de estos estados:
- Confirmado — el hecho coincide con la fuente oficial.
- Reportado — apoyado en prensa confiable, sin documento primario aún.
- En disputa — versiones contradictorias en distintas fuentes.
- Pendiente — falta un dato clave que esperamos en próximos ciclos.
- No verificable — fuente primaria no accesible (404, paywall).
- Corregido — había un error en un ciclo anterior, este reporte lo enmienda.
Limitaciones que reconocemos
- Cobertura parcial de Parlamento. El sitio
parlamento.gub.uycambia mucho y tiene anti-bot agresivo. Algunos ciclos pueden no incluir asuntos entrados. - El Poder Judicial es de baja frecuencia. Solo aparecen sentencias o comunicados de alto impacto público.
- No cubrimos todas las intendencias. Por ahora solo Montevideo, Canelones, Maldonado y el Congreso de Intendentes. Las otras 15 quedan pendientes.
- Sesgo de fuente. Las fuentes que cubrimos son todas oficiales o de prensa con posicionamiento conocido. No intentamos "balancear" artificialmente; tratamos de linkear siempre a la fuente primaria para que vos verifiques.
- El bot puede equivocarse. Por eso cada hecho lleva su URL fuente — un click la verifica.
Stack técnico
| Componente | Tecnología |
|---|---|
| Scrapers | Python 3 + requests + BeautifulSoup |
| Orquestador | bash + python (en Mac) |
| LLM curador | Gemini 3.5 Flash vía agy (Antigravity CLI) |
| Sitio estático | Jekyll 4 |
| Servidor | nginx + Let's Encrypt (luz.uy) |
| Agenda | launchd (macOS) cada 3600s |
Código y prompts abiertos. MIT.
¿Listo para leer? Mirá el último reporte →