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

sunat-engine-nest

v2.0.0

Published

Motor de Facturación Electrónica SUNAT UBL 2.1 para NestJS

Readme

sunat-engine-nest

npm license

Motor de Facturación Electrónica SUNAT UBL 2.1 para NestJS.

Soporta Facturas, Boletas, Notas de Crédito/Débito, Guías de Remisión (GRE), Resúmenes Diarios y Comunicaciones de Baja — firma XML, envío a SUNAT/OSE, parseo de CDR y generación de PDF.

Índice

Instalación

npm install sunat-engine-nest
# o
pnpm add sunat-engine-nest

Configuración

Registra el módulo en tu AppModule. Puedes definir las credenciales de tu empresa una sola vez en el módulo usando el campo sunat, evitando repetirlas en cada llamada.

Registro síncrono

import { SunatEngineModule } from 'sunat-engine-nest';

@Module({
  imports: [
    SunatEngineModule.forRoot({
      sunat: {
        ruc:          '20123456789',
        solUser:      'MODDATOS',
        solPass:      'moddatos',
        certPem:      'BASE64_DEL_P12_O_PEM',
        endpointMode: 'beta', // 'beta' | 'produccion'
      },
      gre: {
        authUrl:      'https://gre-test.nubefact.com/v1',
        apiUrl:       'https://gre-test.nubefact.com/v1',
        clientId:     'TU_CLIENT_ID',
        clientSecret: 'TU_CLIENT_SECRET',
      },
    }),
  ],
})
export class AppModule {}

Registro asíncrono

Recomendado cuando las credenciales dependen de variables de entorno.

import { SunatEngineModule } from 'sunat-engine-nest';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    SunatEngineModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        provider: config.get('PROVIDER'), // 'sunat' | 'ose'
        sunat: {
          ruc:          config.get('SUNAT_RUC'),
          solUser:      config.get('SUNAT_SOL_USER'),
          solPass:      config.get('SUNAT_SOL_PASS'),
          certPem:      config.get('SUNAT_CERT_PEM'),
          endpointMode: config.get('SUNAT_MODE') ?? 'beta',
        },
        gre: {
          authUrl:      config.get('GRE_AUTH_URL'),
          apiUrl:       config.get('GRE_API_URL'),
          clientId:     config.get('GRE_CLIENT_ID'),
          clientSecret: config.get('GRE_CLIENT_SECRET'),
        },
        ose: {
          url:   config.get('OSE_URL'),
          token: config.get('OSE_TOKEN'),
        },
      }),
    }),
  ],
})
export class AppModule {}

Proveedor: SUNAT directo vs OSE

Puedes elegir enviar tus comprobantes directamente a SUNAT o a través de un OSE (Operador de Servicios Electrónicos) autorizado como Nubefact, EFACT, DigiFlow, Bizlinks, entre otros.

// Envío directo a SUNAT (por defecto)
SunatEngineModule.forRoot({
  provider: 'sunat',
  sunat: { ruc: '...', solUser: '...', solPass: '...', certPem: '...' },
})

// Envío a través de un OSE
SunatEngineModule.forRoot({
  provider: 'ose',
  sunat: { ruc: '...', certPem: '...' }, // solo se usa para firmar el XML
  ose: {
    url:   'https://api.tuose.com/v1/documents',
    token: 'TU_TOKEN_OSE',               // Bearer token
    // o en lugar de token:
    // username: 'usuario',
    // password: 'clave',
  },
})

El OseClient es genérico y compatible con cualquier OSE que acepte { fileName, contentFile } (ZIP en base64) vía POST con autenticación Bearer o Basic.

Certificado digital

El motor acepta el certificado en tres formatos:

// Formato 1: ruta al archivo .p12 o .pem en disco
const credentials: CompanyCredentials = {
  certPem: '/ruta/al/certificado.p12',
};

// Formato 2: ruta a cert PEM + ruta a clave privada PEM por separado
const credentials: CompanyCredentials = {
  certPem: '/ruta/al/cert.pem',
  certKey: '/ruta/al/key.pem',
};

// Formato 3: contenido directo en base64 (.p12) o texto PEM
const credentials: CompanyCredentials = {
  certPem: 'MIIKJAIBAzCCCd4GCSqGSI...', // .p12 en base64
};

Variables de entorno

# Credenciales SUNAT
SUNAT_RUC=20123456789
SUNAT_SOL_USER=MODDATOS
SUNAT_SOL_PASS=moddatos
SUNAT_CERT_PEM=BASE64_DEL_P12
SUNAT_MODE=beta

# GRE (Guía de Remisión Electrónica)
GRE_AUTH_URL=https://gre-test.nubefact.com/v1
GRE_API_URL=https://gre-test.nubefact.com/v1
GRE_CLIENT_ID=tu_client_id
GRE_CLIENT_SECRET=tu_client_secret

