npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@betalyx/facturacion-ec

v1.1.8

Published

Facturación electrónica SRI Ecuador — XAdES-BES SHA-256, validadores, catálogos.

Readme

@betalyx/facturacion-ec

Facturación electrónica del SRI Ecuador en TypeScript: emisión firmada (XAdES-BES SHA-256) de los 5 comprobantes, RIDE (PDF + QR) y helper de anulación.


Para agentes de IA (léelo primero)

Este paquete emite comprobantes electrónicos del SRI Ecuador (facturas, notas de crédito/débito, retenciones, guías de remisión).

  • Núcleo (emisión XML, firma, envío al SRI): import { crearCliente } from '@betalyx/facturacion-ec'
  • RIDE (PDF + QR): import { generarRideFactura } from '@betalyx/facturacion-ec/ride' — submódulo separado; requiere las optionalDependencies pdfkit y qrcode.
  • Patrón de resultado: todas las funciones de emisión devuelven Promise<ResultadoEmision>. Siempre revisa resultado.exito. NO uses try/catch para el flujo normal de éxito/fallo; try/catch es solo para errores de programación (bugs).

Ver Reglas que un agente debe respetar para los gotchas críticos antes de integrar.


Instalación

pnpm add @betalyx/facturacion-ec
npm i @betalyx/facturacion-ec
yarn add @betalyx/facturacion-ec

El RIDE (PDF) requiere las dependencias opcionales pdfkit y qrcode. Con npm/pnpm se instalan por defecto; si usas --no-optional tendrás que instalarlas a mano cuando uses el submódulo /ride.


Uso rápido (Factura)

import { crearCliente, Ambiente, TipoEmision, TipoIdentificacion } from '@betalyx/facturacion-ec';
import { readFileSync } from 'node:fs';

const cliente = crearCliente({
  emisor: {
    ruc: '0924383631001',
    razonSocial: 'MI EMPRESA S.A.',
    direccionMatriz: 'Guayaquil',
    direccionEstablecimiento: 'Guayaquil',
    codigoEstablecimiento: '001',
    puntoEmision: '001',
    obligadoContabilidad: true,
    ambiente: Ambiente.PRUEBAS,
    tipoEmision: TipoEmision.NORMAL,
  },
  certificado: {
    buffer: readFileSync('./cert.p12'),
    clave: 'mi-clave',
  },
});

// emitirFactura SIEMPRE devuelve ResultadoEmision — nunca lanza en el flujo normal
const resultado = await cliente.emitirFactura(
  {
    fechaEmision: new Date(),
    tipoIdentificacionComprador: TipoIdentificacion.CEDULA,
    identificacionComprador: '0926687856',
    razonSocialComprador: 'JUAN PEREZ',
    totalSinImpuestos: 100,
    totalDescuento: 0,
    totalConImpuestos: [{ codigo: '2', codigoPorcentaje: '4', baseImponible: 100, valor: 15 }],
    importeTotal: 115,
    moneda: 'DOLAR',
    pagos: [{ formaPago: '01', total: 115 }],
    detalles: [/* ... */],
  },
  '000000001',
);

// Patrón uniforme: revisar resultado.exito
if (resultado.exito) {
  console.log('AUTORIZADO:', resultado.numeroAutorizacion);
} else {
  console.log('Estado:', resultado.estado);
  console.log('TraceId (para buscar en logs):', resultado.traceId);
  console.log('Para el usuario:', resultado.mensajeAmigable);
  // resultado.diagnostico[0] = {
  //   codigoSri: 'LOCAL_ErrorCertificadoExpirado' o '35',
  //   categoria: 'CERTIFICADO' | 'TOTALES' | ...,
  //   recuperable: true | false,
  //   accionSugerida: 'Renovar el certificado...',
  // }
  console.log('Diagnóstico:', resultado.diagnostico);
}

Nota de Crédito

Cuándo usarlo: para acreditar (reversar, total o parcialmente) una factura ya autorizada. La NC referencia la factura original por número y fecha. No es posible emitir una NC a consumidor final (el SRI la rechaza; la librería la valida localmente).

