dgii-ts 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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +192 -0
  3. package/README.md +196 -0
  4. package/dist/bulk.cjs +11 -0
  5. package/dist/bulk.d.cts +30 -0
  6. package/dist/bulk.d.ts +30 -0
  7. package/dist/bulk.js +11 -0
  8. package/dist/chunk-2ST6QKHA.cjs +1 -0
  9. package/dist/chunk-4OYLCOUL.cjs +201 -0
  10. package/dist/chunk-4TKAQ4WV.js +198 -0
  11. package/dist/chunk-53A7IG4Y.js +23 -0
  12. package/dist/chunk-6CIP7YH6.cjs +198 -0
  13. package/dist/chunk-7RJWFPCZ.cjs +44 -0
  14. package/dist/chunk-D26S6EXD.js +12 -0
  15. package/dist/chunk-DMGDJEUY.js +44 -0
  16. package/dist/chunk-ERBXSS64.js +0 -0
  17. package/dist/chunk-EZKSB553.js +0 -0
  18. package/dist/chunk-FZ6QDTC4.cjs +98 -0
  19. package/dist/chunk-HGE4QZ3H.js +281 -0
  20. package/dist/chunk-HR4DCHH7.js +201 -0
  21. package/dist/chunk-KIS7MVIW.js +98 -0
  22. package/dist/chunk-QH4BK5X3.cjs +12 -0
  23. package/dist/chunk-QOSGH7F5.cjs +1 -0
  24. package/dist/chunk-SUSDEKID.cjs +784 -0
  25. package/dist/chunk-TZC6RSWL.js +784 -0
  26. package/dist/chunk-VJ4ZQQIQ.cjs +281 -0
  27. package/dist/chunk-WY6V4Y7S.cjs +23 -0
  28. package/dist/client.cjs +17 -0
  29. package/dist/client.d.cts +95 -0
  30. package/dist/client.d.ts +95 -0
  31. package/dist/client.js +17 -0
  32. package/dist/errors.cjs +14 -0
  33. package/dist/errors.d.cts +40 -0
  34. package/dist/errors.d.ts +40 -0
  35. package/dist/errors.js +14 -0
  36. package/dist/index-CS-7YY2y.d.cts +58 -0
  37. package/dist/index-CS-7YY2y.d.ts +58 -0
  38. package/dist/index.cjs +66 -0
  39. package/dist/index.d.cts +7 -0
  40. package/dist/index.d.ts +7 -0
  41. package/dist/index.js +66 -0
  42. package/dist/scraping.cjs +16 -0
  43. package/dist/scraping.d.cts +47 -0
  44. package/dist/scraping.d.ts +47 -0
  45. package/dist/scraping.js +16 -0
  46. package/dist/soap.cjs +23 -0
  47. package/dist/soap.d.cts +38 -0
  48. package/dist/soap.d.ts +38 -0
  49. package/dist/soap.js +23 -0
  50. package/dist/validators.cjs +17 -0
  51. package/dist/validators.d.cts +55 -0
  52. package/dist/validators.d.ts +55 -0
  53. package/dist/validators.js +17 -0
  54. package/package.json +109 -0
