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

consultame-sdk

v1.0.1

Published

SDK oficial de Consultame para integración con servicios de facturación electrónica

Readme

consultame-sdk

Librería en TypeScript para generar los mensajes XML (SOAP) e insumos criptográficos que se necesitan para interactuar con SIFEN (Sistema Integrado de Facturación Electrónica Nacional de Paraguay – SET).

La librería no hace las peticiones HTTP al web service de SIFEN: su responsabilidad es construir los sobres XML (consulta de RUC, consulta de lote, consulta de documento), extraer el certificado y la llave privada desde un archivo de firma digital .p12/.pfx y generar el código QR que se incrusta en el documento electrónico firmado. Con esa salida, la aplicación consumidora firma y envía los mensajes por su cuenta.


Tabla de contenido


Instalación

pnpm add consultame-sdk
# o
npm install consultame-sdk

Dependencias en tiempo de ejecución:

| Paquete | Para qué se usa | | -------------------------------------------------------- | ------------------------------------------------------------ | | node-forge | Leer los certificados .p12/.pfx y exportarlos a PEM. | | xml2js | Parsear y reconstruir el XML del documento al generar el QR. | | crypto-js | Calcular el hash SHA-256 (cHashQR) del código QR. |

La librería se publica en formato dual CommonJS + ESM con tipos incluidos:

// ESM
import { SifenService } from "consultame-sdk";

// CommonJS
const { SifenService } = require("consultame-sdk");

Uso rápido

Todo el punto de entrada público es la clase SifenService. Se instancia sin argumentos y ya arma internamente sus casos de uso y dependencias.

import { SifenService } from "consultame-skd";

const sifen = new SifenService();

// 1) Consultar un RUC → devuelve el XML SOAP listo, normalizado en una sola línea
const xmlRuc = sifen.consultRuc({
  id: 1,
  ruc: "80012345-6",
});

// 2) Consultar un documento electrónico por su CDC
const xmlDocumento = sifen.consultDocument({
  id: 2,
  cdc: "01800123456001001000000012023052212345678901",
});

// 3) Consultar el estado de un lote enviado
const xmlLote = sifen.consultBatch({
  id: 3,
  codigoLote: "1234567890",
});

// 4) Extraer certificado + llave privada desde la firma digital (.p12)
const firma = sifen.obtainDigitalSign({
  camino: "./certificados/firma.p12",
  contraseña: "mi-clave-secreta",
});

console.log(firma.certificado); // PEM del certificado
console.log(firma.llavePrivada); // PEM de la llave privada

// 5) Generar el QR e incrustarlo en un documento electrónico ya firmado (asíncrono)
const xmlConQr = await sifen.generateQr({
  xml: xmlFirmado, // XML del rDE ya firmado digitalmente
  csc: "ABCD0000000000000000000000000000",
  idCsc: "0001",
  ambiente: "test", // "test" | "produccion"
});

API pública

Todos los métodos viven en la clase SifenService. Son síncronos, salvo generateQr, que es asíncrono y devuelve una Promise.

consultRuc(consultRucDto)

Genera el sobre SOAP rEnviConsRUC para consultar los datos de un contribuyente por RUC.

| Parámetro | Tipo | Descripción | | --------- | -------- | ------------------------------------- | | id | number | Identificador de la petición (dId). | | ruc | string | RUC del contribuyente a consultar. |

Retorna: string — XML SOAP normalizado (sin saltos de línea ni tabulaciones).

const xml = sifen.consultRuc({ id: 1, ruc: "80012345-6" });
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
  <env:Header/>
  <env:Body>
    <rEnviConsRUC xmlns="http://ekuatia.set.gov.py/sifen/xsd">
      <dId>1</dId>
      <dRUCCons>80012345-6</dRUCCons>
    </rEnviConsRUC>
  </env:Body>
</env:Envelope>

consultDocument(consultDocumentDto)

