@ssolis-ti/mcp-compra-agil 2.1.0 → 2.4.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 (53) hide show
  1. package/README.md +31 -11
  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/reports/templates/radar-oportunidades.d.ts +1 -1
  9. package/dist/reports/templates/radar-oportunidades.js +2 -2
  10. package/dist/reports/templates/radar-oportunidades.js.map +1 -1
  11. package/dist/resources/glosario.d.ts.map +1 -1
  12. package/dist/resources/glosario.js +6 -2
  13. package/dist/resources/glosario.js.map +1 -1
  14. package/dist/services/monitor.js +1 -1
  15. package/dist/services/monitor.js.map +1 -1
  16. package/dist/tools/analizar-precios-mercado.js +2 -2
  17. package/dist/tools/auditar-desiertas.js +2 -2
  18. package/dist/tools/detalle-oc.d.ts.map +1 -1
  19. package/dist/tools/detalle-oc.js +2 -3
  20. package/dist/tools/detalle-oc.js.map +1 -1
  21. package/dist/tools/documentos.d.ts.map +1 -1
  22. package/dist/tools/documentos.js +68 -37
  23. package/dist/tools/documentos.js.map +1 -1
  24. package/dist/tools/estadisticas-uso.d.ts.map +1 -1
  25. package/dist/tools/estadisticas-uso.js +12 -2
  26. package/dist/tools/estadisticas-uso.js.map +1 -1
  27. package/dist/tools/generar-borrador.d.ts +25 -1
  28. package/dist/tools/generar-borrador.d.ts.map +1 -1
  29. package/dist/tools/generar-borrador.js +96 -78
  30. package/dist/tools/generar-borrador.js.map +1 -1
  31. package/dist/tools/radar-oportunidades.d.ts +4 -1
  32. package/dist/tools/radar-oportunidades.d.ts.map +1 -1
  33. package/dist/tools/radar-oportunidades.js +24 -2
  34. package/dist/tools/radar-oportunidades.js.map +1 -1
  35. package/dist/tools/verificar-oc.d.ts.map +1 -1
  36. package/dist/tools/verificar-oc.js +30 -4
  37. package/dist/tools/verificar-oc.js.map +1 -1
  38. package/dist/utils/cache.d.ts +53 -0
  39. package/dist/utils/cache.d.ts.map +1 -0
  40. package/dist/utils/cache.js +131 -0
  41. package/dist/utils/cache.js.map +1 -0
  42. package/dist/utils/doc-search.d.ts +52 -0
  43. package/dist/utils/doc-search.d.ts.map +1 -0
  44. package/dist/utils/doc-search.js +97 -0
  45. package/dist/utils/doc-search.js.map +1 -0
  46. package/dist/utils/error-handler.d.ts.map +1 -1
  47. package/dist/utils/error-handler.js +4 -6
  48. package/dist/utils/error-handler.js.map +1 -1
  49. package/dist/utils/rate-limiter.d.ts +37 -2
  50. package/dist/utils/rate-limiter.d.ts.map +1 -1
  51. package/dist/utils/rate-limiter.js +101 -13
  52. package/dist/utils/rate-limiter.js.map +1 -1
  53. package/package.json +1 -1
@@ -4,7 +4,8 @@ import { logger } from '../utils/logger.js';
4
4
  import { safeError } from '../utils/redact.js';
5
5
  const TOOL_NAME = 'radar_oportunidades_calientes';
