@deuriib/dgii-ecf 0.5.0 → 0.6.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/dist/index.d.cts CHANGED
@@ -30,6 +30,7 @@ declare enum Endpoint {
30
30
  InquiryStatus = "consultaestado/api/Consultas/Estado",
31
31
  AllTrackingEcf = "ConsultaTrackIds/api/TrackIds/Consulta",
32
32
  Directory = "consultadirectorio/api/consultas/obtenerdirectorioporrnc",
33
+ DirectoryList = "consultadirectorio/api/consultas/listado",
33
34
  Void = "anulacionrangos/api/operaciones/anularrango"
34
35
  }
35
36
  /** Endpoints used when talking to a buyer's Sender↔Receiver host. */
@@ -39,6 +40,20 @@ declare enum SenderReceiverEndpoint {
39
40
  SendInvoice = "fe/recepcion/api/ecf",
40
41
  CommercialApproval = "fe/aprobacioncomercial/api/ecf"
41
42
  }
43
+ /** Service-status endpoints. They carry no environment segment. */
44
+ declare enum StatusEndpoint {
45
+ Services = "api/estatusservicios/obtenerestatus",
46
+ MaintenanceWindows = "api/estatusservicios/obtenerventanasmantenimiento",
47
+ Check = "api/estatusservicios/verificarestado"
48
+ }
49
+ /** Numeric `ambiente` codes accepted by the status service. */
50
+ declare enum StatusEnvironment {
51
+ PreCertification = 1,
52
+ Production = 2,
53
+ Certification = 3
54
+ }
55
+ /** Build an absolute status-service URL (no environment segment). */
56
+ declare const buildStatusUrl: (endpoint: StatusEndpoint, baseUrl?: string) => string;
42
57
  /** Build an absolute DGII URL: `${baseUrl}/${environment}/${endpoint}`. */
43
58
  declare const buildUrl: (environment: Environment, endpoint: Endpoint, baseUrl?: string) => string;
44
59
  /** Build an absolute Sender↔Receiver URL against a buyer host. */
