Todos los proyectos2026En pausa

Pulso Fiscal

Pulso Fiscal es una plataforma abierta para que cualquier persona revise y compare los gastos operacionales que publica el Senado de Chile, con cada cifra trazable hasta la fuente oficial.

MVP con datos del Senado desde 2021: cada dato guarda la URL de la fuente, la fecha de captura y el hash SHA-256. En pausa desde junio de 2026.

Problema

Que un dato sea público no lo hace accesible. El Senado publica los gastos operacionales de cada senador en su página de Transparencia Activa, mes a mes, en un formato difícil de revisar y de comparar. El tema tiene interés público; en marzo de 2026, por ejemplo, BioBioChile publicó un reportaje sobre los senadores que más gastan en traslado.

Quería una herramienta que tomara esos datos tal como los publica el Senado, los ordenara en rankings y tablas, y dejara a la vista de dónde sale cada número, para que cualquiera pueda comprobarlo en vez de creerme.

Restricciones

  • Tiempo. Es un proyecto personal de tiempo libre. El primer commit es del 24 de mayo de 2026 y el último cambio de código, del 19 de junio. Desde entonces está en pausa.
  • Equipo. Lo hice solo.
  • Fuente. Hay una sola: la API JSON pública que consume la página de gastos operacionales del Senado. No está documentada como API, así que puede cambiar de URL o de formato, y los datos pueden venir incompletos.
  • Dominio. Son datos sobre autoridades. El proyecto mide y muestra; no acusa. Un monto alto no implica por sí solo una irregularidad, y no se publican datos personales innecesarios como RUT, domicilio o teléfono.

Decisiones

Solo el Senado, por ahora

La idea de fondo es explicar en lenguaje normal muchos datos del Estado que nadie usa. Abrir varias fuentes a la vez era la alternativa obvia; la descarté y fijé el alcance por escrito en docs/decisiones.md (Senado, gastos operacionales, desde enero de 2021) para evitar cambios de alcance prematuros. La extensión a otros datos públicos del Estado queda abierta para después.

Trazabilidad por dato, con el crudo fuera de Git

Cada respuesta descargada se guarda con su URL fuente, la fecha de captura en UTC y el SHA-256 del cuerpo original, y cada fila del CSV normalizado arrastra esas columnas. Los archivos crudos y procesados van a Cloudflare R2, no al repositorio. El costo es depender de un almacenamiento externo para reproducir un run.

Promedio mensual en vez de total

Comparar montos totales pone arriba a quien tiene más meses publicados. El ranking por defecto usa total_monto / meses_con_datos, contando solo meses con al menos un registro comparable. Las filas que no son seguras de sumar quedan fuera y marcadas: montos vacíos, montos escritos dentro de texto, duplicados por source_id y ajustes negativos, que se reportan aparte. Las categorías se agrupan en un catálogo canónico, porque la fuente escribe la misma categoría de varias formas.

Ningún run se publica sin manifiesto público

La regla está en tres capas. El normalizador corre un quality gate que detiene el run si, por ejemplo, faltan meses en el rango público o hay duplicados con contenido distinto. El loader exige que el manifiesto del run esté publicado en R2, lo descarga y verifica que coincida con el run local antes de abrir la conexión a la base. Y una constraint en Postgres impide que un run marcado ok quede sin URL de manifiesto. No hay atajo local: el runbook lo dice explícitamente. El costo es que sin R2 no se puede cargar nada, ni siquiera para probar.

La web solo ve vistas

Las tablas base tienen RLS activado y ningún permiso para los roles públicos de Supabase. Lo único público son siete vistas sobre el último run correcto y tres funciones SQL agregadas, y la web consulta solo eso. Revertir un run es cambiar su estado a error: las vistas vuelven solas al run anterior. El costo es que cada consulta nueva necesita su vista o función, con su permiso explícito en una migración.

Arquitectura

El código tiene tres partes. etl/ es el pipeline en Python: un scraper que descarga la API y escribe un manifiesto de descarga, un normalizador que genera el CSV limpio, el ranking mensual, las categorías y el reporte de calidad, y un loader que carga Supabase. supabase/migrations/ tiene cinco migraciones versionadas: el esquema base, el cierre del acceso público, las constraints de calidad, permisos por defecto para objetos futuros y la exigencia de manifiesto. web/ es la aplicación Next.js, con el ranking, una ficha por senador y páginas de metodología y fuentes.

