Repository navigation
Releases: ssolis-ti/compra-agil-mcp
Release list
v2.10.0
Base del espacio de cotización: leer todos los adjuntos, exportar búsquedas completas y trabajar con varios tickets.
Añadido
- Se leen los adjuntos en Word, Excel, CSV y ZIP (2.10.0, fase 1).
descargar_y_leer_documentoleía solo PDF, y en una muestra de 244 adjuntos reales el 43 % era de otro formato (33 %.docx, 7 %.doc).- Formatos: ahora lee
.docx(con sus tablas, fila por fila),.doc,.xlsx(cada hoja),.csvy.zip(lee lo que trae adentro). - Detección: el formato se reconoce por el contenido, no por el nombre del archivo.
- Caché: el texto de cada adjunto se guarda 24 h, así que consultarlo de nuevo no lo vuelve a descargar.
- Lo que no se lee: imágenes y
.xlsantiguos; la herramienta lo dice y entrega la ficha. - Seguridad: las fórmulas y macros nunca se ejecutan, y hay límites contra archivos enormes y bombas ZIP.
- Sin módulos nativos: la extensión sigue arrancando con el Node de Claude.
- Formatos: ahora lee
- Nueva herramienta
exportar_procesos(2.10.0, fase 2). Exporta a Excel, CSV o JSON todas las compras de una búsqueda, no solo una página de 10.- Filtros: los mismos de
buscar_compras_agiles, másformatoymax_paginas(20 por defecto, máximo 50). - Respuesta: la ruta del archivo y un resumen (cantidad, monto total, mediana, por organismo y por región), no las filas.
- Búsquedas largas: siguen en segundo plano. Se vuelve a llamar con los mismos filtros para obtener el archivo, sin repetir consultas.
- Más páginas que el tope: la respuesta dice con qué
publicado_hastaseguir. - Una página que falla: se exporta lo leído y se dice desde qué página falta.
- El CSV usa
;y abre directo en Excel en español.
- Filtros: los mismos de
- Varios tickets con rotación (2.10.0, fase 3; MEJORA-006). Con
COMPRA_AGIL_TICKETS(separados por coma) las consultas se reparten entre varios tickets. La velocidad no sube: el freno por minuto es uno solo.- Si uno recibe 429 se salta hasta que pase su espera; si es rechazado (401/403) queda fuera hasta reiniciar. En los dos casos la consulta sale por otro.
- Cuota por ticket: cada uno lleva su conteo, guardado con un hash, nunca con el ticket. El conteo de antes pasa al primero.
verificar_ticket,obtener_estadisticas_usoy--checkmuestran cada ticket enmascarado con su estado.- Con un solo ticket todo funciona igual que antes.
- ⚠ Usa solo tickets de tu propia organización: juntar tickets de distintas personas para multiplicar la cuota puede ir contra los términos de ChileCompra.
Corregido
- El
.gitignoredejaba fuera un archivo de prueba (test/fixtures/adjuntos/), y la CI fallaba. Las reglas de datos quedan ancladas a la raíz.
v2.9.3
Mejoras reportadas por Hermes al usar el servidor.
Corregido
- Leer una sección de un adjunto (MEJORA-001).
querydevuelve la sección completa de cada coincidencia (hasta el siguiente encabezado I., II.…), no una sola línea, y respetamax_caracteres. Condesde_caracterse lee un documento largo por partes. - El aviso de que la vigilancia no puede revisar ya no adivina la causa (MEJORA-002). Tras un reinicio (por ejemplo, un corte de luz) dice que se reinició; en los demás casos da solo el hecho y el último error real.
fecha_ultimo_cambioadvierte que es el sello del lote de 5 minutos (MEJORA-003), no la hora exacta del cambio.consultar_documentos_localesordena mejor (MEJORA-004). Las palabras raras pesan más que las comunes, y el nombre del archivo cuenta. Además, los fragmentos ya no empiezan a mitad de palabra.- «1 día corrido» en la carta de cotización (MEJORA-005), y el nuevo
tipo_diaspermite días hábiles.
v2.9.2
Corregido
- Los adjuntos de Compra Ágil se pueden leer (BUG-002).
descargar_y_leer_documentoafirmaba que era imposible y ahora lee las bases y los términos de referencia de una compra. Basta el código de la compra;nombre_adjuntoelige entre varios archivos, y la respuesta nombra los demás.obtener_enlace_documentoentrega la ficha y la lista de adjuntos.- Errores claros: si algo falla, dice qué falló (proceso no encontrado, archivo vacío, rechazo del portal, tiempo agotado) y entrega la ficha.
- Interruptor:
COMPRA_AGIL_ADJUNTOS=offapaga la lectura de adjuntos.
v2.9.1
Corregido
- Telegram en grupos y canales (un usuario no pudo conectar el bot a un canal):
- Canales: ahora se detectan. Lo que se publica en un canal le llega al bot como
channel_post, y antes solo se leían los mensajes directos. - Grupos: con el modo privacidad, que viene activado, el bot no lee un «hola» en un grupo. Ahora basta con agregarlo al grupo, o escribir
/start@NombreDelBot. - Bot expulsado: si lo sacan de un grupo, ese grupo deja de elegirse.
- Grupo convertido en supergrupo: el envío se reintenta en el id nuevo, y el chat conectado con
conectar_telegramlo guarda solo. - Grupos con temas: el aviso llega al tema desde donde se conectó (
COMPRA_AGIL_TELEGRAM_THREAD_IDsi se fija en el.env). - Errores comunes, explicados: sin permiso para publicar, bot expulsado, chat inexistente, webhook ajeno, entre otros.
- Instrucciones por caso: el asistente,
--configurary el manual dicen qué hacer para un chat personal, un grupo o un canal.
- Canales: ahora se detectan. Lo que se publica en un canal le llega al bot como
- El servidor arranca sin ticket (9-oct). Glama y otros catálogos de MCP lo lanzan sin credenciales para listar sus herramientas, y terminaba con código 1 al arrancar. Ahora arranca y lista las 23 herramientas, y cada consulta a la API responde «Falta el ticket», con cómo pedirlo y dónde ponerlo, sin salir a la red. La vigilancia no se retoma sin ticket.
Cambiado
- Textos más simples: los avisos del sistema que llegan por Telegram y correo, el encabezado de la vigilancia en la terminal, el README y las guías ahora hablan en palabras del usuario. Ya no aparecen «lotes», «API» ni nombres de variables donde no hacen falta. Ejemplos: «Resumen del día: revisó 50 compras y te avisó de 2», o «La vigilancia no puede revisar las compras».
v2.9.0
Vigilancia confiable de compras nuevas, con avisos por Telegram, correo y webhook firmado. Se instala y configura conversando con el asistente, también como extensión de Claude Desktop. Especificación: docs/internals/specs/2.9.0-vigilancia-y-avisos/.
Corregido
-
Revisión de código de la 2.9.0 (9-oct):
- Modo «siempre» con Claude Desktop normal: como extensión, la tarea usaba
Claude.execomo si fuera Node, en cualquier instalación (no solo la de la Store), y abría la app en vez de vigilar. Ahora usa el Node del PATH, y si no hay, lo dice. La tarea recibe también los criterios y canales de entorno que faltaban (MONITOR_SOLO_NUEVAS,MONITOR_TODAS_EN_REGION, intervalo, destinatarios). - Correo desde
--configurar-web: se activaba solo si el.envno fijabaCOMPRA_AGIL_AVISOS. Ahora se suma a la lista. - Vigilancia atrasada: una que se atrasaba cada vez más sin que fallara ninguna ronda nunca se avisaba como ciega. Una ronda parcial ya no cuenta como revisión completa, y la recuperación exige una revisión completa posterior.
- Reapertura en segundo llamado: un proceso avisado al publicarse no se volvía a avisar al reabrirse en segundo llamado. Ahora sí, con 🔁.
- Código de vinculación de Telegram: pasa a guardarse con scrypt y sal. Con el hash rápido anterior, quien leyera el archivo recuperaba el código en menos de un segundo.
- Cola del gateway: solo se llena si un gateway la pidió en los últimos 7 días. Antes duplicaba la bandeja y mostraba «sin confirmar» a quien no usa gateway.
- Instrucciones al modelo: ya no dicen que ninguna herramienta cambia destinos, porque
conectar_telegramlo hace con código.
- Modo «siempre» con Claude Desktop normal: como extensión, la tarea usaba
-
Un cambio de criterios a mitad de ronda ya no avisa lo que no corresponde (2.9.0, prueba real del 8-oct). Una ronda empezó con los criterios a medio configurar (sin región) y, aunque a los segundos ya decían «solo Aysén», mandó 169 avisos de 16 regiones. Ahora la ronda relee los criterios guardados antes de cada lote y vuelve a filtrar justo antes de avisar. Lo descartado no queda marcado como avisado.
-
El modo «siempre» lo dice claro en la versión de la Microsoft Store (2.9.0, prueba real del 8-oct). Ahí la extensión corre con el Node interno de Claude, que no se puede lanzar desde una tarea programada, y Windows guarda sus datos dentro del paquete (
LocalCache), donde la tarea no los vería.activar_vigilancia(siempre)ahora responde qué usar en lugar de instalar una tarea que no funcionaría. El manual suma tres pasos que salieron de la prueba:- revisar que la extensión quedó habilitada;
- qué significa el aviso de «no firmada»;
- cómo quitar una instalación anterior con Claude cerrado.
-
Apagar la vigilancia la apaga en todos los procesos (2.9.0, prueba real del 8-oct). Con varias sesiones de Claude abiertas, cada una con su servidor,
activar_vigilancia(apagar)detenía solo el bucle del proceso que la recibía, y otra sesión siguió mandando avisos. Ahora cada bucle «con Claude» lee la preferencia guardada antes de cada ronda. -
La extensión de Claude Desktop ya arranca (2.9.0, prueba real del 8-oct). Con el Node que trae Claude, el proceso moría apenas lo lanzaban, sin escribir ni una línea en el log, y Claude Desktop decía «No se puede conectar al servidor de la extensión». La causa era
pdf-parse, que al importarse carga un módulo nativo (@napi-rs/canvas). Ahora el lector de PDF se carga recién al leer el primer PDF: el servidor arranca siempre, y si ese módulo no carga, solo falla la lectura de PDF, con un mensaje claro. -
La vigilancia encendida desde el chat ya no se queda sin tiempo (2.9.0, prueba real del 8-oct). Al encenderla con
activar_vigilancia, el bucle quedaba atado a los 45 s de esa herramienta. Vencidos, cada consulta de las rondas siguientes se cancelaba a los 5 s o ni se enviaba, y en una hora quedaron 11 lotes pendientes. Ahora el bucle corre fuera del presupuesto de la herramienta que lo enciende. No afectaba al daemon ni a la vigilancia que vuelve sola al abrir Claude. -
El daemon de vigilancia ya no pierde procesos sin decirlo (2.9.0, fase 2). Hasta la 2.8.0 tenía tres fallas:
- revisaba «la última hora» y, si un ciclo fallaba, esa hora no se volvía a mirar;
- cortaba en 100 procesos por ciclo sin avisar;
- al paginar, un proceso que cambiaba a mitad de la lectura hacía saltar a otro.
Ahora lee la API lote por lote (la API registra los cambios cada 5 minutos) desde una marca del último lote completo. Comprueba cada lote paginado (total y códigos) y lo relee o lo lee por región si no cuadra. Los lotes que fallan se reintentan antes de avanzar, y más de 48 h sin vigilar se informan como hueco. Un solo vigilante a la vez, con latido. El estado pasa a
.vigilancia.json(versión 2) y migra el.monitor-state.jsonanterior sin perder la deduplicación. -
El presupuesto mínimo por defecto de la vigilancia pasa de $5.000.000 a 0. El anterior dejaba fuera el 84 % de los procesos publicados. El intervalo por defecto pasa de 60 a 15 min: con lotes, el intervalo solo cambia cuánto tarda el aviso, no la cuota.
Añadido
- Correo con dos datos (2.9.0, pedido del 8-oct): basta la dirección y una contraseña de aplicación (
COMPRA_AGIL_CORREOyCOMPRA_AGIL_CORREO_CLAVE).- Proveedores: el servidor se deduce del dominio (Gmail, Yahoo, iCloud, Zoho, GMX). Para un correo de empresa con Google Workspace o Microsoft 365,
--configurarlo busca en el DNS (MX). - Destino y activación: los avisos llegan a la misma dirección y el canal se activa solo.
- Contraseña: se le quitan los espacios con que se muestran las contraseñas de aplicación, y el formulario web y
--configurarla prueban contra el servidor real antes de guardarla. - Extensión: suma los campos «Tu correo», «Contraseña de aplicación» y «Servidor de correo».
- Outlook personal: Outlook.com, Hotmail y Live se rechazan con el porqué (Microsoft exige OAuth) y qué usar en su lugar.
- Configuración anterior: las variables
SMTP_*siguen valiendo y mandan.
- Proveedores: el servidor se deduce del dominio (Gmail, Yahoo, iCloud, Zoho, GMX). Para un correo de empresa con Google Workspace o Microsoft 365,
- Solo compras nuevas (2.9.0, prueba real del 8-oct). La API informa lo que cambió en cada lote, y de 22 avisos de Aysén 6 eran procesos de días atrás que se modificaron.
- Qué se avisa ahora (por defecto): lo publicado en las 24 h previas, y lo reabierto en segundo llamado sin ofertas, marcado con 🔁.
- Cómo desactivarlo:
solo_nuevas=falseenconfigurar_criterios, oMONITOR_SOLO_NUEVAS=false. - Integraciones: el webhook y el gateway reciben
publicada_hora_chileysegundo_llamado.
- Avisos de Telegram más claros (2.9.0, pedido del 8-oct):
- Una etiqueta por línea: 🔖 Código, 🏛 Organismo, 📍 Región, 💰 Presupuesto, 🗓 Publicada, ⏰ Cierra (en negrita, con día de la semana y cuánto falta) y 🔗 Ver ficha.
- Orden: las compras van numeradas y ordenadas por cierre, la más urgente primero. Las de la alerta total de la región ocupan dos líneas con código, cierre y monto.
- Coincidencia: «Calza con» solo aparece cuando hay una palabra clave.
- Alerta total de la región (2.9.0, pedido del 8-oct): con
todas_en_regionenconfigurar_criteriosoMONITOR_TODAS_EN_REGION=true, además de lo del rubro llega toda compra ágil nueva publicada en las regiones elegidas, sin mirar palabras ni monto.- Formato: el aviso pone primero lo del rubro, completo (🎯), y después lo demás de la región, en una línea cada una (📍). Funciona igual en Telegram y en el correo, y el webhook y el gateway reciben el campo
nivel(rubrooregion). - Límites: sin regiones no aplica, para no avisar todo el país. Las exclusiones y «solo sin ofertas» siguen valiendo, y no gasta consultas extra, porque la vigilancia ya lee todo el país.
- Formato: el aviso pone primero lo del rubro, completo (🎯), y después lo demás de la región, en una línea cada una (📍). Funciona igual en Telegram y en el correo, y el webhook y el gateway reciben el campo
- Instalación y configuración a través del agente, para cualquier nivel (2.9.0, anexo):
- Extensión de Claude Desktop (
.mcpb): doble clic, sin terminal ni Node. El ticket y el token del bot se piden en la ventana de la extensión y quedan en la bóveda del sistema. - Prompt
empezar: lleva a cualquier usuario de cero al primer aviso conversando. conectar_telegram: vincula el chat con un código de 6 dígitos que el usuario dicta (ADR 0027), para que nadie pueda desviar los avisos.activar_vigilancia: «con_claude», mientras Claude está abierto y sin instalar nada, o «siempre», como tarea de Windows y con confirmación explícita (ADR 0028).--configurar-web: un formulario local para que un agente con terminal configure los secretos sin que pasen por el chat.- Manual y README por perfil.
- Extensión de Claude Desktop (
- Pensado para quien no programa (2.9.0):
node dist/index.js --configurar: un asistente en la terminal pregunta el ticket (oculto), qué vendes (y dice cuántas compras abiertas hay con cada palabra), región y monto mínimo. Además conecta Telegram (encuentra el chat solo y manda una prueba), escribe el.envcon respaldo del anterior y deja la vigilancia encendida al iniciar Windows.- Prompt MCP
configurar_vigilancia: ajusta los criterios conversando, prueba las palabras contra compras reales y guarda solo con tu confirmación; no pide secretos en el chat. - Manual de uso en lenguaje simple y un «Empieza aquí» al inicio del README.
- Vigilancia probada contra la API real (8-oct): la primera prueba no leyó ningún lote en 15 minutos. La API tardaba ~10 s por consulta, con timeouts, y los lotes de la mañana traían hasta 8 páginas que se pedían una por una, en rondas que solo guardaban al final.
- Arreglos: las páginas de un lote van en paralelo, y el daemon hace rondas de 4 minutos como máximo, que guardan, avisan y siguen enseguida si quedaron atrasadas.
- Resultado de la segunda prueba: 11 lotes y 592 procesos en ~9 minutos.
- **Cuota cor...
v2.8.0
Versión cerrada: junta los arreglos que encontró el enjambre de agentes contra la API real (6-oct) y las dos simulaciones anteriores, las fases de calidad y rendimiento del Sprint 1 y las de operación del Sprint 2. El cambio de fondo es la hora: la API entrega hora de Chile, no UTC, y hasta la 2.7.0 cada cierre se mostraba 3 horas antes y el radar ocultaba los procesos que cerraban en las 3 horas siguientes. No hay cambios incompatibles en los parámetros; sí cambian los valores de las horas, que ahora son los correctos.
Seguridad
- SDK de MCP 1.29.0 → 1.32.1 por el aviso GHSA-6qxp-vccf-f47h (severidad alta, versiones 1.12.0–1.30.1): el cliente OAuth del SDK podía enviar credenciales a un servidor de autorización elegido por el servidor MCP. Este servidor usa solo el lado servidor sobre stdio y no usa OAuth, así que no estaba expuesto, pero el aviso hacía fallar la CI (
npm audit --omit=dev --audit-level=high) desde su publicación.npm audit --omit=dev: 0 vulnerabilidades.
Corregido — la API entrega hora de Chile, no UTC
- Los cierres se mostraban 3 horas antes (4 en invierno). La API entrega
fecha_cierre,fecha_publicacionyfecha_ultimo_cambioen hora de Chile, esta última con una "Z" que no le corresponde. Hasta la 2.7.0 se leían como UTC. Medido el 6 de octubre contra la API real, con el reloj verificado contra el SHOA: a las 15:42 de Chile el cambio más reciente decía15:40Z, y 89 de 92 cierres caen en horario de oficina leídos como hora de Chile (leídos como UTC aparecían cierres a las 03:00 y 05:00). La validación de la 2.7.0 había concluido lo contrario porque supuso que el filtro de la API compara bien las horas; comete el mismo error ahí. - El radar ocultaba los procesos que cerraban en las 3 horas siguientes, justo los más urgentes: los daba por cerrados. Ahora los muestra, con su puntaje de urgencia.
monitorear_cambios_recientescon menos de 180 minutos volvía siempre vacío, y con más devolvía una ventana 3 h más corta:ttl_cambio_mscompara las marcas de la API contra la hora UTC real. Las dos ventanas se mandan ahora como rangocambio_desde/cambio_hastaescrito como la API compara. Verificado: los últimos 60 minutos trajeron 1.517 cambios, el más reciente de hace 2 minutos.- Un rango
cambio_desde/cambio_hastasalía corrido 3 horas, y los dos extremos se comparaban como texto:10:00-03:00parecía anterior a12:00Z. Ahora se comparan instantes. verificar_ticketinformaba «0 cambios en la última hora» en pleno horario hábil, por el mismo filtro. Ahora consulta los últimos 10 minutos.- El daemon de monitoreo cubría 21 de las 24 horas pedidas por el mismo motivo.
- La auditoría calculaba la duración de los procesos con
new Date(), que depende de la zona del servidor.
Corregido — conclusiones que la evidencia no sostenía
- La auditoría inventaba un problema de requisitos (E1). Bastaba con que todas las cotizaciones fueran inadmisibles, o con que el proceso tuviera criterios ambientales, para marcar
requisitos_complejosy recomendar revisar «certificaciones difíciles de obtener». En el enjambre contra la API real, un proceso desierto por «PROVEEDOR SOBREPASA PRESUPUESTO MÁXIMO», con su única cotización inadmisible por precio, recibió esa recomendación. Ahora se leen las justificaciones de inadmisibilidad: si todas son de precio, no se infieren requisitos, y los criterios ambientales solo se mencionan como algo a revisar cuando ningún otro dato explica la deserción. - El precio por defecto del borrador no cabía en el presupuesto (E2). Era el presupuesto menos 10 % en neto: con IVA el total quedaba en el 107 % ($552.874 frente a $516.267). Ahora el total con IVA queda en el 90 % del presupuesto, que cabe lo incluya o no.
- El análisis de precios por código fallaba entero si no respondía el detalle del proceso (S12). Ahora toma el nombre del listado, que es más liviano y suele responder cuando el detalle da 504. Si tampoco responde, dice qué falló y sugiere
q. Si falla la búsqueda de históricos, dice que fue el primer paso y que no consultó ningún detalle. - La auditoría por código no entregaba nada si fallaba la búsqueda de comparables (S12). Ahora entrega la evidencia del propio proceso —motivo, cotizaciones, montos— y el fallo va como aviso de cobertura.
- Los comparables se buscaban con el nombre completo del producto (S14) y solo se encontraba el propio proceso. Ahora se usan hasta dos palabras con significado y, si no hay comparables, se reintenta una vez con una. Sin comparables, sugiere un término más general.
- La nota metodológica decía que los comparables eran «cerrado o desierto» (E3); el código usa solo desiertos, porque son los únicos que publican cotizaciones. La nota lo dice ahora, con el sesgo que implica.
- Con un solo comparable lo llamaba «promedio» (S13) y no advertía que lo cotizado es neto. La lectura dice cuántos comparables la sostienen (
comparables_usados), advierte que uno solo es una referencia débil y que si el presupuesto incluye IVA la holgura real es menor. - El borrador no avisaba que el pedido completo puede estar en los adjuntos. En el enjambre la API listaba un producto y el pedido real tenía cuatro, en un
.docx. Si hay adjuntos, el borrador lo advierte con el enlace a la ficha.
Corregido — errores en un solo formato, y sin gastar cuota en lo que se puede validar
- Un mismo tipo de problema salía en cuatro formatos (E5, S17): texto plano,
MCP error -32602: Input validation error…, «Error de validación:» y mensajes de zod en inglés («Invalid input: expected string, received undefined»), y solo algunos decían si se había gastado cuota. Ahora todo rechazo de una entrada —del esquema o de la herramienta— llega comoError de validación: <qué corregir>. No se consultó la API., con los mensajes de zod en español. Se instala en un solo punto (utils/validacion.ts), así que una herramienta nueva lo hereda; una prueba de protocolo lo verifica de punta a punta y falla si una versión del SDK cambia cómo rechaza argumentos. - Un código con formato imposible gastaba una consulta (E4).
obtener_detalle_compraconABCllegaba a la API para volver con un 400 que hablaba de fechas, deq/idy de regiones. Los códigos de compra y de OC se validan ahora en el esquema de las ocho herramientas que los reciben. - Los errores de la API nombran lo que falló (E4, E11, S18). El 400 sobre un código habla del código; el 404 dice si no existe la compra o la orden de compra; el 504 del detalle trae el enlace a la ficha.
tiene_ocytiene_orden_compraeranfalsesin dato (S18), mientras la misma respuesta advertía que eso no prueba que la OC no exista. Ahora sonnull(«no informado»).- El precio del borrador no decía de qué muestra salía (S18). Ahora nombra el término con que se buscaron los procesos comparables.
- El rechazo de
ruta_salidamostraba la ruta absoluta de la carpeta de informes (E10), con el usuario del sistema. Ahora muestra solo su nombre.
Corregido — documentos, textos y privacidad
- La búsqueda en documentos ponía el manual técnico antes que las guías en preguntas de negocio (E6). «¿Qué es un segundo llamado?» traía primero la tabla de herramientas del manual del servidor y dejaba fuera la guía que lo explica. Ahora el manual va primero solo en preguntas sobre el servidor o la API; en las de negocio, a igual relevancia, mandan las guías sobre todo lo de
docs/api/. Probado con los documentos reales: las cuatro preguntas del enjambre abren con una guía, y la de segundo llamado trae la explicación del primer y el segundo llamado. - Los fragmentos se repetían (E6). Dos coincidencias en líneas vecinas se incluían una a la otra como contexto, y un título repetido en cada página del PDF salía cinco veces. Ahora no se repite contexto ni línea.
- Palabras de relleno como «cuántos», «tengo» o «puedo» contaban como términos (E6), y si el mejor fragmento reúne menos de la mitad de los términos, la respuesta advierte que los documentos pueden no responder la pregunta de forma directa.
- El informe de competencia copiaba teléfonos y correos personales (E8) de la descripción libre de las cotizaciones. Ahora se omiten.
motivo_seleccionaparecía en procesos desiertos sin ninguna marca (E9). La fila trae_nota_motivo: es un criterio del comprador, no prueba una adjudicación. Y si la moneda no es CLP,_nota_monedaaclara quepresupuesto_clpya está en pesos.- La carta del borrador prometía «Cumplimiento garantizado con todas las especificaciones» (S10) sin conocerlas, y decía «No ha sido enviado» en el texto dirigido al comprador. Ahora deja un espacio para que el proveedor detalle el cumplimiento, y lo recuerda en
_campos_a_revisar. - La auditoría decía «Región 5» donde el detalle dice «Región de Valparaíso» (S10).
- El nombre de los archivos de informe usaba la hora del servidor (S10) y no coincidía con la hora impresa adentro. En un servidor en Punta Arenas (UTC-3 todo el año) el nombre salía con una hora de más en invierno.
- El pie de los cinco informes imprimía la hora de generación 3 horas corrida al pasar un instante del servidor por el lector de fechas de la API. Era una regresión del cambio horario de esta misma versión; se detectó y corrigió antes de publicarla.
Corregido — lo que encontró el segundo enjambre, ya sobre la 2.8.0
Cuatro agentes usaron la 2.8.0 contra la API real (62 llamadas, 0 fugas del ticket). La corrección horaria se confirmó en vivo y las estadísticas de precios se verificaron a mano sobre datos reales por primera vez. Aparecieron estos defectos, verificados con las respuestas reales que quedaron en caché:
- El análisis de precios podía promediar el producto equivocado. En «nitrilo», un proceso cotizaba depósitos químicos y guantes; ninguna línea decía «nitrilo» y se tomaba...
v2.7.0
Auditoría QA de la 2.6.1, dos simulaciones de uso con agentes y la validación contra la API real (etapa 1). Lo que impedía llevar el servidor a producción y lo que llevaba a un modelo a dar un consejo equivocado. 463 tests (eran 345), CI en Ubuntu y Windows con Node 20 y 22.
En npm la versión anterior publicada es la 2.5.0: la 2.6.0 y la 2.6.1 no llegaron al registro. Quien actualice desde npm recibe también sus cambios (secciones de abajo).
Cambios de comportamiento (detalle en cada sección):
- La carpeta de datos (caché, estado de cuota, daemon,
informes/) ya no depende de la carpeta desde la que se lanza el servidor. Ver «fase 1.0». ruta_salidadegenerar_informesolo acepta subcarpetas de la carpeta de informes; para otra carpeta,COMPRA_AGIL_INFORMES_DIR.regionfuera de 1-16 se rechaza en el servidor, sin consultar la API.- Una herramienta que antes esperaba hasta ~60 s en el límite propio de consultas por minuto ahora responde enseguida («reintenta en ~N s») o con resultados parciales.
- En
auditar_compras_desiertas,analisis_de_brechaspuede traernull(no evaluable); la evidencia del propio proceso va primero en las recomendaciones. analizar_precios_mercadoconcodigo_compraes nacional salvo que se paseregion.
Corregido — paquete de npm
- El paquete no traía las guías ni los manuales.
filesdepackage.jsonsolo incluíadist/, así que connpxonpm install -gla carpetadocs/no existía:consultar_documentos_localesy el recursocompra-agil://documentacion/…quedaban vacíos sin decir por qué. Ahora se publicandocs/api/ydocs/guias/;docs/internals/(notas de ingeniería) sigue fuera. Lo detectó la revisión del contenido del paquete antes de publicar;test/paquete.test.tslo comprueba connpm pack --dry-run.
Seguridad — dependencias de desarrollo
- vitest 3.2 → 4.1.11.
npm auditmarcaba 3 vulnerabilidades (2 críticas entinypool, de ejecución de código por contaminación de prototipos, y 1 moderada en@vitest/mocker, de lectura de archivos). Solo afectaban a quien corre los tests: el paquete publicado no las incluye, ynpm audit --omit=devya daba 0. Se eligió la 4.1.11 y no la 5, que exige Node 22.12 y dejaría fuera a Node 20, todavía soportado. El árbol de producción no cambia.
Añadido — publicación
- Publicación automática en npm (
.github/workflows/publicar.yml): al empujar un tagvX.Y.Z, comprueba que exista el secretNPM_TOKEN, que el tag coincida conpackage.json, que esa versión no esté publicada y que el CHANGELOG tenga su sección; publica (con build y tests porprepublishOnly, y procedencia firmada si el repositorio es público) y crea la release de GitHub con esa sección como notas.
Documentación — validación y retome
- README: la instalación manual empieza con
git clonee indica la versión de Node, y la opciónnpxavisa que npm puede ir atrás del repositorio (hoy sirve la 2.5.0). El punto de retome de QA (docs/internals/qa/README.md) y la fase 0 del roadmap reflejan que los PR #8 a #13 ya están enmainy que falta la validación real y el tag. - Validación de la 2.7.0 contra la API real (
docs/internals/qa/resultado-api-real.md): sin bloqueantes, 17 consultas y ningún 429. Queda confirmado quefecha_cierresin zona es UTC: el estado pasa acerradaen el primer ciclo de 5 minutos después del cierre, en el mismo reloj quefecha_ultimo_cambio, y la ventana relativattl_cambio_msprueba que ese reloj es UTC real. De noche la API puede dejar un procesopublicadacon el cierre ya vencido.
Corregido — rutas independientes del directorio de trabajo (fase 1.0)
- Lanzado por un cliente MCP como indicaba el README, el servidor no encontraba el
.envy repartía su estado por la carpeta del cliente. Todo se resolvía conprocess.cwd(), y Claude Desktop lanza el servidor desde su propia carpeta: sin el ticket en texto plano en la config del cliente, terminaba al arrancar (medido el 5 de octubre; se rodeaba concmd /c cd /d <proyecto> && …). Ahora el.envse busca en la carpeta de trabajo, la del proyecto y la de datos, y caché, estado de cuota, estado del daemon e informes van a una carpeta de datos fija:COMPRA_AGIL_DATA_DIR, la del proyecto si es un clon, o una del usuario si se instaló connpx. El README ya no pide el ticket en la config del cliente.- ⚠ Cambio de comportamiento: quien lanzaba el servidor desde otra carpeta encontrará la caché, el estado y los
informes/en la carpeta de datos, no en esa carpeta.
- ⚠ Cambio de comportamiento: quien lanzaba el servidor desde otra carpeta encontrará la caché, el estado y los
Corregido — rendimiento (fase 1.1)
- El freno propio de 15 consultas/min podía retener una herramienta hasta el corte del cliente MCP. Medido: una consulta esperó 57 s en la batería de
scripts/qa/, y en la simulación con agentes cuatro llamadas tardaron 25-45 s con la API respondiendo en ~400 ms. Ahora cada llamada a una herramienta tiene un presupuesto de 45 s (COMPRA_AGIL_PRESUPUESTO_MS): si una consulta tendría que esperar más de lo que queda, no se envía —no gasta cuota— y la herramienta responde con lo que tenga. Las de análisis distinguen en sus avisos los detalles que fallaron en la API de los que se omitieron por tiempo (procesos_que_fallaron_detalle[].motivo). El corte de cada consulta tampoco pasa lo que le queda a la herramienta. - Las consultas que esperaban turno salían en ráfaga. El freno esperaba a que saliera la más antigua y, al despertar, anotaba la hora sin volver a mirar: varias esperas simultáneas superaban juntas el máximo. Ahora cada consulta reserva su turno antes de esperar, en orden de llegada.
- Dos herramientas simultáneas sumaban el doble del límite de concurrencia. Cada tanda contaba solo sus consultas en vuelo; ahora el conteo es global, y el último HTTP de error se anota por llamada a herramienta, no por cliente.
Corregido — hallazgos de la simulación de uso con agentes (fase 1.8)
- S1 ·
auditar_compras_desiertasconcluía lo contrario de la evidencia del propio proceso. Solo comparaba contra otros procesos: uno desierto por «Ofertas sobre el presupuesto disponible», con una cotización 33 % sobre su presupuesto, recibía «No se detectaron discrepancias… amarrados a una única marca». Ahora empieza por el motivo oficial (clasificado en presupuesto, requisitos, plazo o sin ofertas, sin inventar una causa si no se reconoce) y por las cotizaciones del proceso frente a su presupuesto, incluido el caso en que la oferta cabe en neto pero no con IVA. La nuevaevidencia_del_proceso_auditadoy el informe impreso lo muestran. Sin comparables distintos ya no se borra esa evidencia, y la sugerencia genérica sobre las bases solo aparece cuando no hay ninguna otra pista, sin atribuir causa. - S1b · La auditoría declaraba «presupuesto suficiente» aunque ninguna oferta cabía con IVA. El aviso solo se daba si el motivo oficial era presupuesto: en la segunda simulación, una desierta por «requisitos técnicos» con sus cuatro ofertas sobre el presupuesto con IVA (y tres en neto) salía con
presupuesto_insuficiente: false. Ahora se marca cuando ninguna cotización cabe con IVA o la mayoría supera el presupuesto en neto, cualquiera sea el motivo, y la recomendación explica que depende de si el presupuesto incluye IVA. - S11 · La descripción de
obtener_detalle_compraafirmaba algo no confirmado sobre las cotizaciones. Decía que «solo se muestran desde el estado "Cerrada" en segundo llamado», tomado de la guía oficial; lo medido es que las traen los procesos desiertos (5 de 8) y no los cerrados de primer llamado (0 de 8), y en eso se apoyaanalizar_precios_mercado. Un modelo que creyera la descripción descartaba los procesos útiles. Ahora dice lo medido y que una lista vacía de proveedores no significa que no haya ofertas (total_ofertas). - S16 · El informe impreso de precio decía menos que la herramienta JSON. No mostraba la suficiencia de la muestra, la cobertura ni la distribución sin inadmisibles, y su nota remitía a un campo JSON que quien lee el papel no ve. Ahora muestra la suficiencia (destacada si es baja), procesos encontrados, revisados y sin detalle, la frescura de la caché y una tabla «Con y sin inadmisibles».
- S2 · El borrador de cotización superaba el presupuesto del comprador sin avisar. En la simulación ofreció $9.148.113 con IVA frente a $9.021.000, con
_campos_a_revisar: []. Ahora compara el neto y el total con el presupuesto (la API no dice si lo incluye IVA), lo advierte con el precio unitario máximo que cabría en cada caso, y devuelvecomparacion_presupuesto,fecha_cierreyfecha_cierre_hora_chile. El informecotizacionmuestra el presupuesto, si el total cabe y el cierre en hora de Chile; su resumen ya no dice «Sin placeholders marcados» cuando hay advertencias. - S3 ·
analizar_precios_mercadoconcodigo_compracontradecía al borrador del mismo proceso. Imponía en silencio la región del comprador, respondía «quita el filtro de región» (un filtro que el usuario no puso) y no encontraba históricos que el borrador, que busca en todo el país, sí encontraba. Ahora el análisis es nacional salvo que se paseregion;region_analisisdice de qué región es el comprador y cómo acotar, y el mensaje sin resultados solo sugiere quitar la región si el usuario la pidió. - S4 · La descripción de
analizar_precios_mercadocontradecía su respuesta. Decía que las cotizaciones inadmisibles «se excluyen de las estadísticas»; el código y la nota de la respuesta las incluyen, y un modelo que solo leyera la descripción informaba lo contrario. Ahora la descripción dice que entran, la respuesta agregaestadisticas_precio_unitario_solo_admisiblespara comparar, y la nota cuenta las inadmisibles de esa muestra en vez de afirmar que «casi todas lo son». - S5 · La auditoría informaba «sin brecha» cuando no había podido comparar. Sin comparables devolvía
presupuesto_insuficiente: falsey el informe imprimía «Presupuesto en rango». Ahora cada brec...