
En enero de 2026 el Estado colombiano firmó 486,013 contratos por $35.7 billones COP. El 92% fueron por contratación directa y otro 7.6% por régimen especial: en la práctica, la totalidad sin competencia. Y el 30 de enero, último día hábil antes de que la Ley de Garantías electorales congelara la contratación directa, se firmaron 43,036 contratos en un solo día: casi tantos como en todo enero de 2020.
No encontre estos números en un informe de la Contraloria. Los encontre corriendo SQL sobre la base publica de SECOP II desde mi laptop.
El problema que quise resolver
La contratacion publica colombiana mueve mas de $850 billones COP registrados en SECOP II desde 2018. Eso son 5.1 millones de contratos con 84 columnas cada uno: fechas, valores, entidades, contratistas, modalidades, objetos contractuales. Todo publico, todo descargable via API. Y sin embargo, la mayoría de los ejercicios de veeduria ciudadana siguen siendo manuales: alguien abre SECOP, busca una entidad, revisa contratos uno por uno, toma notas en una hoja de calculo.
Daniel Briceno, entre otros veedores, popularizo esa métodologia de escrutinio sistematico del SECOP. Lo que yo quise hacer fue industrializarla. Tomar los patrónes que un veedor busca a mano (fraccionamiento, concentracion, contratistas que aparecen en todas partes, picos sospechosos) y convertirlos en queries automatizadas sobre la base completa.
El resultado es BricenoBot: un radar que ingesta contratos de SECOP II, ejecuta un motor de banderas rojas codificadas, calcula un score de riesgo 0-100, y presenta todo en un dashboard para priorizar donde mirar.
El stack: deliberadamente simple
La decisión técnica mas importante fue no sobreingenieria. El volumen de datos (5.1 millones de filas, 29 columnas selecciónadas) es grande para una hoja de calculo pero trivial para una base analitica moderna. El stack completo:
- DuckDB como base local. Un solo archivo
.duckdb, zero infraestructura, queries analiticas en segundos sobre millones de filas. La decisión de no usar Postgres fue deliberada: queria que cualquier persona pudiera replicar el analisis en su maquína sin levantar un servidor. - Python + requests para la ingesta desde la API SODA de datos.gov.co, con paginacíon por ventanas mensuales para evitar los offsets profundos de Socrata.
- Streamlit para el dashboard local de exploracion.
- GitHub Pages para el dashboard publico estático.
El requirements.txt tiene cinco dependencias:
duckdb>=1.0
requests>=2.31
pandas>=2.0
streamlit>=1.35
plotly>=5.20
La ingesta completa (5.1 millones de contratos) tarda alrededor de una hora. Una muestra de 50,000 contratos tarda menos de un minuto.
Como funciona el pipeline
Tres pasos, tres comandos:
# 1. Ingesta: descarga contratos de SECOP II a DuckDB
python -m src.ingest --desde 2018-08-07
# 2. Banderas rojas: ejecuta el motor de deteccion
python -m src.flags
# 3. Dashboard: explora los resultados
streamlit run app.py
La ingesta normaliza identidades al vuelo. Esto es el 60% del trabajo real y probablemente el punto que mas subestimaria alguien que no ha trabajado con datos del Estado colombiano: un mismo NIT aparece con y sin digito de verificacion (830123456 vs 830123456-1), los nombres de entidades tienen variantes (ALCALDIA DE BOGOTA, ALCALDÍA DE BOGOTÁ*, ALCALDÍA MAYOR DE BOGOTÁ D.C.), y los documentos de proveedores vienen en formatos inconsistentes. Sin normalizar, los cruces no funcionan.
# Normalización básica de identidades
df["nit_entidad"] = df["nit_entidad"].astype(str).str.strip().str.split("-").str[0]
df["documento_proveedor"] = (
df["documento_proveedor"].astype(str).str.strip().str.split("-").str[0]
)
df["nombre_entidad"] = df["nombre_entidad"].str.strip().str.rstrip("*").str.upper()
Después de la ingesta, se crea una vista contratos_clean que agrega columnas derivadas: año y mes de firma, si la modalidad es “sin competencia” (contratación directa + régimen especial), y si el proveedor es persona natural (por tipo de documento).
Las banderas rojas
El motor ejecuta ocho banderas, cada una es un query SQL puro sobre contratos_clean. Cada bandera produce alertas con un formato homogeneo: código, nivel (contrato, contratista o entidad), detalle legible, valor en COP y severidad.
Estas son las ocho del MVP:
F01: Fraccionamiento. Misma entidad + mismo proveedor + 5 o mas contratos sin competencia en el mismo ano, sumando mas de $500 millones. Patron clasíco para evadir umbrales de licitacion.
F02: Salto anomalo de facturacion. Proveedor cuya facturacion anual con el Estado crece mas de 10x respecto al año anterior, superando $500 millones.
F03: Pico anomalo de contratacion. Mes en que una entidad firma contratos 3 o mas desviaciones estandar por encima de su propia media historica. El detector de “afan de enero” y “afan de diciembre”.
F04: Entidad capturada. Indice HHI (Herfindahl-Hirschman) de concentracion de proveedores mayor a 0.5, con al menos 10 contratos y $1,000 millones.
F06: Contratista pulpo. Persona natural con contratos de prestacion de servicios en 3 o mas entidades el mismo ano.
F08: Contratacion sin competencia dominante. Entidad que adjudica mas del 80% de su valor por modalidades sin competencia, con al menos 50 contratos y $2,000 millones.
F09: Objeto difuso de alto valor. Contratos de mas de $1,000 millones con objetos genéricos como “apoyo a la gestion” o “fortalecimiento institucional”.
F10: Adicion de tiempo excesiva. Contratos con 180 o mas dias adicionados sobre el plazo original.
Un ejemplo concreto del SQL detras de F04 (concentracion):
WITH participacion AS (
SELECT nit_entidad, any_value(nombre_entidad) AS nombre_entidad, anio,
documento_proveedor,
sum(valor_del_contrato) AS v_prov,
sum(sum(valor_del_contrato)) OVER (PARTITION BY nit_entidad, anio) AS v_total,
sum(count(*)) OVER (PARTITION BY nit_entidad, anio) AS n_total
FROM contratos_clean
GROUP BY nit_entidad, anio, documento_proveedor
)
SELECT 'F04' AS código,
'Entidad capturada (alta concentración)' AS bandera,
any_value(nombre_entidad) AS nombre_entidad,
nit_entidad, anio,
'HHI ' || round(sum((v_prov / v_total) ** 2), 2) AS detalle,
any_value(v_total) AS valor_cop,
20 AS severidad
FROM participacion
GROUP BY nit_entidad, anio
HAVING sum((v_prov / v_total) ** 2) > 0.5
AND any_value(n_total) >= 10
AND any_value(v_total) > 1000e6
El score final es la suma de severidades de las alertas asociadas, con tope en 100. Simple, auditable, sin machine learning. Cualquier persona puede leer el query y entender exactamente por que una entidad o un contratista tiene una bandera.
Lo que encontro el radar
Sobre 5,101,581 contratos (agosto 2018 a julio 2026), el motor genero 42,335 alertas por un valor combinado de $775 billones COP bajo alerta. Los números gruesos de la base:
- 65% del valor total se adjudico por modalidades sin competencia (contratación directa + régimen especial).
- 4,791 entidades y 1,164,931 proveedores distintos.
- Las tres banderas con mayor valor bajo alerta: F08 (contratacion sin competencia dominante, $343 billones), F04 (entidades capturadas, $118 billones) y F01 (fraccionamiento, $94 billones).
- 16,228 contratistas pulpo: personas naturales con OPS simultaneos en 3 o mas entidades.
El hallazgo mas revelador fue temporal. La bandera F03 (pico anomalo) ilumino un patrón que ya se sospechaba pero que nunca se había cuantificado a esta escala: el “afan de enero” de 2026.
El afan de enero
En enero de 2026, 590 entidades firmaron contratos a un ritmo superior a 5 veces su propia media mensual historica. La UARIV (Unidad para las Victimas) firmo 21 veces su media. El ICBF Regional Cundinamarca, 14 veces. El Distrito de Barranquilla, casí 14 veces.
La composicion de esos 486,013 contratos: 96% prestacion de servicios, 95% con personas naturales, 92% por contratación directa. 121,038 contratos contienen literalmente “apoyo a la gestion” en su objeto. La mediana fue de $28 millones por contrato, consistente con vinculaciones de 3 a 6 meses.
El patrón diario lo dice todo: la contratacion se concentro en la última semana de enero, con 43,036 contratos el viernes 30 (último día hábil antes de la restricción). En febrero cayeron a 22,103. En marzo a 17,071. La contratacion no se distribuyó: se adelantó en masa.
Para contexto: el enero previo a las elecciónes anteriores (enero 2022) también fue anomalo con 293,275 contratos. El de 2026 lo multiplico por 1.7.
Expedientes
El radar genera expedientes automaticos de las alertas mas severas con los contratos subyacentes, incluyendo id_contrato y proceso_de_compra para verificacion directa en SECOP II. El expediente mas grande (la relacion entre la Alcaldia de Santa Marta y su empresa EDUS, con 17 contratos por $1.02 billones transferidos por contratación directa en 2025) incluye la cronologia completa: la advertencia de la Procuraduria sobre posible “evasíon de la normativa de contratacion estatal”, los contratos firmados el 31 de diciembre de 2025 (incluido el convenio del acueducto El Curval por $893,052 millones), y las adjudicaciones subsiguientes de la EDUS bajo régimen especial durante la Ley de Garantias.
Lo que el radar no hace (y por que importa decirlo)
Una bandera roja es una señal estadistica que amerita revision humana. No es una acusacion. El aviso métodologico esta en cada vista del dashboard, en cada informe, en el README. Esta distincion no es decorativa: es constitutiva del proyecto.
El fraccionamiento puede ser una practica operativa legitima. Un pico de enero puede reflejar la continuidad del servicio. Una empresa con HHI alto puede ser la única que ofrece ese servicio en una region. El radar señala donde mirar. El juicio lo pone un humano: un periodista, un veedor, un organo de control.
Por qué construi esto
Soy físico cuántico de formación, cofundador de una agencia B2B de dia, y colombiano todo el tiempo. BricenoBot no nacío de un brief ni de un roadmap de producto. Nacio de la frustracion de ver que los datos ya existen, que las API ya estan abiertas, y que nadie había industrializado el analisis.
Los datos del SECOP son sorprendentemente buenos. Tienen problemas de normalizacion (el eterno NIT con guion, los nombres con asteriscos), pero la cobertura es amplia y la API funciona. El cuello de botella nunca fue la disponibilidad de la información: fue que nadie había escrito los queries.
Como replicarlo
El repositorio es open source (MIT). Para correr el analisis completo:
git clone https://github.com/nicogarazzo/BricenoBot.git
cd BricenoBot
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Muestra rápida (50k contratos, <1 min)
python -m src.ingest --limit 50000
python -m src.flags
streamlit run app.py
# Base completa (5.1M contratos, ~1 hora)
python -m src.ingest --desde 2018-08-07
python -m src.flags
El dashboard publico esta en nicogarazzo.github.io/BricenoBot. El informe completo “El afan de enero” con todas las tablas y la métodologia esta en el directorio informes/ del repositorio.
Si quieres contribuir, las prioridades del roadmap son: cruces con Cuentas Claras (financiadores de campanas vs. contratistas), integracion con RUES (edad de empresas, representantes legales compartidos), y analisis de redes para detectar clusters de contratistas que comparten infraestructura.
Los datos son publicos. Las queries son auditables. Los hallazgos son reproducibles. El resto es SQL.