Saltar al contenido
Factuplan
Factuplan — Facturar ya no es un dolor de cabeza. Activa tu cuenta y obtén tus primeras 25 facturas gratis, autorizadas por el SRI.
Terminal de Node.js ejecutando un script que emite una factura electrónica autorizada por el SRI Ecuador con la API REST de Factuplan

Facturación electrónica Nodejs para Ecuador (paso a paso, 2026)

Integra facturación electrónica SRI en Node.js con el SDK oficial de Factuplan: webhooks, manejo de errores y repo facturación electrónica Ecuador en GitHub.

· 11 min de lectura · Equipo Factuplan

Tu app en Node.js necesita emitir facturas electrónicas autorizadas por el SRI de Ecuador. La buena noticia: con el SDK oficial de Factuplan lo puedes resolver en una tarde, sin tocar XMLs, sin firmar XAdES-BES manualmente y sin pelear con la API SOAP del SRI.

En este tutorial vas a ver cada paso: pre-requisitos, instalación, configuración, emisión de la primera factura, manejo de webhooks y troubleshooting. Todo con código real probado en producción.

Pre-requisitos

Antes de escribir una línea de código, necesitas:

1. Cuenta en Factuplan

Crea cuenta gratuita en app.factuplan.com.ec. El plan Free te da 25 facturas/mes sin costo, sin tarjeta de crédito. Suficiente para desarrollo y proyectos pequeños.

2. RUC activo

Tu negocio o el de tu cliente debe tener RUC activo en el SRI. Si todavía no tienes RUC, hay una guía completa para sacarlo.

3. Firma electrónica vigente

Para producción necesitas un certificado P12 emitido por una entidad acreditada en Ecuador (Banco Central, UANATACA, Security Data, ANF AC). Lo cargas una sola vez en tu workspace de Factuplan y ya. Para desarrollo en pruebas no se necesita firma real.

4. Node.js 18+

Ventana de terminal
node --version # debe ser v18.x o superior

Si usas una versión anterior, el SDK no funciona.

5. API key

Una vez dentro de Factuplan: Developer → Create API key.

  • ak_test_* para pruebas (comprobantes ficticios, se borran cada hora).
  • ak_live_* para producción (facturas reales firmadas y enviadas al SRI).

Guárdala en .env:

Ventana de terminal
FACTUPLAN_API_KEY=ak_test_xxxxxxxxxxxxxxxxxx
FACTUPLAN_RUC=0950194407001

Paso 1 — Crear el proyecto

Ventana de terminal
mkdir mi-app-facturacion
cd mi-app-facturacion
npm init -y
npm install factuplan dotenv
npm install -D @types/node typescript tsx
npx tsc --init --target es2022 --module nodenext --moduleResolution nodenext --strict

Si prefieres CommonJS o JavaScript puro, salta el último comando y tsx. El SDK funciona en ambos.

Estructura mínima:

mi-app-facturacion/
├── .env
├── .gitignore
├── package.json
├── tsconfig.json
└── src/
├── factuplan.ts # cliente del SDK
├── emitir-factura.ts # ejemplo básico
└── webhooks.ts # endpoint webhooks

En .gitignore añade siempre:

.env
node_modules/
dist/

Paso 2 — Inicializar el cliente

src/factuplan.ts
import "dotenv/config"
import { Factuplan } from "factuplan"
if (!process.env.FACTUPLAN_API_KEY) {
throw new Error("FACTUPLAN_API_KEY no está definida en .env")
}
export const factuplan = new Factuplan(process.env.FACTUPLAN_API_KEY, {
ruc: process.env.FACTUPLAN_RUC!,
})

Tip: importa este archivo desde cualquier módulo. El SDK reutiliza la misma conexión HTTP, no se crea un nuevo cliente por request.

Paso 3 — Emitir tu primera factura

src/emitir-factura.ts
import { factuplan } from "./factuplan.js"
async function main() {
const factura = await factuplan.invoices.create({
customer: {
identificationType: "RUC",
identification: "0993378150001",
legalName: "Cliente Demo S.A.",
email: "facturas@cliente-demo.ec",
saveToContacts: true,
},
items: [
{
code: "SERV-001",
description: "Desarrollo web mensual",
quantity: 1,
unitPrice: 500,
taxType: "IVA_RATE",
tax: 15,
},
],
payments: [
{
method: "20", // Transferencia bancaria (catálogo SRI)
amount: 575, // 500 + IVA 15%
},
],
sendEmail: true,
})
console.log("✅ Factura emitida")
console.log("Clave de acceso:", factura.accessKey)
console.log("RIDE PDF:", factura.pdfUrl)
console.log("XML:", factura.xmlUrl)
console.log("Estado:", factura.status) // PENDING | AUTHORIZED
}
main().catch(console.error)

