Saltar a contenido

Primeros pasos

De cero a una factura emitida.

1. Instalar

pip install smartdocpy

Se instala como smartdocpy y se importa como smartdoc.

2. Conseguir una API Key

Generala desde el panel de SmartDoc, en la sección de API Keys del contribuyente. Al crearla, marcá los permisos que va a usar.

Los permisos no son jerárquicos

write_invoices no habilita nada que pida read_invoices. Para seguir esta guía marcá read_taxpayer, read_stamps, read_establishments, read_dispatch_points, read_invoices y write_invoices.

Si falta alguno, el SDK levanta un PermissionError que dice cuál.

La clave se muestra una sola vez. Guardala en una variable de entorno:

export SMARTDOC_API_KEY="pk_..."

El SDK apunta a la instancia de producción de SmartDoc. Si tu contribuyente está en una instancia propia, indicá su URL:

export SMARTDOC_BASE_URL="https://tu-instancia/api/v2"

3. Crear el cliente

import smartdoc

sd = smartdoc.Client()          # toma la key y la URL del entorno

Si el contribuyente tiene más de un establecimiento o punto de expedición, indicá cuál usar:

sd = smartdoc.Client(establishment="001", dispatch_point="001")

4. Emitir una factura

factura = sd.invoices.create(
    recipient=smartdoc.Recipient.entity(
        ruc="80012345-1",
        social_name="Cliente Ejemplo S.A.",
        email="facturas@cliente.com.py",
    ),
    items=[
        smartdoc.Item("Consultoría de octubre", quantity=1, unit_amount=1100000),
    ],
)

print(factura.id, factura.status)

El estado inicial es pending o generated: la factura se encoló y todavía no está aprobada.

El IVA de cada ítem

Sin indicar nada, el ítem se emite al 10%. Para los demás tratamientos:

sd.invoices.create(
    recipient=...,
    items=[
        smartdoc.Item("Consultoría", quantity=1, unit_amount=1100000),           # 10%
        smartdoc.Item("Arroz", quantity=2, unit_amount=105000,
                      iva=smartdoc.Iva.FIVE),                                     # 5%
        smartdoc.Item("Libro", quantity=1, unit_amount=80000,
                      iva=smartdoc.Iva.EXEMPT),                                   # exento
        smartdoc.Item("Combo", quantity=1, unit_amount=1000000,
                      iva=smartdoc.Iva.MIXED_TEN, iva_base=60),                   # mixto
    ],
)

Los mixtos exigen iva_base: el porcentaje de la línea que está gravado.

Los precios van con IVA incluido

En Paraguay el IVA está contenido en el precio, no se suma. Si tu sistema guarda precios sin IVA, sumáselo antes de armar el ítem.

5. Esperar la aprobación

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

print(factura.status)        # DocumentStatus.APPROVED_BY_SET
print(factura.cdc)
print(factura.is_approved)   # True

Esto es para probar, no para producción

wait_until_final consulta hasta que el documento llegue a su estado final, y eso consume cuota del límite de solicitudes por segundo.

En producción no se espera: se escucha. La aprobación llega por webhook. Ver Cómo integrar.

6. Descargar el KuDE

url = sd.invoices.kude_url(factura.id)

Devuelve una URL temporal al PDF, disponible una vez que el documento se generó.

7. Anular

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

anulada = sd.invoices.wait_until_final(factura.id)
print(anulada.status)        # DocumentStatus.CANCELLED

Solo se puede anular un documento aprobado, y el motivo es obligatorio.

Si estás en una cuenta demo

Los documentos se generan de forma válida pero no se envían a la DNIT: la respuesta se simula. Todo lo anterior funciona igual, y además podés provocar los errores a voluntad.

Para que la DNIT rechace, usá uno de los dos RUC de receptor reservados:

rechazada = sd.invoices.create(
    recipient=smartdoc.Recipient.entity(
        ruc="99999901-7",
        social_name="Prueba de rechazo",
    ),
    items=[smartdoc.Item("Servicio", quantity=1, unit_amount=100000)],
)
rechazada = sd.invoices.wait_until_final(rechazada.id)

print(rechazada.is_error)       # True
print(rechazada.error_code)     # "0160"
print(rechazada.error_message)
RUC del receptor Resultado
99999901-7 Rechazado por la DNIT, código 0160
99999902-4 Error de procesamiento de lote, código 0301
cualquier otro Aprobado

Van con dígito verificador: "99999901-7", no "99999901".

Para cambiar el estado de un documento ya emitido:

sd.sandbox.force_status(factura.cdc, "error")
sd.sandbox.force_status(factura.cdc, "approved")

Las dos vías disparan los mismos eventos y webhooks que el flujo real.

Si no estás en una cuenta demo

Todo lo que emitas se envía a la DNIT y tiene validez fiscal. Antes de correr los ejemplos, cambiá el receptor y los ítems por datos reales.

Para confirmar en qué tipo de cuenta estás:

print(sd.taxpayer.is_demo)

Y ahora

Lo de arriba sirve para probar. Para integrar de verdad:

  • Cómo integrar — las tres piezas que hay que implementar y el código de cada operación. Empezá por acá.
  • Adaptar tu sistema — cómo traducir tu modelo de datos y qué revisar antes de producción.
  • Los catálogos listan los valores válidos de cada campo.