6
6
  const TOOL_DESCRIPTION = `Escanea, califica y clasifica de forma priorizada los procesos de Compra Ágil activos (publicados).
7
- Utiliza una fórmula ponderada (Hot Score) basada en la falta de oferentes, el presupuesto disponible y las horas restantes de cierre para destacar los llamados más convenientes y fáciles de ganar.`;
7
+ Utiliza una fórmula ponderada (Hot Score) basada en la falta de oferentes, el presupuesto disponible, las horas restantes de cierre y si el proceso va en segundo llamado, para destacar los llamados más convenientes y fáciles de ganar.
8
+ Cada resultado incluye "llamado" (1 = primero, 2 = segundo) y el desglose de factores que explican su puntuación.`;
8
9
  const inputSchema = {
9
10
  region: z.string().optional().describe('Código de la región para filtrar (1-16). Ej: "13" para Metropolitana.'),
10
11
  q: z.string().optional().describe('Término de búsqueda opcional para acotar a un rubro o producto específico (ej: "licencias").'),
@@ -15,7 +16,8 @@ const inputSchema = {
15
16
  /**
16
17
  * Calcula el "Hot Score" de una oportunidad de forma pura (sin efectos secundarios).
17
18
  * Retorna null si el proceso debe omitirse (ya cerrado o bajo el presupuesto mínimo).
18
- * Ponderación: baja competencia (máx 50) + urgencia (máx 30) + presupuesto (máx 20) + facilidad (máx 5).
19
+ * Ponderación: baja competencia (máx 50) + urgencia (máx 30) + presupuesto (máx 20)
20
+ * + facilidad (máx 5) + segundo llamado (máx 10). Máximo teórico: 115 pts.
19
21
  */
20
22
  export function evaluarOportunidad(item, now, minBudget = 0) {
21
23
  const budget = item.montos?.monto_disponible_clp || item.montos?.monto_disponible || 0;
@@ -80,6 +82,25 @@ export function evaluarOportunidad(item, now, minBudget = 0) {
80
82
  score += 5;
81
83
  factors.push('Sin documentos adjuntos (postulación rápida sin leer bases) (+5 pts)');
82
84
  }
85
+ // Factor 5: Segundo Llamado — Máx 10 pts
86
+ //
87
+ // Un segundo llamado significa que el primero no logró adjudicar. Vale
88
+ // señalarlo por dos razones: el comprador vuelve con más urgencia, y existe
89
+ // un motivo de fracaso concreto que conviene averiguar —si sabes cumplir lo
90
+ // que otros no pudieron, es tu ventaja; si el presupuesto era corto, es una
91
+ // pérdida de tiempo—.
92
+ //
93
+ // ⚠ Lo que NO implica es menos competencia, aunque suene intuitivo. Medido
94
+ // contra la API real: de 200 procesos activos, el único en segundo llamado
95
+ // tenía 11 ofertas. Un primer llamado puede caerse porque todas las ofertas
96
+ // fueron inadmisibles por papeleo, y entonces el segundo atrae a los mismos
97
+ // interesados y más. Por eso el factor puntúa moderado y la competencia
98
+ // real la sigue reportando el Factor 1, que es quien la mide de verdad.
99
+ const llamado = item.convocatoria?.estado_convocatoria ?? 1;
100
+ if (llamado === 2) {
101
+ score += 10;
102
+ factors.push('Segundo llamado: el primero no logró adjudicar, así que el comprador vuelve con urgencia — averigua por qué falló antes de cotizar, ahí está la ventaja o la trampa (+10 pts)');
103
+ }
83
104
  return {
84
105
  codigo: item.codigo,
85
106
  nombre: item.nombre,
@@ -89,6 +110,7 @@ export function evaluarOportunidad(item, now, minBudget = 0) {
89
110
  ofertas_recibidas: bids,
90
111
  horas_restantes: Math.round(hoursLeft * 10) / 10,
91
112
  fecha_cierre: item.fechas?.fecha_cierre,
113
+ llamado,
92
114
  puntuacion_caliente: score,
93
115
  factores_calificacion: factors,
94
116
  };
@@ -1 +1 @@
1
- {"version":3,"file":"radar-oportunidades.js","sourceRoot":"","sources":["../../src/tools/radar-oportunidades.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"radar-oportunidades.js","sourceRoot":"","sources":["../../src/tools/radar-oportunidades.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"verificar-oc.d.ts","sourceRoot":"","sources":["../../src/tools/verificar-oc.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAkBhE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CA+ErF"}
1
+ {"version":3,"file":"verificar-oc.d.ts","sourceRoot":"","sources":["../../src/tools/verificar-oc.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAkBhE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CA0GrF"}
@@ -13,9 +13,9 @@ import { logger } from '../utils/logger.js';
13
13
  import { esGanador } from '../utils/quotation.js';
14
14
  import { safeError } from '../utils/redact.js';
15
15
  const TOOL_NAME = 'verificar_orden_compra';
16
- const TOOL_DESCRIPTION = `Verifica si una Compra Ágil específica ya tiene una Orden de Compra (OC) emitida, leyendo id_orden_compra del detalle y cruzándolo con la API de Órdenes de Compra.
17
- ⚠ LIMITACIÓN VERIFICADA EMPÍRICAMENTE (julio 2026): en 45 procesos inspeccionados, NINGUNO traía id_orden_compra (siempre null), y el filtro de estado "proveedor_seleccionado" devuelve 0 resultados. En la práctica, la API de Compra Ágil no está publicando adjudicaciones ni órdenes de compra, por lo que esta herramienta reportará "sin OC" en la mayoría o totalidad de los casos.
18
- Un resultado "sin OC" NO significa necesariamente que la OC no exista: puede significar que la API no la expone. Para confirmarlo, consulta la ficha pública del proceso en https://buscador.mercadopublico.cl/ficha?code={codigo}`;
16
+ const TOOL_DESCRIPTION = `Informa si una Compra Ágil tiene Orden de Compra emitida, leyendo id_orden_compra del detalle.
17
+ ⚠ LIMITACIÓN VERIFICADA (julio 2026, re-confirmada en septiembre): en 45 procesos inspeccionados NINGUNO traía id_orden_compra, y el filtro "proveedor_seleccionado" devuelve 0 resultados. La API de Compra Ágil no publica adjudicaciones. Por eso esta herramienta NO consulta la API por su cuenta —sería gastar cuota para responder "no puedo saberlo"—: reutiliza el detalle si ya se pidió con "obtener_detalle_compra", y en cualquier caso indica cómo confirmarlo en la ficha pública.
18
+ Un "sin OC" NO prueba que la OC no exista: significa que la API no la expone.`;
19
19
  const inputSchema = {
20
20
  codigo: z.string().describe('Código único de la Compra Ágil a verificar. Formato: XXXXXX-YYY-COTXX. Ej: "1057539-228-COT26".'),
21
21
  };
