@ssolis-ti/mcp-compra-agil 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +3 -2
  2. package/dist/api/compra-agil-client.d.ts +35 -1
  3. package/dist/api/compra-agil-client.d.ts.map +1 -1
  4. package/dist/api/compra-agil-client.js +69 -4
  5. package/dist/api/compra-agil-client.js.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/services/monitor.js +1 -1
  9. package/dist/services/monitor.js.map +1 -1
  10. package/dist/tools/analizar-precios-mercado.js +2 -2
  11. package/dist/tools/auditar-desiertas.js +2 -2
  12. package/dist/tools/detalle-oc.d.ts.map +1 -1
  13. package/dist/tools/detalle-oc.js +2 -3
  14. package/dist/tools/detalle-oc.js.map +1 -1
  15. package/dist/tools/documentos.d.ts.map +1 -1
  16. package/dist/tools/documentos.js +68 -37
  17. package/dist/tools/documentos.js.map +1 -1
  18. package/dist/tools/estadisticas-uso.d.ts.map +1 -1
  19. package/dist/tools/estadisticas-uso.js +12 -2
  20. package/dist/tools/estadisticas-uso.js.map +1 -1
  21. package/dist/tools/generar-borrador.d.ts +25 -1
  22. package/dist/tools/generar-borrador.d.ts.map +1 -1
  23. package/dist/tools/generar-borrador.js +96 -78
  24. package/dist/tools/generar-borrador.js.map +1 -1
  25. package/dist/tools/verificar-oc.d.ts.map +1 -1
  26. package/dist/tools/verificar-oc.js +30 -4
  27. package/dist/tools/verificar-oc.js.map +1 -1
  28. package/dist/utils/cache.d.ts +53 -0
  29. package/dist/utils/cache.d.ts.map +1 -0
  30. package/dist/utils/cache.js +131 -0
  31. package/dist/utils/cache.js.map +1 -0
  32. package/dist/utils/doc-search.d.ts +52 -0
  33. package/dist/utils/doc-search.d.ts.map +1 -0
  34. package/dist/utils/doc-search.js +97 -0
  35. package/dist/utils/doc-search.js.map +1 -0
  36. package/dist/utils/error-handler.d.ts.map +1 -1
  37. package/dist/utils/error-handler.js +4 -6
  38. package/dist/utils/error-handler.js.map +1 -1
  39. package/dist/utils/rate-limiter.d.ts +37 -2
  40. package/dist/utils/rate-limiter.d.ts.map +1 -1
  41. package/dist/utils/rate-limiter.js +101 -13
  42. package/dist/utils/rate-limiter.js.map +1 -1
  43. package/package.json +1 -1
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Caché de respuestas de la API, con vencimiento y persistencia en disco.
3
+ *
4
+ * ⚠ POR QUÉ EXISTE: la cuota del ticket se agotaba en minutos. Medido en
5
+ * auditoría, evaluar UNA oportunidad con el flujo natural (radar → detalle →
6
+ * analizar_precios → generar_borrador → auditar_desiertas) costaba ~29
7
+ * consultas, porque las tres herramientas de análisis repiten exactamente la
8
+ * misma pareja de llamadas —`buscar({q, estado:'desierta'})` y luego
9
+ * `detalle()` sobre los primeros resultados— y no existía ninguna caché: el
10
+ * mismo histórico se descargaba hasta tres veces en cuestión de minutos.
11
+ *
12
+ * Un proceso ya declarado desierto es inmutable, así que reutilizarlo es
13
+ * gratis en exactitud y enorme en cuota.
14
+ *
15
+ * 🔒 El ticket NUNCA entra aquí: se excluye al construir la clave (el endpoint
16
+ * heredado de Órdenes de Compra lo lleva en el query string) y solo se
17
+ * guarda el cuerpo de la respuesta, que es información pública.
18
+ */
19
+ import fs from 'fs';
20
+ import { logger } from './logger.js';
21
+ /** Parámetros que jamás deben formar parte de la clave ni tocar el disco. */
22
+ const PARAMS_SECRETOS = new Set(['ticket']);
23
+ export class ResponseCache {
24
+ entradas = new Map();
25
+ rutaEstado;
26
+ maxEntradas;
27
+ aciertos = 0;
28
+ fallos = 0;
29
+ constructor(opciones = {}) {
30
+ this.rutaEstado = opciones.rutaEstado ?? null;
31
+ this.maxEntradas = opciones.maxEntradas ?? 500;
32
+ this.cargar();
33
+ }
34
+ /**
35
+ * Clave estable para una consulta: mismo endpoint y mismos parámetros
36
+ * producen la misma clave sin importar el orden en que se escribieron.
37
+ */
38
+ static clave(path, params) {
39
+ const limpios = Object.entries(params ?? {})
40
+ .filter(([k, v]) => !PARAMS_SECRETOS.has(k.toLowerCase()) && v !== undefined && v !== null && v !== '')
41
+ .sort(([a], [b]) => a.localeCompare(b))
42
+ .map(([k, v]) => `${k}=${String(v)}`)
43
+ .join('&');
44
+ return limpios ? `${path}?${limpios}` : path;
45
+ }
46
+ /** Devuelve el valor vigente, o `undefined` si no está o ya venció. */
47
+ obtener(clave) {
48
+ const e = this.entradas.get(clave);
49
+ if (!e) {
50
+ this.fallos++;
51
+ return undefined;
52
+ }
53
+ if (Date.now() >= e.expira) {
54
+ this.entradas.delete(clave);
55
+ this.fallos++;
56
+ return undefined;
57
+ }
58
+ // Renovar posición para que la purga descarte lo realmente frío.
59
+ this.entradas.delete(clave);
60
+ this.entradas.set(clave, e);
61
+ this.aciertos++;
62
+ return e.valor;
63
+ }
64
+ guardar(clave, valor, ttlSegundos) {
65
+ if (ttlSegundos <= 0)
66
+ return;
67
+ this.entradas.set(clave, { expira: Date.now() + ttlSegundos * 1000, valor });
68
+ this.purgar();
69
+ this.persistir();
70
+ }
71
+ /** Elimina lo vencido y, si aún sobra, lo más antiguo. */
72
+ purgar() {
73
+ const ahora = Date.now();
74
+ for (const [k, e] of this.entradas) {
75
+ if (ahora >= e.expira)
76
+ this.entradas.delete(k);
77
+ }
78
+ while (this.entradas.size > this.maxEntradas) {
79
+ const primera = this.entradas.keys().next();
80
+ if (primera.done)
81
+ break;
82
+ this.entradas.delete(primera.value);
83
+ }
84
+ }
85
+ estadisticas() {
86
+ const total = this.aciertos + this.fallos;
87
+ return {
88
+ entradas: this.entradas.size,
89
+ aciertos: this.aciertos,
90
+ fallos: this.fallos,
91
+ ahorroPorcentaje: total === 0 ? 0 : Math.round((this.aciertos / total) * 100),
92
+ };
93
+ }
94
+ /** Vacía la caché (memoria y disco). */
95
+ limpiar() {
96
+ this.entradas.clear();
97
+ this.persistir();
98
+ }
99
+ cargar() {
100
+ if (!this.rutaEstado)
101
+ return;
102
+ try {
103
+ if (!fs.existsSync(this.rutaEstado))
104
+ return;
105
+ const crudo = JSON.parse(fs.readFileSync(this.rutaEstado, 'utf8'));
106
+ const ahora = Date.now();
107
+ for (const [k, e] of Object.entries(crudo)) {
108
+ if (e && typeof e.expira === 'number' && ahora < e.expira) {
109
+ this.entradas.set(k, e);
110
+ }
111
+ }
112
+ if (this.entradas.size > 0) {
113
+ logger.debug(`Caché: ${this.entradas.size} respuesta(s) vigente(s) recuperada(s) del disco.`);
114
+ }
115
+ }
116
+ catch {
117
+ // Una caché ilegible se descarta: es una optimización, no un requisito.
118
+ }
119
+ }
120
+ persistir() {
121
+ if (!this.rutaEstado)
122
+ return;
123
+ try {
124
+ fs.writeFileSync(this.rutaEstado, JSON.stringify(Object.fromEntries(this.entradas)), 'utf8');
125
+ }
126
+ catch {
127
+ // Si el disco no deja escribir, se sigue con la caché en memoria.
128
+ }
129
+ }
130
+ }
131
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Búsqueda de texto dentro de los documentos locales (manuales, guías, normativa).
3
+ *
4
+ * ⚠ POR QUÉ EXISTE ESTE MÓDULO: la primera implementación buscaba la consulta
5
+ * completa como subcadena literal (`texto.includes(query)`). Con una palabra
6
+ * suelta funcionaba, pero cualquier pregunta en lenguaje natural —que es
7
+ * justo lo que envía un LLM— no coincidía nunca. Peor aún, el fallo era
8
+ * silencioso y seguro de sí mismo: "no se encontraron coincidencias en
9
+ * ninguno de los 10 documentos" ante la pregunta "¿qué multas me pueden
10
+ * aplicar?", existiendo un PDF entero dedicado a multas y sanciones. El
11
+ * modelo repetía ese "no hay información" al usuario como si fuera cierto.
12
+ *
13
+ * Ahora la consulta se descompone en términos, se ignoran las palabras
14
+ * vacías y se puntúa cada línea por cuántos términos distintos contiene.
15
+ * Además se normalizan los acentos, porque en documentos en español
16
+ * "sanción" y "sancion" deben encontrarse mutuamente.
17
+ */
18
+ /** Minúsculas y sin acentos, para que "sanción" y "sancion" se encuentren. */
19
+ export declare function normalizar(texto: string): string;
20
+ /**
21
+ * Descompone la consulta en términos buscables: normalizados, sin palabras
22
+ * vacías y de al menos 3 caracteres.
23
+ *
24
+ * Si al filtrar no queda ningún término (consulta hecha solo de palabras
25
+ * vacías, ej. "qué es esto"), se devuelven todos los de 3+ caracteres antes
26
+ * que dejar al usuario sin búsqueda.
27
+ */
28
+ export declare function tokenizar(query: string): string[];
29
+ export interface Fragmento {
30
+ /** Línea que coincidió, con su contexto inmediato. */
31
+ texto: string;
32
+ /** Términos de la consulta presentes en la línea. */
33
+ terminos: string[];
34
+ puntaje: number;
35
+ }
36
+ export interface ResultadoBusqueda {
37
+ fragmentos: Fragmento[];
38
+ /** Términos en que se descompuso la consulta. */
39
+ terminos: string[];
40
+ /** Términos que no aparecen en ninguna parte del texto. */
41
+ ausentes: string[];
42
+ }
43
+ /**
44
+ * Busca los términos de `query` en `texto` y devuelve los fragmentos más
45
+ * relevantes, ordenados por cuántos términos distintos concentra cada uno.
46
+ *
47
+ * Una línea que contiene la consulta completa e íntegra se prioriza sobre
48
+ * cualquier coincidencia parcial: si el usuario acertó la frase exacta, esa
49
+ * es la mejor respuesta posible.
50
+ */
51
+ export declare function buscarEnTexto(texto: string, query: string, maxFragmentos?: number): ResultadoBusqueda;
52
+ //# sourceMappingURL=doc-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-search.d.ts","sourceRoot":"","sources":["../../src/utils/doc-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAQjD;AAED,MAAM,WAAW,SAAS;IACxB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,aAAa,SAAK,GACjB,iBAAiB,CAwCnB"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Búsqueda de texto dentro de los documentos locales (manuales, guías, normativa).
3
+ *
4
+ * ⚠ POR QUÉ EXISTE ESTE MÓDULO: la primera implementación buscaba la consulta
5
+ * completa como subcadena literal (`texto.includes(query)`). Con una palabra
6
+ * suelta funcionaba, pero cualquier pregunta en lenguaje natural —que es
7
+ * justo lo que envía un LLM— no coincidía nunca. Peor aún, el fallo era
8
+ * silencioso y seguro de sí mismo: "no se encontraron coincidencias en
9
+ * ninguno de los 10 documentos" ante la pregunta "¿qué multas me pueden
10
+ * aplicar?", existiendo un PDF entero dedicado a multas y sanciones. El
11
+ * modelo repetía ese "no hay información" al usuario como si fuera cierto.
12
+ *
13
+ * Ahora la consulta se descompone en términos, se ignoran las palabras
14
+ * vacías y se puntúa cada línea por cuántos términos distintos contiene.
15
+ * Además se normalizan los acentos, porque en documentos en español
16
+ * "sanción" y "sancion" deben encontrarse mutuamente.
17
+ */
18
+ /** Palabras sin valor discriminante en una consulta en español. */
19
+ const VACIAS = new Set([
20
+ 'que', 'qué', 'cual', 'cuál', 'cuales', 'cuáles', 'como', 'cómo', 'cuando',
21
+ 'cuándo', 'donde', 'dónde', 'quien', 'quién', 'por', 'para', 'con', 'sin',
22
+ 'los', 'las', 'del', 'unos', 'unas', 'una', 'uno', 'sus', 'sobre', 'entre',
23
+ 'este', 'esta', 'esto', 'estos', 'estas', 'ese', 'esa', 'eso', 'aquel',
24
+ 'son', 'ser', 'está', 'esta', 'estan', 'están', 'hay', 'han', 'hace',
25
+ 'puede', 'pueden', 'debe', 'deben', 'tiene', 'tienen', 'mas', 'más',
26
+ 'pero', 'sus', 'les', 'nos', 'mis', 'tus', 'muy', 'ya', 'les', 'algun',
27
+ 'algún', 'alguna', 'todo', 'toda', 'todos', 'todas', 'otro', 'otra',
28
+ ]);
29
+ /** Minúsculas y sin acentos, para que "sanción" y "sancion" se encuentren. */
30
+ export function normalizar(texto) {
31
+ return texto
32
+ .toLowerCase()
33
+ .normalize('NFD')
34
+ .replace(/[̀-ͯ]/g, '');
35
+ }
36
+ /**
37
+ * Descompone la consulta en términos buscables: normalizados, sin palabras
38
+ * vacías y de al menos 3 caracteres.
39
+ *
40
+ * Si al filtrar no queda ningún término (consulta hecha solo de palabras
41
+ * vacías, ej. "qué es esto"), se devuelven todos los de 3+ caracteres antes
42
+ * que dejar al usuario sin búsqueda.
43
+ */
44
+ export function tokenizar(query) {
45
+ const crudos = normalizar(query)
46
+ .split(/[^a-z0-9ñ]+/)
47
+ .filter((t) => t.length >= 3);
48
+ const utiles = crudos.filter((t) => !VACIAS.has(t));
49
+ const elegidos = utiles.length > 0 ? utiles : crudos;
50
+ return [...new Set(elegidos)];
51
+ }
52
+ /**
53
+ * Busca los términos de `query` en `texto` y devuelve los fragmentos más
54
+ * relevantes, ordenados por cuántos términos distintos concentra cada uno.
55
+ *
56
+ * Una línea que contiene la consulta completa e íntegra se prioriza sobre
57
+ * cualquier coincidencia parcial: si el usuario acertó la frase exacta, esa
58
+ * es la mejor respuesta posible.
59
+ */
60
+ export function buscarEnTexto(texto, query, maxFragmentos = 12) {
61
+ const terminos = tokenizar(query);
62
+ if (terminos.length === 0) {
63
+ return { fragmentos: [], terminos: [], ausentes: [] };
64
+ }
65
+ const frase = normalizar(query).trim();
66
+ const lineas = texto.split('\n');
67
+ const normalizadas = lineas.map(normalizar);
68
+ const ausentes = terminos.filter((t) => !normalizadas.some((l) => l.includes(t)));
69
+ const candidatos = [];
70
+ for (let i = 0; i < lineas.length; i++) {
71
+ const linea = lineas[i]?.trim();
72
+ if (!linea)
73
+ continue;
74
+ const norm = normalizadas[i];
75
+ const presentes = terminos.filter((t) => norm.includes(t));
76
+ if (presentes.length === 0)
77
+ continue;
78
+ // La frase completa vale más que la suma de sus partes.
79
+ const bonus = frase.length > 0 && norm.includes(frase) ? terminos.length * 10 : 0;
80
+ const contexto = [];
81
+ const anterior = lineas[i - 1]?.trim();
82
+ const siguiente = lineas[i + 1]?.trim();
83
+ if (anterior)
84
+ contexto.push(`[Anterior] ${anterior}`);
85
+ contexto.push(`[COINCIDENCIA] ${linea}`);
86
+ if (siguiente)
87
+ contexto.push(`[Siguiente] ${siguiente}`);
88
+ candidatos.push({
89
+ texto: contexto.join('\n'),
90
+ terminos: presentes,
91
+ puntaje: presentes.length + bonus,
92
+ });
93
+ }
94
+ candidatos.sort((a, b) => b.puntaje - a.puntaje);
95
+ return { fragmentos: candidatos.slice(0, maxFragmentos), terminos, ausentes };
96
+ }
97
+ //# sourceMappingURL=doc-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-search.js","sourceRoot":"","sources":["../../src/utils/doc-search.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/utils/error-handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtC,SAAgB,iBAAiB,EAAE,MAAM,CAAC;gBAE9B,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,QAAQ,EAAO;CAQ3D;AAuCD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAoB5E"}
1
+ {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/utils/error-handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,QAAQ,EAAE,CAAC;CACpB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtC,SAAgB,iBAAiB,EAAE,MAAM,CAAC;gBAE9B,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,QAAQ,EAAO;CAQ3D;AAoCD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAoB5E"}
@@ -44,12 +44,10 @@ function getActionableMessage(httpStatus, apiErrors) {
44
44
  }
45
45
  }
