Saltar a contenido

Python

Paquete: py-dgii-ecf · importa py_dgii_ecf

httpx2 síncrono, tenacity + pybreaker y Patrón Result (result). Con verificación de tipos mediante mypy --strict.

Autentícate

from py_dgii_ecf import ECF, Environment, P12Reader

credentials = P12Reader(password).get_key_from_file("/secure/identity.p12")
if credentials.is_err():
    raise SystemExit(credentials.unwrap_err())

ecf = ECF(credentials.unwrap(), Environment.CERT)
auth = ecf.authenticate()
if auth.is_err():
    raise SystemExit(auth.unwrap_err())

print(auth.unwrap().token)

Firma un documento

from py_dgii_ecf import Signature

data = credentials.unwrap()
signed = Signature(data.key, data.cert).sign_xml(xml)
if signed.is_err():
    raise SystemExit(signed.unwrap_err())

Para construir el firmador directamente desde un archivo .p12, usa try_from_p12 — devuelve un Result en lugar de lanzar una excepción:

signer = Signature.try_from_p12("identity.p12", password)
if signer.is_err():
    raise SystemExit(signer.unwrap_err())
signed = signer.unwrap().sign_xml(xml)

Envía el documento

response = ecf.send_electronic_document(
    signed.unwrap(), "130862346E310000000001.xml"
)
if response.is_err():
    raise SystemExit(response.unwrap_err())
print(response.unwrap())

Patrón Result

result = ecf.status_track_id("track-id")
if result.is_ok():
    print(result.unwrap().estado)
else:
    error = result.unwrap_err()
    print(error.code, error.message)

API

Cada método devuelve un modelo tipado, no un dict.

Método Devuelve
authenticate(buyer_host=None) Result[AuthToken]
send_electronic_document(xml, file_name, buyer_host=None) Result[InvoiceResponse]
send_summary(xml, file_name) Result[InvoiceSummaryResponse]
send_document(xml, file_name, buyer_host=None) Result[InvoiceResponse \| InvoiceSummaryResponse]
status_track_id(track_id) Result[TrackingStatusResponse]
inquiry_status(rnc, ncf, comprador=None, codigo=None) Result[InquiryStatusResponse]
track_statuses(rnc, encf) Result[list[SummaryTrackingStatusResponse]]
get_customer_directory(rnc) Result[list[ServiceDirectoryResponse] \| None]
get_directory_listing() Result[list[ServiceDirectoryResponse]]
get_summary_invoice_inquiry(rnc, encf, codigo) Result[InquiryInvoiceSummary]
send_commercial_approval(xml, file_name, buyer_host=None) Result[CommercialApprovalResponse \| B2BCommercialApprovalResponse]
void_encf(xml, file_name) Result[VoidNCFResponse]
reconcile_submission(xml) Result[ReconciliationResult]
get_service_status() Result[list[ServiceStatusEntry]]
get_maintenance_windows() Result[list[MaintenanceWindow]]
check_service_availability(ambiente) Result[ServiceAvailabilityResponse]

Enrutamiento con send_document

send_document 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 buyer_host.

Usa resolve_reception_route(signed_xml) cuando necesites la decisión sin enviar el documento.

Estado de los servicios

get_service_status, get_maintenance_windows y check_service_availability hablan con el host statusecf.dgii.gov.do. Se autentica con una API key, no con el token bearer, así que pasa status_api_key al constructor y omite authenticate():

import os

ecf = ECF(credentials.unwrap(), Environment.PROD, status_api_key=os.environ["DGII_STATUS_API_KEY"])
availability = ecf.check_service_availability(StatusEnvironment.PRODUCTION)
if availability.is_ok() and availability.unwrap().estado == "No Disponible":
    ...  # Ventana de mantenimiento: aplaza el lote.

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 enforce_b2b_rules=False cuando necesites reproducir un escenario de certificación:

result = ecf.send_electronic_document(signed_xml, "f.xml", buyer_host)
if result.is_err() and result.unwrap_err().code is DgiiErrorCode.VALIDATION:
    ...  # El tipo o el monto no son elegibles para envío directo.

Endpoints exclusivos de producción

get_summary_invoice_inquiry (consulta de RFCE, fc.dgii.gov.do/consultarfce) solo lo expone la DGII en producción (Environment.PROD / eCF). En TesteCF o CerteCF devuelve un error VALIDATION sin emitir la solicitud:

inquiry = ecf.get_summary_invoice_inquiry(rnc, encf, codigo_seguridad)
if inquiry.is_err():
    print(inquiry.unwrap_err().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:

ack = ecf.send_commercial_approval(acecf, "acecf.xml", buyer_host)
if ack.is_ok() and not ack.unwrap().accepted:
    ...  # El receptor rechazó la aprobación; el emisor debe responder con una E34.

Validación por esquema

validate_document verifica un documento contra el XML Schema oficial de la DGII y reporta cada violación con la ruta del elemento. Los esquemas vienen empaquetados, así que la validación funciona sin red.

from py_dgii_ecf import validate_document

result = validate_document(signed_xml)
if result.is_ok():
    for violation in result.unwrap():
        print(violation.path, violation.message)

validate_before_send=True ejecuta la misma verificación antes de un envío general de e-CF y falla con VALIDATION sin llamar a la DGII:

ecf = ECF(credentials.unwrap(), Environment.PROD, validate_before_send=True)

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 en vez de pasar en silencio. Ver examples/fixtures/schemas/README.md para las razones exactas.

Redondeo monetario

Transformer.json_to_xml(..., round_monetary=True) redondea los valores decimales a dos posiciones con ROUND_HALF_UP (2.665 → 2.67, -2.675 → -2.68), igual que el SDK de TypeScript. Version y el texto no monetario nunca se redondean.

CLI

py-dgii-ecf sign invoice.xml identity.p12 --output signed.xml
py-dgii-ecf authenticate identity.p12 --environment CerteCF
py-dgii-ecf status <track-id> identity.p12 --environment CerteCF

Cada comando pide la contraseña del .p12 (--password, entrada oculta). --environment acepta TesteCF / CerteCF / eCF, o los alias dev, cert y prod.

Consulta Firma, Resiliencia y el README del paquete.