Errores¶
Todas las excepciones del SDK heredan de SmartDocError, y se dividen según de
dónde vienen.
SmartDocError
├── APIError ← la respondió SmartDoc
│ ├── AuthenticationError 401
│ ├── PermissionError 403
│ ├── NotFoundError 404
│ ├── ValidationError 422, 400, o una validación local
│ ├── ConflictError 409
│ ├── RateLimitError 429
│ └── ServerError 5xx
├── ConfigurationError ← el SDK no pudo resolver la configuración
├── SignatureError ← un webhook no validó
└── TimeoutError ← se agotó wait_until_final
Todas las APIError traen .code, .message, .status_code y .raw.
ValidationError¶
Puede venir de la API o detectarse localmente, antes de salir a la red.
try:
sd.invoices.create(recipient=..., items=[...])
except smartdoc.ValidationError as e:
print(e.code) # "VALIDATION_ERROR", o None si es local
print(e.field_errors) # {"amount": "Must be a valid amount."}
Las validaciones locales fallan sin gastar una llamada y dicen qué corregir:
sd.invoices.create(..., sale_type="Contado")
# ValidationError: 'Contado' no es un valor válido de SaleType.
# ¿Quisiste decir 'cash'? Valores válidos: 'cash', 'credit'.
sd.invoices.create(..., currency="USD")
# ValidationError: Con moneda USD hace falta exchange_rate.
smartdoc.Item("X", quantity=1, unit_amount=100, iva=smartdoc.Iva.MIXED_TEN)
# ValidationError: El tipo de IVA 'mixed_10_percent' es mixto y necesita iva_base.
Un ValidationError no se arregla reintentando: hay que corregir los datos.
PermissionError¶
try:
sd.invoices.list()
except smartdoc.PermissionError as e:
print(e.missing_permission) # "read_invoices"
Los permisos no son jerárquicos
write_invoices no habilita nada que pida read_invoices. Si una clave crea
facturas y después las consulta, necesita los dos marcados.
La clave pertenece al contribuyente, no al usuario: los permisos de quien la creó no influyen.
Acciones no permitidas¶
Una acción que no aplica al estado actual del documento llega como
ValidationError con código ACTION_NOT_ALLOWED:
try:
sd.invoices.cancel(factura.id, reason="...")
except smartdoc.ValidationError as e:
if e.code == "ACTION_NOT_ALLOWED":
# Por ejemplo, la factura todavía no está aprobada.
...
RateLimitError¶
El SDK regula las solicitudes por debajo del límite y reintenta con backoff exponencial. Si aun así llega esta excepción, es que se agotaron los reintentos.
sd = smartdoc.Client(max_retries=5) # más reintentos
sd = smartdoc.Client(rate_limit_per_second=3) # más conservador
sd = smartdoc.Client(rate_limit_per_second=0) # sin regulación
ConfigurationError¶
El SDK no pudo resolver algo que necesita para emitir:
ConfigurationError: El contribuyente tiene 2 establecimientos y no se indicó
cuál usar. Elegilo con establishment='001' al construir el cliente, o
establishment='001' en la llamada. Opciones disponibles: 001, 002.
Nunca elige por su cuenta: el código elegido queda impreso en el documento y en su CDC, y equivocarlo obliga a anular y reemitir.
TimeoutError¶
try:
factura = sd.invoices.wait_until_final(factura.id)
except smartdoc.TimeoutError as e:
print(e.last_status) # "uploaded_to_set"
No significa que el documento haya fallado: sigue su curso. Lo que se agotó es la espera.
Documentos que terminan en error¶
Un documento puede emitirse bien y aun así terminar rechazado. Eso no levanta una excepción —la llamada fue correcta—, queda en el estado del documento:
Ramificá por is_error
error_code puede venir vacío aunque el documento haya fallado, según de
dónde venga el rechazo; error_message trae el detalle en todos los casos.
La pregunta que corresponde es if factura.is_error:, que mira el estado.
Cómo se corrigen¶
Editando el documento. SmartDoc lo vuelve a procesar solo: no hay que reenviarlo ni emitir uno nuevo.
if factura.is_error:
sd.invoices.update(factura.id, recipient=..., items=[...])
factura = sd.invoices.wait_until_final(factura.id)
update reemplaza el documento entero, así que hay que mandar todos los campos,
no solo el que se corrige.
Se puede editar en cualquier estado menos uploaded_to_set y approved_by_set.
Ahí la API responde EDIT_NOT_ALLOWED, y lo que corresponde es anular y
reemitir.
Idempotencia¶
La API exige una clave de idempotencia al crear documentos. El SDK la pone: cada
create() va con una clave nueva, así que no hay que hacer nada.
Para controlarla vos, pasala con idempotency_key:
Dos llamadas con la misma clave devuelven el mismo documento, sin emitirlo dos veces. La respuesta queda guardada 24 horas.
Reintentos¶
El SDK reintenta ante 429, 5xx y fallas de red, con backoff exponencial y jitter.
| Solicitud | ¿Reintenta ante 5xx? | ¿Ante 429? |
|---|---|---|
GET, PUT, DELETE |
Sí | Sí |
POST de creación |
Sí, lleva clave de idempotencia | Sí |
POST de acción (cancel, …) |
No | Sí |
Una acción no lleva clave de idempotencia, así que ante un 5xx no se puede saber si el servidor alcanzó a ejecutarla y reintentar podría repetirla. Un 429, en cambio, se rechaza antes de ejecutar nada, y por eso siempre se reintenta.
Loguear¶
Para ver el detalle de las solicitudes HTTP: