dgii-ts 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.en.md +39 -3
  2. package/README.md +40 -3
  3. package/dist/bulk.cjs +8 -2
  4. package/dist/bulk.d.cts +48 -5
  5. package/dist/bulk.d.ts +48 -5
  6. package/dist/bulk.js +7 -1
  7. package/dist/{chunk-5FZ4F5UR.cjs → chunk-6ZRPVQPP.cjs} +20 -15
  8. package/dist/{chunk-HRRYZUKI.js → chunk-7ILLRYE4.js} +114 -11
  9. package/dist/{chunk-JBKAE66C.cjs → chunk-DLB5RJ7E.cjs} +16 -16
  10. package/dist/{chunk-BQPH3JDZ.cjs → chunk-FRM5XREF.cjs} +124 -21
  11. package/dist/chunk-H42U4GI5.js +49 -0
  12. package/dist/{chunk-DMGDJEUY.js → chunk-IRQX4WNQ.js} +8 -1
  13. package/dist/{chunk-ROJUYPIK.js → chunk-JV4HWHYP.js} +11 -6
  14. package/dist/{chunk-53A7IG4Y.js → chunk-KW4ZV6FJ.js} +1 -1
  15. package/dist/{chunk-7RJWFPCZ.cjs → chunk-NASWEFHN.cjs} +8 -1
  16. package/dist/chunk-OSGHCQFA.cjs +49 -0
  17. package/dist/{chunk-43LGK4YP.js → chunk-QTN5ZT4P.js} +2 -2
  18. package/dist/{chunk-TZC6RSWL.js → chunk-RTI3CLHV.js} +1 -48
  19. package/dist/{chunk-SUSDEKID.cjs → chunk-UW6W2JHW.cjs} +1 -48
  20. package/dist/{chunk-FZ6QDTC4.cjs → chunk-VIZWYFHP.cjs} +62 -11
  21. package/dist/{chunk-WY6V4Y7S.cjs → chunk-WMFHPNFP.cjs} +3 -3
  22. package/dist/{chunk-KIS7MVIW.js → chunk-Z72T6CZG.js} +61 -10
  23. package/dist/client.cjs +7 -6
  24. package/dist/client.d.cts +8 -3
  25. package/dist/client.d.ts +8 -3
  26. package/dist/client.js +6 -5
  27. package/dist/errors.cjs +4 -2
  28. package/dist/errors.d.cts +10 -1
  29. package/dist/errors.d.ts +10 -1
  30. package/dist/errors.js +3 -1
  31. package/dist/index-BPvMDXya.d.cts +101 -0
  32. package/dist/index-BPvMDXya.d.ts +101 -0
  33. package/dist/index.cjs +16 -10
  34. package/dist/index.d.cts +3 -3
  35. package/dist/index.d.ts +3 -3
  36. package/dist/index.js +15 -9
  37. package/dist/scraping.cjs +5 -4
  38. package/dist/scraping.d.cts +14 -3
  39. package/dist/scraping.d.ts +14 -3
  40. package/dist/scraping.js +4 -3
  41. package/dist/soap.cjs +4 -4
  42. package/dist/soap.d.cts +1 -1
  43. package/dist/soap.d.ts +1 -1
  44. package/dist/soap.js +3 -3
  45. package/dist/validators.cjs +4 -2
  46. package/dist/validators.d.cts +1 -1
  47. package/dist/validators.d.ts +1 -1
  48. package/dist/validators.js +5 -3
  49. package/package.json +2 -2
  50. package/dist/index-BE6Wfozd.d.cts +0 -63
  51. package/dist/index-BE6Wfozd.d.ts +0 -63
package/README.en.md CHANGED
@@ -135,9 +135,41 @@ const result = await client.getContribuyente('131098193');
135
135
 
136
136
  ```typescript
137
137
  const ncfResult = await client.getNCF('131098193', 'B0100000001');
138
- // { rnc: '131098193', ncf: 'B0100000001', estado: '...', ... }
138
+ // { valid: true, rnc: '131098193', ncf: 'B0100000001', nombreComercial: '...' }
139
139
  ```
140
140
 
141
+ ### Validate an e-NCF online
142
+
143
+ For an e-NCF (E-series) DGII requires the buyer's RNC. Without it DGII
144
+ answers with a required-field message and `getNCF` throws
145
+ `DgiiServiceError`. The security code is optional.
146
+
147
+ ```typescript
148
+ const ecfResult = await client.getNCF('101010632', 'E310125217173', {
149
+ rncComprador: '131262414',
150
+ codigoSeguridad: 'KrOLI0',
151
+ });
152
+ // {
153
+ // valid: true,
154
+ // rnc: '101010632',
155
+ // ncf: 'E310125217173',
156
+ // rncComprador: '131262414',
157
+ // codigoSeguridad: 'KrOLI0',
158
+ // estado: 'Aceptado',
159
+ // montoTotal: 230677.74,
160
+ // totalItbis: 35188.13,
161
+ // fechaEmision: '2026-02-11',
162
+ // fechaFirma: '2026-02-11',
163
+ // }
164
+ ```
165
+
166
+ `valid: true` means DGII found the document, not that it is accepted:
167
+ check `estado`. The fields `rncComprador`, `codigoSeguridad`, `estado`,
168
+ `montoTotal`, `totalItbis`, `fechaEmision` and `fechaFirma` are only
169
+ filled for the E-series, and `nombreComercial` is not filled for that
170
+ series. Dates are strings exactly as DGII renders them. The options are
171
+ ignored for a B-series NCF.
172
+
141
173
  ## API reference
