@araitek/smartdocjs
v0.1.2
Published
SDK de Node.js para SmartDoc — facturación electrónica en Paraguay (DNIT/SIFEN)
Maintainers
Readme
@araitek/smartdocjs
SDK de Node.js y TypeScript para SmartDoc, el sistema de facturación electrónica para Paraguay. Cubre la API pública v2 y la recepción de sus webhooks.
Documentación completa: https://www.smartdoc.com.py/docs/node/
Instalación
npm install @araitek/smartdocjsRequiere Node.js 18 o superior. No tiene dependencias de terceros.
Funciona igual con ESM y con CommonJS, y trae sus propios tipos:
import { Client, Item, Recipient } from '@araitek/smartdocjs'; // ESM / TypeScriptconst { Client, Item, Recipient } = require('@araitek/smartdocjs'); // CommonJSEmitir un documento
import { Client, Item, Recipient } from '@araitek/smartdocjs';
const sd = new Client(); // toma la API Key del entorno
let factura = await sd.invoices.create({
recipient: Recipient.entity({
ruc: '80012345-1',
socialName: 'Cliente Ejemplo S.A.',
email: '[email protected]',
}),
items: [
new Item('Consultoría de octubre', { quantity: 1, unitAmount: 1_100_000 }),
],
});
factura = await sd.invoices.waitUntilFinal(factura.id);
console.log(factura.status, factura.cdc);El SDK completa los datos del emisor, la clave de idempotencia y el desglose de IVA. En Paraguay el IVA va incluido en el precio: sobre ese ejemplo emite 1.000.000 de base gravada y 100.000 de IVA.
Están los seis tipos de documento electrónico, cada uno con sus acciones:
| Recurso | Documento |
|---|---|
| sd.invoices | Factura |
| sd.receipts | Recibo |
| sd.creditNotes | Nota de crédito |
| sd.debitNotes | Nota de débito |
| sd.remissionNotes | Nota de remisión |
| sd.autoInvoices | Autofactura |
Configuración
La API Key sale de SMARTDOC_API_KEY. La URL apunta a la instancia de
producción; solo hace falta SMARTDOC_BASE_URL con una instancia propia.
export SMARTDOC_API_KEY="pk_..."Con más de un establecimiento o punto de expedición hay que decir cuál, porque el SDK no elige por su cuenta:
const sd = new Client({ establishment: '001', dispatchPoint: '001' });o por llamada, si emitís desde varias sucursales:
await sd.invoices.create({
recipient,
items,
establishment: '002',
dispatchPoint: '001',
});Idempotencia
Cada create() va con una clave nueva que genera el SDK, así que reintentar
nunca emite dos veces. Si preferís que la clave venga de tu sistema:
const factura = await sd.invoices.create({
recipient,
items,
idempotencyKey: `venta-${venta.id}`,
});Repetir la llamada con la misma clave devuelve el documento ya creado.
Errores
import { RateLimitError, ServerError, ValidationError } from '@araitek/smartdocjs';
try {
await sd.invoices.create({ recipient, items });
} catch (error) {
if (error instanceof ValidationError) {
// datos mal armados; no sirve reintentar
} else if (error instanceof RateLimitError || error instanceof ServerError) {
// transitorio; el SDK ya reintentó
} else {
throw error;
}
}Todos heredan de SmartDocError. Las validaciones que el SDK puede hacer sin red
—largos, catálogos cerrados, campos condicionales— fallan antes de salir, con el
mensaje de qué corregir.
Webhooks
En producción conviene escuchar los eventos en vez de hacer polling.
import express from 'express';
import { Event, Webhooks } from '@araitek/smartdocjs';
const webhooks = new Webhooks({ secret: process.env.SMARTDOC_WEBHOOK_SECRET });
webhooks.on(Event.INVOICE_APPROVED, async (evento) => {
await marcarAprobada(evento.entityId, evento.cdc);
});
webhooks.onAnyError(async (evento) => {
await avisar(`${evento.errorCode}: ${evento.errorMessage}`);
});
const app = express();
app.post(
'/webhooks/smartdoc',
express.raw({ type: 'application/json' }), // la firma va sobre el cuerpo crudo
webhooks.expressHandler(),
);Hay adaptadores para Express y Fastify, y uno con Request/Response estándar
para Hono, Next.js, Deno y Bun. Para desarrollo, webhooks.serve({ port: 3000 })
levanta un servidor mínimo.
El SDK verifica la firma HMAC sobre el cuerpo crudo, descarta las entregas repetidas y rechaza las que llegan fuera de la ventana de tolerancia.
Catálogos y geografía
Los catálogos de SIFEN vienen validados. Un valor inválido falla antes de salir a la red:
import { Iva, SaleType, geo } from '@araitek/smartdocjs';
SaleType.CASH; // 'cash'
Iva.TEN; // '10_percent'
SaleType.coerce('Contado'); // ValidationError: ¿Quisiste decir "cash"?
geo.findCity('Asunción'); // resuelve departamento, distrito y ciudadDesarrollo
npm install
npm run build # ESM + CJS + tipos
npm test # compila y corre la suite
npm run typecheck
npm run docs:dev # el sitio de documentación, en localLos tests de integración corren contra una instancia real y se saltean si no hay
credenciales. Copiá .env.example a .env y completalo:
npm run e2eConviene apuntarlos a un contribuyente demo: ahí SmartDoc simula la respuesta de la DNIT en vez de enviarla.
En examples/ hay tres programas completos —emitir una factura, recibir webhooks
y el ciclo entero incluido el camino de error—. Para correrlos desde el repo, con
el paquete construido:
npm link # deja @araitek/smartdocjs resoluble
node examples/01-emitir-factura.mjsLicencia
MIT.