Genera el sobre SOAP rEnviConsDeRequest para consultar un documento electrónico (factura, nota, etc.) mediante su CDC (Código de Control).

| Parámetro | Tipo | Descripción | | --------- | -------- | ------------------------------------- | | id | number | Identificador de la petición (dId). | | cdc | string | CDC de 44 dígitos del documento. |

Retorna: string — XML SOAP normalizado.

const xml = sifen.consultDocument({
  id: 2,
  cdc: "01800123456001001000000012023052212345678901",
});
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
  <env:Header/>
  <env:Body>
    <rEnviConsDeRequest xmlns="http://ekuatia.set.gov.py/sifen/xsd">
      <dId>2</dId>
      <dCDC>01800123456001001000000012023052212345678901</dCDC>
    </rEnviConsDeRequest>
  </env:Body>
</env:Envelope>

consultBatch(consultBatchDto)

Genera el sobre SOAP rEnviConsLoteDe para consultar el estado de un lote previamente enviado a SIFEN.

| Parámetro | Tipo | Descripción | | ------------ | -------- | ----------------------------------------------- | | id | number | Identificador de la petición (dId). | | codigoLote | string | Número de protocolo del lote (dProtConsLote). |

Retorna: string — XML SOAP normalizado.

const xml = sifen.consultBatch({ id: 3, codigoLote: "1234567890" });
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
  <env:Header/>
  <env:Body>
    <rEnviConsLoteDe xmlns="http://ekuatia.set.gov.py/sifen/xsd">
      <dId>3</dId>
      <dProtConsLote>1234567890</dProtConsLote>
    </rEnviConsLoteDe>
  </env:Body>
</env:Envelope>

obtainDigitalSign(digitalSignDto)

Lee un archivo de firma digital .p12/.pfx, valida que contenga llave privada y certificado, y los devuelve en formato PEM.

| Parámetro | Tipo | Descripción | | ------------ | -------- | ---------------------------------------- | | camino | string | Ruta al archivo .p12/.pfx. | | contraseña | string | Contraseña que protege la firma digital. |

Retorna: ResponseDigitalSign

interface ResponseDigitalSign {
  certificado: string; // certificado en formato PEM
  llavePrivada: string; // llave privada en formato PEM
}

Errores que puede lanzar:

  • "La firma digital no contiene una llave privada" — el .p12 no tiene llave.
  • "La firma digital no contiene un certificado" — el .p12 no tiene certificado.
  • Errores de node-forge si la contraseña es incorrecta o el archivo está corrupto.
try {
  const { certificado, llavePrivada } = sifen.obtainDigitalSign({
    camino: "./certificados/firma.p12",
    contraseña: "clave",
  });
} catch (error) {
  // contraseña incorrecta, archivo inválido o sin llave/certificado
}

generateQr(generationQrDto)

Toma un documento electrónico (rDE) ya firmado digitalmente, calcula el enlace del código QR exigido por SIFEN y devuelve el mismo XML con el nodo <gCamFuFD><dCarQR> incrustado.

⚠️ Este es el único método asíncrono de la librería: devuelve Promise<string>.

| Parámetro | Tipo | Descripción | | ---------- | -------- | ---------------------------------------------------------------------- | | xml | string | XML del rDE firmado (debe contener el nodo Signature). | | csc | string | Código de Seguridad del Contribuyente (secreto, entregado por la SET). | | idCsc | string | Identificador del CSC (ej. "0001"). | | ambiente | string | "test" o "produccion". Define el dominio base del enlace del QR. |

Retorna: Promise<string> — el XML reconstruido con el nodo dCarQR.

const xmlConQr = await sifen.generateQr({
  xml: xmlFirmado,
  csc: "ABCD0000000000000000000000000000",
  idCsc: "0001",
  ambiente: "test",
});