import { crearCliente, TipoIdentificacion } from '@betalyx/facturacion-ec';

// `cliente` es la instancia de crearCliente({...}) de la sección "Uso rápido".
const resultado = await cliente.emitirNotaCredito(
  {
    fechaEmision: new Date(),
    tipoIdentificacionComprador: TipoIdentificacion.CEDULA,
    identificacionComprador: '0926687856',
    razonSocialComprador: 'JUAN PEREZ',
    codDocModificado: '01',                       // 01 = factura
    numDocModificado: '001-001-000000123',        // factura que se acredita
    fechaEmisionDocSustento: new Date('2026-05-01'),
    totalSinImpuestos: 100,
    totalConImpuestos: [{ codigo: '2', codigoPorcentaje: '4', baseImponible: 100, valor: 15 }],
    valorModificacion: 115,
    moneda: 'DOLAR',
    motivo: 'Devolución parcial',
    detalles: [/* ... mismos DetalleFactura ... */],
  },
  '000000001',
);
// Mismo patrón que factura: revisar resultado.exito

Notas:

  • La NC a consumidor final se rechaza localmente (ErrorIdentificacionInvalida) antes de llegar al SRI.
  • codDocModificado: '01' para créditar una factura; ver catálogo CATALOGOS.documentosSustento para otros tipos.

Nota de Débito

Cuándo usarlo: para cobrar recargos (intereses, comisiones) sobre una factura ya emitida. Los motivos reemplazan a los detalles de línea. Usa calcularTotalesNotaDebito para calcular los totales automáticamente.

import { calcularTotalesNotaDebito, TipoIdentificacion } from '@betalyx/facturacion-ec';

// `cliente` es la instancia de crearCliente({...}) de la sección "Uso rápido".
const motivos = [{ razon: 'Interés por mora', valor: 1 }];
const totales = calcularTotalesNotaDebito(motivos, { codigoPorcentaje: '4', tarifa: 15 });

const resultado = await cliente.emitirNotaDebito(
  {
    fechaEmision: new Date(),
    tipoIdentificacionComprador: TipoIdentificacion.CEDULA,
    identificacionComprador: '0926687856',
    razonSocialComprador: 'JUAN PEREZ',
    codDocModificado: '01',                       // 01 = factura
    numDocModificado: '001-001-000000123',        // factura que origina el débito
    fechaEmisionDocSustento: new Date('2026-05-01'),
    totalSinImpuestos: totales.totalSinImpuestos,
    impuestos: totales.impuestos,
    valorTotal: totales.valorTotal,
    motivos,
  },
  '000000001',
);
// Mismo patrón que factura: revisar resultado.exito

Notas:

  • Para varias tarifas de IVA arma impuestos[] manualmente y omite el helper.
  • calcularTotalesNotaDebito asume una sola tarifa de IVA para todos los motivos.

Guía de Remisión

Cuándo usarlo: para documentar el transporte de mercadería. No tiene montos ni impuestos; su estructura es emisor → transportista → destinatarios → detalles. La clave de acceso se deriva de fechaIniTransporte (este comprobante no tiene fechaEmision propia).

import { TipoIdentificacion } from '@betalyx/facturacion-ec';

// `cliente` es la instancia de crearCliente({...}) de la sección "Uso rápido".
const resultado = await cliente.emitirGuiaRemision(
  {
    dirPartida: 'Av. Principal 123, Guayaquil',
    razonSocialTransportista: 'TRANSPORTE RAPIDO S.A.',
    tipoIdentificacionTransportista: TipoIdentificacion.RUC,
    rucTransportista: '0924383631001',
    placa: 'ABC-1234',
    fechaIniTransporte: new Date(),
    fechaFinTransporte: new Date(),
    destinatarios: [
      {
        identificacionDestinatario: '0926687856',
        razonSocialDestinatario: 'JUAN PEREZ',
        dirDestinatario: 'Calle Secundaria 456, Quito',
        motivoTraslado: 'Traslado de mercadería',
        detalles: [
          { descripcion: 'PRODUCTO A', cantidad: 10 },
          { descripcion: 'PRODUCTO B', cantidad: 5 },
        ],
      },
    ],
  },
  '000000001',
);
// Mismo patrón que factura: revisar resultado.exito