# OSE (solo si provider=ose)
PROVIDER=ose
OSE_URL=https://api.tuose.com/v1/documents
OSE_TOKEN=tu_token_aqui

Uso

Enviar un comprobante

Inyecta SunatEngineService en cualquier servicio o controlador. Si configuraste sunat:{} en el módulo, no necesitas pasar credenciales en cada llamada:

import { Injectable } from '@nestjs/common';
import { SunatEngineService, InvoicePayload } from 'sunat-engine-nest';

@Injectable()
export class InvoicesService {
  constructor(private readonly engine: SunatEngineService) {}

  async sendInvoice() {
    // Sin credenciales — usa las del módulo automáticamente
    const payload: InvoicePayload = {
      tipoOperacion: '0101',
      tipoDoc:       '01', // 01=Factura, 03=Boleta
      serie:         'F001',
      correlativo:   '00000001',
      fechaEmision:  '2026-07-20T00:00:00-05:00',
      tipoMoneda:    'PEN',
      company: {
        ruc:         '20123456789',
        razonSocial: 'MI EMPRESA S.A.C.',
        address: { direccion: 'Av. Principal 123, Lima' },
      },
      client: {
        tipoDoc:   '6',
        numDoc:    '20987654321',
        rznSocial: 'CLIENTE S.A.C.',
      },
      details: [
        {
          unidad:            'NIU',
          cantidad:          2,
          descripcion:       'Producto de prueba',
          mtoValorUnitario:  100,
          mtoValorVenta:     200,
          mtoBaseIgv:        200,
          porcentajeIgv:     18,
          igv:               36,
          tipAfeIgv:         '10',
          totalImpuestos:    36,
          mtoPrecioUnitario: 118,
        },
      ],
      legends: [{ code: '1000', value: 'DOSCIENTOS TREINTA Y SEIS Y 00/100 SOLES' }],
      mtoOperGravadas: 200,
      mtoIGV:          36,
      totalImpuestos:  36,
      valorVenta:      200,
      subTotal:        236,
      mtoImpVenta:     236,
    };

    return this.engine.sendInvoice(payload); // credenciales del módulo
  }
}

También puedes sobreescribir las credenciales por llamada cuando manejas múltiples empresas:

// Credenciales específicas para esta llamada (tienen prioridad sobre el módulo)
return this.engine.sendInvoice(payload, {
  ruc:     '20987654321',
  solUser: 'OTRO_USUARIO',
  solPass: 'otra_clave',
  certPem: 'OTRO_CERT_BASE64',
});

Documentos soportados

| Tipo | Método de envío | Descripción | PDF | |------|------------------|-------------|-----| | Factura (01) | sendInvoice() | Factura electrónica — SOAP síncrono | ✅ generateInvoice() | | Boleta (03) | sendInvoice() | Boleta de venta electrónica — SOAP síncrono | ✅ generateInvoice() | | Nota de Crédito (07) | sendNote() | NC electrónica — SOAP síncrono | ✅ generateNote() | | Nota de Débito (08) | sendNote() | ND electrónica — SOAP síncrono | ✅ generateNote() | | Guía de Remisión (09) | sendDespatch() | GRE 2022 — REST/OAuth asíncrono | ✅ generateDespatch() | | Resumen Diario (RC) | sendSummary() | Resumen de boletas — SOAP asíncrono | — (comunicación interna a SUNAT) | | Comunicación de Baja (RA) | sendVoided() | Baja de comprobantes — SOAP asíncrono | — (comunicación interna a SUNAT) | | Consulta de ticket | getTicketStatus() | Estado de procesos asíncronos | — |

Generación de PDF (función paga)

El envío a SUNAT (XML, firma, CDR, GRE) sigue siendo MIT / gratuito. La generación de PDF es una función paga: necesitas una license key para usarla — escríbeme por WhatsApp para adquirir una.

La licencia se valida localmente (firma Ed25519), sin llamadas a ningún servidor externo — tus datos de facturación nunca salen de tu infraestructura.

SunatEngineModule.forRoot({
  sunat: { ruc: '...', solUser: '...', solPass: '...', certPem: '...' },
  pdf: {
    licenseKey: 'LA_LICENSE_KEY_QUE_TE_ENTREGO', // o desde tu ConfigService/env
  },
})
import { DocumentPdfService } from 'sunat-engine-nest';

@Injectable()
export class InvoicesService {
  constructor(
    private readonly engine: SunatEngineService,
    private readonly pdf: DocumentPdfService,
  ) {}

  async getPdf(payload: InvoicePayload): Promise<Buffer> {
    return this.pdf.generateInvoice(payload, 'A4'); // 'A4' | 'TICKET_80MM' | 'STICKER_A6'
  }
}