Cómo se construye el QR

  1. Se parsea el XML y se valida que exista el nodo Signature; si no está, lanza error.

  2. Se elige el dominio base según el ambiente:

    • testhttps://ekuatia.set.gov.py/consultas-test
    • produccionhttps://ekuatia.set.gov.py/consultas
  3. Se le concatena /qr? y los parámetros extraídos del documento, en este orden:

    | Parámetro | Origen en el XML | Notas | | ------------------------- | -------------------------------------------------------- | ----------------------------------------------- | | nVersion | rDE > dVerFor | Versión del formato. | | Id | rDE > DE[@Id] | El CDC del documento. | | dFeEmiDE | rDE > DE > gDatGralOpe > dFeEmiDE | Convertido a hexadecimal. | | dRucRec o dNumIDRec | ... > gDatRec | dRucRec si iNatRec == 1, si no dNumIDRec. | | dTotGralOpe | rDE > DE > gTotSub > dTotGralOpe | 0 si no existe. | | dTotIVA | rDE > DE > gTotSub > dTotIVA | 0 si no existe. | | cItems | Cantidad de gDtipDE > gCamItem | 0 si no hay ítems. | | DigestValue | rDE > Signature > SignedInfo > Reference > DigestValue | Convertido a hexadecimal. | | IdCSC | Parámetro idCsc | Último antes del hash. | | cHashQR | SHA256(cadena_de_parámetros + csc) | Se agrega al final. |

  4. El enlace resultante se escribe en rDE > gCamFuFD > dCarQR y se reconstruye el XML.

https://ekuatia.set.gov.py/consultas-test/qr?nVersion=150&Id=01800123456001001000000012023052212345678901&dFeEmiDE=323032332d30352d32325431303a30303a3030&dRucRec=80012345&dTotGralOpe=550000&dTotIVA=50000&cItems=3&DigestValue=6a4b2c...&IdCSC=0001&cHashQR=9f2a7c1e4b8d...

Y así queda incrustado en el XML de salida:

<rDE>
  <dVerFor>150</dVerFor>
  <DE Id="01800123456001001000000012023052212345678901">
    ...
  </DE>
  <Signature>...</Signature>
  <gCamFuFD>
    <dCarQR>https://ekuatia.set.gov.py/consultas-test/qr?nVersion=150&amp;Id=...&amp;cHashQR=...</dCarQR>
  </gCamFuFD>
</rDE>

Errores que puede lanzar:

  • "XML debe estar firmado digitalmente para generar el QR" — el XML no tiene nodo Signature.
  • Errores de parseo de xml2js si el XML está malformado.
  • TypeError si el documento no respeta la estructura esperada del rDE (por ejemplo, falta gDatRec o gDtipDE).
try {
  const xmlConQr = await sifen.generateQr({
    xml: xmlFirmado,
    csc: process.env.SIFEN_CSC!,
    idCsc: "0001",
    ambiente: "produccion",
  });
} catch (error) {
  // XML sin firmar, malformado o con estructura inesperada
}

Nota sobre el ambiente: internamente se compara contra el enum SIFEN_ENVIRONMENT ("test" / "produccion"). Cualquier valor distinto de "test" cae en el enlace de producción, así que conviene pasar el valor exacto.


DTOs (contratos de entrada)

Todos los DTOs de consulta extienden GenerateIdDto, que aporta el campo id.

interface GenerateIdDto {
  id: number;
}

interface ConsultRucDto extends GenerateIdDto {
  ruc: string;
}

interface ConsultDocumentDto extends GenerateIdDto {
  cdc: string;
}

interface ConsultBatchDto extends GenerateIdDto {
  codigoLote: string;
}

interface DigitalSignDto {
  camino: string; // ruta al .p12
  contraseña: string; // clave del .p12
}

interface GenerationQrDto {
  xml: string; // XML del rDE ya firmado
  csc: string; // Código de Seguridad del Contribuyente
  idCsc: string; // Identificador del CSC
  ambiente: string; // "test" | "produccion"
}

Enums disponibles en src/shared/utils/dictionary.utils.ts:

enum SIFEN_ENVIRONMENT {
  TEST = "test",
  PRODUCCION = "produccion",
}

enum LINK_SIFEN {
  TEST = "https://ekuatia.set.gov.py/consultas-test",
  PRODUCCION = "https://ekuatia.set.gov.py/consultas",
}

DTOs ya definidos pero aún sin implementación en la persistencia (ver roadmap):

interface CancelInvoiceDto extends GenerateIdDto {
  cdc: string;
  motivo: string;
}

interface InvalidationInvoiceDto extends GenerateIdDto {
  timbrado: string;
  establecimiento: number;
  puntoExpedicion: number;
  desde: number;
  hasta: number;
  tipoDocumento: number;
  motivo: string;
}

Funciones utilitarias internas

Viven en src/shared/functions/functions.utils.ts. No forman parte de la API pública, pero es útil conocerlas:

| Función | Qué hace | | ---------------------------- | --------------------------------------------------------------------------------------------- | | generateConsultRucXml | Arma el string XML de consulta de RUC. | | generateConsultDocumentXml | Arma el string XML de consulta de documento por CDC. | | generateConsultBatchXml | Arma el string XML de consulta de lote. | | normalizeXML | Colapsa el XML a una sola línea: elimina saltos, tabulaciones y espacios entre etiquetas. | | readDigitalSign | Lee el .p12 con node-forge y extrae certificado + llave privada en PEM. | | generateQr | Arma el enlace del QR (params + hash SHA-256) y lo incrusta en gCamFuFD > dCarQR. (async) | | calculateDigitVerification | Calcula el dígito verificador (DV) de una cédula/RUC según el algoritmo módulo 11. |


Arquitectura del proyecto

El proyecto sigue una arquitectura hexagonal (puertos y adaptadores) combinada con principios de Clean Architecture, organizada por el módulo de negocio sifen.

             ┌──────────────────────────────────────────────┐
             │                 applications                 │
             │   SifenService  (punto de entrada / fachada)  │
             │   DTOs (contratos de entrada)                 │
             └───────────────────────┬──────────────────────┘
                                     │ orquesta
                                     ▼
             ┌──────────────────────────────────────────────┐
             │                    domain                     │
             │   useCases  (ConsultRuc, ConsultBatch, ...)   │
             │   repository (SifenRepository = PUERTO)        │
             └───────────────────────┬──────────────────────┘
                                     │ implementado por
                                     ▼
             ┌──────────────────────────────────────────────┐
             │                infrastructure                 │
             │   SifenPersistence (ADAPTADOR del puerto)      │
             │   → usa funciones utilitarias (XML, forge)     │
             └──────────────────────────────────────────────┘

Flujo de una llamada (ej. consultRuc):

  1. La app llama a SifenService.consultRuc(dto) — capa applications.
  2. El servicio delega en ConsultRucUseCase.execute(dto) — capa domain.
  3. El caso de uso invoca la interfaz SifenRepository (puerto), sin conocer la implementación.
  4. En runtime el puerto lo resuelve SifenPersistence (adaptador) — capa infrastructure.
  5. La persistencia usa las funciones utilitarias para generar y normalizar el XML.

Por qué esta separación:

  • domain no depende de nada externo: define qué se hace (casos de uso) y el contrato SifenRepository. Es el núcleo estable.
  • infrastructure contiene los detalles concretos (armado de XML, node-forge, fs). Se puede reemplazar sin tocar el dominio.
  • applications expone la fachada y los DTOs que consume el mundo exterior.
  • shared agrupa utilidades y DTOs de respuesta transversales a los módulos.

La inversión de dependencias es clave: SifenPersistence implements SifenRepository, de modo que el dominio depende de la abstracción, no de la implementación.


Estructura de carpetas