46
46
  function formatRateLimitMessage(detail) {
47
- const now = new Date();
48
- const tomorrow = new Date(now);
49
- tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);
50
- tomorrow.setUTCHours(0, 1, 0, 0);
51
- const waitHours = Math.ceil((tomorrow.getTime() - now.getTime()) / (1000 * 60 * 60));
52
- return `Cuota diaria de la API agotada. El límite se restablece al inicio del próximo día calendario UTC (en aproximadamente ${waitHours} horas). Para reducir el consumo: usa filtros más específicos (estado, región, fechas), evita consultas masivas, y usa 'ttl_cambio_ms' para sincronización incremental en vez de descargas completas. Si necesitas más cuota, contacta a ChileCompra.${detail}`;
47
+ // La cuota se comporta como un token bucket que se recarga solo (glosario de
48
+ // la guía oficial): medido en producción, la API volvió a responder ~13 min
49
+ // después de un 429. Por eso no se aconseja esperar al día siguiente.
50
+ return `Se agotaron temporalmente los tokens de cuota de la API. No es necesario esperar al día siguiente: la cuota se recarga sola, así que reintenta en unos minutos. Para gastar menos: usa filtros más específicos (estado, región, fechas), baja 'limite_analisis'/'max_paginas' en las herramientas de análisis, y prefiere 'ttl_cambio_ms' o un rango 'cambio_desde'/'cambio_hasta' para sincronización incremental en vez de descargas completas. Si el 429 persiste durante horas, la cuota diaria del ticket sí puede estar agotada: contacta a ChileCompra para uno de mayor límite.${detail}`;
53
51
  }
