En breve: TENDER es un sistema que cada día recorre 29 plataformas internacionales, recoge todas las licitaciones publicadas por la ONU y los bancos de desarrollo, y muestra al experto solo aquellas donde realmente merece la pena presentarse. La decisión la toma un modelo de lenguaje instalado en la máquina del cliente: ni el currículum ni los pliegos salen a ningún servicio externo.
Cliente: experto consultor independiente · Categoría: scraping · LLM local · bot de Telegram
¿Qué es lo esencial del proyecto?
- Más de 300 licitaciones nuevas al día en las plataformas de la ONU; al experto llegan unas pocas.
- Precisión de la selección del 100%: no hay ruido en la salida, no hay que descartar nada irrelevante.
- La IA trabaja en local. El currículum son datos personales y parte de los pliegos está bajo condiciones del cliente, por eso el modelo vive en su equipo y no en la nube.
- Coste de tokens cero: el gasto no crece con el número de licitaciones procesadas.
- 29 fuentes bajo una misma interfaz: agencias de la ONU, bancos de desarrollo, portales de la UE y la OSCE.
- Control desde Telegram: las ofertas llegan al chat, allí mismo se rechazan y desde allí se entra al panel sin contraseña.
¿Con qué está hecho el proyecto?
- Scraping:
- Python 3.12, httpx, BeautifulSoup, lxml. Para los sitios con JavaScript (bancos asiático y africano) Playwright a través de Bright Data Scraping Browser: Chrome real con renderizado remoto, sin navegador local.
- Antibot:
- Bright Data Web Unlocker para Cloudflare y JS, Scraping Browser para el renderizado completo.
- IA:
- modelo local Ollama con Qwen 2.5 y API compatible con OpenAI, desplegado en los equipos del cliente. Prompts en ruso, editables desde el panel.
- Backend y panel:
- Flask 3 con gunicorn, plantillas Jinja2, CSS propio con dos temas y versión móvil.
- Bot:
- aiogram 3 en long-polling, sin puertos abiertos.
- Base de datos:
- SQLite aislada, sin MySQL compartido.
- Exportación:
- openpyxl para Excel, reportlab para PDF con cirílico.
- Infraestructura:
- nginx con bloque propio y basic auth, systemd con cuatro servicios y temporizadores, usuario separado en el servidor.
- Integraciones:
- API de saldo de Bright Data, tipo de cambio del Banco Nacional de Kazajistán.
No se usa ningún servicio de IA externo. El modelo puede cambiarse sin reescribir el pipeline: la interfaz es compatible con OpenAI.
¿Cómo empezó todo?
Las licitaciones de la ONU son concursos de agencias de la ONU y bancos de desarrollo: consultoría, peritajes, investigaciones. Los anuncios están repartidos por decenas de sitios, entre ellos UNGM, UNICEF, PNUD y el Banco Mundial. Cada día salen más de trescientos nuevos y revisarlos a mano lleva horas.
El problema no es solo el volumen. El título de una licitación casi nunca describe el contenido: un proyecto puede llamarse «Gestión de finanzas públicas» y dentro pedir un ecólogo o un consultor local con dominio obligatorio del idioma del país. Eso no se filtra por el título, hay que leer la descripción entera.
Delegar ese trabajo a una IA en la nube no era una opción. Al contexto del modelo entra el currículum completo del experto, que son datos personales, y parte de los pliegos está bajo condiciones del cliente. Por eso el modelo se desplegó en su propia máquina.
¿Qué se ha implementado?
🧠 ¿Por qué el LLM funciona en el equipo del cliente?
- Ollama y Qwen 2.5 en la máquina del cliente: la decisión sobre la correspondencia se toma en local
- El currículum y los textos de los pliegos no salen a APIs externas: ni a la nube ni a registros ajenos
- Dos opciones de despliegue: equipos propios del cliente o servidor alquilado, con el mismo perímetro de privacidad
- Coste de tokens cero: el gasto no crece con el número de licitaciones procesadas
- Interfaz compatible con OpenAI: el modelo se cambia sin reescribir el pipeline
- Los prompts se editan desde el panel y de cada oportunidad se guarda el diálogo completo del modelo
🤖 ¿Cómo funcionan los scrapers y la recolección?
- 29 fuentes con una interfaz única Connector → Vacancy: agencias de la ONU (UNICEF, ONU Mujeres, UNFPA, OIM, UNGM, PNUD), bancos (Mundial, Asiático, Islámico, Africano, BERD, BEAD, FEDA), UE y OSCE (TED, OSCE, OIT)
- Cinco tipos de protocolo bajo una abstracción: HTML de servidor, API REST JSON, Oracle Recruiting Cloud (un motor para tres agencias), POST con fragmento HTML, sitemap XML
- Renderizado de sitios JS con navegador real allí donde la lista la monta un script de cliente
- Controlador de IA para la recuperación: si cambia la maquetación de la fuente y el scraper falla, el modelo reconstruye los campos de la ficha, anclado al HTML y con límite contra avalanchas
- La caída de una fuente no tumba la ejecución; hay antithrottling con pausas y deduplicación dentro de cada fuente
⚙️ ¿Cómo funciona el pipeline de procesamiento?
- Normalización: fechas de seis formatos incompatibles a ISO, países de códigos NUTS y formas libres a nombre y región según un catálogo de 249 países ISO 3166-1
- Determinación del país: heurística textual estricta con respaldo de IA cuando no se reconoce
- Deduplicación entre fuentes: una misma licitación aparece en varios portales, el sistema une los duplicados y conserva el registro más completo enriqueciéndolo con el resto
- Prefiltro de dos niveles: palabras STOP, palabras CORE, filtro territorial, idioma, consultor nacional, fecha límite. Todas las reglas se editan en el panel y el registro muestra con etiquetas cuáles se activaron
- Descarte de caducadas en dos puntos: en el prefiltro y de nuevo tras cargar los detalles, porque en los bancos la fecha límite solo se ve dentro
🌍 ¿Cómo funciona el filtro territorial?
- Catálogo de 249 países: ISO 3166-1, nombres en ruso e inglés, códigos, normalización
- Lista editable de países permitidos con búsqueda entre los 249
- Doble aplicación: en el prefiltro y otra vez tras la decisión del modelo
🧠 ¿Cómo selecciona la IA las licitaciones?
- Al modelo entra el currículum completo del experto: 69 proyectos, publicaciones, formación, idiomas, unos 34 000 caracteres, no un resumen
- Juicio por el contenido real del puesto y no por el título: el modelo lee la descripción completa y descarta lo que no encaja
- Veredicto estructurado: sí o no, motivo corto por regla, motivo largo descriptivo, resumen en ruso e inglés, país y marca de pliego adjunto
- Calibración demostrada sobre casos de referencia, con registro completo de cada oportunidad
- Prompts y perfil editables desde el panel, con protección frente a errores
🖥 ¿Qué hace el panel web de 13 páginas?
- Cuadro de mando: salud del servidor en tiempo real, embudo gráfico de todas las fases, estado de cada scraper
- Finanzas: gasto por ejecución, previsión mensual por mediana, coste de la ejecución y de cada oferta encontrada, saldo del proxy con botón de recarga
- Ofertas seleccionadas con ficha: descripción completa, adjuntos, registro de entrega, rechazo manual con motivo y palabra STOP
- Aceptadas, rechazadas y registro de todas las ofertas con búsqueda y filtro por etiquetas
- Ficha de cada una de las 29 fuentes: qué recoge, método, antibot, calendario
- Editores de STOP y SIGNAL, pestaña de territorio, registros, algoritmo con los prompts reales, editor de prompts, destinatarios, ajustes
- Exportación de listas a CSV, Excel, JSON y PDF respetando el filtro activo
- Gestión de usuarios del panel y acceso por enlace desde Telegram
- Dos temas, diseño adaptable, versión móvil con tarjetas
🤖 ¿Cómo sirve el bot de Telegram de mando a distancia?
- Menú con botones fijos: todas las ofertas, fuentes, estadísticas, ayuda
- Entrega automática de cada nueva oferta adecuada a los destinatarios activos
- El botón «No encaja» rechaza la oferta desde el chat, en sincronía con el panel
- Los adjuntos y los pliegos llegan como documentos
- Estadísticas ampliadas: embudo, base, saldos, eficiencia
- Acceso al panel sin contraseña mediante enlace personal
🚀 ¿Cómo funcionan la automatización y la producción?
- Pipeline diario por temporizador systemd: recolección, IA, entrega, recuperando las ejecuciones perdidas
- Planificador de informes: un temporizador horario comprueba a quién le toca, informes intermedios a la hora elegida y semanal los lunes, según la zona horaria de cada destinatario
- Ejecución manual con el botón «Lanzar ahora»
- Cuatro servicios en producción, aislados de los demás proyectos del servidor
💳 ¿Cómo se lleva el control financiero?
- Gasto por ejecución: tráfico de proxy y navegador, sin pagar tokens
- Eficiencia: coste de la ejecución y de cada oferta encontrada, previsión mensual
- Saldo del proxy en línea vía API con botón de recarga
¿Cuál es el resultado?
| Indicador | Valor |
|---|---|
| Licitaciones nuevas al día en las plataformas | 300+ |
| Fuentes monitorizadas | 29 |
| Precisión de la selección | 100% |
| Países en el catálogo del filtro | 249 |
| Proyectos del currículum que entran al modelo | 69 |
| Contexto por decisión | ~34 000 caracteres |
| Páginas del panel | 13 |
| Servicios en producción | 4 |
| Pago por tokens de IA | ninguno, el modelo es propio |
¿Dónde saber más sobre este caso?
La historia completa de por qué los títulos de las licitaciones mienten y cómo enseñamos a una IA a leer las licitaciones de la ONU de verdad: lee el artículo en LinkedIn.