@@ -0,0 +1,44 @@
1
+ // src/errors/index.ts
2
+ var DgiiError = class extends Error {
3
+ constructor(message, code, options) {
4
+ super(message, options);
5
+ this.name = "DgiiError";
6
+ this.code = code;
7
+ }
8
+ };
9
+ var DgiiConnectionError = class extends DgiiError {
10
+ constructor(message, options) {
11
+ super(message, "DGII_CONNECTION_ERROR", options);
12
+ this.name = "DgiiConnectionError";
13
+ }
14
+ };
15
+ var DgiiNotFoundError = class extends DgiiError {
16
+ constructor(message) {
17
+ super(message, "DGII_NOT_FOUND");
18
+ this.name = "DgiiNotFoundError";
19
+ }
20
+ };
21
+ var DgiiServiceError = class extends DgiiError {
22
+ constructor(message, options) {
23
+ super(message, "DGII_SERVICE_ERROR", options);
24
+ this.name = "DgiiServiceError";
25
+ this.statusCode = options?.statusCode;
26
+ }
27
+ };
28
+ var AllStrategiesFailedError = class extends DgiiError {
29
+ constructor(message, errors) {
30
+ super(message, "DGII_ALL_STRATEGIES_FAILED", {
31
+ cause: errors[0]
32
+ });
33
+ this.name = "AllStrategiesFailedError";
34
+ this.errors = errors;
35
+ }
36
+ };
37
+
38
+ export {
39
+ DgiiError,
40
+ DgiiConnectionError,
41
+ DgiiNotFoundError,
42
+ DgiiServiceError,
43
+ AllStrategiesFailedError
44
+ };
File without changes
File without changes
@@ -0,0 +1,98 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
+
3
+ var _chunkQH4BK5X3cjs = require('./chunk-QH4BK5X3.cjs');
4
+
5
+ // src/bulk/downloader.ts
6
+ var _https = require('https');
7
+ var _fs = require('fs');
8
+ var _promises = require('stream/promises');
9
+ var _path = require('path');
10
+
11
+ // src/bulk/types.ts
12
+ var DGII_BULK_URL = "https://dgii.gov.do/app/WebApps/Consultas/RNC/DGII_RNC.zip";
13
+
14
+ // src/bulk/downloader.ts
15
+ async function downloadBulkFile(options) {
16
+ const timeout = Math.min(
17
+ Math.max(_nullishCoalesce(options.timeout, () => ( 6e4)), 5e3),
18
+ 6e5
19
+ );
20
+ const destPath = _path.join.call(void 0, options.outputDir, "DGII_RNC.zip");
21
+ return new Promise((resolve, reject) => {
22
+ const req = _https.get.call(void 0, DGII_BULK_URL, (response) => {
23
+ const status = _nullishCoalesce(response.statusCode, () => ( 0));
24
+ if ([301, 302, 307, 308].includes(status)) {
25
+ const redirectUrl = response.headers["location"];
26
+ if (redirectUrl) {
27
+ response.resume();
28
+ const req2 = _https.get.call(void 0, redirectUrl, (res2) => {
29
+ handleResponse(res2, destPath, resolve, reject);
30
+ });
31
+ req2.on("error", (err) => reject(
32
+ new Error("Error de conexi\xF3n al descargar archivo masivo", {
33
+ cause: err
34
+ })
35
+ ));
36
+ return;
37
+ }
38
+ }
39
+ handleResponse(response, destPath, resolve, reject);
40
+ });
41
+ req.on("error", (err) => reject(
42
+ new Error("Error de conexi\xF3n al descargar archivo masivo", {
43
+ cause: err
44
+ })
45
+ ));
46
+ req.setTimeout(timeout, () => {
47
+ req.destroy();
48
+ reject(new Error(
49
+ `Timeout de ${timeout}ms excedido al descargar archivo masivo`
50
+ ));
51
+ });
52
+ });
53
+ }
54
+ function handleResponse(response, destPath, resolve, reject) {
55
+ if (response.statusCode !== 200) {
56
+ response.resume();
57
+ reject(new Error(
58
+ `La DGII respondi\xF3 con HTTP ${_nullishCoalesce(response.statusCode, () => ( "desconocido"))} al descargar el archivo masivo`
59
+ ));
60
+ return;
61
+ }
62
+ const fileStream = _fs.createWriteStream.call(void 0, destPath);
63
+ _promises.pipeline.call(void 0, response, fileStream).then(() => resolve(destPath)).catch((err) => reject(
64
+ new Error("Error al escribir archivo masivo", { cause: err })
65
+ ));
66
+ }
67
+
68
+ // src/bulk/parser.ts
69
+
70
+ var _readline = require('readline');
71
+ async function parseBulkFile(options) {
72
+ const results = [];
73
+ const rl = _readline.createInterface.call(void 0, {
74
+ input: _fs.createReadStream.call(void 0, options.filePath, { encoding: "utf-8" }),
75
+ crlfDelay: Infinity
76
+ });
77
+ for await (const line of rl) {
78
+ if (line.trim() === "") continue;
79
+ const fields = line.split("|");
80
+ if (fields.length < 7) continue;
81
+ results.push({
82
+ rnc: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, _nullishCoalesce(fields[0], () => ( ""))),
83
+ nombre: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, _nullishCoalesce(fields[1], () => ( ""))),
84
+ nombreComercial: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, _nullishCoalesce(fields[2], () => ( ""))),
85
+ actividad: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, _nullishCoalesce(fields[6], () => ( ""))),
86
+ estado: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, _nullishCoalesce(fields[5], () => ( ""))),
87
+ regimen: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, _nullishCoalesce(fields[4], () => ( ""))),
88
+ fechaConstitucion: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, _nullishCoalesce(fields[7], () => ( "")))
89
+ });
90
+ }
91
+ return results;
92
+ }
93
+
94
+
95
+
96
+
97
+
98
+ exports.DGII_BULK_URL = DGII_BULK_URL; exports.downloadBulkFile = downloadBulkFile; exports.parseBulkFile = parseBulkFile;
@@ -0,0 +1,281 @@
1
+ import {
2
+ wrapFetchError
3
+ } from "./chunk-53A7IG4Y.js";
4
+ import {
5
+ collapseSpaces,
6
+ stripNonDigits
7
+ } from "./chunk-D26S6EXD.js";
8
+ import {
9
+ DgiiNotFoundError,
10
+ DgiiServiceError
11
+ } from "./chunk-DMGDJEUY.js";
12
+
13
+ // src/scraping/endpoints.ts
14
+ var DGII_RNC_URL = "https://dgii.gov.do/app/WebApps/ConsultasWeb2/ConsultasWeb/consultas/rnc.aspx";
15
+ var DGII_NCF_URL = "https://dgii.gov.do/app/WebApps/ConsultasWeb2/ConsultasWeb/consultas/ncf.aspx";
16
+ var FORM_FIELDS = /* @__PURE__ */ Object.freeze({
17
+ viewState: "__VIEWSTATE",
18
+ viewStateGenerator: "__VIEWSTATEGENERATOR",
19
+ eventValidation: "__EVENTVALIDATION",
20
+ rncInput: "ctl00$cphMain$txtRNCCedula",
21
+ rncSubmit: "ctl00$cphMain$btnBuscarPorRNC",
22
+ ncfRncInput: "ctl00$cphMain$txtRNC",
23
+ ncfInput: "ctl00$cphMain$txtNCF",
24
+ ncfSubmit: "ctl00$cphMain$btnConsultar"
25
+ });
26
+
27
+ // src/scraping/html-parser.ts
28
+ function extractViewStateTokens(html) {
29
+ const viewState = extractInputValue(html, "__VIEWSTATE");
30
+ const viewStateGenerator = extractInputValue(html, "__VIEWSTATEGENERATOR");
31
+ const eventValidation = extractInputValue(html, "__EVENTVALIDATION");
32
+ if (!viewState || !eventValidation) {
33
+ throw new DgiiServiceError(
34
+ "No se encontraron tokens ViewState en la p\xE1gina de la DGII"
35
+ );
36
+ }
37
+ return { viewState, viewStateGenerator, eventValidation };
38
+ }
39
+ function parseContribuyenteHtml(html) {
40
+ const infoStart = html.indexOf("cphMain_lblInformacion");
41
+ if (infoStart !== -1) {
42
+ const infoEnd = html.indexOf("</span>", infoStart);
43
+ if (infoEnd !== -1) {
44
+ const infoBlock = html.slice(infoStart, infoEnd);
45
+ if (infoBlock.includes("no se encuentra")) {
46
+ throw new DgiiNotFoundError(
47
+ "RNC o c\xE9dula no encontrado en el registro de la DGII"
48
+ );
49
+ }
50
+ }
51
+ }
52
+ const tableId = "cphMain_dvDatosContribuyentes";
53
+ const tableStart = html.indexOf(tableId);
54
+ if (tableStart === -1) {
55
+ throw new DgiiServiceError(
56
+ "Formato de respuesta HTML inesperado: tabla de resultados no encontrada"
57
+ );
58
+ }
59
+ const tableEnd = html.indexOf("</table>", tableStart);
60
+ if (tableEnd === -1) {
61
+ throw new DgiiServiceError(
62
+ "Formato de respuesta HTML inesperado: tabla incompleta"
63
+ );
64
+ }
65
+ const tableHtml = html.slice(tableStart, tableEnd);
66
+ const fields = extractTableFields(tableHtml);
67
+ const rnc = fields.get("Cedula/RNC") ?? fields.get("RNC") ?? "";
68
+ const nombre = fields.get("Nombre/Razon Social") ?? fields.get("Nombre / Razon Social") ?? "";
69
+ const nombreComercial = fields.get("Nombre Comercial") ?? "";
70
+ const categoria = fields.get("Categoria") ?? "";
71
+ const rawEstado = fields.get("Estado") ?? "";
72
+ const actividadEconomica = fields.get("Actividad Economica") ?? void 0;
73
+ const regimenDePagos = fields.get("Regimen de pagos") ?? void 0;
74
+ const administracionLocal = fields.get("Administracion Local") ?? void 0;
75
+ return {
76
+ rnc: stripNonDigits(rnc),
77
+ nombre: collapseSpaces(nombre),
78
+ nombreComercial: collapseSpaces(nombreComercial),
79
+ estado: rawEstado.toUpperCase().includes("ACTIVO") ? "ACTIVO" : "INACTIVO",
80
+ categoria: collapseSpaces(categoria),
81
+ actividadEconomica: actividadEconomica ? collapseSpaces(actividadEconomica) : void 0,
82
+ regimenDePagos: regimenDePagos ? collapseSpaces(regimenDePagos) : void 0,
83
+ administracionLocal: administracionLocal ? collapseSpaces(administracionLocal) : void 0
84
+ };
85
+ }
86
+ function parseNcfHtml(html) {
87
+ const infoStart = html.indexOf("cphMain_lblInformacion");
88
+ if (infoStart !== -1) {
89
+ const infoEnd = html.indexOf("</span>", infoStart);
90
+ if (infoEnd !== -1) {
91
+ const infoBlock = html.slice(infoStart, infoEnd);
92
+ if (infoBlock.includes("no es v") || infoBlock.includes("no se encuentra") || infoBlock.includes("no existe")) {
93
+ return { valid: false, rnc: "", ncf: "", nombreComercial: void 0 };
94
+ }
95
+ }
96
+ }
97
+ const tableId = "cphMain_dvDatosComprobante";
98
+ const altTableId = "cphMain_dvDatosContribuyentes";
99
+ let tableStart = html.indexOf(tableId);
100
+ if (tableStart === -1) {
101
+ tableStart = html.indexOf(altTableId);
102
+ }
103
+ if (tableStart === -1) {
104
+ return { valid: false, rnc: "", ncf: "", nombreComercial: void 0 };
105
+ }
106
+ const tableEnd = html.indexOf("</table>", tableStart);
107
+ if (tableEnd === -1) {
108
+ return { valid: false, rnc: "", ncf: "", nombreComercial: void 0 };
109
+ }
110
+ const tableHtml = html.slice(tableStart, tableEnd);
111
+ const fields = extractTableFields(tableHtml);
112
+ const rnc = fields.get("RNC") ?? fields.get("Cedula/RNC") ?? "";
113
+ const ncf = fields.get("NCF") ?? fields.get("No. Comprobante Fiscal") ?? "";
114
+ const nombreComercial = fields.get("Nombre Comercial") ?? fields.get("Nombre / Razon Social") ?? void 0;
115
+ return {
116
+ valid: true,
117
+ rnc: stripNonDigits(rnc),
118
+ ncf: collapseSpaces(ncf),
119
+ nombreComercial: nombreComercial ? collapseSpaces(nombreComercial) : void 0
120
+ };
121
+ }
122
+ function extractInputValue(html, name) {
123
+ let idx = html.indexOf(`id="${name}"`);
124
+ if (idx === -1) {
125
+ idx = html.indexOf(`name="${name}"`);
126
+ }
127
+ if (idx === -1) return "";
128
+ const tagStart = html.lastIndexOf("<", idx);
129
+ if (tagStart === -1) return "";
130
+ const tagEnd = html.indexOf(">", idx);
131
+ if (tagEnd === -1) return "";
132
+ const tag = html.slice(tagStart, tagEnd + 1);
133
+ const valueMatch = tag.match(/value="([^"]*)"/);
134
+ return valueMatch?.[1] ?? "";
135
+ }
136
+ var BOLD_STYLE_PATTERN = /<td[^>]*style="font-weight:bold;"[^>]*>(.*?)<\/td>\s*<td[^>]*>(.*?)<\/td>/gi;
137
+ var BOLD_TAG_PATTERN = /<td><b>(.*?)<\/b><\/td>\s*<td>(.*?)<\/td>/gi;
138
+ function extractTableFields(tableHtml) {
139
+ const fields = /* @__PURE__ */ new Map();
140
+ for (const match of tableHtml.matchAll(BOLD_STYLE_PATTERN)) {
141
+ const label = normalizeLabel(match[1] ?? "");
142
+ const value = stripHtmlTags(match[2] ?? "");
143
+ if (label) fields.set(label, value);
144
+ }
145
+ if (fields.size === 0) {
146
+ for (const match of tableHtml.matchAll(BOLD_TAG_PATTERN)) {
147
+ const label = normalizeLabel(match[1] ?? "");
148
+ const value = stripHtmlTags(match[2] ?? "");
149
+ if (label) fields.set(label, value);
150
+ }
151
+ }
152
+ return fields;
153
+ }
154
+ var ACCENT_PATTERN = /[\u0300-\u036f]/g;
155
+ function normalizeLabel(raw) {
156
+ return decodeHtmlEntities(stripHtmlTags(raw)).replace(/\s+/g, " ").trim().normalize("NFD").replace(ACCENT_PATTERN, "");
157
+ }
158
+ function stripHtmlTags(str) {
159
+ return str.replace(/<[^>]*>/g, "").trim();
160
+ }
161
+ function decodeHtmlEntities(str) {
162
+ return str.replace(
163
+ /&#(\d+);/g,
164
+ (_, code) => String.fromCharCode(parseInt(code, 10))
165
+ ).replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&nbsp;/g, " ");
166
+ }
167
+
168
+ // src/scraping/client.ts
169
+ var DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
170
+ var ScrapingClient = class {
171
+ constructor(options) {
172
+ this._rncUrl = options?.baseRncUrl ?? DGII_RNC_URL;
173
+ this._ncfUrl = options?.baseNcfUrl ?? DGII_NCF_URL;
174
+ this._timeout = Math.min(
175
+ Math.max(options?.timeout ?? 15e3, 1e3),
176
+ 12e4
177
+ );
178
+ }
179
+ /**
180
+ * Consulta datos de un contribuyente por RNC o cédula.
181
+ */
182
+ async getContribuyente(rnc) {
183
+ if (typeof rnc !== "string" || rnc.trim() === "") {
184
+ throw new DgiiServiceError("El par\xE1metro rnc es requerido");
185
+ }
186
+ const tokens = await this._fetchTokens(this._rncUrl);
187
+ const body = buildFormBody([
188
+ ["__EVENTTARGET", ""],
189
+ ["__EVENTARGUMENT", ""],
190
+ [FORM_FIELDS.viewState, tokens.viewState],
191
+ [FORM_FIELDS.viewStateGenerator, tokens.viewStateGenerator],
192
+ [FORM_FIELDS.eventValidation, tokens.eventValidation],
193
+ [FORM_FIELDS.rncInput, rnc.trim()],
194
+ ["ctl00$cphMain$txtRazonSocial", ""],
195
+ ["ctl00$cphMain$hidActiveTab", "rnc"],
196
+ [FORM_FIELDS.rncSubmit, "BUSCAR"]
197
+ ]);
198
+ const html = await this._post(this._rncUrl, body);
199
+ return parseContribuyenteHtml(html);
200
+ }
201
+ /**
202
+ * Valida un comprobante fiscal (NCF) contra la DGII.
203
+ */
204
+ async getNCF(rnc, ncf) {
205
+ if (typeof rnc !== "string" || rnc.trim() === "") {
206
+ throw new DgiiServiceError("El par\xE1metro rnc es requerido");
207
+ }
208
+ if (typeof ncf !== "string" || ncf.trim() === "") {
209
+ throw new DgiiServiceError("El par\xE1metro ncf es requerido");
210
+ }
211
+ const tokens = await this._fetchTokens(this._ncfUrl);
212
+ const body = buildFormBody([
213
+ [FORM_FIELDS.viewState, tokens.viewState],
214
+ [FORM_FIELDS.viewStateGenerator, tokens.viewStateGenerator],
215
+ [FORM_FIELDS.eventValidation, tokens.eventValidation],
216
+ [FORM_FIELDS.ncfRncInput, rnc.trim()],
217
+ [FORM_FIELDS.ncfInput, ncf.trim()],
218
+ [FORM_FIELDS.ncfSubmit, "Buscar"]
219
+ ]);
220
+ const html = await this._post(this._ncfUrl, body);
221
+ return parseNcfHtml(html);
222
+ }
223
+ async _fetchTokens(url) {
224
+ let response;
225
+ try {
226
+ response = await fetch(url, {
227
+ headers: { "User-Agent": DEFAULT_USER_AGENT },
228
+ signal: AbortSignal.timeout(this._timeout)
229
+ });
230
+ } catch (error) {
231
+ throw this._wrapFetchError(error);
232
+ }
233
+ if (!response.ok) {
234
+ throw new DgiiServiceError(
235
+ `La DGII respondi\xF3 con HTTP ${response.status} al obtener la p\xE1gina`,
236
+ { statusCode: response.status }
237
+ );
238
+ }
239
+ const html = await response.text();
240
+ return extractViewStateTokens(html);
241
+ }
242
+ async _post(url, body) {
243
+ let response;
244
+ try {
245
+ response = await fetch(url, {
246
+ method: "POST",
247
+ headers: {
248
+ "User-Agent": DEFAULT_USER_AGENT,
249
+ "Content-Type": "application/x-www-form-urlencoded",
250
+ "Referer": url
251
+ },
252
+ body,
253
+ signal: AbortSignal.timeout(this._timeout)
254
+ });
255
+ } catch (error) {
256
+ throw this._wrapFetchError(error);
257
+ }
258
+ if (!response.ok) {
259
+ throw new DgiiServiceError(
260
+ `La DGII respondi\xF3 con HTTP ${response.status}`,
261
+ { statusCode: response.status }
262
+ );
263
+ }
264
+ return response.text();
265
+ }
266
+ _wrapFetchError(error) {
267
+ return wrapFetchError(error, this._timeout);
268
+ }
269
+ };
270
+ function buildFormBody(fields) {
271
+ return fields.map(
272
+ ([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`.replace(/%24/g, "$")
273
+ ).join("&");
274
+ }
275
+
276
+ export {
277
+ DGII_RNC_URL,
278
+ DGII_NCF_URL,
279
+ FORM_FIELDS,
280
+ ScrapingClient
281
+ };
@@ -0,0 +1,201 @@
1
+ import {
2
+ wrapFetchError
3
+ } from "./chunk-53A7IG4Y.js";
4
+ import {
5
+ collapseSpaces,
6
+ stripNonDigits
7
+ } from "./chunk-D26S6EXD.js";
8
+ import {
9
+ DgiiNotFoundError,
10
+ DgiiServiceError
11
+ } from "./chunk-DMGDJEUY.js";
12
+
13
+ // src/soap/endpoints.ts
14
+ var DGII_SOAP_BASE_URL = "https://dgii.gov.do/wsMovilDGII/WSMovilDGII.asmx";
15
+ var SOAP_ACTIONS = /* @__PURE__ */ Object.freeze({
16
+ getContribuyentes: "http://dgii.gov.do/GetContribuyentes",
17
+ getNCF: "http://dgii.gov.do/GetNCF"
18
+ });
19
+
20
+ // src/soap/xml.ts
21
+ function escapeXml(value) {
22
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
23
+ }
24
+ function extractTagContent(xml, tagName) {
25
+ const openTag = `<${tagName}>`;
26
+ const closeTag = `</${tagName}>`;
27
+ const start = xml.indexOf(openTag);
28
+ if (start === -1) return void 0;
29
+ const contentStart = start + openTag.length;
30
+ const end = xml.indexOf(closeTag, contentStart);
31
+ if (end === -1) return void 0;
32
+ return xml.slice(contentStart, end);
33
+ }
34
+
35
+ // src/soap/envelopes.ts
36
+ var SOAP_HEADER = '<?xml version="1.0" encoding="utf-8"?><soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"><soap12:Body>';
37
+ var SOAP_FOOTER = "</soap12:Body></soap12:Envelope>";
38
+ function buildGetContribuyentesEnvelope(value) {
39
+ return SOAP_HEADER + `<GetContribuyentes xmlns="http://dgii.gov.do/"><value>${escapeXml(value)}</value><patronBusqueda>0</patronBusqueda><inicioFilas>1</inicioFilas><filaFilas>1</filaFilas><IMEI></IMEI></GetContribuyentes>` + SOAP_FOOTER;
40
+ }
41
+ function buildGetNcfEnvelope(rnc, ncf) {
42
+ return SOAP_HEADER + `<GetNCF xmlns="http://dgii.gov.do/"><RNC>${escapeXml(rnc)}</RNC><NCF>${escapeXml(ncf)}</NCF><IMEI></IMEI></GetNCF>` + SOAP_FOOTER;
43
+ }
44
+
45
+ // src/soap/parser.ts
46
+ function sanitizeJson(raw) {
47
+ return raw.replace(/\n/g, "\\n").replace(/\t/g, "\\t").replace(/\r/g, "\\r");
48
+ }
49
+ function parseContribuyenteResponse(responseXml) {
50
+ const raw = extractTagContent(responseXml, "GetContribuyentesResult");
51
+ if (raw === void 0) {
52
+ throw new DgiiServiceError(
53
+ "Respuesta SOAP sin GetContribuyentesResult"
54
+ );
55
+ }
56
+ const trimmed = raw.trim();
57
+ if (trimmed === "0" || trimmed === "") {
58
+ throw new DgiiNotFoundError(
59
+ "RNC o c\xE9dula no encontrado en el registro de la DGII"
60
+ );
61
+ }
62
+ const firstResult = trimmed.split("@@@")[0];
63
+ const sanitized = sanitizeJson(firstResult);
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(sanitized);
67
+ } catch {
68
+ throw new DgiiServiceError(
69
+ "Respuesta de la DGII no es JSON v\xE1lido"
70
+ );
71
+ }
72
+ if (typeof parsed !== "object" || parsed === null || typeof parsed["RGE_RUC"] !== "string") {
73
+ throw new DgiiServiceError(
74
+ "Formato de respuesta de la DGII inesperado"
75
+ );
76
+ }
77
+ const data = parsed;
78
+ return {
79
+ rnc: stripNonDigits(String(data["RGE_RUC"] ?? "")),
80
+ nombre: collapseSpaces(String(data["RGE_NOMBRE"] ?? "")),
81
+ nombreComercial: collapseSpaces(String(data["NOMBRE_COMERCIAL"] ?? "")),
82
+ estado: String(data["ESTATUS"] ?? "") === "2" ? "ACTIVO" : "INACTIVO",
83
+ categoria: String(data["CATEGORIA"] ?? "")
84
+ };
85
+ }
86
+ function parseNcfResponse(responseXml) {
87
+ const raw = extractTagContent(responseXml, "GetNCFResult");
88
+ if (raw === void 0) {
89
+ throw new DgiiServiceError("Respuesta SOAP sin GetNCFResult");
90
+ }
91
+ const trimmed = raw.trim();
92
+ if (trimmed === "0" || trimmed === "") {
93
+ return { valid: false, rnc: "", ncf: "", nombreComercial: void 0 };
94
+ }
95
+ const sanitized = sanitizeJson(trimmed);
96
+ let parsed;
97
+ try {
98
+ parsed = JSON.parse(sanitized);
99
+ } catch {
100
+ throw new DgiiServiceError(
101
+ "Respuesta de la DGII no es JSON v\xE1lido"
102
+ );
103
+ }
104
+ if (typeof parsed !== "object" || parsed === null) {
105
+ throw new DgiiServiceError(
106
+ "Formato de respuesta de la DGII inesperado"
107
+ );
108
+ }
109
+ const data = parsed;
110
+ const esValido = Boolean(data["ES_VALIDO"]);
111
+ return {
112
+ valid: esValido,
113
+ rnc: String(data["RNC"] ?? ""),
114
+ ncf: String(data["NCF"] ?? ""),
115
+ nombreComercial: data["NOMBRE_COMERCIAL"] ? collapseSpaces(String(data["NOMBRE_COMERCIAL"])) : void 0
116
+ };
117
+ }
118
+
119
+ // src/soap/client.ts
120
+ var DgiiSoapClient = class {
121
+ constructor(options) {
122
+ const baseUrl = options?.baseUrl ?? DGII_SOAP_BASE_URL;
123
+ if (!baseUrl.startsWith("https://")) {
124
+ throw new Error("baseUrl debe usar HTTPS");
125
+ }
126
+ this._options = {
127
+ timeout: Math.min(Math.max(options?.timeout ?? 1e4, 1e3), 12e4),
128
+ baseUrl
129
+ };
130
+ }
131
+ async _fetch(soapAction, body) {
132
+ const url = this._options.baseUrl;
133
+ const timeout = this._options.timeout;
134
+ let response;
135
+ try {
136
+ response = await fetch(url, {
137
+ method: "POST",
138
+ headers: {
139
+ "Content-Type": `application/soap+xml; charset=utf-8; action="${soapAction}"`
140
+ },
141
+ body,
142
+ signal: AbortSignal.timeout(timeout)
143
+ });
144
+ } catch (error) {
145
+ throw wrapFetchError(error, timeout);
146
+ }
147
+ const contentLength = response.headers.get("content-length");
148
+ if (contentLength && parseInt(contentLength, 10) > 1048576) {
149
+ throw new DgiiServiceError(
150
+ "Respuesta de la DGII excede el tama\xF1o m\xE1ximo permitido"
151
+ );
152
+ }
153
+ if (response.status === 403) {
154
+ throw new DgiiServiceError(
155
+ "Acceso denegado por la DGII (HTTP 403). El servicio puede estar restringido por ubicaci\xF3n geogr\xE1fica.",
156
+ { statusCode: 403 }
157
+ );
158
+ }
159
+ if (!response.ok) {
160
+ throw new DgiiServiceError(
161
+ `El servicio de la DGII respondi\xF3 con HTTP ${response.status}`,
162
+ { statusCode: response.status }
163
+ );
164
+ }
165
+ return response.text();
166
+ }
167
+ /**
168
+ * Consulta datos de un contribuyente por RNC o cédula.
169
+ */
170
+ async getContribuyente(rnc) {
171
+ if (typeof rnc !== "string" || rnc.trim() === "") {
172
+ throw new DgiiServiceError("El par\xE1metro rnc es requerido");
173
+ }
174
+ const envelope = buildGetContribuyentesEnvelope(rnc.trim());
175
+ const responseXml = await this._fetch(
176
+ SOAP_ACTIONS.getContribuyentes,
177
+ envelope
178
+ );
179
+ return parseContribuyenteResponse(responseXml);
180
+ }
181
+ /**
182
+ * Valida un comprobante fiscal (NCF) contra la DGII.
183
+ */
184
+ async getNCF(rnc, ncf) {
185
+ if (typeof rnc !== "string" || rnc.trim() === "") {
186
+ throw new DgiiServiceError("El par\xE1metro rnc es requerido");
187
+ }
188
+ if (typeof ncf !== "string" || ncf.trim() === "") {
189
+ throw new DgiiServiceError("El par\xE1metro ncf es requerido");
190
+ }
191
+ const envelope = buildGetNcfEnvelope(rnc.trim(), ncf.trim());
192
+ const responseXml = await this._fetch(SOAP_ACTIONS.getNCF, envelope);
193
+ return parseNcfResponse(responseXml);
194
+ }
195
+ };
196
+
197
+ export {
198
+ DGII_SOAP_BASE_URL,
199
+ SOAP_ACTIONS,
200
+ DgiiSoapClient
201
+ };