54
52
  /**
55
53
  * Parsea la respuesta de error de la API y lanza una excepción tipada.
@@ -1 +1 @@
1
- {"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/utils/error-handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/utils/error-handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAerC,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3B,UAAU,CAAS;IACnB,SAAS,CAAa;IACtB,iBAAiB,CAAS;IAE1C,YAAY,UAAkB,EAAE,YAAwB,EAAE;QACxD,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC/D,KAAK,CAAC,UAAU,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,iBAAiB,GAAG,UAAU,CAAC;IACtC,CAAC;CACF;AAED,SAAS,oBAAoB,CAAC,UAAkB,EAAE,SAAqB;IACrE,6EAA6E;IAC7E,gFAAgF;IAChF,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC;QACjC,CAAC,CAAC,wBAAwB,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,GAAG;QAC/D,CAAC,CAAC,EAAE,CAAC;IAEP,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,GAAG;YACN,OAAO,qLAAqL,MAAM,EAAE,CAAC;QACvM,KAAK,GAAG;YACN,OAAO,2JAA2J,MAAM,EAAE,CAAC;QAC7K,KAAK,GAAG;YACN,OAAO,gLAAgL,MAAM,EAAE,CAAC;QAClM,KAAK,GAAG;YACN,OAAO,mKAAmK,MAAM,EAAE,CAAC;QACrL,KAAK,GAAG;YACN,OAAO,sBAAsB,CAAC,MAAM,CAAC,CAAC;QACxC,KAAK,GAAG;YACN,OAAO,4IAA4I,MAAM,EAAE,CAAC;QAC9J,KAAK,GAAG;YACN,OAAO,gHAAgH,MAAM,EAAE,CAAC;QAClI;YACE,OAAO,yBAAyB,UAAU,6BAA6B,MAAM,EAAE,CAAC;IACpF,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,MAAc;IAC5C,6EAA6E;IAC7E,4EAA4E;IAC5E,sEAAsE;IACtE,OAAO,0jBAA0jB,MAAM,EAAE,CAAC;AAC5kB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,QAAkB;IACxD,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAA6C,CAAC;QAC9E,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;YACnC,MAAM,IAAI,kBAAkB,CAAC,QAAQ,CAAC,MAAM,EAAG,IAAoC,CAAC,MAAM,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACvF,CAAC;IAED,IAAI,SAAS,GAAe,EAAE,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAsB,CAAC;QAC5D,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;YACrB,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC;QAC/B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,4DAA4D;IAC9D,CAAC;IAED,MAAM,IAAI,kBAAkB,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC3D,CAAC"}
@@ -5,14 +5,32 @@
5
5
  * Este módulo lleva un contador local de requests para advertir
6
6
  * proactivamente antes de alcanzar el límite.
7
7
  */