Sin pdf.licenseKey configurada (o con una key inválida/expirada), los métodos de PDF lanzan un PdfLicenseError.

Personalizar diseño (logo y color de marca)

El formato A4 acepta branding opcional. Si no se especifica, usa un azul corporativo por defecto.

return this.pdf.generateInvoice(payload, 'A4', {
  licenseKey: 'OTRA_LICENSE_KEY', // opcional — tiene prioridad sobre la del módulo
  branding: {
    color: '#0f766e',                        // hex, opcional
    logoBase64: 'data:image/png;base64,...',  // opcional
  },
});

Nota de Crédito / Débito

Mismo diseño y misma licencia que las facturas.

await this.pdf.generateNote(notePayload, 'A4'); // 'A4' | 'TICKET_80MM' | 'STICKER_A6'

Guía de Remisión (GRE)

Solo formato A4 — incluye destinatario, datos de traslado, punto de partida/llegada, transportista/vehículo/conductor.

await this.pdf.generateDespatch(despatchPayload, {
  branding: { color: '#0f766e' }, // opcional
});

Resumen Diario (RC) y Comunicación de Baja (RA) no tienen representación en PDF — son comunicaciones internas a SUNAT, no documentos que se entreguen a un cliente ni que viajen con la mercadería.

Testing / Modo simulado

SUNAT beta rechaza sendSummary (RC/RA) con el RUC genérico de prueba 20000000001 — ese RUC solo funciona de forma confiable para sendBill (facturas y boletas). Para probar el flujo completo sin depender del entorno beta, el módulo incluye un cliente SOAP simulado que devuelve respuestas válidas sin realizar ninguna llamada HTTP.

Opción 1 — useFake: true en el módulo (recomendado)

// app.module.ts (para tests)
SunatEngineModule.forRoot({
  useFake: true, // ← activa FakeSunatSoapClient automáticamente
  sunat: {
    ruc:     '20000000001',
    solUser: 'MODDATOS',
    solPass: 'moddatos',
    certPem: 'BASE64_DEL_P12',
  },
})

// También con forRootAsync:
SunatEngineModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    useFake: config.get('NODE_ENV') === 'test',
    sunat: { ruc: config.get('SUNAT_RUC'), ... },
  }),
})

Opción 2 — overrideProvider en NestJS Testing

import { Test } from '@nestjs/testing';
import { SunatSoapClient, FakeSunatSoapClient } from 'sunat-engine-nest';

const moduleRef = await Test.createTestingModule({
  imports: [SunatEngineModule.forRoot({ sunat: { ... } })],
})
  .overrideProvider(SunatSoapClient)
  .useClass(FakeSunatSoapClient)
  .compile();

Comportamiento del cliente simulado

| Método | Respuesta simulada | |--------|-------------------| | sendBill | CDR ZIP con ResponseCode=0 (ACEPTADA) | | sendSummary | Ticket numérico incremental (1000000001, 1000000002, ...) | | getStatus | CDR ZIP con ResponseCode=0 (ACEPTADA) |

No se realiza ninguna llamada HTTP — el XML se genera y firma normalmente, solo el envío a SUNAT es simulado.

Respuesta del motor

interface EngineResponse {
  xml?:           string;       // XML firmado en base64
  hash?:          string;       // SHA256 del XML
  sunatResponse?: {
    success?:     boolean;
    ticket?:      string;       // procesos asíncronos (RC/RA/GRE)
    cdrZip?:      string;       // ZIP CDR en base64 (procesos síncronos)
    cdrResponse?: {
      code?:        string;     // 0=aceptado, 2xxx=rechazado, 4xxx=obs
      description?: string;
      notes?:       string[];
    };
    error?: { code?: string | number; message?: string };
  };
}

Endpoints SUNAT

| Modo | Factura/Boleta/NC/ND | GRE | |------|----------------------|-----| | beta | e-beta.sunat.gob.pe | gre-test.nubefact.com | | produccion | e-factura.sunat.gob.pe | api-cpe.sunat.gob.pe |

Soporte y actualizaciones

SUNAT actualiza continuamente sus esquemas, validaciones y normativas de facturación electrónica. Este paquete recibe mantenimiento activo para mantenerse alineado con cada cambio oficial, garantizando que tu integración siga funcionando sin interrupciones.

Si este proyecto te ha sido útil y quieres apoyar su desarrollo continuo, puedes hacerlo de las siguientes formas:

Yape / WhatsApp

Número: +51 907 596 305

Cualquier aporte, por pequeño que sea, ayuda a mantener la librería actualizada y en funcionamiento. ¡Gracias por tu apoyo!

Licencia

MIT — excepto la generación de PDF (DocumentPdfService), que requiere una license key comercial. Ver Generación de PDF.