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.