@@ -46,7 +61,11 @@ declare const buildSenderReceiverUrl: (host: string, endpoint: SenderReceiverEnd
46
61
 
47
62
  interface Mensaje {
48
63
  valor: string;
49
- codigo: number;
64
+ /**
65
+ * DGII message code. Numeric on the status endpoints, a string on the RFCE
66
+ * response, so both shapes are preserved instead of coerced.
67
+ */
68
+ codigo: number | string;
50
69
  }
51
70
  interface AuthToken {
52
71
  token: string;
@@ -72,10 +91,17 @@ interface TrackingStatusResponse {
72
91
  mensajes?: Mensaje[];
73
92
  }
74
93
  type InquiryStatusCode = 0 | 1 | 2;
75
- type InquiryStatus = 'No encontrado' | 'Aceptado' | 'Rechazado';
94
+ /**
95
+ * eNCF inquiry states. "Aceptado Condicional" is observable for an E32 below
96
+ * RD$250,000 even though the tracking service reports it with a different
97
+ * code, so it belongs in the union.
98
+ */
99
+ type InquiryStatus = 'No encontrado' | 'Aceptado' | 'Rechazado' | 'Aceptado Condicional';
76
100
  interface InquiryStatusResponse {
77
101
  codigo: InquiryStatusCode;
78
102
  estado: InquiryStatus;
103
+ /** `true` when the e-NCF sequence is already consumed. */
104
+ secuenciaUtilizada?: boolean;
79
105
  rncEmisor?: string;
80
106
  ncfElectronico?: string;
81
107
  montoTotal?: number;
@@ -102,9 +128,18 @@ interface InvoiceResponse {
102
128
  /** Tolerated array form used by the RFCE and status endpoints. */
103
129
  mensajes?: Mensaje[];
104
130
  }
131
+ /**
132
+ * Documented states of an RFCE reception. DGII is not consistent about casing
133
+ * between services (the tracking service reports "Aceptado Condicional"), so
134
+ * `estado` stays a `string` and these constants are the documented values.
135
+ */
136
+ declare const SUMMARY_ACCEPTED = "Aceptado";
137
+ declare const SUMMARY_CONDITIONAL_ACCEPTED = "Aceptado condicional";
138
+ declare const SUMMARY_REJECTED = "Rechazado";
105
139
  interface InvoiceSummaryResponse {
106
140
  codigo: number;
107
- estado: 'Aceptado' | 'Rechazado' | 'Aceptado Condicional';
141
+ /** One of the `SUMMARY_*` constants, compared case-insensitively. */
142
+ estado: string;
108
143
  mensajes?: Mensaje[];
109
144
  encf: string;
110
145
  secuenciaUtilizada: boolean;
@@ -134,7 +169,34 @@ interface VoidNCFResponse {
134
169
  rnc: string;
135
170
  codigo: string;
136
171
  nombre: string;
137
- mensajes: string[];
172
+ /** DGII returns objects on some services and bare strings on others. */
173
+ mensajes: Mensaje[];
174
+ }
175
+ /** Operational state reported by the DGII status service. */
176
+ type ServiceAvailability = 'Disponible' | 'No Disponible';
177
+ /** One entry of `getServiceStatus` (`estatusservicios/obtenerestatus`). */
178
+ interface ServiceStatusEntry {
179
+ servicio: string;
180
+ estatus: ServiceAvailability;
181
+ ambiente: string;
182
+ }
183
+ /** One maintenance window of `getMaintenanceWindows`. */
184
+ interface MaintenanceWindow {
185
+ ambiente: string;
186
+ horaInicio: string;
187
+ horaFin: string;
188
+ dias: string[];
189
+ }
190
+ /** Result of `checkServiceAvailability` (`estatusservicios/verificarestado`). */
191
+ interface ServiceAvailabilityResponse {
192
+ estado: ServiceAvailability;
193
+ }
194
+ /** Outcome of a direct ACECF delivery to a buyer host (Emisor↔Receptor). */
195
+ interface B2BCommercialApprovalResponse {
196
+ /** HTTP status returned by the buyer host: 200 satisfactory, 400 unsatisfactory. */
197
+ status: 200 | 400;
198
+ /** `true` for HTTP 200, `false` for HTTP 400. */
199
+ accepted: boolean;
138
200
  }
139
201
 
140
202
  type DgiiErrorCode = 'INVALID_XML' | 'CERTIFICATE' | 'SIGNING' | 'HTTP' | 'AUTH' | 'TIMEOUT' | 'CIRCUIT_OPEN' | 'IDEMPOTENCY' | 'VALIDATION' | 'UNKNOWN';
@@ -275,6 +337,8 @@ interface RequestOptions {
275
337
  resource?: string;
276
338
  /** Skip the auth hooks (used by the authentication round-trip itself). */
277
339
  skipAuthHooks?: boolean;
340
+ /** Do not attach the bearer token (used by the status service). */
341
+ omitAuth?: boolean;
278
342
  }
279
343
  /** Thin HTTP client over the native `fetch`, wrapped in resilience policies. */
280
344
  declare class HttpClient {
@@ -366,6 +430,14 @@ declare const validateXMLCertificate: (xml: string) => XmlCertificateValidation;
366
430
  * Values that are not plain decimals (e.g. `1.0e3`) are returned unchanged.
367
431
  */
368
432
  declare const roundHalfUpToCents: (value: string) => string;
433
+ /**
434
+ * Drop empty leaves and the containers they leave behind.
435
+ *
436
+ * DGII forbids empty tags: any element without a value must be omitted
437
+ * entirely instead of being serialized as `<Tag/>` or `<Tag></Tag>`. An object
438
+ * that only carries `_attributes` is *not* empty — it still renders an element.
439
+ */
440
+ declare const pruneEmptyValues: (input: unknown) => unknown;
369
441
  /**
370
442
  * Converts between the DGII XML documents and the "compact" JSON shape used by
371
443
  * `xml-js` (a dict key is an element, an array is a repeated element, and
@@ -414,6 +486,66 @@ declare const compareDecimal: (a: string, b: string) => number;
414
486
  * cannot drift from what is actually being transmitted.
415
487
  */
416
488
  declare const resolveReceptionRoute: (signedXml: string) => Result<ReceptionRoute, DgiiError>;
489
+ /**
490
+ * Whether a signed document may be delivered directly to a buyer host.
491
+ *
492
+ * DGII excludes E32 below RD$250,000 (only the RFCE summary applies) plus
493
+ * E41, E43, E45, E46 and E47. E31, E33, E34, E44 and an E32 at or above the
494
+ * threshold can go peer-to-peer.
495
+ */
496
+ declare const checkB2BApplicability: (signedXml: string) => Result<void, DgiiError>;
497
+
498
+ /** Message returned when a submission with the same key is already in flight. */
499
+ declare const PENDING_SUBMISSION_MESSAGE = "A submission with this key is already pending; call reconcileSubmission() before retrying";
500
+ /** Outcome of reconciling a pending submission against DGII. */
501
+ interface ReconciliationResult {
502
+ /** DGII's answer for the e-NCF. */
503
+ inquiry: InquiryStatusResponse;
504
+ /** `true` when DGII already has a record of the document. */
505
+ delivered: boolean;
506
+ /** `true` when the e-NCF sequence may be reused. */
507
+ canReuseSequence: boolean;
508
+ /** `true` when the pending marker was cleared and a retry is safe. */
509
+ cleared: boolean;
510
+ }
511
+ /**
512
+ * Whether a rejected e-NCF sequence may be reused.
513
+ *
514
+ * DGII sets `secuenciaUtilizada` to `true` when the sequence is consumed and
515
+ * `false` when the rejection leaves it reusable: invalid certificate or
516
+ * signature, invalid XML, non-delegated signer, unauthorized or expired e-NCF,
517
+ * non-electronic issuer, unknown or inactive RNC.
518
+ *
519
+ * An absent flag is treated as "not reusable" — the conservative answer.
520
+ */
521
+ declare const canReuseSequence: (response: {
522
+ secuenciaUtilizada?: boolean | null;
523
+ }) => boolean;
524
+
525
+ declare const SCHEMA_DIR: string;
526
+ /** Schemas DGII publishes but that do not compile, with the reason. */
527
+ declare const UNAVAILABLE_SCHEMAS: Record<string, string>;
528
+ /** One schema violation, with the element DGII would complain about. */
529
+ interface SchemaViolation {
530
+ path: string;
531
+ message: string;
532
+ }
533
+ interface ValidationOptions {
534
+ /**
535
+ * `xmllint` binary to use. Defaults to `DGII_XMLLINT_BIN` or `xmllint` on
536
+ * `PATH`. There is no bundled engine: validation shells out.
537
+ */
538
+ bin?: string;
539
+ }
540
+ /**
541
+ * Validate a DGII document against its bundled XML Schema.
542
+ *
543
+ * Requires `xmllint` (libxml2-utils) on `PATH`, or `DGII_XMLLINT_BIN` pointing
544
+ * at it. A missing binary is an error — never a silent pass.
545
+ */
546
+ declare const validateDocument: (xml: string, options?: ValidationOptions) => Result<SchemaViolation[], DgiiError>;
547
+ /** One-line summary of the first violations, for an error message. */
548
+ declare const formatViolations: (violations: readonly SchemaViolation[]) => string;
417
549
 
418
550
  /** First 6 chars of the `<SignatureValue>` (DGII security code). */
419
551
  declare const getCodeSixDigitfromSignature: (signedXml: string) => Result<string, DgiiError>;
@@ -452,6 +584,20 @@ interface EcfOptions {
452
584
  tokenRefreshSkewMs?: number;
453
585
  /** Injectable clock (epoch milliseconds); useful for tests. */
454
586
  now?: () => number;
587
+ /** API key for the `statusecf` service (`Authorization: Apikey …`). */
588
+ statusApiKey?: string;
589
+ /**
590
+ * Reject a direct buyer-host delivery for the e-CF types DGII excludes
591
+ * (E32 below RD$250,000, E41, E43, E45, E46, E47). Defaults to `true`.
592
+ */
593
+ enforceB2BRules?: boolean;
594
+ /**
595
+ * Validate a general e-CF against its bundled XML Schema before sending.
596
+ * Requires `xmllint` (or `DGII_XMLLINT_BIN`). Defaults to `false`.
597
+ */
598
+ validateBeforeSend?: boolean;
599
+ /** `xmllint` binary used by `validateBeforeSend`. */
600
+ xmllintBin?: string;
455
601
  }
456
602
  /**
457
603
  * DGII e-CF client. Every method returns a `Result` — no thrown domain errors.
@@ -464,6 +610,10 @@ declare class ECF {
464
610
  private readonly inFlight;
465
611
  private readonly tokenRefreshSkewMs;
466
612
  private readonly now;
613
+ private readonly statusApiKey;
614
+ private readonly enforceB2BRules;
615
+ private readonly validateBeforeSend;
616
+ private readonly xmllintBin;
467
617
  private tokenExpiresAt;
468
618
  private refreshPromise;
469
619
  constructor(credentials: P12ReaderData, environment?: Environment, options?: EcfOptions);
@@ -490,6 +640,28 @@ declare class ECF {
490
640
  private reauthenticate;
491
641
  /** Authenticate against DGII (or a buyer host) and store the bearer token. */
492
642
  authenticate(buyerHost?: string): Promise<Result<AuthToken, DgiiError>>;
643
+ /**
644
+ * Query the operational status of every DGII service.
645
+ *
646
+ * The status host takes an API key instead of a bearer token, so this call
647
+ * neither needs `authenticate()` nor triggers a token refresh.
648
+ */
649
+ getServiceStatus(): Promise<Result<ServiceStatusEntry[], DgiiError>>;
650
+ /** Scheduled maintenance windows, one entry per environment. */
651
+ getMaintenanceWindows(): Promise<Result<MaintenanceWindow[], DgiiError>>;
652
+ /** Whether an environment is currently inside a maintenance window. */
653
+ checkServiceAvailability(ambiente: StatusEnvironment): Promise<Result<ServiceAvailabilityResponse, DgiiError>>;
654
+ private statusRequest;
655
+ /**
656
+ * Resolve what happened to a submission whose idempotency marker is pending.
657
+ *
658
+ * Queries DGII by e-NCF. When DGII has no record of the document the marker
659
+ * is cleared, because re-submitting cannot double-issue. When DGII does have
660
+ * a record the marker stays in place — it is the lock that prevents a second
661
+ * submission of a document that already exists — and `delivered` tells the
662
+ * caller to stop retrying.
663
+ */
664
+ reconcileSubmission(signedXml: string): Promise<Result<ReconciliationResult, DgiiError>>;
493
665
  private getSeed;
494
666
  private getAuthToken;
495
667
  /**
@@ -513,6 +685,8 @@ declare class ECF {
513
685
  trackStatuses(rncEmisor: string, encf: string): Promise<Result<SummaryTrackingStatusResponse[], DgiiError>>;
514
686
  /** Normalizes DGII's inconsistent (array | single object) directory payload. */
515
687
  getCustomerDirectory(rnc: string): Promise<Result<ServiceDirectoryResponse[] | undefined, DgiiError>>;
688
+ /** General directory listing — every receiver that publishes service URLs. */
689
+ getDirectoryListing(): Promise<Result<ServiceDirectoryResponse[], DgiiError>>;
516
690
  /** RFCE inquiry — DGII production only. */
517
691
  getSummaryInvoiceInquiry(rnc: string, encf: string, codSeguridad: string): Promise<Result<InquiryInvoiceSummary, DgiiError>>;
518
692
  /**
@@ -520,6 +694,7 @@ declare class ECF {
520
694
  * keyed on `RNC + eNCF + SHA-256(signed XML)`.
521
695
  */
522
696
  sendCommercialApproval(signedXml: string, fileName: string): Promise<Result<CommercialApprovalResponse, DgiiError>>;
697
+ sendCommercialApproval(signedXml: string, fileName: string, buyerHost: string): Promise<Result<B2BCommercialApprovalResponse, DgiiError>>;
523
698
  /**
524
699
  * Submit a signed range-voiding request (ANECF). Idempotent like
525
700
  * submissions, keyed on `RNC + RangoNCF + SHA-256(signed XML)`.
@@ -527,4 +702,4 @@ declare class ECF {
527
702
  voidENCF(signedXml: string, fileName: string): Promise<Result<VoidNCFResponse, DgiiError>>;
528
703
  }
529
704
 
530
- export { type AuthToken, BaseUrl, type BaseUrlKey, CONSUMPTION_SUMMARY_THRESHOLD, type CertificateInfo, type CommercialApprovalResponse, type DgiiDocumentType, DgiiError, type DgiiErrorCode, type DgiiErrorContext, type DgiiErrorOptions, type DgiiResult, ECF, ECF_TYPE_CODES, type EcfOptions, type EcfTypeCode, Endpoint, Environment, type GeneralReceptionRoute, HttpClient, type HttpClientOptions, type IdempotencyStore, InMemoryIdempotencyStore, type InMemoryIdempotencyStoreOptions, type InquiryInvoiceSummary, type InquiryStatus, type InquiryStatusCode, type InquiryStatusResponse, type InvoiceResponse, type InvoiceSummaryResponse, type Mensaje, P12Reader, type P12ReaderData, type ReceptionRoute, type RequestOptions, type ResilienceOptions, type ResiliencePolicies, SenderReceiverEndpoint, type ServiceDirectoryResponse, Signature, type SummaryReceptionRoute, type SummaryTrackingStatusResponse, type TrackStatusCode, TrackStatusEnum, type TrackingStatusResponse, Transformer, type VoidNCFResponse, type XmlCertificateValidation, buildSenderReceiverUrl, buildUrl, compareDecimal, convertECF32ToRFCE, createResiliencePolicies, defaultResilienceOptions, extractDgiiErrorMessage, generateEcfQRCodeURL, generateFcQRCodeURL, getCodeSixDigitfromSignature, getCurrentFormattedDate, getCurrentFormattedDateTime, getXmlFromBodyResponse, isEcfTypeCode, isRetryableError, isRetryableStatus, resolveReceptionRoute, roundHalfUpToCents, toDgiiError, validateXMLCertificate };
705
+ export { type AuthToken, type B2BCommercialApprovalResponse, BaseUrl, type BaseUrlKey, CONSUMPTION_SUMMARY_THRESHOLD, type CertificateInfo, type CommercialApprovalResponse, type DgiiDocumentType, DgiiError, type DgiiErrorCode, type DgiiErrorContext, type DgiiErrorOptions, type DgiiResult, ECF, ECF_TYPE_CODES, type EcfOptions, type EcfTypeCode, Endpoint, Environment, type GeneralReceptionRoute, HttpClient, type HttpClientOptions, type IdempotencyStore, InMemoryIdempotencyStore, type InMemoryIdempotencyStoreOptions, type InquiryInvoiceSummary, type InquiryStatus, type InquiryStatusCode, type InquiryStatusResponse, type InvoiceResponse, type InvoiceSummaryResponse, type MaintenanceWindow, type Mensaje, P12Reader, type P12ReaderData, PENDING_SUBMISSION_MESSAGE, type ReceptionRoute, type ReconciliationResult, type RequestOptions, type ResilienceOptions, type ResiliencePolicies, SCHEMA_DIR, SUMMARY_ACCEPTED, SUMMARY_CONDITIONAL_ACCEPTED, SUMMARY_REJECTED, type SchemaViolation, SenderReceiverEndpoint, type ServiceAvailability, type ServiceAvailabilityResponse, type ServiceDirectoryResponse, type ServiceStatusEntry, Signature, StatusEndpoint, StatusEnvironment, type SummaryReceptionRoute, type SummaryTrackingStatusResponse, type TrackStatusCode, TrackStatusEnum, type TrackingStatusResponse, Transformer, UNAVAILABLE_SCHEMAS, type ValidationOptions, type VoidNCFResponse, type XmlCertificateValidation, buildSenderReceiverUrl, buildStatusUrl, buildUrl, canReuseSequence, checkB2BApplicability, compareDecimal, convertECF32ToRFCE, createResiliencePolicies, defaultResilienceOptions, extractDgiiErrorMessage, formatViolations, generateEcfQRCodeURL, generateFcQRCodeURL, getCodeSixDigitfromSignature, getCurrentFormattedDate, getCurrentFormattedDateTime, getXmlFromBodyResponse, isEcfTypeCode, isRetryableError, isRetryableStatus, pruneEmptyValues, resolveReceptionRoute, roundHalfUpToCents, toDgiiError, validateDocument, validateXMLCertificate };
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ declare enum Endpoint {
30
30
  InquiryStatus = "consultaestado/api/Consultas/Estado",
31
31
  AllTrackingEcf = "ConsultaTrackIds/api/TrackIds/Consulta",
32
32
  Directory = "consultadirectorio/api/consultas/obtenerdirectorioporrnc",
33
+ DirectoryList = "consultadirectorio/api/consultas/listado",
33
34
  Void = "anulacionrangos/api/operaciones/anularrango"
34
35
  }
35
36
  /** Endpoints used when talking to a buyer's Sender↔Receiver host. */
@@ -39,6 +40,20 @@ declare enum SenderReceiverEndpoint {
39
40
  SendInvoice = "fe/recepcion/api/ecf",
40
41
  CommercialApproval = "fe/aprobacioncomercial/api/ecf"
41
42
  }
43
+ /** Service-status endpoints. They carry no environment segment. */
44
+ declare enum StatusEndpoint {
45
+ Services = "api/estatusservicios/obtenerestatus",
46
+ MaintenanceWindows = "api/estatusservicios/obtenerventanasmantenimiento",
47
+ Check = "api/estatusservicios/verificarestado"
48
+ }
49
+ /** Numeric `ambiente` codes accepted by the status service. */
50
+ declare enum StatusEnvironment {
51
+ PreCertification = 1,
52
+ Production = 2,
53
+ Certification = 3
54
+ }
55
+ /** Build an absolute status-service URL (no environment segment). */
56
+ declare const buildStatusUrl: (endpoint: StatusEndpoint, baseUrl?: string) => string;
42
57
  /** Build an absolute DGII URL: `${baseUrl}/${environment}/${endpoint}`. */
43
58
  declare const buildUrl: (environment: Environment, endpoint: Endpoint, baseUrl?: string) => string;
44
59
  /** Build an absolute Sender↔Receiver URL against a buyer host. */
@@ -46,7 +61,11 @@ declare const buildSenderReceiverUrl: (host: string, endpoint: SenderReceiverEnd
46
61
 
47
62
  interface Mensaje {
48
63
  valor: string;
49
- codigo: number;
64
+ /**
65
+ * DGII message code. Numeric on the status endpoints, a string on the RFCE
66
+ * response, so both shapes are preserved instead of coerced.
67
+ */
68
+ codigo: number | string;
50
69
  }
51
70
  interface AuthToken {
52
71
  token: string;
@@ -72,10 +91,17 @@ interface TrackingStatusResponse {
72
91
  mensajes?: Mensaje[];
73
92
  }
74
93
  type InquiryStatusCode = 0 | 1 | 2;
75
- type InquiryStatus = 'No encontrado' | 'Aceptado' | 'Rechazado';
94
+ /**
95
+ * eNCF inquiry states. "Aceptado Condicional" is observable for an E32 below
96
+ * RD$250,000 even though the tracking service reports it with a different
97
+ * code, so it belongs in the union.
98
+ */
99
+ type InquiryStatus = 'No encontrado' | 'Aceptado' | 'Rechazado' | 'Aceptado Condicional';
76
100
  interface InquiryStatusResponse {
77
101
  codigo: InquiryStatusCode;
78
102
  estado: InquiryStatus;
103
+ /** `true` when the e-NCF sequence is already consumed. */
104
+ secuenciaUtilizada?: boolean;
79
105
  rncEmisor?: string;
80
106
  ncfElectronico?: string;
81
107
  montoTotal?: number;
@@ -102,9 +128,18 @@ interface InvoiceResponse {
102
128
  /** Tolerated array form used by the RFCE and status endpoints. */
103
129
  mensajes?: Mensaje[];
104
130
  }
131
+ /**
132
+ * Documented states of an RFCE reception. DGII is not consistent about casing
133
+ * between services (the tracking service reports "Aceptado Condicional"), so
134
+ * `estado` stays a `string` and these constants are the documented values.
135
+ */
136
+ declare const SUMMARY_ACCEPTED = "Aceptado";
137
+ declare const SUMMARY_CONDITIONAL_ACCEPTED = "Aceptado condicional";
138
+ declare const SUMMARY_REJECTED = "Rechazado";
105
139
  interface InvoiceSummaryResponse {
106
140
  codigo: number;
107
- estado: 'Aceptado' | 'Rechazado' | 'Aceptado Condicional';
141
+ /** One of the `SUMMARY_*` constants, compared case-insensitively. */
142
+ estado: string;
108
143
  mensajes?: Mensaje[];
109
144
  encf: string;
110
145
  secuenciaUtilizada: boolean;
@@ -134,7 +169,34 @@ interface VoidNCFResponse {
134
169
  rnc: string;
135
170
  codigo: string;
136
171
  nombre: string;
137
- mensajes: string[];
172
+ /** DGII returns objects on some services and bare strings on others. */
173
+ mensajes: Mensaje[];
174
+ }
175
+ /** Operational state reported by the DGII status service. */
176
+ type ServiceAvailability = 'Disponible' | 'No Disponible';
177
+ /** One entry of `getServiceStatus` (`estatusservicios/obtenerestatus`). */
178
+ interface ServiceStatusEntry {
179
+ servicio: string;
180
+ estatus: ServiceAvailability;
181
+ ambiente: string;
182
+ }
183
+ /** One maintenance window of `getMaintenanceWindows`. */
184
+ interface MaintenanceWindow {
185
+ ambiente: string;
186
+ horaInicio: string;
187
+ horaFin: string;
188
+ dias: string[];
189
+ }
190
+ /** Result of `checkServiceAvailability` (`estatusservicios/verificarestado`). */
191
+ interface ServiceAvailabilityResponse {
192
+ estado: ServiceAvailability;
193
+ }
194
+ /** Outcome of a direct ACECF delivery to a buyer host (Emisor↔Receptor). */
195
+ interface B2BCommercialApprovalResponse {
196
+ /** HTTP status returned by the buyer host: 200 satisfactory, 400 unsatisfactory. */
197
+ status: 200 | 400;
198
+ /** `true` for HTTP 200, `false` for HTTP 400. */
199
+ accepted: boolean;
138
200
  }
139
201
 
140
202
  type DgiiErrorCode = 'INVALID_XML' | 'CERTIFICATE' | 'SIGNING' | 'HTTP' | 'AUTH' | 'TIMEOUT' | 'CIRCUIT_OPEN' | 'IDEMPOTENCY' | 'VALIDATION' | 'UNKNOWN';
@@ -275,6 +337,8 @@ interface RequestOptions {
275
337
  resource?: string;
276
338
  /** Skip the auth hooks (used by the authentication round-trip itself). */
277
339
  skipAuthHooks?: boolean;
340
+ /** Do not attach the bearer token (used by the status service). */
341
+ omitAuth?: boolean;
278
342
  }
279
343
  /** Thin HTTP client over the native `fetch`, wrapped in resilience policies. */
280
344
  declare class HttpClient {
@@ -366,6 +430,14 @@ declare const validateXMLCertificate: (xml: string) => XmlCertificateValidation;
366
430
  * Values that are not plain decimals (e.g. `1.0e3`) are returned unchanged.
367
431
  */
368
432
  declare const roundHalfUpToCents: (value: string) => string;
433
+ /**
434
+ * Drop empty leaves and the containers they leave behind.
435
+ *
436
+ * DGII forbids empty tags: any element without a value must be omitted
437
+ * entirely instead of being serialized as `<Tag/>` or `<Tag></Tag>`. An object
438
+ * that only carries `_attributes` is *not* empty — it still renders an element.
439
+ */
440
+ declare const pruneEmptyValues: (input: unknown) => unknown;
369
441
  /**
370
442
  * Converts between the DGII XML documents and the "compact" JSON shape used by
371
443
  * `xml-js` (a dict key is an element, an array is a repeated element, and
@@ -414,6 +486,66 @@ declare const compareDecimal: (a: string, b: string) => number;
414
486
  * cannot drift from what is actually being transmitted.
415
487
  */
416
488
  declare const resolveReceptionRoute: (signedXml: string) => Result<ReceptionRoute, DgiiError>;
489
+ /**
490
+ * Whether a signed document may be delivered directly to a buyer host.
491
+ *
492
+ * DGII excludes E32 below RD$250,000 (only the RFCE summary applies) plus
493
+ * E41, E43, E45, E46 and E47. E31, E33, E34, E44 and an E32 at or above the
494
+ * threshold can go peer-to-peer.
495
+ */
496
+ declare const checkB2BApplicability: (signedXml: string) => Result<void, DgiiError>;
497
+
498
+ /** Message returned when a submission with the same key is already in flight. */
499
+ declare const PENDING_SUBMISSION_MESSAGE = "A submission with this key is already pending; call reconcileSubmission() before retrying";
500
+ /** Outcome of reconciling a pending submission against DGII. */
501
+ interface ReconciliationResult {
502
+ /** DGII's answer for the e-NCF. */
503
+ inquiry: InquiryStatusResponse;
504
+ /** `true` when DGII already has a record of the document. */
505
+ delivered: boolean;
506
+ /** `true` when the e-NCF sequence may be reused. */
507
+ canReuseSequence: boolean;
508
+ /** `true` when the pending marker was cleared and a retry is safe. */
509
+ cleared: boolean;
510
+ }
511
+ /**
512
+ * Whether a rejected e-NCF sequence may be reused.
513
+ *
514
+ * DGII sets `secuenciaUtilizada` to `true` when the sequence is consumed and
515
+ * `false` when the rejection leaves it reusable: invalid certificate or
516
+ * signature, invalid XML, non-delegated signer, unauthorized or expired e-NCF,
517
+ * non-electronic issuer, unknown or inactive RNC.
518
+ *
519
+ * An absent flag is treated as "not reusable" — the conservative answer.
520
+ */
521
+ declare const canReuseSequence: (response: {
522
+ secuenciaUtilizada?: boolean | null;
523
+ }) => boolean;
524
+
525
+ declare const SCHEMA_DIR: string;
526
+ /** Schemas DGII publishes but that do not compile, with the reason. */
527
+ declare const UNAVAILABLE_SCHEMAS: Record<string, string>;
528
+ /** One schema violation, with the element DGII would complain about. */
529
+ interface SchemaViolation {
530
+ path: string;
531
+ message: string;
532
+ }
533
+ interface ValidationOptions {
534
+ /**
535
+ * `xmllint` binary to use. Defaults to `DGII_XMLLINT_BIN` or `xmllint` on
536
+ * `PATH`. There is no bundled engine: validation shells out.
537
+ */
538
+ bin?: string;
539
+ }
540
+ /**
541
+ * Validate a DGII document against its bundled XML Schema.
542
+ *
543
+ * Requires `xmllint` (libxml2-utils) on `PATH`, or `DGII_XMLLINT_BIN` pointing
544
+ * at it. A missing binary is an error — never a silent pass.
545
+ */
546
+ declare const validateDocument: (xml: string, options?: ValidationOptions) => Result<SchemaViolation[], DgiiError>;
547
+ /** One-line summary of the first violations, for an error message. */
548
+ declare const formatViolations: (violations: readonly SchemaViolation[]) => string;
417
549
 
418
550
  /** First 6 chars of the `<SignatureValue>` (DGII security code). */
419
551
  declare const getCodeSixDigitfromSignature: (signedXml: string) => Result<string, DgiiError>;
@@ -452,6 +584,20 @@ interface EcfOptions {
452
584
  tokenRefreshSkewMs?: number;
453
585
  /** Injectable clock (epoch milliseconds); useful for tests. */
454
586
  now?: () => number;
587
+ /** API key for the `statusecf` service (`Authorization: Apikey …`). */
588
+ statusApiKey?: string;
589
+ /**
590
+ * Reject a direct buyer-host delivery for the e-CF types DGII excludes
591
+ * (E32 below RD$250,000, E41, E43, E45, E46, E47). Defaults to `true`.
592
+ */
593
+ enforceB2BRules?: boolean;
594
+ /**
595
+ * Validate a general e-CF against its bundled XML Schema before sending.
596
+ * Requires `xmllint` (or `DGII_XMLLINT_BIN`). Defaults to `false`.
597
+ */
598
+ validateBeforeSend?: boolean;
599
+ /** `xmllint` binary used by `validateBeforeSend`. */
600
+ xmllintBin?: string;
455
601
  }
456
602
  /**
457
603
  * DGII e-CF client. Every method returns a `Result` — no thrown domain errors.
@@ -464,6 +610,10 @@ declare class ECF {
464
610
  private readonly inFlight;
465
611
  private readonly tokenRefreshSkewMs;
466
612
  private readonly now;
613
+ private readonly statusApiKey;
614
+ private readonly enforceB2BRules;
615
+ private readonly validateBeforeSend;
616
+ private readonly xmllintBin;
467
617
  private tokenExpiresAt;
468
618
  private refreshPromise;
469
619
  constructor(credentials: P12ReaderData, environment?: Environment, options?: EcfOptions);
@@ -490,6 +640,28 @@ declare class ECF {
490
640
  private reauthenticate;
491
641
  /** Authenticate against DGII (or a buyer host) and store the bearer token. */
492
642
  authenticate(buyerHost?: string): Promise<Result<AuthToken, DgiiError>>;
643
+ /**
644
+ * Query the operational status of every DGII service.
645
+ *
646
+ * The status host takes an API key instead of a bearer token, so this call
647
+ * neither needs `authenticate()` nor triggers a token refresh.
648
+ */
649
+ getServiceStatus(): Promise<Result<ServiceStatusEntry[], DgiiError>>;
650
+ /** Scheduled maintenance windows, one entry per environment. */
651
+ getMaintenanceWindows(): Promise<Result<MaintenanceWindow[], DgiiError>>;
652
+ /** Whether an environment is currently inside a maintenance window. */
653
+ checkServiceAvailability(ambiente: StatusEnvironment): Promise<Result<ServiceAvailabilityResponse, DgiiError>>;
654
+ private statusRequest;
655
+ /**
656
+ * Resolve what happened to a submission whose idempotency marker is pending.
657
+ *
658
+ * Queries DGII by e-NCF. When DGII has no record of the document the marker
659
+ * is cleared, because re-submitting cannot double-issue. When DGII does have
660
+ * a record the marker stays in place — it is the lock that prevents a second
661
+ * submission of a document that already exists — and `delivered` tells the
662
+ * caller to stop retrying.
663
+ */
664
+ reconcileSubmission(signedXml: string): Promise<Result<ReconciliationResult, DgiiError>>;
493
665
  private getSeed;
494
666
  private getAuthToken;
495
667
  /**
@@ -513,6 +685,8 @@ declare class ECF {
513
685
  trackStatuses(rncEmisor: string, encf: string): Promise<Result<SummaryTrackingStatusResponse[], DgiiError>>;
514
686
  /** Normalizes DGII's inconsistent (array | single object) directory payload. */
515
687
  getCustomerDirectory(rnc: string): Promise<Result<ServiceDirectoryResponse[] | undefined, DgiiError>>;
688
+ /** General directory listing — every receiver that publishes service URLs. */
689
+ getDirectoryListing(): Promise<Result<ServiceDirectoryResponse[], DgiiError>>;
516
690
  /** RFCE inquiry — DGII production only. */
517
691
  getSummaryInvoiceInquiry(rnc: string, encf: string, codSeguridad: string): Promise<Result<InquiryInvoiceSummary, DgiiError>>;
518
692
  /**
@@ -520,6 +694,7 @@ declare class ECF {
520
694
  * keyed on `RNC + eNCF + SHA-256(signed XML)`.
521
695
  */
522
696
  sendCommercialApproval(signedXml: string, fileName: string): Promise<Result<CommercialApprovalResponse, DgiiError>>;
697
+ sendCommercialApproval(signedXml: string, fileName: string, buyerHost: string): Promise<Result<B2BCommercialApprovalResponse, DgiiError>>;
523
698
  /**
524
699
  * Submit a signed range-voiding request (ANECF). Idempotent like
525
700
  * submissions, keyed on `RNC + RangoNCF + SHA-256(signed XML)`.
@@ -527,4 +702,4 @@ declare class ECF {
527
702
  voidENCF(signedXml: string, fileName: string): Promise<Result<VoidNCFResponse, DgiiError>>;
528
703
  }
529
704
 
530
- export { type AuthToken, BaseUrl, type BaseUrlKey, CONSUMPTION_SUMMARY_THRESHOLD, type CertificateInfo, type CommercialApprovalResponse, type DgiiDocumentType, DgiiError, type DgiiErrorCode, type DgiiErrorContext, type DgiiErrorOptions, type DgiiResult, ECF, ECF_TYPE_CODES, type EcfOptions, type EcfTypeCode, Endpoint, Environment, type GeneralReceptionRoute, HttpClient, type HttpClientOptions, type IdempotencyStore, InMemoryIdempotencyStore, type InMemoryIdempotencyStoreOptions, type InquiryInvoiceSummary, type InquiryStatus, type InquiryStatusCode, type InquiryStatusResponse, type InvoiceResponse, type InvoiceSummaryResponse, type Mensaje, P12Reader, type P12ReaderData, type ReceptionRoute, type RequestOptions, type ResilienceOptions, type ResiliencePolicies, SenderReceiverEndpoint, type ServiceDirectoryResponse, Signature, type SummaryReceptionRoute, type SummaryTrackingStatusResponse, type TrackStatusCode, TrackStatusEnum, type TrackingStatusResponse, Transformer, type VoidNCFResponse, type XmlCertificateValidation, buildSenderReceiverUrl, buildUrl, compareDecimal, convertECF32ToRFCE, createResiliencePolicies, defaultResilienceOptions, extractDgiiErrorMessage, generateEcfQRCodeURL, generateFcQRCodeURL, getCodeSixDigitfromSignature, getCurrentFormattedDate, getCurrentFormattedDateTime, getXmlFromBodyResponse, isEcfTypeCode, isRetryableError, isRetryableStatus, resolveReceptionRoute, roundHalfUpToCents, toDgiiError, validateXMLCertificate };
705
+ export { type AuthToken, type B2BCommercialApprovalResponse, BaseUrl, type BaseUrlKey, CONSUMPTION_SUMMARY_THRESHOLD, type CertificateInfo, type CommercialApprovalResponse, type DgiiDocumentType, DgiiError, type DgiiErrorCode, type DgiiErrorContext, type DgiiErrorOptions, type DgiiResult, ECF, ECF_TYPE_CODES, type EcfOptions, type EcfTypeCode, Endpoint, Environment, type GeneralReceptionRoute, HttpClient, type HttpClientOptions, type IdempotencyStore, InMemoryIdempotencyStore, type InMemoryIdempotencyStoreOptions, type InquiryInvoiceSummary, type InquiryStatus, type InquiryStatusCode, type InquiryStatusResponse, type InvoiceResponse, type InvoiceSummaryResponse, type MaintenanceWindow, type Mensaje, P12Reader, type P12ReaderData, PENDING_SUBMISSION_MESSAGE, type ReceptionRoute, type ReconciliationResult, type RequestOptions, type ResilienceOptions, type ResiliencePolicies, SCHEMA_DIR, SUMMARY_ACCEPTED, SUMMARY_CONDITIONAL_ACCEPTED, SUMMARY_REJECTED, type SchemaViolation, SenderReceiverEndpoint, type ServiceAvailability, type ServiceAvailabilityResponse, type ServiceDirectoryResponse, type ServiceStatusEntry, Signature, StatusEndpoint, StatusEnvironment, type SummaryReceptionRoute, type SummaryTrackingStatusResponse, type TrackStatusCode, TrackStatusEnum, type TrackingStatusResponse, Transformer, UNAVAILABLE_SCHEMAS, type ValidationOptions, type VoidNCFResponse, type XmlCertificateValidation, buildSenderReceiverUrl, buildStatusUrl, buildUrl, canReuseSequence, checkB2BApplicability, compareDecimal, convertECF32ToRFCE, createResiliencePolicies, defaultResilienceOptions, extractDgiiErrorMessage, formatViolations, generateEcfQRCodeURL, generateFcQRCodeURL, getCodeSixDigitfromSignature, getCurrentFormattedDate, getCurrentFormattedDateTime, getXmlFromBodyResponse, isEcfTypeCode, isRetryableError, isRetryableStatus, pruneEmptyValues, resolveReceptionRoute, roundHalfUpToCents, toDgiiError, validateDocument, validateXMLCertificate };