142
174
 
143
175
  ### Validators
@@ -155,7 +187,7 @@ const ncfResult = await client.getNCF('131098193', 'B0100000001');
155
187
  | --- | --- |
156
188
  | `DgiiClient` | Scraping + SOAP fallback, circuit breaker, retry |
157
189
  | `client.getContribuyente(rnc)` | Looks up taxpayer data by RNC |
158
- | `client.getNCF(rnc, ncf)` | Validates a fiscal receipt against DGII |
190
+ | `client.getNCF(rnc, ncf, options?)` | Validates a fiscal receipt against DGII (`options` for e-NCF) |
159
191
 
160
192
  ### Scraping
161
193
 
@@ -163,7 +195,7 @@ const ncfResult = await client.getNCF('131098193', 'B0100000001');
163
195
  | --- | --- |
164
196
  | `ScrapingClient` | Queries DGII's ASP.NET pages |
165
197
  | `client.getContribuyente(rnc)` | Looks up taxpayer data by RNC |
166
- | `client.getNCF(rnc, ncf)` | Validates a fiscal receipt |
198
+ | `client.getNCF(rnc, ncf, options?)` | Validates a fiscal receipt (`options` for e-NCF) |
167
199
 
168
200
  ### SOAP client (deprecated)
169
201
 
@@ -180,6 +212,10 @@ const ncfResult = await client.getNCF('131098193', 'B0100000001');
180
212
  | `downloadBulkFile(options)` | Downloads DGII\_RNC.zip to the given directory |
181
213
  | `parseBulkFile(options)` | Parses the taxpayer TXT file |
182
214
 
215
+ `parseBulkFile` throws `BulkFormatError` if the file doesn't match the
216
+ expected 11-column layout (for example, if DGII changes the format). It
217
+ accepts an `encoding` option (defaults to `latin1`).
218
+
183
219
  ## Architecture
184
220
 
185
221
  ```text
package/README.md CHANGED
@@ -138,9 +138,42 @@ const result = await client.getContribuyente('131098193');
138
138
 
139
139
  ```typescript
140
140
  const ncfResult = await client.getNCF('131098193', 'B0100000001');
141
- // { rnc: '131098193', ncf: 'B0100000001', estado: '...', ... }
141
+ // { valid: true, rnc: '131098193', ncf: 'B0100000001', nombreComercial: '...' }
142
142
  ```
143
143
 
144
+ ### Validar e-NCF en línea
145
+
146
+ Para un e-NCF (serie E) la DGII exige el RNC del comprador. Sin él
147
+ responde con un mensaje de campo requerido y `getNCF` lanza
148
+ `DgiiServiceError`. El código de seguridad es opcional.
149
+
150
+ ```typescript
151
+ const ecfResult = await client.getNCF('101010632', 'E310125217173', {
152
+ rncComprador: '131262414',
153
+ codigoSeguridad: 'KrOLI0',
154
+ });
155
+ // {
156
+ // valid: true,
157
+ // rnc: '101010632',
158
+ // ncf: 'E310125217173',
159
+ // rncComprador: '131262414',
160
+ // codigoSeguridad: 'KrOLI0',
161
+ // estado: 'Aceptado',
162
+ // montoTotal: 230677.74,
163
+ // totalItbis: 35188.13,
164
+ // fechaEmision: '2026-02-11',
165
+ // fechaFirma: '2026-02-11',
166
+ // }
167
+ ```
168
+
169
+ `valid: true` significa que la DGII encontró el comprobante, no que
170
+ esté aceptado: revisa `estado`. Los campos `rncComprador`,
171
+ `codigoSeguridad`, `estado`, `montoTotal`, `totalItbis`,
172
+ `fechaEmision` y `fechaFirma` solo se llenan para la serie E, y
173
+ `nombreComercial` no se llena para esa serie. Las fechas son strings
174
+ tal como la DGII las renderiza. Las opciones se ignoran para un NCF de
175
+ serie B.
176
+
144
177
  ## Referencia del API
145
178
 
146
179
  ### Validadores
@@ -158,7 +191,7 @@ const ncfResult = await client.getNCF('131098193', 'B0100000001');
158
191
  | --- | --- |
159
192
  | `DgiiClient` | Cliente con scraping + SOAP fallback, circuit breaker y retry |
160
193
  | `client.getContribuyente(rnc)` | Consulta datos de un contribuyente por RNC |
161
- | `client.getNCF(rnc, ncf)` | Valida un comprobante fiscal contra la DGII |
194
+ | `client.getNCF(rnc, ncf, options?)` | Valida un comprobante fiscal contra la DGII (`options` para e-NCF) |
162
195
 
163
196
  ### Scraping
164
197
 
@@ -166,7 +199,7 @@ const ncfResult = await client.getNCF('131098193', 'B0100000001');
166
199
  | --- | --- |
167
200
  | `ScrapingClient` | Consulta páginas ASP.NET de la DGII |
168
201
  | `client.getContribuyente(rnc)` | Consulta contribuyente por RNC |
169
- | `client.getNCF(rnc, ncf)` | Valida comprobante fiscal |
202
+ | `client.getNCF(rnc, ncf, options?)` | Valida comprobante fiscal (`options` para e-NCF) |
170
203
 
171
204
  ### Cliente SOAP (deprecated)
172
205
 
@@ -183,6 +216,10 @@ const ncfResult = await client.getNCF('131098193', 'B0100000001');
183
216
  | `downloadBulkFile(options)` | Descarga DGII\_RNC.zip |
184
217
  | `parseBulkFile(options)` | Parsea el archivo TXT |
185
218
 
219
+ `parseBulkFile` lanza `BulkFormatError` si el archivo no coincide con el
220
+ layout de 11 columnas esperado (por ejemplo, si la DGII cambia el formato).
221
+ Acepta `encoding` en las opciones (`latin1` por defecto).
222
+
186
223
  ## Arquitectura
187
224
 
188
225
  ```text
