Referencia de la API¶
Clientes¶
smartdoc.Client ¶
Punto de entrada al SDK.
::
import smartdoc
sd = smartdoc.Client(api_key="pk_...")
factura = sd.invoices.create(
receptor=smartdoc.Receptor.entity(
ruc="80012345-1", social_name="Cliente S.A."
),
items=[smartdoc.Item("Consultoría", quantity=1, unit_amount=1_100_000)],
)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
api_key
|
str | None
|
la API Key del contribuyente. Si no se pasa, se lee de
|
None
|
base_url
|
str | None
|
la URL de la API v2. Si no se pasa, se lee de
|
None
|
establishment
|
str | int | None
|
código de establecimiento por defecto. Solo hace falta si el contribuyente tiene más de uno. |
None
|
dispatch_point
|
str | int | None
|
punto de expedición por defecto. Ídem. |
None
|
stamp
|
tuple[str, str] | str | None
|
timbrado, como |
None
|
timeout
|
float
|
segundos de espera por solicitud. |
30.0
|
max_retries
|
int
|
reintentos ante 429, 5xx y fallas de red. |
3
|
rate_limit_per_second
|
float
|
regulación preventiva. La API permite 5 por
segundo; poner |
5.0
|
auto_fill
|
bool
|
si el SDK completa los datos del emisor. Apagarlo obliga a mandar los siete campos en cada documento. |
True
|
smartdoc.async_client.AsyncClient ¶
Igual que :class:smartdoc.Client, con await.
::
async with smartdoc.AsyncClient(api_key="pk_...") as sd:
factura = await sd.invoices.create(
receptor=smartdoc.Receptor.entity(
ruc="80012345-1", social_name="Cliente S.A."
),
items=[smartdoc.Item("Consultoría", quantity=1, unit_amount=1_100_000)],
)
Los parámetros son los mismos que los del cliente síncrono.
Constructores de datos¶
smartdoc.Recipient
dataclass
¶
Datos del receptor de un documento.
En general no se instancia directo: se usan los constructores
:meth:person, :meth:entity, :meth:foreign, :meth:not_nominated,
:meth:government, :meth:diplomatic o :meth:from_client_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client_type
|
ClientType
|
tipo de receptor. Uno de |
required |
ruc
|
str
|
RUC con dígito verificador. Con una persona que no es contribuyente va la cédula; con un extranjero, su documento. |
''
|
social_name
|
str
|
razón social o nombre, tal como va impreso. |
''
|
fantasy_name
|
str | None
|
nombre de fantasía. |
None
|
is_taxpayer
|
bool
|
si el receptor es contribuyente. |
False
|
country_code
|
str
|
código de país. Por defecto |
PARAGUAY
|
city
|
City | None
|
ciudad, con :func: |
None
|
address
|
str | None
|
dirección. Si se pasa, hacen falta también |
None
|
house_number
|
int | None
|
número de casa. |
None
|
email
|
str | None
|
correo al que SmartDoc manda el documento. |
None
|
phone
|
str | None
|
teléfono. |
None
|
code
|
str | None
|
identificador propio, de 3 a 15 caracteres. Queda impreso en el documento y sirve para conciliar contra tu base. |
None
|
client_id
|
int | None
|
id del cliente en SmartDoc, si ya está cargado. |
None
|
extra
|
dict[str, Any]
|
campos crudos que el SDK no modela. |
dict()
|
person
classmethod
¶
Una persona física.
ruc es el RUC con dígito verificador, o la cédula si no es
contribuyente.
entity
classmethod
¶
Una empresa o entidad con RUC.
foreign
classmethod
¶
Un cliente del exterior.
Es el único tipo que puede llevar dirección sin códigos geográficos paraguayos.
not_nominated
classmethod
¶
Venta sin identificar al comprador.
Es la factura innominada del mostrador. Se puede nominar después con
sd.invoices.nominate(id, client_id=...).
government
classmethod
¶
Un organismo del Estado.
diplomatic
classmethod
¶
Una misión diplomática con exoneración fiscal.
from_client_id
classmethod
¶
Un cliente ya cargado en SmartDoc, por su id.
Igual hay que dar los datos que van impresos en el documento; el id sirve para que SmartDoc lo asocie a su ficha.
validate_for ¶
Verifica que este tipo de receptor sirva para el documento.
Los recibos, por ejemplo, aceptan solo persona, entidad y extranjero.
smartdoc.Item
dataclass
¶
Un ítem de un documento.
total_amount se calcula como quantity * unit_amount salvo que se
pase explícitamente.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
str
|
descripción del producto o servicio. |
required |
quantity
|
float | int | Decimal
|
cantidad. |
required |
unit_amount
|
float | int | Decimal
|
precio unitario, con el IVA ya incluido. |
required |
iva
|
Iva | str
|
tratamiento de IVA de este ítem. Si no se pasa, se emite al
10%, que es la tasa general. Los cinco valores posibles son
El tratamiento es una propiedad del producto, no de su categoría comercial: dentro de cualquier categoría hay excepciones. Emitir con el tratamiento equivocado obliga a anular y reemitir, así que conviene guardarlo en la ficha del producto y no inferirlo. |
TEN
|
code
|
str | None
|
código interno. Si no se pasa, se numera por posición. |
None
|
measure_unit
|
MeasureUnit | int
|
unidad de medida SIFEN. Por defecto |
UNIT
|
discount
|
float | int | Decimal
|
descuento por unidad, no del total de la línea. Así lo interpreta la validación del servidor, que multiplica por la cantidad. |
0
|
iva_base
|
float | int | Decimal | None
|
porcentaje gravado, solo para los tipos de IVA mixtos. |
None
|
obs
|
str | None
|
observación de la línea. |
None
|
total_amount
|
float | int | Decimal | None
|
total de la línea, si se quiere fijar en vez de dejar que
se calcule como |
None
|
dncp_level_code_general
|
str | None
|
código de nivel general de la DNCP, en contrataciones públicas. |
None
|
dncp_level_code_specific
|
str | None
|
código de nivel específico de la DNCP. |
None
|
iva_type
property
¶
El IVA como miembro del catálogo, sin importar cómo se haya pasado.
line_discount
property
¶
Descuento total de la línea: el unitario por la cantidad.
taxable_share
property
¶
Fracción de la línea que está gravada.
Es 1 salvo en los tipos mixtos, donde la define iva_base.
iva_amount
property
¶
IVA contenido en la línea.
El precio ya lo incluye: al 10% se divide por 11, al 5% por 21.
smartdoc.AssociatedDocument
dataclass
¶
Referencia al documento que se corrige o recibe.
Se construye con :meth:electronic, :meth:printed o :meth:none, que
completan los campos que corresponden a cada tipo.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
type
|
AssociatedDocumentType
|
uno de |
required |
cdc
|
str | None
|
CDC del documento electrónico, de 44 caracteres. |
None
|
printed_type
|
PrintedDocumentType | int | None
|
qué documento impreso es. Uno de |
None
|
stamp_identifier
|
str | None
|
timbrado del documento impreso, 8 dígitos. |
None
|
establishment_code
|
str | None
|
establecimiento del documento impreso, 3 dígitos. |
None
|
dispatch_point_code
|
str | None
|
punto de expedición del impreso, 3 dígitos. |
None
|
number
|
str | None
|
número del documento impreso, 7 dígitos. |
None
|
document_date
|
date | str | None
|
fecha del documento impreso. |
None
|
electronic
classmethod
¶
Una factura electrónica, referida por su CDC de 44 caracteres.
printed
classmethod
¶
printed(
*,
stamp_identifier: str,
establishment_code: str,
dispatch_point_code: str,
number: str,
document_date: date | str,
printed_type: PrintedDocumentType
| int = PrintedDocumentType.INVOICE,
) -> AssociatedDocument
Un documento preimpreso, de antes de la facturación electrónica.
none
classmethod
¶
Sin documento asociado. Solo lo aceptan los recibos.
smartdoc.builders.items.totals_for ¶
totals_for(
items: Sequence[Item],
*,
global_discount: float | int | Decimal = 0,
sedeco_discount: float | int | Decimal = 0,
) -> dict[str, Any]
Totales del documento a partir de sus ítems.
Devuelve amount más el desglose de IVA, listo para el cuerpo. El
amount replica la fórmula que valida el servidor: suma de las líneas,
menos los descuentos por ítem, menos el global, menos el de SEDECO.
Documentos¶
smartdoc.resources.invoices.Invoices ¶
Bases: DocumentResource
Facturas electrónicas (/invoices).
Es el documento con más operaciones: seis acciones, borradores y nominación.
create ¶
create(
*,
recipient: Recipient,
items: Sequence[Item],
sale_type: SaleType | str = SaleType.CASH,
transaction_type: TransactionType
| int = TransactionType.SERVICES,
currency: Currency | str = Currency.PYG,
invoice_date: date | datetime | str | None = None,
exchange_rate: float | None = None,
description: str | None = None,
obs: str | None = None,
term: str | None = None,
invoice_number: str | None = None,
global_discount: float = 0,
sedeco_discount: float = 0,
draft: bool = False,
establishment: str | int | None = None,
dispatch_point: str | int | None = None,
idempotency_key: str | None = None,
**extra: Any,
) -> Document
Emite una factura.
Los datos del emisor, el timbrado, los totales y el desglose de IVA los completa el SDK. Lo que hay que dar es a quién se le factura y qué se le factura.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recipient
|
Recipient
|
a quién se le factura. Ver :class: |
required |
items
|
Sequence[Item]
|
qué. Ver :class: |
required |
sale_type
|
SaleType | str
|
contado o crédito. Ojo: los valores son |
CASH
|
transaction_type
|
TransactionType | int
|
tipo de operación según SIFEN. |
SERVICES
|
currency
|
Currency | str
|
moneda. Con cualquiera distinta de PYG hace falta
|
PYG
|
invoice_date
|
date | datetime | str | None
|
fecha de emisión. Por defecto, ahora. |
None
|
draft
|
bool
|
si es |
False
|
establishment
|
str | int | None
|
código de establecimiento, si no es el del cliente. |
None
|
dispatch_point
|
str | int | None
|
punto de expedición, si no es el del cliente. |
None
|
idempotency_key
|
str | None
|
si se quiere controlar la clave. Por defecto se genera una nueva por llamada. |
None
|
**extra
|
Any
|
campos crudos que el SDK no modela. |
{}
|
Returns:
| Type | Description |
|---|---|
Document
|
La factura recién creada. Su |
Document
|
|
Document
|
consultando con :meth: |
update ¶
Reemplaza una factura.
No es un parche: la API valida el cuerpo completo igual que al crear, así que hay que pasar el documento entero.
Es la forma de corregir una factura que quedó en error. Al editarla, SmartDoc la vuelve a procesar sola: no hay que reenviarla ni reemitirla.
Se puede editar en cualquier estado salvo uploaded_to_set y
approved_by_set; ahí la API responde 400 con EDIT_NOT_ALLOWED y
la vía es anular y reemitir.
nominate ¶
Le pone nombre a una factura innominada.
Se usa cuando se emitió sin identificar al comprador y después el cliente
pide la factura a su nombre. El cliente tiene que estar cargado en
SmartDoc: client_id es su id, el que devuelve sd.clients.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client_id
|
int
|
el cliente al que se nomina la factura. |
required |
reason
|
str | None
|
motivo del evento, entre 5 y 500 caracteres. Si no se pasa, SmartDoc usa uno por omisión. |
None
|
La nominación es asíncrona y la API responde su id, no el documento: hay
que releerlo con :meth:get para ver cómo quedó.
list ¶
list(
*,
status: str | None = None,
stamp_id: int | None = None,
client_id: str | None = None,
establishment_id: int | None = None,
date_from: date | str | None = None,
date_to: date | str | None = None,
limit: int | None = None,
cursor: str | None = None,
) -> Any
Lista facturas, de la más reciente a la más vieja.
date_to incluye el día que nombra: pedir hasta el 31 de julio trae
las facturas del 31.
smartdoc.resources.receipts.Receipts ¶
Bases: DocumentResource
Recibos electrónicos (/receipts).
create ¶
create(
*,
recipient: Recipient,
amount: float | int | Decimal,
payment_method: ReceiptPaymentMethod
| str = ReceiptPaymentMethod.CASH,
associated_document: AssociatedDocument | None = None,
items: Sequence[ReceiptItem] | None = None,
currency: Currency | str = Currency.PYG,
receipt_date: date | datetime | str | None = None,
exchange_rate: float | None = None,
invoice_id: int | None = None,
check_bank: str | None = None,
check_number: str | None = None,
description: str | None = None,
obs: str | None = None,
draft: bool = False,
establishment: str | int | None = None,
dispatch_point: str | int | None = None,
idempotency_key: str | None = None,
**extra: Any,
) -> Document
Emite un recibo.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
amount
|
float | int | Decimal
|
monto cobrado. |
required |
payment_method
|
ReceiptPaymentMethod | str
|
efectivo, cheque o transferencia. Con cheque hay que
dar además |
CASH
|
associated_document
|
AssociatedDocument | None
|
la factura que se está cobrando. A diferencia de
las notas, acá es opcional: se puede usar
|
None
|
Returns:
| Type | Description |
|---|---|
Document
|
El recibo. Su estado final de éxito es |
Document
|
|
smartdoc.resources.notes.CreditNotes ¶
Bases: _Nota
Notas de crédito electrónicas (/credit_notes).
smartdoc.resources.notes.DebitNotes ¶
Bases: _Nota
Notas de débito electrónicas (/debit_notes).
No tienen /kude en la API v2.
smartdoc.resources.remission_notes.RemissionNotes ¶
Bases: DocumentResource
Notas de remisión electrónicas (/remission_notes).
create ¶
create(
*,
recipient: Recipient,
items: Sequence[RemissionItem],
departure: TransportEndpoint,
arrival: TransportEndpoint,
vehicle: Vehicle,
carrier: Carrier,
estimated_kilometers: int,
start_date: date | str,
end_date: date | str,
associated_document: Any = None,
reason: RemissionReason | int = RemissionReason.SALE,
responsible: RemissionResponsible
| int = RemissionResponsible.INVOICE_ISSUER,
transport_type: TransportType | int = TransportType.OWN,
transport_mode: TransportMode
| int = TransportMode.LAND,
freight_responsible: FreightResponsible
| int = FreightResponsible.OWN_TRANSPORT,
remission_date: date | datetime | str | None = None,
invoice_id: int | None = None,
description: str | None = None,
obs: str | None = None,
number: str | None = None,
establishment: str | int | None = None,
dispatch_point: str | int | None = None,
idempotency_key: str | None = None,
**extra: Any,
) -> Document
Emite una nota de remisión.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
departure
|
TransportEndpoint
|
de dónde sale la mercadería. |
required |
arrival
|
TransportEndpoint
|
a dónde llega. |
required |
estimated_kilometers
|
int
|
distancia estimada del traslado. Obligatorio. |
required |
start_date
|
date | str
|
fecha estimada de inicio del traslado. Obligatorio. |
required |
end_date
|
date | str
|
fecha estimada de fin. Obligatorio. |
required |
associated_document
|
Any
|
documento que ampara el traslado. Si no se pasa,
se manda |
None
|
reason
|
RemissionReason | int
|
motivo del traslado. SIFEN define 15; SmartDoc solo ofrece
|
SALE
|
smartdoc.resources.auto_invoices.AutoInvoices ¶
Bases: DocumentResource
Autofacturas electrónicas (/auto_invoices).
create ¶
create(
*,
provider: Provider,
items: Sequence[Item],
transaction_address: str,
transaction_city: City,
transaction_house_number: int,
certificate_code: str,
certificate_number: str,
currency: Currency | str = Currency.PYG,
auto_invoice_date: date | datetime | str | None = None,
exchange_rate: float | None = None,
description: str | None = None,
obs: str | None = None,
term: str | None = None,
number: str | None = None,
global_discount: float = 0,
establishment: str | int | None = None,
dispatch_point: str | int | None = None,
idempotency_key: str | None = None,
**extra: Any,
) -> Document
Emite una autofactura.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
Provider
|
a quién se le compró. Ver :class: |
required |
transaction_address
|
str
|
dónde se hizo la operación. La DNIT lo pide aparte del domicilio del proveedor, porque pueden no coincidir. |
required |
Piezas de la nota de remisión¶
smartdoc.resources.remission_notes.RemissionItem
dataclass
¶
Un bien trasladado. Sin monto: la remisión no liquida impuestos.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
str
|
qué se traslada. |
required |
quantity
|
float | int
|
cantidad. |
required |
code
|
str | None
|
código interno. Si no se pasa, se numera por posición. |
None
|
measure_unit
|
MeasureUnit | int
|
unidad de medida SIFEN. Por defecto |
UNIT
|
obs
|
str | None
|
observación de la línea. |
None
|
smartdoc.resources.remission_notes.TransportEndpoint
dataclass
¶
Un extremo del traslado: de dónde sale o a dónde llega.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
address
|
str
|
dirección del local. |
required |
city
|
City
|
ciudad, con :func: |
required |
house_number
|
int
|
número de casa. Obligatorio en los dos extremos. |
0
|
to_api ¶
Serializa con prefijo transportDeparture o transportArrival.
smartdoc.resources.remission_notes.Vehicle
dataclass
¶
El vehículo que traslada.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
brand
|
str
|
marca, de 1 a 10 caracteres. |
required |
type
|
str
|
descripción libre del vehículo, de 4 a 10 caracteres: |
required |
document_type
|
VehicleIdentificationType | int
|
cómo se identifica el vehículo. Decide cuál de los dos números hay que dar; el otro se ignora. |
REGISTRATION
|
document_number
|
str
|
número de identificación, de 1 a 20 caracteres.
Obligatorio con |
''
|
registration_number
|
str
|
matrícula, de 6 o 7 caracteres. Obligatorio con
|
''
|
smartdoc.resources.remission_notes.Carrier
dataclass
¶
El transportista y su conductor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
social_name
|
str
|
razón social o nombre del transportista. |
required |
ruc
|
str
|
RUC con dígito verificador, si es contribuyente. |
''
|
is_taxpayer
|
bool
|
si el transportista es contribuyente. |
False
|
document_type
|
CarrierDocumentType | int
|
tipo de documento del transportista. Uno de
|
PARAGUAYAN_ID
|
document_number
|
str
|
número de ese documento. |
''
|
address
|
str
|
dirección del transportista. |
''
|
driver_name
|
str
|
nombre del conductor. |
''
|
driver_document_number
|
str
|
documento del conductor. |
''
|
driver_address
|
str
|
dirección del conductor. |
''
|
Piezas de la autofactura¶
smartdoc.resources.auto_invoices.Provider ¶
El proveedor al que se le compró.
Se construye con :meth:no_ruc o :meth:foreign, según por qué el proveedor
no puede emitir factura.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider_type
|
ProviderType | str
|
por qué no puede emitir factura. |
required |
full_name
|
str
|
nombre completo del proveedor. |
required |
document_type
|
ProviderDocumentType | str
|
tipo de documento. Uno de |
required |
document_number
|
str
|
número de ese documento. |
required |
address
|
str
|
domicilio del proveedor. Va aparte del lugar donde se hizo la
operación, que se pasa en |
required |
city
|
City
|
ciudad, con :func: |
required |
house_number
|
int
|
número de casa. |
0
|
no_ruc
classmethod
¶
no_ruc(
*,
full_name: str,
document_number: str,
address: str,
city: City,
document_type: ProviderDocumentType
| str = ProviderDocumentType.PARAGUAYAN_ID,
house_number: int = 0,
) -> Provider
Un proveedor paraguayo que no es contribuyente.
foreign
classmethod
¶
foreign(
*,
full_name: str,
document_number: str,
address: str,
city: City,
document_type: ProviderDocumentType
| str = ProviderDocumentType.PASSPORT,
house_number: int = 0,
) -> Provider
Un proveedor del exterior.
Piezas del recibo¶
smartdoc.resources.receipts.ReceiptItem
dataclass
¶
Un concepto del recibo.
Mucho más simple que el ítem de una factura: acá no se liquida IVA.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
description
|
str
|
concepto que se cobra. |
required |
amount
|
float | int | Decimal
|
monto del concepto. |
required |
Recursos de soporte¶
smartdoc.resources.support.Stamps ¶
smartdoc.resources.support.Clients ¶
Bases: Resource[Client]
Clientes (/clients).
Cargar un cliente acá es opcional para facturar —los datos del receptor van en el documento— pero sirve para reusarlos y para nominar una factura innominada después.
smartdoc.resources.support.Establishments ¶
Bases: Resource[Establishment]
Establecimientos (/establishments).
La API no expone borrado: un establecimiento con documentos emitidos no puede desaparecer sin romper la trazabilidad.
smartdoc.resources.support.DispatchPoints ¶
Bases: Resource[DispatchPoint]
Puntos de expedición (/dispatch_points).
Como los establecimientos, no se pueden borrar por la API.
smartdoc.resources.support.TaxPayerResource ¶
smartdoc.resources.support.ReceivedDocuments ¶
Documentos recibidos de proveedores (/received_documents).
Sirven para registrar las compras: facturas, notas de crédito y de débito que otros contribuyentes emitieron a nombre propio.
lookup_by_cdc ¶
Consulta un documento en la DNIT por su CDC.
Trae los datos del documento tal como los tiene la DNIT, sin registrarlo. Sirve para verificar una factura de proveedor antes de cargarla.
smartdoc.resources.support.Sandbox ¶
Operaciones que solo existen en cuentas demo (/sandbox).
En una cuenta demo los documentos se generan de forma válida pero no se envían a la DNIT: la respuesta se simula. Eso permite integrar y probar todo el circuito, webhooks incluidos.
Por defecto todo se aprueba. Para probar el camino de error hay dos formas:
- RUC de receptor reservado.
99999901fuerza un rechazo de la DNIT con código0160;99999902fuerza un error de procesamiento de lote con código0301. Son la parte numérica del RUC: hay que mandarlos con dígito verificador,"99999901-7", porque la API lo exige. - Este endpoint, que cambia el estado de un documento ya emitido.
En ambos casos se disparan los mismos eventos y webhooks que en el flujo real.
force_status ¶
Fuerza el estado de un documento ya emitido.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cdc
|
str
|
el CDC del documento. |
required |
status
|
str
|
|
required |
Raises:
| Type | Description |
|---|---|
ValidationError
|
si la cuenta no es demo, la API responde
|
Base compartida¶
smartdoc.resources.base.Resource ¶
Bases: Generic[R]
CRUD sobre un recurso de la API.
list ¶
Una página de resultados.
Para recorrer todo sin escribir el bucle del cursor::
for factura in sd.invoices.list().auto_paging_iter():
...
smartdoc.resources.base.DocumentResource ¶
Un recurso que además emite documentos electrónicos.
cancel ¶
Anula el documento ante la DNIT.
Solo se puede anular un documento aprobado, y el motivo es obligatorio.
La anulación es asíncrona: el documento pasa a cancellation_requested
y termina en cancelled.
No devuelve el documento porque la API no lo manda —responde el id de la
anulación—; para ver cómo quedó hay que releerlo con :meth:get o
esperarlo con :meth:wait_until_final.
resend_to_set ¶
Reenvía a la DNIT un documento que quedó en error.
Como :meth:cancel, no devuelve el documento: hay que releerlo.
kude_url ¶
URL temporal del KuDE en PDF.
Está disponible recién cuando el documento se generó. Antes, la API responde 404.
wait_until_final ¶
Consulta el documento hasta que llegue a un estado final.
Sirve para el flujo "emito y necesito el CDC ahora". Si se puede escuchar webhooks, es mejor eso: esto consume cuota del límite de 5 solicitudes por segundo.
El plazo por omisión es holgado a propósito: un documento pasa por varios estados intermedios antes de quedar aprobado, y cada salto lo da el servidor por su cuenta, así que el recorrido completo puede llevar minutos. Agotar la espera no cancela nada —el documento sigue su curso—, así que cortar temprano solo confunde.
Ojo con los estados intermedios que parecen finales:
recoverable_error no corta la espera, porque SmartDoc lo
reintenta solo.
Raises:
| Type | Description |
|---|---|
TimeoutError
|
si se agota el tiempo. El documento sigue su curso; lo que se agotó es la espera. |
Objetos de respuesta¶
smartdoc.models.Document ¶
Bases: Record
Un documento electrónico: factura, recibo, nota o autofactura.
status
property
¶
Estado actual. Devuelve el valor crudo si SmartDoc agregó uno nuevo.
cdc
property
¶
Código de Control de 44 caracteres.
Es None mientras el documento no se generó, y no existe en los
recibos, que no se envían a la DNIT.
is_approved
property
¶
Si quedó válido fiscalmente.
Para un recibo, alcanza con estar generado: no pasa por la DNIT.
error_code
property
¶
Código del error, o None si el documento no está en error.
Puede ser None aun con el documento en error, según de dónde venga
el rechazo; :attr:error_message trae el detalle en todos los casos.
Para ramificar conviene :attr:is_error.
error_message
property
¶
Descripción del error, o None si el documento no está en error.
smartdoc.models.TaxPayer ¶
Bases: Record
El contribuyente dueño de la API Key.
is_demo
property
¶
En una cuenta demo los documentos no se envían a la DNIT.
La respuesta se simula, así que se puede integrar y probar todo el circuito, webhooks incluidos.
Con una salvedad: antes de enviar nada, SmartDoc verifica que el
certificado del contribuyente esté vigente, y esa verificación no
distingue si la cuenta es demo. Con el certificado vencido el documento
queda en error con código 2450 y nunca llega a la simulación.
Los recibos no aplican esa verificación.
smartdoc.models.Record ¶
Un objeto de la API, con acceso por clave a lo que el SDK no modela.
No hereda de Mapping a propósito: :class:Document necesita exponer
items como los ítems del documento, y eso chocaría con el items() de
la interfaz de mapeo. Para el JSON completo está :attr:raw.
Paginación¶
smartdoc._core.pagination.Page
dataclass
¶
Bases: Generic[T]
Una página de resultados.
Se comporta como una secuencia: len(page), page[0], for x in page
recorren solo esta página. Para recorrer todas, usar
:meth:auto_paging_iter.
auto_paging_iter ¶
Recorre todos los resultados, siguiendo el cursor solo.
El transporte regula a 5 solicitudes por segundo, así que recorrer un listado largo es lento pero no dispara 429.
smartdoc._core.pagination.AsyncPage
dataclass
¶
Webhooks¶
smartdoc.Webhooks ¶
Recibe, verifica y rutea los webhooks de SmartDoc.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
secret
|
str
|
el secreto del endpoint, que SmartDoc muestra al crearlo en el panel. Cada endpoint tiene el suyo. |
required |
max_age
|
float | None
|
segundos de tolerancia para el |
300.0
|
dedupe
|
bool
|
si se descartan las entregas repetidas. |
True
|
dedupe_size
|
int
|
cuántos ids de entrega recordar. |
1000
|
on ¶
Registra un handler para uno o varios tipos de evento.
::
@webhooks.on("invoice.approved")
def aprobada(evento):
print(evento.entity_id, evento.cdc)
@webhooks.on(Event.INVOICE_ERROR, Event.CREDIT_NOTE_ERROR)
def falla(evento):
avisar(evento.error_message)
on_any_error ¶
Registra un handler para los cinco eventos *.error.
Son cinco y no seis: receipt.error no existe, porque un recibo que
falla queda en pending y se reintenta.
handlers_for ¶
Los handlers que aplican a un tipo de evento.
signature_for ¶
La firma esperada para un cuerpo, con el formato del encabezado.
verify ¶
Verifica la firma y devuelve el evento.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
body
|
bytes | str
|
el cuerpo crudo de la solicitud. Si se pasa el JSON reserializado, la firma no va a validar. |
required |
headers
|
Mapping[str, str]
|
los encabezados. La búsqueda no distingue mayúsculas. |
required |
Raises:
| Type | Description |
|---|---|
SignatureError
|
si falta la firma, no coincide, o el evento es demasiado viejo. |
is_duplicate ¶
Si esta entrega ya se procesó, y la marca como vista.
SmartDoc reintenta hasta 5 veces, y una entrega que el servidor procesó pero respondió tarde se vuelve a mandar. Sin esto, un webhook lento genera trabajo duplicado.
dispatch ¶
Verifica, deduplica y corre los handlers.
Si un handler levanta una excepción, se loguea y no se propaga: reintentar no arregla un bug de código, y SmartDoc solo hace 5 intentos en algo más de un minuto. Lo que corresponde ahí es responder 200 y arreglar el handler.
flask_view ¶
Una vista de Flask lista para montar.
::
app.add_url_rule(
"/webhooks", view_func=webhooks.flask_view(), methods=["POST"]
)
fastapi_route ¶
Un handler de FastAPI listo para montar.
::
app.post("/webhooks")(webhooks.fastapi_route())
django_view ¶
Una vista de Django lista para montar.
::
urlpatterns += [path("webhooks/", webhooks.django_view())]
serve ¶
Levanta un servidor mínimo, para desarrollo.
Bloquea hasta Ctrl-C. No usar en producción: es de un solo hilo y no tiene nada de lo que hace falta para aguantar tráfico real.
SmartDoc exige que la URL del endpoint sea https:// y alcanzable
desde internet, así que localhost no sirve como endpoint: hay que
exponer el puerto con un túnel (ngrok, cloudflared, smee.io) y registrar
la URL pública que devuelva.
smartdoc.WebhookEvent
dataclass
¶
Una entrega de webhook, ya verificada y parseada.
Los campos del sobre son estables. Los de data no todos: SmartDoc
garantiza un subconjunto —id, status, los datos del emisor, el
timbrado, el establecimiento y el punto de expedición— y agrega otros
propios de cada tipo de documento que pueden cambiar sin aviso.
Por eso la recomendación es tratar el evento como un disparador y, si hace
falta el detalle completo y estable, traerlo con :meth:fetch.
document_key
property
¶
Identifica al documento sin ambigüedad: (entidad, id).
entity_id no es único por sí solo. Se numera por tipo de
documento, así que el recibo 20 y la nota de débito 20 conviven. Guardar
solo el número y buscar por él cruza documentos distintos.
Esta es la clave que corresponde usar contra la propia base::
Venta.objects.get(
smartdoc_entity=evento.document_key[0],
smartdoc_id=evento.document_key[1],
)
is_error
property
¶
Si el documento quedó en error.
El motivo está en :attr:error_code y :attr:error_message.
is_approved
property
¶
Si el documento quedó válido.
Para un recibo, esto es cierto con el documento en generated: no pasa
por la DNIT.
error_code
property
¶
Código del error, o None si el documento no está en error.
Para ramificar conviene :attr:is_error.
error_message
property
¶
Descripción del error, o None si el documento no está en error.
fetch ¶
Trae el documento completo desde la API.
Sirve cuando hace falta un campo que no está garantizado en data.
from_payload
classmethod
¶
Construye el evento desde el cuerpo ya parseado.
Geografía¶
smartdoc.geo.find_city ¶
Busca una ciudad por nombre exacto y devuelve una sola.
Ignora mayúsculas y tildes. Si el nombre es ambiguo se levanta
ValidationError con todas las opciones y su código, en vez de elegir una:
la ciudad queda impresa en el documento y adivinar sería imprimir una
dirección equivocada.
La ambigüedad viene de dos lados. A veces son ciudades homónimas en
departamentos distintos (hay tres CONCEPCION), y ahí desempata
department. Otras veces SIFEN distingue el centro urbano del municipio
dentro del mismo distrito —ENCARNACION y ENCARNACION (MUNICIPIO)—, y
ahí no hay nada que desempate: hay que elegir por código con :func:city.
Para búsquedas parciales o exploratorias, usar :func:search_cities.
smartdoc.geo.search_cities ¶
Ciudades cuyo nombre contiene term, ignorando mayúsculas y tildes.
smartdoc.geo.City
dataclass
¶
Ciudad, con su distrito y su departamento.
Es lo que se le pasa a Receptor: de acá salen los seis campos de
dirección que exige el documento electrónico.
smartdoc.geo.District
dataclass
¶
Distrito, con su departamento.
smartdoc.geo.Department
dataclass
¶
Departamento del catálogo de la DNIT.
Catálogos¶
smartdoc.constants.documents ¶
Catálogos propios de SmartDoc: valores de texto que espera la API v2.
A diferencia de los de :mod:smartdoc.constants.sifen, que son códigos
numéricos de la DNIT, estos son las cadenas que SmartDoc guarda en su base. Son
cerrados: mandar otra cosa no es "un código que el SDK todavía no conoce",
es un error.
Conviene no confundir el valor con la etiqueta: en :class:SaleType, lo que
viaja en el campo es "cash", mientras que "Contado" es el texto para
mostrar en pantalla. Los constructores aceptan las dos formas y mandan siempre el
valor.
Iva ¶
Bases: Catalog
Tratamiento de IVA de un ítem (items[].ivaType).
Los dos tipos mixtos exigen además ivaBase, el porcentaje de la
operación que está gravado.
Currency ¶
Bases: Catalog
Moneda del documento (currency).
SIFEN admite las ~200 monedas ISO, pero SmartDoc valida contra estas tres y rechaza el resto, así que el catálogo es cerrado.
Con cualquier moneda distinta de :attr:PYG hay que mandar además
pygExchangeRate.
SaleType ¶
Bases: Catalog
Condición de venta de una factura (saleType).
Ojo con el valor: es "cash" / "credit", no "Contado" /
"Crédito". Ver la nota al principio del módulo.
ClientType ¶
Bases: Catalog
Naturaleza del receptor (clientType).
Los recibos aceptan solo :attr:PERSON, :attr:ENTITY y
:attr:FOREIGN_ENTITY; el resto de los documentos acepta los seis.
:attr:FOREIGN_ENTITY es el único tipo que no exige dirección aunque
clientHasAddress sea verdadero.
AssociatedDocumentType ¶
Bases: Catalog
Tipo de documento asociado (associatedDocumentType).
Las notas de crédito y débito aceptan :attr:ELECTRONIC_INVOICE y
:attr:PRINTED_INVOICE; los recibos aceptan además :attr:NONE.
Con :attr:ELECTRONIC_INVOICE hay que mandar associatedDocumentCdc. Con
:attr:PRINTED_INVOICE, los datos del documento impreso: timbrado,
establecimiento, punto de expedición, número y fecha.
DocumentType ¶
Bases: Catalog
Los tipos de documento electrónico que emite SmartDoc.
Sirve para trabajar de forma genérica sobre un documento; cada uno tiene su
recurso en el cliente (sd.invoices, sd.credit_notes, …).
ReceiptPaymentMethod ¶
Bases: Catalog
Forma de pago de un recibo (paymentMethod).
ProviderType ¶
Bases: Catalog
Naturaleza del proveedor en una autofactura (providerType).
La autofactura se emite cuando se compra a alguien que no puede emitir factura, así que el proveedor es siempre uno de estos dos.
ProviderDocumentType ¶
Bases: Catalog
Documento de identidad del proveedor (providerDocumentType).
smartdoc.constants.sifen ¶
Catálogos oficiales de SIFEN (códigos numéricos de la DNIT).
Están los catálogos completos de la DNIT, y solo aquellos que tienen un campo donde ir en el cuerpo de algún endpoint de la API v2.
Todos son abiertos: si la DNIT agrega un código, se puede pasar el entero crudo sin esperar una versión nueva del SDK.
TransactionType ¶
Bases: IntCatalog
Tipo de transacción de una factura (transactionTypeCode).
La API valida que esté entre 1 y 13, que es justo el rango de este catálogo.
MeasureUnit ¶
Bases: IntCatalog
Unidad de medida de un ítem (items[].measureUnit).
SmartDoc no ofrece un selector para esto: fija UNIT (77) en todos sus
formularios. La API acepta cualquier código del catálogo.
RemissionReason ¶
Bases: IntCatalog
Motivo de una nota de remisión (reasonCode).
RemissionResponsible ¶
Bases: IntCatalog
Responsable de la emisión de la nota de remisión (responsibleCode).
TransportType ¶
Bases: IntCatalog
Tipo de transporte (transportTypeCode).
TransportMode ¶
Bases: IntCatalog
Modalidad de transporte (transportModeCode).
FreightResponsible ¶
Bases: IntCatalog
Responsable del flete (transportResponsibleCode).
VehicleIdentificationType ¶
Bases: IntCatalog
Tipo de identificación del vehículo (transportVehicleDocumentTypeCode).
PrintedDocumentType ¶
Bases: IntCatalog
Tipo de documento impreso asociado (associatedDocumentPrintedType).
Se usa cuando el documento asociado no es electrónico.
CarrierDocumentType ¶
Bases: IntCatalog
Tipo de documento del transportista (carrierDocumentTypeCode).
Es el catálogo de documentos de identidad del receptor que usa SIFEN.
smartdoc.constants.statuses ¶
Estados de un documento electrónico.
Los seis tipos de documento comparten el mismo vocabulario de estados —19 en
total, todos alcanzables por una factura— así que hay un solo
:class:DocumentStatus. Lo que cambia por tipo es cuáles son alcanzables, y
eso está en :data:REACHABLE.
El flujo habitual es::
pending → generated → uploaded_to_set → approved_by_set
Las agrupaciones (:data:SUCCESS, :data:ERROR, :data:TERMINAL) siguen la
misma clasificación que usa SmartDoc para decidir qué webhook dispara cada
estado, así que "esperar a que termine" y "esperar el evento" coinciden.
El recibo es la excepción. No se envía a la DNIT, así que nunca llega a
approved_by_set: su estado final de éxito es generated, que para todos
los demás documentos es un paso intermedio. Por eso conviene preguntar con
:func:is_terminal y :func:is_success, que reciben el tipo de documento, en
vez de comparar contra los conjuntos a mano.
DocumentStatus ¶
Bases: Catalog
Estado de un documento electrónico.
is_success ¶
Si el documento quedó válido.
Para un recibo, generated cuenta como éxito: es su estado final, porque
no se envía a la DNIT. Para el resto, generated es intermedio.
is_terminal ¶
Si el documento ya no va a cambiar de estado por su cuenta.
Es lo que usa wait_until_final para saber cuándo dejar de consultar.
smartdoc.constants.events ¶
Tipos de evento de webhook.
Son 23, con formato {entidad}.{subtipo}: seis entidades por cuatro subtipos,
menos receipt.error, que no existe —si falla la generación de un recibo, el
recibo queda en pending y se reintenta, no pasa a un estado de error—.
Un detalle que cambia cómo se escucha: receipt.approved no significa
aprobado por la DNIT. El recibo no se envía a la DNIT, así que ese evento
quiere decir "el recibo se generó y su KuDE ya está disponible", con el
documento en estado generated. Esperar un approved_by_set para un recibo
es esperar algo que nunca llega.
EntityType ¶
Bases: Catalog
La entidad a la que pertenece un evento (campo entityType).
smartdoc.constants.permissions ¶
Permisos de una API Key.
Dos cosas que sorprenden y hacen perder tiempo:
- No hay jerarquía.
write_invoicesno habilita nada que pidaread_invoices. La verificación es por coincidencia exacta, así que una clave que crea facturas y después las consulta necesita los dos permisos marcados. - La clave pertenece al contribuyente, no al usuario. Los permisos del usuario que la creó no influyen, y la clave sigue funcionando aunque ese usuario cambie de permisos o se dé de baja.
Cuando falta un permiso la respuesta es 403 con código FORBIDDEN, y el
SDK la convierte en :class:smartdoc.errors.PermissionError.
Permission ¶
Bases: Catalog
Permiso de una API Key, tal como se marca en el panel de SmartDoc.
smartdoc.constants.countries ¶
Códigos de país (clientCountryCode).
Son 249 códigos ISO-3, así que van como datos y no como enum: nadie quiere
recorrer 249 miembros en el autocompletado. Lo que hace falta casi siempre es
:data:PARAGUAY; para el resto están :func:code y :func:name.
El código se valida contra el catálogo, porque la API lo acepta como texto libre y un código inventado recién falla al enviarse a la DNIT.
Errores¶
smartdoc.errors ¶
Excepciones del SDK.
Este módulo no importa nada del resto del paquete, así que puede usarse desde cualquier lado sin ciclos.
La jerarquía separa dos orígenes:
- Errores que devolvió SmartDoc (
APIErrory sus subclases), que traen elcodey elmessagedel cuerpo de la respuesta. - Errores que detecta el SDK antes de salir a la red (
ValidationError,ConfigurationError,SignatureError), que existen justamente para no gastar una llamada en algo que ya se sabe que va a fallar.
SmartDocError ¶
Bases: Exception
Base de todos los errores del SDK.
APIError ¶
Bases: SmartDocError
Error devuelto por SmartDoc.
Atributos
code: el campo code del cuerpo (por ejemplo NOT_FOUND).
message: el campo message.
status_code: el código HTTP.
raw: el cuerpo completo, por si trae algo que el SDK no modela.
PermissionError ¶
Bases: APIError
403 — la API Key es válida pero no tiene el permiso necesario.
Los permisos de SmartDoc no son jerárquicos ni se heredan: write_invoices
no habilita nada que pida read_invoices. Si esto salta, hay que marcar el
permiso que falta en la API Key desde el panel.
ValidationError ¶
Bases: APIError
422 de la API, o una validación que el SDK hizo localmente.
Cuando viene de la API, field_errors mapea campo → mensaje. Cuando la
detecta el SDK, status_code es None y el mensaje explica cómo
corregirlo.
RateLimitError ¶
Bases: APIError
429 — se superó el límite de 5 solicitudes por segundo.
SmartDoc no envía Retry-After, así que el SDK reintenta con backoff
exponencial propio. Si igual llega hasta acá, es que se agotaron los
reintentos.
ConfigurationError ¶
Bases: SmartDocError
El SDK no pudo resolver la configuración necesaria para emitir.
El caso típico es que el contribuyente tenga más de un establecimiento o punto de expedición y no se haya indicado cuál usar. El SDK nunca elige uno en silencio, porque el elegido queda impreso en el documento.
SignatureError ¶
Bases: SmartDocError
La firma de un webhook no validó.
Las dos causas habituales son verificar contra el JSON reserializado en vez del cuerpo crudo, y usar el secreto de otro endpoint.
TimeoutError ¶
smartdoc.constants.api_errors ¶
Códigos de error que devuelve la API v2 en el campo code.
El SDK los traduce a las excepciones de :mod:smartdoc.errors, así que rara vez
hace falta compararlos a mano. Están acá para los casos en que sí: distinguir un
ACTION_NOT_ALLOWED de un UNKNOWN_ACTION, por ejemplo, cuando ambos
llegan como :class:smartdoc.errors.ConflictError.
El catálogo es abierto: SmartDoc puede agregar códigos y el SDK no debería romperse por eso.
ErrorCode ¶
Bases: Catalog
Código de error de la API v2.