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

@fe-peru/sdk

v0.2.1

Published

See and Api facades for SUNAT electronic invoicing

Readme

@fe-peru/sdk

Cliente TypeScript unificado para SUNAT. SunatClient es la API recomendada; See (SOAP CPE) y Api (GRE REST) se mantienen por compatibilidad.

pnpm add @fe-peru/sdk @fe-peru/core

Node.js 22, ESM. Documentación completa: repositorio.

SunatClient (recomendado)

import { SunatClient } from "@fe-peru/sdk";

const sunat = new SunatClient({
  environment: "beta",
  signing: { certificate: { kind: "pem", certificate: certPem, privateKey: keyPem } },
  soap: { username: solUser, password: solPassword },
  gre: { clientId, clientSecret, username: solUser, password: solPassword },
});

const prepared = sunat.prepare(invoice);
console.log(prepared.route, prepared.stem, prepared.signatureInfo.digestValue);

const submission = await sunat.submit(prepared);
// submission.artifact es exactamente el artefacto transmitido.
// En `passthrough` el envio solo conserva las comprobaciones tecnicas
// indispensables; usa validateForSubmission() para diagnostico completo.
if (submission.receipt.state === "queued") {
  await sunat.submissions.waitFor(submission.receipt, {
    intervalMs: 2_000,
    maxAttempts: 10,
  });
}

sunat.dispose();

SunatClient firma con SHA-256 de forma predeterminada, que es el perfil usado por la matriz beta aceptada del 28/08/2026. SHA-1 permanece disponible únicamente mediante signing.algorithm: "sha1" para compatibilidad heredada explícita.

inspectSignedXml(xml) permite importar y reenviar XML firmado sin volver a construirlo ni firmarlo. Las consultas bajo submissions y cpe no requieren configurar signing.

SunatClient usa validation.mode: "passthrough" de forma predeterminada: construye, firma y transporta el XML, conservando los valores que el renderer sabe representar. Antes de transmitir solo reconstruye el descriptor, comprueba tipo/stem/ruta y verifica criptográficamente XMLDSig. validation.mode: "strict" conserva las validaciones locales completas antes del envío, incluido el XSD y las invariantes deterministas. Este comportamiento reproduce el flujo predeterminado de Greenter: construir, firmar y enviar sin ejecutar automáticamente su validador opcional.

Todos los builders XML, prepareDocument, DocumentPreparer, See y Api también usan passthrough por defecto. Para validar al construir, solicita { mode: "strict" } al builder, new DocumentPreparer({ ...options, mode: "strict" }), new See({ ...options, mode: "strict" }) o new Api({ ...options, mode: "strict" }). En la función de bajo nivel se selecciona mediante prepareDocument(document, identity, undefined, "strict"). El modo explícito se conserva en el artefacto preparado; las comprobaciones técnicas de firma y transporte siguen vigentes.

En percepción y retención, passthrough representa escalares ausentes mediante elementos vacíos y omite objetos o colecciones ausentes. No calcula ni completa importes fiscales.

Pagos parciales en retención y percepción

Payment admite numero?: string | null, con getNumero() y setNumero(). El número se conserva en from(), toObject(), toJSON() y al leer XML. Los builders lo escriben en cac:Payment/cbc:ID; si se omite o es null, mantienen el índice de la lista más uno como fallback.

Representa cada pago mediante un detalle separado, incluso si corresponde a la misma factura: un único elemento en pagos (retención) o cobros (percepción), con numero: "1", numero: "2", etc. Cada detalle contiene sus propios importes, fecha de retención/percepción y tipoCambio; la fecha de conversión corresponde a la fecha del pago. Se repiten los datos y el importe original de la factura. El SDK no divide listas automáticamente ni calcula importes o números correlativos.

Los casos retencion-pagos-parciales y percepcion-pagos-parciales en examples/src/cases muestran dos pagos de una factura en dólares, con fechas y factores de cambio ilustrativos distintos. No consultan proveedores de tasas.

Fecha y hora de emisión

Facturas, boletas, notas, guías, percepción y retención mantienen fechaEmision y horaEmision. Si fechaEmision recibe un Date, se extraen su fecha y hora en America/Lima, independientemente de la zona horaria del proceso. La precedencia es: hora explícita no nula, hora inferida del Date, y 00:00:00 al generar XML si no hay hora. La hora explícita prevalece en cualquier orden de setters; setHoraEmision(null) la elimina. Sustituir fechaEmision por YYYY-MM-DD o null elimina la hora inferida anterior. toObject() y toJSON() conservan la hora efectiva en el campo público horaEmision. Las demás fechas mantienen su comportamiento y nunca se usa la hora actual automáticamente.

invoice.setFechaEmision(new Date("2026-10-04T04:15:30Z"));
// IssueDate: 2026-10-03; IssueTime: 23:15:30 (America/Lima).
invoice.setHoraEmision("08:00:00"); // Hora explícita con prioridad.

Percepción admite indExcepcional opcional, equivalente al ExceptionalIndicator de Greenter. Percepción y retención mantienen #ISSUER-SIGNATURE como referencia de firma.

const report = await sunat.validateForSubmission(prepared);

console.log(report.ready, report.structural, report.signature, report.xsd);
// ready cubre descriptor/ruta, invariantes deterministas, firma y XSD.
// No sustituye la respuesta autoritativa de SUNAT.
const strictSunat = new SunatClient({
  environment: "beta",
  validation: { mode: "strict" },
  // signing, soap y/o gre...
});

isSignedDocumentArtifact() es únicamente un type guard estructural, no una frontera de confianza. sunat.soap.submit(...), sunat.gre.submit(...), See y Api se conservan como primitivas avanzadas/de compatibilidad y no ejecutan necesariamente todas las garantías del pipeline canónico.

See (compatibilidad: factura / boleta / nota / resumen)

import { readFileSync } from "node:fs";
import { Invoice } from "@fe-peru/core";
import { FE_BETA, See, XmlErrorCodeProvider } from "@fe-peru/sdk";

const see = new See({
  endpointUrl: FE_BETA,
  username: process.env.FE_PERU_SOL_USER!,
  password: process.env.FE_PERU_SOL_PASSWORD!,
  certificate: {
    kind: "pem",
    certificate: readFileSync("cert.pem"),
    privateKey: readFileSync("key.pem"),
  },
  codeProvider: new XmlErrorCodeProvider(),
});

const xml = see.getXmlSigned(invoice);
const cdr = await see.send(invoice);
await see.sendXmlFile(xml);
await see.getStatus(ticket);

See.send no acepta Despatch (SdkError WRONG_PATH).

Api (compatibilidad: guía de remisión)

import { Api } from "@fe-peru/sdk";

const api = new Api({
  clientId: process.env.FE_PERU_GRE_CLIENT_ID!,
  clientSecret: process.env.FE_PERU_GRE_CLIENT_SECRET!,
  username: process.env.FE_PERU_SOL_USER!,
  password: process.env.FE_PERU_SOL_PASSWORD!,
  certificate: {
    kind: "pem",
    certificate: readFileSync("cert.pem"),
    privateKey: readFileSync("key.pem"),
  },
});

await api.send(despatch);

Api.send solo acepta Despatch.