Tema
Primeros pasos
De cero a una factura emitida.
1. Instalar
bash
npm install @araitek/smartdocjsRequiere 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 entornoSi 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); // trueEsto 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 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:
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.
