@ssddo/ecf-sdk 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/README.md +244 -0
- package/dist/index.cjs +463 -0
- package/dist/index.d.cts +9317 -0
- package/dist/index.d.ts +9317 -0
- package/dist/index.js +420 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# @ssddo/ecf-sdk
|
|
2
|
+
|
|
3
|
+
SDK de TypeScript para la API de ECF DGII (comprobantes fiscales electrónicos de República Dominicana).
|
|
4
|
+
|
|
5
|
+
Construido con [openapi-typescript](https://github.com/openapi-ts/openapi-typescript) y [openapi-fetch](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch) para acceso a la API completamente tipado.
|
|
6
|
+
|
|
7
|
+
## Instalación
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @ssddo/ecf-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Inicio rápido
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { EcfClient } from '@ssddo/ecf-sdk';
|
|
17
|
+
|
|
18
|
+
const client = new EcfClient({
|
|
19
|
+
apiKey: 'tu-token-jwt',
|
|
20
|
+
environment: 'test', // 'test' | 'cert' | 'prod'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Enviar un ECF y esperar a que termine de procesarse
|
|
24
|
+
const result = await client.sendEcf({
|
|
25
|
+
Encabezado: {
|
|
26
|
+
IdDoc: {
|
|
27
|
+
ENCF: "E310000051630",
|
|
28
|
+
TipoeCF: "FacturaDeCreditoFiscalElectronica",
|
|
29
|
+
TipoPago: "Contado",
|
|
30
|
+
TipoIngresos: "01",
|
|
31
|
+
TablaFormasPago: [
|
|
32
|
+
{ FormaPago: "Efectivo", MontoPago: 1015.25 },
|
|
33
|
+
],
|
|
34
|
+
IndicadorMontoGravado: "ConITBISIncluido",
|
|
35
|
+
FechaVencimientoSecuencia: "2028-12-31T00:00:00",
|
|
36
|
+
},
|
|
37
|
+
Emisor: {
|
|
38
|
+
RNCEmisor: "131460941",
|
|
39
|
+
FechaEmision: "2026-01-10",
|
|
40
|
+
DireccionEmisor: "AVE. ISABEL AGUIAR NO. 269, ZONA INDUSTRIAL DE HERRERA",
|
|
41
|
+
RazonSocialEmisor: "DOCUMENTOS ELECTRONICOS DE 02",
|
|
42
|
+
},
|
|
43
|
+
Totales: {
|
|
44
|
+
ITBIS1: 18,
|
|
45
|
+
MontoGravadoI1: 762.71,
|
|
46
|
+
MontoGravadoTotal: 762.71,
|
|
47
|
+
TotalITBIS1: 137.29,
|
|
48
|
+
TotalITBIS: 137.29,
|
|
49
|
+
MontoNoFacturable: 100.0,
|
|
50
|
+
ImpuestosAdicionales: [
|
|
51
|
+
{
|
|
52
|
+
TipoImpuesto: "002",
|
|
53
|
+
TasaImpuestoAdicional: 2,
|
|
54
|
+
OtrosImpuestosAdicionales: 15.25,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
MontoImpuestoAdicional: 15.25,
|
|
58
|
+
MontoTotal: 1015.25,
|
|
59
|
+
MontoPeriodo: 1015.25,
|
|
60
|
+
},
|
|
61
|
+
Version: "Version1_0",
|
|
62
|
+
Comprador: {
|
|
63
|
+
RNCComprador: "131880681",
|
|
64
|
+
RazonSocialComprador: "DOCUMENTOS ELECTRONICOS DE 03",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
DetallesItems: [
|
|
68
|
+
{
|
|
69
|
+
MontoItem: 1016.95,
|
|
70
|
+
NombreItem: "Iphone 18 Pro max",
|
|
71
|
+
NumeroLinea: 1,
|
|
72
|
+
CantidadItem: 1,
|
|
73
|
+
UnidadMedida: "Unidad",
|
|
74
|
+
PrecioUnitarioItem: 1016.95,
|
|
75
|
+
IndicadorFacturacion: "ITBIS1_18Percent",
|
|
76
|
+
IndicadorBienoServicio: "Bien",
|
|
77
|
+
TablaImpuestoAdicional: [{ TipoImpuesto: "002" }],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
MontoItem: 100.0,
|
|
81
|
+
NombreItem: "Costo de Envío",
|
|
82
|
+
NumeroLinea: 2,
|
|
83
|
+
CantidadItem: 1,
|
|
84
|
+
UnidadMedida: "Unidad",
|
|
85
|
+
PrecioUnitarioItem: 100.0,
|
|
86
|
+
IndicadorFacturacion: "NoFacturable_18Percent",
|
|
87
|
+
IndicadorBienoServicio: "Servicio",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
DescuentosORecargos: [
|
|
91
|
+
{
|
|
92
|
+
TipoValor: "$",
|
|
93
|
+
TipoAjuste: "D",
|
|
94
|
+
NumeroLinea: 1,
|
|
95
|
+
MontoDescuentooRecargo: 84.75,
|
|
96
|
+
DescripcionDescuentooRecargo: "Descuento",
|
|
97
|
+
IndicadorFacturacionDescuentooRecargo: "ITBIS1_18Percent",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
console.log(result.progress); // 'Finished'
|
|
103
|
+
console.log(result.encf);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Configuración de entorno
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Usando un entorno específico
|
|
110
|
+
const client = new EcfClient({
|
|
111
|
+
apiKey: 'tu-token-jwt',
|
|
112
|
+
environment: 'prod',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Usando una URL base personalizada (sobreescribe el entorno)
|
|
116
|
+
const client = new EcfClient({
|
|
117
|
+
apiKey: 'tu-token-jwt',
|
|
118
|
+
baseUrl: 'https://api-personalizada.ejemplo.com',
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### URLs de entorno
|
|
123
|
+
|
|
124
|
+
| Entorno | URL |
|
|
125
|
+
|---------|-----|
|
|
126
|
+
| `test` | `https://api.test.ecfx.ssd.com.do` |
|
|
127
|
+
| `cert` | `https://api.cert.ecfx.ssd.com.do` |
|
|
128
|
+
| `prod` | `https://api.prod.ecfx.ssd.com.do` |
|
|
129
|
+
|
|
130
|
+
## Opciones de polling
|
|
131
|
+
|
|
132
|
+
El método `sendEcf` hace polling hasta que el ECF sea procesado. Puedes personalizar el comportamiento:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const result = await client.sendEcf(ecf, {
|
|
136
|
+
initialDelay: 1000, // Iniciar polling después de 1s (por defecto)
|
|
137
|
+
maxDelay: 30000, // Delay máximo entre polls (por defecto)
|
|
138
|
+
maxRetries: 60, // Máximo de intentos de poll (por defecto)
|
|
139
|
+
backoffMultiplier: 2, // Multiplicador de backoff exponencial (por defecto)
|
|
140
|
+
timeout: 120000, // Timeout total en ms
|
|
141
|
+
signal: abortController.signal, // AbortSignal para cancelación
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Arquitectura Backend / Frontend
|
|
146
|
+
|
|
147
|
+
En la mayoría de aplicaciones, el backend maneja la lógica de negocio (validación, almacenamiento, conversión) y envía el ECF. El frontend obtiene un token de solo lectura para consultar el estado directamente:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Backend: tu endpoint de facturas
|
|
151
|
+
const ecfClient = new EcfClient({
|
|
152
|
+
apiKey: process.env.ECF_BACKEND_TOKEN,
|
|
153
|
+
environment: 'prod',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
app.post('/api/v1/invoices', async (req, res) => {
|
|
157
|
+
// 1. Validar y guardar tu factura interna
|
|
158
|
+
const invoice = await validateAndSave(req.body);
|
|
159
|
+
// 2. Convertir a formato ECF
|
|
160
|
+
const ecf = convertToEcf(invoice);
|
|
161
|
+
// 3. Enviar a ECF SSD
|
|
162
|
+
const { data } = await ecfClient.raw.POST('/ecf/31', { body: ecf });
|
|
163
|
+
await updateInvoice(invoice.id, { messageId: data.messageId });
|
|
164
|
+
res.json({ id: invoice.id, messageId: data.messageId });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Endpoint separado: generar token de solo lectura para el frontend
|
|
168
|
+
app.get('/api/v1/ecf-token', async (req, res) => {
|
|
169
|
+
const { data } = await ecfClient.createApiKey({ rnc: tenant.rnc });
|
|
170
|
+
res.json({ token: data.token });
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
El frontend almacena el token de forma segura, lo renueva ante `401 Unauthorized` o expiración, y consulta ECF SSD directamente. Consulta el [README principal](../README.md#arquitectura-backend--frontend) para el diagrama completo y ejemplo con React.
|
|
175
|
+
|
|
176
|
+
> **`sendEcf`** envuelve envío + polling en una sola llamada. Ideal para scripts o backends simples sin frontend.
|
|
177
|
+
|
|
178
|
+
## Acceso directo al cliente
|
|
179
|
+
|
|
180
|
+
Para acceso directo a todos los endpoints de la API con tipado completo:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// Operaciones de empresa
|
|
184
|
+
const { data } = await client.raw.GET('/company', {
|
|
185
|
+
params: { query: { Page: 1, Limit: 10 } },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Consultar estado de ECF
|
|
189
|
+
const { data } = await client.raw.GET('/ecf/{rnc}/{encf}', {
|
|
190
|
+
params: { path: { rnc: '123456789', encf: 'E320000000001' } },
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Operaciones DGII
|
|
194
|
+
const { data } = await client.raw.GET('/dgii/{rnc}/consultaresultado/estado', {
|
|
195
|
+
params: { path: { rnc: '123456789' }, query: { trackId: 'abc123' } },
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Métodos de conveniencia
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// Empresa
|
|
203
|
+
await client.getCompanies({ Page: 1, Limit: 10 });
|
|
204
|
+
await client.getCompanyByRnc('123456789');
|
|
205
|
+
await client.upsertCompany({ /* ... */ });
|
|
206
|
+
await client.deleteCompany('123456789');
|
|
207
|
+
|
|
208
|
+
// Certificados
|
|
209
|
+
await client.getCertificate('123456789');
|
|
210
|
+
|
|
211
|
+
// Consultas ECF
|
|
212
|
+
await client.queryEcf('123456789', 'E320000000001');
|
|
213
|
+
await client.searchEcfs('123456789');
|
|
214
|
+
await client.searchAllEcfs();
|
|
215
|
+
await client.getEcfById('123456789', 'message-uuid');
|
|
216
|
+
|
|
217
|
+
// Aprobación comercial
|
|
218
|
+
await client.aprobacionComercial('123456789', 'E320000000001', { /* ... */ });
|
|
219
|
+
|
|
220
|
+
// Anulación rangos
|
|
221
|
+
await client.anulacionRangos('123456789', { /* ... */ });
|
|
222
|
+
await client.listAnulaciones();
|
|
223
|
+
|
|
224
|
+
// Recepción
|
|
225
|
+
await client.searchEcfReceptionRequests();
|
|
226
|
+
await client.getEcfReceptionRequest('123456789', 'message-uuid');
|
|
227
|
+
|
|
228
|
+
// DGII
|
|
229
|
+
await client.consultaResultado('123456789', { trackId: 'abc' });
|
|
230
|
+
await client.consultaTimbre('123456789', { /* ... */ });
|
|
231
|
+
await client.estatusServicios('123456789');
|
|
232
|
+
|
|
233
|
+
// API Keys
|
|
234
|
+
await client.createApiKey({ rnc: '123456789' });
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Compatibilidad
|
|
238
|
+
|
|
239
|
+
Este SDK usa la API estándar `fetch` y funciona con:
|
|
240
|
+
|
|
241
|
+
- Node.js 18+
|
|
242
|
+
- Deno
|
|
243
|
+
- Bun
|
|
244
|
+
- Navegadores modernos
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
EcfClient: () => EcfClient,
|
|
34
|
+
EcfError: () => EcfError,
|
|
35
|
+
EcfFrontendClient: () => EcfFrontendClient,
|
|
36
|
+
PollingMaxRetriesError: () => PollingMaxRetriesError,
|
|
37
|
+
PollingTimeoutError: () => PollingTimeoutError,
|
|
38
|
+
createFrontendClient: () => createFrontendClient,
|
|
39
|
+
pollUntilComplete: () => pollUntilComplete
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/client.ts
|
|
44
|
+
var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
|
|
45
|
+
|
|
46
|
+
// src/polling.ts
|
|
47
|
+
var PollingTimeoutError = class extends Error {
|
|
48
|
+
constructor(message = "Polling timed out") {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = "PollingTimeoutError";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var PollingMaxRetriesError = class extends Error {
|
|
54
|
+
constructor(retries) {
|
|
55
|
+
super(`Polling exceeded maximum retries (${retries})`);
|
|
56
|
+
this.name = "PollingMaxRetriesError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
async function pollUntilComplete(fn, isComplete, options) {
|
|
60
|
+
const {
|
|
61
|
+
initialDelay = 1e3,
|
|
62
|
+
maxDelay = 3e4,
|
|
63
|
+
maxRetries = 60,
|
|
64
|
+
backoffMultiplier = 2,
|
|
65
|
+
timeout,
|
|
66
|
+
signal
|
|
67
|
+
} = options ?? {};
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
let delay = initialDelay;
|
|
70
|
+
let retries = 0;
|
|
71
|
+
while (true) {
|
|
72
|
+
if (signal?.aborted) {
|
|
73
|
+
throw new DOMException("Polling was aborted", "AbortError");
|
|
74
|
+
}
|
|
75
|
+
const result = await fn();
|
|
76
|
+
if (isComplete(result)) {
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
retries++;
|
|
80
|
+
if (retries >= maxRetries) {
|
|
81
|
+
throw new PollingMaxRetriesError(maxRetries);
|
|
82
|
+
}
|
|
83
|
+
if (timeout !== void 0 && Date.now() - startTime >= timeout) {
|
|
84
|
+
throw new PollingTimeoutError();
|
|
85
|
+
}
|
|
86
|
+
await sleep(delay, signal);
|
|
87
|
+
delay = Math.min(delay * backoffMultiplier, maxDelay);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function sleep(ms, signal) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
if (signal?.aborted) {
|
|
93
|
+
reject(new DOMException("Polling was aborted", "AbortError"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const timer = setTimeout(resolve, ms);
|
|
97
|
+
signal?.addEventListener(
|
|
98
|
+
"abort",
|
|
99
|
+
() => {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
reject(new DOMException("Polling was aborted", "AbortError"));
|
|
102
|
+
},
|
|
103
|
+
{ once: true }
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/client.ts
|
|
109
|
+
var ENVIRONMENT_URLS = {
|
|
110
|
+
test: "https://api.test.ecfx.ssd.com.do",
|
|
111
|
+
cert: "https://api.cert.ecfx.ssd.com.do",
|
|
112
|
+
prod: "https://api.prod.ecfx.ssd.com.do"
|
|
113
|
+
};
|
|
114
|
+
var ECF_TYPE_ROUTE_MAP = {
|
|
115
|
+
FacturaDeCreditoFiscalElectronica: "31",
|
|
116
|
+
FacturaDeConsumoElectronica: "32",
|
|
117
|
+
NotaDeDebitoElectronica: "33",
|
|
118
|
+
NotaDeCreditoElectronica: "34",
|
|
119
|
+
ComprasElectronico: "41",
|
|
120
|
+
GastosMenoresElectronico: "43",
|
|
121
|
+
RegimenesEspecialesElectronico: "44",
|
|
122
|
+
GubernamentalElectronico: "45",
|
|
123
|
+
ComprobanteDeExportacionesElectronico: "46",
|
|
124
|
+
ComprobanteParaPagosAlExteriorElectronico: "47"
|
|
125
|
+
};
|
|
126
|
+
var EcfError = class extends Error {
|
|
127
|
+
response;
|
|
128
|
+
constructor(message, response) {
|
|
129
|
+
super(message);
|
|
130
|
+
this.name = "EcfError";
|
|
131
|
+
this.response = response;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var EcfClient = class {
|
|
135
|
+
/** The underlying openapi-fetch client for direct endpoint access. */
|
|
136
|
+
raw;
|
|
137
|
+
constructor(config) {
|
|
138
|
+
const baseUrl = config.baseUrl ?? ENVIRONMENT_URLS[config.environment ?? "test"];
|
|
139
|
+
const authMiddleware = {
|
|
140
|
+
async onRequest({ request }) {
|
|
141
|
+
request.headers.set("Authorization", `Bearer ${config.apiKey}`);
|
|
142
|
+
return request;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
this.raw = (0, import_openapi_fetch.default)({ baseUrl });
|
|
146
|
+
this.raw.use(authMiddleware);
|
|
147
|
+
}
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// ECF send + poll
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
/**
|
|
152
|
+
* Send an ECF and poll until processing completes.
|
|
153
|
+
*
|
|
154
|
+
* Determines the correct endpoint from `ecf.encabezado.idDoc.tipoeCF`,
|
|
155
|
+
* posts the ECF, then polls until `progress` is `Finished` or `Error`.
|
|
156
|
+
*/
|
|
157
|
+
async sendEcf(ecf, pollingOptions) {
|
|
158
|
+
const tipoeCF = ecf.encabezado?.idDoc?.tipoeCF;
|
|
159
|
+
if (!tipoeCF) {
|
|
160
|
+
throw new Error("ECF must have encabezado.idDoc.tipoeCF");
|
|
161
|
+
}
|
|
162
|
+
const route = ECF_TYPE_ROUTE_MAP[tipoeCF];
|
|
163
|
+
if (!route) {
|
|
164
|
+
throw new Error(`Unknown tipoeCF: ${tipoeCF}`);
|
|
165
|
+
}
|
|
166
|
+
const rnc = ecf.encabezado?.emisor?.rncEmisor;
|
|
167
|
+
if (!rnc) {
|
|
168
|
+
throw new Error("ECF must have encabezado.emisor.rncEmisor");
|
|
169
|
+
}
|
|
170
|
+
const encf = ecf.encabezado?.idDoc?.encf;
|
|
171
|
+
if (!encf) {
|
|
172
|
+
throw new Error("ECF must have encabezado.idDoc.encf");
|
|
173
|
+
}
|
|
174
|
+
const response = await this.postEcf(route, ecf);
|
|
175
|
+
const result = await pollUntilComplete(
|
|
176
|
+
async () => {
|
|
177
|
+
const { data: queryData, error: queryError } = await this.raw.GET("/ecf/{rnc}/{encf}", {
|
|
178
|
+
params: { path: { rnc, encf } }
|
|
179
|
+
});
|
|
180
|
+
if (queryError) {
|
|
181
|
+
throw new Error(`Failed to query ECF status: ${JSON.stringify(queryError)}`);
|
|
182
|
+
}
|
|
183
|
+
const results = queryData;
|
|
184
|
+
const match = results.find((r) => r.messageId === response.messageId) ?? results[0];
|
|
185
|
+
if (!match) {
|
|
186
|
+
throw new Error("No ECF response found for the given rnc/encf");
|
|
187
|
+
}
|
|
188
|
+
return match;
|
|
189
|
+
},
|
|
190
|
+
(r) => r.progress === "Finished" || r.progress === "Error",
|
|
191
|
+
pollingOptions
|
|
192
|
+
);
|
|
193
|
+
if (result.progress === "Error") {
|
|
194
|
+
throw new EcfError(
|
|
195
|
+
result.errors ?? result.mensaje ?? "ECF processing failed",
|
|
196
|
+
result
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
async postEcf(route, body) {
|
|
202
|
+
const path = `/ecf/${route}`;
|
|
203
|
+
const { data, error } = await this.raw.POST(path, { body });
|
|
204
|
+
if (error) {
|
|
205
|
+
throw new Error(`Failed to send ECF: ${JSON.stringify(error)}`);
|
|
206
|
+
}
|
|
207
|
+
return data;
|
|
208
|
+
}
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Company operations
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
/** List companies with optional filters. */
|
|
213
|
+
async getCompanies(params) {
|
|
214
|
+
return this.raw.GET("/company", { params: { query: params } });
|
|
215
|
+
}
|
|
216
|
+
/** Get a company by RNC. */
|
|
217
|
+
async getCompanyByRnc(rnc) {
|
|
218
|
+
return this.raw.GET("/company/{rnc}", { params: { path: { rnc } } });
|
|
219
|
+
}
|
|
220
|
+
/** Create or update a company. */
|
|
221
|
+
async upsertCompany(body) {
|
|
222
|
+
return this.raw.PUT("/company", { body });
|
|
223
|
+
}
|
|
224
|
+
/** Delete a company by RNC. */
|
|
225
|
+
async deleteCompany(rnc) {
|
|
226
|
+
return this.raw.DELETE("/company/{rnc}", { params: { path: { rnc } } });
|
|
227
|
+
}
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Certificate operations
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
/** Get the current certificate for a company. */
|
|
232
|
+
async getCertificate(rnc) {
|
|
233
|
+
return this.raw.GET("/company/{rnc}/certificate", { params: { path: { rnc } } });
|
|
234
|
+
}
|
|
235
|
+
/** Update a company's certificate. Pass a FormData with 'certificate' file and 'password' field. */
|
|
236
|
+
async updateCertificate(rnc, body) {
|
|
237
|
+
const formData = new FormData();
|
|
238
|
+
formData.append("certificate", body.certificate);
|
|
239
|
+
formData.append("password", body.password);
|
|
240
|
+
return this.raw.PUT("/company/{rnc}/certificate", {
|
|
241
|
+
params: { path: { rnc } },
|
|
242
|
+
body: formData,
|
|
243
|
+
bodySerializer: (b) => b
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// ECF query operations
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
/** Query ECFs by RNC and eNCF. */
|
|
250
|
+
async queryEcf(rnc, encf) {
|
|
251
|
+
return this.raw.GET("/ecf/{rnc}/{encf}", { params: { path: { rnc, encf } } });
|
|
252
|
+
}
|
|
253
|
+
/** Search ECFs for a specific RNC. */
|
|
254
|
+
async searchEcfs(rnc, params) {
|
|
255
|
+
return this.raw.GET("/ecf/{rnc}", {
|
|
256
|
+
params: { path: { rnc }, query: params }
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/** Search all ECFs across all companies. */
|
|
260
|
+
async searchAllEcfs(params) {
|
|
261
|
+
return this.raw.GET("/ecf", { params: { query: params } });
|
|
262
|
+
}
|
|
263
|
+
/** Get a specific ECF by message ID. */
|
|
264
|
+
async getEcfById(rnc, id) {
|
|
265
|
+
return this.raw.GET("/ecf/{rnc}/message/{id}", { params: { path: { rnc, id } } });
|
|
266
|
+
}
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Aprobacion comercial
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
/** Send aprobacion comercial for an ECF. */
|
|
271
|
+
async aprobacionComercial(rnc, encf, body) {
|
|
272
|
+
return this.raw.POST("/ecf/aprobacioncomercial/{rnc}/{encf}", {
|
|
273
|
+
params: { path: { rnc, encf } },
|
|
274
|
+
body
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Anulacion rangos
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
/** Request range annulment. */
|
|
281
|
+
async anulacionRangos(rnc, body) {
|
|
282
|
+
return this.raw.POST("/ecf/anularrango/{rnc}", {
|
|
283
|
+
params: { path: { rnc } },
|
|
284
|
+
body
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/** List annulments. */
|
|
288
|
+
async listAnulaciones(params) {
|
|
289
|
+
return this.raw.GET("/ecf/anulaciones", { params: { query: params } });
|
|
290
|
+
}
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// Firmar semilla
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
/** Sign a seed for a company. */
|
|
295
|
+
async firmarSemilla(rnc, body) {
|
|
296
|
+
const formData = new FormData();
|
|
297
|
+
formData.append("xml", body.xml);
|
|
298
|
+
return this.raw.POST("/ecf/FirmarSemilla/{rnc}", {
|
|
299
|
+
params: { path: { rnc } },
|
|
300
|
+
body: formData,
|
|
301
|
+
bodySerializer: (b) => b
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
// Recepcion operations
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
/** Search ECF reception requests. */
|
|
308
|
+
async searchEcfReceptionRequests(params) {
|
|
309
|
+
return this.raw.GET("/recepcion/ecf", { params: { query: params } });
|
|
310
|
+
}
|
|
311
|
+
/** Search ACECF reception requests. */
|
|
312
|
+
async searchAcecfReceptionRequests(params) {
|
|
313
|
+
return this.raw.GET("/recepcion/acecf", { params: { query: params } });
|
|
314
|
+
}
|
|
315
|
+
/** Search ECF reception requests by RNC. */
|
|
316
|
+
async searchEcfReceptionRequestsByRnc(rnc, params) {
|
|
317
|
+
return this.raw.GET("/recepcion/{rnc}/ecf", {
|
|
318
|
+
params: { path: { rnc }, query: params }
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/** Get a specific ECF reception request. */
|
|
322
|
+
async getEcfReceptionRequest(rnc, messageId) {
|
|
323
|
+
return this.raw.GET("/recepcion/{rnc}/ecf/{messageId}", {
|
|
324
|
+
params: { path: { rnc, messageId } }
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/** Search ACECF reception requests by RNC. */
|
|
328
|
+
async searchAcecfReceptionRequestsByRnc(rnc, params) {
|
|
329
|
+
return this.raw.GET("/recepcion/{rnc}/acecf", {
|
|
330
|
+
params: { path: { rnc }, query: params }
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/** Get a specific ACECF reception request. */
|
|
334
|
+
async getAcecfReceptionRequest(rnc, messageId) {
|
|
335
|
+
return this.raw.GET("/recepcion/{rnc}/acecf/{messageId}", {
|
|
336
|
+
params: { path: { rnc, messageId } }
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
// DGII operations
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
/** Consulta directorio - listado. */
|
|
343
|
+
async consultaDirectorioListado(rnc) {
|
|
344
|
+
return this.raw.GET("/dgii/{rnc}/consultadirectorio/listado", {
|
|
345
|
+
params: { path: { rnc } }
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/** Consulta directorio - obtener directorio por RNC. */
|
|
349
|
+
async consultaDirectorioPorRnc(rnc, query) {
|
|
350
|
+
return this.raw.GET("/dgii/{rnc}/consultadirectorio/obtener-directorio-por-rnc", {
|
|
351
|
+
params: { path: { rnc }, query }
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/** Consulta estado. */
|
|
355
|
+
async consultaEstado(rnc, query) {
|
|
356
|
+
return this.raw.GET("/dgii/{rnc}/consultaestado/estado", {
|
|
357
|
+
params: { path: { rnc }, query }
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/** Consulta resultado. */
|
|
361
|
+
async consultaResultado(rnc, query) {
|
|
362
|
+
return this.raw.GET("/dgii/{rnc}/consultaresultado/estado", {
|
|
363
|
+
params: { path: { rnc }, query }
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/** Consulta RFCE. */
|
|
367
|
+
async consultaRFCE(rnc, query) {
|
|
368
|
+
return this.raw.GET("/dgii/{rnc}/consultarfce/consulta", {
|
|
369
|
+
params: { path: { rnc }, query }
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
/** Consulta timbre. */
|
|
373
|
+
async consultaTimbre(rnc, query) {
|
|
374
|
+
return this.raw.GET("/dgii/{rnc}/consultatimbre", {
|
|
375
|
+
params: { path: { rnc }, query }
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/** Consulta timbre FC. */
|
|
379
|
+
async consultaTimbreFC(rnc, query) {
|
|
380
|
+
return this.raw.GET("/dgii/{rnc}/consultatimbrefc", {
|
|
381
|
+
params: { path: { rnc }, query }
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/** Consulta track IDs. */
|
|
385
|
+
async consultaTrackId(rnc, query) {
|
|
386
|
+
return this.raw.GET("/dgii/{rnc}/consultatrackids/consulta", {
|
|
387
|
+
params: { path: { rnc }, query }
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/** Estatus servicios - obtener estatus. */
|
|
391
|
+
async estatusServicios(rnc) {
|
|
392
|
+
return this.raw.GET("/dgii/{rnc}/estatusservicios/obtener-estatus", {
|
|
393
|
+
params: { path: { rnc } }
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
/** Estatus servicios - obtener ventanas de mantenimiento. */
|
|
397
|
+
async ventanasMantenimiento(rnc) {
|
|
398
|
+
return this.raw.GET("/dgii/{rnc}/estatusservicios/obtener-ventanas-mantenimiento", {
|
|
399
|
+
params: { path: { rnc } }
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// ApiKey operations
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
/** Create a new API key. */
|
|
406
|
+
async createApiKey(body) {
|
|
407
|
+
return this.raw.POST("/apiKey", { body });
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
var EcfFrontendClient = class {
|
|
411
|
+
/** The underlying openapi-fetch client for direct endpoint access. */
|
|
412
|
+
raw;
|
|
413
|
+
constructor(config) {
|
|
414
|
+
const baseUrl = config.baseUrl ?? ENVIRONMENT_URLS[config.environment ?? "test"];
|
|
415
|
+
const authMiddleware = {
|
|
416
|
+
async onRequest({ request }) {
|
|
417
|
+
request.headers.set("Authorization", `Bearer ${config.apiKey}`);
|
|
418
|
+
return request;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
this.raw = (0, import_openapi_fetch.default)({ baseUrl });
|
|
422
|
+
this.raw.use(authMiddleware);
|
|
423
|
+
}
|
|
424
|
+
/** Query ECFs by RNC and eNCF. */
|
|
425
|
+
async queryEcf(rnc, encf) {
|
|
426
|
+
return this.raw.GET("/ecf/{rnc}/{encf}", { params: { path: { rnc, encf } } });
|
|
427
|
+
}
|
|
428
|
+
/** Search ECFs for a specific RNC. */
|
|
429
|
+
async searchEcfs(rnc, params) {
|
|
430
|
+
return this.raw.GET("/ecf/{rnc}", {
|
|
431
|
+
params: { path: { rnc }, query: params }
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
/** Search all ECFs across all companies. */
|
|
435
|
+
async searchAllEcfs(params) {
|
|
436
|
+
return this.raw.GET("/ecf", { params: { query: params } });
|
|
437
|
+
}
|
|
438
|
+
/** Get a specific ECF by message ID. */
|
|
439
|
+
async getEcfById(rnc, id) {
|
|
440
|
+
return this.raw.GET("/ecf/{rnc}/message/{id}", { params: { path: { rnc, id } } });
|
|
441
|
+
}
|
|
442
|
+
/** List companies with optional filters. */
|
|
443
|
+
async getCompanies(params) {
|
|
444
|
+
return this.raw.GET("/company", { params: { query: params } });
|
|
445
|
+
}
|
|
446
|
+
/** Get a company by RNC. */
|
|
447
|
+
async getCompanyByRnc(rnc) {
|
|
448
|
+
return this.raw.GET("/company/{rnc}", { params: { path: { rnc } } });
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
function createFrontendClient(config) {
|
|
452
|
+
return new EcfFrontendClient(config);
|
|
453
|
+
}
|
|
454
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
455
|
+
0 && (module.exports = {
|
|
456
|
+
EcfClient,
|
|
457
|
+
EcfError,
|
|
458
|
+
EcfFrontendClient,
|
|
459
|
+
PollingMaxRetriesError,
|
|
460
|
+
PollingTimeoutError,
|
|
461
|
+
createFrontendClient,
|
|
462
|
+
pollUntilComplete
|
|
463
|
+
});
|