Ejecútalo:

Ventana de terminal
npx tsx src/emitir-factura.ts

Deberías ver algo como:

✅ Factura emitida
Clave de acceso: 1305202401099337815000110010010000056781234567811
RIDE PDF: https://api.factuplan.com.ec/pdfs/inv_abc123.pdf
XML: https://api.factuplan.com.ec/xmls/inv_abc123.xml
Estado: AUTHORIZED

En pruebas, la autorización del SRI es simulada e instantánea. En producción tarda típicamente 2-5 segundos.

Paso 4 — Manejo de errores

En producción siempre vas a encontrar errores. El SDK los lanza tipados:

import {
FactuplanError,
FactuplanValidationError,
FactuplanAuthError,
FactuplanRateLimitError,
FactuplanSRIError,
} from "factuplan"
import { factuplan } from "./factuplan.js"
async function emitirConManejo() {
try {
const factura = await factuplan.invoices.create({ /* ... */ })
return { ok: true, factura }
} catch (err) {
if (err instanceof FactuplanValidationError) {
// Datos del request inválidos (400/422)
return { ok: false, kind: "validacion", code: err.code, details: err.details }
}
if (err instanceof FactuplanAuthError) {
// API key inválida (401)
return { ok: false, kind: "auth", message: "Revisa tu FACTUPLAN_API_KEY" }
}
if (err instanceof FactuplanRateLimitError) {
// 429 — cuota mensual o rate limit
return { ok: false, kind: "limite", retryAfter: err.retryAfter }
}
if (err instanceof FactuplanSRIError) {
// El SRI rechazó (código del catálogo SRI)
return { ok: false, kind: "sri", sriCode: err.sriCode, sriMessage: err.sriMessage }
}
if (err instanceof FactuplanError) {
// Otro error de la API
return { ok: false, kind: "api", status: err.statusCode, message: err.message }
}
// Error de red u otro
throw err
}
}

Errores más frecuentes en producción

ErrorCausa típicaSolución
FactuplanAuthErrorAPI key faltante o de producción usada en pruebasRevisar .env y el prefijo de la key
FactuplanValidationError con INVOICE_4002RUC del cliente inválidoValidar dígito verificador antes de enviar
FactuplanSRIError código 50Secuencial duplicadoReintentar con siguiente secuencial automático
FactuplanSRIError código 39Certificado P12 con RUC distinto al emisorCargar el P12 correcto del emisor
FactuplanRateLimitErrorPasaste tu cuota mensual o picosEsperar retryAfter o subir de plan
FactuplanError 500Caída temporalEl SDK reintenta automáticamente

Paso 5 — Webhooks

Para apps reales no basta con “fire and forget”. Configura webhooks para enterarte de:

  • invoice.authorized — el SRI autorizó.
  • invoice.rejected — el SRI rechazó (necesitas reaccionar).
  • credit_note.authorized — nota de crédito aprobada.
  • waybill.authorized — guía de remisión aprobada.

Configurar el endpoint en Factuplan

  1. Dashboard → Developer → Webhooks.
  2. Agrega tu URL (https://tu-app.com/webhooks/factuplan).
  3. Copia el webhook secret (lo necesitas para verificar firmas).

Implementar el endpoint con Express

src/webhooks.ts
import "dotenv/config"
import express from "express"
import { Factuplan } from "factuplan"
import { factuplan } from "./factuplan.js"
const app = express()
app.post(
"/webhooks/factuplan",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-factuplan-signature"] as string
try {
const event = Factuplan.webhooks.constructEvent(
req.body,
signature,
process.env.FACTUPLAN_WEBHOOK_SECRET!,
)
switch (event.type) {
case "invoice.authorized":
console.log("✅ Autorizada:", event.data.accessKey)
// Tu lógica: marcar pedido como facturado, notificar al cliente, etc.
break
case "invoice.rejected":
console.error("❌ Rechazada por SRI:", event.data.sriError)
// Tu lógica: avisar al ops team, intentar corrección automática, etc.
break
case "credit_note.authorized":
console.log("✅ NC autorizada:", event.data.accessKey)
break
}
res.json({ received: true })
} catch (err) {
console.error("Firma de webhook inválida")
res.status(400).send("Invalid signature")
}
},
)
const PORT = process.env.PORT || 3000
app.listen(PORT, () => console.log(`Webhook listener en :${PORT}`))