8
+ /** Archivo donde el servidor recuerda la cuota entre reinicios. */
9
+ export declare const RUTA_ESTADO_POR_DEFECTO: string;
8
10
  export declare class RateLimiter {
9
11
  private requestCount;
10
12
  private currentDay;
11
13
  private isLimited;
12
14
  private limitResetTime;
15
+ /** 429 seguidos sin una consulta exitosa en medio; gradúa la espera. */
16
+ private consecutive429;
13
17
  private readonly maxPerMinute;
14
18
  private requestTimestamps;
15
- constructor(maxPerMinute?: number);
19
+ /**
20
+ * Ruta de persistencia, o `null` para operar solo en memoria.
21
+ *
22
+ * ⚠ POR QUÉ SE PERSISTE: el contador vivía únicamente en memoria, así que
23
+ * cada arranque del servidor empezaba en cero. Como un servidor MCP se
24
+ * reinicia cada vez que el usuario reinicia su cliente, `obtener_estadisticas_uso`
25
+ * informaba `isLimited: false` incluso segundos después de que la API
26
+ * hubiera respondido 429 por cuota agotada, y el throttle preventivo
27
+ * disparaba peticiones contra un muro. Detectado en auditoría.
28
+ */
29
+ private readonly statePath;
30
+ constructor(maxPerMinute?: number, statePath?: string | null);
31
+ /** Lee el estado del día en curso. Un estado de otro día se descarta. */
32
+ private cargarEstado;
33
+ private guardarEstado;
16
34
  private getTodayUTC;
17
35
  /**
18
36
  * Espera (si es necesario) hasta que haya cupo dentro de la ventana de 1 minuto,
@@ -25,8 +43,25 @@ export declare class RateLimiter {
25
43
  recordRequest(): void;
26
44
  /**
27
45
  * Marca que se recibió un error 429.
46
+ *
47
+ * ⚠ POR QUÉ NO SE BLOQUEA HASTA MAÑANA: antes, un solo 429 fijaba el reset
48
+ * en las 00:01 UTC del día siguiente y `checkLimit()` rechazaba localmente
49
+ * TODA consulta posterior — el MCP quedaba inutilizable durante horas sin
50
+ * siquiera intentar llegar a la API. Medido en auditoría: tras un 429, la
51
+ * API volvió a responder con normalidad 13 minutos después. La propia guía
52
+ * oficial describe la cuota como un *token bucket* que "se recarga
53
+ * automáticamente" (glosario) y manda esperar el header `Retry-After`
54
+ * (§7), aunque su §4 diga que el límite es por día calendario.
55
+ *
56
+ * Ahora se espera lo que indique `Retry-After`; si no viene, se aplica una
57
+ * espera creciente (15 → 30 → 60 min, tope 2 h) que se reinicia con la
58
+ * primera consulta exitosa.
59
+ *
60
+ * @param retryAfterSeconds Valor del header `Retry-After`, si la API lo envió.
28
61
  */
29
- markLimited(): void;
62
+ markLimited(retryAfterSeconds?: number): void;
63
+ /** Espera creciente ante 429 sucesivos, para no martillar la API. */
64
+ private esperaPorDefecto;
30
65
  /**
31
66
  * Verifica si estamos actualmente limitados.
32
67
  */
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,iBAAiB,CAAgB;gBAE7B,YAAY,SAAK;IAK7B,OAAO,CAAC,WAAW;IAInB;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB/B;;OAEG;IACH,aAAa,IAAI,IAAI;IAarB;;OAEG;IACH,WAAW,IAAI,IAAI;IAUnB;;OAEG;IACH,UAAU,IAAI;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAyBpD;;OAEG;IACH,QAAQ,IAAI;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAOpF"}
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,QAAwD,CAAC;AAS7F,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAqB;IAC3C,wEAAwE;IACxE,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,iBAAiB,CAAgB;IAEzC;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;gBAE9B,YAAY,SAAK,EAAE,SAAS,GAAE,MAAM,GAAG,IAAW;IAO9D,yEAAyE;IACzE,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,WAAW;IAInB;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB/B;;OAEG;IACH,aAAa,IAAI,IAAI;IAoBrB;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI;IAiB7C,qEAAqE;IACrE,OAAO,CAAC,gBAAgB;IAMxB;;OAEG;IACH,UAAU,IAAI;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IA0BpD;;OAEG;IACH,QAAQ,IAAI;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAOpF"}
@@ -5,18 +5,74 @@
5
5
  * Este módulo lleva un contador local de requests para advertir
6
6
  * proactivamente antes de alcanzar el límite.
7
7
  */
8
+ import fs from 'fs';
9
+ import path from 'path';
8
10
  import { logger } from './logger.js';
11
+ /** Archivo donde el servidor recuerda la cuota entre reinicios. */
12
+ export const RUTA_ESTADO_POR_DEFECTO = path.resolve(process.cwd(), '.rate-limit-state.json');
9
13
  export class RateLimiter {
10
14
  requestCount = 0;
11
15
  currentDay;
12
16
  isLimited = false;
13
17
  limitResetTime = null;
18
+ /** 429 seguidos sin una consulta exitosa en medio; gradúa la espera. */
19
+ consecutive429 = 0;
14
20
  // Throttle proactivo por minuto (ventana deslizante de timestamps).
15
21
  maxPerMinute;
16
22
  requestTimestamps = [];
17
- constructor(maxPerMinute = 40) {
23
+ /**
24
+ * Ruta de persistencia, o `null` para operar solo en memoria.
25
+ *
26
+ * ⚠ POR QUÉ SE PERSISTE: el contador vivía únicamente en memoria, así que
27
+ * cada arranque del servidor empezaba en cero. Como un servidor MCP se
28
+ * reinicia cada vez que el usuario reinicia su cliente, `obtener_estadisticas_uso`
29
+ * informaba `isLimited: false` incluso segundos después de que la API
30
+ * hubiera respondido 429 por cuota agotada, y el throttle preventivo
31
+ * disparaba peticiones contra un muro. Detectado en auditoría.
32
+ */
33
+ statePath;
34
+ constructor(maxPerMinute = 40, statePath = null) {
18
35
  this.currentDay = this.getTodayUTC();
19
36
  this.maxPerMinute = maxPerMinute;
37
+ this.statePath = statePath;
38
+ this.cargarEstado();
39
+ }
40
+ /** Lee el estado del día en curso. Un estado de otro día se descarta. */
41
+ cargarEstado() {
42
+ if (!this.statePath)
43
+ return;
44
+ try {
45
+ if (!fs.existsSync(this.statePath))
46
+ return;
47
+ const raw = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
48
+ if (raw.day !== this.currentDay)
49
+ return;
50
+ this.requestCount = raw.requestCount ?? 0;
51
+ this.isLimited = raw.isLimited ?? false;
52
+ this.limitResetTime = raw.limitResetTime ? new Date(raw.limitResetTime) : null;
53
+ if (this.isLimited) {
54
+ logger.info('Rate limiter: se recuperó un estado de cuota agotada de esta misma jornada UTC.');
55
+ }
56
+ }
57
+ catch {
58
+ // Un estado ilegible no debe impedir arrancar: se sigue en memoria.
59
+ }
60
+ }
61
+ guardarEstado() {
62
+ if (!this.statePath)
63
+ return;
64
+ try {
65
+ const estado = {
66
+ day: this.currentDay,
67
+ requestCount: this.requestCount,
68
+ isLimited: this.isLimited,
69
+ limitResetTime: this.limitResetTime?.toISOString() ?? null,
70
+ };
71
+ fs.writeFileSync(this.statePath, JSON.stringify(estado), 'utf8');
72
+ }
73
+ catch {
74
+ // Persistir es una mejora, no un requisito: si el disco no deja, se sigue.
75
+ }
20
76
  }
21
77
  getTodayUTC() {
22
78
  return new Date().toISOString().split('T')[0];
@@ -54,19 +110,50 @@ export class RateLimiter {
54
110
  logger.info('Rate limiter: contador diario reseteado (nuevo día calendario UTC).');
55
111
  }
56
112
  this.requestCount++;
113
+ // Una consulta exitosa prueba que el balde volvió a tener fichas.
114
+ if (this.consecutive429 > 0 || this.isLimited) {
115
+ this.consecutive429 = 0;
116
+ this.isLimited = false;
117
+ this.limitResetTime = null;
118
+ }
57
119
  logger.debug(`Rate limiter: request #${this.requestCount} del día.`);
120
+ this.guardarEstado();
58
121
  }
59
122
  /**
60
123
  * Marca que se recibió un error 429.
124
+ *
125
+ * ⚠ POR QUÉ NO SE BLOQUEA HASTA MAÑANA: antes, un solo 429 fijaba el reset
126
+ * en las 00:01 UTC del día siguiente y `checkLimit()` rechazaba localmente
127
+ * TODA consulta posterior — el MCP quedaba inutilizable durante horas sin
128
+ * siquiera intentar llegar a la API. Medido en auditoría: tras un 429, la
129
+ * API volvió a responder con normalidad 13 minutos después. La propia guía
130
+ * oficial describe la cuota como un *token bucket* que "se recarga
131
+ * automáticamente" (glosario) y manda esperar el header `Retry-After`
132
+ * (§7), aunque su §4 diga que el límite es por día calendario.
133
+ *
134
+ * Ahora se espera lo que indique `Retry-After`; si no viene, se aplica una
135
+ * espera creciente (15 → 30 → 60 min, tope 2 h) que se reinicia con la
136
+ * primera consulta exitosa.
137
+ *
138
+ * @param retryAfterSeconds Valor del header `Retry-After`, si la API lo envió.
61
139
  */
62
- markLimited() {
140
+ markLimited(retryAfterSeconds) {
63
141
  this.isLimited = true;
64
- const now = new Date();
65
- const tomorrow = new Date(now);
66
- tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);
67
- tomorrow.setUTCHours(0, 1, 0, 0);
68
- this.limitResetTime = tomorrow;
69
- logger.warn(`Rate limiter: cuota agotada. Reset estimado: ${tomorrow.toISOString()}`);
142
+ this.consecutive429++;
143
+ const esperaSegundos = retryAfterSeconds && retryAfterSeconds > 0
144
+ ? retryAfterSeconds
145
+ : this.esperaPorDefecto();
146
+ this.limitResetTime = new Date(Date.now() + esperaSegundos * 1000);
147
+ const origen = retryAfterSeconds && retryAfterSeconds > 0 ? 'header Retry-After' : 'espera progresiva local';
148
+ logger.warn(`Rate limiter: 429 recibido (#${this.consecutive429} consecutivo). ` +
149
+ `Reintentable a partir de ${this.limitResetTime.toISOString()} (${origen}).`);
150
+ this.guardarEstado();
151
+ }
152
+ /** Espera creciente ante 429 sucesivos, para no martillar la API. */
153
+ esperaPorDefecto() {
154
+ const escala = [15, 30, 60, 120]; // minutos
155
+ const i = Math.min(this.consecutive429 - 1, escala.length - 1);
156
+ return escala[Math.max(0, i)] * 60;
70
157
  }
71
158
  /**
72
159
  * Verifica si estamos actualmente limitados.
@@ -81,16 +168,17 @@ export class RateLimiter {
81
168
  this.limitResetTime = null;
82
169
  this.requestCount = 0;
83
170
  this.currentDay = this.getTodayUTC();
171
+ this.guardarEstado();
84
172
  return { limited: false };
85
173
  }
86
174
  const waitMs = this.limitResetTime
87
175
  ? this.limitResetTime.getTime() - now.getTime()
88
176
  : 0;
89
- const waitHours = Math.ceil(waitMs / (1000 * 60 * 60));
90
- return {
91
- limited: true,
92
- resetIn: `aproximadamente ${waitHours} hora(s)`,
93
- };
177
+ const waitMin = Math.max(1, Math.ceil(waitMs / 60_000));
178
+ const resetIn = waitMin >= 60
179
+ ? `aproximadamente ${Math.ceil(waitMin / 60)} hora(s)`
180
+ : `aproximadamente ${waitMin} minuto(s)`;
181
+ return { limited: true, resetIn };
94
182
  }
95
183
  /**
96
184
  * Obtiene estadísticas del uso actual.
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssolis-ti/mcp-compra-agil",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "Servidor MCP para la API Compra Ágil v2 de Mercado Público (Chile)",
5
5
  "type": "module",
6
6
  "bin": {