@@ -25,7 +25,33 @@ export function registerVerificarOC(server, client) {
25
25
  inputSchema: inputSchema,
26
26
  }, async (args) => {
27
27
  try {
28
- const detalle = await client.detalle(args.codigo);
28
+ // ⚠ NO se consulta la API si el detalle no está ya en caché.
29
+ // Esta herramienta gastaba una consulta de cuota para responder algo
30
+ // que la API estructuralmente no publica: en 45 procesos inspeccionados
31
+ // más las verificaciones de septiembre 2026, NINGUNO trajo
32
+ // id_orden_compra. Pagar cuota por un "no puedo saberlo" garantizado
33
+ // es el peor negocio posible cuando el balde de tokens es escaso.
34
+ //
35
+ // Si el detalle ya se pagó antes (flujo natural: obtener_detalle_compra
36
+ // y luego preguntar por la OC), se aprovecha y la respuesta va completa
37
+ // sin costo. Si no, se responde igual con la explicación y la ficha.
38
+ const detalle = client.detalleEnCache(args.codigo);
39
+ if (!detalle) {
40
+ return {
41
+ content: [{
42
+ type: 'text',
43
+ text: JSON.stringify({
44
+ codigo: args.codigo,
45
+ verificacion_oc: {
46
+ tiene_orden_compra: null,
47
+ motivo: 'No se consultó la API a propósito, para no gastar cuota en una respuesta que se conoce de antemano: la API de Compra Ágil no publica adjudicaciones ni órdenes de compra (0 de 45 procesos inspeccionados traían id_orden_compra, re-verificado en septiembre de 2026).',
48
+ como_confirmarlo: `Abre la ficha pública del proceso, que sí muestra el estado real: https://buscador.mercadopublico.cl/ficha?code=${args.codigo}`,
49
+ si_quieres_los_datos_del_proceso: 'Llama primero a "obtener_detalle_compra"; después esta herramienta reutiliza ese detalle sin costo adicional y te informa lo que la API sí trae.',
50
+ },
51
+ }, null, 2),
52
+ }],
53
+ };
54
+ }
29
55
  const idOrdenCompra = detalle.id_orden_compra ?? detalle.orden_compra?.id_orden_compra ?? null;
30
56
  const idOc = detalle.orden_compra?.id_oc ?? null;
31
57
  const tieneOC = idOrdenCompra !== null && idOrdenCompra !== undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"verificar-oc.js","sourceRoot":"","sources":["../../src/tools/verificar-oc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"verificar-oc.js","sourceRoot":"","sources":["../../src/tools/verificar-oc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
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
+ export interface OpcionesCache {
20
+ /** Ruta del archivo de persistencia, o `null` para vivir solo en memoria. */
21
+ rutaEstado?: string | null;
22
+ /** Máximo de entradas retenidas; al excederlo se descartan las más antiguas. */
23
+ maxEntradas?: number;
24
+ }
25
+ export declare class ResponseCache {
26
+ private entradas;
27
+ private readonly rutaEstado;
28
+ private readonly maxEntradas;
29
+ private aciertos;
30
+ private fallos;
31
+ constructor(opciones?: OpcionesCache);
32
+ /**
33
+ * Clave estable para una consulta: mismo endpoint y mismos parámetros
34
+ * producen la misma clave sin importar el orden en que se escribieron.
35
+ */
36
+ static clave(path: string, params?: Record<string, unknown>): string;
37
+ /** Devuelve el valor vigente, o `undefined` si no está o ya venció. */
38
+ obtener<T>(clave: string): T | undefined;
39
+ guardar(clave: string, valor: unknown, ttlSegundos: number): void;
40
+ /** Elimina lo vencido y, si aún sobra, lo más antiguo. */
41
+ private purgar;
42
+ estadisticas(): {
43
+ entradas: number;
44
+ aciertos: number;
45
+ fallos: number;
46
+ ahorroPorcentaje: number;
47
+ };
48
+ /** Vacía la caché (memoria y disco). */
49
+ limpiar(): void;
50
+ private cargar;
51
+ private persistir;
52
+ }
53
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAcH,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAK;gBAEP,QAAQ,GAAE,aAAkB;IAMxC;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IASpE,uEAAuE;IACvE,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAkBxC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAOjE,0DAA0D;IAC1D,OAAO,CAAC,MAAM;IAYd,YAAY,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE;IAUhG,wCAAwC;IACxC,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,MAAM;IAmBd,OAAO,CAAC,SAAS;CAQlB"}
@@ -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"}