mercadopublico 0.0.2 → 0.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # mercadopublico
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1b30195: Primera versión pública (0.1).
8
+
9
+ - **Cliente tipado** para la API v1 (licitaciones, órdenes de compra, `BuscarProveedor` y `BuscarComprador`) y la v2 de Compra Ágil (listado paginado y detalle), con errores tipados (`MercadoPublicoError`) y validación de entradas antes de gastar consultas.
10
+ - **Un solo modelo, `Proceso`,** para v1, v2 y OCDS:
11
+ - fechas ISO con offset de Santiago;
12
+ - montos `{ valor, moneda, clp, fechaValor, fuente }` convertidos con el valor del día del documento: mindicador.cl, luego el Banco Central y al final la paridad mensual de ChileCompra.
13
+ - **Resiliencia:**
14
+ - cola serial con ritmo adaptativo ante el 10500;
15
+ - reintentos con presupuesto;
16
+ - circuit breaker por fuente (`mp.salud()`);
17
+ - cancelación con `AbortSignal`;
18
+ - eventos de observabilidad (`alEvento`);
19
+ - cuota diaria en `mp.cuota`.
20
+ - **`mp.sync.planificar` y `mp.sync.ejecutar`:**
21
+ - sincronización incremental con costo estimado antes de empezar;
22
+ - cursores que nunca adelantan a los datos y pendientes con tope;
23
+ - stores en memoria y en archivo, con lock y escritura atómica.
24
+ - **Avisos cuando la API cambia de forma** (campo nuevo o faltante, tipo distinto, valor desconocido).
25
+ - **Lector OCDS en streaming** (`leerOcds`) para el histórico de datos abiertos.
26
+ - **Tablas de códigos** con las contradicciones de la documentación oficial corregidas.
27
+ - **Datos personales de funcionarios** marcados en `personales`. `ocultarPersonales` los tapa, junto con correos y teléfonos en texto libre.
28
+ - **CLI `mercadopublico`** con salida JSON, CSV y tabla, `sync`, `radar` y `ocds`. Oculta datos personales por defecto.
29
+ - **`mercadopublico/radar`:** `decidir`, `priorizar`, `precioSugerido`, `rangoDePrecio`, `proximasCompras`, `competidores` y `calibrar`, con fórmulas explícitas.
package/LICENSE CHANGED
@@ -1,21 +1,133 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 keroke.ro
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree
8
+ to them as both strict obligations and conditions to all
9
+ your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the
14
+ software to do everything you might do with the software
15
+ that would otherwise infringe the licensor's copyright
16
+ in it for any permitted purpose. However, you may
17
+ only distribute the software according to [Distribution
18
+ License](#distribution-license) and make changes or new works
19
+ based on the software according to [Changes and New Works
20
+ License](#changes-and-new-works-license).
21
+
22
+ ## Distribution License
23
+
24
+ The licensor grants you an additional copyright license
25
+ to distribute copies of the software. Your license
26
+ to distribute covers distributing the software with
27
+ changes and new works permitted by [Changes and New Works
28
+ License](#changes-and-new-works-license).
29
+
30
+ ## Notices
31
+
32
+ You must ensure that anyone who gets a copy of any part of
33
+ the software from you also gets a copy of these terms or the
34
+ URL for them above, as well as copies of any plain-text lines
35
+ beginning with `Required Notice:` that the licensor provided
36
+ with the software. For example:
37
+
38
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
39
+
40
+ ## Changes and New Works License
41
+
42
+ The licensor grants you an additional copyright license to
43
+ make changes and new works based on the software for any
44
+ permitted purpose.
45
+
46
+ ## Patent License
47
+
48
+ The licensor grants you a patent license for the software that
49
+ covers patent claims the licensor can license, or becomes able
50
+ to license, that you would infringe by using the software.
51
+
52
+ ## Noncommercial Purposes
53
+
54
+ Any noncommercial purpose is a permitted purpose.
55
+
56
+ ## Personal Uses
57
+
58
+ Personal use for research, experiment, and testing for
59
+ the benefit of public knowledge, personal study, private
60
+ entertainment, hobby projects, amateur pursuits, or religious
61
+ observance, without any anticipated commercial application,
62
+ is use for a permitted purpose.
63
+
64
+ ## Noncommercial Organizations
65
+
66
+ Use by any charitable organization, educational institution,
67
+ public research organization, public safety or health
68
+ organization, environmental protection organization,
69
+ or government institution is use for a permitted purpose
70
+ regardless of the source of funding or obligations resulting
71
+ from the funding.
72
+
73
+ ## Fair Use
74
+
75
+ You may have "fair use" rights for the software under the
76
+ law. These terms do not limit them.
77
+
78
+ ## No Other Rights
79
+
80
+ These terms do not allow you to sublicense or transfer any of
81
+ your licenses to anyone else, or prevent the licensor from
82
+ granting licenses to anyone else. These terms do not imply
83
+ any other licenses.
84
+
85
+ ## Patent Defense
86
+
87
+ If you make any written claim that the software infringes or
88
+ contributes to infringement of any patent, your patent license
89
+ for the software granted under these terms ends immediately. If
90
+ your company makes such a claim, your patent license ends
91
+ immediately for work on behalf of your company.
92
+
93
+ ## Violations
94
+
95
+ The first time you are notified in writing that you have
96
+ violated any of these terms, or done anything with the software
97
+ not covered by your licenses, your licenses can nonetheless
98
+ continue if you come into full compliance with these terms,
99
+ and take practical steps to correct past violations, within
100
+ 32 days of receiving notice. Otherwise, all your licenses
101
+ end immediately.
102
+
103
+ ## No Liability
104
+
105
+ ***As far as the law allows, the software comes as is, without
106
+ any warranty or condition, and the licensor will not be liable
107
+ to you for any damages arising out of these terms or the use
108
+ or nature of the software, under any kind of legal claim.***
109
+
110
+ ## Definitions
111
+
112
+ The **licensor** is the individual or entity offering these
113
+ terms, and the **software** is the software the licensor makes
114
+ available under these terms.
115
+
116
+ **You** refers to the individual or entity agreeing to these
117
+ terms.
118
+
119
+ **Your company** is any legal entity, sole proprietorship,
120
+ or other kind of organization that you work for, plus all
121
+ organizations that have control over, are under the control of,
122
+ or are under common control with that organization. **Control**
123
+ means ownership of substantially all the assets of an entity,
124
+ or the power to direct its management and policies by vote,
125
+ contract, or otherwise. Control can be direct or indirect.
126
+
127
+ **Your licenses** are all the licenses granted to you for the
128
+ software under these terms.
129
+
130
+ **Use** means anything you do with the software requiring one
131
+ of your licenses.
132
+
133
+ Required Notice: Copyright KeroKero (https://kerokero.cl)
package/README.md CHANGED
@@ -1,20 +1,167 @@
1
- # mercadopublico
1
+ ![mercadopublico: la API de Mercado Público, bien usada. SDK en TypeScript por KeroKero.](https://unpkg.com/mercadopublico/docs/assets/readme/portada.png)
2
2
 
3
- SDK en TypeScript para la [API de Mercado Público](https://www.chilecompra.cl/api/) de ChileCompra.
3
+ [![ci](https://img.shields.io/github/actions/workflow/status/keroke-ro/mercadopublico/ci.yml?branch=main&label=ci&style=flat-square&labelColor=171A17)](https://github.com/keroke-ro/mercadopublico/actions/workflows/ci.yml)
4
+ [![contrato](https://img.shields.io/github/actions/workflow/status/keroke-ro/mercadopublico/contrato.yml?branch=main&label=contrato&style=flat-square&labelColor=171A17)](https://github.com/keroke-ro/mercadopublico/actions/workflows/contrato.yml)
5
+ [![npm](https://img.shields.io/npm/v/mercadopublico?color=2F6A3B&style=flat-square&labelColor=171A17)](https://www.npmjs.com/package/mercadopublico)
6
+ [![dependencias](https://img.shields.io/badge/dependencias_en_runtime-0-2F6A3B?style=flat-square&labelColor=171A17)](package.json)
7
+ [![licencia](https://img.shields.io/badge/licencia-PolyForm_Noncommercial-2F6A3B?style=flat-square&labelColor=171A17)](LICENSE)
4
8
 
5
- > En construcción (v0.1). No oficial: no está afiliado a la Dirección ChileCompra.
9
+ SDK en TypeScript para la [API de Mercado Público](https://www.chilecompra.cl/api/) de ChileCompra. Junta la API v1 (licitaciones, órdenes de compra y empresas), la v2 de Compra Ágil y las descargas OCDS en un solo modelo, `Proceso`, para que tu código no dependa de cuál de las tres trajo el dato. Se hace cargo de lo que cuesta usar bien la API: manda las consultas de a una para no gatillar el error 10500, reintenta solo cuando sirve, lleva la cuenta de las 10.000 consultas diarias del ticket, convierte los montos a pesos con el valor del día del documento y oculta los datos personales de funcionarios en la CLI. No tiene dependencias en runtime.
6
10
 
7
- ## Objetivo
11
+ ![Las tres APIs (v1, Compra Ágil y OCDS) se traducen a un solo tipo, Proceso: con tipo y origen, montos en pesos al valor del día, fechas ISO con offset de Santiago, datos de funcionarios aparte y lo desconocido en raw.](https://unpkg.com/mercadopublico/docs/assets/readme/modelo.png)
8
12
 
9
- - Cliente tipado para licitaciones, órdenes de compra, empresas y Compra Ágil, con los códigos como enums y errores tipados.
10
- - Control de cuota (10.000 consultas diarias por ticket), cola serial y reintentos solo cuando corresponde (10500, 429, 5xx).
11
- - Normalización de montos (CLP, UF, UTM, USD) y fechas en America/Santiago.
12
- - Lector del histórico OCDS de datos abiertos de ChileCompra.
13
+ > No oficial: no está afiliado a la Dirección ChileCompra.
14
+
15
+ ## Instalación
16
+
17
+ ```sh
18
+ npm install mercadopublico
19
+ ```
20
+
21
+ Requiere Node 20 o superior (usa `fetch` nativo). Se publica como ESM y CJS. Necesitas un ticket de la API: se pide con Clave Única en [chilecompra.cl/api](https://www.chilecompra.cl/api/) y llega por correo.
22
+
23
+ ## En 30 segundos
24
+
25
+ ```ts
26
+ import { MercadoPublico } from "mercadopublico";
27
+
28
+ const mp = new MercadoPublico({ ticket: process.env.MP_TICKET! });
29
+
30
+ const activas = await mp.licitaciones.activas(); // 1 consulta
31
+ console.log(activas.length, "licitaciones publicadas hoy");
32
+
33
+ const lic = await mp.licitaciones.porCodigo("1509-5-L114"); // 1 consulta; null si no existe
34
+ if (lic) {
35
+ console.log(lic.estado, lic.fechas.cierre); // "publicada" "2026-10-20T15:00:00-03:00"
36
+ console.log(lic.monto); // { valor: 1200, moneda: "CLF", clp: 47_000_000, fechaValor: "2026-10-01", fuente: "mindicador.cl" }
37
+ }
38
+
39
+ // Compra Ágil: pagina sola, 1 consulta por página de 50
40
+ for await (const ca of mp.compraAgil.listar({ region: [13], estado: ["publicada"], q: "papel" })) {
41
+ console.log(ca.codigo, ca.nombre, ca.monto?.clp);
42
+ }
43
+
44
+ console.log(mp.cuota.toJSON()); // { dia, usadas, limite: 10000, restante, reinicia }
45
+ ```
46
+
47
+ ## Cuota
48
+
49
+ El ticket da 10.000 consultas por día calendario. Si mandas dos a la vez, la API responde 10500; si se te acaban, 429. El SDK:
50
+
51
+ - **Pone todas las consultas en una cola serial.** Nunca hay dos en vuelo, aunque llames en paralelo.
52
+ - **Reintenta solo lo que tiene arreglo:** 10500, 5xx y cortes de red, con backoff exponencial y jitter (4 reintentos, base 1 s). Nunca reintenta un ticket inválido, un 404, parámetros malos ni un 429.
53
+ - **Lleva la cuenta en `mp.cuota`** (`usadas`, `restante`, `limite`, `reinicia`). Cada intento cuenta, también los reintentos. La API no informa cuánto queda, así que es la cuenta de este proceso: si quieres que sobreviva entre ejecuciones, guarda `mp.cuota.toJSON()` y pásalo como `cuota` al crear el cliente. La CLI ya lo hace.
54
+ - **Falla antes de gastar** con `cuota_insuficiente` si una operación no cabe en lo que queda del día.
55
+ - **Planifica los sync.** `mp.sync.planificar()` dice cuántas consultas va a costar y si alcanzan hoy, antes de pedir un solo detalle:
56
+
57
+ ```ts
58
+ const plan = await mp.sync.planificar({ desde: "2026-10-01", store });
59
+ plan.consultas; // 1240
60
+ plan.cabeHoy; // true
61
+ await mp.sync.ejecutar(plan);
62
+ ```
63
+
64
+ Detalle en [docs/sync.md](docs/sync.md).
65
+
66
+ ## Resiliencia
67
+
68
+ La API falla seguido: rechaza con 10500 incluso consultas seriales (3 de cada 11 con 1 s de pausa) y a veces se cae entera. El SDK alarga la pausa sola ante cada 10500, pone un tope a los reintentos, deja de consultar una fuente caída por 60 s (circuit breaker) y acepta un `signal` en cada método. Un sync cortado en cualquier punto se reanuda sin huecos.
69
+
70
+ ```ts
71
+ const mp = new MercadoPublico({
72
+ ticket: process.env.MP_TICKET!,
73
+ alEvento: (e) => console.error(JSON.stringify(e)), // consultas, reintentos, circuito, cuota, avisos de cambios en la API
74
+ });
75
+ mp.salud(); // { v1: { estado: "cerrado", fallas: 0, intervaloMs: 1000 }, v2: { … }, … }
76
+ ```
77
+
78
+ ![Cada falla y su respuesta: 10500, ritmo adaptativo; 5xx, timeout o red, reintentos con backoff y jitter; 5 caídas seguidas, circuito abierto 60 s; sync cortado, reanuda sin huecos; la API cambia, parsea igual y avisa; conversor caído, pasa al siguiente.](https://unpkg.com/mercadopublico/docs/assets/readme/resiliencia.png)
79
+
80
+ Qué pasa en cada falla, en [docs/resiliencia.md](docs/resiliencia.md).
81
+
82
+ ## Datos personales
83
+
84
+ La v1 trae nombre, RUT, correo y teléfono de funcionarios (`RutUsuario`, `NombreUsuario`, `EmailResponsablePago`, `FonoResponsableContrato`, etc.). El SDK los separa en `proceso.personales`, y:
85
+
86
+ - **La CLI los oculta por defecto.** Para verlos: `--incluir-personales`.
87
+ - **En código los recibes tal como vienen.** Antes de exportar o publicar, pasa cada proceso por `ocultarPersonales`, que deja `personales` en `null` y tapa esos campos dentro de `raw`:
88
+
89
+ ```ts
90
+ import { ocultarPersonales } from "mercadopublico";
91
+
92
+ const publicable = procesos.map(ocultarPersonales);
93
+ ```
94
+
95
+ No se tapan el RUT ni la razón social de los proveedores: son datos de empresas, salvo cuando el proveedor es persona natural, y la API no lo indica. La Ley 21.719 de protección de datos personales empieza a regir en diciembre de 2026; si vas a publicar o cruzar estos datos, confirma el alcance con un abogado.
96
+
97
+ ## Radar
98
+
99
+ `mercadopublico/radar` responde lo que se pregunta cada mañana un equipo que le vende al Estado: qué postular primero, a qué precio, contra quién compite y qué viene. Son fórmulas a la vista, sin machine learning: cada número trae su porqué en texto, y tu criterio (pesos, rubros, costos, vetos) entra en un `Perfil`.
100
+
101
+ ![El puntaje es una suma ponderada de seis factores. decidir lo pasa por reglas duras, umbrales (0,7 postular, 0,4 revisar), capacidad y orden por cierre.](https://unpkg.com/mercadopublico/docs/assets/readme/radar.png)
102
+
103
+ ```sh
104
+ npx mercadopublico radar --perfil perfil.json --decisiones decisiones.json
105
+ ```
106
+
107
+ Detalle en [docs/radar.md](docs/radar.md).
108
+
109
+ ## CLI
110
+
111
+ ```sh
112
+ export MP_TICKET=...
113
+ npx mercadopublico licitacion 1509-5-L114
114
+ npx mercadopublico licitaciones --dia 2026-10-01 --estado adjudicada --formato csv > adjudicadas.csv
115
+ npx mercadopublico compra-agil --region 13 --estado publicada --q papel --max 50
116
+ npx mercadopublico ocds 2026 --max 10 # no pide ticket
117
+ npx mercadopublico sync --desde 2026-10-01 --plan
118
+ npx mercadopublico cuota
119
+ ```
120
+
121
+ Salida en tabla si es una terminal y JSON si no; `--formato json|csv|tabla` para elegir. Referencia completa en [docs/cli.md](docs/cli.md).
122
+
123
+ ## Documentación
124
+
125
+ - [Guía](docs/guia.md): cliente, licitaciones, órdenes de compra, empresas, Compra Ágil y errores.
126
+ - [Modelo](docs/modelo.md): `Proceso`, `Monto`, fechas y conversores.
127
+ - [Sync](docs/sync.md): plan, costo en consultas, cursores y stores.
128
+ - [CLI](docs/cli.md)
129
+ - [OCDS](docs/ocds.md): el histórico de datos abiertos, en streaming.
130
+ - [Radar](docs/radar.md): `mercadopublico/radar` y `mercadopublico radar`: qué postular primero, a qué precio, contra quién y qué viene.
131
+ - [Resiliencia](docs/resiliencia.md): qué hace el SDK cuando la API falla, eventos y `mp.salud()`.
132
+ - [Testing](docs/testing.md): qué se prueba, dónde corre y cuántas consultas reales gasta.
133
+ - [Contrato nocturno](docs/contrato.md)
134
+ - [Notas de la API](docs/api-notes.md): lo que dice cada fuente oficial y dónde se contradicen.
135
+
136
+ ## Calidad
137
+
138
+ Cada PR corre, en Node 20, 22 y 24, typecheck, tests unitarios, propiedades con fast-check, un corpus de 59 respuestas reales anonimizadas con snapshots y pruebas de caos contra un servidor local; además, cobertura con umbrales y el paquete tal como se publica, importado desde Node, Bun, Deno y un entorno tipo edge. Cada semana corren la prueba de mutación y un smoke contra la API real; cada noche, el [contrato](docs/contrato.md). Detalle en [docs/testing.md](docs/testing.md).
139
+
140
+ ## Estado
141
+
142
+ Versión 0.x: la API pública todavía puede cambiar entre versiones menores (ver [CONTRIBUTING.md](CONTRIBUTING.md#versiones)).
143
+
144
+ Probado con un ticket real desde el 2026-10-13: la v1 tiene fixtures grabados de la API (`test/fixtures/v1/*.real.json`) y un corpus de respuestas reales anonimizadas (ver [docs/testing.md](docs/testing.md#corpus-real-y-snapshots)). Los fixtures de la v2 y de OCDS siguen reconstruidos desde la documentación oficial y llevan la marca `"_fixture": "reconstruido…"`. Estas dudas siguen abiertas (detalle en [docs/api-notes.md](docs/api-notes.md)):
145
+
146
+ - En qué zona horaria se reinicia la cuota (el SDK supone medianoche de Santiago) y si v1 y v2 comparten contador.
147
+ - Si el servidor acepta la grafía correcta de dos filtros de estado de OC que la documentación escribe con errata (`recepcionaceptadacialmente`, `recepecionconformeincompleta`). El SDK manda la de la documentación.
148
+ - Detalles de forma: si `Publico` en las rutas de Empresas distingue mayúsculas, si el filtro `region` de la v2 va separado por comas o repetido, y con qué status HTTP llega el error 10300.
149
+ - Si las fechas OCDS con sufijo `Z` son UTC reales o la hora de Santiago etiquetada como UTC. El SDK las lee como hora de Santiago.
150
+ - En Compra Ágil, `codigo_orden_compra` viene en `null` aunque la OC exista (el SDK usa `id_orden_compra`), y `seleccion.proveedor_seleccionado` está documentado pero no confirmado.
151
+ - mindicador.cl no respondía al investigar; si falla, el conversor usa los valores diarios del Banco Central (sus páginas "Ver serie", no una API) y, si eso también falla, el promedio mensual oficial de ChileCompra.
13
152
 
14
153
  ## Fuente de los datos
15
154
 
16
155
  Los datos vienen de la Dirección ChileCompra (api.mercadopublico.cl). Si publicas datos obtenidos con esta librería, cita a ChileCompra como fuente.
17
156
 
157
+ Esta librería no está afiliada a la Dirección ChileCompra ni cuenta con su respaldo.
158
+
18
159
  ## Licencia
19
160
 
20
- MIT © KeroKero
161
+ [PolyForm Noncommercial 1.0.0](LICENSE): libre para uso no comercial (personal, investigación, educación, organizaciones sin fines de lucro e instituciones públicas). Para uso comercial, escríbenos a [KeroKero](https://kerokero.cl).
162
+
163
+ Required Notice: Copyright KeroKero (https://kerokero.cl)
164
+
165
+ ---
166
+
167
+ <img src="https://unpkg.com/mercadopublico/docs/assets/readme/kerokero.svg" width="18" height="18" alt=""> Hecho por [KeroKero](https://kerokero.cl): sistemas de decisión para tu operación.
@@ -0,0 +1,75 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/fecha.ts
8
+ var ZONA = "America/Santiago";
9
+ var partes = new Intl.DateTimeFormat("en-CA", {
10
+ timeZone: ZONA,
11
+ year: "numeric",
12
+ month: "2-digit",
13
+ day: "2-digit",
14
+ hour: "2-digit",
15
+ minute: "2-digit",
16
+ second: "2-digit",
17
+ hourCycle: "h23",
18
+ timeZoneName: "longOffset"
19
+ });
20
+ function piezas(d) {
21
+ const p = {};
22
+ for (const { type, value } of partes.formatToParts(d)) p[type] = value;
23
+ const gmt = p.timeZoneName === "GMT" ? "+00:00" : p.timeZoneName.slice(3);
24
+ const ms = (gmt[0] === "-" ? -1 : 1) * (Number(gmt.slice(1, 3)) * 3600 + Number(gmt.slice(4, 6)) * 60 + Number(gmt.slice(7, 9) || 0)) * 1e3;
25
+ return { dia: `${p.year}-${p.month}-${p.day}`, hora: `${p.hour}:${p.minute}:${p.second}`, off: gmt.slice(0, 6), ms };
26
+ }
27
+ function isoSantiago(d) {
28
+ const { dia, hora, off } = piezas(d);
29
+ return `${dia}T${hora}${off}`;
30
+ }
31
+ function diaSantiago(d = /* @__PURE__ */ new Date()) {
32
+ return piezas(d).dia;
33
+ }
34
+ var FORMA = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
35
+ function desdeHoraSantiago(texto) {
36
+ if (!FORMA.test(texto)) throw new RangeError(`Fecha inv\xE1lida: ${texto.slice(0, 40)}`);
37
+ if (/(Z|[+-]\d{2}:?\d{2})$/.test(texto)) return new Date(texto);
38
+ const comoUtc = /* @__PURE__ */ new Date(`${texto.length === 10 ? `${texto}T00:00:00` : texto}Z`);
39
+ if (Number.isNaN(comoUtc.getTime())) throw new RangeError(`Fecha inv\xE1lida: ${texto}`);
40
+ const pared = comoUtc.toISOString().slice(0, 19);
41
+ const candidatos = [-1, 1].map((d) => comoUtc.getTime() - piezas(new Date(comoUtc.getTime() + d * 864e5)).ms);
42
+ const t = candidatos.find((c) => {
43
+ const p = piezas(new Date(c));
44
+ return `${p.dia}T${p.hora}` === pared;
45
+ });
46
+ return new Date(t ?? candidatos[0]);
47
+ }
48
+ function normalizarFecha(texto) {
49
+ if (!texto) return null;
50
+ if (/^\d{4}/.test(texto) && Number(texto.slice(0, 4)) < 1990) return null;
51
+ return isoSantiago(desdeHoraSantiago(texto));
52
+ }
53
+ function ddmmaaaa(dia) {
54
+ const [a, m, d] = dia.split("-");
55
+ return `${d}${m}${a}`;
56
+ }
57
+ function sumarDias(dia, n) {
58
+ const d = /* @__PURE__ */ new Date(`${dia}T12:00:00Z`);
59
+ d.setUTCDate(d.getUTCDate() + n);
60
+ return d.toISOString().slice(0, 10);
61
+ }
62
+ function proximaMedianoche(ahora = /* @__PURE__ */ new Date()) {
63
+ return isoSantiago(desdeHoraSantiago(sumarDias(diaSantiago(ahora), 1)));
64
+ }
65
+
66
+ export {
67
+ __export,
68
+ isoSantiago,
69
+ diaSantiago,
70
+ desdeHoraSantiago,
71
+ normalizarFecha,
72
+ ddmmaaaa,
73
+ sumarDias,
74
+ proximaMedianoche
75
+ };