consultame-sdk/
├── src/
│   ├── index.ts                          # Punto de entrada público → exporta SifenService
│   │
│   ├── shared/                           # Código transversal
│   │   ├── dto/
│   │   │   └── responseDigitalSign.dto.ts # ResponseDigitalSign { certificado, llavePrivada }
│   │   ├── functions/
│   │   │   └── functions.utils.ts        # XML, normalizeXML, readDigitalSign, generateQr, DV
│   │   └── utils/
│   │       └── dictionary.utils.ts       # Enums SIFEN_ENVIRONMENT y LINK_SIFEN
│   │
│   └── sifen/                            # Módulo de negocio SIFEN
│       ├── applications/                 # Capa de aplicación (fachada + contratos)
│       │   ├── dtos/
│       │   │   ├── index.ts              # Barrel de DTOs
│       │   │   ├── generateId.dto.ts     # GenerateIdDto (base)
│       │   │   ├── consultRuc.dto.ts
│       │   │   ├── consultDocument.dto.ts
│       │   │   ├── consultBatch.dto.ts
│       │   │   ├── digitalSign.dto.ts
│       │   │   ├── generationQr.dto.ts
│       │   │   ├── cancelDocument.dto.ts
│       │   │   └── invalidationInvoice.dto.ts
│       │   └── services/
│       │       └── sifen.service.ts      # SifenService (fachada pública)
│       │
│       ├── domain/                       # Núcleo de negocio
│       │   ├── repository/
│       │   │   └── sifen.repository.ts   # SifenRepository (PUERTO / interfaz)
│       │   └── useCases/
│       │       ├── index.ts              # Barrel de casos de uso
│       │       ├── consultRuc.useCase.ts
│       │       ├── consultDocument.useCase.ts
│       │       ├── consultBatch.useCase.ts
│       │       ├── obtainDigitalSign.useCase.ts
│       │       └── generateQr.useCase.ts
│       │
│       └── infrastructure/               # Detalles concretos (ADAPTADORES)
│           └── persistence/
│               └── sifen.persistence.ts  # SifenPersistence implements SifenRepository
│
├── package.json
├── tsconfig.json
├── tsup.config.ts                        # Build dual CJS + ESM (target node22)
├── pnpm-workspace.yaml
└── README.md

Scripts de desarrollo

| Script | Comando | Descripción | | ------------------ | --------------------------- | --------------------------------------------------- | | pnpm build | tsup && build:types | Compila a dist/ (CJS + ESM) y genera los .d.ts. | | pnpm build:types | tsc --emitDeclarationOnly | Solo emite las declaraciones de tipos. | | pnpm dev | tsup --watch | Build en modo watch. | | pnpm typecheck | tsc --noEmit | Verifica tipos sin emitir archivos. | | pnpm play | tsx src/index.ts | Ejecuta src/index.ts para pruebas manuales. | | pnpm play:watch | tsx watch src/index.ts | Igual que play pero en modo watch. |


Estado / roadmap

Funcionalidades implementadas:

  • ✅ Consulta de RUC (consultRuc)
  • ✅ Consulta de documento por CDC (consultDocument)
  • ✅ Consulta de lote (consultBatch)
  • ✅ Lectura de firma digital .p12 → PEM (obtainDigitalSign)
  • ✅ Generación e incrustación del código QR (generateQr)

Definidas pero pendientes de implementación (hoy lanzan Method not implemented o aún no están expuestas en SifenService):

  • ⏳ Cancelación de documento (cancelationInvoice / CancelInvoiceDto)
  • ⏳ Inutilización de documentos (invalidationInvoice / InvalidationInvoiceDto)

Nota: esta librería genera insumos (XML, credenciales y el QR) para SIFEN, pero no realiza el envío HTTP ni la firma XMLDSig de los documentos. Esa parte queda a cargo de la aplicación consumidora — de hecho, generateQr requiere que el XML ya venga firmado, porque toma el DigestValue de la firma para calcular el hash del QR.