Saltar a contenido

Documentos

Los seis tipos comparten la misma forma. Lo que cambia es qué campos piden y qué acciones aceptan.

Recurso Documento KuDE Acciones propias
sd.invoices Factura nominate, confirm_draft, discard_draft
sd.receipts Recibo confirm_draft, discard_draft
sd.credit_notes Nota de crédito
sd.debit_notes Nota de débito
sd.remission_notes Nota de remisión
sd.auto_invoices Autofactura

Todos aceptan cancel y resend_email. Todos menos el recibo aceptan además resend_to_set, que reenvía a la DNIT un documento que quedó en error.

Establecimiento y punto de expedición

Se pueden dar por su código —el que va impreso en el documento— o por el id interno de SmartDoc:

smartdoc.Client(establishment="001", dispatch_point="001")   # por código
smartdoc.Client(establishment=8,     dispatch_point=14)      # por id interno

También por llamada, que es lo que corresponde si emitís desde varias sucursales:

sd.invoices.create(..., establishment="003", dispatch_point="001")

Si no se indica ninguno, el SDK los descubre solo cuando el contribuyente tiene exactamente uno. Si hay más, levanta ConfigurationError con las opciones disponibles en vez de elegir: el código elegido queda impreso en el documento y en su CDC, y equivocarlo obliga a anular y reemitir.

Crear y editar

create() emite; update() reemplaza. No es un parche: hay que pasar el documento entero, porque la API valida el cuerpo completo igual que al crear.

factura = sd.invoices.create(recipient=..., items=[...])
factura = sd.invoices.update(factura.id, recipient=..., items=[...])

Solo antes de subir a la DNIT

Una vez que el documento pasó a uploaded_to_set, la API rechaza la edición. A partir de ahí la vía es anular y reemitir.

Ítems y montos

smartdoc.Item(
    "Consultoría",
    quantity=2,
    unit_amount=550000,        # con el IVA ya incluido
    iva=smartdoc.Iva.TEN,       # por omisión
    code="SKU-123",             # si no se pasa, se numera por posición
    measure_unit=smartdoc.MeasureUnit.UNIT,
    discount=10000,
)

El descuento es por unidad, no de la línea

Un discount de 10.000 sobre 3 unidades descuenta 30.000 en total.

El SDK calcula amount y el desglose completo de IVA, contemplando el descuento por ítem, el global y el de SEDECO. Los montos se calculan con Decimal.

smartdoc.builders.totals_for([smartdoc.Item("X", quantity=1, unit_amount=1100000)])
# {"amount": 1100000, "amount10Percent": 1100000,
#  "taxed10Percent": 1000000, "iva10Percent": 100000}

IVA mixto

Cuando parte de la línea está gravada y parte exenta:

smartdoc.Item(
    "Combo", quantity=1, unit_amount=1000000,
    iva=smartdoc.Iva.MIXED_TEN,
    iva_base=60,          # 60% gravado al 10%, 40% exento
)

El receptor

smartdoc.Recipient.entity(ruc="80012345-1", social_name="Cliente S.A.")
smartdoc.Recipient.person(ruc="5000001-1", name="Juan Pérez")
smartdoc.Recipient.not_nominated()
smartdoc.Recipient.foreign(name="Cliente do Brasil", country_code="BRA")
smartdoc.Recipient.government(ruc="...", social_name="...")
smartdoc.Recipient.diplomatic(ruc="...", social_name="...")

Cada constructor se expande a los campos client* que corresponden, con sus reglas condicionales.

Direcciones

Un documento cuyo receptor lleva dirección necesita seis campos: departamento, distrito y ciudad, cada uno con código y descripción. El SDK embebe el catálogo geográfico, así que una búsqueda por nombre los resuelve:

smartdoc.Recipient.person(
    ruc="5000001-1",
    name="Juan Pérez",
    address="Av. España 1234",
    house_number=1234,
    city=smartdoc.geo.find_city("Asunción"),
)

Cuando el nombre es ambiguo, find_city levanta ValidationError con las opciones en vez de elegir una:

smartdoc.geo.find_city("Concepción")
# ValidationError: 'Concepción' es ambiguo: hay 3 ciudades con ese nombre...

smartdoc.geo.find_city("Concepción", department="Misiones")   # desempata
smartdoc.geo.city(2252)                                       # o por código

El receptor extranjero es el único que puede llevar dirección sin códigos geográficos paraguayos.

Estados

Los seis tipos comparten el mismo vocabulario. El flujo habitual:

pending → generated → uploaded_to_set → approved_by_set
factura.status          # DocumentStatus.APPROVED_BY_SET
factura.is_approved
factura.is_terminal
factura.is_error
factura.error_code      # None si el documento no está en error
factura.error_message

Preguntá por las propiedades, no por el estado

is_approved, is_terminal e is_error conocen las reglas de cada tipo de documento. Comparar contra estados a mano es más frágil.

recoverable_error no es un estado final

SmartDoc lo reintenta por su cuenta, así que el documento todavía puede llegar a aprobarse. wait_until_final no corta ahí.

Para el recibo, generated es el estado final de éxito

El recibo no se envía a la DNIT, así que nunca llega a approved_by_set. is_approved y is_terminal ya lo contemplan.

Esperar

factura = sd.invoices.wait_until_final(factura.id)

Consulta hasta llegar a un estado terminal, respetando el límite de tasa. Acepta timeout y poll_interval; si se agota el plazo levanta TimeoutError, que no significa que el documento haya fallado sino que se acabó la espera.

En producción conviene escuchar webhooks en lugar de esto.

Borradores

Facturas y recibos se pueden crear en borrador: quedan guardados y no se envían a la DNIT hasta confirmarlos.

borrador = sd.invoices.create(recipient=..., items=[...], draft=True)
sd.invoices.confirm_draft(borrador.id)     # ahora sí sale
sd.invoices.discard_draft(borrador.id)     # o se descarta

Acciones

sd.invoices.cancel(factura.id, reason="Error en el monto")
sd.invoices.resend_email(factura.id)
sd.invoices.resend_to_set(factura.id)

Estas acciones no devuelven el documento: se ejecutan de forma asíncrona, así que para ver cómo quedó hay que volver a consultarlo.

sd.invoices.cancel(factura.id, reason="Error en el monto")
factura = sd.invoices.wait_until_final(factura.id)   # DocumentStatus.CANCELLED

Anular exige que el documento esté aprobado y que el motivo no vaya vacío. Si el estado no lo permite, la API responde ACTION_NOT_ALLOWED.

Facturas innominadas

Venta de mostrador sin identificar al comprador:

factura = sd.invoices.create(
    recipient=smartdoc.Recipient.not_nominated(),
    items=[...],
)

Si después el cliente pide la factura a su nombre:

cliente = sd.clients.create(ruc="80012345-1", socialName="Cliente S.A.")
sd.invoices.nominate(factura.id, client_id=cliente.id)

Notas de crédito y débito

Corrigen una factura ya emitida. El documento asociado es obligatorio: sin él la DNIT no sabe sobre qué aplicar la corrección.

sd.credit_notes.create(
    recipient=...,
    items=[smartdoc.Item("Devolución", quantity=1, unit_amount=1100000)],
    associated_document=smartdoc.AssociatedDocument.electronic(factura.cdc),
)

Para un documento anterior a la facturación electrónica:

smartdoc.AssociatedDocument.printed(
    stamp_identifier="12345678",
    establishment_code="001",
    dispatch_point_code="001",
    number="0000001",
    document_date="2024-05-01",
)

Recibos

sd.receipts.create(
    recipient=smartdoc.Recipient.person(ruc="5000001-1", name="Juan Pérez"),
    amount=1100000,
    payment_method=smartdoc.constants.ReceiptPaymentMethod.CASH,
    associated_document=smartdoc.AssociatedDocument.electronic(factura.cdc),
)

Aceptan tres tipos de receptor: persona, entidad y extranjero. Sus ítems son más simples —descripción y monto— porque el recibo no liquida impuestos. El documento asociado es opcional: si no se pasa, va AssociatedDocument.none().