Notas:

  • La clave de acceso usa fechaIniTransporte en lugar de fechaEmision.
  • Soporta múltiples destinatarios en el mismo comprobante.

Comprobante de Retención

Cuándo usarlo: para documentar la retención de impuestos (Renta e IVA) al pagar a un proveedor. Usa calcularValorRetenido para calcular el monto retenido en cada línea.

import { calcularValorRetenido, TipoIdentificacion } from '@betalyx/facturacion-ec';

// `cliente` es la instancia de crearCliente({...}) de la sección "Uso rápido".
const fechaEmision = new Date();
const periodoFiscal = `${String(fechaEmision.getMonth() + 1).padStart(2, '0')}/${fechaEmision.getFullYear()}`;

const resultado = await cliente.emitirRetencion(
  {
    fechaEmision,
    tipoIdentificacionSujetoRetenido: TipoIdentificacion.RUC,
    identificacionSujetoRetenido: '0924383631001',
    razonSocialSujetoRetenido: 'PROVEEDOR S.A.',
    periodoFiscal,                                 // 'mm/aaaa'
    docsSustento: [
      {
        codSustento: '01',                         // Tabla 4 ATS
        codDocSustento: '01',                      // 01 = factura
        numDocSustento: '001001000000123',         // 15 dígitos sin guiones
        fechaEmisionDocSustento: new Date('2026-05-01'),
        pagoLocExt: '01',                          // 01 = pago local
        totalSinImpuestos: 100,
        importeTotal: 115,
        impuestos: [
          {
            codImpuestoDocSustento: '2',           // 2 = IVA
            codigoPorcentaje: '4',                 // 4 = 15%
            baseImponible: 100,
            tarifa: 15,
            valorImpuesto: 15,
          },
        ],
        retenciones: [
          {
            codigo: '1',                           // 1 = Renta
            codigoRetencion: '312',                // Tabla 19 IR
            baseImponible: 100,
            porcentajeRetener: 1.75,
            valorRetenido: calcularValorRetenido(100, 1.75),  // → 1.75
          },
        ],
        pagos: [{ formaPago: '01', total: 115 }],
      },
    ],
  },
  '000000001',
);
// Mismo patrón que factura: revisar resultado.exito

Notas:

  • Alcance MVP: retención local sobre documento sustento. Sin reembolsos, dividendos ni pago al exterior.
  • periodoFiscal tiene formato 'mm/aaaa' (p.ej. '06/2026').

RIDE (PDF + QR)

Los 5 comprobantes tienen generador de RIDE. Todos devuelven Promise<Uint8Array> con los bytes del PDF.

Importante: el RIDE se importa desde @betalyx/facturacion-ec/ride, no desde la raíz.

import {
  generarRideFactura,
  generarRideNotaCredito,
  generarRideNotaDebito,
  generarRideRetencion,
  generarRideGuiaRemision,
} from '@betalyx/facturacion-ec/ride';
import { writeFileSync } from 'node:fs';

// Factura
const pdfFac = await generarRideFactura({
  emisor,   // ConfiguracionEmisor
  factura,  // DatosFactura
  claveAcceso: resultado.claveAcceso,  // 49 dígitos (siempre presente; = numeroAutorizacion cuando hay autorización online)
  marca: {
    colorPrimario: '#1a4f8b',
    colorSecundario: '#eef2f7',
    textoPie: 'Gracias por su compra — MI EMPRESA',
    datosContacto: { telefono: '04-2000000', email: '[email protected]', web: 'miempresa.ec' },
    // logo?: Uint8Array   // PNG/JPG opcional
    // fuente?: Uint8Array // TTF/OTF — por defecto usa Inter (SIL OFL)
  },
});
writeFileSync('factura.pdf', pdfFac);

// Nota de Crédito
const pdfNc = await generarRideNotaCredito({ emisor, notaCredito, claveAcceso, marca });
writeFileSync('nota-credito.pdf', pdfNc);

// Nota de Débito
const pdfNd = await generarRideNotaDebito({ emisor, notaDebito, claveAcceso, marca });
writeFileSync('nota-debito.pdf', pdfNd);

