Skip to content

Primeros pasos

De cero a una factura emitida.

1. Instalar

bash
npm install @araitek/smartdocjs

Requiere Node.js 18 o superior.

Funciona igual con import y con require:

ts
import { Client, Item, Recipient } from '@araitek/smartdocjs';
js
const { Client, Item, Recipient } = require('@araitek/smartdocjs');

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 lanza un PermissionError que dice cuál.

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

bash
export SMARTDOC_API_KEY="pk_..."

Si tu contribuyente está en una instancia propia, indicá su URL, si no, ignora este paso:

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

3. Crear el cliente

ts
import { Client } from '@araitek/smartdocjs';

const sd = new 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:

ts
const sd = new Client({ establishment: '001', dispatchPoint: '001' });

4. Emitir una factura

ts
import { Item, Recipient } from '@araitek/smartdocjs';

const factura = await sd.invoices.create({
  recipient: Recipient.entity({
    ruc: '80012345-1',
    socialName: 'Cliente Ejemplo S.A.',
    email: 'facturas@cliente.com.py',
  }),
  items: [
    new Item('Consultoría de octubre', { quantity: 1, unitAmount: 1_100_000 }),
  ],
});

console.log(factura.id, factura.status);

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

Todas las llamadas devuelven una promesa

No hay cliente sincrónico: create, get, list, cancel y las demás llevan await.

El IVA de cada ítem

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

ts
import { Item, Iva } from '@araitek/smartdocjs';

await sd.invoices.create({
  recipient,
  items: [
    new Item('Consultoría', { quantity: 1, unitAmount: 1_100_000 }),            // 10%
    new Item('Arroz', { quantity: 2, unitAmount: 105_000, iva: Iva.FIVE }),     // 5%
    new Item('Libro', { quantity: 1, unitAmount: 80_000, iva: Iva.EXEMPT }),    // exento
    new Item('Combo', {
      quantity: 1,
      unitAmount: 1_000_000,
      iva: Iva.MIXED_TEN,
      ivaBase: 60,                                                              // mixto
    }),
  ],
});

Los mixtos exigen ivaBase: 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

ts
const aprobada = await sd.invoices.waitUntilFinal(factura.id);

console.log(aprobada.status);        // 'approved_by_set'
console.log(aprobada.cdc);
console.log(aprobada.isApproved);    // true

Esto es para probar, no para producción

waitUntilFinal 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

ts
const url = await sd.invoices.kudeUrl(factura.id);

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

7. Anular

ts
await sd.invoices.cancel(factura.id, 'Error en el monto facturado');

const anulada = await sd.invoices.waitUntilFinal(factura.id);
console.log(anulada.status);        // '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:

ts
let rechazada = await sd.invoices.create({
  recipient: Recipient.entity({
    ruc: '99999901-7',
    socialName: 'Prueba de rechazo',
  }),
  items: [new Item('Servicio', { quantity: 1, unitAmount: 100_000 })],
});
rechazada = await sd.invoices.waitUntilFinal(rechazada.id);

console.log(rechazada.isError);       // true
console.log(rechazada.errorCode);     // '0160'
console.log(rechazada.errorMessage);
RUC del receptorResultado
99999901-7Rechazado por la DNIT, código 0160
99999902-4Error de procesamiento de lote, código 0301
cualquier otroAprobado

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

Para cambiar el estado de un documento ya emitido:

ts
const cdc = factura.cdc;      // disponible una vez que el documento se generó

if (cdc) {
  await sd.sandbox.forceStatus(cdc, 'error');      // o '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:

ts
const contribuyente = await sd.taxPayer();
console.log(contribuyente.isDemo);

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.