El diagrama de abajo muestra el recorrido de un run, del Senado a la web.

Pipeline de Pulso FiscalEl scraper en Python descarga la API JSON de Transparencia Activa del Senado y guarda cada respuesta cruda con su URL fuente, su fecha de captura y su hash SHA-256, más un manifiesto de descarga. El normalizador genera el CSV limpio, el ranking mensual y un reporte de calidad; si el quality gate falla, el run no se publica ni se carga. Los archivos procesados y el manifiesto público se suben a Cloudflare R2. El loader exige que ese manifiesto público sea alcanzable y coincida con el run local antes de cargar Supabase, donde las tablas base están cerradas con RLS y solo son públicas las vistas y funciones del último run correcto. La web en Next.js lee solo esas vistas y funciones.Senado de ChileAPI JSON de Transparencia ActivaScraper (Python)JSON crudo con URL fuente, fechade captura y SHA-256 por respuestaNormalizador y quality gateCSV limpio, ranking y reporte de calidadsi el gate falla, el run se detieneCloudflare R2crudo, procesado y manifiesto públicoSupabase (PostgreSQL)tablas base cerradas con RLSvistas y funciones del último run okWeb Next.jsranking, ficha por senador,metodología y fuentesdescargadownload_manifest.jsonsube procesado y manifiestoloader: exige el manifiesto públicolee solo vistas y funciones
Del Senado a la web: cada paso conserva la trazabilidad y el loader no carga un run sin manifiesto público.

Resultado

Cada fila publicada se puede rastrear hasta la respuesta exacta del Senado que la originó: URL, fecha de captura y SHA-256. Y ningún run llega a la web sin pasar el quality gate y sin un manifiesto público que lo describa.

  • Datos del Senado desde enero de 2021 hasta la última carga.
  • 30 pruebas automáticas en el ETL, para el scraper, el normalizador y las validaciones del loader.
  • Cinco migraciones versionadas, aplicables con la CLI de Supabase.
  • Metodología, fuentes, decisiones y runbook de publicación y rollback escritos en docs/.
  • Código bajo MIT y datos publicados bajo CC BY 4.0.

Qué cambiaría

  • Publicarlo. El MVP no tiene una versión pública: el dominio y el hosting quedaron diferidos. Es lo primero que haría al retomarlo.
  • La carga es manual. Hoy un run se publica corriendo dos comandos a mano. Lo programaría, con el mismo quality gate y el mismo requisito de manifiesto.
  • Constraints sin validar. Las de calidad se agregaron como NOT VALID para no bloquear datos ya cargados; Postgres las exige solo en filas nuevas o actualizadas. Después de recargar desde cero, las validaría.
  • La web no tiene pruebas. El ETL sí; la web solo tiene typecheck. Agregaría pruebas de las consultas y de los estados de error, que hoy reviso a mano.
  • Detectar cambios en la fuente. Si el Senado cambia el formato de su API, hoy lo noto recién al correr el pipeline. Una prueba programada contra la fuente lo avisaría antes.

Cómo verificar esto

  • Código: github.com/mansep12/pulso-fiscal, código MIT y datos CC BY 4.0. El historial muestra las fechas de trabajo y la pausa.
  • Trazabilidad: el scraper (etl/src/pulso_fiscal/scrapers/senado_gastos_operacionales.py) guarda URL, fecha y SHA-256 por respuesta; el quality gate está en enforce_quality_gate, en el normalizador; el loader (etl/src/pulso_fiscal/loaders/supabase.py) valida el manifiesto público antes de conectarse.
  • Base de datos: supabase/migrations/20260603000000_lockdown_public_access.sql cierra las tablas y 20260605020000_require_public_manifest_for_ok_runs.sql agrega la constraint del manifiesto.
  • Correrlo: desde etl/, uv sync --group dev instala el entorno y uv run senado-gastos-operacionales --max-periods 1 descarga el primer periodo disponible desde la fuente oficial. Las pruebas están en etl/tests y corren con pytest.
  • Fuente oficial: senado.cl/transparencia/gastos-operacionales-senadores, para comparar cualquier cifra con su origen.
  • Sin demo: no hay una versión publicada; el repositorio no enlaza ningún despliegue.