@ssolis-ti/mcp-compra-agil 1.0.2 → 2.1.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 (110) hide show
  1. package/README.md +113 -25
  2. package/dist/api/compra-agil-client.d.ts +2 -1
  3. package/dist/api/compra-agil-client.d.ts.map +1 -1
  4. package/dist/api/compra-agil-client.js +12 -2
  5. package/dist/api/compra-agil-client.js.map +1 -1
  6. package/dist/index.js +31 -7
  7. package/dist/index.js.map +1 -1
  8. package/dist/reports/components.d.ts +59 -0
  9. package/dist/reports/components.d.ts.map +1 -0
  10. package/dist/reports/components.js +125 -0
  11. package/dist/reports/components.js.map +1 -0
  12. package/dist/reports/export.d.ts +27 -0
  13. package/dist/reports/export.d.ts.map +1 -0
  14. package/dist/reports/export.js +47 -0
  15. package/dist/reports/export.js.map +1 -0
  16. package/dist/reports/format.d.ts +24 -0
  17. package/dist/reports/format.d.ts.map +1 -0
  18. package/dist/reports/format.js +74 -0
  19. package/dist/reports/format.js.map +1 -0
  20. package/dist/reports/render.d.ts +18 -0
  21. package/dist/reports/render.d.ts.map +1 -0
  22. package/dist/reports/render.js +30 -0
  23. package/dist/reports/render.js.map +1 -0
  24. package/dist/reports/templates/radar-oportunidades.d.ts +41 -0
  25. package/dist/reports/templates/radar-oportunidades.d.ts.map +1 -0
  26. package/dist/reports/templates/radar-oportunidades.js +170 -0
  27. package/dist/reports/templates/radar-oportunidades.js.map +1 -0
  28. package/dist/reports/theme.d.ts +83 -0
  29. package/dist/reports/theme.d.ts.map +1 -0
  30. package/dist/reports/theme.js +284 -0
  31. package/dist/reports/theme.js.map +1 -0
  32. package/dist/resources/compras-template.d.ts.map +1 -1
  33. package/dist/resources/compras-template.js +2 -1
  34. package/dist/resources/compras-template.js.map +1 -1
  35. package/dist/resources/documentacion.d.ts.map +1 -1
  36. package/dist/resources/documentacion.js +5 -36
  37. package/dist/resources/documentacion.js.map +1 -1
  38. package/dist/resources/estados.d.ts.map +1 -1
  39. package/dist/resources/estados.js +24 -10
  40. package/dist/resources/estados.js.map +1 -1
  41. package/dist/services/monitor.js +37 -2
  42. package/dist/services/monitor.js.map +1 -1
  43. package/dist/tools/analizar-precios-mercado.d.ts +21 -0
  44. package/dist/tools/analizar-precios-mercado.d.ts.map +1 -0
  45. package/dist/tools/analizar-precios-mercado.js +217 -0
  46. package/dist/tools/analizar-precios-mercado.js.map +1 -0
  47. package/dist/tools/auditar-desiertas.d.ts.map +1 -1
  48. package/dist/tools/auditar-desiertas.js +64 -46
  49. package/dist/tools/auditar-desiertas.js.map +1 -1
  50. package/dist/tools/buscar-compras.d.ts.map +1 -1
  51. package/dist/tools/buscar-compras.js +2 -1
  52. package/dist/tools/buscar-compras.js.map +1 -1
  53. package/dist/tools/detalle-compra.d.ts.map +1 -1
  54. package/dist/tools/detalle-compra.js +6 -5
  55. package/dist/tools/detalle-compra.js.map +1 -1
  56. package/dist/tools/detalle-oc.d.ts.map +1 -1
  57. package/dist/tools/detalle-oc.js +6 -5
  58. package/dist/tools/detalle-oc.js.map +1 -1
  59. package/dist/tools/documentos.d.ts.map +1 -1
  60. package/dist/tools/documentos.js +51 -50
  61. package/dist/tools/documentos.js.map +1 -1
  62. package/dist/tools/generar-borrador.d.ts.map +1 -1
  63. package/dist/tools/generar-borrador.js +37 -26
  64. package/dist/tools/generar-borrador.js.map +1 -1
  65. package/dist/tools/generar-informe.d.ts +13 -0
  66. package/dist/tools/generar-informe.d.ts.map +1 -0
  67. package/dist/tools/generar-informe.js +111 -0
  68. package/dist/tools/generar-informe.js.map +1 -0
  69. package/dist/tools/monitorear-cambios.d.ts +32 -2
  70. package/dist/tools/monitorear-cambios.d.ts.map +1 -1
  71. package/dist/tools/monitorear-cambios.js +79 -10
  72. package/dist/tools/monitorear-cambios.js.map +1 -1
  73. package/dist/tools/radar-oportunidades.d.ts +42 -1
  74. package/dist/tools/radar-oportunidades.d.ts.map +1 -1
  75. package/dist/tools/radar-oportunidades.js +123 -103
  76. package/dist/tools/radar-oportunidades.js.map +1 -1
  77. package/dist/tools/recomendar-precio.d.ts +13 -0
  78. package/dist/tools/recomendar-precio.d.ts.map +1 -1
  79. package/dist/tools/recomendar-precio.js +54 -58
  80. package/dist/tools/recomendar-precio.js.map +1 -1
  81. package/dist/tools/verificar-oc.d.ts.map +1 -1
  82. package/dist/tools/verificar-oc.js +9 -7
  83. package/dist/tools/verificar-oc.js.map +1 -1
  84. package/dist/tools/verificar-ticket.d.ts +13 -0
  85. package/dist/tools/verificar-ticket.d.ts.map +1 -0
  86. package/dist/tools/verificar-ticket.js +79 -0
  87. package/dist/tools/verificar-ticket.js.map +1 -0
  88. package/dist/utils/docs-locator.d.ts +41 -0
  89. package/dist/utils/docs-locator.d.ts.map +1 -0
  90. package/dist/utils/docs-locator.js +82 -0
  91. package/dist/utils/docs-locator.js.map +1 -0
  92. package/dist/utils/error-handler.d.ts.map +1 -1
  93. package/dist/utils/error-handler.js +7 -4
  94. package/dist/utils/error-handler.js.map +1 -1
  95. package/dist/utils/logger.d.ts.map +1 -1
  96. package/dist/utils/logger.js +9 -6
  97. package/dist/utils/logger.js.map +1 -1
  98. package/dist/utils/quotation.d.ts +71 -0
  99. package/dist/utils/quotation.d.ts.map +1 -0
  100. package/dist/utils/quotation.js +140 -0
  101. package/dist/utils/quotation.js.map +1 -0
  102. package/dist/utils/rate-limiter.d.ts +8 -1
  103. package/dist/utils/rate-limiter.d.ts.map +1 -1
  104. package/dist/utils/rate-limiter.js +25 -1
  105. package/dist/utils/rate-limiter.js.map +1 -1
  106. package/dist/utils/redact.d.ts +48 -0
  107. package/dist/utils/redact.d.ts.map +1 -0
  108. package/dist/utils/redact.js +100 -0
  109. package/dist/utils/redact.js.map +1 -0
  110. package/package.json +55 -53