// Comprobante de Retención
const pdfRet = await generarRideRetencion({ emisor, retencion, claveAcceso, marca });
writeFileSync('retencion.pdf', pdfRet);

// Guía de Remisión
const pdfGr = await generarRideGuiaRemision({ emisor, guiaRemision, claveAcceso, marca });
writeFileSync('guia-remision.pdf', pdfGr);

Tipografía: el RIDE embebe Inter Regular e Inter Bold por defecto (SIL OFL 1.1 — ver LICENSE-fonts en el paquete). Para usar tu propia tipografía TTF/OTF, pasa marca.fuente.

Dependencias: generarRideX requiere pdfkit y qrcode, declaradas como optionalDependencies. El núcleo de emisión XML no las carga si no importas /ride.


Anulación de comprobantes

La anulación ante el SRI es un trámite por el portal (SRI en línea), no una API. prepararAnulacion no anula nada: calcula las reglas y arma una guía del trámite a partir de la clave de acceso.

import { prepararAnulacion } from '@betalyx/facturacion-ec';

const r = prepararAnulacion({
  claveAcceso: '...49 dígitos...',
  esConsumidorFinal: false,   // declarado por ti; la clave no lo indica
  // feriados: [new Date(2026, 7, 10)],  // opcional; si no se pasan, solo se ajusta por fin de semana
});

if (!r.anulablePorPortal) {
  console.log(r.motivo); // p.ej. factura a consumidor final
} else if (!r.dentroDePlazo) {
  console.log(r.motivo); // fuera de plazo → usar nota de crédito
} else {
  console.log(r.tipoComprobante, 'anulable hasta', r.fechaLimite);
  console.log(r.requiereAceptacionReceptor ? 'Requiere aceptación del receptor (5 días hábiles)' : 'No requiere aceptación');
}
console.log(r.instrucciones); // mensaje guía del trámite

Notas:

  • Plazo: hasta el día 7 del mes siguiente a la emisión (ajustado a día hábil).
  • NC/ND/retenciones requieren aceptación del receptor (5 días hábiles).
  • Si no se pasan feriados, solo se ajusta por fin de semana; el campo instrucciones lo advierte.
  • Para feriados nacionales (Ecuador), pásalos explícitamente como Date[].

Reglas que un agente debe respetar

Lista de gotchas críticos para integración correcta:

  1. No usar try/catch para el flujo normal. emitirFactura, emitirNotaCredito, emitirNotaDebito, emitirRetencion y emitirGuiaRemision SIEMPRE devuelven Promise<ResultadoEmision> — nunca lanzan en el flujo normal de éxito/fallo del SRI. Controla el resultado con resultado.exito. Reserva try/catch para errores de programación (bug que impide que la función corra).

  2. Consumidor final: usa identificación 9999999999999 + razón social CONSUMIDOR FINAL. La librería normaliza automáticamente en facturas. La Nota de Crédito a consumidor final se rechaza localmente (ErrorIdentificacionInvalida) antes de llegar al SRI — nunca la intentes emitir.

  3. La clave de acceso tiene 49 dígitos. En modo offline (sin enviar al SRI), resultado.claveAcceso (49 dígitos) actúa como número de autorización. Pasa estos 49 dígitos a generarRideX como claveAcceso.

  4. La anulación NO es por API. prepararAnulacion solo guía el trámite; no anula nada en el SRI. La anulación efectiva se hace por el portal SRI en línea.

  5. El RIDE se importa de /ride, no de la raíz. import { generarRideFactura } from '@betalyx/facturacion-ec/ride' — si importas desde la raíz obtendrás un error de resolución de módulo.

  6. Para diagnosticar fallos: usa resultado.estado (estado del SRI), resultado.diagnostico[] ({ codigoSri, categoria, recuperable, accionSugerida }), resultado.mensajeAmigable (texto para mostrar al usuario), y resultado.traceId (para correlacionar con logs).


Referencia de API pública

