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()yModelo.from_dict(raw_json)(ignora campos desconocidos)
- Autocompletado y tipado:
- Enumeraciones de la DGII:
DGIIEnv(ambientes) yECFType(tipos de e-CF). También acepta strings y enteros. - Compatibilidad híbrida: dentro de Odoo,
FacturoAPIErrorhereda deodoo.exceptions.UserError, por lo que los errores se muestran como diálogos nativos de Odoo sin traza. Fuera de Odoo se usa una claseUserErrorestá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ámetro | Descripción |
|---|---|
base_url | URL base de la API. Por defecto https://ecf.facturord.app/. |
api_key | API Key con los permisos necesarios (ej.: documents.emit, documents.view). |
company_id | UUID de la empresa por defecto. Se puede sobrescribir por método. |
timeout | Timeout 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étodo | Retorna | Descripción |
|---|---|---|
cancel_ranges(details, company_id=None, dgii_env=None) | CancelRangesResponse | Anula rangos agrupados por tipo de e-CF (ANECF v1.0). |
cancel_single_sequence(ecf_type, encf, company_id=None, dgii_env=None) | CancelRangesResponse | Anula un único e-NCF. |
cancel_sequence_range(ecf_type, from_encf, to_encf, company_id=None, dgii_env=None) | CancelRangesResponse | Anula un rango continuo. |
emit_rich_ecf(ecf_payload, company_id=None) | ECFEmissionResponse | Emite un e-CF completo (ECFEmissionRequest o dict). |
get_tracking_status(track_id, company_id=None) | InvoiceTrackingStatusResponse | Estado en la DGII de un Track ID. |
get_signed_xml(document_id_or_encf, company_id=None, dgii_env=None, download=False) | bytes | XML firmado por UUID o e-NCF. |
retry_emission(document_id, company_id=None) | EmissionResult | Reintenta 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 (oNoneen errores de red/timeout)e.payload: mensaje de error devuelto por la API