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,201 @@
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
+
3
+ var _chunkWY6V4Y7Scjs = require('./chunk-WY6V4Y7S.cjs');
4
+
5
+
6
+
7
+ var _chunkQH4BK5X3cjs = require('./chunk-QH4BK5X3.cjs');
8
+
9
+
10
+
11
+ var _chunk7RJWFPCZcjs = require('./chunk-7RJWFPCZ.cjs');
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 (0, _chunk7RJWFPCZcjs.DgiiServiceError)(
53
+ "Respuesta SOAP sin GetContribuyentesResult"
54
+ );
55
+ }
56
+ const trimmed = raw.trim();
57
+ if (trimmed === "0" || trimmed === "") {
58
+ throw new (0, _chunk7RJWFPCZcjs.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 (e) {
68
+ throw new (0, _chunk7RJWFPCZcjs.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 (0, _chunk7RJWFPCZcjs.DgiiServiceError)(
74
+ "Formato de respuesta de la DGII inesperado"
75
+ );
76
+ }
77
+ const data = parsed;
78
+ return {
79
+ rnc: _chunkQH4BK5X3cjs.stripNonDigits.call(void 0, String(_nullishCoalesce(data["RGE_RUC"], () => ( "")))),
80
+ nombre: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, String(_nullishCoalesce(data["RGE_NOMBRE"], () => ( "")))),
81
+ nombreComercial: _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, String(_nullishCoalesce(data["NOMBRE_COMERCIAL"], () => ( "")))),
82
+ estado: String(_nullishCoalesce(data["ESTATUS"], () => ( ""))) === "2" ? "ACTIVO" : "INACTIVO",
83
+ categoria: String(_nullishCoalesce(data["CATEGORIA"], () => ( "")))
84
+ };
85
+ }
86
+ function parseNcfResponse(responseXml) {
87
+ const raw = extractTagContent(responseXml, "GetNCFResult");
88
+ if (raw === void 0) {
89
+ throw new (0, _chunk7RJWFPCZcjs.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 (e2) {
100
+ throw new (0, _chunk7RJWFPCZcjs.DgiiServiceError)(
101
+ "Respuesta de la DGII no es JSON v\xE1lido"
102
+ );
103
+ }
104
+ if (typeof parsed !== "object" || parsed === null) {
105
+ throw new (0, _chunk7RJWFPCZcjs.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(_nullishCoalesce(data["RNC"], () => ( ""))),
114
+ ncf: String(_nullishCoalesce(data["NCF"], () => ( ""))),
115
+ nombreComercial: data["NOMBRE_COMERCIAL"] ? _chunkQH4BK5X3cjs.collapseSpaces.call(void 0, String(data["NOMBRE_COMERCIAL"])) : void 0
116
+ };
117
+ }
118
+
119
+ // src/soap/client.ts
120
+ var DgiiSoapClient = class {
121
+ constructor(options) {
122
+ const baseUrl = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.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(_nullishCoalesce(_optionalChain([options, 'optionalAccess', _2 => _2.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 _chunkWY6V4Y7Scjs.wrapFetchError.call(void 0, error, timeout);
146
+ }
147
+ const contentLength = response.headers.get("content-length");
148
+ if (contentLength && parseInt(contentLength, 10) > 1048576) {
149
+ throw new (0, _chunk7RJWFPCZcjs.DgiiServiceError)(
150
+ "Respuesta de la DGII excede el tama\xF1o m\xE1ximo permitido"
151
+ );
152
+ }
153
+ if (response.status === 403) {
154
+ throw new (0, _chunk7RJWFPCZcjs.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 (0, _chunk7RJWFPCZcjs.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 (0, _chunk7RJWFPCZcjs.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 (0, _chunk7RJWFPCZcjs.DgiiServiceError)("El par\xE1metro rnc es requerido");
187
+ }
188
+ if (typeof ncf !== "string" || ncf.trim() === "") {
189
+ throw new (0, _chunk7RJWFPCZcjs.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
+
198
+
199
+
200
+
201
+ exports.DGII_SOAP_BASE_URL = DGII_SOAP_BASE_URL; exports.SOAP_ACTIONS = SOAP_ACTIONS; exports.DgiiSoapClient = DgiiSoapClient;
@@ -0,0 +1,198 @@
1
+ import {
2
+ DgiiSoapClient
3
+ } from "./chunk-HR4DCHH7.js";
4
+ import {
5
+ ScrapingClient
6
+ } from "./chunk-HGE4QZ3H.js";
7
+ import {
8
+ AllStrategiesFailedError,
9
+ DgiiConnectionError,
10
+ DgiiNotFoundError,
11
+ DgiiServiceError
12
+ } from "./chunk-DMGDJEUY.js";
13
+
14
+ // src/client/circuit-breaker.ts
15
+ var DEFAULT_CIRCUIT_BREAKER_OPTIONS = /* @__PURE__ */ Object.freeze({
16
+ failureThreshold: 5,
17
+ recoveryTimeoutMs: 6e4,
18
+ successThreshold: 2
19
+ });
20
+ var ConsecutiveBreaker = class {
21
+ constructor(options) {
22
+ this._state = "CLOSED";
23
+ this._failures = 0;
24
+ this._successes = 0;
25
+ this._lastFailureTime = 0;
26
+ this._options = {
27
+ ...DEFAULT_CIRCUIT_BREAKER_OPTIONS,
28
+ ...options
29
+ };
30
+ }
31
+ get state() {
32
+ if (this._state === "OPEN") {
33
+ if (Date.now() - this._lastFailureTime >= this._options.recoveryTimeoutMs) {
34
+ return "HALF_OPEN";
35
+ }
36
+ }
37
+ return this._state;
38
+ }
39
+ async execute(fn) {
40
+ const currentState = this.state;
41
+ if (currentState === "OPEN") {
42
+ throw new DgiiServiceError(
43
+ "Circuito abierto: estrategia deshabilitada temporalmente"
44
+ );
45
+ }
46
+ if (currentState === "HALF_OPEN" && this._state === "OPEN") {
47
+ this._state = "HALF_OPEN";
48
+ this._successes = 0;
49
+ }
50
+ try {
51
+ const result = await fn();
52
+ this._onSuccess();
53
+ return result;
54
+ } catch (error) {
55
+ this._onFailure();
56
+ throw error;
57
+ }
58
+ }
59
+ reset() {
60
+ this._state = "CLOSED";
61
+ this._failures = 0;
62
+ this._successes = 0;
63
+ this._lastFailureTime = 0;
64
+ }
65
+ _onSuccess() {
66
+ if (this._state === "HALF_OPEN") {
67
+ this._successes++;
68
+ if (this._successes >= this._options.successThreshold) {
69
+ this._state = "CLOSED";
70
+ this._failures = 0;
71
+ }
72
+ } else {
73
+ this._failures = 0;
74
+ }
75
+ }
76
+ _onFailure() {
77
+ this._failures++;
78
+ this._lastFailureTime = Date.now();
79
+ if (this._failures >= this._options.failureThreshold) {
80
+ this._state = "OPEN";
81
+ }
82
+ }
83
+ };
84
+
85
+ // src/client/retry.ts
86
+ var DEFAULT_RETRY_OPTIONS = /* @__PURE__ */ Object.freeze({
87
+ maxRetries: 2,
88
+ baseDelayMs: 500,
89
+ maxDelayMs: 1e4
90
+ });
91
+ function isRetryableError(error) {
92
+ if (error instanceof DgiiNotFoundError) return false;
93
+ if (error instanceof DgiiConnectionError) return true;
94
+ if (error instanceof DgiiServiceError) {
95
+ const code = error.statusCode;
96
+ if (code !== void 0 && code >= 500) return true;
97
+ return false;
98
+ }
99
+ return false;
100
+ }
101
+ async function withRetry(fn, options = DEFAULT_RETRY_OPTIONS) {
102
+ let lastError;
103
+ for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
104
+ try {
105
+ return await fn();
106
+ } catch (error) {
107
+ lastError = error;
108
+ if (attempt >= options.maxRetries || !isRetryableError(error)) {
109
+ throw error;
110
+ }
111
+ const exponential = Math.min(
112
+ options.maxDelayMs,
113
+ options.baseDelayMs * Math.pow(2, attempt)
114
+ );
115
+ const delay = Math.random() * exponential;
116
+ await new Promise((resolve) => setTimeout(resolve, delay));
117
+ }
118
+ }
119
+ throw lastError;
120
+ }
121
+
122
+ // src/client/client.ts
123
+ var DgiiClient = class {
124
+ constructor(options) {
125
+ const timeout = options?.timeout;
126
+ this._scraping = new ScrapingClient(
127
+ timeout ? { timeout } : void 0
128
+ );
129
+ this._soap = new DgiiSoapClient(
130
+ timeout ? { timeout } : void 0
131
+ );
132
+ this._scrapingBreaker = new ConsecutiveBreaker(
133
+ options?.circuitBreaker
134
+ );
135
+ this._soapBreaker = new ConsecutiveBreaker(
136
+ options?.circuitBreaker
137
+ );
138
+ this._retryOptions = {
139
+ ...DEFAULT_RETRY_OPTIONS,
140
+ ...options?.retry
141
+ };
142
+ this._soapFallback = options?.soapFallback ?? true;
143
+ }
144
+ /**
145
+ * Consulta datos de un contribuyente por RNC o cédula.
146
+ */
147
+ async getContribuyente(rnc) {
148
+ return this._executeWithFallback(
149
+ (client) => client.getContribuyente(rnc)
150
+ );
151
+ }
152
+ /**
153
+ * Valida un comprobante fiscal (NCF) contra la DGII.
154
+ */
155
+ async getNCF(rnc, ncf) {
156
+ return this._executeWithFallback(
157
+ (client) => client.getNCF(rnc, ncf)
158
+ );
159
+ }
160
+ async _executeWithFallback(operation) {
161
+ const errors = [];
162
+ try {
163
+ return await this._scrapingBreaker.execute(
164
+ () => withRetry(
165
+ () => operation(this._scraping),
166
+ this._retryOptions
167
+ )
168
+ );
169
+ } catch (error) {
170
+ if (error instanceof DgiiNotFoundError) throw error;
171
+ errors.push(error instanceof Error ? error : new Error(String(error)));
172
+ }
173
+ if (this._soapFallback) {
174
+ try {
175
+ return await this._soapBreaker.execute(
176
+ () => withRetry(
177
+ () => operation(this._soap),
178
+ this._retryOptions
179
+ )
180
+ );
181
+ } catch (error) {
182
+ if (error instanceof DgiiNotFoundError) throw error;
183
+ errors.push(error instanceof Error ? error : new Error(String(error)));
184
+ }
185
+ }
186
+ throw new AllStrategiesFailedError(
187
+ `Todas las estrategias de consulta fallaron. \xDAltimo error: ${errors[errors.length - 1]?.message ?? "desconocido"}`,
188
+ errors
189
+ );
190
+ }
191
+ };
192
+
193
+ export {
194
+ ConsecutiveBreaker,
195
+ isRetryableError,
196
+ withRetry,
197
+ DgiiClient
198
+ };
@@ -0,0 +1,23 @@
1
+ import {
2
+ DgiiConnectionError
3
+ } from "./chunk-DMGDJEUY.js";
4
+
5
+ // src/utils/fetch-error.ts
6
+ function wrapFetchError(error, timeoutMs) {
7
+ const name = error.name;
8
+ const causeName = error.cause?.name;
9
+ if (name === "TimeoutError" || causeName === "TimeoutError" || name === "AbortError" || causeName === "AbortError") {
10
+ return new DgiiConnectionError(
11
+ `Timeout de ${timeoutMs}ms excedido al conectar con la DGII`,
12
+ { cause: error }
13
+ );
14
+ }
15
+ return new DgiiConnectionError(
16
+ "Error de conexi\xF3n con el servicio de la DGII",
17
+ { cause: error }
18
+ );
19
+ }
20
+
21
+ export {
22
+ wrapFetchError
23
+ };
@@ -0,0 +1,198 @@
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
+
3
+ var _chunk4OYLCOULcjs = require('./chunk-4OYLCOUL.cjs');
4
+
5
+
6
+ var _chunkVJ4ZQQIQcjs = require('./chunk-VJ4ZQQIQ.cjs');
7
+
8
+
9
+
10
+
11
+
12
+ var _chunk7RJWFPCZcjs = require('./chunk-7RJWFPCZ.cjs');
13
+
14
+ // src/client/circuit-breaker.ts
15
+ var DEFAULT_CIRCUIT_BREAKER_OPTIONS = /* @__PURE__ */ Object.freeze({
16
+ failureThreshold: 5,
17
+ recoveryTimeoutMs: 6e4,
18
+ successThreshold: 2
19
+ });
20
+ var ConsecutiveBreaker = class {
21
+ constructor(options) {
22
+ this._state = "CLOSED";
23
+ this._failures = 0;
24
+ this._successes = 0;
25
+ this._lastFailureTime = 0;
26
+ this._options = {
27
+ ...DEFAULT_CIRCUIT_BREAKER_OPTIONS,
28
+ ...options
29
+ };
30
+ }
31
+ get state() {
32
+ if (this._state === "OPEN") {
33
+ if (Date.now() - this._lastFailureTime >= this._options.recoveryTimeoutMs) {
34
+ return "HALF_OPEN";
35
+ }
36
+ }
37
+ return this._state;
38
+ }
39
+ async execute(fn) {
40
+ const currentState = this.state;
41
+ if (currentState === "OPEN") {
42
+ throw new (0, _chunk7RJWFPCZcjs.DgiiServiceError)(
43
+ "Circuito abierto: estrategia deshabilitada temporalmente"
44
+ );
45
+ }
46
+ if (currentState === "HALF_OPEN" && this._state === "OPEN") {
47
+ this._state = "HALF_OPEN";
48
+ this._successes = 0;
49
+ }
50
+ try {
51
+ const result = await fn();
52
+ this._onSuccess();
53
+ return result;
54
+ } catch (error) {
55
+ this._onFailure();
56
+ throw error;
57
+ }
58
+ }
59
+ reset() {
60
+ this._state = "CLOSED";
61
+ this._failures = 0;
62
+ this._successes = 0;
63
+ this._lastFailureTime = 0;
64
+ }
65
+ _onSuccess() {
66
+ if (this._state === "HALF_OPEN") {
67
+ this._successes++;
68
+ if (this._successes >= this._options.successThreshold) {
69
+ this._state = "CLOSED";
70
+ this._failures = 0;
71
+ }
72
+ } else {
73
+ this._failures = 0;
74
+ }
75
+ }
76
+ _onFailure() {
77
+ this._failures++;
78
+ this._lastFailureTime = Date.now();
79
+ if (this._failures >= this._options.failureThreshold) {
80
+ this._state = "OPEN";
81
+ }
82
+ }
83
+ };
84
+
85
+ // src/client/retry.ts
86
+ var DEFAULT_RETRY_OPTIONS = /* @__PURE__ */ Object.freeze({
87
+ maxRetries: 2,
88
+ baseDelayMs: 500,
89
+ maxDelayMs: 1e4
90
+ });
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) {
95
+ const code = error.statusCode;
96
+ if (code !== void 0 && code >= 500) return true;
97
+ return false;
98
+ }
99
+ return false;
100
+ }
101
+ async function withRetry(fn, options = DEFAULT_RETRY_OPTIONS) {
102
+ let lastError;
103
+ for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
104
+ try {
105
+ return await fn();
106
+ } catch (error) {
107
+ lastError = error;
108
+ if (attempt >= options.maxRetries || !isRetryableError(error)) {
109
+ throw error;
110
+ }
111
+ const exponential = Math.min(
112
+ options.maxDelayMs,
113
+ options.baseDelayMs * Math.pow(2, attempt)
114
+ );
115
+ const delay = Math.random() * exponential;
116
+ await new Promise((resolve) => setTimeout(resolve, delay));
117
+ }
118
+ }
119
+ throw lastError;
120
+ }
121
+
122
+ // src/client/client.ts
123
+ var DgiiClient = class {
124
+ constructor(options) {
125
+ const timeout = _optionalChain([options, 'optionalAccess', _ => _.timeout]);
126
+ this._scraping = new (0, _chunkVJ4ZQQIQcjs.ScrapingClient)(
127
+ timeout ? { timeout } : void 0
128
+ );
129
+ this._soap = new (0, _chunk4OYLCOULcjs.DgiiSoapClient)(
130
+ timeout ? { timeout } : void 0
131
+ );
132
+ this._scrapingBreaker = new ConsecutiveBreaker(
133
+ _optionalChain([options, 'optionalAccess', _2 => _2.circuitBreaker])
134
+ );
135
+ this._soapBreaker = new ConsecutiveBreaker(
136
+ _optionalChain([options, 'optionalAccess', _3 => _3.circuitBreaker])
137
+ );
138
+ this._retryOptions = {
139
+ ...DEFAULT_RETRY_OPTIONS,
140
+ ..._optionalChain([options, 'optionalAccess', _4 => _4.retry])
141
+ };
142
+ this._soapFallback = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _5 => _5.soapFallback]), () => ( true));
143
+ }
144
+ /**
145
+ * Consulta datos de un contribuyente por RNC o cédula.
146
+ */
147
+ async getContribuyente(rnc) {
148
+ return this._executeWithFallback(
149
+ (client) => client.getContribuyente(rnc)
150
+ );
151
+ }
152
+ /**
153
+ * Valida un comprobante fiscal (NCF) contra la DGII.
154
+ */
155
+ async getNCF(rnc, ncf) {
156
+ return this._executeWithFallback(
157
+ (client) => client.getNCF(rnc, ncf)
158
+ );
159
+ }
160
+ async _executeWithFallback(operation) {
161
+ const errors = [];
162
+ try {
163
+ return await this._scrapingBreaker.execute(
164
+ () => withRetry(
165
+ () => operation(this._scraping),
166
+ this._retryOptions
167
+ )
168
+ );
169
+ } catch (error) {
170
+ if (error instanceof _chunk7RJWFPCZcjs.DgiiNotFoundError) throw error;
171
+ errors.push(error instanceof Error ? error : new Error(String(error)));
172
+ }
173
+ if (this._soapFallback) {
174
+ try {
175
+ return await this._soapBreaker.execute(
176
+ () => withRetry(
177
+ () => operation(this._soap),
178
+ this._retryOptions
179
+ )
180
+ );
181
+ } catch (error) {
182
+ if (error instanceof _chunk7RJWFPCZcjs.DgiiNotFoundError) throw error;
183
+ errors.push(error instanceof Error ? error : new Error(String(error)));
184
+ }
185
+ }
186
+ throw new (0, _chunk7RJWFPCZcjs.AllStrategiesFailedError)(
187
+ `Todas las estrategias de consulta fallaron. \xDAltimo error: ${_nullishCoalesce(_optionalChain([errors, 'access', _6 => _6[errors.length - 1], 'optionalAccess', _7 => _7.message]), () => ( "desconocido"))}`,
188
+ errors
189
+ );
190
+ }
191
+ };
192
+
193
+
194
+
195
+
196
+
197
+
198
+ exports.ConsecutiveBreaker = ConsecutiveBreaker; exports.isRetryableError = isRetryableError; exports.withRetry = withRetry; exports.DgiiClient = DgiiClient;
@@ -0,0 +1,44 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); 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; }// 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 = _optionalChain([options, 'optionalAccess', _ => _.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
+
39
+
40
+
41
+
42
+
43
+
44
+ exports.DgiiError = DgiiError; exports.DgiiConnectionError = DgiiConnectionError; exports.DgiiNotFoundError = DgiiNotFoundError; exports.DgiiServiceError = DgiiServiceError; exports.AllStrategiesFailedError = AllStrategiesFailedError;
@@ -0,0 +1,12 @@
1
+ // src/utils/index.ts
2
+ function stripNonDigits(value) {
3
+ return value.replace(/\D/g, "");
4
+ }
5
+ function collapseSpaces(value) {
6
+ return value.replace(/\s+/g, " ").trim();
7
+ }
8
+
9
+ export {
10
+ stripNonDigits,
11
+ collapseSpaces
12
+ };