Arquitectura
Vista General
Sección titulada «Vista General»El Observatorio del Congreso es una plataforma de análisis cuantitativo del poder legislativo de México (Cámara de Diputados + Senado de la República). Utiliza un esquema unificado Popolo-Graph almacenado en SQLite para modelar legisladores, partidos, votos y redes de poder informales a lo largo de siete legislaturas (LX a LXVI, 2006-2026). El dataset cubre aproximadamente 3.5 millones de votos individuales, 8,000 eventos de votación y 3,800 legisladores.
Pipeline
Sección titulada «Pipeline»┌─────────────────────────────────────────────────────────────────────┐│ RECOLECCIÓN DE DATOS ││ ││ ┌──────────────────────┐ ┌──────────────────────────────────┐ ││ │ Scraper Senado │ │ Scraper Diputados │ ││ │ curl_cffi + TLS │ │ httpx + BeautifulSoup │ ││ │ fingerprint │ │ │ ││ │ (Anti-WAF: │ │ SITL / INFOPAL portal abierto │ ││ │ bypass Incapsula) │ │ + API datos.abiertos │ ││ └──────────┬───────────┘ └──────────────┬───────────────────┘ ││ │ │ │└─────────────┼──────────────────────────────────┼────────────────────┘ │ │ ▼ ▼┌─────────────────────────────────────────────────────────────────────┐│ PARSEO Y CARGA ││ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ Transformers → Loaders (deduplicación vía source_id) │ ││ └──────────────────────────────┬───────────────────────────────┘ ││ │ │└─────────────────────────────────┼───────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────────┐│ CAPA DE ALMACENAMIENTO ││ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ SQLite (WAL mode) — congreso.db │ ││ │ Esquema Popolo-Graph: 12 tablas │ ││ │ area · organization · person · membership · post │ ││ │ motion · vote_event · vote · count │ ││ │ actor_externo · relacion_poder · evento_politico │ ││ └──────────────────────────────┬───────────────────────────────┘ ││ │ │└─────────────────────────────────┼───────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────────┐│ CAPA DE ANÁLISIS ││ ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ ││ │ W-NOMINATE │ │ Co-votación │ │ Detección de │ ││ │ (scipy, │ │ Matriz │ │ Comunidades │ ││ │ numpy) │ │ y Grafo │ │ (networkx, │ ││ │ │ │ │ │ python-louvain) │ ││ └──────┬───────┘ └──────┬───────┘ └───────────┬──────────────┘ ││ │ │ │ ││ ┌──────┴───────┐ ┌──────┴───────┐ ┌───────────┴──────────────┐ ││ │ Centralidad │ │ Índices de │ │ Poder Empírico │ ││ │ (grado, │ │ Poder │ │ (des coaliciones de │ ││ │ betweenness)│ │ (Shapley- │ │ votación reales) │ ││ │ │ │ Shubik, │ │ │ ││ │ │ │ Banzhaf) │ │ │ ││ └──────┬───────┘ └──────┬───────┘ └───────────┬──────────────┘ ││ │ │ │ │└─────────┼──────────────────┼───────────────────────┼─────────────────┘ │ │ │ ▼ ▼ ▼┌─────────────────────────────────────────────────────────────────────┐│ CAPA DE EXPORTACIÓN ││ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ Archivos JSON → public/data/observatorio/ │ ││ │ Pre-agregados, estáticos, sin cómputo en servidor │ ││ └──────────────────────────────┬───────────────────────────────┘ ││ │ │└─────────────────────────────────┼───────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────────┐│ CAPA DE VISUALIZACIÓN ││ ││ ┌──────────────────────────────────────────────────────────────┐ ││ │ CachorroSpace (Astro + Starlight) │ ││ │ ECharts 6 vía React islands │ ││ │ Gráficas interactivas: mapas NOMINATE, grafos de co-voto, │ ││ │ índices de poder, estructuras comunitarias │ ││ └──────────────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────┘Stack Tecnológico
Sección titulada «Stack Tecnológico»| Componente | Tecnología | Propósito |
|---|---|---|
| Scraping (Senado) | curl_cffi + impersonación de huella TLS | Evasión Anti-WAF para portal protegido por Incapsula |
| Scraping (Diputados) | httpx + BeautifulSoup | Scraping del portal de datos abiertos (SITL / INFOPAL) |
| Base de datos | SQLite (WAL mode) | Almacenamiento unificado Popolo-Graph |
| Análisis — NOMINATE | scipy, numpy, matplotlib | Estimación de puntos ideales (algoritmo W-NOMINATE) |
| Análisis — Redes | networkx, python-louvain | Grafos de co-votación, detección de comunidades |
| Exportación | JSON (estático) | Datos pre-agregados para visualizaciones |
| Visualizaciones | ECharts 6 (React islands) | Gráficas interactivas en CachorroSpace |
Fuentes de Datos
Sección titulada «Fuentes de Datos»| Fuente | URL | Cámara | Datos |
|---|---|---|---|
| Cámara de Diputados | datos_abiertos / SITL / INFOPAL | Diputados | Registros de votación, perfiles de legisladores, composición |
| Senado de la República | senado.gob.mx/66/ | Senado | Registros de votación, perfiles de senadores, directorio |
Flujo de Datos
Sección titulada «Flujo de Datos»El pipeline procesa datos en cuatro etapas:
1. Scraping y Parseo
Sección titulada «1. Scraping y Parseo»Cada cámara tiene un scraper dedicado con su propio cliente HTTP, parser y módulos transformadores:
- Senado: una sesión
curl_cfficon impersonación TLS recupera las páginas de votación. Los parsers extraen datos de votación del HTML. Los transformers normalizan los datos al formato Popolo-Graph. - Diputados: un cliente
httpxcon caché basada en archivos y rate limiting consulta los sistemas SITL/INFOPAL. Los parsers manejan tanto respuestas XML como HTML.
2. Carga en SQLite
Sección titulada «2. Carga en SQLite»Los datos fluyen a través de loaders que insertan registros en congreso.db con deduplicación vía la columna source_id en la tabla vote_event. El módulo id_generator produce IDs legibles con prefijos (P01, O01, VE01, etc.).
3. Análisis
Sección titulada «3. Análisis»Los scripts de análisis leen desde SQLite y calculan:
- W-NOMINATE: estimación de puntos ideales que ubica a los legisladores en un mapa ideológico 2D
- Matriz de co-votación: tasas de acuerdo por pares entre legisladores, exportadas como grafos ponderados
- Detección de comunidades: el algoritmo Louvain identifica bloques de votación dentro de las redes de co-votación
- Centralidad: medidas de centralidad de grado y betweenness en grafos de co-votación
- Índices de poder: Shapley-Shubik y Banzhaf basados en distribuciones de escaños
- Poder empírico: medido a partir de datos reales de coaliciones de votación, no solo conteo de escaños
4. Exportación y Visualización
Sección titulada «4. Exportación y Visualización»El script export_observatorio_json.py lee los CSV de salida del análisis y produce archivos JSON estáticos consumidos por las visualizaciones ECharts 6 embebidas como React islands en CachorroSpace.
analysis/output/*.csv │ ▼export_observatorio_json.py │ ▼public/data/observatorio/*.json │ ▼React ECharts islands (CachorroSpace)Estructura del Proyecto
Sección titulada «Estructura del Proyecto»observatorio-congreso/├── db/│ ├── schema.sql # Esquema Popolo-Graph (12 tablas)│ ├── senado_schema.sql # Extensiones de esquema para Senado│ ├── init_db.py # Inicialización de BD + datos semilla│ ├── helpers.py # Funciones auxiliares SQLite│ ├── id_generator.py # Generación de IDs legibles (P01, O01...)│ ├── constants.py # Mapeos de legislaturas y constantes│ ├── migrations/ # Migraciones de esquema y correcciones│ └── congreso.db # Base de datos SQLite (~3.5M votos)│├── diputados/│ └── scraper/│ ├── client.py # Cliente HTTP httpx con caché│ ├── config.py # Configuración del scraper│ ├── pipeline.py # Pipeline principal de scraping│ ├── loader.py # Loader SQLite (dedup vía source_id)│ ├── models.py # Modelos de datos│ ├── legislatura.py # Lógica de rangos de legislatura│ └── parsers/│ ├── votaciones.py # Parser de eventos de votación│ ├── nominal.py # Parser de votos nominales (roll-call)│ ├── desglose.py # Parser de desglose de votos│ ├── diputado.py # Parser de perfil de legislador│ └── composicion.py # Parser de composición de la Cámara│├── senado/│ └── scrapers/│ ├── shared/│ │ ├── client.py # Cliente Anti-WAF (curl_cffi)│ │ ├── config.py # Configuración del scraper│ │ └── models.py # Modelos de datos compartidos│ ├── votaciones/│ │ ├── __main__.py # Entry point CLI│ │ ├── cli.py # Interfaz de línea de comandos│ │ ├── transformers.py # Normalización de datos│ │ ├── congreso_loader.py # Loader SQLite│ │ └── parsers/│ │ └── lxvi_portal.py # Parser del portal LXVI│ └── perfiles/│ ├── __main__.py # Entry point CLI│ ├── scraper.py # Scraper de perfiles│ └── parsers/│ └── perfil_parser.py # Parser de perfil de senador│├── analysis/│ ├── nominate.py # Implementación W-NOMINATE│ ├── covotacion.py # Matriz y grafo de co-votación│ ├── covotacion_dinamica.py # Co-votación dinámica (ventanas de tiempo)│ ├── comunidades.py # Detección de comunidades Louvain│ ├── centralidad.py # Centralidad de grado y betweenness│ ├── poder_partidos.py # Índices Shapley-Shubik y Banzhaf│ ├── poder_empirico.py # Poder empírico desde votos reales│ ├── run_analysis.py # Ejecutar todos los análisis│ ├── run_nominate.py # Ejecutar solo NOMINATE│ ├── run_covotacion_dinamica.py # Ejecutar co-votación dinámica│ ├── visualizacion.py # Exportaciones de visualización general│ ├── visualizacion_nominate.py # Datos para gráficas NOMINATE│ ├── visualizacion_dinamica.py # Datos para co-votación dinámica│ ├── visualizacion_poder.py # Datos para índices de poder│ ├── visualizacion_articulo.py # Visualizaciones específicas por artículo│ ├── scripts/│ │ └── export_observatorio_json.py # CSV → JSON para ECharts│ ├── analisis-diputados/ # Salidas de análisis específicos Diputados│ ├── analisis-senado/ # Salidas de análisis específicos Senado│ └── analisis-bicameral/ # Salidas de análisis bicameral│├── utils/│ ├── db_utils.py # Funciones utilitarias de base de datos│ ├── text_utils.py # Utilidades de normalización de texto│ └── tests/│ └── test_text_utils.py # Tests de utilidades de texto│├── cache/ # Caché de respuestas HTTP├── logs/ # Logs de scrapers├── pyproject.toml # Dependencias del proyecto (uv)└── scrape_diputados_all.sh # Script de scraping batchConfiguración de la Base de Datos
Sección titulada «Configuración de la Base de Datos»SQLite se configura para acceso concurrente seguro e integridad de datos:
PRAGMA foreign_keys = ON;PRAGMA encoding = "UTF-8";PRAGMA journal_mode = WAL;PRAGMA busy_timeout = 5000;| Configuración | Valor | Propósito |
|---|---|---|
journal_mode | WAL | Lecturas concurrentes sin bloquear escrituras |
foreign_keys | ON | Aplicar integridad referencial entre tablas |
busy_timeout | 5000ms | Esperar hasta 5 segundos si la BD está bloqueada |
encoding | UTF-8 | Manejo correcto de caracteres en español (acentos, ñ) |
Resumen del Esquema
Sección titulada «Resumen del Esquema»El esquema Popolo-Graph contiene 12 tablas, organizadas en cuatro grupos:
Entidades Popolo centrales (estándar de datos parlamentarios):
| Tabla | Propósito |
|---|---|
area | Divisiones geográficas (estados, distritos, circunscripciones) |
organization | Partidos políticos, bancadas, coaliciones, instituciones |
person | Legisladores y actores políticos |
membership | Relaciones persona-organización con roles y fechas |
post | Cargos legislativos dentro de organizaciones y áreas |
motion | Iniciativas y propuestas legislativas |
vote_event | Instancias específicas de votación (cámara + fecha) |
vote | Votos individuales de legisladores por evento |
count | Conteos agregados de votos por grupo por evento |
Extensiones de redes de poder (más allá del estándar Popolo):
| Tabla | Propósito |
|---|---|
actor_externo | Actores externos (gobernadores, dirigentes, jueces) |
relacion_poder | Relaciones de poder informales (lealtad, presión, alianzas) |
evento_politico | Eventos políticos que afectan dinámicas de poder |
Índices
Sección titulada «Índices»El esquema incluye índices sobre los patrones de consulta más frecuentes:
- Consultas de
membershippor persona y por organización - Búsquedas de
vote_eventpor motion y porsource_id(deduplicación) - Consultas de
votepor votante y por evento - Consultas de
countpor evento y por grupo - Consultas de
relacion_poderpor origen, destino y tipo - Filtrado de
personpor corriente interna (corriente_interna)
Triggers de Integridad
Sección titulada «Triggers de Integridad»Los triggers de validación de fechas aseguran que end_date >= start_date en las tablas person y membership tanto para inserciones como para actualizaciones. Estos se ejecutan a nivel de SQLite para prevenir corrupción de datos independientemente de qué loader escriba los datos.