@@ -4,6 +4,7 @@
4
4
  * Cada mensaje está redactado para que un agente de IA entienda
5
5
  * exactamente qué salió mal y qué acción tomar.
6
6
  */
7
+ import { redact } from './redact.js';
7
8
  export class CompraAgilApiError extends Error {
8
9
  httpStatus;
9
10
  apiErrors;
@@ -18,8 +19,10 @@ export class CompraAgilApiError extends Error {
18
19
  }
19
20
  }
20
21
  function getActionableMessage(httpStatus, apiErrors) {
22
+ // El mensaje viene de la API: no se controla su contenido y podría hacer eco
23
+ // de la URL solicitada (que en el endpoint legado lleva el ticket en la query).
21
24
  const detail = apiErrors.length > 0
22
- ? ` Detalle de la API: "${apiErrors[0].mensaje}"`
25
+ ? ` Detalle de la API: "${redact(apiErrors[0].mensaje ?? '')}"`
23
26
  : '';
24
27
  switch (httpStatus) {
25
28
  case 400:
@@ -29,7 +32,7 @@ function getActionableMessage(httpStatus, apiErrors) {
29
32
  case 403:
30
33
  return `Ticket de acceso inválido, inactivo o bloqueado. Verifica que el ticket sea correcto y esté vigente. Si fue bloqueado, solicita uno nuevo en https://www.chilecompra.cl/api/.${detail}`;
31
34
  case 404:
32
- return `No se encontró una Compra Ágil con el código proporcionado. Verifica que el código tenga el formato correcto (ej: 1057539-228-COT26) y que el proceso exista y sea público.${detail}`;
35
+ return `No se encontró el recurso solicitado (Compra Ágil u Orden de Compra) con el código proporcionado. Verifica que el código sea correcto, que exista y sea público.${detail}`;
33
36
  case 429:
34
37
  return formatRateLimitMessage(detail);
35
38
  case 500:
@@ -54,10 +57,10 @@ function formatRateLimitMessage(detail) {
54
57
  export async function handleApiResponse(response) {
55
58
  if (response.ok) {
56
59
  const json = await response.json();
57
- if (json.success === 'NOK') {
60
+ if (json && json.success === 'NOK') {
58
61
  throw new CompraAgilApiError(response.status, json.errors);
59
62
  }
60
- return json.payload;
63
+ return (json && typeof json === 'object' && 'payload' in json) ? json.payload : json;
61
64
  }
62
65
  let apiErrors = [];
63
66
  try {
@@ -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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmBH,wBAAgB,YAAY,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAE9C;AAyBD,eAAO,MAAM,MAAM;iBACJ,MAAM,SAAS,OAAO;gBACvB,MAAM,SAAS,OAAO;gBACtB,MAAM,SAAS,OAAO;iBACrB,MAAM,SAAS,OAAO;CACpC,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqBH,wBAAgB,YAAY,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CAE9C;AA2BD,eAAO,MAAM,MAAM;iBACJ,MAAM,SAAS,OAAO;gBACvB,MAAM,SAAS,OAAO;gBACtB,MAAM,SAAS,OAAO;iBACrB,MAAM,SAAS,OAAO;CACpC,CAAC"}
@@ -5,6 +5,7 @@
5
5
  * a stdout (console.log) ya que corrompe el stream JSON-RPC.
6
6
  * Todo el logging va exclusivamente a stderr (console.error).
7
7
  */
8
+ import { redact } from './redact.js';
8
9
  const LOG_LEVELS = {
9
10
  debug: 0,
10
11
  info: 1,
@@ -23,17 +24,19 @@ function log(level, message, data) {
23
24
  if (LOG_LEVELS[level] < LOG_LEVELS[currentLevel])
24
25
  return;
25
26
  const prefix = `[${formatTimestamp()}] [${level.toUpperCase()}]`;
26
- const line = data
27
- ? `${prefix} ${message} ${JSON.stringify(data)}`
28
- : `${prefix} ${message}`;
27
+ // Redacción en el punto de salida: cubre tanto el mensaje como los datos
28
+ // adjuntos, sin importar de qué ruta provengan.
29
+ const cuerpo = data ? `${message} ${JSON.stringify(data)}` : message;
30
+ const seguro = redact(cuerpo);
29
31
  // SIEMPRE stderr, NUNCA stdout
30
- console.error(line);
31
- // Intentar enviar el log de forma nativa al cliente MCP si el servidor está configurado
32
+ console.error(`${prefix} ${seguro}`);
33
+ // El cliente MCP recibe los logs de forma nativa — es decir, van al contexto
34
+ // del LLM y a la transcripción. Se envía la versión ya redactada.
32
35
  if (mcpServer) {
33
36
  mcpServer.sendLoggingMessage({
34
37
  level,
35
38
  logger: 'mcp-compra-agil',
36
- data: data ? `${message} ${JSON.stringify(data)}` : message,
39
+ data: seguro,
37
40
  }).catch(() => {
38
41
  // Ignorar fallos silenciosamente para no interrumpir el flujo principal
39
42
  });
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,YAAY,GAAc,OAAO,CAAC,GAAG,CAAC,SAAsB,IAAI,MAAM,CAAC;AAE7E,SAAS,eAAe;IACtB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,IAAI,SAAS,GAAQ,IAAI,CAAC;AAE1B,MAAM,UAAU,YAAY,CAAC,MAAW;IACtC,SAAS,GAAG,MAAM,CAAC;AACrB,CAAC;AAED,SAAS,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAAc;IAC3D,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO;IAEzD,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,MAAM,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI;QACf,CAAC,CAAC,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QAChD,CAAC,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC;IAE3B,+BAA+B;IAC/B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEpB,wFAAwF;IACxF,IAAI,SAAS,EAAE,CAAC;QACd,SAAS,CAAC,kBAAkB,CAAC;YAC3B,KAAK;YACL,MAAM,EAAE,iBAAiB;YACzB,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;SAC5D,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,wEAAwE;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;IAC/D,IAAI,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;CAChE,CAAC"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrC,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,YAAY,GAAc,OAAO,CAAC,GAAG,CAAC,SAAsB,IAAI,MAAM,CAAC;AAE7E,SAAS,eAAe;IACtB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,IAAI,SAAS,GAAQ,IAAI,CAAC;AAE1B,MAAM,UAAU,YAAY,CAAC,MAAW;IACtC,SAAS,GAAG,MAAM,CAAC;AACrB,CAAC;AAED,SAAS,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAAc;IAC3D,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO;IAEzD,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,MAAM,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC;IACjE,yEAAyE;IACzE,gDAAgD;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IACrE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAE9B,+BAA+B;IAC/B,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC;IAErC,6EAA6E;IAC7E,kEAAkE;IAClE,IAAI,SAAS,EAAE,CAAC;QACd,SAAS,CAAC,kBAAkB,CAAC;YAC3B,KAAK;YACL,MAAM,EAAE,iBAAiB;YACzB,IAAI,EAAE,MAAM;SACb,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,wEAAwE;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;IAC/D,IAAI,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAW,EAAE,IAAc,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;CAChE,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Utilidades de cotización: análisis de precios y detección de adjudicación.
3
+ *
4
+ * ⚠ REALIDAD DE LA API (verificado empíricamente contra el servicio real,
5
+ * julio 2026 — 45 procesos inspeccionados, 52 cotizaciones — y re-confirmado
6
+ * en septiembre 2026 con resultados idénticos):
7
+ *
8
+ * • `proveedor_seleccionado` SÍ existe, pero su valor fue **0 en el 100%**
9
+ * de las cotizaciones observadas. Nunca se observó un 1.
10
+ * • Ningún proceso traía `id_orden_compra` (siempre null).
11
+ * • El filtro `estado=proveedor_seleccionado` devuelve **0 resultados**.
12
+ * • El filtro `estado=oc_emitida` devuelve **HTTP 400** (ni siquiera es válido).
13
+ * • `seleccion.*` y `estado_cotizacion.*` NO existen en la respuesta.
14
+ * En su lugar hay `estado` (número; siempre 3 en la muestra).
15
+ *
16
+ * CONCLUSIÓN: la API no expone procesos adjudicados. Cualquier análisis
17
+ * basado en "el precio que ganó" está condenado a no encontrar datos.
18
+ * El Ejemplo 8.6 de la Guía oficial v3.0 (detectar OCs emitidas recorriendo
19
+ * `estado=proveedor_seleccionado`) es irrealizable por la misma razón:
20
+ * su primer paso devuelve una lista vacía.
21
+ *
22
+ * Lo que SÍ hay: cotizaciones reales con `precio_unitario` y `valor_neto`
23
+ * (17 de 45 procesos las traían). Por eso el análisis se basa en precios
24
+ * COTIZADOS — señal de mercado genuina — y no en precios adjudicados.
25
+ */
26
+ import type { ProveedorCotizando } from '../api/compra-agil-client.js';
27
+ /**
28
+ * Determina si un proveedor fue el adjudicado.
29
+ *
30
+ * Se conserva porque es correcta si la API alguna vez publica adjudicaciones,
31
+ * pero en la práctica retorna false SIEMPRE (ver nota de cabecera). No debe
32
+ * usarse como única fuente de un análisis: hazlo degradar con elegancia.
33
+ */
34
+ export declare function esGanador(prov: ProveedorCotizando): boolean;
35
+ /**
36
+ * Una cotización es admisible si el comprador no la declaró inadmisible.
37
+ * Útil para separar precios "sanos" de los descartados por incumplimiento
38
+ * (ej: "Oferta no cumple con garantía solicitada"), que distorsionan la muestra.
39
+ */
40
+ export declare function esAdmisible(prov: ProveedorCotizando): boolean;
41
+ /**
42
+ * Extrae el precio UNITARIO de la cotización de un proveedor para un término
43
+ * de búsqueda dado. Retorna null si no hay un precio unitario confiable.
44
+ *
45
+ * No cae al monto total: el precio unitario y el total NO son comparables y
46
+ * mezclarlos corrompe cualquier estadística agregada.
47
+ */
48
+ export declare function extraerPrecioUnitario(prov: ProveedorCotizando, keyword?: string): number | null;
49
+ /**
50
+ * Extrae el monto NETO total de la cotización de un proveedor.
51
+ * Útil para estadísticas de monto cotizado (distintas de precio unitario).
52
+ */
53
+ export declare function extraerMontoNeto(prov: ProveedorCotizando): number | null;
54
+ export interface EstadisticasPrecio {
55
+ muestras: number;
56
+ minimo: number;
57
+ maximo: number;
58
+ promedio: number;
59
+ mediana: number;
60
+ /** Percentil 25 — referencia para posicionarse de forma competitiva. */
61
+ p25: number;
62
+ }
63
+ /**
64
+ * Calcula estadísticas de una serie de precios.
65
+ * Retorna null si la serie está vacía, para poder distinguir "sin datos"
66
+ * de "datos que dan cero".
67
+ */
68
+ export declare function calcularEstadisticas(valores: number[]): EstadisticasPrecio | null;
69
+ /** Percentil por interpolación lineal sobre una serie YA ordenada. */
70
+ export declare function percentil(ordenados: number[], p: number): number;
71
+ //# sourceMappingURL=quotation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quotation.d.ts","sourceRoot":"","sources":["../../src/utils/quotation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAmB3D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAG7D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,EACxB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CA4Bf;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAExE;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,kBAAkB,GAAG,IAAI,CAYjF;AAED,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAQhE"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Utilidades de cotización: análisis de precios y detección de adjudicación.
3
+ *
4
+ * ⚠ REALIDAD DE LA API (verificado empíricamente contra el servicio real,
5
+ * julio 2026 — 45 procesos inspeccionados, 52 cotizaciones — y re-confirmado
6
+ * en septiembre 2026 con resultados idénticos):
7
+ *
8
+ * • `proveedor_seleccionado` SÍ existe, pero su valor fue **0 en el 100%**
9
+ * de las cotizaciones observadas. Nunca se observó un 1.
10
+ * • Ningún proceso traía `id_orden_compra` (siempre null).
11
+ * • El filtro `estado=proveedor_seleccionado` devuelve **0 resultados**.
12
+ * • El filtro `estado=oc_emitida` devuelve **HTTP 400** (ni siquiera es válido).
13
+ * • `seleccion.*` y `estado_cotizacion.*` NO existen en la respuesta.
14
+ * En su lugar hay `estado` (número; siempre 3 en la muestra).
15
+ *
16
+ * CONCLUSIÓN: la API no expone procesos adjudicados. Cualquier análisis
17
+ * basado en "el precio que ganó" está condenado a no encontrar datos.
18
+ * El Ejemplo 8.6 de la Guía oficial v3.0 (detectar OCs emitidas recorriendo
19
+ * `estado=proveedor_seleccionado`) es irrealizable por la misma razón:
20
+ * su primer paso devuelve una lista vacía.
21
+ *
22
+ * Lo que SÍ hay: cotizaciones reales con `precio_unitario` y `valor_neto`
23
+ * (17 de 45 procesos las traían). Por eso el análisis se basa en precios
24
+ * COTIZADOS — señal de mercado genuina — y no en precios adjudicados.
25
+ */
26
+ /**
27
+ * Determina si un proveedor fue el adjudicado.
28
+ *
29
+ * Se conserva porque es correcta si la API alguna vez publica adjudicaciones,
30
+ * pero en la práctica retorna false SIEMPRE (ver nota de cabecera). No debe
31
+ * usarse como única fuente de un análisis: hazlo degradar con elegancia.
32
+ */
33
+ export function esGanador(prov) {
34
+ // Señal 1: flag directo (la API lo entrega como número: 0 | 1)
35
+ if (prov.proveedor_seleccionado === true || prov.proveedor_seleccionado === 1) {
36
+ return true;
37
+ }
38
+ // Señal 2: objeto `seleccion` anidado — documentado pero inexistente en la
39
+ // respuesta real. Se mantiene por si la API lo incorpora.
40
+ if (prov.seleccion?.proveedor_seleccionado === true) {
41
+ return true;
42
+ }
43
+ // Señal 3: estado_por_comprador === '1' (observado siempre null)
44
+ if (prov.estado_por_comprador === '1') {
45
+ return true;
46
+ }
47
+ // Señal 4: motivo/criterio de selección presente implica adjudicación
48
+ if (prov.seleccion?.motivo_seleccion || prov.seleccion?.criterio_seleccion) {
49
+ return true;
50
+ }
51
+ return false;
52
+ }
53
+ /**
54
+ * Una cotización es admisible si el comprador no la declaró inadmisible.
55
+ * Útil para separar precios "sanos" de los descartados por incumplimiento
56
+ * (ej: "Oferta no cumple con garantía solicitada"), que distorsionan la muestra.
57
+ */
58
+ export function esAdmisible(prov) {
59
+ const j = prov.justificacion_inadmisibilidad;
60
+ return j === null || j === undefined || String(j).trim() === '';
61
+ }
62
+ /**
63
+ * Extrae el precio UNITARIO de la cotización de un proveedor para un término
64
+ * de búsqueda dado. Retorna null si no hay un precio unitario confiable.
65
+ *
66
+ * No cae al monto total: el precio unitario y el total NO son comparables y
67
+ * mezclarlos corrompe cualquier estadística agregada.
68
+ */
69
+ export function extraerPrecioUnitario(prov, keyword) {
70
+ const productos = prov.productos_cotizados;
71
+ if (!productos || productos.length === 0) {
72
+ return null;
73
+ }
74
+ // Un solo producto → su precio unitario
75
+ if (productos.length === 1) {
76
+ return normalizarPrecio(productos[0].precio_unitario);
77
+ }
78
+ // Varios productos → intentar casar con la keyword
79
+ if (keyword) {
80
+ const kw = keyword.toLowerCase();
81
+ const matched = productos.find((p) => (p.nombre_producto || '').toLowerCase().includes(kw));
82
+ if (matched) {
83
+ return normalizarPrecio(matched.precio_unitario);
84
+ }
85
+ }
86
+ // Fallback: primer producto con precio unitario válido
87
+ for (const p of productos) {
88
+ const precio = normalizarPrecio(p.precio_unitario);
89
+ if (precio !== null)
90
+ return precio;
91
+ }
92
+ return null;
93
+ }
94
+ /**
95
+ * Extrae el monto NETO total de la cotización de un proveedor.
96
+ * Útil para estadísticas de monto cotizado (distintas de precio unitario).
97
+ */
98
+ export function extraerMontoNeto(prov) {
99
+ return normalizarPrecio(prov.valor_neto) ?? normalizarPrecio(prov.monto_total);
100
+ }
101
+ /**
102
+ * Calcula estadísticas de una serie de precios.
103
+ * Retorna null si la serie está vacía, para poder distinguir "sin datos"
104
+ * de "datos que dan cero".
105
+ */
106
+ export function calcularEstadisticas(valores) {
107
+ if (!valores || valores.length === 0)
108
+ return null;
109
+ const s = [...valores].sort((a, b) => a - b);
110
+ const mid = Math.floor(s.length / 2);
111
+ return {
112
+ muestras: s.length,
113
+ minimo: s[0],
114
+ maximo: s[s.length - 1],
115
+ promedio: Math.round(s.reduce((a, b) => a + b, 0) / s.length),
116
+ mediana: s.length % 2 !== 0 ? s[mid] : Math.round((s[mid - 1] + s[mid]) / 2),
117
+ p25: percentil(s, 25),
118
+ };
119
+ }
120
+ /** Percentil por interpolación lineal sobre una serie YA ordenada. */
121
+ export function percentil(ordenados, p) {
122
+ if (ordenados.length === 0)
123
+ return 0;
124
+ if (ordenados.length === 1)
125
+ return ordenados[0];
126
+ const pos = (p / 100) * (ordenados.length - 1);
127
+ const bajo = Math.floor(pos);
128
+ const alto = Math.ceil(pos);
129
+ if (bajo === alto)
130
+ return ordenados[bajo];
131
+ return Math.round(ordenados[bajo] + (ordenados[alto] - ordenados[bajo]) * (pos - bajo));
132
+ }
133
+ function normalizarPrecio(valor) {
134
+ if (valor === null || valor === undefined)
135
+ return null;
136
+ if (typeof valor !== 'number' || !Number.isFinite(valor) || valor <= 0)
137
+ return null;
138
+ return valor;
139
+ }
140
+ //# sourceMappingURL=quotation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quotation.js","sourceRoot":"","sources":["../../src/utils/quotation.ts"],"names":[],"mappings":"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"}
@@ -10,8 +10,15 @@ export declare class RateLimiter {
10
10
  private currentDay;
11
11
  private isLimited;
12
12
  private limitResetTime;
13
- constructor();
13
+ private readonly maxPerMinute;
14
+ private requestTimestamps;
15
+ constructor(maxPerMinute?: number);
14
16
  private getTodayUTC;
17
+ /**
18
+ * Espera (si es necesario) hasta que haya cupo dentro de la ventana de 1 minuto,
19
+ * de forma proactiva, ANTES de enviar la solicitud. Evita gatillar 429 por ráfagas.
20
+ */
21
+ throttle(): Promise<void>;
15
22
  /**
16
23
  * Registra un request exitoso. Resetea el contador si cambió el día.
17
24
  */
@@ -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;;IAM3C,OAAO,CAAC,WAAW;IAInB;;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;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"}
@@ -11,12 +11,36 @@ export class RateLimiter {
11
11
  currentDay;
12
12
  isLimited = false;
13
13
  limitResetTime = null;
14
- constructor() {
14
+ // Throttle proactivo por minuto (ventana deslizante de timestamps).
15
+ maxPerMinute;
16
+ requestTimestamps = [];
17
+ constructor(maxPerMinute = 40) {
15
18
  this.currentDay = this.getTodayUTC();
19
+ this.maxPerMinute = maxPerMinute;
16
20
  }
17
21
  getTodayUTC() {
18
22
  return new Date().toISOString().split('T')[0];
19
23
  }
24
+ /**
25
+ * Espera (si es necesario) hasta que haya cupo dentro de la ventana de 1 minuto,
26
+ * de forma proactiva, ANTES de enviar la solicitud. Evita gatillar 429 por ráfagas.
27
+ */
28
+ async throttle() {
29
+ // Purgar timestamps con más de 60s de antigüedad
30
+ const cutoff = Date.now() - 60_000;
31
+ this.requestTimestamps = this.requestTimestamps.filter((t) => t > cutoff);
32
+ if (this.requestTimestamps.length >= this.maxPerMinute) {
33
+ // Esperar hasta que el request más antiguo salga de la ventana
34
+ const oldest = this.requestTimestamps[0];
35
+ const waitMs = Math.max(0, oldest + 60_000 - Date.now()) + 5;
36
+ logger.debug(`Rate limiter: throttling ${waitMs}ms (${this.requestTimestamps.length}/${this.maxPerMinute} req/min).`);
37
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
38
+ // Repurgar tras la espera
39
+ const cutoff2 = Date.now() - 60_000;
40
+ this.requestTimestamps = this.requestTimestamps.filter((t) => t > cutoff2);
41
+ }
42
+ this.requestTimestamps.push(Date.now());
43
+ }
20
44
  /**
21
45
  * Registra un request exitoso. Resetea el contador si cambió el día.
22
46
  */
@@ -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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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Redacción de secretos: punto único de estrangulamiento.
3
+ *
4
+ * POR QUÉ EXISTE
5
+ * El ticket de acceso viaja en la query string del endpoint legado
6
+ * (/servicios/.../OrdenCompra.json). Se verificó empíricamente que algunos
7
+ * errores de `fetch` incluyen la URL completa en su mensaje:
8
+ *
9
+ * TypeError: Failed to parse URL from http://host/x?ticket=SECRETO
10
+ *
11
+ * Ese texto termina en la respuesta de la tool → contexto del LLM →
12
+ * transcripción, logs y capturas de pantalla. Auditar caso por caso cada
13
+ * `String(error)` es una batalla perdida: siempre aparecerá una ruta nueva.
14
+ *
15
+ * En vez de eso, TODO string que sale del proceso pasa por `redact()`.
16
+ * Aunque surja un camino de error imprevisto, el secreto no se escapa.
17
+ *
18
+ * Se aplica en tres puntos: el logger, el error-handler y los catch de tools.
19
+ */
20
+ /**
21
+ * Registra un valor secreto para que sea removido de toda salida.
22
+ * Idempotente y seguro de llamar con undefined.
23
+ */
24
+ export declare function registrarSecreto(valor: string | undefined | null): void;
25
+ /** Solo para tests: limpia los secretos registrados. */
26
+ export declare function _resetSecretos(): void;
27
+ /** Cantidad de secretos registrados (diagnóstico). */
28
+ export declare function secretosRegistrados(): number;
29
+ /**
30
+ * Elimina cualquier secreto conocido de un texto.
31
+ *
32
+ * Defensa en dos capas:
33
+ * 1. Coincidencia exacta de los secretos registrados.
34
+ * 2. Patrón `ticket=<valor>` en query strings — cubre el caso en que el
35
+ * secreto no alcanzó a registrarse (ej. error durante el arranque).
36
+ */
37
+ export declare function redact(texto: string): string;
38
+ /**
39
+ * Convierte cualquier valor lanzado en un string seguro para mostrar.
40
+ * Reemplaza el patrón `String(error)` usado en los catch de las tools.
41
+ */
42
+ export declare function safeError(error: unknown): string;
43
+ /**
44
+ * Enmascara una credencial para mostrarla como referencia sin revelarla.
45
+ * Ej: "abcd1234-ef56-7890" → "••••7890"
46
+ */
47
+ export declare function pista(valor: string | undefined | null): string;
48
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/utils/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AASH;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CAWvE;AAED,wDAAwD;AACxD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,sDAAsD;AACtD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgB5C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAUhD;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAK9D"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Redacción de secretos: punto único de estrangulamiento.
3
+ *
4
+ * POR QUÉ EXISTE
5
+ * El ticket de acceso viaja en la query string del endpoint legado
6
+ * (/servicios/.../OrdenCompra.json). Se verificó empíricamente que algunos
7
+ * errores de `fetch` incluyen la URL completa en su mensaje:
8
+ *
9
+ * TypeError: Failed to parse URL from http://host/x?ticket=SECRETO
10
+ *
11
+ * Ese texto termina en la respuesta de la tool → contexto del LLM →
12
+ * transcripción, logs y capturas de pantalla. Auditar caso por caso cada
13
+ * `String(error)` es una batalla perdida: siempre aparecerá una ruta nueva.
14
+ *
15
+ * En vez de eso, TODO string que sale del proceso pasa por `redact()`.
16
+ * Aunque surja un camino de error imprevisto, el secreto no se escapa.
17
+ *
18
+ * Se aplica en tres puntos: el logger, el error-handler y los catch de tools.
19
+ */
20
+ const MARCA = '[REDACTED]';
21
+ /** Longitud mínima para registrar un secreto: evita redactar palabras comunes. */
22
+ const LARGO_MINIMO = 8;
23
+ const secretos = new Set();
24
+ /**
25
+ * Registra un valor secreto para que sea removido de toda salida.
26
+ * Idempotente y seguro de llamar con undefined.
27
+ */
28
+ export function registrarSecreto(valor) {
29
+ if (!valor)
30
+ return;
31
+ const v = valor.trim();
32
+ // Un secreto muy corto redactaría texto legítimo por coincidencia.
33
+ if (v.length < LARGO_MINIMO)
34
+ return;
35
+ secretos.add(v);
36
+ // La misma credencial puede aparecer URL-encodeada dentro de una URL.
37
+ const encoded = encodeURIComponent(v);
38
+ if (encoded !== v)
39
+ secretos.add(encoded);
40
+ }
41
+ /** Solo para tests: limpia los secretos registrados. */
42
+ export function _resetSecretos() {
43
+ secretos.clear();
44
+ }
45
+ /** Cantidad de secretos registrados (diagnóstico). */
46
+ export function secretosRegistrados() {
47
+ return secretos.size;
48
+ }
49
+ /**
50
+ * Elimina cualquier secreto conocido de un texto.
51
+ *
52
+ * Defensa en dos capas:
53
+ * 1. Coincidencia exacta de los secretos registrados.
54
+ * 2. Patrón `ticket=<valor>` en query strings — cubre el caso en que el
55
+ * secreto no alcanzó a registrarse (ej. error durante el arranque).
56
+ */
57
+ export function redact(texto) {
58
+ if (!texto)
59
+ return texto;
60
+ let out = texto;
61
+ for (const s of secretos) {
62
+ if (out.includes(s)) {
63
+ out = out.split(s).join(MARCA);
64
+ }
65
+ }
66
+ // Capa 2: cualquier ticket= en una URL, esté o no registrado.
67
+ out = out.replace(/([?&]ticket=)[^&\s"'<>)\]]+/gi, `$1${MARCA}`);
68
+ // Capa 2b: header/JSON con forma "ticket": "valor"
69
+ out = out.replace(/(["']?ticket["']?\s*[:=]\s*["'])([^"']+)(["'])/gi, `$1${MARCA}$3`);
70
+ return out;
71
+ }
72
+ /**
73
+ * Convierte cualquier valor lanzado en un string seguro para mostrar.
74
+ * Reemplaza el patrón `String(error)` usado en los catch de las tools.
75
+ */
76
+ export function safeError(error) {
77
+ let base;
78
+ if (error instanceof Error) {
79
+ // El `cause` de undici puede traer detalle útil (y potencialmente la URL).
80
+ const cause = error.cause;
81
+ base = cause ? `${error.message} (${String(cause)})` : error.message;
82
+ }
83
+ else {
84
+ base = String(error);
85
+ }
86
+ return redact(base);
87
+ }
88
+ /**
89
+ * Enmascara una credencial para mostrarla como referencia sin revelarla.
90
+ * Ej: "abcd1234-ef56-7890" → "••••7890"
91
+ */
92
+ export function pista(valor) {
93
+ if (!valor)
94
+ return '(no configurado)';
95
+ const v = valor.trim();
96
+ if (v.length <= 4)
97
+ return '••••';
98
+ return `••••${v.slice(-4)}`;
99
+ }
100
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/utils/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,KAAK,GAAG,YAAY,CAAC;AAE3B,kFAAkF;AAClF,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;AAEnC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAgC;IAC/D,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,mEAAmE;IACnE,IAAI,CAAC,CAAC,MAAM,GAAG,YAAY;QAAE,OAAO;IAEpC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAEhB,sEAAsE;IACtE,MAAM,OAAO,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;IACtC,IAAI,OAAO,KAAK,CAAC;QAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,cAAc;IAC5B,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,mBAAmB;IACjC,OAAO,QAAQ,CAAC,IAAI,CAAC;AACvB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,GAAG,GAAG,KAAK,CAAC;IAEhB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACpB,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,+BAA+B,EAAE,KAAK,KAAK,EAAE,CAAC,CAAC;IACjE,mDAAmD;IACnD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,kDAAkD,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC;IAEtF,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,IAAY,CAAC;IACjB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,2EAA2E;QAC3E,MAAM,KAAK,GAAI,KAAqC,CAAC,KAAK,CAAC;QAC3D,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,KAAK,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;IACvE,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,KAAgC;IACpD,IAAI,CAAC,KAAK;QAAE,OAAO,kBAAkB,CAAC;IACtC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACjC,OAAO,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9B,CAAC"}