@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/coreNode.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.
