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.
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+
node --version # debe ser v18.x o superiorSi 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:
FACTUPLAN_API_KEY=ak_test_xxxxxxxxxxxxxxxxxxFACTUPLAN_RUC=0950194407001Paso 1 — Crear el proyecto
mkdir mi-app-facturacioncd mi-app-facturacionnpm init -ynpm install factuplan dotenv expressnpm install -D @types/node @types/express typescript tsxnpx tsc --init --target es2022 --module nodenext --moduleResolution nodenext --strictSi 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 webhooksEn .gitignore añade siempre:
.envnode_modules/dist/Paso 2 — Inicializar el cliente
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
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("ID:", factura.id) console.log("Estado:", factura.status) // DRAFT: la emisión sigue en segundo plano
// El PDF y el XML se piden aparte, cuando la factura ya está en COMPLETED // (AUTHORIZED es un estado de paso: el SRI ya autorizó y el PDF puede no estar listo): // const { status } = await factuplan.invoices.getStatus(factura.id) // const { url: pdfUrl } = await factuplan.invoices.downloadPdf(factura.id) // const { url: xmlUrl } = await factuplan.invoices.downloadXml(factura.id)}
main().catch(console.error)Ejecútalo:
npx tsx src/emitir-factura.tsDeberías ver algo como:
✅ Factura emitidaClave de acceso: 1305202401099337815000110010010000056781234567811ID: 0199c2d4-7b1e-7c3a-9f40-2d6a8e5b1c77Estado: DRAFT
create()responde enseguida con la factura enDRAFT: la firma, el envío al SRI, el PDF y el correo siguen en segundo plano. En pruebas, la autorización del SRI es simulada. En producción depende del SRI y suele tardar unos segundos. El estado final esCOMPLETED(AUTHORIZEDes un estado de paso: el SRI ya autorizó y el PDF puede no estar listo); consúltalo confactuplan.invoices.getStatus(factura.id)o espera el webhookinvoice.authorized.
Paso 4 — Manejo de errores
En producción siempre vas a encontrar errores. El SDK los lanza tipados:
import { FactuplanError, AuthenticationError, RateLimitError } 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 AuthenticationError) { // API key inválida, revocada o vencida (401) return { ok: false, kind: "auth", message: "Revisa tu FACTUPLAN_API_KEY" } } if (err instanceof RateLimitError) { // Todo 429 — superaste el límite de peticiones: espera y repite la misma petición. // Si tu API key tiene un límite mensual propio y lo agotó, también llega aquí: // el mensaje lo dice y esperar no lo resuelve. return { ok: false, kind: "limite", message: err.message } } if (err instanceof FactuplanError) { // Cualquier otro error de la API: 400 validación (GENERAL_0002), 403 plan o cupo, // 409 devReference (API_10011 / API_10012), 5xx… return { ok: false, kind: "api", status: err.statusCode, code: err.code, message: err.message, details: err.details } } // Error de red o timeout (el SDK corta a los 30 s y no reintenta). // Un rechazo del SRI no llega aquí: aparece después como estado REJECTED / RETURNED // o en el webhook invoice.rejected. throw err }}Errores más frecuentes en producción
| Error | Causa típica | Solución |
|---|---|---|
AuthenticationError (401) | API key faltante, revocada o vencida | Revisar .env y el prefijo de la key |
FactuplanError 400 con GENERAL_0002 | Datos inválidos: por ejemplo un RUC que no tiene 13 dígitos terminados en 001, o un campo que la API no conoce | Corregir el body; el detalle llega en err.details.validation |
FactuplanError 403 con SUB_12002 | Se acabó el cupo de documentos de tu plan | Subir de plan o renovar tu suscripción |
FactuplanError 409 con API_10011 / API_10012 | La devReference ya pertenece a otro documento (contenido distinto) o a uno anulado | Usar una referencia nueva; los datos del documento existente llegan en err.details |
RateLimitError (429) | Superaste el límite de peticiones | Esperar y repetir la petición con la misma devReference |
RateLimitError (429) cuyo mensaje habla del límite mensual | La API key tiene un límite mensual propio y lo agotó | Reintentar no lo resuelve |
FactuplanError 5xx, o un error de red o timeout (estos dos no son FactuplanError) | Caída temporal | El SDK no reintenta: repite tú la petición con la misma devReference |
Estado REJECTED / RETURNED (no es una excepción) | El SRI no aceptó el comprobante; lo ves con getStatus() o en el webhook invoice.rejected | Leer los mensajes del SRI, corregir y emitir de nuevo con otra devReference |
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 (las guías se emiten desde el panel: no existe/v1/developer/waybills).
Configurar el endpoint en Factuplan
- Dashboard → Developer → Webhooks.
- Agrega tu URL (
https://tu-app.com/webhooks/factuplan). - Copia el webhook secret (lo necesitas para verificar firmas).
Implementar el endpoint con Express
import "dotenv/config"import express from "express"import { verifyWebhookSignature } from "factuplan"
const app = express()
app.post( "/webhooks/factuplan", express.raw({ type: "application/json" }), (req, res) => { const valida = verifyWebhookSignature( req.body, // Buffer crudo, gracias a express.raw() req.headers["x-factuplan-signature"], process.env.FACTUPLAN_WEBHOOK_SECRET!, )
if (!valida) { console.error("Firma de webhook inválida") res.status(400).send("Invalid signature") return }
const event = JSON.parse(req.body.toString("utf-8"))
switch (event.event) { case "invoice.authorized": console.log("✅ Autorizada:", event.data.accessKey) // Tu lógica: marcar pedido como facturado, notificar al cliente, etc. // Si emitiste con devReference, event.data.devReference trae tu referencia // (junto con event.data.environment: TESTING o PRODUCTION). break case "invoice.rejected": console.error("❌ Rechazada por SRI:", event.data.reason) // Tu lógica: avisar al ops team, corregir y emitir de nuevo, etc. break case "credit_note.authorized": console.log("✅ NC autorizada:", event.data.accessKey) break }
res.json({ received: true }) },)
const PORT = process.env.PORT || 3000app.listen(PORT, () => console.log(`Webhook listener en :${PORT}`))Importante: usa
express.raw()para este endpoint (noexpress.json()). La verificación HMAC necesita el body sin parsear.
Probar webhooks localmente
Usa ngrok o cloudflared tunnel para exponer localhost:3000 a internet:
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:
"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: [ { code: "SERV-001", 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, id: factura.id, accessKey: factura.accessKey, status: factura.status } } catch (err) { return { ok: false, error: (err as Error).message } }}Y en el componente cliente:
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)
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) => ({ code: item.code, 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:
import { describe, it, expect, vi } from "vitest"
vi.mock("factuplan", () => ({ Factuplan: vi.fn().mockImplementation(() => ({ invoices: { create: vi.fn().mockResolvedValue({ id: "0199c2d4-7b1e-7c3a-9f40-2d6a8e5b1c77", accessKey: "1305202401099337815000110010010000056781234567811", sequential: "000005678", status: "DRAFT", total: "575", }), }, })),}))
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 enviando tu propia referencia en el campo devReference del body (texto libre de 1 a 100 caracteres, por ejemplo tu número de pedido). No existe ningún header de idempotencia: ni Idempotency-Key ni X-Idempotency-Key tienen efecto.
const factura = await factuplan.invoices.create({ ...payload, // customer, items, payments… devReference: `pedido-${pedidoId}`, // la MISMA en cada reintento})
if (factura.duplicate) { // Ya existía: esta llamada no emitió nada nuevo. // Mira factura.status para saber en qué quedó (en proceso, COMPLETED, REJECTED…).}La referencia es única por contribuyente, ambiente y tipo de documento. Si repites la petición con la misma referencia y el mismo contenido, la API responde 201 con la factura que ya existía y duplicate: true (revisa su status). Con la misma referencia y otro contenido responde 409 (API_10011), y si ese documento fue anulado, 409 (API_10012). En pruebas los comprobantes se borran cada hora y su referencia vuelve a quedar libre; en producción una referencia usada queda ocupada para siempre.
2. Reintentos cuando no llegó la respuesta
Si la red se cae después del POST pero antes de recibir la respuesta, no emitas otra factura con una referencia distinta. Repite la misma petición con la misma devReference: si la primera llegó, recibes esa factura con duplicate: true; si no llegó, se emite ahora. También puedes buscarla primero por tu referencia:
const { data } = await factuplan.invoices.list({ devReference: `pedido-${pedidoId}` })const existente = data[0] // cero o un documento: undefined si todavía no existe3. Backoff exponencial
El SDK no reintenta por su cuenta (hace una sola petición y la corta a los 30 segundos). Si quieres reintentar, hazlo tú con backoff y solo ante fallos temporales, y en las emisiones envía siempre la misma devReference para que un reintento nunca emita dos veces:
import { FactuplanError, RateLimitError } from "factuplan"
function esReintentable(err: unknown): boolean { if (err instanceof RateLimitError) return true // 429: limita siempre el número de reintentos if (err instanceof FactuplanError) { // 5xx, o 408 si la API cortó la petición. Un 400, 401, 403 o 409 // responden lo mismo aunque repitas. return err.statusCode >= 500 || err.statusCode === 408 } return true // error de red o timeout: no llegó ninguna respuesta}
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 || !esReintentable(err)) throw err const delay = Math.min(1000 * Math.pow(2, attempt), 30000) await new Promise((r) => setTimeout(r, delay)) } } throw new Error("unreachable")}
// El mismo payload, con la misma devReference, en todos los intentosconst factura = await withBackoff(() => factuplan.invoices.create({ ...payload, devReference: `pedido-${pedidoId}` }),)4. Logs estructurados
Loguea siempre id, accessKey, status y tu devReference para poder debuggear:
import pino from "pino"const log = pino()
try { const factura = await factuplan.invoices.create(payload) log.info( { id: factura.id, accessKey: factura.accessKey, status: factura.status, devReference: factura.devReference, duplicate: factura.duplicate, }, "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:
FACTUPLAN_API_KEY=ak_test_dev_xxxFACTUPLAN_WEBHOOK_SECRET=whsec_dev_xxx
# .env.productionFACTUPLAN_API_KEY=ak_live_prod_xxxFACTUPLAN_WEBHOOK_SECRET=whsec_prod_xxxTroubleshooting
”AuthenticationError” (401)
Causa: la key no existe, fue revocada o venció. Pruebas y producción usan el mismo host: el ambiente lo decide el prefijo de la key (ak_test_ o ak_live_).
Solución: regenerar en el dashboard de Factuplan y actualizar .env.
”RUC del cliente inválido”
El SDK no valida nada en local: el error llega de la API como FactuplanError 400 (GENERAL_0002), con el detalle en err.details.validation. La API comprueba el formato (13 dígitos terminados en 001 para RUC, 10 dígitos para cédula), no el dígito verificador.
Solución: valida el dígito verificador en tu sistema antes de enviar, por ejemplo con la herramienta de validación de RUC.
”La factura se queda en proceso”
create() no espera al SRI: responde enseguida con la factura en DRAFT y la autorización sigue en segundo plano. En horas pico el SRI puede tardar más de lo normal.
Solución: no bloquees tu petición esperando. Espera el webhook invoice.authorized o consulta factuplan.invoices.getStatus(id) hasta COMPLETED. El SDK corta cada petición a los 30 segundos y no reintenta.
”EAI_AGAIN” o “ECONNRESET”
Errores de red transitorios.
Solución: el SDK no reintenta; repite tú la petición (con la misma devReference si era una emisión). Si persiste, revisa la conectividad de tu servidor con api-rest.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
- Instala
factuplanydotenven tu proyecto Node.js 18+. - Crea API key de pruebas en
app.factuplan.com.ec. - Inicializa el cliente con API key + RUC.
- Emite tu primera factura en 10 líneas con
factuplan.invoices.create({ ... }). - Configura webhooks para reaccionar a estados de autorización del SRI.
- Maneja errores tipados (
FactuplanError,AuthenticationError,RateLimitError). - Usa
devReferencepara la idempotencia, 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 unos segundos en horarios normales hasta varios minutos en cierres de mes o picos de declaración. La API no te hace esperar al SRI: create() responde enseguida con la factura en estado DRAFT y la emisión sigue en segundo plano, así que no necesitas un timeout HTTP amplio. Hay dos patrones en Node.js: 1) Webhooks: devuelves a tu cliente la clave de acceso con la factura en proceso y actualizas el estado cuando llegue invoice.authorized (o invoice.rejected / invoice.error); 2) Polling: consultas factuplan.invoices.getStatus(id) cada N segundos hasta COMPLETED (AUTHORIZED es un estado de paso: el SRI ya autorizó y el PDF puede no estar listo) o hasta REJECTED, RETURNED o ERROR. La opción 1 (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?
El rechazo del SRI no llega como una excepción de create(): la emisión es asíncrona, create() responde con la factura en DRAFT y el rechazo aparece después como estado REJECTED o RETURNED. Te enteras de dos formas: por el webhook invoice.rejected, cuyo data.reason trae los mensajes del SRI, o consultando el estado con factuplan.invoices.getStatus(id). Patrón recomendado: loguea el id, la accessKey y los mensajes del SRI para tener trazabilidad y decide según el motivo. Si fue un problema temporal, factuplan.invoices.retry(id) vuelve a procesar el mismo comprobante (tras un rechazo lo regenera con una clave de acceso nueva y los mismos datos). Si el rechazo fue por datos inválidos, el reintento volverá a fallar: corrige los datos y emite una factura nueva con otra devReference.
¿Cómo configuro webhooks de Factuplan en una app Node.js detrás de un proxy o load balancer?
El webhook se verifica con el header x-factuplan-signature, que tiene la forma t=<timestamp>,v1=<firma>: la firma es el HMAC-SHA256, con tu webhook secret, de la cadena <timestamp>.<body crudo>. La función verifyWebhookSignature del SDK hace ese cálculo por ti y rechaza entregas con más de 5 minutos. 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 tu handler recibe un Request estándar (por ejemplo un Route Handler de Next.js), lee el body crudo con await request.text() y pásalo tal cual a verifyWebhookSignature, en lugar de usar await request.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; es el único valor que la API acepta para ese tipo). legalName puede ser 'CONSUMIDOR FINAL'. El campo email es obligatorio también para consumidor final: si falta, la API responde 400. 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.
¿Te sirvió este artículo? Marca a Factuplan como fuente preferida y aparecerá más en tus resultados de Google.
Añadir a fuentes preferidas
¿Aún no tienes firma electrónica?
Sin ella no puedes emitir comprobantes ante el SRI. Consíguela 100% en línea.
Compra tu firma electrónica