Saltar al contenido principal

Cliente Python

ecf_client.py es un cliente ligero e idiomático para la API de Facturo ECF. Funciona como cliente independiente (scripts, CLIs, cron jobs, workers de Celery, microservicios u otros frameworks web) y dentro de Odoo (ver guía de Odoo).

Características​

  • Dataclasses tipadas con acceso dual: todos los modelos heredan de BaseDTO:
    • Autocompletado y tipado: res.code, res.name, res.total_cancelled
    • Acceso tipo diccionario, compatible con código existente: res["code"], res.get("code"), "code" in res
    • Serialización: res.to_dict() y Modelo.from_dict(raw_json) (ignora campos desconocidos)
  • Enumeraciones de la DGII: DGIIEnv (ambientes) y ECFType (tipos de e-CF). También acepta strings y enteros.
  • Compatibilidad híbrida: dentro de Odoo, FacturoAPIError hereda de odoo.exceptions.UserError, por lo que los errores se muestran como diálogos nativos de Odoo sin traza. Fuera de Odoo se usa una clase UserError estándar de Python.

Requisitos e instalación​

Compatible con Python 3.7 a 3.13+ (incompatible con Python 2.x y ≤ 3.6). Solo requiere requests:

pip install requests

Descarga ecf_client.py y cópialo en tu proyecto.

Uso​

from ecf_client import FacturoClient, FacturoAPIError, DGIIEnv, ECFType

# base_url por defecto: "https://ecf.facturord.app/"
client = FacturoClient(
base_url="https://ecf.facturord.app/",
api_key="tu_api_key",
company_id="a1b2c3d4-e5f6-7890-abcd-ef1234567890",
timeout=30,
)
ParámetroDescripción
base_urlURL base de la API. Por defecto https://ecf.facturord.app/.
api_keyAPI Key con los permisos necesarios (ej.: documents.emit, documents.view).
company_idUUID de la empresa por defecto. Se puede sobrescribir por método.
timeoutTimeout por petición en segundos. Por defecto 30.

1. Anular un e-NCF no utilizado​

try:
response = client.cancel_single_sequence(
ecf_type=ECFType.E31_CREDITO_FISCAL, # o simplemente 31
encf="E310000000001",
dgii_env=DGIIEnv.TESTECF, # o "testecf"
)
print("Anulación exitosa:", response.code, response.name)
# response -> rnc='101672919', code='1', name='Aprobada', total_cancelled=1, signed_xml='...'
except FacturoAPIError as e:
print(f"No se pudo anular la secuencia: {e}")

2. Anular un rango de secuencias​

try:
response = client.cancel_sequence_range(
ecf_type=31,
from_encf="E310000000010",
to_encf="E310000000020",
)
print(f"Se anularon {response.get('total_cancelled')} secuencias.")
except FacturoAPIError as e:
print(f"Error: {e}")

Para enviar varios tipos y rangos en una sola petición usa cancel_ranges con CancellationDetail y SequenceRange:

from ecf_client import CancellationDetail, SequenceRange

res = client.cancel_ranges(
details=[
CancellationDetail(
ecf_type=ECFType.E31_CREDITO_FISCAL,
sequence_ranges=[
SequenceRange(from_encf="E310000000010", to_encf="E310000000020"),
SequenceRange.single("E310000000035"),
],
),
CancellationDetail(
ecf_type=ECFType.E32_CONSUMO,
sequence_ranges=[SequenceRange.single("E320000000100")],
),
],
dgii_env=DGIIEnv.TESTECF,
)

3. Emitir un e-CF​

from ecf_client import ECFEmissionRequest

res = client.emit_rich_ecf(
ECFEmissionRequest(
ecf=ecf_payload, # dict con la estructura del e-CF
dgii_env=DGIIEnv.TESTECF,
)
)
print(res.document_id, res.encf, res.track_id, res.security_code)

La estructura de ecf_payload se describe en Estructura del e-CF.

4. Consultar el estado por Track ID​

status = client.get_tracking_status(res.track_id)
print(status.dgii_status, status.last_status, status.last_updated_at)

5. Descargar el XML firmado​

try:
xml_bytes = client.get_signed_xml("E310000000001", dgii_env="testecf")
with open("E310000000001.xml", "wb") as f:
f.write(xml_bytes)
except FacturoAPIError as e:
print(f"XML no encontrado: {e}")

6. Reintentar una emisión​

result = client.retry_emission(document_id="9f1c2d3e-4b5a-6789-0abc-def123456789")
print(result.internal_status, result.dgii_status)

Referencia de métodos​

MétodoRetornaDescripción
cancel_ranges(details, company_id=None, dgii_env=None)CancelRangesResponseAnula rangos agrupados por tipo de e-CF (ANECF v1.0).
cancel_single_sequence(ecf_type, encf, company_id=None, dgii_env=None)CancelRangesResponseAnula un único e-NCF.
cancel_sequence_range(ecf_type, from_encf, to_encf, company_id=None, dgii_env=None)CancelRangesResponseAnula un rango continuo.
emit_rich_ecf(ecf_payload, company_id=None)ECFEmissionResponseEmite un e-CF completo (ECFEmissionRequest o dict).
get_tracking_status(track_id, company_id=None)InvoiceTrackingStatusResponseEstado en la DGII de un Track ID.
get_signed_xml(document_id_or_encf, company_id=None, dgii_env=None, download=False)bytesXML firmado por UUID o e-NCF.
retry_emission(document_id, company_id=None)EmissionResultReintenta un documento pendiente o fallido.

Errores​

Todas las fallas (HTTP 4xx/5xx, timeout o red) se lanzan como FacturoAPIError, con:

  • str(e): mensaje legible (Facturo API Error (400): ...)
  • e.status_code: código HTTP (o None en errores de red/timeout)
  • e.payload: mensaje de error devuelto por la API