TypeScript¶
Paquete: @deuriib/dgii-ecf
Pensado primero para Bun, fetch nativo, TypeScript estricto y patrón
result (neverthrow). Resiliencia
con cockatiel.
Autentícate¶
import { ECF, Environment, P12Reader } from '@deuriib/dgii-ecf';
const credentials = new P12Reader(
process.env.CERT_PASSWORD ?? '',
).getKeyFromFile('/secure/identity.p12');
if (credentials.isErr()) throw credentials.error;
const ecf = new ECF(credentials.value, Environment.Cert);
const auth = await ecf.authenticate();
if (auth.isErr()) throw auth.error;
console.log(auth.value.token);
Firma un documento¶
import { Signature } from '@deuriib/dgii-ecf';
const signed = new Signature(
credentials.value.key!,
credentials.value.cert!,
).signXml(xml);
if (signed.isErr()) throw signed.error;
Envía el documento¶
const response = await ecf.sendElectronicDocument(
signed.value,
'130862346E310000000001.xml',
);
response.match(
(invoice) => console.log(invoice.trackId),
(error) => console.error(error.code, error.message),
);
Patrón Result¶
Toda operación que puede fallar devuelve un Result:
const result = await ecf.statusTrackId('track-id');
if (result.isOk()) {
console.log(result.value.estado);
} else {
console.error(result.error.code, result.error.message);
}
API¶
| Método | Devuelve |
|---|---|
authenticate(buyerHost?) |
Result<AuthToken> |
sendElectronicDocument(xml, fileName, buyerHost?) |
Result<InvoiceResponse> |
sendSummary(xml, fileName) |
Result<InvoiceSummaryResponse> |
sendDocument(xml, fileName, buyerHost?) |
Result<InvoiceResponse \| InvoiceSummaryResponse> |
statusTrackId(trackId) |
Result<TrackingStatusResponse> |
inquiryStatus(rnc, ncf, comprador?, codigo?) |
Result<InquiryStatusResponse> |
trackStatuses(rnc, encf) |
Result<SummaryTrackingStatusResponse[]> |
getCustomerDirectory(rnc) |
Result<ServiceDirectoryResponse[] \| undefined> |
getDirectoryListing() |
Result<ServiceDirectoryResponse[]> |
getSummaryInvoiceInquiry(rnc, encf, codigo) |
Result<InquiryInvoiceSummary> |
sendCommercialApproval(xml, fileName, buyerHost?) |
Result<CommercialApprovalResponse \| B2BCommercialApprovalResponse> |
voidENCF(xml, fileName) |
Result<VoidNCFResponse> |
reconcileSubmission(xml) |
Result<ReconciliationResult> |
getServiceStatus() |
Result<ServiceStatusEntry[]> |
getMaintenanceWindows() |
Result<MaintenanceWindow[]> |
checkServiceAvailability(ambiente) |
Result<ServiceAvailabilityResponse> |
Enrutamiento con sendDocument¶
sendDocument lee el elemento raíz, TipoeCF y MontoTotal del XML firmado
y elige el endpoint por ti: un RFCE va a la recepción de resúmenes, un ECF a
la recepción general. Una E32 menor a RD$250,000 se rechaza con VALIDATION
—construye primero el RFCE— y un RFCE no puede entregarse directo a un
buyerHost.
Usa resolveReceptionRoute(signedXml) cuando necesites la decisión sin enviar
el documento.
Estado de los servicios¶
getServiceStatus, getMaintenanceWindows y checkServiceAvailability hablan
con el host statusecf.dgii.gov.do. Se autentica con una API key, no con el
token bearer, así que pasa statusApiKey al constructor y omite
authenticate():
const ecf = new ECF(credentials.value, Environment.Prod, {
statusApiKey: process.env.DGII_STATUS_API_KEY,
});
const availability = await ecf.checkServiceAvailability(
StatusEnvironment.Production,
);
if (availability.isOk() && availability.value.estado === 'No Disponible') {
// Ventana de mantenimiento: aplaza el lote.
}
Aplicabilidad B2B¶
Aplicabilidad B2B¶
El envío directo Emisor↔Receptor (buyerHost / buyer_host) no aplica a todos
los tipos. La DGII excluye la E32 menor a RD$250,000 (solo aplica el resumen
RFCE) y los tipos E41, E43, E45, E46 y E47. La E31, E33, E34, E44 y una E32
igual o mayor al umbral sí pueden entregarse entre pares.
El cliente lo verifica antes de emitir la solicitud y falla con VALIDATION sin
llamar al host del comprador. Desactiva la validación con
enforceB2BRules: false (TypeScript) o enforce_b2b_rules=False (Python)
cuando necesites reproducir un escenario de certificación:
const result = await ecf.sendElectronicDocument(xml, 'f.xml', buyerHost);
if (result.isErr() && result.error.code === 'VALIDATION') {
// El tipo o el monto no son elegibles para envío directo.
}
Endpoints exclusivos de producción¶
getSummaryInvoiceInquiry (consulta de RFCE, fc.dgii.gov.do/consultarfce) solo
lo expone la DGII en producción (Environment.Prod / eCF). En Dev o
Cert devuelve un error VALIDATION sin emitir la solicitud:
const inquiry = await ecf.getSummaryInvoiceInquiry(rnc, encf, codigoSeguridad);
if (inquiry.isErr()) {
console.error(inquiry.error.code); // 'VALIDATION' outside production
}
El envío directo de un ACECF va al endpoint fe/aprobacioncomercial/api/ecf
del comprador y devuelve B2BCommercialApprovalResponse en lugar del payload
{codigo, estado} de la DGII. La DGII documenta HTTP 200 como satisfactorio
y HTTP 400 como insatisfactorio, así que un 400 se reporta como
accepted: false y no como un fallo de transporte:
const ack = await ecf.sendCommercialApproval(acecf, 'acecf.xml', buyerHost);
if (ack.isOk() && !ack.value.accepted) {
// El receptor rechazó la aprobación; el emisor debe responder con una E34.
}
Validación por esquema¶
validateDocument verifica un documento contra el XML Schema oficial de la
DGII y reporta cada violación con la ruta del elemento. Usa xmllint por
subprocess, así que no hay motor empaquetado ni dependencia nativa:
import { validateDocument } from '@deuriib/dgii-ecf';
const result = validateDocument(signedXml);
if (result.isOk()) {
for (const violation of result.value) {
console.log(violation.path, violation.message);
}
}
validateBeforeSend: true ejecuta la misma verificación antes de un envío
general de e-CF y falla con VALIDATION sin llamar a la DGII:
const ecf = new ECF(credentials.value, Environment.Prod, {
validateBeforeSend: true,
});
El binario se busca en PATH; usa DGII_XMLLINT_BIN (o la opción xmllintBin
del cliente) para apuntarlo. Si falta el binario es un error, nunca un pase
silencioso.
Se empaquetan doce esquemas: e-CF 32, 33, 34, 41, 43, 44, 45, 46, 47, ANECF,
ARECF y Semilla. Tres esquemas publicados no compilan — e-CF 31,
RFCE 32 y ACECF — así que esos tipos se reportan como esquema no
disponible. Ver examples/fixtures/schemas/README.md para las razones.
Redondeo monetario¶
Transformer.json2xml(data, true) redondea los valores decimales a dos
posiciones con ROUND_HALF_UP (2.665 → 2.67, -2.675 → -2.68),
calculado sobre la cadena decimal y no con toFixed (que pierde precisión:
(2.675).toFixed(2) === '2.67'). Version y el texto no monetario nunca se
redondean.
Consulta Firma, Resiliencia y el README del paquete.