package/dist/bulk.cjs CHANGED
@@ -2,10 +2,16 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkFZ6QDTC4cjs = require('./chunk-FZ6QDTC4.cjs');
5
+
6
+ var _chunkVIZWYFHPcjs = require('./chunk-VIZWYFHP.cjs');
6
7
  require('./chunk-QH4BK5X3.cjs');
7
8
 
8
9
 
10
+ var _chunkNASWEFHNcjs = require('./chunk-NASWEFHN.cjs');
11
+
12
+
13
+
14
+
9
15
 
10
16
 
11
- exports.DGII_BULK_URL = _chunkFZ6QDTC4cjs.DGII_BULK_URL; exports.downloadBulkFile = _chunkFZ6QDTC4cjs.downloadBulkFile; exports.parseBulkFile = _chunkFZ6QDTC4cjs.parseBulkFile;
17
+ exports.BulkFormatError = _chunkNASWEFHNcjs.BulkFormatError; exports.DGII_BULK_URL = _chunkVIZWYFHPcjs.DGII_BULK_URL; exports.DGII_ESTADOS = _chunkVIZWYFHPcjs.DGII_ESTADOS; exports.downloadBulkFile = _chunkVIZWYFHPcjs.downloadBulkFile; exports.parseBulkFile = _chunkVIZWYFHPcjs.parseBulkFile;
package/dist/bulk.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import { B as BulkContribuyente } from './index-BE6Wfozd.cjs';
1
+ import { B as BulkContribuyente } from './index-BPvMDXya.cjs';
2
+ export { BulkFormatError } from './errors.cjs';
2
3
 
