@ssolis-ti/mcp-compra-agil 2.4.0 → 2.5.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.
- package/README.md +21 -6
- package/dist/api/compra-agil-client.d.ts +29 -0
- package/dist/api/compra-agil-client.d.ts.map +1 -1
- package/dist/api/compra-agil-client.js +35 -0
- package/dist/api/compra-agil-client.js.map +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/services/monitor.js +11 -1
- package/dist/services/monitor.js.map +1 -1
- package/dist/tools/analizar-precios-mercado.d.ts.map +1 -1
- package/dist/tools/analizar-precios-mercado.js +68 -14
- package/dist/tools/analizar-precios-mercado.js.map +1 -1
- package/dist/tools/auditar-desiertas.d.ts.map +1 -1
- package/dist/tools/auditar-desiertas.js +31 -8
- package/dist/tools/auditar-desiertas.js.map +1 -1
- package/dist/tools/buscar-compras.d.ts.map +1 -1
- package/dist/tools/buscar-compras.js +2 -0
- package/dist/tools/buscar-compras.js.map +1 -1
- package/dist/tools/detalle-compra.d.ts.map +1 -1
- package/dist/tools/detalle-compra.js +2 -0
- package/dist/tools/detalle-compra.js.map +1 -1
- package/dist/tools/detalle-oc.d.ts.map +1 -1
- package/dist/tools/detalle-oc.js +2 -0
- package/dist/tools/detalle-oc.js.map +1 -1
- package/dist/tools/documentos.d.ts.map +1 -1
- package/dist/tools/documentos.js +6 -0
- package/dist/tools/documentos.js.map +1 -1
- package/dist/tools/estadisticas-uso.d.ts.map +1 -1
- package/dist/tools/estadisticas-uso.js +2 -0
- package/dist/tools/estadisticas-uso.js.map +1 -1
- package/dist/tools/generar-borrador.d.ts +1 -1
- package/dist/tools/generar-borrador.d.ts.map +1 -1
- package/dist/tools/generar-borrador.js +35 -18
- package/dist/tools/generar-borrador.js.map +1 -1
- package/dist/tools/generar-informe.d.ts.map +1 -1
- package/dist/tools/generar-informe.js +5 -1
- package/dist/tools/generar-informe.js.map +1 -1
- package/dist/tools/monitorear-cambios.d.ts.map +1 -1
- package/dist/tools/monitorear-cambios.js +2 -0
- package/dist/tools/monitorear-cambios.js.map +1 -1
- package/dist/tools/radar-oportunidades.d.ts +5 -0
- package/dist/tools/radar-oportunidades.d.ts.map +1 -1
- package/dist/tools/radar-oportunidades.js +18 -4
- package/dist/tools/radar-oportunidades.js.map +1 -1
- package/dist/tools/verificar-hora.d.ts +13 -0
- package/dist/tools/verificar-hora.d.ts.map +1 -0
- package/dist/tools/verificar-hora.js +71 -0
- package/dist/tools/verificar-hora.js.map +1 -0
- package/dist/tools/verificar-oc.d.ts.map +1 -1
- package/dist/tools/verificar-oc.js +2 -0
- package/dist/tools/verificar-oc.js.map +1 -1
- package/dist/tools/verificar-ticket.js +1 -1
- package/dist/tools/verificar-ticket.js.map +1 -1
- package/dist/utils/concurrencia.d.ts +63 -0
- package/dist/utils/concurrencia.d.ts.map +1 -0
- package/dist/utils/concurrencia.js +122 -0
- package/dist/utils/concurrencia.js.map +1 -0
- package/dist/utils/error-handler.d.ts.map +1 -1
- package/dist/utils/error-handler.js +8 -0
- package/dist/utils/error-handler.js.map +1 -1
- package/dist/utils/fechas.d.ts +50 -0
- package/dist/utils/fechas.d.ts.map +1 -0
- package/dist/utils/fechas.js +77 -0
- package/dist/utils/fechas.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +13 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/ntp.d.ts +51 -0
- package/dist/utils/ntp.d.ts.map +1 -0
- package/dist/utils/ntp.js +121 -0
- package/dist/utils/ntp.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radar-oportunidades.d.ts","sourceRoot":"","sources":["../../src/tools/radar-oportunidades.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"radar-oportunidades.d.ts","sourceRoot":"","sources":["../../src/tools/radar-oportunidades.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAoBhF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,MAAM,EACX,SAAS,SAAI,GACZ,gBAAgB,GAAG,IAAI,CAqGzB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,yEAAyE;IACzE,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,8EAA8E;IAC9E,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,WAAW,EACnB,GAAG,GAAE,MAAmB,GACvB,OAAO,CAAC,UAAU,CAAC,CA6BrB;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAoD5F"}
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { CompraAgilApiError } from '../utils/error-handler.js';
|
|
3
3
|
import { logger } from '../utils/logger.js';
|
|
4
4
|
import { safeError } from '../utils/redact.js';
|
|
5
|
+
import { parsearFechaApi, enHoraDeChile, NOTA_ZONA_HORARIA } from '../utils/fechas.js';
|
|
5
6
|
const TOOL_NAME = 'radar_oportunidades_calientes';
|
|
6
7
|
const TOOL_DESCRIPTION = `Escanea, califica y clasifica de forma priorizada los procesos de Compra Ágil activos (publicados).
|
|
7
8
|
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.
|
|
@@ -11,7 +12,7 @@ const inputSchema = {
|
|
|
11
12
|
q: z.string().optional().describe('Término de búsqueda opcional para acotar a un rubro o producto específico (ej: "licencias").'),
|
|
12
13
|
presupuesto_minimo: z.number().optional().describe('Filtrar solo procesos con presupuesto disponible mayor o igual a este monto en CLP.'),
|
|
13
14
|
limite_resultados: z.number().min(1).max(20).default(10).optional().describe('Cantidad máxima de oportunidades destacadas a retornar (1-20, default 10).'),
|
|
14
|
-
max_paginas: z.number().min(1).max(10).default(3).optional().describe('Cuántas páginas de 50 resultados escanear antes de rankear (1-10, default 3 = hasta 150 procesos). Más páginas
|
|
15
|
+
max_paginas: z.number().min(1).max(10).default(3).optional().describe('Cuántas páginas de 50 resultados escanear antes de rankear (1-10, default 3 = hasta 150 procesos). Cada página es una consulta secuencial de 10-17 s (medido en septiembre de 2026), así que 3 páginas rondan los 30-50 s. Más páginas dan más cobertura, pero cuestan cuota y tiempo de forma lineal.'),
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
17
18
|
* Calcula el "Hot Score" de una oportunidad de forma pura (sin efectos secundarios).
|
|
@@ -23,10 +24,16 @@ export function evaluarOportunidad(item, now, minBudget = 0) {
|
|
|
23
24
|
const budget = item.montos?.monto_disponible_clp || item.montos?.monto_disponible || 0;
|
|
24
25
|
if (budget < minBudget)
|
|
25
26
|
return null;
|
|
27
|
+
// ⚠ La fecha se interpreta con `parsearFechaApi`, NO con `new Date()`. La API
|
|
28
|
+
// entrega "2026-09-11 12:00" sin zona horaria, y V8 lo leería en la del
|
|
29
|
+
// servidor: medido, el mismo dato daba 15:00Z desplegado en Chile y 12:00Z
|
|
30
|
+
// en UTC. Es decir, `horas_restantes` —y con ella hasta 30 puntos de
|
|
31
|
+
// urgencia— cambiaban según dónde corriera el proceso. Ahora el resultado
|
|
32
|
+
// es el mismo en cualquier parte.
|
|
26
33
|
let hoursLeft = 0;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
hoursLeft = (
|
|
34
|
+
const cierre = parsearFechaApi(item.fechas?.fecha_cierre);
|
|
35
|
+
if (cierre) {
|
|
36
|
+
hoursLeft = (cierre.getTime() - now) / (1000 * 60 * 60);
|
|
30
37
|
}
|
|
31
38
|
if (hoursLeft <= 0)
|
|
32
39
|
return null;
|
|
@@ -110,6 +117,7 @@ export function evaluarOportunidad(item, now, minBudget = 0) {
|
|
|
110
117
|
ofertas_recibidas: bids,
|
|
111
118
|
horas_restantes: Math.round(hoursLeft * 10) / 10,
|
|
112
119
|
fecha_cierre: item.fechas?.fecha_cierre,
|
|
120
|
+
fecha_cierre_hora_chile: enHoraDeChile(item.fechas?.fecha_cierre),
|
|
113
121
|
llamado,
|
|
114
122
|
puntuacion_caliente: score,
|
|
115
123
|
factores_calificacion: factors,
|
|
@@ -151,7 +159,9 @@ export async function recolectarDatosRadar(client, params, now = Date.now()) {
|
|
|
151
159
|
}
|
|
152
160
|
export function registerRadarOportunidades(server, client) {
|
|
153
161
|
server.registerTool(TOOL_NAME, {
|
|
162
|
+
title: "Radar de oportunidades",
|
|
154
163
|
description: TOOL_DESCRIPTION,
|
|
164
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
155
165
|
inputSchema: inputSchema,
|
|
156
166
|
}, async (args) => {
|
|
157
167
|
try {
|
|
@@ -165,6 +175,10 @@ export function registerRadarOportunidades(server, client) {
|
|
|
165
175
|
};
|
|
166
176
|
}
|
|
167
177
|
const result = {
|
|
178
|
+
// Va primero y en la salida —no en los logs— porque quien decide si
|
|
179
|
+
// alcanza a cotizar necesita leerlo. `horas_restantes` se calcula
|
|
180
|
+
// sobre esta misma interpretación.
|
|
181
|
+
_nota_horaria: NOTA_ZONA_HORARIA,
|
|
168
182
|
total_oportunidades_analizadas: datos.totalAnalizadas,
|
|
169
183
|
radar_limite_resultados: datos.limite,
|
|
170
184
|
oportunidades_calientes: datos.oportunidades,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radar-oportunidades.js","sourceRoot":"","sources":["../../src/tools/radar-oportunidades.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"radar-oportunidades.js","sourceRoot":"","sources":["../../src/tools/radar-oportunidades.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: verificar_hora_oficial
|
|
3
|
+
*
|
|
4
|
+
* Compara el reloj de esta máquina con la hora oficial de Chile, que fija el
|
|
5
|
+
* SHOA (Servicio Hidrográfico y Oceanográfico de la Armada).
|
|
6
|
+
*
|
|
7
|
+
* Existe porque los plazos de este servidor se calculan restando la hora local:
|
|
8
|
+
* si el reloj está desviado, `horas_restantes` y el puntaje de urgencia del
|
|
9
|
+
* radar quedan mal aunque la fecha de la API se interprete perfectamente.
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
export declare function registerVerificarHora(server: McpServer): void;
|
|
13
|
+
//# sourceMappingURL=verificar-hora.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verificar-hora.d.ts","sourceRoot":"","sources":["../../src/tools/verificar-hora.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAapE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAyD7D"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: verificar_hora_oficial
|
|
3
|
+
*
|
|
4
|
+
* Compara el reloj de esta máquina con la hora oficial de Chile, que fija el
|
|
5
|
+
* SHOA (Servicio Hidrográfico y Oceanográfico de la Armada).
|
|
6
|
+
*
|
|
7
|
+
* Existe porque los plazos de este servidor se calculan restando la hora local:
|
|
8
|
+
* si el reloj está desviado, `horas_restantes` y el puntaje de urgencia del
|
|
9
|
+
* radar quedan mal aunque la fecha de la API se interprete perfectamente.
|
|
10
|
+
*/
|
|
11
|
+
import { consultarHoraOficial, interpretarDesfase, SERVIDOR_NTP_CHILE, DESFASE_PREOCUPANTE_MS, } from '../utils/ntp.js';
|
|
12
|
+
import { NOTA_ZONA_HORARIA } from '../utils/fechas.js';
|
|
13
|
+
import { safeError } from '../utils/redact.js';
|
|
14
|
+
const TOOL_NAME = 'verificar_hora_oficial';
|
|
15
|
+
const TOOL_DESCRIPTION = `Compara el reloj de esta máquina con la hora oficial de Chile (${SERVIDOR_NTP_CHILE}, del SHOA).
|
|
16
|
+
Úsala cuando los plazos no cuadren o antes de decidir sobre un cierre ajustado: "horas_restantes" y el puntaje de urgencia del radar se calculan restando la hora local, así que un reloj desviado los falsea aunque los datos de la API sean correctos.
|
|
17
|
+
No consume cuota de la API de Mercado Público. Si la red bloquea el puerto UDP 123 —habitual en redes corporativas— lo informa sin fallar.`;
|
|
18
|
+
export function registerVerificarHora(server) {
|
|
19
|
+
server.registerTool(TOOL_NAME, {
|
|
20
|
+
title: 'Verificar la hora oficial de Chile',
|
|
21
|
+
description: TOOL_DESCRIPTION,
|
|
22
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
23
|
+
}, async () => {
|
|
24
|
+
try {
|
|
25
|
+
const r = await consultarHoraOficial();
|
|
26
|
+
if (!r.ok) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: JSON.stringify({
|
|
31
|
+
sincronizado: null,
|
|
32
|
+
servidor_consultado: r.servidor,
|
|
33
|
+
motivo: r.motivo,
|
|
34
|
+
que_significa: 'No se pudo comprobar el reloj, lo que NO implica que esté mal: solo que no se pudo verificar. Los plazos siguen calculándose con la hora local de esta máquina.',
|
|
35
|
+
alternativa: 'Compara manualmente el reloj del sistema con https://www.horaoficial.cl (SHOA).',
|
|
36
|
+
}, null, 2),
|
|
37
|
+
}],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const preocupante = Math.abs(r.desfaseMs) >= DESFASE_PREOCUPANTE_MS;
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: 'text',
|
|
44
|
+
text: JSON.stringify({
|
|
45
|
+
sincronizado: !preocupante,
|
|
46
|
+
servidor_consultado: r.servidor,
|
|
47
|
+
hora_oficial_chile_utc: r.horaOficial,
|
|
48
|
+
hora_de_esta_maquina_utc: r.horaLocal,
|
|
49
|
+
desfase_ms: r.desfaseMs,
|
|
50
|
+
demora_consulta_ms: r.demoraMs,
|
|
51
|
+
diagnostico: interpretarDesfase(r.desfaseMs),
|
|
52
|
+
_nota_horaria: NOTA_ZONA_HORARIA,
|
|
53
|
+
}, null, 2),
|
|
54
|
+
}],
|
|
55
|
+
// Un reloj desviado más de un minuto puede costar una licitación:
|
|
56
|
+
// se marca como error para que el modelo no lo pase por alto.
|
|
57
|
+
isError: preocupante,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{
|
|
63
|
+
type: 'text',
|
|
64
|
+
text: `No se pudo verificar la hora oficial: ${safeError(error)}. Los plazos siguen usando el reloj local de esta máquina.`,
|
|
65
|
+
}],
|
|
66
|
+
isError: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=verificar-hora.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verificar-hora.js","sourceRoot":"","sources":["../../src/tools/verificar-hora.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,oBAAoB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,sBAAsB,GACrF,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,SAAS,GAAG,wBAAwB,CAAC;AAE3C,MAAM,gBAAgB,GAAG,kEAAkE,kBAAkB;;2IAE8B,CAAC;AAE5I,MAAM,UAAU,qBAAqB,CAAC,MAAiB;IACrD,MAAM,CAAC,YAAY,CACjB,SAAS,EACT;QACE,KAAK,EAAE,oCAAoC;QAC3C,WAAW,EAAE,gBAAgB;QAC7B,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;KACzD,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,oBAAoB,EAAE,CAAC;YAEvC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACV,OAAO;oBACL,OAAO,EAAE,CAAC;4BACR,IAAI,EAAE,MAAe;4BACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gCACnB,YAAY,EAAE,IAAI;gCAClB,mBAAmB,EAAE,CAAC,CAAC,QAAQ;gCAC/B,MAAM,EAAE,CAAC,CAAC,MAAM;gCAChB,aAAa,EAAE,iKAAiK;gCAChL,WAAW,EAAE,iFAAiF;6BAC/F,EAAE,IAAI,EAAE,CAAC,CAAC;yBACZ,CAAC;iBACH,CAAC;YACJ,CAAC;YAED,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,sBAAsB,CAAC;YACpE,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,YAAY,EAAE,CAAC,WAAW;4BAC1B,mBAAmB,EAAE,CAAC,CAAC,QAAQ;4BAC/B,sBAAsB,EAAE,CAAC,CAAC,WAAW;4BACrC,wBAAwB,EAAE,CAAC,CAAC,SAAS;4BACrC,UAAU,EAAE,CAAC,CAAC,SAAS;4BACvB,kBAAkB,EAAE,CAAC,CAAC,QAAQ;4BAC9B,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;4BAC5C,aAAa,EAAE,iBAAiB;yBACjC,EAAE,IAAI,EAAE,CAAC,CAAC;qBACZ,CAAC;gBACF,kEAAkE;gBAClE,8DAA8D;gBAC9D,OAAO,EAAE,WAAW;aACrB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,yCAAyC,SAAS,CAAC,KAAK,CAAC,4DAA4D;qBAC5H,CAAC;gBACF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -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,
|
|
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,CA8GrF"}
|
|
@@ -21,7 +21,9 @@ const inputSchema = {
|
|
|
21
21
|
};
|
|
22
22
|
export function registerVerificarOC(server, client) {
|
|
23
23
|
server.registerTool(TOOL_NAME, {
|
|
24
|
+
title: "Verificar Orden de Compra",
|
|
24
25
|
description: TOOL_DESCRIPTION,
|
|
26
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
25
27
|
inputSchema: inputSchema,
|
|
26
28
|
}, async (args) => {
|
|
27
29
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verificar-oc.js","sourceRoot":"","sources":["../../src/tools/verificar-oc.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,SAAS,GAAG,wBAAwB,CAAC;AAE3C,MAAM,gBAAgB,GAAG;;8EAEqD,CAAC;AAE/E,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CACzB,iGAAiG,CAClG;CACF,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,MAAwB;IAC7E,MAAM,CAAC,YAAY,CACjB,SAAS,EACT;QACE,WAAW,EAAE,gBAAgB;
|
|
1
|
+
{"version":3,"file":"verificar-oc.js","sourceRoot":"","sources":["../../src/tools/verificar-oc.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@ const TOOL_DESCRIPTION = `Verifica que el ticket de acceso a la API de Mercado P
|
|
|
14
14
|
NUNCA revela el valor del ticket: solo informa si funciona y muestra los últimos 4 caracteres como referencia.
|
|
15
15
|
Úsala como primer diagnóstico cuando otras herramientas fallen con errores de autenticación.`;
|
|
16
16
|
export function registerVerificarTicket(server, client) {
|
|
17
|
-
server.registerTool(TOOL_NAME, { description: TOOL_DESCRIPTION }, async () => {
|
|
17
|
+
server.registerTool(TOOL_NAME, { title: "Verificar el ticket de acceso", description: TOOL_DESCRIPTION, annotations: { readOnlyHint: true, openWorldHint: true } }, async () => {
|
|
18
18
|
const referencia = pista(process.env.COMPRA_AGIL_TICKET);
|
|
19
19
|
if (!process.env.COMPRA_AGIL_TICKET) {
|
|
20
20
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verificar-ticket.js","sourceRoot":"","sources":["../../src/tools/verificar-ticket.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAErC,MAAM,gBAAgB,GAAG;;6FAEoE,CAAC;AAE9F,MAAM,UAAU,uBAAuB,CAAC,MAAiB,EAAE,MAAwB;IACjF,MAAM,CAAC,YAAY,CACjB,SAAS,EACT,EAAE,WAAW,EAAE,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"verificar-ticket.js","sourceRoot":"","sources":["../../src/tools/verificar-ticket.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAErC,MAAM,gBAAgB,GAAG;;6FAEoE,CAAC;AAE9F,MAAM,UAAU,uBAAuB,CAAC,MAAiB,EAAE,MAAwB;IACjF,MAAM,CAAC,YAAY,CACjB,SAAS,EACT,EAAE,KAAK,EAAE,+BAA+B,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,EACnI,KAAK,IAAI,EAAE;QACT,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAEzD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;YACpC,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE;4BACJ,8BAA8B;4BAC9B,EAAE;4BACF,wFAAwF;4BACxF,4EAA4E;4BAC5E,EAAE;4BACF,6EAA6E;4BAC7E,yCAAyC;yBAC1C,CAAC,IAAI,CAAC,IAAI,CAAC;qBACb,CAAC;gBACF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,yDAAyD;YACzD,EAAE;YACF,yEAAyE;YACzE,0EAA0E;YAC1E,oEAAoE;YACpE,kEAAkE;YAClE,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC;YACpG,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE;4BACJ,8BAA8B;4BAC9B,EAAE;4BACF,uBAAuB,UAAU,EAAE;4BACnC,mDAAmD,IAAI,CAAC,UAAU,CAAC,gBAAgB,gBAAgB;4BACnG,mCAAmC,MAAM,CAAC,iBAAiB,EAAE,CAAC,aAAa,cAAc;4BACzF,EAAE;4BACF,yEAAyE;4BACzE,qEAAqE;yBACtE,CAAC,IAAI,CAAC,IAAI,CAAC;qBACb,CAAC;aACH,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,kBAAkB;gBACjD,CAAC,CAAC,KAAK,CAAC,iBAAiB;gBACzB,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACrB,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE;4BACJ,0DAA0D;4BAC1D,EAAE;4BACF,uBAAuB,UAAU,EAAE;4BACnC,WAAW,OAAO,EAAE;yBACrB,CAAC,IAAI,CAAC,IAAI,CAAC;qBACb,CAAC;gBACF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Límite de concurrencia adaptativo para las consultas a la API.
|
|
3
|
+
*
|
|
4
|
+
* ⚠ POR QUÉ EXISTE: las herramientas de análisis piden varios detalles a la vez
|
|
5
|
+
* —en serie tardaban más de 105 s y ningún cliente MCP espera tanto—, pero un
|
|
6
|
+
* paralelismo fijo es igual de ingenuo en el otro sentido. Medido en
|
|
7
|
+
* producción el 8 de septiembre de 2026, el endpoint de detalle tardaba 20-25 s
|
|
8
|
+
* y devolvía HTTP 504 en 1 de cada 3 consultas: cuando el servicio está así,
|
|
9
|
+
* insistir con la misma tanda solo produce más 504 y gasta cuota en llamadas
|
|
10
|
+
* condenadas.
|
|
11
|
+
*
|
|
12
|
+
* La política es la de control de congestión de TCP (AIMD): ante una señal de
|
|
13
|
+
* saturación se baja a la mitad de inmediato, y se sube de a uno solo cuando
|
|
14
|
+
* una tanda completa sale limpia. Bajar rápido y subir despacio es lo correcto
|
|
15
|
+
* aquí porque el costo de los errores es asimétrico — un 504 desperdicia 30
|
|
16
|
+
* segundos de espera, mientras que ir un poco lento solo cuesta unos segundos.
|
|
17
|
+
*
|
|
18
|
+
* Lo que NO cuenta como congestión: 400, 404 y demás errores por consulta.
|
|
19
|
+
* Esos hablan del ítem pedido, no del estado del servicio, y reaccionar a
|
|
20
|
+
* ellos bajaría el paralelismo por motivos equivocados. El 429 tampoco: de la
|
|
21
|
+
* cuota se encarga el RateLimiter, que sabe esperar el `Retry-After`.
|
|
22
|
+
*/
|
|
23
|
+
export interface OpcionesConcurrencia {
|
|
24
|
+
/** Tareas simultáneas al empezar. */
|
|
25
|
+
inicial?: number;
|
|
26
|
+
/** Nunca se baja de aquí: con 0 no avanzaría nada. */
|
|
27
|
+
minimo?: number;
|
|
28
|
+
/** Techo al recuperarse. */
|
|
29
|
+
maximo?: number;
|
|
30
|
+
}
|
|
31
|
+
/** ¿El fallo habla del servicio saturado o del ítem que se pidió? */
|
|
32
|
+
export declare function esSenalDeCongestion(error: unknown): boolean;
|
|
33
|
+
export declare class LimitadorConcurrencia {
|
|
34
|
+
private limite;
|
|
35
|
+
private readonly minimo;
|
|
36
|
+
private readonly maximo;
|
|
37
|
+
private tandas;
|
|
38
|
+
private congestiones;
|
|
39
|
+
constructor(opciones?: OpcionesConcurrencia);
|
|
40
|
+
get limiteActual(): number;
|
|
41
|
+
estadisticas(): {
|
|
42
|
+
limiteActual: number;
|
|
43
|
+
minimo: number;
|
|
44
|
+
maximo: number;
|
|
45
|
+
tandas: number;
|
|
46
|
+
congestiones: number;
|
|
47
|
+
};
|
|
48
|
+
/** Disminución multiplicativa: a la mitad, en cuanto aparece la primera señal. */
|
|
49
|
+
private reducir;
|
|
50
|
+
/** Aumento aditivo: de a uno, y solo tras una tanda entera sin congestión. */
|
|
51
|
+
private aumentar;
|
|
52
|
+
/**
|
|
53
|
+
* Ejecuta las tareas respetando el límite vigente, que puede bajar A MITAD DE
|
|
54
|
+
* TANDA: si las primeras consultas ya vienen con 504, las que quedan salen con
|
|
55
|
+
* menos paralelismo en vez de repetir el error en bloque.
|
|
56
|
+
*
|
|
57
|
+
* Devuelve un arreglo del mismo largo y en el mismo orden que `tareas`, con
|
|
58
|
+
* `null` en las que fallaron. Nunca lanza: quien llama distingue el fallo por
|
|
59
|
+
* el `null`, que es lo que las herramientas de análisis ya esperan.
|
|
60
|
+
*/
|
|
61
|
+
ejecutar<T>(tareas: Array<() => Promise<T>>): Promise<Array<T | null>>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=concurrencia.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrencia.d.ts","sourceRoot":"","sources":["../../src/utils/concurrencia.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,MAAM,WAAW,oBAAoB;IACnC,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAM3D;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,YAAY,CAAK;gBAEb,QAAQ,GAAE,oBAAyB;IAM/C,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,YAAY,IAAI;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;IAU9G,kFAAkF;IAClF,OAAO,CAAC,OAAO;IASf,8EAA8E;IAC9E,OAAO,CAAC,QAAQ;IAMhB;;;;;;;;OAQG;IACG,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CAoC7E"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Límite de concurrencia adaptativo para las consultas a la API.
|
|
3
|
+
*
|
|
4
|
+
* ⚠ POR QUÉ EXISTE: las herramientas de análisis piden varios detalles a la vez
|
|
5
|
+
* —en serie tardaban más de 105 s y ningún cliente MCP espera tanto—, pero un
|
|
6
|
+
* paralelismo fijo es igual de ingenuo en el otro sentido. Medido en
|
|
7
|
+
* producción el 8 de septiembre de 2026, el endpoint de detalle tardaba 20-25 s
|
|
8
|
+
* y devolvía HTTP 504 en 1 de cada 3 consultas: cuando el servicio está así,
|
|
9
|
+
* insistir con la misma tanda solo produce más 504 y gasta cuota en llamadas
|
|
10
|
+
* condenadas.
|
|
11
|
+
*
|
|
12
|
+
* La política es la de control de congestión de TCP (AIMD): ante una señal de
|
|
13
|
+
* saturación se baja a la mitad de inmediato, y se sube de a uno solo cuando
|
|
14
|
+
* una tanda completa sale limpia. Bajar rápido y subir despacio es lo correcto
|
|
15
|
+
* aquí porque el costo de los errores es asimétrico — un 504 desperdicia 30
|
|
16
|
+
* segundos de espera, mientras que ir un poco lento solo cuesta unos segundos.
|
|
17
|
+
*
|
|
18
|
+
* Lo que NO cuenta como congestión: 400, 404 y demás errores por consulta.
|
|
19
|
+
* Esos hablan del ítem pedido, no del estado del servicio, y reaccionar a
|
|
20
|
+
* ellos bajaría el paralelismo por motivos equivocados. El 429 tampoco: de la
|
|
21
|
+
* cuota se encarga el RateLimiter, que sabe esperar el `Retry-After`.
|
|
22
|
+
*/
|
|
23
|
+
import { logger } from './logger.js';
|
|
24
|
+
/** Códigos que indican que la pasarela no dio abasto, no que la consulta fuera inválida. */
|
|
25
|
+
const CODIGOS_DE_CONGESTION = new Set([502, 503, 504]);
|
|
26
|
+
/** ¿El fallo habla del servicio saturado o del ítem que se pidió? */
|
|
27
|
+
export function esSenalDeCongestion(error) {
|
|
28
|
+
const e = error;
|
|
29
|
+
if (!e)
|
|
30
|
+
return false;
|
|
31
|
+
if (typeof e.httpStatus === 'number' && CODIGOS_DE_CONGESTION.has(e.httpStatus))
|
|
32
|
+
return true;
|
|
33
|
+
// Los cortes de red y timeouts cuentan igual: el servicio no alcanzó a responder.
|
|
34
|
+
return /timeout|ETIMEDOUT|ECONNRESET|socket hang up|fetch failed/i.test(String(e.message ?? ''));
|
|
35
|
+
}
|
|
36
|
+
export class LimitadorConcurrencia {
|
|
37
|
+
limite;
|
|
38
|
+
minimo;
|
|
39
|
+
maximo;
|
|
40
|
+
tandas = 0;
|
|
41
|
+
congestiones = 0;
|
|
42
|
+
constructor(opciones = {}) {
|
|
43
|
+
this.maximo = Math.max(1, opciones.maximo ?? 5);
|
|
44
|
+
this.minimo = Math.max(1, Math.min(opciones.minimo ?? 1, this.maximo));
|
|
45
|
+
this.limite = Math.min(this.maximo, Math.max(this.minimo, opciones.inicial ?? this.maximo));
|
|
46
|
+
}
|
|
47
|
+
get limiteActual() {
|
|
48
|
+
return this.limite;
|
|
49
|
+
}
|
|
50
|
+
estadisticas() {
|
|
51
|
+
return {
|
|
52
|
+
limiteActual: this.limite,
|
|
53
|
+
minimo: this.minimo,
|
|
54
|
+
maximo: this.maximo,
|
|
55
|
+
tandas: this.tandas,
|
|
56
|
+
congestiones: this.congestiones,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Disminución multiplicativa: a la mitad, en cuanto aparece la primera señal. */
|
|
60
|
+
reducir() {
|
|
61
|
+
const antes = this.limite;
|
|
62
|
+
this.limite = Math.max(this.minimo, Math.floor(this.limite / 2));
|
|
63
|
+
this.congestiones++;
|
|
64
|
+
if (this.limite !== antes) {
|
|
65
|
+
logger.warn(`Concurrencia: señal de saturación, se baja de ${antes} a ${this.limite} consultas simultáneas.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Aumento aditivo: de a uno, y solo tras una tanda entera sin congestión. */
|
|
69
|
+
aumentar() {
|
|
70
|
+
if (this.limite >= this.maximo)
|
|
71
|
+
return;
|
|
72
|
+
this.limite++;
|
|
73
|
+
logger.debug(`Concurrencia: tanda limpia, se sube a ${this.limite} consultas simultáneas.`);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Ejecuta las tareas respetando el límite vigente, que puede bajar A MITAD DE
|
|
77
|
+
* TANDA: si las primeras consultas ya vienen con 504, las que quedan salen con
|
|
78
|
+
* menos paralelismo en vez de repetir el error en bloque.
|
|
79
|
+
*
|
|
80
|
+
* Devuelve un arreglo del mismo largo y en el mismo orden que `tareas`, con
|
|
81
|
+
* `null` en las que fallaron. Nunca lanza: quien llama distingue el fallo por
|
|
82
|
+
* el `null`, que es lo que las herramientas de análisis ya esperan.
|
|
83
|
+
*/
|
|
84
|
+
async ejecutar(tareas) {
|
|
85
|
+
const resultados = new Array(tareas.length).fill(null);
|
|
86
|
+
if (tareas.length === 0)
|
|
87
|
+
return resultados;
|
|
88
|
+
this.tandas++;
|
|
89
|
+
let huboCongestion = false;
|
|
90
|
+
let siguiente = 0;
|
|
91
|
+
let enVuelo = 0;
|
|
92
|
+
await new Promise((resolve) => {
|
|
93
|
+
const lanzar = () => {
|
|
94
|
+
while (enVuelo < this.limite && siguiente < tareas.length) {
|
|
95
|
+
const i = siguiente++;
|
|
96
|
+
enVuelo++;
|
|
97
|
+
tareas[i]()
|
|
98
|
+
.then((valor) => { resultados[i] = valor; })
|
|
99
|
+
.catch((e) => {
|
|
100
|
+
resultados[i] = null;
|
|
101
|
+
if (esSenalDeCongestion(e)) {
|
|
102
|
+
huboCongestion = true;
|
|
103
|
+
this.reducir(); // efecto inmediato sobre lo que queda por lanzar
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
.finally(() => {
|
|
107
|
+
enVuelo--;
|
|
108
|
+
if (siguiente >= tareas.length && enVuelo === 0)
|
|
109
|
+
resolve();
|
|
110
|
+
else
|
|
111
|
+
lanzar();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
lanzar();
|
|
116
|
+
});
|
|
117
|
+
if (!huboCongestion)
|
|
118
|
+
this.aumentar();
|
|
119
|
+
return resultados;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=concurrencia.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrencia.js","sourceRoot":"","sources":["../../src/utils/concurrencia.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,4FAA4F;AAC5F,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAWvD,qEAAqE;AACrE,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,MAAM,CAAC,GAAG,KAAyD,CAAC;IACpE,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,IAAI,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7F,kFAAkF;IAClF,OAAO,2DAA2D,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;AACnG,CAAC;AAED,MAAM,OAAO,qBAAqB;IACxB,MAAM,CAAS;IACN,MAAM,CAAS;IACf,MAAM,CAAS;IACxB,MAAM,GAAG,CAAC,CAAC;IACX,YAAY,GAAG,CAAC,CAAC;IAEzB,YAAY,WAAiC,EAAE;QAC7C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,YAAY;QACV,OAAO;YACL,YAAY,EAAE,IAAI,CAAC,MAAM;YACzB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC;IACJ,CAAC;IAED,kFAAkF;IAC1E,OAAO;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,iDAAiD,KAAK,MAAM,IAAI,CAAC,MAAM,yBAAyB,CAAC,CAAC;QAChH,CAAC;IACH,CAAC;IAED,8EAA8E;IACtE,QAAQ;QACd,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,MAAM,CAAC,KAAK,CAAC,yCAAyC,IAAI,CAAC,MAAM,yBAAyB,CAAC,CAAC;IAC9F,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,QAAQ,CAAI,MAA+B;QAC/C,MAAM,UAAU,GAAoB,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC;QAE3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,cAAc,GAAG,KAAK,CAAC;QAC3B,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,OAAO,GAAG,CAAC,CAAC;QAEhB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClC,MAAM,MAAM,GAAG,GAAG,EAAE;gBAClB,OAAO,OAAO,GAAG,IAAI,CAAC,MAAM,IAAI,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;oBAC1D,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;oBACtB,OAAO,EAAE,CAAC;oBACV,MAAM,CAAC,CAAC,CAAC,EAAE;yBACR,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;yBAC3C,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;wBACX,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;wBACrB,IAAI,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;4BAC3B,cAAc,GAAG,IAAI,CAAC;4BACtB,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,iDAAiD;wBACnE,CAAC;oBACH,CAAC,CAAC;yBACD,OAAO,CAAC,GAAG,EAAE;wBACZ,OAAO,EAAE,CAAC;wBACV,IAAI,SAAS,IAAI,MAAM,CAAC,MAAM,IAAI,OAAO,KAAK,CAAC;4BAAE,OAAO,EAAE,CAAC;;4BACtD,MAAM,EAAE,CAAC;oBAChB,CAAC,CAAC,CAAC;gBACP,CAAC;YACH,CAAC,CAAC;YACF,MAAM,EAAE,CAAC;QACX,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,cAAc;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACrC,OAAO,UAAU,CAAC;IACpB,CAAC;CACF"}
|
|
@@ -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;
|
|
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;AA4CD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAoB5E"}
|
|
@@ -39,6 +39,14 @@ function getActionableMessage(httpStatus, apiErrors) {
|
|
|
39
39
|
return `Error interno del servidor de Mercado Público (api2.mercadopublico.cl). Esto no es un problema de tu consulta. Reintenta en unos minutos.${detail}`;
|
|
40
40
|
case 503:
|
|
41
41
|
return `El servicio de Mercado Público está temporalmente no disponible (posible mantenimiento). Reintenta más tarde.${detail}`;
|
|
42
|
+
// 502 y 504 no están en la tabla de errores de la guía oficial, pero la API
|
|
43
|
+
// los devuelve: se observó un 504 sistemático (septiembre 2026) al pedir
|
|
44
|
+
// tamano_pagina=50 sobre `estado=desierta` con búsqueda de texto — la
|
|
45
|
+
// pasarela corta a los ~30 s. Sin este caso caían en "Error inesperado", que
|
|
46
|
+
// hacía parecer un fallo del cliente lo que es una lentitud del servicio.
|
|
47
|
+
case 502:
|
|
48
|
+
case 504:
|
|
49
|
+
return `La pasarela de Mercado Público cortó la conexión antes de que la API respondiera (HTTP ${httpStatus}): la consulta tardó demasiado, no es un error de tus parámetros. Reintenta, y si se repite reduce el trabajo por consulta — un 'tamano_pagina' más chico, menos 'limite_analisis' o un filtro más específico. Las búsquedas por texto sobre 'estado=desierta' son las más lentas.${detail}`;
|
|
42
50
|
default:
|
|
43
51
|
return `Error inesperado HTTP ${httpStatus} de la API de Compra Ágil.${detail}`;
|
|
44
52
|
}
|
|
@@ -1 +1 @@
|
|
|
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"}
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/utils/error-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interpretación de las fechas que entrega la API.
|
|
3
|
+
*
|
|
4
|
+
* ⚠ EL PROBLEMA: la API mezcla dos formatos para el mismo instante.
|
|
5
|
+
*
|
|
6
|
+
* fechas.fecha_cierre "2026-09-11 12:00" ← sin zona
|
|
7
|
+
* convocatoria.fecha_cierre_primer_llamado "2026-09-11T12:00:00Z" ← UTC
|
|
8
|
+
*
|
|
9
|
+
* Verificado en 8 de 8 procesos: el valor es idéntico, pero solo uno declara
|
|
10
|
+
* su zona horaria. Los campos sin marca son justamente los que un proveedor
|
|
11
|
+
* mira para saber cuándo cierra un llamado.
|
|
12
|
+
*
|
|
13
|
+
* ⚠ POR QUÉ NO BASTA CON `new Date()`: ante un string sin zona y con espacio en
|
|
14
|
+
* vez de "T", V8 lo interpreta en la zona horaria DEL SERVIDOR. Medido: el
|
|
15
|
+
* mismo "2026-09-11 12:00" se convierte en 15:00Z si el proceso corre en
|
|
16
|
+
* Chile y en 12:00Z si corre en UTC. Es decir, el radar calculaba
|
|
17
|
+
* `horas_restantes` —y con ello su puntaje de urgencia— con tres horas de
|
|
18
|
+
* diferencia según dónde estuviera desplegado, con los mismos datos.
|
|
19
|
+
*
|
|
20
|
+
* ⚠ POR QUÉ SE ASUME UTC: no se pudo determinar con certeza si esos valores son
|
|
21
|
+
* UTC u hora de Chile. La evidencia está dividida — el filtro `ttl_cambio_ms`
|
|
22
|
+
* de la API trata las marcas `Z` como UTC real, pero la ficha del portal
|
|
23
|
+
* muestra ese mismo "12:00" a usuarios chilenos sin convertir.
|
|
24
|
+
*
|
|
25
|
+
* Ante la duda se elige UTC porque el error es asimétrico. Si el valor fuera
|
|
26
|
+
* hora de Chile y lo leemos como UTC, mostramos el cierre TRES HORAS ANTES de
|
|
27
|
+
* lo real: el proveedor se apura de más. Al revés —leerlo como hora local
|
|
28
|
+
* siendo UTC— le haría creer que tiene tres horas extra y perdería el plazo.
|
|
29
|
+
* Entre apurarse de más y llegar tarde a una licitación, la elección es clara.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Convierte una fecha de la API a un instante absoluto, sin depender de la zona
|
|
33
|
+
* horaria del servidor. Devuelve `null` si el valor falta o no se entiende, para
|
|
34
|
+
* que quien llame distinga "no hay fecha" de "medianoche".
|
|
35
|
+
*/
|
|
36
|
+
export declare function parsearFechaApi(valor: string | null | undefined): Date | null;
|
|
37
|
+
/** ¿El valor viene sin declarar su zona horaria? Entonces se asumió UTC. */
|
|
38
|
+
export declare function esFechaAmbigua(valor: string | null | undefined): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Texto que acompaña a una fecha ambigua para que ni el modelo ni la persona
|
|
41
|
+
* asuman hora local. Va en la salida de las herramientas, no en los logs:
|
|
42
|
+
* quien decide si alcanza a cotizar necesita verlo.
|
|
43
|
+
*/
|
|
44
|
+
export declare const NOTA_ZONA_HORARIA: string;
|
|
45
|
+
/**
|
|
46
|
+
* Misma fecha, expresada también en hora de Chile, para que el usuario no tenga
|
|
47
|
+
* que hacer la resta mental. Devuelve `null` si la fecha no se pudo interpretar.
|
|
48
|
+
*/
|
|
49
|
+
export declare function enHoraDeChile(valor: string | null | undefined): string | null;
|
|
50
|
+
//# sourceMappingURL=fechas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fechas.d.ts","sourceRoot":"","sources":["../../src/utils/fechas.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CAgB7E;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAExE;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,QAG4E,CAAC;AAE3G;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAK7E"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interpretación de las fechas que entrega la API.
|
|
3
|
+
*
|
|
4
|
+
* ⚠ EL PROBLEMA: la API mezcla dos formatos para el mismo instante.
|
|
5
|
+
*
|
|
6
|
+
* fechas.fecha_cierre "2026-09-11 12:00" ← sin zona
|
|
7
|
+
* convocatoria.fecha_cierre_primer_llamado "2026-09-11T12:00:00Z" ← UTC
|
|
8
|
+
*
|
|
9
|
+
* Verificado en 8 de 8 procesos: el valor es idéntico, pero solo uno declara
|
|
10
|
+
* su zona horaria. Los campos sin marca son justamente los que un proveedor
|
|
11
|
+
* mira para saber cuándo cierra un llamado.
|
|
12
|
+
*
|
|
13
|
+
* ⚠ POR QUÉ NO BASTA CON `new Date()`: ante un string sin zona y con espacio en
|
|
14
|
+
* vez de "T", V8 lo interpreta en la zona horaria DEL SERVIDOR. Medido: el
|
|
15
|
+
* mismo "2026-09-11 12:00" se convierte en 15:00Z si el proceso corre en
|
|
16
|
+
* Chile y en 12:00Z si corre en UTC. Es decir, el radar calculaba
|
|
17
|
+
* `horas_restantes` —y con ello su puntaje de urgencia— con tres horas de
|
|
18
|
+
* diferencia según dónde estuviera desplegado, con los mismos datos.
|
|
19
|
+
*
|
|
20
|
+
* ⚠ POR QUÉ SE ASUME UTC: no se pudo determinar con certeza si esos valores son
|
|
21
|
+
* UTC u hora de Chile. La evidencia está dividida — el filtro `ttl_cambio_ms`
|
|
22
|
+
* de la API trata las marcas `Z` como UTC real, pero la ficha del portal
|
|
23
|
+
* muestra ese mismo "12:00" a usuarios chilenos sin convertir.
|
|
24
|
+
*
|
|
25
|
+
* Ante la duda se elige UTC porque el error es asimétrico. Si el valor fuera
|
|
26
|
+
* hora de Chile y lo leemos como UTC, mostramos el cierre TRES HORAS ANTES de
|
|
27
|
+
* lo real: el proveedor se apura de más. Al revés —leerlo como hora local
|
|
28
|
+
* siendo UTC— le haría creer que tiene tres horas extra y perdería el plazo.
|
|
29
|
+
* Entre apurarse de más y llegar tarde a una licitación, la elección es clara.
|
|
30
|
+
*/
|
|
31
|
+
/** Formato sin zona horaria que devuelve la API: "2026-09-11 12:00". */
|
|
32
|
+
const SIN_ZONA = /^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})(:(\d{2}))?$/;
|
|
33
|
+
/**
|
|
34
|
+
* Convierte una fecha de la API a un instante absoluto, sin depender de la zona
|
|
35
|
+
* horaria del servidor. Devuelve `null` si el valor falta o no se entiende, para
|
|
36
|
+
* que quien llame distinga "no hay fecha" de "medianoche".
|
|
37
|
+
*/
|
|
38
|
+
export function parsearFechaApi(valor) {
|
|
39
|
+
if (!valor)
|
|
40
|
+
return null;
|
|
41
|
+
const texto = String(valor).trim();
|
|
42
|
+
const m = SIN_ZONA.exec(texto);
|
|
43
|
+
if (m) {
|
|
44
|
+
// Se le añade la Z que la API omitió: así el resultado es el mismo en
|
|
45
|
+
// cualquier servidor, que es la mitad del problema que esto resuelve.
|
|
46
|
+
const segundos = m[4] ?? '00';
|
|
47
|
+
const d = new Date(`${m[1]}T${m[2]}:${segundos}Z`);
|
|
48
|
+
return isNaN(d.getTime()) ? null : d;
|
|
49
|
+
}
|
|
50
|
+
// Ya trae zona horaria (Z u offset): se respeta lo que dice.
|
|
51
|
+
const d = new Date(texto);
|
|
52
|
+
return isNaN(d.getTime()) ? null : d;
|
|
53
|
+
}
|
|
54
|
+
/** ¿El valor viene sin declarar su zona horaria? Entonces se asumió UTC. */
|
|
55
|
+
export function esFechaAmbigua(valor) {
|
|
56
|
+
return Boolean(valor) && SIN_ZONA.test(String(valor).trim());
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Texto que acompaña a una fecha ambigua para que ni el modelo ni la persona
|
|
60
|
+
* asuman hora local. Va en la salida de las herramientas, no en los logs:
|
|
61
|
+
* quien decide si alcanza a cotizar necesita verlo.
|
|
62
|
+
*/
|
|
63
|
+
export const NOTA_ZONA_HORARIA = 'Las horas provienen de la API sin declarar zona horaria y se interpretan como UTC. ' +
|
|
64
|
+
'En Chile continental (UTC-3) resta 3 horas: un cierre a las "12:00" corresponde a las 09:00 locales. ' +
|
|
65
|
+
'Como la API no lo especifica, confirma el plazo exacto en la ficha del proceso antes de comprometerte.';
|
|
66
|
+
/**
|
|
67
|
+
* Misma fecha, expresada también en hora de Chile, para que el usuario no tenga
|
|
68
|
+
* que hacer la resta mental. Devuelve `null` si la fecha no se pudo interpretar.
|
|
69
|
+
*/
|
|
70
|
+
export function enHoraDeChile(valor) {
|
|
71
|
+
const d = parsearFechaApi(valor);
|
|
72
|
+
if (!d)
|
|
73
|
+
return null;
|
|
74
|
+
// sv-SE da el formato ISO "YYYY-MM-DD HH:MM", legible y sin ambigüedad.
|
|
75
|
+
return d.toLocaleString('sv-SE', { timeZone: 'America/Santiago' }).slice(0, 16);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=fechas.js.map
|