Importante: usa express.raw() para este endpoint (no express.json()). La verificación HMAC necesita el body sin parsear.

Probar webhooks localmente

Usa ngrok o cloudflared tunnel para exponer localhost:3000 a internet:

Ventana de terminal
ngrok http 3000
# Copia el URL https que te da y úsalo en el dashboard de Factuplan

Paso 6 — Patrón para apps web (Next.js)

Si estás en Next.js App Router, usa Server Actions:

app/actions/emitir-factura.ts
"use server"
import { factuplan } from "@/lib/factuplan"
import { revalidatePath } from "next/cache"
export async function emitirFacturaAction(formData: FormData) {
try {
const factura = await factuplan.invoices.create({
customer: {
identificationType: "RUC",
identification: formData.get("ruc") as string,
legalName: formData.get("legalName") as string,
email: formData.get("email") as string,
},
items: [
{
description: formData.get("descripcion") as string,
quantity: 1,
unitPrice: Number(formData.get("precio")),
taxType: "IVA_RATE",
tax: 15,
},
],
sendEmail: true,
})
revalidatePath("/facturas")
return { ok: true, accessKey: factura.accessKey, pdfUrl: factura.pdfUrl }
} catch (err) {
return { ok: false, error: (err as Error).message }
}
}

Y en el componente cliente:

app/facturas/nueva/page.tsx
import { emitirFacturaAction } from "@/actions/emitir-factura"
export default function NuevaFactura() {
return (
<form action={emitirFacturaAction}>
<input name="ruc" required />
<input name="legalName" required />
<input name="email" type="email" required />
<input name="descripcion" required />
<input name="precio" type="number" step="0.01" required />
<button>Emitir</button>
</form>
)
}

Paso 7 — Patrón para apps backend (NestJS)

src/facturacion/facturacion.service.ts
import { Injectable } from "@nestjs/common"
import { Factuplan } from "factuplan"
@Injectable()
export class FacturacionService {
private readonly factuplan: Factuplan
constructor() {
this.factuplan = new Factuplan(process.env.FACTUPLAN_API_KEY!, {
ruc: process.env.FACTUPLAN_RUC!,
})
}
async emitirFactura(data: EmisionDTO) {
return this.factuplan.invoices.create({
customer: {
identificationType: data.identificationType,
identification: data.identification,
legalName: data.legalName,
email: data.email,
},
items: data.items.map((item) => ({
description: item.description,
quantity: item.quantity,
unitPrice: item.unitPrice,
taxType: "IVA_RATE",
tax: 15,
})),
sendEmail: true,
})
}
}

Paso 8 — Testing

Para tests unitarios sin llamar al SRI real, usa mocks de Jest/Vitest:

__tests__/emitir.test.ts
import { describe, it, expect, vi } from "vitest"
vi.mock("factuplan", () => ({
Factuplan: vi.fn().mockImplementation(() => ({
invoices: {
create: vi.fn().mockResolvedValue({
id: "inv_mock",
accessKey: "1305202401099337815000110010010000056781234567811",
status: "AUTHORIZED",
pdfUrl: "https://example.com/mock.pdf",
xmlUrl: "https://example.com/mock.xml",
}),
},
})),
}))
describe("emitirFactura", () => {
it("emite con datos válidos", async () => {
const { emitirFactura } = await import("../src/emitir.js")
const result = await emitirFactura({ /* ... */ })
expect(result.accessKey).toHaveLength(49)
})
})

Para tests end-to-end contra Factuplan, usa siempre la API key de pruebas (ak_test_*) y no la de producción.

Buenas prácticas en producción

1. Idempotencia

Si tu sistema reintenta una emisión por timeout, evita facturas duplicadas usando un idempotency key:

const factura = await factuplan.invoices.create({
// ...
}, {
idempotencyKey: `order-${pedidoId}-attempt-${intento}`,
})

Factuplan detecta el key y, si ya emitió una factura con ese identificador, devuelve la misma sin crear nueva.

2. Reintentos con accessKey ya generado

Si la red se cae después del POST pero antes de recibir la respuesta, no reintentes ciegamente. Consulta primero el estado:

const status = await factuplan.invoices.findByIdempotencyKey(key)
if (status) return status

3. Backoff exponencial

Por defecto el SDK ya lo hace, pero si tu lógica está afuera del SDK (por ejemplo, llamadas a tu propia DB que fallan), implementa backoff:

async function withBackoff<T>(fn: () => Promise<T>, maxAttempts = 5): Promise<T> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await fn()
} catch (err) {
if (attempt === maxAttempts - 1) throw err
const delay = Math.min(1000 * Math.pow(2, attempt), 30000)
await new Promise((r) => setTimeout(r, delay))
}
}
throw new Error("unreachable")
}

4. Logs estructurados

Loguea siempre accessKey, status, sriCode y requestId para poder debuggear:

import pino from "pino"
const log = pino()
try {
const factura = await factuplan.invoices.create(payload)
log.info({ accessKey: factura.accessKey, status: factura.status }, "factura emitida")
} catch (err) {
log.error({ err, payload }, "error emitiendo factura")
}

5. Variables de entorno por ambiente

Usa keys distintas en development, staging y producción:

.env.development
FACTUPLAN_API_KEY=ak_test_dev_xxx
FACTUPLAN_WEBHOOK_SECRET=whsec_dev_xxx
# .env.production
FACTUPLAN_API_KEY=ak_live_prod_xxx
FACTUPLAN_WEBHOOK_SECRET=whsec_prod_xxx

Troubleshooting

”FactuplanAuthError: API key inválida”

Causa: la key no existe, fue revocada o se usó key de prod con dominio de prueba.

Solución: regenerar en el dashboard de Factuplan y actualizar .env.

”RUC del cliente inválido”

El SDK valida el dígito verificador antes de enviar. Si tu RUC fue mal escrito o le falta el 001 al final, el error es local.

Solución: validar con la herramienta de validación de RUC o usar el SDK con el wrapper interno.

”Timeout esperando autorización del SRI”

En horas pico el SRI puede tardar más de lo normal. El SDK reintenta automáticamente, pero si tu HTTP timeout es muy corto (5 segundos), no llega.

Solución: subir timeout a 30 segundos o esperar el webhook invoice.authorized en lugar de bloquear.

”EAI_AGAIN” o “ECONNRESET”

Errores de red transitorios.

Solución: el SDK ya reintenta. Si persiste, revisa la conectividad de tu servidor con api.factuplan.com.ec.

Repositorio de ejemplo

El repo de referencia con todos estos snippets se publicará en GitHub pronto. Por ahora puedes clonar el quickstart desde el hub de developers (próximamente en factuplan.com.ec/desarrolladores).

Resumen práctico

  1. Instala factuplan y dotenv en tu proyecto Node.js 18+.
  2. Crea API key de pruebas en app.factuplan.com.ec.
  3. Inicializa el cliente con API key + RUC.
  4. Emite tu primera factura en 10 líneas con factuplan.invoices.create({ ... }).
  5. Configura webhooks para reaccionar a estados de autorización del SRI.
  6. Maneja errores tipados (FactuplanError, FactuplanSRIError, etc.).
  7. Usa idempotency keys, backoff, logs estructurados y variables de entorno por ambiente.

Crea tu API key gratis → y empieza con el plan freemium (25 facturas/mes sin tarjeta).


Referencia completa de la API: factuplan.com.ec/docs/api Referencia del SDK: factuplan.com.ec/docs/sdk Colección Postman: descarga desde tu dashboard de Factuplan.

Preguntas frecuentes

¿Puedo emitir facturas electrónicas en Node.js sin pasar por un servicio como Factuplan?

Técnicamente sí, pero implica resolver por tu cuenta: 1) Generación de XMLs según la ficha técnica del SRI v2.32 con todos los campos obligatorios y reglas de cascada IVA/ICE; 2) Firma electrónica XAdES-BES con tu certificado P12 (cargar el P12, extraer la cadena de certificados, firmar el XML con el algoritmo correcto); 3) Comunicación con los webservices SOAP del SRI (recepción y autorización) en lugar de REST; 4) Manejo de la clave de acceso de 49 dígitos con dígito verificador módulo 11; 5) Generación del RIDE PDF con QR. Es viable en proyectos donde tienes recursos para mantener todo eso, pero la mayoría de empresas usan un servicio como Factuplan para evitarse meses de desarrollo y mantenimiento continuo cada vez que el SRI publica una nueva ficha técnica.

¿Cómo manejo timeouts cuando el SRI tarda en autorizar?

El esquema offline del SRI puede tardar desde 2-5 segundos en horarios normales hasta varios minutos en cierres de mes o picos de declaración. Hay tres patrones recomendados en Node.js: 1) HTTP timeout amplio (30-60 segundos) si tu UI puede esperar; 2) Respuesta asíncrona: emites la factura, devuelves al cliente status PENDING con la access key, y procesas la autorización vía webhook invoice.authorized en background; 3) Polling con timeout: emites, esperas X segundos, si no autorizó haces poll cada N segundos hasta que el SRI responda. La opción 2 (webhooks) es la más limpia para producción porque desacopla la UX del tiempo del SRI y no bloquea tu servidor con peticiones largas.