| Símbolo | Entry point | Firma resumida | Devuelve | |---|---|---|---| | crearCliente | . | crearCliente(opts: OpcionesClienteBetalyx) | ClienteBetalyx | | cliente.emitirFactura | . | (factura: DatosFactura, secuencial: string) | Promise<ResultadoEmision> | | cliente.emitirNotaCredito | . | (nc: DatosNotaCredito, secuencial: string) | Promise<ResultadoEmision> | | cliente.emitirNotaDebito | . | (nd: DatosNotaDebito, secuencial: string) | Promise<ResultadoEmision> | | cliente.emitirRetencion | . | (ret: DatosRetencion, secuencial: string) | Promise<ResultadoEmision> | | cliente.emitirGuiaRemision | . | (guia: DatosGuiaRemision, secuencial: string) | Promise<ResultadoEmision> | | calcularTotalesNotaDebito | . | (motivos: MotivoNotaDebito[], iva: { codigoPorcentaje: string; tarifa: number }) | TotalesNotaDebito | | calcularValorRetenido | . | (baseImponible: number, porcentajeRetener: number) | number | | generarClaveAcceso | . | (params: ParamsClaveAcceso) | string | | parsearClaveAcceso | . | (clave: string) | ClaveAccesoParseada | | validarClaveAcceso | . | (clave: string) | boolean | | prepararAnulacion | . | (opts: OpcionesAnulacion) | ResultadoAnulacion | | validarCedula | . | (cedula: string) | ResultadoValidacion | | validarRuc | . | (ruc: string) | ResultadoValidacion | | validarIdentificacion | . | (identificacion: string, tipo: TipoIdentificacion) | ResultadoValidacion | | obtenerTarifaIva | . | (codigo: string) | number \| null \| undefined | | CATALOGOS | . | { tiposImpuesto, codigosPorcentajeIva, formasPago, tiposIdentificacion, documentosSustento } | objeto de solo lectura | | generarRideFactura | ./ride | (opts: OpcionesRideFactura) | Promise<Uint8Array> | | generarRideNotaCredito | ./ride | (opts: OpcionesRideNotaCredito) | Promise<Uint8Array> | | generarRideNotaDebito | ./ride | (opts: OpcionesRideNotaDebito) | Promise<Uint8Array> | | generarRideRetencion | ./ride | (opts: OpcionesRideRetencion) | Promise<Uint8Array> | | generarRideGuiaRemision | ./ride | (opts: OpcionesRideGuiaRemision) | Promise<Uint8Array> | | generarQrPng | ./ride | (contenido: string) | Promise<Buffer> |

Los tipos de datos (DatosFactura, ConfiguracionEmisor, ResultadoEmision, etc.) se exportan del entry point . y se documentan en los .d.ts del paquete.


Comprobantes soportados

| Tipo | Código | Estado | |---|---|---| | Factura | 01 | ✅ | | Nota de Crédito | 04 | ✅ | | Nota de Débito | 05 | ✅ | | Guía de Remisión | 06 | ✅ | | Retención | 07 | ✅ |


Errores

import {
  ErrorSri,
  ErrorCertificadoInvalido,
  ErrorCertificadoExpirado,
  ErrorClaveAccesoInvalida,
  ErrorFirma,
  ErrorRedSri,
  ErrorTimeoutSri,
  ErrorTotalesInconsistentes,
  ErrorIdentificacionInvalida,  // NC/ND a consumidor final; ver sección "Nota de Crédito" y Gotcha #2
  ErrorGuiaRemisionInvalida,    // guía con datos incoherentes; ver sección "Guía de Remisión"
} from '@betalyx/facturacion-ec';

Estos errores solo se lanzan por bugs del programador (configuración incorrecta, tipo incorrecto). El flujo normal de éxito/fallo del SRI se comunica a través de ResultadoEmision.


Estabilidad y versionado

Desde v1.0.0, este paquete sigue Semantic Versioning.

  • La API de la tabla de referencia es estable desde 1.0.0.
  • Cambios incompatibles en esa API dispararán una versión mayor (2.0.0+).
  • Todo lo no documentado como público (internos de firma, SOAP, c14n, validadores de coherencia, helpers de XML) es implementación interna y puede cambiar entre versiones menores sin previo aviso.

Licencia

MIT