VeedurIA es una capa cívica de lectura pública del poder en Colombia.
No intenta reemplazar la fuente oficial. La reorganiza para que periodistas, veedurías, ONG, academia y ciudadanía lleguen más rápido a tres preguntas:
- ¿Qué contrato conviene abrir primero?
- ¿Cómo votó un legislador frente a lo que prometía defender?
- ¿Qué red de relaciones vale la pena seguir después?
La app vive hoy en: https://veeduria.vercel.app
- Qué incluye hoy
- Arquitectura del repo
- VotóMeter
- Legal, privacidad y seguridad
- Desarrollo local
- Base de datos y bootstrap
- Variables de entorno
- Ingesta y sincronización
- Backoffice de revisión
- Pruebas y verificación
- Deploy de staging / producción
- Datos y fallback
- Archivos clave
- Limitaciones / TODO
Lee contratación pública, permite filtrar el corte visible y ordena qué revisar primero.
Incluye:
- filtros por texto, territorio, riesgo, modalidad y fechas
- mapa de riesgo por departamento
- caso principal con factores explicativos
- comparativos del mismo corte
- explorador y sandbox exportable
- salto directo al expediente oficial en SECOP II
El puntaje es una señal de prioridad, no una acusación.
Directorio vivo de legisladores con roster, votos, asistencia y coherencia pública solo donde haya promesas revisadas.
Incluye:
- directorio SSR con filtros por URL y paginación server-side
- perfiles individuales por legislador
- API pública desde Next.js sobre Supabase
- backoffice mínimo para revisar promesas y colisiones de identidad
- sincronización automática desde datos.gov.co y Senado abierto
La coherencia visible solo aparece cuando existe una promesa revisada en backoffice.
Es la siguiente capa del producto.
Hoy muestra:
- red interactiva de entidades, proveedores y clusters territoriales
- filtros por confianza, departamento y tipo de nodo
- nodos sugeridos para empezar la lectura
- paneles de concentración y evidencia
- advertencia explícita de que la alerta no prueba irregularidad
El objetivo final es conectar contratistas, donantes, funcionarios, votaciones y aprobaciones presupuestales dentro de una misma lectura.
La ruta /etica-y-privacidad documenta el alcance real del producto:
- El landing enlaza esta política desde el bloque de impacto para que el uso responsable sea visible antes de navegar módulos.
- VeedurIA muestra señales revisables, no conclusiones jurídicas.
- Los puntajes no declaran culpabilidad, sanción, hallazgo fiscal ni irregularidad comprobada.
- El usuario debe verificar SECOP, datos.gov.co, Senado abierto y documentos primarios antes de publicar, denunciar o tomar decisiones.
- La plataforma no solicita datos sensibles y no debe usarse para doxxing, acoso, extracción masiva de datos personales o decisiones de alto impacto.
- La página no elimina todo riesgo legal; deja límites claros y remite a asesoría profesional cuando corresponda.
El repo está dividido para mantener producto web y capa analítica sin mezclar responsabilidades:
web/Next.js App Router. Aquí vive la interfaz pública, navegación, OG images, estilos y módulos.backend/FastAPI entrypoint para el frontend web.src/servicios Python, lectura de datos, scoring y lógica analítica.scripts/bootstrap SQL y scripts operativos.data/artefactos procesados, metadata del modelo y archivos de referencia.
VotóMeter es el módulo legislativo de VeedurIA. En su MVP actual:
- usa Supabase como fuente pública de datos en producción
- sirve el directorio principal desde
/votometro - sirve perfiles individuales desde
/votometro/legislador/[slug] - expone API pública bajo
/api/votometro/* - mantiene un panel interno mínimo en
/votometro/review
- Esquema y storage
El bootstrap está en
scripts/setup_supabase.sql. Ahí se crean las tablas de VotóMeter, vistas públicas y el bucketvotometro-source-snapshots. - Capa server-side
La lógica de lectura, filtros, DTOs y composición SSR vive en
web/lib/votometro-server.ts. - Rutas públicas
La UI SSR vive en
web/app/votometro/page.tsxyweb/app/votometro/legislador/[slug]/page.tsx. - API pública
Los route handlers viven en
web/app/api/votometro/. - Review / backoffice
La UI está en
web/app/votometro/review/page.tsxy la autenticación ligera se apoya enweb/lib/votometro-admin.ts. - Ingesta
Los adapters y el sync están en
src/ingestion/votometro/. - Orquestación
La sincronización programada se define en
.github/workflows/votometro_sync.yml.
UI:
/votometro/votometro/legislador/[slug]/votometro/review
API pública:
GET /api/votometro/legislatorsGET /api/votometro/legislators/[slug]GET /api/votometro/votesGET /api/votometro/partiesGET /api/votometro/topics
Handlers internos de review:
POST /api/votometro/review/loginPOST /api/votometro/review/logoutPOST /api/votometro/review/promises/[id]POST /api/votometro/review/conflicts/[id]
Rutas públicas:
/etica-y-privacidad?lang=es/etica-y-privacidad?lang=en/legal?lang=esredirige a la página principal legal.
La página cubre naturaleza del servicio, fuentes y verificación, protección de datos personales, seguridad, uso permitido, responsabilidad del usuario y alcance legal realista. No se presenta como blindaje jurídico absoluto: su propósito es evitar que señales analíticas se conviertan en acusaciones sin verificación.
En ContratoLimpio, la frescura de datos se obtiene por GET /api/contracts/freshness, con cache: no-store, consultando SECOP/datos.gov.co en vivo y comparando esa fecha contra el último scoring local.
- Python 3.11+
- Node.js 20+
- un proyecto Supabase accesible
Backend / scripts Python:
pip install -r requirements.txtFrontend:
cd web
npm install
cd ..No es obligatorio para el directorio SSR de VotóMeter, pero sigue siendo útil para módulos heredados y servicios Python.
Desde la raíz del repo:
uvicorn backend.main:app --reload --port 8000O usando el script del repo:
npm run dev:apiDesde la raíz del repo:
cd web
NEXT_PUBLIC_APP_URL=http://localhost:3000 \
NEXT_PUBLIC_SITE_URL=http://localhost:3000 \
NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000 \
npm run devO usando el script del repo:
npm run dev:webAbrir:
http://localhost:3000Antes de esperar datos reales en /votometro:
- Ejecuta
scripts/setup_supabase.sqlen tu proyecto Supabase local, staging o dev. - Define las variables de entorno de Supabase indicadas abajo.
- Corre un sync manual:
python scripts/sync_votometro.py --mode=dailyTambién puedes llamar el módulo directamente:
python -m src.ingestion.votometro.sync --mode=dailyVotóMeter no se considera listo para producción hasta que el esquema SQL haya sido aplicado en el proyecto Supabase destino.
- Abre el SQL Editor del proyecto Supabase de staging o producción.
- Ejecuta por completo:
scripts/setup_supabase.sql- Verifica que queden creadas:
- tablas de VotóMeter como
legislators,legislator_terms,projects,vote_events,vote_records,attendance_records,legislator_metrics_current,party_metrics_current,promise_claims,promise_reviews,promise_vote_matches,identity_conflicts,ingestion_runs - vistas públicas como
votometro_directory_public,votometro_vote_records_publicyvotometro_approved_promises_public - bucket de storage
votometro-source-snapshots
Hoy no hay un sistema separado de migraciones para VotóMeter. El contrato operativo es:
- editar
scripts/setup_supabase.sql - aplicar el cambio explícitamente en Supabase
- sincronizar staging antes de producción
Si cambias el schema, documenta ese cambio en el PR y vuelve a correr los checks de build/tests.
Las variables más relevantes para operar VotóMeter son:
| Variable | Dónde aplica | Obligatoria | Uso |
|---|---|---|---|
SUPABASE_URL |
frontend, backend, sync, CI | Sí | URL base del proyecto Supabase |
SUPABASE_ANON_KEY |
Next.js público | Sí para /api/votometro/* y lecturas públicas |
la usa createServerSupabase() para lecturas con RLS |
SUPABASE_SERVICE_KEY |
Next.js server-side / admin | Sí en staging/prod | la usa createServiceSupabase() para review y operaciones internas |
SUPABASE_KEY |
sync jobs, fallback server-side, scripts Python | Sí hoy para sync/CI | el pipeline Python y algunos fallbacks lo siguen usando; también es fallback del service client |
VOTOMETRO_REVIEW_PASSWORD |
frontend | Sí si vas a usar /votometro/review |
password del panel de revisión |
SOCRATA_APP_TOKEN |
sync jobs | Recomendado | mejora cuota/estabilidad al leer Cámara desde datos.gov.co |
VOTOMETRO_STORAGE_BUCKET |
sync jobs | Opcional | bucket para snapshots raw; si no existe, usa votometro-source-snapshots |
NEXT_PUBLIC_APP_URL |
frontend | Recomendado | base URL usada por fetches server-side al llamar route handlers internos |
NEXT_PUBLIC_SITE_URL |
frontend | Recomendado | canonical URLs y metadata |
NEXT_PUBLIC_API_BASE_URL |
frontend | Opcional para VotóMeter, útil para módulos heredados | base del backend FastAPI |
CRON_SECRET |
Vercel producción | Sí para cron jobs | valida que solo Vercel pueda invocar /api/cron/* |
GITHUB_ACTIONS_REPOSITORY |
Vercel producción | Sí para cron de contratos | repo destino para disparar secop_ingestion.yml |
GITHUB_ACTIONS_REF |
Vercel producción | Recomendado | branch/ref que debe ejecutar el workflow; si falta, usa VERCEL_GIT_COMMIT_REF y luego main |
GITHUB_ACTIONS_DISPATCH_TOKEN |
Vercel producción | Sí para cron de contratos | token GitHub con permiso para actions:write sobre el repo |
Notas importantes:
- El prompt de despliegue debe tratar
SUPABASE_SERVICE_KEYcomo la credencial correcta para server-side / admin. - En el código actual,
SUPABASE_KEYsigue siendo una variable operativa real para scripts Python y como fallback del service client. - Si más adelante introduces acceso browser-side directo a Supabase, documenta explícitamente si se apoya en
SUPABASE_ANON_KEYo en otra convención. Hoy las rutas públicas de VotóMeter pasan por Next.js. - El refresh diario de ContratoLimpio ya no depende del scheduler de GitHub Actions. La fuente de verdad pasa a ser el cron de producción en Vercel, que dispara el workflow pesado por
workflow_dispatch.
El refresh diario de contratos queda dividido en dos capas:
- Vercel Cron
web/vercel.jsonprograma una llamada diaria a/api/cron/contracts-refresha las05:00 UTC(12:00 a. m. Colombia). - Route handler seguro
web/app/api/cron/contracts-refresh/route.tsvalidaCRON_SECRETy dispara el workflowsecop_ingestion.ymlvía GitHub Actions. - Workflow pesado
.github/workflows/secop_ingestion.ymltambién conserva un cron diario de respaldo y el disparo manual. Ahí vive la ingesta de SECOP, re-score del modelo, upload del parquet y upsert a Supabase.
Este split evita intentar correr la ETL Python dentro del límite de ejecución de Vercel. Si la app muestra una brecha entre SECOP y scoring, significa que la fuente oficial avanzó pero la tabla puntuada publicada por el pipeline todavía no cerró ese corte.
El sync del MVP usa estas fuentes:
- Cámara
datos.gov.covía SODA / Socrata - Senado
https://app.senado.gov.co/open_data/ - Backfill semanal gancho preparado para Cámara / Congreso Visible, todavía incompleto
src/ingestion/votometro/sync.py:
- descarga el roster visible de Cámara y Senado
- normaliza nombres y slugs
- calcula votos y asistencia del Senado abierto
- calcula agregados por legislador y por partido
- sube snapshots raw a Supabase Storage
- registra corridas en
ingestion_runs - no reemplaza la capa pública si detecta una caída de cobertura mayor al 10%
Sync diario:
python scripts/sync_votometro.py --mode=dailySync semanal:
python scripts/sync_votometro.py --mode=weeklyTambién sirve:
python -m src.ingestion.votometro.sync --mode=daily
python -m src.ingestion.votometro.sync --mode=weeklyArchivo:
.github/workflows/votometro_sync.yml
Comportamiento actual:
- cron diario:
30 5 * * * - cron semanal:
0 6 * * 0 workflow_dispatchconmode=daily|weekly- instala
requirements-phase1.txt - ejecuta
python -m src.ingestion.votometro.sync --mode=...
Secrets mínimos que deben existir en GitHub Actions:
SUPABASE_URLSUPABASE_KEY
Secret recomendado:
SOCRATA_APP_TOKEN
Variables mínimas que deben existir en Vercel producción para el cron de contratos:
CRON_SECRETGITHUB_ACTIONS_REPOSITORYGITHUB_ACTIONS_DISPATCH_TOKEN
Variable recomendada:
GITHUB_ACTIONS_REF
Antes de activar el workflow en staging o producción, confirma:
- que el cron programado es el que realmente quieres operar
- que las secrets están cargadas
- que
setup_supabase.sqlya corrió en el proyecto Supabase destino
Ruta:
/votometro/review
Cómo funciona:
- el acceso se habilita cuando existe
VOTOMETRO_REVIEW_PASSWORD - el login genera una cookie HTTP-only llamada
votometro_review - la sesión se usa para aprobar/rechazar promesas y resolver colisiones de identidad
Uso mínimo:
- define
VOTOMETRO_REVIEW_PASSWORD - despliega el frontend con
SUPABASE_SERVICE_KEY - abre
/votometro/review - ingresa el password
- revisa:
- promesas pendientes
- colisiones de identidad
- corridas recientes de sync
Importante:
- sin
VOTOMETRO_REVIEW_PASSWORD, el panel se renderiza pero queda deshabilitado - sin
SUPABASE_SERVICE_KEYoSUPABASE_KEY, las operaciones internas no podrán escribir
Desde la raíz del repo:
pytest tests/test_votometro_adapters.pyCubren:
- normalización
- adapters de Cámara y Senado
- métricas y agregados
Desde web/:
npm run buildEste build debe pasar antes de mover cambios de VotóMeter a staging o producción.
pytest tests/test_votometro_adapters.py
cd web && npm run buildChecklist recomendado:
- Ejecutar
scripts/setup_supabase.sqlen el proyecto Supabase destino. - Configurar en el frontend:
SUPABASE_URLSUPABASE_ANON_KEYSUPABASE_SERVICE_KEYVOTOMETRO_REVIEW_PASSWORDNEXT_PUBLIC_APP_URLNEXT_PUBLIC_SITE_URL
- Configurar en GitHub Actions:
SUPABASE_URLSUPABASE_KEYSOCRATA_APP_TOKENsi vas a consumir Cámara con cuota estable
- Verificar que
.github/workflows/votometro_sync.ymltenga el cron correcto y no esté deshabilitado. - Lanzar un
workflow_dispatchmanual en mododailydespués del bootstrap. - Verificar en la app desplegada:
/votometro/votometro/legislador/[slug]/api/votometro/legislators/votometro/review
- Confirmar que
ingestion_runsregistre la corrida y que no quede enwarningpor caída de cobertura.
La experiencia web histórica intenta consumir la API Python y, si no está disponible, puede caer a mocks en algunos módulos heredados.
Para VotóMeter, el objetivo operativo del MVP es otro:
- producción: Supabase + route handlers de Next.js
- no el dataset estático anterior
Si /votometro aparece vacío en un entorno nuevo, normalmente la causa no es el frontend:
- falta correr
setup_supabase.sql - faltan variables de entorno de Supabase
- el sync todavía no ha corrido
- el sync quedó en
warningy no reemplazó la capa pública
| Path | Rol |
|---|---|
scripts/setup_supabase.sql |
bootstrap de tablas, vistas públicas y bucket de snapshots |
scripts/sync_votometro.py |
wrapper CLI para correr el sync |
src/ingestion/votometro/camera_adapter.py |
ingestión de Cámara desde datos.gov.co |
src/ingestion/votometro/senate_adapter.py |
ingestión de Senado abierto |
src/ingestion/votometro/congreso_visible.py |
gancho de backfill semanal, hoy incompleto |
src/ingestion/votometro/sync.py |
orquestación principal del sync y guardrail de cobertura |
web/lib/votometro-server.ts |
queries, DTOs y filtros server-side de VotóMeter |
web/lib/votometro-admin.ts |
auth ligera del review panel |
web/app/votometro/page.tsx |
directorio SSR |
web/app/votometro/legislador/[slug]/page.tsx |
perfil individual SSR |
web/app/votometro/review/page.tsx |
panel interno de revisión |
web/app/api/votometro/ |
API pública e interna de VotóMeter |
.github/workflows/votometro_sync.yml |
cron y workflow de sincronización |
tests/test_votometro_adapters.py |
cobertura de normalización, adapters y métricas |
Limitaciones conocidas del MVP:
- El backfill de Cámara / Congreso Visible está intencionalmente incompleto.
- El gancho semanal existe y se ejecuta, pero hoy devuelve un warning controlado en vez de correr un scraper completo.
- La coherencia pública solo aparece cuando hay promesas revisadas en backoffice.
- El módulo todavía no hace un backfill histórico completo de proyectos y votaciones de Cámara.
Qué hacer después:
- completar el scraper / ingestión real de Congreso Visible para Cámara e histórico
- enriquecer proyectos de ley y relaciones promesa → voto
- endurecer observabilidad de los syncs
- agregar documentación de migraciones incrementales si el schema deja de vivir solo en
setup_supabase.sql
- fuente primero
- explicación antes que jerga
- una sola lectura, no diez pantallas
- contraste entre dato oficial, scoring y evidencia visible
- diseño sobrio, no dashboard genérico
VeedurIA ya no es solo una prueba visual. Hoy funciona como un producto web con navegación compartida, módulos activos y una ruta clara de expansión:
- ContratoLimpio ya sirve como frente operativo
- VotóMeter ya reemplazó al módulo anterior de promesas
- SigueElDinero ya muestra avance real y no una pantalla vacía
- Si haces push a
main, Vercel despliega automáticamente el frontend. - Si la UI en producción no refleja un cambio reciente, normalmente basta un hard refresh.
- Para contexto más detallado de arquitectura y flujo de trabajo, ver
CLAUDE.md.