¿Cuál es el RUC para usar en pruebas con el SDK?

Para pruebas debes usar el RUC asociado a tu workspace de Factuplan al crearlo. Las API keys de prueba (prefijo ak_test_) generan comprobantes ficticios que NO se envían al SRI real (se mantienen en el sandbox interno de Factuplan y se borran cada hora). Puedes usar cualquier RUC válido sintácticamente para el cliente receptor en tus pruebas, por ejemplo el RUC de prueba estándar 0993378150001. En producción debes usar el RUC real de tu negocio, con la firma electrónica P12 vigente cargada en tu workspace, y los comprobantes se envían al SRI real con autorización efectiva.

¿Qué pasa si el SRI rechaza una factura? ¿Cómo lo manejo en Node.js?

Cuando el SRI rechaza, el SDK lanza FactuplanSRIError con propiedades sriCode (código del catálogo SRI) y sriMessage (descripción). Patrón recomendado: capturar el error, loguear con el accessKey y sriCode para tener trazabilidad, decidir según el código si reintentar automáticamente (por ejemplo error 50 secuencial duplicado: el SDK usa siguiente secuencial) o requerir intervención humana (por ejemplo error 39 firma no coincide con el RUC del emisor: necesitas cargar el P12 correcto). Si tu flujo requiere reaccionar después de la respuesta inicial, usa webhooks: cuando llegue invoice.rejected con event.data.sriError tu sistema decide la acción correctiva sin bloquear el cliente original.

¿Cómo configuro webhooks de Factuplan en una app Node.js detrás de un proxy o load balancer?

El webhook se verifica con HMAC del body crudo (no parseado) usando el header x-factuplan-signature contra tu webhook secret. Si tu app está detrás de NGINX, AWS ALB o Cloudflare, debes asegurar que: 1) El body llegue sin modificación al handler (no usar middleware que reparse JSON antes del verificador); 2) En Express, usar express.raw({ type: 'application/json' }) específicamente en la ruta del webhook, ANTES que cualquier express.json() global; 3) El path del webhook esté expuesto en el ALB/proxy sin reescritura del body; 4) Si usas serverless (Vercel, Cloudflare Workers), accede al request.body como Buffer antes de hacer await req.json(). Si la verificación falla con cuerpo válido, casi siempre es porque algún middleware reparseó el body.

¿Puedo emitir facturas con consumidor final desde Node.js?

Sí. En la propiedad customer del SDK, usa identificationType FINAL_CONSUMER en lugar de RUC o CEDULA, e identification con valor '9999999999999' (trece nueves) o '13 nueves' según la práctica vigente. legalName puede ser 'CONSUMIDOR FINAL'. El SRI permite consumidor final hasta el monto máximo establecido en la normativa vigente (verifica el monto actualizado en la guía de monto a consumidor final). Para montos superiores debes solicitar al cliente RUC o cédula. Algunos comercios (restaurantes, farmacias, comercio masivo) emiten todo a consumidor final por velocidad, lo cual es legal mientras no excedan el monto tope por transacción.

¿Es seguro guardar la API key de Factuplan en variables de entorno del servidor?

Sí, siempre que el servidor esté correctamente configurado. Buenas prácticas: 1) Usar un gestor de secretos (AWS Secrets Manager, Vercel Environment Variables, Doppler, 1Password Secrets Automation) en lugar de archivos .env en producción; 2) Rotar la API key periódicamente (cada 3-6 meses); 3) Usar keys distintas por ambiente (dev, staging, prod) con permisos granulares cuando estén disponibles; 4) Auditar los logs de uso de la API key en el dashboard de Factuplan para detectar acceso anómalo; 5) Tener un procedimiento documentado de revocación rápida (botón en el dashboard) si sospechas filtración; 6) Nunca commitear .env al repositorio (siempre en .gitignore). Lo que NO debes hacer es exponer la key en NEXT_PUBLIC_, VITE_PUBLIC_ ni en código JavaScript del lado del cliente.

Equipo Factuplan

Especialista en facturación electrónica

Equipo editorial de Factuplan, especializado en facturación electrónica y normativa tributaria del SRI.

Conocer al equipo

Empieza a facturar electrónicamente hoy

1 mes gratis al comprar tu firma electrónica con FirmaOK. Sin tarjeta de crédito, sin compromiso.