3
4
  interface DownloadOptions {
4
5
  /** Directorio de destino para el archivo ZIP */
@@ -9,8 +10,27 @@ interface DownloadOptions {
9
10
  interface ParseOptions {
10
11
  /** Ruta al archivo TXT extraído del ZIP */
11
12
  filePath: string;
13
+ /**
14
+ * Codificación del archivo. La DGII publica el TXT en latin-1
15
+ * (ISO-8859-1), que es el valor por defecto. Permite anularla si una
16
+ * futura versión del archivo llega en otra codificación (p. ej. UTF-8).
17
+ */
18
+ encoding?: BufferEncoding;
12
19
  }
13
20
  declare const DGII_BULK_URL: string;
21
+ /**
22
+ * Valores conocidos de la columna `estado` (col 9) en el archivo masivo,
23
+ * verificados contra ~778k filas en producción. El parser usa este
24
+ * vocabulario para detectar un cambio de layout: si ninguna fila de una
25
+ * muestra trae un `estado` reconocido, el formato de la DGII cambió.
26
+ */
27
+ declare const DGII_ESTADOS: readonly ["ACTIVO", "SUSPENDIDO", "DADO DE BAJA", "CESE TEMPORAL", "ANULADO", "RECHAZADO"];
28
+ /**
29
+ * Valor de la columna `estado` del archivo masivo: uno de
30
+ * {@link DGII_ESTADOS}. Vocabulario completo de la DGII, distinto del
31
+ * `estado` binario (ACTIVO/INACTIVO) que exponen el scraping y el SOAP.
32
+ */
33
+ type DgiiEstado = (typeof DGII_ESTADOS)[number];
14
34
 
15
35
  /**
16
36
  * Descarga el archivo masivo DGII_RNC.zip desde el portal de la DGII.
@@ -20,11 +40,34 @@ declare const DGII_BULK_URL: string;
20
40
  declare function downloadBulkFile(options: DownloadOptions): Promise<string>;
21
41
 
22
42
  /**
23
- * Parsea el archivo TXT de contribuyentes extraido de DGII_RNC.zip.
43
+ * Parsea el archivo TXT de contribuyentes extraído de DGII_RNC.zip.
44
+ *
45
+ * El archivo usa delimitador pipe (|) con 11 columnas y viene codificado
46
+ * en latin-1 (ISO-8859-1); leerlo como UTF-8 corrompe los nombres con
47
+ * ñ/acentos (p. ej. MUÑOZ, PEÑA). Usa {@link ParseOptions.encoding} para
48
+ * anular la codificación si una futura versión del archivo cambia.
49
+ *
50
+ * Layout verificado contra el archivo en producción (~778k filas):
51
+ *
52
+ * col 0 rnc (100% dígitos)
53
+ * col 1 nombre (razón social)
54
+ * col 2 nombreComercial
55
+ * col 3 actividad
56
+ * col 4-7 reservadas/vacías en el archivo actual
57
+ * col 8 fechaConstitucion (dd/mm/yyyy)
58
+ * col 9 estado (ACTIVO / SUSPENDIDO / DADO DE BAJA / ...)
59
+ * col 10 regimen (NORMAL / RST / PST)
60
+ *
61
+ * `estado` se normaliza a mayúsculas para que coincida con
62
+ * {@link DGII_ESTADOS} sin importar la capitalización de la fuente.
24
63
  *
25
- * El archivo usa delimitador pipe (|) con 9 columnas.
26
- * Los registros pueden tener espacios dobles que se limpian.
64
+ * Las filas cuyo número de columnas no sea exactamente 11 se descartan por
65
+ * malformadas (formato viejo más corto, o una columna añadida/insertada).
66
+ * Si el archivo trae filas pero la mayoría de la muestra no trae un
67
+ * `estado` reconocido, se lanza {@link BulkFormatError}: un cambio de
68
+ * layout de la DGII debe fallar ruidosamente en vez de devolver `estado`
69
+ * vacío o mapeado a la columna equivocada en silencio.
27
70
  */
28
71
  declare function parseBulkFile(options: ParseOptions): Promise<BulkContribuyente[]>;
29
72
 
30
- export { BulkContribuyente, DGII_BULK_URL, type DownloadOptions, type ParseOptions, downloadBulkFile, parseBulkFile };
73
+ export { BulkContribuyente, DGII_BULK_URL, DGII_ESTADOS, type DgiiEstado, type DownloadOptions, type ParseOptions, downloadBulkFile, parseBulkFile };
package/dist/bulk.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { B as BulkContribuyente } from './index-BE6Wfozd.js';
1
+ import { B as BulkContribuyente } from './index-BPvMDXya.js';
2
+ export { BulkFormatError } from './errors.js';
2
3
 
3
4
  interface DownloadOptions {
4
5
  /** Directorio de destino para el archivo ZIP */
@@ -9,8 +10,27 @@ interface DownloadOptions {
9
10
  interface ParseOptions {
10
11
  /** Ruta al archivo TXT extraído del ZIP */
11
12
  filePath: string;
13
+ /**
14
+ * Codificación del archivo. La DGII publica el TXT en latin-1
15
+ * (ISO-8859-1), que es el valor por defecto. Permite anularla si una
16
+ * futura versión del archivo llega en otra codificación (p. ej. UTF-8).
17
+ */
18
+ encoding?: BufferEncoding;
12
19
  }
13
20
  declare const DGII_BULK_URL: string;
21
+ /**
22
+ * Valores conocidos de la columna `estado` (col 9) en el archivo masivo,
23
+ * verificados contra ~778k filas en producción. El parser usa este
24
+ * vocabulario para detectar un cambio de layout: si ninguna fila de una
25
+ * muestra trae un `estado` reconocido, el formato de la DGII cambió.
26
+ */
27
+ declare const DGII_ESTADOS: readonly ["ACTIVO", "SUSPENDIDO", "DADO DE BAJA", "CESE TEMPORAL", "ANULADO", "RECHAZADO"];
28
+ /**
29
+ * Valor de la columna `estado` del archivo masivo: uno de
30
+ * {@link DGII_ESTADOS}. Vocabulario completo de la DGII, distinto del
31
+ * `estado` binario (ACTIVO/INACTIVO) que exponen el scraping y el SOAP.
32
+ */
33
+ type DgiiEstado = (typeof DGII_ESTADOS)[number];
14
34
 
15
35
  /**
16
36
  * Descarga el archivo masivo DGII_RNC.zip desde el portal de la DGII.
@@ -20,11 +40,34 @@ declare const DGII_BULK_URL: string;
20
40
  declare function downloadBulkFile(options: DownloadOptions): Promise<string>;
21
41
 
22
42
  /**
23
- * Parsea el archivo TXT de contribuyentes extraido de DGII_RNC.zip.
43
+ * Parsea el archivo TXT de contribuyentes extraído de DGII_RNC.zip.
44
+ *
45
+ * El archivo usa delimitador pipe (|) con 11 columnas y viene codificado
46
+ * en latin-1 (ISO-8859-1); leerlo como UTF-8 corrompe los nombres con
47
+ * ñ/acentos (p. ej. MUÑOZ, PEÑA). Usa {@link ParseOptions.encoding} para
48
+ * anular la codificación si una futura versión del archivo cambia.
49
+ *
50
+ * Layout verificado contra el archivo en producción (~778k filas):
51
+ *
52
+ * col 0 rnc (100% dígitos)
53
+ * col 1 nombre (razón social)
54
+ * col 2 nombreComercial
55
+ * col 3 actividad
56
+ * col 4-7 reservadas/vacías en el archivo actual
57
+ * col 8 fechaConstitucion (dd/mm/yyyy)
58
+ * col 9 estado (ACTIVO / SUSPENDIDO / DADO DE BAJA / ...)
59
+ * col 10 regimen (NORMAL / RST / PST)
60
+ *
61
+ * `estado` se normaliza a mayúsculas para que coincida con
62
+ * {@link DGII_ESTADOS} sin importar la capitalización de la fuente.
24
63
  *
25
- * El archivo usa delimitador pipe (|) con 9 columnas.
26
- * Los registros pueden tener espacios dobles que se limpian.
64
+ * Las filas cuyo número de columnas no sea exactamente 11 se descartan por
65
+ * malformadas (formato viejo más corto, o una columna añadida/insertada).
66
+ * Si el archivo trae filas pero la mayoría de la muestra no trae un
67
+ * `estado` reconocido, se lanza {@link BulkFormatError}: un cambio de
68
+ * layout de la DGII debe fallar ruidosamente en vez de devolver `estado`
69
+ * vacío o mapeado a la columna equivocada en silencio.
27
70
  */
28
71
  declare function parseBulkFile(options: ParseOptions): Promise<BulkContribuyente[]>;
29
72
 
30
- export { BulkContribuyente, DGII_BULK_URL, type DownloadOptions, type ParseOptions, downloadBulkFile, parseBulkFile };
73
+ export { BulkContribuyente, DGII_BULK_URL, DGII_ESTADOS, type DgiiEstado, type DownloadOptions, type ParseOptions, downloadBulkFile, parseBulkFile };
package/dist/bulk.js CHANGED
@@ -1,11 +1,17 @@
1
1
  import {
2
2
  DGII_BULK_URL,
3
+ DGII_ESTADOS,
3
4
  downloadBulkFile,
4
5
  parseBulkFile
5
- } from "./chunk-KIS7MVIW.js";
6
+ } from "./chunk-Z72T6CZG.js";
6
7
  import "./chunk-D26S6EXD.js";
8
+ import {
9
+ BulkFormatError
10
+ } from "./chunk-IRQX4WNQ.js";
7
11
  export {
12
+ BulkFormatError,
8
13
  DGII_BULK_URL,
14
+ DGII_ESTADOS,
9
15
  downloadBulkFile,
10
16
  parseBulkFile
11
17
  };
@@ -1,15 +1,15 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkJBKAE66Ccjs = require('./chunk-JBKAE66C.cjs');
3
+ var _chunkDLB5RJ7Ecjs = require('./chunk-DLB5RJ7E.cjs');
4
4
 
5
5
 
6
- var _chunkBQPH3JDZcjs = require('./chunk-BQPH3JDZ.cjs');
6
+ var _chunkFRM5XREFcjs = require('./chunk-FRM5XREF.cjs');
7
7
 
8
8
 
9
9
 
10
10
 
11
11
 
12
- var _chunk7RJWFPCZcjs = require('./chunk-7RJWFPCZ.cjs');
12
+ var _chunkNASWEFHNcjs = require('./chunk-NASWEFHN.cjs');
13
13
 
14
14
  // src/client/circuit-breaker.ts
15
15
  var DEFAULT_CIRCUIT_BREAKER_OPTIONS = /* @__PURE__ */ Object.freeze({
@@ -39,7 +39,7 @@ var ConsecutiveBreaker = class {
39
39
  async execute(fn) {
40
40
  const currentState = this.state;
41
41
  if (currentState === "OPEN") {
42
- throw new (0, _chunk7RJWFPCZcjs.DgiiServiceError)(
42
+ throw new (0, _chunkNASWEFHNcjs.DgiiServiceError)(
43
43
  "Circuito abierto: estrategia deshabilitada temporalmente"
44
44
  );
45
45
  }
@@ -89,9 +89,9 @@ var DEFAULT_RETRY_OPTIONS = /* @__PURE__ */ Object.freeze({
89
89
  maxDelayMs: 1e4
90
90
  });
91
91
  function isRetryableError(error) {
92
- if (error instanceof _chunk7RJWFPCZcjs.DgiiNotFoundError) return false;
93
- if (error instanceof _chunk7RJWFPCZcjs.DgiiConnectionError) return true;
94
- if (error instanceof _chunk7RJWFPCZcjs.DgiiServiceError) {
92
+ if (error instanceof _chunkNASWEFHNcjs.DgiiNotFoundError) return false;
93
+ if (error instanceof _chunkNASWEFHNcjs.DgiiConnectionError) return true;
94
+ if (error instanceof _chunkNASWEFHNcjs.DgiiServiceError) {
95
95
  const code = error.statusCode;
96
96
  if (code !== void 0 && code >= 500) return true;
97
97
  return false;
@@ -123,10 +123,10 @@ async function withRetry(fn, options = DEFAULT_RETRY_OPTIONS) {
123
123
  var DgiiClient = class {
124
124
  constructor(options) {
125
125
  const timeout = _optionalChain([options, 'optionalAccess', _ => _.timeout]);
126
- this._scraping = new (0, _chunkBQPH3JDZcjs.ScrapingClient)(
126
+ this._scraping = new (0, _chunkFRM5XREFcjs.ScrapingClient)(
127
127
  timeout ? { timeout } : void 0
128
128
  );
129
- this._soap = new (0, _chunkJBKAE66Ccjs.DgiiSoapClient)(
129
+ this._soap = new (0, _chunkDLB5RJ7Ecjs.DgiiSoapClient)(
130
130
  timeout ? { timeout } : void 0
131
131
  );
132
132
  this._scrapingBreaker = new ConsecutiveBreaker(
@@ -150,11 +150,16 @@ var DgiiClient = class {
150
150
  );
151
151
  }
152
152
  /**
153
- * Valida un comprobante fiscal (NCF) contra la DGII.
153
+ * Valida un comprobante fiscal contra la DGII.
154
+ *
155
+ * @param rnc - RNC del emisor
156
+ * @param ncf - NCF o e-NCF a validar
157
+ * @param options - Datos adicionales del e-NCF (`rncComprador`,
158
+ * `codigoSeguridad`). Se ignoran cuando `ncf` no es un e-NCF válido.
154
159
  */
155
- async getNCF(rnc, ncf) {
160
+ async getNCF(rnc, ncf, options) {
156
161
  return this._executeWithFallback(
157
- (client) => client.getNCF(rnc, ncf)
162
+ (client) => client.getNCF(rnc, ncf, options)
158
163
  );
159
164
  }
160
165
  async _executeWithFallback(operation) {
@@ -167,7 +172,7 @@ var DgiiClient = class {
167
172
  )
168
173
  );
169
174
  } catch (error) {
170
- if (error instanceof _chunk7RJWFPCZcjs.DgiiNotFoundError) throw error;
175
+ if (error instanceof _chunkNASWEFHNcjs.DgiiNotFoundError) throw error;
171
176
  errors.push(error instanceof Error ? error : new Error(String(error)));
172
177
  }
173
178
  if (this._soapFallback) {
@@ -179,11 +184,11 @@ var DgiiClient = class {
179
184
  )
180
185
  );
181
186
  } catch (error) {
182
- if (error instanceof _chunk7RJWFPCZcjs.DgiiNotFoundError) throw error;
187
+ if (error instanceof _chunkNASWEFHNcjs.DgiiNotFoundError) throw error;
183
188
  errors.push(error instanceof Error ? error : new Error(String(error)));
184
189
  }
185
190
  }
186
- throw new (0, _chunk7RJWFPCZcjs.AllStrategiesFailedError)(
191
+ throw new (0, _chunkNASWEFHNcjs.AllStrategiesFailedError)(
187
192
  `Todas las estrategias de consulta fallaron. \xDAltimo error: ${_nullishCoalesce(_optionalChain([errors, 'access', _6 => _6[errors.length - 1], 'optionalAccess', _7 => _7.message]), () => ( "desconocido"))}`,
188
193
  errors
189
194
  );
@@ -1,6 +1,9 @@
1
+ import {
2
+ validateEcf
3
+ } from "./chunk-H42U4GI5.js";
1
4
  import {
2
5
  wrapFetchError
3
- } from "./chunk-53A7IG4Y.js";
6
+ } from "./chunk-KW4ZV6FJ.js";
4
7
  import {
5
8
  collapseSpaces,
6
9
  stripNonDigits
@@ -8,7 +11,7 @@ import {
8
11
  import {
9
12
  DgiiNotFoundError,
10
13
  DgiiServiceError
11
- } from "./chunk-DMGDJEUY.js";
14
+ } from "./chunk-IRQX4WNQ.js";
12
15
 
13
16
  // src/scraping/endpoints.ts
14
17
  var DGII_RNC_URL = "https://dgii.gov.do/app/WebApps/ConsultasWeb2/ConsultasWeb/consultas/rnc.aspx";
@@ -21,10 +24,24 @@ var FORM_FIELDS = /* @__PURE__ */ Object.freeze({
21
24
  rncSubmit: "ctl00$cphMain$btnBuscarPorRNC",
22
25
  ncfRncInput: "ctl00$cphMain$txtRNC",
23
26
  ncfInput: "ctl00$cphMain$txtNCF",
24
- ncfSubmit: "ctl00$cphMain$btnConsultar"
27
+ ncfSubmit: "ctl00$cphMain$btnConsultar",
28
+ // Campos adicionales para e-NCF (serie E)
29
+ ncfRncCompradorInput: "ctl00$cphMain$txtRncComprador",
30
+ ncfCodigoSeguridadInput: "ctl00$cphMain$txtCodigoSeg"
25
31
  });
26
32
 
27
33
  // src/scraping/html-parser.ts
34
+ var NCF_INVALID_PHRASES = /* @__PURE__ */ Object.freeze([
35
+ "no es v",
36
+ "no se encuentra",
37
+ "no existe",
38
+ // Respuesta de la DGII a un e-NCF con código de seguridad incorrecto
39
+ "no encontrado"
40
+ ]);
41
+ function isKnownInvalidMessage(message) {
42
+ const normalized = message.toLowerCase();
43
+ return NCF_INVALID_PHRASES.some((phrase) => normalized.includes(phrase));
44
+ }
28
45
  function extractViewStateTokens(html) {
29
46
  const viewState = extractInputValue(html, "__VIEWSTATE");
30
47
  const viewStateGenerator = extractInputValue(html, "__VIEWSTATEGENERATOR");
@@ -91,7 +108,7 @@ function parseNcfHtml(html) {
91
108
  const infoEnd = html.indexOf("</span>", infoStart);
92
109
  if (infoEnd !== -1) {
93
110
  const infoBlock = html.slice(infoStart, infoEnd);
94
- if (infoBlock.includes("no es v") || infoBlock.includes("no se encuentra") || infoBlock.includes("no existe")) {
111
+ if (isKnownInvalidMessage(infoBlock)) {
95
112
  return { valid: false, rnc: "", ncf: "", nombreComercial: void 0 };
96
113
  }
97
114
  }
@@ -121,6 +138,58 @@ function parseNcfHtml(html) {
121
138
  nombreComercial: nombreComercial ? collapseSpaces(nombreComercial) : void 0
122
139
  };
123
140
  }
141
+ function parseEcfHtml(html) {
142
+ const info = extractSpanValue(html, "cphMain_lblInformacion");
143
+ if (info && isKnownInvalidMessage(info)) {
144
+ return { valid: false, rnc: "", ncf: "" };
145
+ }
146
+ if (info) {
147
+ throw new DgiiServiceError(`La DGII respondi\xF3: ${info}`);
148
+ }
149
+ if (!html.includes("cphMain_PResultadoFE")) {
150
+ throw new DgiiServiceError(
151
+ "Formato de respuesta HTML inesperado: panel de resultado e-NCF no encontrado"
152
+ );
153
+ }
154
+ const rncEmisor = extractSpanValue(html, "cphMain_lblrncemisor");
155
+ const ncf = extractSpanValue(html, "cphMain_lblencf");
156
+ if (!rncEmisor && !ncf) {
157
+ throw new DgiiServiceError(
158
+ "Formato de respuesta HTML inesperado: panel e-NCF sin emisor ni e-NCF"
159
+ );
160
+ }
161
+ const rncComprador = extractSpanValue(html, "cphMain_lblrnccomprador");
162
+ const codigoSeguridad = extractSpanValue(html, "cphMain_lblCodSeguridad");
163
+ const estado = extractSpanValue(html, "cphMain_lblEstadoFe");
164
+ const fechaEmision = extractSpanValue(html, "cphMain_lblFechaEmision");
165
+ const fechaFirma = extractSpanValue(html, "cphMain_lblFechaFirma");
166
+ return {
167
+ valid: true,
168
+ rnc: stripNonDigits(rncEmisor),
169
+ ncf: collapseSpaces(ncf),
170
+ rncComprador: rncComprador ? stripNonDigits(rncComprador) : void 0,
171
+ codigoSeguridad: codigoSeguridad || void 0,
172
+ estado: estado || void 0,
173
+ montoTotal: parseAmount(extractSpanValue(html, "cphMain_lblMontoTotal")),
174
+ totalItbis: parseAmount(extractSpanValue(html, "cphMain_lblTotalItbis")),
175
+ fechaEmision: fechaEmision || void 0,
176
+ fechaFirma: fechaFirma || void 0
177
+ };
178
+ }
179
+ function parseAmount(raw) {
180
+ if (!raw) return void 0;
181
+ const value = Number(raw.replace(/,/g, ""));
182
+ return Number.isFinite(value) ? value : void 0;
183
+ }
184
+ function extractSpanValue(html, spanId) {
185
+ const idx = html.indexOf(`id="${spanId}"`);
186
+ if (idx === -1) return "";
187
+ const tagEnd = html.indexOf(">", idx);
188
+ if (tagEnd === -1) return "";
189
+ const closeTag = html.indexOf("</span>", tagEnd);
190
+ if (closeTag === -1) return "";
191
+ return decodeHtmlEntities(stripHtmlTags(html.slice(tagEnd + 1, closeTag)));
192
+ }
124
193
  function extractInputValue(html, name) {
125
194
  let idx = html.indexOf(`id="${name}"`);
126
195
  if (idx === -1) {
@@ -201,26 +270,60 @@ var ScrapingClient = class {
201
270
  return parseContribuyenteHtml(html);
202
271
  }
203
272
  /**
204
- * Valida un comprobante fiscal (NCF) contra la DGII.
273
+ * Valida un comprobante fiscal contra la DGII.
274
+ *
275
+ * Soporta NCF (serie B) y e-NCF (serie E). Para un e-NCF la DGII
276
+ * exige el RNC del comprador; sin él responde con un mensaje de campo
277
+ * requerido y este método lanza `DgiiServiceError`.
278
+ *
279
+ * @param rnc - RNC del emisor
280
+ * @param ncf - NCF o e-NCF a validar
281
+ * @param options - Datos adicionales del e-NCF (`rncComprador`,
282
+ * `codigoSeguridad`). Se ignoran cuando `ncf` no es un e-NCF válido.
205
283
  */
206
- async getNCF(rnc, ncf) {
284
+ async getNCF(rnc, ncf, options) {
207
285
  if (typeof rnc !== "string" || rnc.trim() === "") {
208
286
  throw new DgiiServiceError("El par\xE1metro rnc es requerido");
209
287
  }
210
288
  if (typeof ncf !== "string" || ncf.trim() === "") {
211
289
  throw new DgiiServiceError("El par\xE1metro ncf es requerido");
212
290
  }
291
+ const rncComprador = options?.rncComprador;
292
+ if (rncComprador !== void 0 && (typeof rncComprador !== "string" || rncComprador.trim() === "")) {
293
+ throw new DgiiServiceError(
294
+ "La opci\xF3n rncComprador debe ser un string no vac\xEDo"
295
+ );
296
+ }
297
+ const codigoSeguridad = options?.codigoSeguridad;
298
+ if (codigoSeguridad !== void 0 && (typeof codigoSeguridad !== "string" || codigoSeguridad.trim() === "")) {
299
+ throw new DgiiServiceError(
300
+ "La opci\xF3n codigoSeguridad debe ser un string no vac\xEDo"
301
+ );
302
+ }
303
+ const isEcf = validateEcf(ncf).valid;
213
304
  const tokens = await this._fetchTokens(this._ncfUrl);
214
- const body = buildFormBody([
305
+ const fields = [
215
306
  [FORM_FIELDS.viewState, tokens.viewState],
216
307
  [FORM_FIELDS.viewStateGenerator, tokens.viewStateGenerator],
217
308
  [FORM_FIELDS.eventValidation, tokens.eventValidation],
218
309
  [FORM_FIELDS.ncfRncInput, rnc.trim()],
219
- [FORM_FIELDS.ncfInput, ncf.trim()],
220
- [FORM_FIELDS.ncfSubmit, "Buscar"]
221
- ]);
310
+ [FORM_FIELDS.ncfInput, ncf.trim()]
311
+ ];
312
+ if (isEcf) {
313
+ if (rncComprador !== void 0) {
314
+ fields.push([FORM_FIELDS.ncfRncCompradorInput, rncComprador.trim()]);
315
+ }
316
+ if (codigoSeguridad !== void 0) {
317
+ fields.push([
318
+ FORM_FIELDS.ncfCodigoSeguridadInput,
319
+ codigoSeguridad.trim()
320
+ ]);
321
+ }
322
+ }
323
+ fields.push([FORM_FIELDS.ncfSubmit, "Buscar"]);
324
+ const body = buildFormBody(fields);
222
325
  const html = await this._post(this._ncfUrl, body);
223
- return parseNcfHtml(html);
326
+ return isEcf ? parseEcfHtml(html) : parseNcfHtml(html);
224
327
  }
225
328
  async _fetchTokens(url) {
226
329
  let response;