¿Qué es esto?
votaciones-e14 es un pipeline de software libre escrito en Python que permite a cualquier ciudadano descargar masivamente las actas E-14 publicadas por la Registraduría Nacional del Estado Civil, validarlas, convertirlas a imágenes y — lo más importante — verificar que la Registraduría no modifique los archivos después de publicarlos.
Las actas E-14 son los formularios que firman los jurados de votación al cierre de cada mesa, donde se consignan los votos por cada candidato, los votos en blanco, nulos y no marcados. Son la fuente primaria del escrutinio electoral.
Video demo
Recorrido completo del menú interactivo: cómo descargar las actas E-14 de un departamento o de los consulados (voto en el exterior), generar el Excel con la verificación de integridad y detectar cambios entre corridas.
¿Para qué se hizo?
En junio de 2026, durante el proceso electoral de la Segunda Vuelta Presidencial, surgió una polémica pública entre el gobierno y la Registraduría Nacional sobre la integridad de los formularios E-14. El gobierno denunció que podrían estarse modificando actas después de publicadas; la Registraduría afirmó que su sistema de candado hash y estampilla de tiempo garantizaba la integridad de cada archivo.
Este proyecto nació para resolver esa pregunta de forma técnica, verificable y reproducible: si descargas hoy un acta y mañana la Registraduría la cambia, el pipeline lo detecta automáticamente comparando los hashes oficiales de Amazon S3 (donde están almacenadas) entre las dos corridas.
Dimensión del proceso electoral
Datos del catálogo oficial de la Registraduría (allTransmissionCodes.json),
capturados al ejecutar el pipeline. Estas cifras describen el universo de mesas
sobre las que actúa la herramienta.
Mesas por departamento
Cantidad de mesas de votación registradas en cada departamento. Útil para dimensionar el esfuerzo de descarga: a más mesas, mayor tiempo y espacio en disco.
Cómo se determina si un acta está disponible
El catálogo público de la Registraduría agrupa las mesas en dos buckets internos
(status3 y status11) cuyo significado preciso no está
documentado públicamente. En pruebas hechas con este pipeline, ambos
buckets contienen actas que sí se descargan correctamente.
Por eso el pipeline no asume nada del bucket: simplemente intenta descargar cada acta y deja que el servidor responda. Las actas que devuelven un PDF válido se guardan, las que devuelven error o HTML se marcan como faltantes para reintentar más tarde.
El indicador real de disponibilidad es el resultado del paso 2 (descarga), no un conteo derivado del catálogo. En el departamento del Cauca (caso piloto), por ejemplo, de 3.271 mesas en el catálogo se descargaron exitosamente 2.486 actas en una sola corrida.
¿Cómo funciona?
El pipeline tiene 5 pasos accesibles desde un menú interactivo:
Descargar índices
Baja el catálogo maestro de mesas en JSON (~36 MB).
Descargar PDFs
Descarga en paralelo todas las actas de un departamento o de los consulados (voto en el exterior).
Excel + integridad
Calcula MD5/SHA-256 locales y consulta los hashes oficiales del servidor. También funciona sin descargar PDFs (línea base nacional).
PDF a imagen
Convierte los PDFs a JPG/PNG para análisis con OCR / IA de visión.
Detectar cambios
Compara dos corridas del paso 3 y reporta archivos modificados.
Modo nacional (línea base de los 34 territorios)
El menú interactivo ofrece una opción especial *** TODOS - Inventario nacional ***
que ejecuta el paso 3 sobre los 34 departamentos (incluyendo consulados) sin descargar los
PDFs — solo captura los headers de integridad oficiales. Es la forma más rápida y liviana
de tener una línea base criptográfica nacional de las ~118.000 mesas:
- Tiempo total: 3-4 horas con 16 workers.
- Espacio: ~50 MB de manifests JSONL (no 100+ GB de PDFs).
- Si la Registraduría modifica una sola acta después, queda registrado.
También hay una opción intermedia TOP 4 - Bogotá, Antioquia, Valle, Cundinamarca
que cubre el 50% de las mesas nacionales en ~1 hora.
Garantías de no perder evidencia
| Archivo | Comportamiento |
|---|---|
manifest_<DEPTO>.jsonl | Append-only. Cada corrida solo agrega líneas con timestamp UTC. El historial completo siempre crece. |
inventario_<DEPTO>.xlsx | Se respalda automáticamente a reportes/<DEPTO>/backups/ antes de sobrescribir. |
cambios_<DEPTO>.xlsx | Se respalda automáticamente antes de sobrescribir. |
| PDFs descargados | Idempotente — los archivos ya descargados nunca se re-descargan ni borran. |
Stack técnico
| Componente | Tecnología |
|---|---|
| Portal de origen | Angular SPA + Akamai CDN |
| Almacenamiento del archivo | Amazon S3 con versionado |
| Lenguaje del pipeline | Python 3.10+ |
| Descargas paralelas | requests + ThreadPoolExecutor |
| Render PDF | PyMuPDF + Pillow |
| Reportes | openpyxl (Excel multi-hoja) |
| Interfaz | questionary (CLI interactiva) |
| HTTP cliente | requests + curl_cffi (impersona TLS de Chrome 131 para evadir Akamai) |
| Visualizaciones | Chart.js 4 vía jsDelivr CDN |
| Iconos | Bootstrap Icons 1.11 vía jsDelivr CDN |
| Distribución | Script Python o ejecutable .exe (PyInstaller) |
Verificación de integridad oficial
La Registraduría almacena las actas en Amazon S3 con versionado activado. En cada descarga, el servidor devuelve en los headers HTTP tres valores que constituyen la huella técnica oficial del archivo:
| Header HTTP | Qué garantiza |
|---|---|
ETag | MD5 oficial del archivo en S3 (cambia si modifican el contenido) |
Last-Modified | Timestamp de la última subida al bucket |
x-amz-version-id | Identificador único de versión S3 (cambia si re-suben) |
El paso 3 captura los tres valores cada vez que se ejecuta y los guarda en un
manifest_<DEPTO>.jsonl append-only con timestamp UTC.
El paso 5 compara las observaciones entre corridas y reporta cualquier cambio.
Cómo el pipeline evade Akamai Bot Manager
El portal de la Registraduría está protegido por Akamai Bot Manager, que en 2026
bloquea masivamente clientes Python estándar mediante TLS fingerprinting
(JA3/JA4). La biblioteca requests tiene un fingerprint
reconocible como "biblioteca Python" sin importar qué headers envíe — Akamai
lo detecta y sirve HTML genérico de "Access Denied" con HTTP 200, sin entregar
cookie de sesión.
La solución integrada en el pipeline (6 capas combinadas):
| Técnica | Qué hace |
|---|---|
curl_cffi + impersonate="chrome131" | Replica el TLS Client Hello exacto de Chrome 131: mismo cipher suite ordering, extensiones TLS, ALPN. Akamai no puede distinguirlo de un navegador real. |
| Rate limit global de 3 req/s | Token bucket compartido entre todos los workers. Independiente del paralelismo, el ritmo total sostenido nunca supera 3 req/s — Akamai lo tolera como tráfico "humano-like" y no marca la IP. |
| Workers limitados (4 default) | El rate limiter hace irrelevante subir workers. 4 es suficiente para HEAD requests cortos. |
| Chunks de 100 mesas con detección en vivo | Mide tasa de bloqueo por chunk. Solo ofrece cambio de red tras DOS chunks consecutivos con >80% bloqueo (Akamai a veces tiene picos transitorios que se autocorrigen). |
Cookie ak_bmsc persistida en la sesión | Después del warmup en /, todas las consultas reutilizan la cookie del Bot Manager Standard Cache. |
| Cache de observaciones <24h | Si una mesa ya tiene ETag capturado en el manifest, no se reconsulta — sobrevive a interrupciones sin perder progreso. |
Resultados medidos (validados en producción):
| Setup | Tasa de bloqueo | Velocidad |
|---|---|---|
requests + 16 workers | 96 % | ~3 req/s (con cambios de red constantes) |
curl_cffi + 4 workers (sin rate limit) | 44–91 % | ~6 req/s (inestable) |
curl_cffi + 4 workers + rate limit 3 req/s | 0 % | 3.0 req/s sostenido |
Con el setup completo, el barrido nacional (~118.000 mesas) toma aproximadamente 11 horas continuas sin intervención manual. Es lento pero consistente: déjalo correr en background y al día siguiente tienes la línea base criptográfica nacional completa. El cache permite interrupciones y reanudaciones sin perder progreso.
Tiempos medidos en producción
Estimación a velocidad sostenida de 3 req/s:
tiempo_minutos = mesas / 180. Los datos son reales,
capturados durante el barrido del 22 de junio 2026.
| Configuración de las pruebas | |
|---|---|
| Workers (hilos paralelos) | 4 (de 20 disponibles en el PC) |
| Rate limit global | 3 req/s (token bucket compartido) |
| Tamaño de chunk | 100 mesas |
| Cliente HTTP | curl_cffi impersonando Chrome 131 |
| RAM ocupada | ~210 MB (proceso completo) |
| CPU usada | <5 % (rate limit, no CPU bound) |
Workers ≠ velocidad. El rate limit global de 3 req/s actúa como techo independiente del paralelismo. Con 4 workers a 0.75 req/s c/u suma 3 req/s totales. Subir a 8 o 16 workers da la misma velocidad (cada uno solo esperaría más). Esta arquitectura es la garantía de no irritar a Akamai.
| Departamento | Mesas | Tiempo estimado | Tiempo medido (4 workers) |
|---|---|---|---|
| Vaupés | 60 | 20 s | 20 s ✓ |
| Vichada | 119 | 40 s | — |
| Caldas | 2.476 | 14 min | — |
| Cauca | 3.271 | 18 min | — |
| Atlántico | 5.989 | 33 min | 33 min ✓ |
| Valle | 10.873 | 1 h | — |
| Antioquia | 15.402 | 1 h 25 min | 1 h 25 min ✓ |
| Bogotá D.C. | 17.001 | 1 h 35 min | — |
| Nacional (los 34) | 117.959 | ~11 horas | — |
Instalación y uso
Opción A — Para usuarios (la más fácil)
Descarga el ejecutable de la última versión y haz doble clic. No requiere Python instalado ni ningún paso adicional — el ejecutable trae todo embebido.
# 1. Descarga votaciones.exe desde:
# https://github.com/jsnavarroc/votaciones-e14/releases
# 2. Doble clic en votaciones.exe (o desde terminal):
votaciones.exe
Opción B — Para desarrolladores
Si quieres leer el código, modificarlo o contribuir mejoras:
# Requiere Python 3.10+
git clone https://github.com/jsnavarroc/votaciones-e14.git
cd votaciones-e14
pip install -r requirements.txt
# requirements.txt incluye curl_cffi (impersona TLS de Chrome para evadir Akamai)
python -m votaciones.menu
Opción C — Generar tu propio .exe
Si quieres compilar el ejecutable tú mismo (por ejemplo para firmarlo con tu propio certificado):
pip install pyinstaller
python build_exe.py
# Resultado: dist/votaciones.exe
Una vez abierto, el menú te guía
==================================================
Pipeline E-14 - Segunda Vuelta Presidencial 2026
==================================================
? Departamento:
> *** TODOS - Inventario nacional (linea base de los 34 territorios) ***
*** TOP 4 - Solo grandes: Bogota, Antioquia, Valle, Cundinamarca ***
-----
01 ANTIOQUIA mun=125 (sin descargar)
11 CAUCA mun= 42 (2486 PDFs descargados)
...
? Accion para CAUCA (11):
> 1) Descargar PDFs
2) Generar Excel + integridad
3) Convertir PDFs a imagenes
4) Detectar cambios
5) Cambiar departamento
0) Salir
Comandos avanzados (modo CLI)
Si prefieres saltar el menú y ejecutar pasos individuales — útil para automatización con cron, GitHub Actions o scripts batch:
# 1. Descargar el catalogo de mesas (una sola vez)
python -m votaciones.indices
# 2. Descargar todos los PDFs de un departamento o de los consulados.
# Ejemplos: 11 = CAUCA 16 = BOGOTA 88 = CONSULADOS (voto exterior)
python -m votaciones.descargar 11 # todo Cauca
python -m votaciones.descargar 88 # voto en el exterior
python -m votaciones.descargar 11 --prueba 5 # modo prueba
python -m votaciones.descargar 11 --workers 16 # mas paralelismo
# 3. Generar el Excel de inventario + capturar integridad del servidor
# (incluso si no descargaste los PDFs primero, captura igual los headers
# ETag/version-id/Last-Modified -> ideal para linea base nacional)
python -m votaciones.inventario 11
python -m votaciones.inventario 11 --workers 16
python -m votaciones.inventario 11 --sin-red # solo validacion local
# 4. Convertir PDFs a imagenes (para mandar a OCR / IA de vision)
python -m votaciones.imagenes 11 --stitch --horizontal --jpg --rotar 90 --dpi 250
# 5. Detectar cambios entre dos corridas del paso 3
python -m votaciones.cambios 11 # reporte completo
python -m votaciones.cambios 11 --solo-cambios # solo modificados
Códigos de departamento y consulados
Algunos códigos comunes que pasarás como argumento a los comandos. El código
88 agrupa todos los consulados donde se vota en el exterior
— el pipeline los procesa exactamente igual que cualquier departamento.
| Código | Territorio | Código | Territorio |
|---|---|---|---|
11 | CAUCA | 01 | ANTIOQUIA |
16 | BOGOTÁ D.C. | 31 | VALLE |
15 | CUNDINAMARCA | 27 | SANTANDER |
03 | ATLÁNTICO | 05 | BOLÍVAR |
88 | CONSULADOS (voto en el exterior — 67 sedes) | ...y 26 departamentos más | |
Lista completa: ejecuta el menú y verás los 33 departamentos + consulados con flechas.
Mantenimiento abierto
Proyecto iniciado por @jsnavarroc
Este pipeline es un punto de partida. La idea es que la comunidad de desarrolladores lo audite, lo mejore, lo extienda a otros departamentos, le agregue detección automática de enmendaduras, OCR de los votos, dashboards en tiempo real, o cualquier otra cosa útil para la transparencia electoral.
Está publicado bajo licencia MIT: código abierto, libre de usar, modificar y redistribuir. Cualquier ciudadano puede ejecutar el pipeline, verificar los resultados o contribuir mejoras vía pull request en GitHub.