Devs
February 10, 2026

Facturación CFDI con Node.js: Tutorial Completo con Ejemplos de Código

Node.js es una de las tecnologías más populares para construir aplicaciones web y APIs. Si estás desarrollando un SaaS, e-commerce o cualquier sistema que necesite facturación electrónica en México, este tutorial te muestra cómo integrar CFDI 4.0 usando JavaScript.

Cubriremos desde la configuración inicial hasta casos de uso avanzados, con código que puedes copiar y adaptar a tu proyecto.

Configuración inicial

Requisitos previos

  • Node.js 18+ instalado
  • Cuenta en gigstack con API keys
  • Conocimientos básicos de async/await y fetch

Instalación de dependencias

Creamos un proyecto nuevo con las dependencias necesarias:

mkdir facturacion-cfdi cd facturacion-cfdi npm init -y npm install dotenv node-fetch

Variables de entorno

Crea un archivo .env con tus credenciales:

GIGSTACK_API_KEY=tu_jwt_token_aqui GIGSTACK_API_URL=https://api.gigstack.io/v2

Cliente HTTP base

Creamos una clase reutilizable para interactuar con la API:

// gigstack-client.js import fetch from 'node-fetch'; import 'dotenv/config'; class GigstackClient { constructor() { this.baseUrl = process.env.GIGSTACK_API_URL; this.apiKey = process.env.GIGSTACK_API_KEY; } async request(endpoint, options = {}) { const url = this.baseUrl + endpoint; const response = await fetch(url, { ...options, headers: { 'Authorization': 'Bearer ' + this.apiKey, 'Content-Type': 'application/json', ...options.headers } }); const data = await response.json(); if (!response.ok) { throw new Error(data.message || 'Error en la API'); } return data; } async get(endpoint) { return this.request(endpoint, { method: 'GET' }); } async post(endpoint, body) { return this.request(endpoint, { method: 'POST', body: JSON.stringify(body) }); } async delete(endpoint, body = {}) { return this.request(endpoint, { method: 'DELETE', body: JSON.stringify(body) }); } } export default new GigstackClient();

Gestión de clientes

Antes de facturar, necesitas crear clientes con sus datos fiscales:

// clients.js import client from './gigstack-client.js'; // Crear un nuevo cliente async function createClient(clientData) { const response = await client.post('/clients', { name: clientData.name, // Razón social exacta email: clientData.email, tax_id: clientData.rfc, // RFC tax_system: clientData.regimen, // Código de régimen fiscal zip_code: clientData.codigoPostal }); console.log('Cliente creado:', response.data.id); return response.data; } // Obtener cliente por RFC async function getClientByRFC(rfc) { const response = await client.get('/clients?tax_id=' + rfc); return response.data[0] || null; } // Validar datos fiscales async function validateClientData(rfc, name) { const response = await client.post('/clients/validate', { tax_id: rfc, name: name }); return response.data.valid; } // Ejemplo de uso async function main() { // Primero validamos const isValid = await validateClientData( 'XAXX010101000', 'Publico En General' ); if (isValid) { const newClient = await createClient({ name: 'Publico En General', email: 'cliente@example.com', rfc: 'XAXX010101000', regimen: '616', codigoPostal: '06600' }); } }

Crear facturas CFDI

El endpoint principal para emitir facturas:

// invoices.js import client from './gigstack-client.js'; async function createInvoice(invoiceData) { const response = await client.post('/invoices/income', { client: invoiceData.clientId, items: invoiceData.items.map(item => ({ description: item.description, quantity: item.quantity, unit_price: item.unitPrice, product_key: item.claveSAT, // Clave producto SAT unit_key: item.claveUnidad, // Clave unidad SAT taxes: [{ type: 'IVA', rate: 0.16, inclusive: false }] })), payment_form: invoiceData.formaPago, // 01=Efectivo, 03=Transferencia, etc. payment_method: invoiceData.metodoPago, // PUE o PPD use: invoiceData.usoCFDI, // G01, G03, etc. currency: invoiceData.currency || 'MXN', notes: invoiceData.notas || '' }); return response.data; } // Ejemplo: Factura de servicio de software async function facturarServicioSoftware() { const invoice = await createInvoice({ clientId: 'cli_abc123', items: [{ description: 'Licencia mensual de software SaaS', quantity: 1, unitPrice: 999.00, claveSAT: '43232408', // Software como servicio claveUnidad: 'E48' // Unidad de servicio }], formaPago: '03', // Transferencia electrónica metodoPago: 'PUE', // Pago en una sola exhibición usoCFDI: 'G03' // Gastos en general }); console.log('Factura creada:'); console.log(' UUID:', invoice.uuid); console.log(' Total:', invoice.total); console.log(' PDF:', invoice.pdf_url); console.log(' XML:', invoice.xml_url); return invoice; } // Ejemplo: Factura con múltiples conceptos async function facturarMultiplesProductos() { const invoice = await createInvoice({ clientId: 'cli_xyz789', items: [ { description: 'Desarrollo de aplicación móvil', quantity: 1, unitPrice: 50000.00, claveSAT: '43232301', claveUnidad: 'E48' }, { description: 'Hosting anual', quantity: 12, unitPrice: 500.00, claveSAT: '43233501', claveUnidad: 'E48' }, { description: 'Dominio .com.mx', quantity: 1, unitPrice: 350.00, claveSAT: '43233502', claveUnidad: 'E48' } ], formaPago: '03', metodoPago: 'PUE', usoCFDI: 'G03' }); return invoice; }

Facturas en moneda extranjera

Para clientes que pagan en USD u otra moneda:

async function facturarEnDolares() { const invoice = await createInvoice({ clientId: 'cli_internacional', items: [{ description: 'Consultoría técnica internacional', quantity: 40, unitPrice: 150.00, // USD claveSAT: '80111501', claveUnidad: 'E48' }], formaPago: '03', metodoPago: 'PUE', usoCFDI: 'G03', currency: 'USD' // gigstack obtiene el tipo de cambio automáticamente }); console.log('Total USD:', invoice.total); console.log('Tipo de cambio:', invoice.exchange_rate); return invoice; }

Cancelación de facturas

Para cancelar una factura emitida:

async function cancelInvoice(uuid, reason, substituteUuid = null) { // Motivos de cancelación: // 01 = Comprobante emitido con errores con relación // 02 = Comprobante emitido con errores sin relación // 03 = No se llevó a cabo la operación // 04 = Operación nominativa relacionada en factura global const body = { uuid: uuid, cancellation_reason: reason }; // Si el motivo es 01, debe incluir UUID de sustitución if (reason === '01' && substituteUuid) { body.substitute_uuid = substituteUuid; } const response = await client.delete('/invoices/' + uuid, body); console.log('Cancelación solicitada:', response.data.status); // Puede ser: 'cancelled', 'pending_acceptance', 'rejected' return response.data; } // Ejemplo: Cancelar por error y reexpedir async function cancelarYReexpedir(uuidOriginal, clientId, items) { // 1. Crear la factura correcta primero const newInvoice = await createInvoice({ clientId, items, formaPago: '03', metodoPago: 'PUE', usoCFDI: 'G03' }); // 2. Cancelar la original con relación a la nueva await cancelInvoice(uuidOriginal, '01', newInvoice.uuid); return newInvoice; }

Complementos de pago

Para facturas con método de pago PPD (Pago en Parcialidades o Diferido):

async function registerPayment(paymentData) { const response = await client.post('/payments/register', { invoice_uuid: paymentData.invoiceUuid, amount: paymentData.amount, payment_date: paymentData.date, // Fecha del pago real payment_form: paymentData.forma // 03=Transferencia, etc. }); console.log('Complemento generado:', response.data.complement_uuid); return response.data; } // Ejemplo: Factura pagada en 2 parcialidades async function pagarEnParcialidades() { // Primero crear factura PPD const invoice = await createInvoice({ clientId: 'cli_abc123', items: [{ description: 'Proyecto de desarrollo', quantity: 1, unitPrice: 100000.00, claveSAT: '43232301', claveUnidad: 'E48' }], formaPago: '99', // Por definir (PPD) metodoPago: 'PPD', // Pago en parcialidades usoCFDI: 'G03' }); // Registrar primer pago (50%) const payment1 = await registerPayment({ invoiceUuid: invoice.uuid, amount: 58000.00, // 50% con IVA date: '2026-02-15', forma: '03' }); // Registrar segundo pago (50%) const payment2 = await registerPayment({ invoiceUuid: invoice.uuid, amount: 58000.00, date: '2026-03-15', forma: '03' }); return { invoice, payments: [payment1, payment2] }; }

Manejo de errores

Implementación robusta de manejo de errores:

class GigstackError extends Error { constructor(message, code, details) { super(message); this.code = code; this.details = details; } } async function safeCreateInvoice(invoiceData) { try { return await createInvoice(invoiceData); } catch (error) { // Errores comunes y cómo manejarlos if (error.message.includes('RFC')) { throw new GigstackError( 'RFC inválido o no registrado en SAT', 'INVALID_RFC', { rfc: invoiceData.clientId } ); } if (error.message.includes('razón social')) { throw new GigstackError( 'La razón social no coincide con el RFC', 'NAME_MISMATCH', { suggestion: 'Verifica el nombre exacto en constancia SAT' } ); } if (error.message.includes('régimen fiscal')) { throw new GigstackError( 'Régimen fiscal inválido para este RFC', 'INVALID_TAX_SYSTEM', { suggestion: 'Verifica el régimen en la constancia de situación fiscal' } ); } if (error.message.includes('código postal')) { throw new GigstackError( 'Código postal no corresponde al domicilio fiscal', 'INVALID_ZIP_CODE', { suggestion: 'Usa el CP del domicilio fiscal registrado en SAT' } ); } // Error genérico throw new GigstackError( 'Error al crear factura: ' + error.message, 'INVOICE_ERROR', { original: error } ); } } // Uso con try/catch async function handleInvoiceRequest(req, res) { try { const invoice = await safeCreateInvoice(req.body); res.json({ success: true, invoice }); } catch (error) { if (error instanceof GigstackError) { res.status(400).json({ success: false, error: error.code, message: error.message, details: error.details }); } else { res.status(500).json({ success: false, error: 'INTERNAL_ERROR', message: 'Error interno del servidor' }); } } }

Integración con Express

Ejemplo completo de API REST para facturación:

import express from 'express'; import client from './gigstack-client.js'; const app = express(); app.use(express.json()); // Crear factura app.post('/api/invoices', async (req, res) => { try { const invoice = await createInvoice(req.body); res.json({ success: true, data: invoice }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } }); // Obtener factura por UUID app.get('/api/invoices/:uuid', async (req, res) => { try { const response = await client.get('/invoices/' + req.params.uuid); res.json({ success: true, data: response.data }); } catch (error) { res.status(404).json({ success: false, error: 'Factura no encontrada' }); } }); // Cancelar factura app.delete('/api/invoices/:uuid', async (req, res) => { try { const result = await cancelInvoice( req.params.uuid, req.body.reason, req.body.substituteUuid ); res.json({ success: true, data: result }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } }); // Registrar pago app.post('/api/payments', async (req, res) => { try { const payment = await registerPayment(req.body); res.json({ success: true, data: payment }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } }); app.listen(3000, () => { console.log('API de facturación corriendo en puerto 3000'); });

Testing

Usa el ambiente de staging para pruebas:

// test-invoices.js import { describe, it, expect } from 'vitest'; import client from './gigstack-client.js'; describe('Facturación CFDI', () => { it('debe crear una factura válida', async () => { const invoice = await createInvoice({ clientId: 'cli_test_123', items: [{ description: 'Producto de prueba', quantity: 1, unitPrice: 100.00, claveSAT: '43232408', claveUnidad: 'E48' }], formaPago: '03', metodoPago: 'PUE', usoCFDI: 'G03' }); expect(invoice.uuid).toBeDefined(); expect(invoice.total).toBe(116.00); expect(invoice.xml_url).toContain('http'); }); it('debe rechazar RFC inválido', async () => { await expect(createInvoice({ clientId: 'cli_rfc_invalido', // ... })).rejects.toThrow('RFC'); }); });

Recursos adicionales

Con estos ejemplos tienes todo lo necesario para integrar facturación CFDI 4.0 en tus aplicaciones Node.js. El código es modular y fácil de adaptar a tu arquitectura específica.

Blogs que te pueden gustar