Notas de remisión

Amparan un traslado, así que no llevan monto, ni moneda, ni IVA. Lo que sí llevan, y ningún otro documento tiene, son los datos del traslado.

from datetime import date

ciudad = smartdoc.geo.find_city("Luque")

sd.remission_notes.create(
    recipient=smartdoc.Recipient.entity(
        ruc="80012345-1",
        social_name="Cliente S.A.",
        address="Av. Principal 123",
        house_number=123,
        city=ciudad,
        email="cliente@ejemplo.com.py",
    ),
    items=[smartdoc.RemissionItem("Caja de tornillos", quantity=10)],
    departure=smartdoc.TransportEndpoint(
        address="Depósito Central", city=ciudad, house_number=100
    ),
    arrival=smartdoc.TransportEndpoint(
        address="Sucursal Norte", city=ciudad, house_number=200
    ),
    vehicle=smartdoc.Vehicle(
        brand="Toyota", type="CAMION", registration_number="ABC123"
    ),
    carrier=smartdoc.Carrier(
        social_name="Transporte S.A.",
        ruc="80099999-1",
        is_taxpayer=True,
        document_number="80099999",
        address="Ruta 2 km 15",
        driver_name="Pedro Gómez",
        driver_document_number="3456789",
        driver_address="Calle 5 casi 8",
    ),
    estimated_kilometers=25,
    start_date=date(2026, 8, 6),
    end_date=date(2026, 8, 7),
)

La remisión exige más datos que el resto

Campos que en los otros documentos son opcionales y acá no:

  • El receptor, con dirección completa —address, house_number y city— y email.
  • Los dos extremos del traslado, con house_number.
  • estimated_kilometers, start_date y end_date.
  • El transportista completo: su documento y dirección, más el nombre, documento y dirección del chofer.

El SDK valida todo esto antes de emitir e indica qué falta.

Los datos del vehículo tienen largo acotado

Campo Qué va
type Descripción libre de 4 a 10 caracteres: "CAMION", "Camioneta", "Furgón"
brand De 1 a 10 caracteres. "Mercedes-Benz" no entra: abreviala
registration_number La matrícula, de 6 o 7 caracteres
document_number El número de identificación, de 1 a 20 caracteres

document_type decide cuál de los dos números hay que dar, y el otro se ignora: con REGISTRATION —el valor por omisión— va registration_number; con VIN, document_number.

type no es transport_type: ese es un código y define si el transporte es propio o de terceros.

El motivo condiciona quién puede ser el receptor

Con reason=RemissionReason.BETWEEN_COMPANY_LOCATIONS —traslado entre locales de la misma empresa— el RUC del receptor tiene que coincidir con el del emisor. El motivo por omisión, SALE, no tiene esa restricción.

Autofacturas

Para cuando se le compra a alguien que no puede emitir factura. No hay receptor: va el proveedor, y aparte el lugar donde se hizo la operación.

sd.auto_invoices.create(
    provider=smartdoc.Provider.no_ruc(
        full_name="Proveedor Informal",
        document_number="1234567",
        address="Calle 1",
        city=ciudad,
        house_number=50,
    ),
    items=[smartdoc.Item("Materia prima", quantity=1, unit_amount=500000)],
    transaction_address="Depósito Central",
    transaction_city=ciudad,
    transaction_house_number=100,
    certificate_code="12345678",      # exactamente 8 caracteres
    certificate_number="12345678901", # exactamente 11 caracteres
)

Son obligatorios el house_number del proveedor y el del lugar de la operación, más el código y el número de la constancia, que tienen largo exacto.

Listar

pagina = sd.invoices.list(status="approved_by_set", date_from="2026-01-01")

for factura in pagina:                       # solo esta página
    ...

for factura in pagina.auto_paging_iter():    # todas, siguiendo el cursor
    ...

El tamaño por omisión es 20 y el máximo 100. auto_paging_iter() respeta el límite de tasa, así que recorrer un listado largo lleva su tiempo.