README
SECOP Analize
Visor y analizador local de los procesos de contratación pública de Colombia (SECOP I y SECOP II), con análisis IA on-demand de los pliegos para evaluar requisitos de aplicación.
Qué hace
- Importa ~3 M procesos de la SODA API (
datos.gov.co) a SQLite local — incremental diario sin captcha. - Visualiza procesos con búsqueda, filtros y vista de detalle.
- Analiza con IA los anexos (PDF/DOCX/XLSX) de procesos puntuales: descarga, extrae texto (con OCR si necesario) y genera un reporte ejecutivo con Ollama indicando qué empresa puede aplicar, requisitos de experiencia, capacidad financiera, banderas rojas, etc.
Arquitectura
┌────────────────────────────────────────────────────────────┐
│ Navegador en localhost:4567 │
└──────────────────────┬─────────────────────────────────────┘
│
┌──────────────────────▼─────────────────────────────────────┐
│ Sinatra (Ruby) en 127.0.0.1:4567 │
│ • bin/web — dashboard + detalle + API de relay │
└──────────┬──────────────────────────┬──────────────────────┘
│ │ HTTP relay
┌──────────▼─────────────────┐ ┌────▼──────────────────────┐
│ SQLite WAL │ │ Node service en 5678 │
│ db/secop.sqlite3 (~13 GB) │ │ • bin/anexos-service │
│ │ │ • Chromium persistente │
│ Tablas: │ │ • Sesión SECOP II │
│ procesos (2.99 M) │ │ reusable │
│ proceso_anexos │◀──┤ • Ollama llm │
│ import_state │ │ qwen2.5:14b │
│ scrape_state │ └───────────────────────────┘
└────────────────────────────┘
▲
│ daily 03:00
┌──────────┴─────────────────┐
│ launchd │
│ com.secop.import-daily │
│ → bin/import-daily.sh │
│ → bin/import-api.mjs │
│ → datos.gov.co (SODA) │
└────────────────────────────┘
URLs
| Servicio | URL | Descripción |
|---|---|---|
| Dashboard | http://127.0.0.1:4567/ | Listado, búsqueda, KPIs |
| Detalle proceso | http://127.0.0.1:4567/proceso/:numero_constancia | Vista con análisis IA |
| Filtro solo IA | http://127.0.0.1:4567/?con_resumen=1 | Procesos con análisis |
| Servicio Node (interno) | http://127.0.0.1:5678/health | Status del browser + sesión |
| Ollama | http://localhost:11434 | LLM local (qwen2.5:14b) |
Stack
| Componente | Versión | Para qué |
|---|---|---|
| Ruby (rbenv) | 3.4.7 | Sinatra dashboard + Sequel ORM |
| Node.js (nvm) | v22.21.1 | Pipeline anexos + servicio persistente |
| SQLite | 3.53 | BD local con WAL |
| Playwright | 1.59 | Chromium headless para enumerar anexos |
| Ollama | 0.23+ | LLM local |
| qwen2.5:14b | 9 GB | Modelo IA para análisis |
| Tesseract | 5.5 | OCR español para PDFs escaneados |
| Poppler | 26 | pdftotext, pdftoppm |
| Antiword, unrtf | — | DOC legacy / RTF |
Instalación
# Dependencias del sistema (Homebrew)
brew install ollama poppler tesseract antiword unrtf
# Modelo LLM
brew services start ollama
ollama pull qwen2.5:14b
# Dependencias Node
cd "/Users/mac/RubymineProjects/SECOP Analize"
npm install
npx playwright install chromium
# Dependencias Ruby
bundle install
Arrancar los servicios
Una sola línea — bin/start.sh mata instancias previas y lanza Sinatra (:4567) + servicio Node (:5678) en background:
cd "/Users/mac/RubymineProjects/SECOP Analize" && bin/start.sh
Verifica Ollama corriendo (necesario para análisis IA):
brew services start ollama
Abre http://127.0.0.1:4567/.
Para detener todo:
pkill -f 'anexos-service\|bin/web'
Para arrancar solo uno:
bin/start.sh web # solo Sinatra
bin/start.sh service # solo servicio Node
Logs en /tmp/secop-web.log y /tmp/secop-service.log.
Windows (cross-platform)
El stack corre igual en Windows y macOS desde el mismo repo. En Windows se usa Ruby (RubyInstaller+DevKit), Node y PowerShell; los scripts .sh/launchd de macOS tienen equivalentes .ps1 y .mjs.
# Dependencias Ruby y Node (una vez)
bundle install
npm install # baja el binario nativo de better-sqlite3 para Windows
npx playwright install chromium
# (opcional, para IA) instala Ollama para Windows desde https://ollama.com/download
# ollama pull qwen2.5:14b
# Arrancar
pwsh bin/start.ps1 # web (:4567) + servicio Node (:5678)
pwsh bin/start.ps1 web # solo Sinatra
pwsh bin/stop.ps1 # detener
# Import diario programado (equivalente del launchd plist)
pwsh bin/register-daily-task.ps1 # registra la tarea a las 03:00
Start-ScheduledTask -TaskName "SECOP import diario" # disparar ahora
Notas:
- Puma corre en single-mode en Windows (
workers 0): Windows no tienefork(), así que el modo cluster no aplica.config/puma.rblo detecta automáticamente. - Logs en
log\secop-web.log,log\secop-service.logylog\import\YYYY-MM-DD.log(en macOS siguen yendo a/tmpy~/Library/Logs). - Para scripts Ruby bajo bundler usar
ruby -rbundler/setup script.rb(en Windowsbundle exec rubytiene un bug con el flag-2); los.ps1ya lo hacen. - Equivalentes cross-platform:
rebuild-competidores.sh→rebuild-competidores.mjs,backup.sh→backup.mjs(ambos usanbetter-sqlite3en vez del CLIsqlite3, ausente en Windows). - Dependencias de IA/OCR externas (Ollama, Poppler/
pdftotext, Tesseract) deben instalarse aparte en Windows; sin ellas, el import diario corre igual pero salta los pasos de IA/OCR (no fatal).
Cómo usar
Buscar / filtrar procesos
- Búsqueda libre (top bar): por número de proceso, entidad, objeto o detalle. Case-insensitive.
- Chips rápidos:
Solo IA,Adjudicados,SECOP I,SECOP II. - Filtros avanzados (colapsable): tipo, estado, departamento, entidad LIKE, cuantía rango, fecha rango.
- Sort: clic en headers de tabla.
Analizar un proceso con IA
- Entra al detalle de un proceso (
/proceso/CO1.REQ.…). - Si la sesión SECOP II está expirada, clic en "Renovar sesión SECOP II" — se abre Chrome, pasa el captcha, navega a cualquier proceso para ver "Documentos", cierra Chrome.
- Clic en "Procesar con IA". Toma ~5-25 min según cantidad de anexos.
- Cuando termina, la página se recarga mostrando:
- Veredicto con color (verde apto / amarillo cautela / rojo amarrado)
- Cifras clave, Tipo de empresa, Experiencia exigida, Capacidad financiera
- Equipo profesional (tabla), Documentos, Cronograma
- Análisis crítico + Recomendación final
- Puedes Imprimir / Guardar PDF del reporte.
Import diario automático (launchd)
Configuración ya instalada en ~/Library/LaunchAgents/com.secop.import-daily.plist. Corre cada día a las 03:00 AM:
# Ver estado
launchctl list | grep secop
# Disparar manualmente (sin esperar a las 3 AM)
launchctl kickstart "gui/$(id -u)/com.secop.import-daily"
# Ver último log
tail -50 ~/Library/Logs/secop-import/$(date +%Y-%m-%d).log
# Desactivar
launchctl unload ~/Library/LaunchAgents/com.secop.import-daily.plist
El import es incremental — solo trae procesos publicados después del último watermark (en la tabla import_state). Sin captcha porque usa la API SODA oficial.
Scripts en bin/
| Script | Propósito |
|---|---|
web | Inicia Sinatra (Ruby) |
anexos-service.mjs | Servicio Node persistente — Chromium + Ollama, endpoint HTTP |
secop2-login.mjs | Abre Chrome headed para pasar reCAPTCHA y guardar sesión |
import-api.mjs | Import desde SODA (INCREMENTAL=1 para solo nuevos) |
import-daily.sh | Wrapper que detecta node correcto + maneja logs (lanzado por launchd) |
anexos.mjs | Pipeline batch CLI (alternativa al servicio) |
anexos-loop.sh | Loop wrapper para batches grandes |
anexos-ocr.mjs | OCR diferido (procesa anexos marcados deferred-ocr) |
anexos-summarize.mjs | Pasada IA posterior (sin tocar SECOP) |
compress-extracted-text.mjs | Backfill gzip de proceso_anexos.extracted_text (~76% ahorro). Idempotente. |
rotate-logs.sh | Rota /tmp/secop-*.log cuando superan 10 MB (5 rotaciones). |
backup.sh | Backup selectivo de tablas de usuario + data/ (~700 KB/día, rotación 7d). |
Variables de entorno
| Variable | Default | Dónde |
|---|---|---|
SECOP_DB_PATH | db/secop.sqlite3 | Ruta de la BD |
OLLAMA_URL | http://localhost:11434 | Endpoint Ollama |
OLLAMA_MODEL | qwen2.5:14b | Modelo a usar (override en cualquier script Node) |
SERVICE_PORT | 5678 | Puerto del servicio Node |
PORT | 4567 | Puerto Sinatra |
BIND | 127.0.0.1 | Bind Sinatra |
PUMA_WORKERS | 2 (macOS) / 0 (Windows) | 0 = single-mode (forzado en Windows: no hay fork()) |
PUMA_THREADS | 0:16 | min:max threads por worker |
PUMA_WORKER_TIMEOUT | 60 | segundos sin progreso → kill worker |
MAX_PAGE | 2000 | tope de paginación (cap de OFFSET) |
PER_PAGE | 50 | filas por página en /listados |
GZ_THRESHOLD | 2048 | bytes mínimos para comprimir extracted_text |
LOG_MAX_BYTES | 10485760 | umbral de rotación (10 MB) |
LOG_KEEP | 5 | rotaciones mantenidas |
LIMIT | 10 | Procesos por batch (anexos.mjs) |
MAX_BYTES | 26214400 | Tamaño máx por anexo (25 MB) |
OCR_MAX_PAGES | 10 | Páginas a OCR-ear por PDF |
SECOP_APP_TOKEN | — | Token Socrata (opcional, mejor rate-limit). Obtener en https://evp.colombiacompra.gov.co/ o https://www.datos.gov.co/profile (sección "App Tokens"). Guardar en ~/.secop_token con permisos 600 (lo carga import-daily.sh). |
INCREMENTAL | — | =1 para usar watermark en import-api.mjs |
SKIP_LLM | — | =1 para migración rápida sin Ollama |
SKIP_OCR | — | =1 para diferir OCR |
SKIP_PAA / SKIP_PROCESOS | — | =1 para saltar fuente en score-alertas.mjs |
FORCE | — | =1 para reprocesar incluso si ya OK |
Esquema de la BD
Tablas públicas (re-importables desde SODA):
procesos -- 2.99 M, una fila por numero_constancia (con FTS5 procesos_fts)
proceso_anexos -- 1 fila por anexo descargado/analizado (extracted_text_gz comprimido)
paa_items -- Plan Anual de Adquisiciones (anticipación de licitaciones)
unspsc_catalog -- Clasificador UNSPSC oficial
import_state -- watermark por dataset (SECOP I, II, PAA) para incremental
Tablas de usuario (cubiertas por bin/backup.sh):
empresa_perfiles, perfil_documentos, perfil_balances, perfil_desempates, perfil_certs
workflows, workflow_tareas
preferencias_vigilancia, sync_auto_log
ofertas_economicas, hojas_vida, enfoques_estrategicos
planes_desarrollo, plantillas_observaciones
alertas, notificaciones, firma_eventos, jobs
Troubleshooting
| Síntoma | Causa probable | Fix | |
|---|---|---|---|
| Botón "Procesar con IA" no avanza | Servicio Node no corre | node bin/anexos-service.mjs | |
| Análisis falla con "sesión expirada" | Cookies SECOP II caducaron | Clic en "Renovar sesión SECOP II" | |
q=palabra no encuentra resultados que sí están en SECOP II | Watermark desactualizado | INCREMENTAL=1 node bin/import-api.mjs | |
| Sinatra se cuelga en alguna página | Reinicia Puma | `lsof -ti :4567 \ | xargs kill -9 y relanza bin/web` |
| Re-análisis muy lento | qwen2.5:14b en máquina chica | Cambiar a OLLAMA_MODEL=qwen2.5:7b | |
| BD muy grande | WAL acumulado | sqlite3 db/secop.sqlite3 "PRAGMA wal_checkpoint(TRUNCATE)" |
Notas operativas
- El
community.secop.gov.corequiere captcha reCAPTCHA v2 para cargar páginas de detalle. Nunca se evade: el usuario lo pasa manualmente una vez por sesión (vía botón "Renovar"). La sesión persiste varias horas hasta que SECOP la invalida. - La SODA API (
datos.gov.co) es la fuente oficial sin captcha. Cubre toda la metadata. Los PDFs solo se obtienen vía portal. - La sesión Playwright (
storageState) se guarda en.secop2-storage.json(gitignored). - El análisis IA se procesa on-demand por proceso, no en bulk. Cada análisis quema ~5-25 min de CPU local.
Licencia y uso
Investigación académica de transparencia en contratación pública. Datos son públicos (Ley 1712 de 2014). No redistribuir el corpus completo sin acuerdo con Colombia Compra Eficiente.