Arce

Inteligencia de mercado sobre las compras del Estado uruguayo.

RolDiseño, desarrollo e infraestructura, de punta a puntaOrigenGrupo CPS, por un pedido de la direcciónAño2025–2026CódigoPúblico en GitHub
TypeScriptNode.jsExpressPostgreSQLZodOllamaAnthropicReactDocker
Auditable

Cada respuesta de la IA se guarda junto con las cifras exactas que vio. Cualquier conclusión se puede rastrear hasta el número que la respalda.

Arce junta el histórico de compras estatales de Uruguay (qué se adjudicó, a quién, a qué precio y qué organismo lo compró) y lo convierte en estadísticas comparables: qué productos conviene vender, a qué precio se gana y contra quién se compite. Un modelo de IA lee esas cifras y escribe el análisis para la dirección, sin calcular nada por su cuenta.

Arce nació para otra cosa: leer el portal de compras estatales (ARCE) cada 15 minutos y decirle a la empresa qué llamados abiertos de aluminio le servían. Esa parte sigue funcionando.

Después la dirección hizo una pregunta más grande: no qué licitación sirve hoy, sino qué conviene venderle al Estado, a qué precio y contra quién. Eso no sale de los llamados abiertos de un rubro. Hace falta todo el histórico de adjudicaciones, de cualquier producto, y compararlo de forma honesta.

Lo que había que tener en cuenta.

Todavía sin datos

La descarga masiva del histórico va a ser un servicio aparte. Este sistema tenía que estar listo para recibirla y sacar las estadísticas solo, sin trabajo manual, apenas lleguen los lotes.

Datos públicos, pero desprolijos

Pesos, dólares y unidades indexadas; kilos, litros y resmas; precios con y sin impuestos; llamados anulados; proveedores que a veces llegan sin RUT. Comparar eso sin normalizar es comparar ruido.

Millones de filas

Con el histórico completo, los rankings no se pueden calcular en cada consulta.

Números que se puedan defender

Las conclusiones van a la dirección para decidir qué vender. Una cifra sin origen, o una IA que inventa, no sirve.

Los números salen de SQL. La IA los interpreta, no los calcula.

Lo tentador era darle los datos a un modelo y pedirle conclusiones. Pero un modelo que calcula se puede equivocar sin que nadie lo note, y no hay forma de reproducir cómo llegó a un número.

Por eso las estadísticas se calculan en PostgreSQL y el modelo solo recibe un paquete con las cifras ya hechas. Responde con un esquema fijo y citando números, los productos que menciona se verifican contra el paquete, y cada respuesta queda guardada junto con lo que vio.

Por qué está construido así.

Contrato tolerante, estructura estricta

La API de ingesta acepta lo que el portal muestra (números con coma, fechas dd/mm/aaaa, la moneda como aparece) y lo normaliza. Pero un ítem sin número o una adjudicación sin proveedor se rechaza con el detalle del error, por llamado, sin tirar el lote entero.

Cada llamado viaja entero

Un llamado llega con sus ítems, ofertas y adjudicaciones y reemplaza su versión anterior en una transacción. El crudo se guarda con un hash: lo repetido no se reprocesa y, si cambian las reglas, todo se recalcula desde los crudos sin volver a scrapear.

Comparar solo lo comparable

Todo en pesos uruguayos sin impuestos, con la cotización del día de cada adjudicación. El precio se compara solo dentro de la misma unidad, sin llamados anulados, y con medianas y percentiles en vez de promedios.

Estadísticas precalculadas

Ocho vistas materializadas en PostgreSQL, con una fila por adjudicación como base. Se recalculan solas un minuto después del último lote y todas las noches, sin bloquear el tablero.

Un índice de oportunidad a la vista

Un puntaje de 0 a 100 por producto que combina volumen, frecuencia, cuántos organismos lo compran y qué tan repartido está entre proveedores. Los pesos están en una línea de SQL y el tablero muestra los cuatro componentes.

El mismo modelo, local o en la nube

El analista usa el proveedor del resto del sistema: Ollama en CPU (uno a tres minutos por informe) o la API de Anthropic (segundos). El paquete de datos se recorta al contexto de cada modelo.

Lo que descarté en el camino.

Que la IA calcule

Más rápido de armar, pero los números no se pueden reproducir ni auditar. El modelo lee cifras; no las produce.

Promedios

Un solo precio mal cargado en el portal arrastra la estadística entera. Medianas y percentiles lo absorben.

Un modelo de scoring entrenado

Sin datos reales todavía, una fórmula visible que se ajusta en una línea es más honesta que una caja negra. Primero hay que ver el mercado real.

