dgii-ts 0.2.0 → 0.3.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.en.md +36 -10
- package/README.md +37 -9
- package/dist/{chunk-FRM5XREF.cjs → chunk-6REHWG3R.cjs} +3 -3
- package/dist/{chunk-6ZRPVQPP.cjs → chunk-DUKBAJTM.cjs} +13 -4
- package/dist/{chunk-7ILLRYE4.js → chunk-LRPZFX62.js} +3 -3
- package/dist/{chunk-JV4HWHYP.js → chunk-YV4IPACJ.js} +12 -3
- package/dist/client.cjs +3 -3
- package/dist/client.d.cts +13 -3
- package/dist/client.d.ts +13 -3
- package/dist/client.js +2 -2
- package/dist/index.cjs +3 -3
- package/dist/index.js +2 -2
- package/dist/scraping.cjs +2 -2
- package/dist/scraping.js +1 -1
- package/package.json +3 -2
- package/skills/dgii-ts/SKILL.md +273 -0
package/README.en.md
CHANGED
|
@@ -40,8 +40,9 @@ that pass validation despite failing the check-digit algorithm.
|
|
|
40
40
|
### Resilient client (DgiiClient)
|
|
41
41
|
|
|
42
42
|
`DgiiClient` is the recommended entry point for real-time queries. It uses
|
|
43
|
-
web scraping
|
|
44
|
-
|
|
43
|
+
web scraping with a circuit breaker to prevent cascading failures and retry
|
|
44
|
+
with exponential backoff. The SOAP fallback is opt-in (`soapFallback: true`)
|
|
45
|
+
and off by default since version 0.3.0.
|
|
45
46
|
|
|
46
47
|
### Web scraping
|
|
47
48
|
|
|
@@ -52,8 +53,8 @@ endpoint in January 2025.
|
|
|
52
53
|
### SOAP client (WSMovilDGII) — deprecated
|
|
53
54
|
|
|
54
55
|
Typed wrapper around DGII's SOAP service. **Permanently blocked by DGII
|
|
55
|
-
since January 2025.** Kept as an
|
|
56
|
-
for direct use.
|
|
56
|
+
since January 2025.** Kept as an opt-in `DgiiClient` fallback (off by
|
|
57
|
+
default) and not recommended for direct use.
|
|
57
58
|
|
|
58
59
|
### Bulk data importer
|
|
59
60
|
|
|
@@ -74,6 +75,29 @@ pnpm add dgii-ts
|
|
|
74
75
|
yarn add dgii-ts
|
|
75
76
|
```
|
|
76
77
|
|
|
78
|
+
## Agent skill
|
|
79
|
+
|
|
80
|
+
dgii-ts ships a skill ([`skills/dgii-ts/SKILL.md`](./skills/dgii-ts/SKILL.md))
|
|
81
|
+
that teaches coding agents (Claude Code, Cursor, Codex, Copilot, and others
|
|
82
|
+
that support [Agent Skills](https://agentskills.io)) to use the library
|
|
83
|
+
correctly: what each validator checks, how to handle `DgiiClient` errors,
|
|
84
|
+
and how to avoid hammering DGII.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Any supported agent
|
|
88
|
+
npx skills add franklinmdev/dgii-ts --skill dgii-ts
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
In Claude Code you can also install it as a plugin:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
/plugin marketplace add franklinmdev/dgii-ts
|
|
95
|
+
/plugin install dgii-ts@dgii-ts
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The skill is also included in the npm package, under
|
|
99
|
+
`node_modules/dgii-ts/skills/`.
|
|
100
|
+
|
|
77
101
|
## Quick start
|
|
78
102
|
|
|
79
103
|
### Validate an RNC
|
|
@@ -141,8 +165,11 @@ const ncfResult = await client.getNCF('131098193', 'B0100000001');
|
|
|
141
165
|
### Validate an e-NCF online
|
|
142
166
|
|
|
143
167
|
For an e-NCF (E-series) DGII requires the buyer's RNC. Without it DGII
|
|
144
|
-
answers with a required-field message
|
|
145
|
-
`DgiiServiceError
|
|
168
|
+
answers with a required-field message: `ScrapingClient.getNCF` throws
|
|
169
|
+
`DgiiServiceError` and `DgiiClient.getNCF` wraps it in
|
|
170
|
+
`AllStrategiesFailedError` after exhausting retries and the fallback
|
|
171
|
+
(the message keeps DGII's text). The same happens without the
|
|
172
|
+
security code: DGII requires it too (verified with an E31 e-NCF).
|
|
146
173
|
|
|
147
174
|
```typescript
|
|
148
175
|
const ecfResult = await client.getNCF('101010632', 'E310125217173', {
|
|
@@ -185,7 +212,7 @@ ignored for a B-series NCF.
|
|
|
185
212
|
|
|
186
213
|
| Class/Method | Description |
|
|
187
214
|
| --- | --- |
|
|
188
|
-
| `DgiiClient` | Scraping
|
|
215
|
+
| `DgiiClient` | Scraping, circuit breaker, retry (optional SOAP fallback) |
|
|
189
216
|
| `client.getContribuyente(rnc)` | Looks up taxpayer data by RNC |
|
|
190
217
|
| `client.getNCF(rnc, ncf, options?)` | Validates a fiscal receipt against DGII (`options` for e-NCF) |
|
|
191
218
|
|
|
@@ -227,7 +254,7 @@ accepts an `encoding` option (defaults to `latin1`).
|
|
|
227
254
|
├─────────────────────────────────────────────────┤
|
|
228
255
|
│ Layer 2: DgiiClient (resilient) │
|
|
229
256
|
│ ✓ Web scraping (primary) │
|
|
230
|
-
│ ✓
|
|
257
|
+
│ ✓ Circuit breaker ✓ Retry │
|
|
231
258
|
├─────────────────────────────────────────────────┤
|
|
232
259
|
│ Layer 3: Bulk data (DGII_RNC.zip) │
|
|
233
260
|
│ ✓ Daily download ✓ TXT parsing │
|
|
@@ -235,8 +262,7 @@ accepts an `encoding` option (defaults to `latin1`).
|
|
|
235
262
|
```
|
|
236
263
|
|
|
237
264
|
**Layer 1** is instant and works offline. **Layer 2** provides real-time data
|
|
238
|
-
using web scraping
|
|
239
|
-
breaker, and retry with exponential backoff. **Layer 3** is ideal for batch
|
|
265
|
+
using web scraping, a circuit breaker, and retry with exponential backoff. **Layer 3** is ideal for batch
|
|
240
266
|
operations where you need to look up thousands of RNCs quickly.
|
|
241
267
|
|
|
242
268
|
## Project status
|
package/README.md
CHANGED
|
@@ -43,8 +43,9 @@ pasan validación aunque no cumplan el algoritmo de dígito verificador.
|
|
|
43
43
|
### Cliente resiliente (DgiiClient)
|
|
44
44
|
|
|
45
45
|
`DgiiClient` es el punto de entrada recomendado para consultas en tiempo real.
|
|
46
|
-
Usa web scraping
|
|
47
|
-
|
|
46
|
+
Usa web scraping con circuit breaker para evitar cascadas de errores y retry
|
|
47
|
+
con backoff exponencial. El fallback a SOAP es opcional (`soapFallback: true`)
|
|
48
|
+
y está apagado por defecto desde la versión 0.3.0.
|
|
48
49
|
|
|
49
50
|
### Web scraping
|
|
50
51
|
|
|
@@ -56,7 +57,8 @@ bloqueó el endpoint SOAP en enero 2025.
|
|
|
56
57
|
|
|
57
58
|
Wrapper tipado alrededor del servicio SOAP de la DGII. **Bloqueado
|
|
58
59
|
permanentemente por la DGII en enero 2025.** Se mantiene como fallback
|
|
59
|
-
|
|
60
|
+
opcional de `DgiiClient` (apagado por defecto) y no se recomienda su uso
|
|
61
|
+
directo.
|
|
60
62
|
|
|
61
63
|
### Importador de datos masivos
|
|
62
64
|
|
|
@@ -77,6 +79,29 @@ pnpm add dgii-ts
|
|
|
77
79
|
yarn add dgii-ts
|
|
78
80
|
```
|
|
79
81
|
|
|
82
|
+
## Skill para agentes de IA
|
|
83
|
+
|
|
84
|
+
dgii-ts incluye un skill ([`skills/dgii-ts/SKILL.md`](./skills/dgii-ts/SKILL.md))
|
|
85
|
+
que enseña a los agentes de código (Claude Code, Cursor, Codex, Copilot y
|
|
86
|
+
otros compatibles con [Agent Skills](https://agentskills.io)) a usar la
|
|
87
|
+
librería correctamente: qué valida cada validador, cómo manejar los
|
|
88
|
+
errores de `DgiiClient` y cómo no saturar a la DGII.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Cualquier agente compatible
|
|
92
|
+
npx skills add franklinmdev/dgii-ts --skill dgii-ts
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
En Claude Code también se puede instalar como plugin:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
/plugin marketplace add franklinmdev/dgii-ts
|
|
99
|
+
/plugin install dgii-ts@dgii-ts
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
El skill viene además dentro del paquete de npm, en
|
|
103
|
+
`node_modules/dgii-ts/skills/`.
|
|
104
|
+
|
|
80
105
|
## Uso rápido
|
|
81
106
|
|
|
82
107
|
### Validar un RNC
|
|
@@ -144,8 +169,11 @@ const ncfResult = await client.getNCF('131098193', 'B0100000001');
|
|
|
144
169
|
### Validar e-NCF en línea
|
|
145
170
|
|
|
146
171
|
Para un e-NCF (serie E) la DGII exige el RNC del comprador. Sin él
|
|
147
|
-
responde con un mensaje de campo requerido
|
|
148
|
-
`DgiiServiceError
|
|
172
|
+
responde con un mensaje de campo requerido: `ScrapingClient.getNCF`
|
|
173
|
+
lanza `DgiiServiceError` y `DgiiClient.getNCF` lo envuelve en
|
|
174
|
+
`AllStrategiesFailedError` tras agotar reintentos y fallback (el
|
|
175
|
+
mensaje conserva el texto de la DGII). Lo mismo pasa sin el código
|
|
176
|
+
de seguridad: la DGII también lo exige (verificado con un e-NCF E31).
|
|
149
177
|
|
|
150
178
|
```typescript
|
|
151
179
|
const ecfResult = await client.getNCF('101010632', 'E310125217173', {
|
|
@@ -189,7 +217,7 @@ serie B.
|
|
|
189
217
|
|
|
190
218
|
| Clase/Método | Descripción |
|
|
191
219
|
| --- | --- |
|
|
192
|
-
| `DgiiClient` | Cliente con scraping
|
|
220
|
+
| `DgiiClient` | Cliente con scraping, circuit breaker y retry (SOAP fallback opcional) |
|
|
193
221
|
| `client.getContribuyente(rnc)` | Consulta datos de un contribuyente por RNC |
|
|
194
222
|
| `client.getNCF(rnc, ncf, options?)` | Valida un comprobante fiscal contra la DGII (`options` para e-NCF) |
|
|
195
223
|
|
|
@@ -231,7 +259,7 @@ Acepta `encoding` en las opciones (`latin1` por defecto).
|
|
|
231
259
|
├─────────────────────────────────────────────────┤
|
|
232
260
|
│ Capa 2: DgiiClient (resiliente) │
|
|
233
261
|
│ ✓ Web scraping (primario) │
|
|
234
|
-
│ ✓
|
|
262
|
+
│ ✓ Circuit breaker ✓ Retry │
|
|
235
263
|
├─────────────────────────────────────────────────┤
|
|
236
264
|
│ Capa 3: Datos masivos (DGII_RNC.zip) │
|
|
237
265
|
│ ✓ Descarga diaria ✓ Parseo TXT │
|
|
@@ -239,8 +267,8 @@ Acepta `encoding` en las opciones (`latin1` por defecto).
|
|
|
239
267
|
```
|
|
240
268
|
|
|
241
269
|
La **capa 1** es instantánea y funciona sin conexión. La **capa 2** ofrece
|
|
242
|
-
datos en tiempo real con web scraping
|
|
243
|
-
|
|
270
|
+
datos en tiempo real con web scraping, circuit breaker y retry con backoff
|
|
271
|
+
exponencial. La **capa 3** es
|
|
244
272
|
ideal para operaciones en lote donde necesitas buscar miles de RNC rápidamente.
|
|
245
273
|
|
|
246
274
|
## Estado del proyecto
|
|
@@ -94,7 +94,7 @@ function parseContribuyenteHtml(html) {
|
|
|
94
94
|
rnc: _chunkQH4BK5X3cjs.stripNonDigits.call(void 0, rnc),
|
|
95
95
|
nombre: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, nombre),
|
|
96
96
|
nombreComercial: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, nombreComercial),
|
|
97
|
-
estado: rawEstado.toUpperCase()
|
|
97
|
+
estado: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, rawEstado).toUpperCase() === "ACTIVO" ? "ACTIVO" : "INACTIVO",
|
|
98
98
|
categoria: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, categoria),
|
|
99
99
|
esFacturadorElectronico: rawFacturadorElectronico.trim().toUpperCase().replace(/[ÍI]/g, "I").startsWith("SI"),
|
|
100
100
|
actividadEconomica: actividadEconomica ? _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, actividadEconomica) : void 0,
|
|
@@ -210,13 +210,13 @@ function extractTableFields(tableHtml) {
|
|
|
210
210
|
const fields = /* @__PURE__ */ new Map();
|
|
211
211
|
for (const match of tableHtml.matchAll(BOLD_STYLE_PATTERN)) {
|
|
212
212
|
const label = normalizeLabel(_nullishCoalesce(match[1], () => ( "")));
|
|
213
|
-
const value = stripHtmlTags(_nullishCoalesce(match[2], () => ( "")));
|
|
213
|
+
const value = decodeHtmlEntities(stripHtmlTags(_nullishCoalesce(match[2], () => ( ""))));
|
|
214
214
|
if (label) fields.set(label, value);
|
|
215
215
|
}
|
|
216
216
|
if (fields.size === 0) {
|
|
217
217
|
for (const match of tableHtml.matchAll(BOLD_TAG_PATTERN)) {
|
|
218
218
|
const label = normalizeLabel(_nullishCoalesce(match[1], () => ( "")));
|
|
219
|
-
const value = stripHtmlTags(_nullishCoalesce(match[2], () => ( "")));
|
|
219
|
+
const value = decodeHtmlEntities(stripHtmlTags(_nullishCoalesce(match[2], () => ( ""))));
|
|
220
220
|
if (label) fields.set(label, value);
|
|
221
221
|
}
|
|
222
222
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var _chunkDLB5RJ7Ecjs = require('./chunk-DLB5RJ7E.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunk6REHWG3Rcjs = require('./chunk-6REHWG3R.cjs');
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
@@ -52,7 +52,11 @@ var ConsecutiveBreaker = class {
|
|
|
52
52
|
this._onSuccess();
|
|
53
53
|
return result;
|
|
54
54
|
} catch (error) {
|
|
55
|
-
|
|
55
|
+
if (error instanceof _chunkNASWEFHNcjs.DgiiNotFoundError) {
|
|
56
|
+
this._onSuccess();
|
|
57
|
+
} else {
|
|
58
|
+
this._onFailure();
|
|
59
|
+
}
|
|
56
60
|
throw error;
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -123,7 +127,7 @@ async function withRetry(fn, options = DEFAULT_RETRY_OPTIONS) {
|
|
|
123
127
|
var DgiiClient = class {
|
|
124
128
|
constructor(options) {
|
|
125
129
|
const timeout = _optionalChain([options, 'optionalAccess', _ => _.timeout]);
|
|
126
|
-
this._scraping = new (0,
|
|
130
|
+
this._scraping = new (0, _chunk6REHWG3Rcjs.ScrapingClient)(
|
|
127
131
|
timeout ? { timeout } : void 0
|
|
128
132
|
);
|
|
129
133
|
this._soap = new (0, _chunkDLB5RJ7Ecjs.DgiiSoapClient)(
|
|
@@ -139,7 +143,7 @@ var DgiiClient = class {
|
|
|
139
143
|
...DEFAULT_RETRY_OPTIONS,
|
|
140
144
|
..._optionalChain([options, 'optionalAccess', _4 => _4.retry])
|
|
141
145
|
};
|
|
142
|
-
this._soapFallback = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _5 => _5.soapFallback]), () => (
|
|
146
|
+
this._soapFallback = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _5 => _5.soapFallback]), () => ( false));
|
|
143
147
|
}
|
|
144
148
|
/**
|
|
145
149
|
* Consulta datos de un contribuyente por RNC o cédula.
|
|
@@ -156,6 +160,11 @@ var DgiiClient = class {
|
|
|
156
160
|
* @param ncf - NCF o e-NCF a validar
|
|
157
161
|
* @param options - Datos adicionales del e-NCF (`rncComprador`,
|
|
158
162
|
* `codigoSeguridad`). Se ignoran cuando `ncf` no es un e-NCF válido.
|
|
163
|
+
*
|
|
164
|
+
* Para un e-NCF sin `rncComprador` la DGII responde con un mensaje de
|
|
165
|
+
* campo requerido: `ScrapingClient.getNCF` lanza `DgiiServiceError` y
|
|
166
|
+
* este método lo envuelve en `AllStrategiesFailedError` tras agotar
|
|
167
|
+
* reintentos y fallback (el mensaje conserva el texto de la DGII).
|
|
159
168
|
*/
|
|
160
169
|
async getNCF(rnc, ncf, options) {
|
|
161
170
|
return this._executeWithFallback(
|
|
@@ -94,7 +94,7 @@ function parseContribuyenteHtml(html) {
|
|
|
94
94
|
rnc: stripNonDigits(rnc),
|
|
95
95
|
nombre: collapseSpaces(nombre),
|
|
96
96
|
nombreComercial: collapseSpaces(nombreComercial),
|
|
97
|
-
estado: rawEstado.toUpperCase()
|
|
97
|
+
estado: collapseSpaces(rawEstado).toUpperCase() === "ACTIVO" ? "ACTIVO" : "INACTIVO",
|
|
98
98
|
categoria: collapseSpaces(categoria),
|
|
99
99
|
esFacturadorElectronico: rawFacturadorElectronico.trim().toUpperCase().replace(/[ÍI]/g, "I").startsWith("SI"),
|
|
100
100
|
actividadEconomica: actividadEconomica ? collapseSpaces(actividadEconomica) : void 0,
|
|
@@ -210,13 +210,13 @@ function extractTableFields(tableHtml) {
|
|
|
210
210
|
const fields = /* @__PURE__ */ new Map();
|
|
211
211
|
for (const match of tableHtml.matchAll(BOLD_STYLE_PATTERN)) {
|
|
212
212
|
const label = normalizeLabel(match[1] ?? "");
|
|
213
|
-
const value = stripHtmlTags(match[2] ?? "");
|
|
213
|
+
const value = decodeHtmlEntities(stripHtmlTags(match[2] ?? ""));
|
|
214
214
|
if (label) fields.set(label, value);
|
|
215
215
|
}
|
|
216
216
|
if (fields.size === 0) {
|
|
217
217
|
for (const match of tableHtml.matchAll(BOLD_TAG_PATTERN)) {
|
|
218
218
|
const label = normalizeLabel(match[1] ?? "");
|
|
219
|
-
const value = stripHtmlTags(match[2] ?? "");
|
|
219
|
+
const value = decodeHtmlEntities(stripHtmlTags(match[2] ?? ""));
|
|
220
220
|
if (label) fields.set(label, value);
|
|
221
221
|
}
|
|
222
222
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-QTN5ZT4P.js";
|
|
4
4
|
import {
|
|
5
5
|
ScrapingClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-LRPZFX62.js";
|
|
7
7
|
import {
|
|
8
8
|
AllStrategiesFailedError,
|
|
9
9
|
DgiiConnectionError,
|
|
@@ -52,7 +52,11 @@ var ConsecutiveBreaker = class {
|
|
|
52
52
|
this._onSuccess();
|
|
53
53
|
return result;
|
|
54
54
|
} catch (error) {
|
|
55
|
-
|
|
55
|
+
if (error instanceof DgiiNotFoundError) {
|
|
56
|
+
this._onSuccess();
|
|
57
|
+
} else {
|
|
58
|
+
this._onFailure();
|
|
59
|
+
}
|
|
56
60
|
throw error;
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -139,7 +143,7 @@ var DgiiClient = class {
|
|
|
139
143
|
...DEFAULT_RETRY_OPTIONS,
|
|
140
144
|
...options?.retry
|
|
141
145
|
};
|
|
142
|
-
this._soapFallback = options?.soapFallback ??
|
|
146
|
+
this._soapFallback = options?.soapFallback ?? false;
|
|
143
147
|
}
|
|
144
148
|
/**
|
|
145
149
|
* Consulta datos de un contribuyente por RNC o cédula.
|
|
@@ -156,6 +160,11 @@ var DgiiClient = class {
|
|
|
156
160
|
* @param ncf - NCF o e-NCF a validar
|
|
157
161
|
* @param options - Datos adicionales del e-NCF (`rncComprador`,
|
|
158
162
|
* `codigoSeguridad`). Se ignoran cuando `ncf` no es un e-NCF válido.
|
|
163
|
+
*
|
|
164
|
+
* Para un e-NCF sin `rncComprador` la DGII responde con un mensaje de
|
|
165
|
+
* campo requerido: `ScrapingClient.getNCF` lanza `DgiiServiceError` y
|
|
166
|
+
* este método lo envuelve en `AllStrategiesFailedError` tras agotar
|
|
167
|
+
* reintentos y fallback (el mensaje conserva el texto de la DGII).
|
|
159
168
|
*/
|
|
160
169
|
async getNCF(rnc, ncf, options) {
|
|
161
170
|
return this._executeWithFallback(
|
package/dist/client.cjs
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkDUKBAJTMcjs = require('./chunk-DUKBAJTM.cjs');
|
|
7
7
|
require('./chunk-DLB5RJ7E.cjs');
|
|
8
|
-
require('./chunk-
|
|
8
|
+
require('./chunk-6REHWG3R.cjs');
|
|
9
9
|
require('./chunk-OSGHCQFA.cjs');
|
|
10
10
|
require('./chunk-WMFHPNFP.cjs');
|
|
11
11
|
require('./chunk-QH4BK5X3.cjs');
|
|
@@ -15,4 +15,4 @@ require('./chunk-NASWEFHN.cjs');
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
exports.ConsecutiveBreaker =
|
|
18
|
+
exports.ConsecutiveBreaker = _chunkDUKBAJTMcjs.ConsecutiveBreaker; exports.DgiiClient = _chunkDUKBAJTMcjs.DgiiClient; exports.isRetryableError = _chunkDUKBAJTMcjs.isRetryableError; exports.withRetry = _chunkDUKBAJTMcjs.withRetry;
|
package/dist/client.d.cts
CHANGED
|
@@ -59,7 +59,11 @@ declare class ConsecutiveBreaker {
|
|
|
59
59
|
interface ClientOptions {
|
|
60
60
|
/** Tiempo de espera en milisegundos (por defecto: 15000) */
|
|
61
61
|
timeout?: number;
|
|
62
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Habilitar fallback a SOAP (por defecto: false). La DGII bloqueó el
|
|
64
|
+
* endpoint SOAP en enero 2025; activarlo solo agrega una petición
|
|
65
|
+
* inútil tras cada falla del scraping.
|
|
66
|
+
*/
|
|
63
67
|
soapFallback?: boolean;
|
|
64
68
|
/** Opciones de reintentos */
|
|
65
69
|
retry?: Partial<RetryOptions>;
|
|
@@ -70,8 +74,9 @@ interface ClientOptions {
|
|
|
70
74
|
/**
|
|
71
75
|
* Cliente resiliente para consultas a la DGII.
|
|
72
76
|
*
|
|
73
|
-
* Usa web scraping
|
|
74
|
-
* fallback (
|
|
77
|
+
* Usa web scraping con circuit breaker y reintentos automáticos. El
|
|
78
|
+
* fallback a SOAP es opcional (`soapFallback: true`) y está apagado por
|
|
79
|
+
* defecto: la DGII bloqueó ese endpoint en enero 2025.
|
|
75
80
|
*/
|
|
76
81
|
declare class DgiiClient {
|
|
77
82
|
private readonly _scraping;
|
|
@@ -92,6 +97,11 @@ declare class DgiiClient {
|
|
|
92
97
|
* @param ncf - NCF o e-NCF a validar
|
|
93
98
|
* @param options - Datos adicionales del e-NCF (`rncComprador`,
|
|
94
99
|
* `codigoSeguridad`). Se ignoran cuando `ncf` no es un e-NCF válido.
|
|
100
|
+
*
|
|
101
|
+
* Para un e-NCF sin `rncComprador` la DGII responde con un mensaje de
|
|
102
|
+
* campo requerido: `ScrapingClient.getNCF` lanza `DgiiServiceError` y
|
|
103
|
+
* este método lo envuelve en `AllStrategiesFailedError` tras agotar
|
|
104
|
+
* reintentos y fallback (el mensaje conserva el texto de la DGII).
|
|
95
105
|
*/
|
|
96
106
|
getNCF(rnc: string, ncf: string, options?: NcfQueryOptions): Promise<NcfQueryResult>;
|
|
97
107
|
private _executeWithFallback;
|
package/dist/client.d.ts
CHANGED
|
@@ -59,7 +59,11 @@ declare class ConsecutiveBreaker {
|
|
|
59
59
|
interface ClientOptions {
|
|
60
60
|
/** Tiempo de espera en milisegundos (por defecto: 15000) */
|
|
61
61
|
timeout?: number;
|
|
62
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* Habilitar fallback a SOAP (por defecto: false). La DGII bloqueó el
|
|
64
|
+
* endpoint SOAP en enero 2025; activarlo solo agrega una petición
|
|
65
|
+
* inútil tras cada falla del scraping.
|
|
66
|
+
*/
|
|
63
67
|
soapFallback?: boolean;
|
|
64
68
|
/** Opciones de reintentos */
|
|
65
69
|
retry?: Partial<RetryOptions>;
|
|
@@ -70,8 +74,9 @@ interface ClientOptions {
|
|
|
70
74
|
/**
|
|
71
75
|
* Cliente resiliente para consultas a la DGII.
|
|
72
76
|
*
|
|
73
|
-
* Usa web scraping
|
|
74
|
-
* fallback (
|
|
77
|
+
* Usa web scraping con circuit breaker y reintentos automáticos. El
|
|
78
|
+
* fallback a SOAP es opcional (`soapFallback: true`) y está apagado por
|
|
79
|
+
* defecto: la DGII bloqueó ese endpoint en enero 2025.
|
|
75
80
|
*/
|
|
76
81
|
declare class DgiiClient {
|
|
77
82
|
private readonly _scraping;
|
|
@@ -92,6 +97,11 @@ declare class DgiiClient {
|
|
|
92
97
|
* @param ncf - NCF o e-NCF a validar
|
|
93
98
|
* @param options - Datos adicionales del e-NCF (`rncComprador`,
|
|
94
99
|
* `codigoSeguridad`). Se ignoran cuando `ncf` no es un e-NCF válido.
|
|
100
|
+
*
|
|
101
|
+
* Para un e-NCF sin `rncComprador` la DGII responde con un mensaje de
|
|
102
|
+
* campo requerido: `ScrapingClient.getNCF` lanza `DgiiServiceError` y
|
|
103
|
+
* este método lo envuelve en `AllStrategiesFailedError` tras agotar
|
|
104
|
+
* reintentos y fallback (el mensaje conserva el texto de la DGII).
|
|
95
105
|
*/
|
|
96
106
|
getNCF(rnc: string, ncf: string, options?: NcfQueryOptions): Promise<NcfQueryResult>;
|
|
97
107
|
private _executeWithFallback;
|
package/dist/client.js
CHANGED
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
DgiiClient,
|
|
4
4
|
isRetryableError,
|
|
5
5
|
withRetry
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YV4IPACJ.js";
|
|
7
7
|
import "./chunk-QTN5ZT4P.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-LRPZFX62.js";
|
|
9
9
|
import "./chunk-H42U4GI5.js";
|
|
10
10
|
import "./chunk-KW4ZV6FJ.js";
|
|
11
11
|
import "./chunk-D26S6EXD.js";
|
package/dist/index.cjs
CHANGED
|
@@ -17,7 +17,7 @@ require('./chunk-2ST6QKHA.cjs');
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
var
|
|
20
|
+
var _chunkDUKBAJTMcjs = require('./chunk-DUKBAJTM.cjs');
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ var _chunkDLB5RJ7Ecjs = require('./chunk-DLB5RJ7E.cjs');
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
var
|
|
30
|
+
var _chunk6REHWG3Rcjs = require('./chunk-6REHWG3R.cjs');
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
@@ -69,4 +69,4 @@ var _chunkNASWEFHNcjs = require('./chunk-NASWEFHN.cjs');
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
exports.AllStrategiesFailedError = _chunkNASWEFHNcjs.AllStrategiesFailedError; exports.BulkFormatError = _chunkNASWEFHNcjs.BulkFormatError; exports.ConsecutiveBreaker =
|
|
72
|
+
exports.AllStrategiesFailedError = _chunkNASWEFHNcjs.AllStrategiesFailedError; exports.BulkFormatError = _chunkNASWEFHNcjs.BulkFormatError; exports.ConsecutiveBreaker = _chunkDUKBAJTMcjs.ConsecutiveBreaker; exports.DGII_BULK_URL = _chunkVIZWYFHPcjs.DGII_BULK_URL; exports.DGII_ESTADOS = _chunkVIZWYFHPcjs.DGII_ESTADOS; exports.DGII_NCF_URL = _chunk6REHWG3Rcjs.DGII_NCF_URL; exports.DGII_RNC_URL = _chunk6REHWG3Rcjs.DGII_RNC_URL; exports.DGII_SOAP_BASE_URL = _chunkDLB5RJ7Ecjs.DGII_SOAP_BASE_URL; exports.DgiiClient = _chunkDUKBAJTMcjs.DgiiClient; exports.DgiiConnectionError = _chunkNASWEFHNcjs.DgiiConnectionError; exports.DgiiError = _chunkNASWEFHNcjs.DgiiError; exports.DgiiNotFoundError = _chunkNASWEFHNcjs.DgiiNotFoundError; exports.DgiiServiceError = _chunkNASWEFHNcjs.DgiiServiceError; exports.DgiiSoapClient = _chunkDLB5RJ7Ecjs.DgiiSoapClient; exports.ECF_TYPE_NAMES = _chunkOSGHCQFAcjs.ECF_TYPE_NAMES; exports.NCF_TYPE_NAMES = _chunkUW6W2JHWcjs.NCF_TYPE_NAMES; exports.SOAP_ACTIONS = _chunkDLB5RJ7Ecjs.SOAP_ACTIONS; exports.ScrapingClient = _chunk6REHWG3Rcjs.ScrapingClient; exports.downloadBulkFile = _chunkVIZWYFHPcjs.downloadBulkFile; exports.isRetryableError = _chunkDUKBAJTMcjs.isRetryableError; exports.parseBulkFile = _chunkVIZWYFHPcjs.parseBulkFile; exports.validateCedula = _chunkUW6W2JHWcjs.validateCedula; exports.validateEcf = _chunkOSGHCQFAcjs.validateEcf; exports.validateNcf = _chunkUW6W2JHWcjs.validateNcf; exports.validateRnc = _chunkUW6W2JHWcjs.validateRnc; exports.withRetry = _chunkDUKBAJTMcjs.withRetry;
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
DgiiClient,
|
|
18
18
|
isRetryableError,
|
|
19
19
|
withRetry
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-YV4IPACJ.js";
|
|
21
21
|
import {
|
|
22
22
|
DGII_SOAP_BASE_URL,
|
|
23
23
|
DgiiSoapClient,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
DGII_NCF_URL,
|
|
28
28
|
DGII_RNC_URL,
|
|
29
29
|
ScrapingClient
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-LRPZFX62.js";
|
|
31
31
|
import {
|
|
32
32
|
ECF_TYPE_NAMES,
|
|
33
33
|
validateEcf
|
package/dist/scraping.cjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunk6REHWG3Rcjs = require('./chunk-6REHWG3R.cjs');
|
|
8
8
|
require('./chunk-OSGHCQFA.cjs');
|
|
9
9
|
require('./chunk-WMFHPNFP.cjs');
|
|
10
10
|
require('./chunk-QH4BK5X3.cjs');
|
|
@@ -14,4 +14,4 @@ require('./chunk-NASWEFHN.cjs');
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
exports.DGII_NCF_URL =
|
|
17
|
+
exports.DGII_NCF_URL = _chunk6REHWG3Rcjs.DGII_NCF_URL; exports.DGII_RNC_URL = _chunk6REHWG3Rcjs.DGII_RNC_URL; exports.FORM_FIELDS = _chunk6REHWG3Rcjs.FORM_FIELDS; exports.ScrapingClient = _chunk6REHWG3Rcjs.ScrapingClient;
|
package/dist/scraping.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dgii-ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Librería TypeScript para validación e integración con la DGII de República Dominicana — RNC, Cédula, NCF, e-NCF",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -80,7 +80,8 @@
|
|
|
80
80
|
},
|
|
81
81
|
"sideEffects": false,
|
|
82
82
|
"files": [
|
|
83
|
-
"dist"
|
|
83
|
+
"dist",
|
|
84
|
+
"skills"
|
|
84
85
|
],
|
|
85
86
|
"scripts": {
|
|
86
87
|
"build": "tsup",
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dgii-ts
|
|
3
|
+
description: Write correct TypeScript or JavaScript with the dgii-ts library for Dominican Republic tax data from the DGII (Dirección General de Impuestos Internos). Covers validating RNC and cédula numbers, checking NCF and e-NCF (e-CF) invoice numbers, looking up taxpayers (contribuyentes) live, verifying an e-NCF's status, and importing the DGII_RNC.zip taxpayer registry. Use this whenever code touches RNC, cédula, NCF, e-NCF, comprobantes fiscales, facturación electrónica, or Dominican tax IDs, even if the user never names dgii-ts, and before writing any mod-11 or Luhn check or any DGII scraper by hand.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dgii-ts
|
|
7
|
+
|
|
8
|
+
dgii-ts validates Dominican tax identifiers offline and queries the DGII
|
|
9
|
+
online. Most mistakes with it come from three misunderstandings: treating
|
|
10
|
+
an offline `valid: true` as "DGII confirmed this", using the wrong entry
|
|
11
|
+
point for live queries, and handling errors in a way that either hides
|
|
12
|
+
real outages or hammers DGII's servers. This skill exists to prevent
|
|
13
|
+
those.
|
|
14
|
+
|
|
15
|
+
If the project does not depend on it yet: `npm install dgii-ts`
|
|
16
|
+
(Node 18 or later).
|
|
17
|
+
|
|
18
|
+
## Pick the entry point
|
|
19
|
+
|
|
20
|
+
| Need | Use | Network |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Is this RNC, cédula, NCF or e-NCF well formed? (forms, imports, and always before a live lookup) | `validateRnc`, `validateCedula`, `validateNcf`, `validateEcf` from `dgii-ts/validators` | none |
|
|
23
|
+
| Does DGII know this taxpayer, what is its name and status? Is this invoice number real? | one shared `DgiiClient` from `dgii-ts/client` | DGII web pages |
|
|
24
|
+
| Many RNCs at once, an offline copy of the registry, search by name | `downloadBulkFile` and `parseBulkFile` from `dgii-ts/bulk` | one ~23 MB download |
|
|
25
|
+
|
|
26
|
+
Three things the library also exports that you should not reach for:
|
|
27
|
+
|
|
28
|
+
- `DgiiSoapClient` (`dgii-ts/soap`) is deprecated. DGII shut the SOAP
|
|
29
|
+
service off in January 2025 and it currently answers without data.
|
|
30
|
+
- `ScrapingClient` (`dgii-ts/scraping`) is the low-level layer that
|
|
31
|
+
`DgiiClient` wraps. Used directly it has no retries and no circuit
|
|
32
|
+
breaker.
|
|
33
|
+
- Hand-written check digits or regexes. DGII has real identifiers that
|
|
34
|
+
fail the checksum; the validators carry a whitelist of them, so a
|
|
35
|
+
hand-rolled mod-11 or Luhn rejects real taxpayers.
|
|
36
|
+
|
|
37
|
+
## Offline validators
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { validateRnc, validateCedula, validateNcf, validateEcf } from 'dgii-ts/validators';
|
|
41
|
+
|
|
42
|
+
validateRnc('131-09819-3'); // { valid: true, formatted: '1-31-09819-3' }
|
|
43
|
+
validateCedula('00114272360'); // { valid: true, formatted: '001-1427236-0' }
|
|
44
|
+
validateNcf('b0100000001'); // { valid: true, type: 'CREDITO_FISCAL', serie: 'B01' }
|
|
45
|
+
validateEcf('E310000000001'); // { valid: true, type: 'CREDITO_FISCAL_ELECTRONICA', serie: 'E31' }
|
|
46
|
+
validateRnc('131098194'); // { valid: false }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **`valid: true` only means well formed.** It does not mean the RNC is
|
|
50
|
+
registered or active, or that the NCF was ever issued or belongs to that
|
|
51
|
+
issuer. Only `DgiiClient` can say that. Word UI messages accordingly
|
|
52
|
+
("invalid format" vs "not registered with DGII").
|
|
53
|
+
- RNC and cédula: every non-digit is stripped first, so dashes and spaces
|
|
54
|
+
are fine. RNC is 9 digits (mod-11), cédula is 11 digits (Luhn).
|
|
55
|
+
`formatted` is `X-XX-XXXXX-X` or `XXX-XXXXXXX-X`.
|
|
56
|
+
- NCF and e-NCF: only uppercased and trimmed; separators are **not**
|
|
57
|
+
stripped, so `B01-00000001` is invalid. NCF is `B` + 2-digit type + 8
|
|
58
|
+
digits (11 chars). e-NCF is `E` + 2-digit type + 10 digits (13 chars).
|
|
59
|
+
Unknown type codes are invalid. Valid NCF types: 01, 02, 03, 04, 11,
|
|
60
|
+
12, 13, 14, 15, 16, 17. Valid e-NCF types: 31, 32, 33, 34, 41, 43, 44,
|
|
61
|
+
45, 46, 47. `NCF_TYPE_NAMES` and `ECF_TYPE_NAMES` (exported from the
|
|
62
|
+
same subpath, keyed by the 2-digit code) give Spanish display names.
|
|
63
|
+
- Non-string input returns `{ valid: false }` and never throws, so the
|
|
64
|
+
validators are safe on raw form data.
|
|
65
|
+
- The result is a discriminated union: check `valid` before reading
|
|
66
|
+
`formatted`, `type` or `serie`.
|
|
67
|
+
- A Dominican taxpayer ID is either an RNC (companies, 9 digits) or a
|
|
68
|
+
cédula (people, 11 digits). A field that accepts "RNC o cédula" should
|
|
69
|
+
accept either validator's `valid: true`. `getContribuyente` accepts
|
|
70
|
+
both.
|
|
71
|
+
- Store the digits (`value.replace(/\D/g, '')`) and use `formatted` for
|
|
72
|
+
display.
|
|
73
|
+
|
|
74
|
+
## Live lookups with DgiiClient
|
|
75
|
+
|
|
76
|
+
Run it on the server. DGII's pages send no CORS headers, so a browser
|
|
77
|
+
cannot call them; use an API route, server action or backend job.
|
|
78
|
+
|
|
79
|
+
Create **one** client and reuse it:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// dgii.ts
|
|
83
|
+
import { DgiiClient } from 'dgii-ts/client';
|
|
84
|
+
|
|
85
|
+
export const dgii = new DgiiClient({ soapFallback: false });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Retry and circuit-breaker state live inside the instance. A new client per
|
|
89
|
+
request never trips its breaker, so during a DGII outage every request
|
|
90
|
+
keeps hitting DGII. `soapFallback` is `false` by default from dgii-ts
|
|
91
|
+
0.3.0; older versions default to `true`, so pass `false` explicitly. The
|
|
92
|
+
SOAP service is shut off: the fallback only adds a wasted request after
|
|
93
|
+
every failed lookup, and an empty SOAP reply would be reported as
|
|
94
|
+
`DgiiNotFoundError`, making an outage look like "not registered". Do not
|
|
95
|
+
turn it on.
|
|
96
|
+
|
|
97
|
+
Options and defaults: `timeout` 15000 ms (clamped to 1 to 120 s);
|
|
98
|
+
`retry: { maxRetries: 2, baseDelayMs: 500, maxDelayMs: 10000 }`
|
|
99
|
+
(exponential backoff with jitter); `circuitBreaker: { failureThreshold: 5,
|
|
100
|
+
recoveryTimeoutMs: 60000, successThreshold: 2 }`. Pass partial objects;
|
|
101
|
+
the rest keeps its default.
|
|
102
|
+
|
|
103
|
+
### Taxpayer: `getContribuyente(rncOrCedula)`
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import type { Contribuyente } from 'dgii-ts';
|
|
107
|
+
import { DgiiNotFoundError } from 'dgii-ts/errors';
|
|
108
|
+
|
|
109
|
+
async function findTaxpayer(digits: string): Promise<Contribuyente | null> {
|
|
110
|
+
try {
|
|
111
|
+
return await dgii.getContribuyente(digits);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
// Not registered with DGII: a normal answer, not an outage
|
|
114
|
+
if (err instanceof DgiiNotFoundError) return null;
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
A `Contribuyente` has `rnc` (digits only), `nombre`, `nombreComercial`,
|
|
121
|
+
`estado`, `categoria`, `esFacturadorElectronico`, and the optional
|
|
122
|
+
`actividadEconomica`, `regimenDePagos` and `administracionLocal`.
|
|
123
|
+
|
|
124
|
+
- `estado` is `'ACTIVO'` or `'INACTIVO'`. Every other DGII status
|
|
125
|
+
(suspended, cancelled, and so on) is reported as `'INACTIVO'`.
|
|
126
|
+
- An unregistered RNC or cédula **throws** `DgiiNotFoundError`. It is not
|
|
127
|
+
retried and does not count against the circuit breaker.
|
|
128
|
+
- Pass the digits you validated, not user input as typed.
|
|
129
|
+
|
|
130
|
+
### Invoice number: `getNCF(rncEmisor, ncf, options?)`
|
|
131
|
+
|
|
132
|
+
For a B-series NCF:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const r = await dgii.getNCF('131098193', 'B0100000001');
|
|
136
|
+
// found: { valid: true, rnc, ncf, nombreComercial? }
|
|
137
|
+
// not found: { valid: false, rnc: '', ncf: '' } (other fields undefined)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Unlike `getContribuyente`, a missing or invalid NCF does **not** throw; it
|
|
141
|
+
returns `valid: false`.
|
|
142
|
+
|
|
143
|
+
For an e-NCF (E-series), DGII needs the buyer's RNC **and** the 6-character
|
|
144
|
+
security code printed on the invoice. The types mark both optional because
|
|
145
|
+
B-series lookups ignore them, but without either one DGII refuses to answer
|
|
146
|
+
and `DgiiClient` throws `AllStrategiesFailedError` whose message contains
|
|
147
|
+
DGII's "es necesario completar el campo..." text.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const e = await dgii.getNCF('101010632', 'E310125217173', {
|
|
151
|
+
rncComprador: '131262414',
|
|
152
|
+
codigoSeguridad: 'KrOLI0',
|
|
153
|
+
});
|
|
154
|
+
// { valid: true, rnc: '101010632', ncf: 'E310125217173',
|
|
155
|
+
// rncComprador: '131262414', codigoSeguridad: 'KrOLI0',
|
|
156
|
+
// estado: 'Aceptado', montoTotal: 230677.74, totalItbis: 35188.13,
|
|
157
|
+
// fechaEmision: '2026-02-11', fechaFirma: '2026-02-11' }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- For an e-NCF, `valid: true` means DGII **found** it, not that it is
|
|
161
|
+
accepted. Check `estado`.
|
|
162
|
+
- A wrong security code gives `valid: false`.
|
|
163
|
+
- `montoTotal` and `totalItbis` are numbers; `fechaEmision` and
|
|
164
|
+
`fechaFirma` are strings exactly as DGII shows them.
|
|
165
|
+
- The library decides "e-NCF or not" with `validateEcf`, so run the
|
|
166
|
+
offline validator first and only pass the options for an e-NCF.
|
|
167
|
+
- DGII also accepts a foreign ID in place of the buyer's RNC, but the
|
|
168
|
+
library has no option for it.
|
|
169
|
+
|
|
170
|
+
## Errors
|
|
171
|
+
|
|
172
|
+
`DgiiClient` only ever throws two things:
|
|
173
|
+
|
|
174
|
+
| Error (`code`) | Meaning | What to do |
|
|
175
|
+
| --- | --- | --- |
|
|
176
|
+
| `DgiiNotFoundError` (`DGII_NOT_FOUND`) | `getContribuyente`: DGII has no such RNC or cédula | Tell the user it is not registered. Do not retry. |
|
|
177
|
+
| `AllStrategiesFailedError` (`DGII_ALL_STRATEGIES_FAILED`) | Everything else, after the built-in retries: network down, timeout, DGII HTTP error, circuit open, DGII page changed, missing e-NCF fields, empty input | Report "DGII is unavailable, try later" and log `err.errors` (one error per strategy tried; `err.cause` is the first). |
|
|
178
|
+
|
|
179
|
+
The underlying `DgiiConnectionError` (`DGII_CONNECTION_ERROR`) and
|
|
180
|
+
`DgiiServiceError` (`DGII_SERVICE_ERROR`, with optional `statusCode`) are
|
|
181
|
+
inside `err.errors`; they are only thrown directly by the low-level
|
|
182
|
+
clients. `BulkFormatError` (`DGII_BULK_FORMAT_ERROR`) comes from
|
|
183
|
+
`parseBulkFile`. All of them extend `DgiiError` and are exported from
|
|
184
|
+
`dgii-ts/errors` (and the root). Matching on `err.code` also works when
|
|
185
|
+
`instanceof` cannot, for example when an app loads the package both as
|
|
186
|
+
ESM and as CommonJS.
|
|
187
|
+
|
|
188
|
+
Do not wrap calls in your own retry loop. The client already retries
|
|
189
|
+
connection errors and HTTP 5xx with backoff, and deliberately does not
|
|
190
|
+
retry 4xx or unexpected pages, because repeating those will not help.
|
|
191
|
+
|
|
192
|
+
Because empty input and missing e-NCF fields also surface as
|
|
193
|
+
`AllStrategiesFailedError`, validate before calling. Otherwise a
|
|
194
|
+
programming error looks like a DGII outage.
|
|
195
|
+
|
|
196
|
+
## Be gentle with DGII
|
|
197
|
+
|
|
198
|
+
DGII publishes no rate limit and these are its public web pages. Each
|
|
199
|
+
lookup is two HTTP requests (load the form, submit it).
|
|
200
|
+
|
|
201
|
+
- Validate offline first. Never send DGII an identifier that fails the
|
|
202
|
+
validator.
|
|
203
|
+
- Share one client (above).
|
|
204
|
+
- Cache results. Taxpayer names and statuses rarely change within a day.
|
|
205
|
+
- For batches, go one at a time or with very low concurrency. For more
|
|
206
|
+
than a few dozen RNCs, use the bulk file instead of live lookups.
|
|
207
|
+
- Keep the default retry and circuit-breaker settings unless you have a
|
|
208
|
+
measured reason; raising retries multiplies load during an outage.
|
|
209
|
+
|
|
210
|
+
## Bulk registry: DGII_RNC.zip
|
|
211
|
+
|
|
212
|
+
Node only (it uses `node:https` and `node:fs`).
|
|
213
|
+
|
|
214
|
+
The library downloads the ZIP but does not unzip it, and it has no zip
|
|
215
|
+
dependency. Extract the single entry `TMP/DGII_RNC.TXT` with the zip tool
|
|
216
|
+
the project already uses, or `adm-zip` (`npm install adm-zip`, plus
|
|
217
|
+
`@types/adm-zip` for TypeScript):
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { writeFile } from 'node:fs/promises';
|
|
221
|
+
import AdmZip from 'adm-zip';
|
|
222
|
+
import { downloadBulkFile, parseBulkFile } from 'dgii-ts/bulk';
|
|
223
|
+
|
|
224
|
+
const zipPath = await downloadBulkFile({ outputDir: '/var/data/dgii' });
|
|
225
|
+
|
|
226
|
+
const entry = new AdmZip(zipPath).getEntry('TMP/DGII_RNC.TXT');
|
|
227
|
+
if (!entry) throw new Error('DGII_RNC.zip no longer contains TMP/DGII_RNC.TXT');
|
|
228
|
+
const txtPath = '/var/data/dgii/DGII_RNC.TXT';
|
|
229
|
+
await writeFile(txtPath, entry.getData());
|
|
230
|
+
|
|
231
|
+
const rows = await parseBulkFile({ filePath: txtPath });
|
|
232
|
+
const byRnc = new Map(rows.map((row) => [row.rnc, row]));
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- `downloadBulkFile` writes `<outputDir>/DGII_RNC.zip` (overwriting it) and
|
|
236
|
+
returns that path, relative if `outputDir` was relative. `timeout`
|
|
237
|
+
defaults to 60 s (clamped 5 s to 10 min). It rejects with a plain
|
|
238
|
+
`Error`, not a `DgiiError`.
|
|
239
|
+
- The ZIP holds one file, `TMP/DGII_RNC.TXT`: about 90 MB, pipe-delimited,
|
|
240
|
+
latin-1. The parser defaults to `latin1`; do not pass `utf8` or names
|
|
241
|
+
like MUÑOZ get corrupted.
|
|
242
|
+
- `parseBulkFile` returns the whole registry as one array (about 790,000
|
|
243
|
+
rows in September 2026, roughly 0.5 GB of memory at peak). Load it in a
|
|
244
|
+
scheduled job and write it to a database or index; do not parse it per
|
|
245
|
+
request.
|
|
246
|
+
- To check a list (suppliers, customers), validate each ID with
|
|
247
|
+
`validateRnc` **or** `validateCedula` (people appear by cédula), report
|
|
248
|
+
the ones that fail as bad format rather than "not registered", and look
|
|
249
|
+
the rest up by their digits.
|
|
250
|
+
- Each row: `rnc` (bare digits: 9 for an RNC, 11 for a cédula), `nombre`,
|
|
251
|
+
`nombreComercial`, `actividad` (DGII truncates it), `estado`, `regimen`,
|
|
252
|
+
`fechaConstitucion` (`dd/mm/yyyy` or empty).
|
|
253
|
+
- Bulk `estado` is DGII's full vocabulary, `DGII_ESTADOS`: `ACTIVO`,
|
|
254
|
+
`SUSPENDIDO`, `DADO DE BAJA`, `CESE TEMPORAL`, `ANULADO`, `RECHAZADO`.
|
|
255
|
+
That is not comparable with `getContribuyente`'s `ACTIVO`/`INACTIVO`.
|
|
256
|
+
- Rows without exactly 11 columns are skipped. If most sampled rows have
|
|
257
|
+
an unknown `estado`, it throws `BulkFormatError`: DGII changed the
|
|
258
|
+
layout, so fail loudly rather than import garbage.
|
|
259
|
+
|
|
260
|
+
## Imports
|
|
261
|
+
|
|
262
|
+
| Subpath | Contents | Runs in |
|
|
263
|
+
| --- | --- | --- |
|
|
264
|
+
| `dgii-ts/validators` | the four validators, `NCF_TYPE_NAMES`, `ECF_TYPE_NAMES` | anywhere, including the browser |
|
|
265
|
+
| `dgii-ts/client` | `DgiiClient` | server (Node 18+) |
|
|
266
|
+
| `dgii-ts/errors` | the error classes | anywhere |
|
|
267
|
+
| `dgii-ts/bulk` | `downloadBulkFile`, `parseBulkFile`, `DGII_ESTADOS`, `DGII_BULK_URL` | Node |
|
|
268
|
+
| `dgii-ts` | everything above plus the low-level clients | server |
|
|
269
|
+
|
|
270
|
+
Types (`ValidationResult`, `NcfValidationResult`, `Contribuyente`,
|
|
271
|
+
`NcfQueryOptions`, `NcfQueryResult`, `BulkContribuyente`, `ClientOptions`)
|
|
272
|
+
are exported from the root. Both ESM `import` and CommonJS `require` work.
|
|
273
|
+
In client-side bundles import from `dgii-ts/validators` only.
|