Un endpoint por entidad

Ítems, ofertas y adjudicaciones por separado le complicaban la vida al scraper y hacían difícil que reenviar un lote no duplicara nada.

Del dato crudo a una recomendación con cifras.

  1. 01

    API de ingesta

    Recibe lotes de llamados históricos con token, los valida contra un contrato publicado como JSON Schema y guarda el crudo.

  2. 02

    Normalización

    Proveedores por RUT, artículos por código del catálogo de ARCE, organismos, monedas y unidades llevados a un mismo canon.

  3. 03

    Qué vender

    Ranking de productos por índice de oportunidad, con el desglose de cada puntaje.

  4. 04

    A qué precio

    Distribución del precio ganador por producto y unidad, tendencia mensual y, donde el portal publica el cuadro comparativo, cuánto más barata fue la oferta que ganó.

  5. 05

    Quién gana y quién compra

    Proveedores con su cuota, tasa de éxito y contra quién compiten; organismos con lo que más compran.

  6. 06

    El analista de IA

    Un informe del mercado para la dirección, un veredicto por producto (entrar, explorar o evitar, con precio de entrada) y respuestas a preguntas libres, siempre citando los datos.

La primera etapa sigue en el mismo servicio: el monitor del rubro lee el portal cada 15 minutos, analiza los pliegos con IA y deja los llamados en una bandeja de revisión. Lo que falta es el scraper del histórico; mientras tanto, el tablero se probó con tres años de compras sintéticas que entran por la misma API.

Cómo encaja todo, en un diagrama que se puede tocar.

Cuatro vistas con cajas que se pueden mover y un recorrido explicado paso a paso: el circuito completo del portal al tablero, cómo entra y se normaliza un lote, cómo una adjudicación se vuelve estadística y cómo la IA escribe el análisis sin inventar cifras.

Cómo fluyen los datos, del lote al tablero.

01

Scraper del histórico (pendiente)

Un servicio aparte que recorre el portal y manda lotes de llamados por API.

02

Ingesta

Token, contrato en Zod, crudo con hash y una transacción por llamado.

03

PostgreSQL normalizado

Organismos, proveedores, artículos, llamados, ítems, ofertas, adjudicaciones y cotizaciones.

04

Vistas materializadas

Una fila por adjudicación en pesos sin impuestos y, sobre ella, precios, rankings y competencia.

05

Analista (Ollama o Anthropic)

Lee un paquete de cifras y responde con JSON validado, guardado junto al paquete.

06

API y tablero

Express y React: qué vender, productos, competidores y compradores.

Vive en el mismo servicio en Docker que el monitor del rubro, con módulo y esquema de base propios: el pipeline original no se tocó.

Qué quedó funcionando.

Listo para datos

apenas el scraper mande lotes, las estadísticas se recalculan solas, sin pasos manuales.

Sin cifras inventadas

los productos que menciona la IA se verifican contra los datos que recibió; los que no existen se descartan.

Cobertura visible

el tablero dice qué parte de los datos tiene precio comparable y qué quedó afuera, y por qué.

Probado de punta a punta

con tres años de compras sintéticas por la misma API y un test de integración contra una base real.

El tablero, con datos de demostración.

Tablero de Arce: ranking de productos por índice de oportunidad y el informe de la IA
Qué vender: productos ordenados por índice de oportunidad, con sus cuatro componentes, y el informe que la IA escribe a partir de esas cifras. Datos sintéticos de demostración.
Ficha de producto en Arce con el veredicto de la IA
Ficha de un producto: el veredicto de la IA con precio de entrada apoyado en los percentiles, competidores que importan y a quién venderle primero. Datos sintéticos.
Ficha de un competidor en Arce
Ficha de un competidor: cuánto gana por mes, qué vende, a quién y con qué tasa de éxito. Datos sintéticos.
  • Cuando cambia la pregunta, cambia el dato que importa: pasar de "¿me sirve este llamado?" a "¿qué conviene vender?" movió el centro del sistema del pliego a la adjudicación.
  • Lo difícil de comparar precios no es la estadística sino la normalización: moneda, unidad e impuestos deciden si un número significa algo.
  • Separar quién calcula de quién interpreta deja que la IA sume sin convertirse en una fuente de cifras sin respaldo.
  • Construir el receptor antes que el scraper, y probarlo con datos sintéticos por la misma puerta, dejó el contrato validado antes del primer dato real.

¿Querés hablar de este proyecto?

El código es público. Si querés discutir el diseño o hablar de una posición, escribime.