kembec / facturacion-electronica-sunat-sdk
PHP client for the sunat-sdk binary: build, sign, zip, send and query SUNAT (Peru) electronic vouchers.
Package info
github.com/Kembec/facturacion-electronica-sunat-sdk
Language:Rust
pkg:composer/kembec/facturacion-electronica-sunat-sdk
Requires
- php: >=7.4
- ext-json: *
Requires (Dev)
- ext-dom: *
- phpunit/phpunit: ^9.6 || ^10.5 || ^11.5 || ^12.0
Suggests
- ext-openssl: Needed by vendor/bin/sunat-sdk-install to download the prebuilt binary over HTTPS
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.0.0-rc.9
- v1.0.0-rc.8
- v1.0.0-rc.7
- v1.0.0-rc.6
- v1.0.0-rc.5
- v1.0.0-rc.4
- v1.0.0-rc.3
- v1.0.0-rc.2
- v1.0.0-rc.1
- v0.1.0-beta.8
- v0.1.0-beta.7
- v0.1.0-beta.6
- v0.1.0-beta.5
- v0.1.0-beta.4
- v0.1.0-beta.3
- v0.1.0-beta.2
- v0.1.0-beta.1
- dev-release/v1.0.0-rc.9
- dev-ci/crates-publish-one-by-one
- dev-release/1.0.0-rc.8
- dev-feat/detraction-1002-1004
- dev-feat/summary-ivap-isc-perception
- dev-ci/npm-latest-prerelease
- dev-ci/desktop-runners-on-release-only
- dev-release/1.0.0-rc.7
- dev-release/1.0.0-rc.6
- dev-fix/line-rules-receipts
- dev-release/1.0.0-rc.5
- dev-fix/despatch-text-format
- dev-feat/cli-version
- dev-fix/total-taxes-notes
- dev-release/1.0.0-rc.4
- dev-fix/text-format-observations
- dev-fix/summary-free-igv-rate
- dev-docs/objc-by-url
- dev-docs/swiftpm-by-url
- dev-release/1.0.0-rc.3
- dev-ci/swiftpm-release
- dev-fix/total-taxes-3294
- dev-feat/export-close
- dev-feat/self-billed-reversal
- dev-feat/self-billed-invoice
- dev-release/1.0.0-rc.2
- dev-feat/export-services
- dev-ci/nuget-listing-wait
- dev-feat/ose-cdr
- dev-release/1.0.0-rc.1
- dev-release/0.1.0-beta.8
- dev-feat/advances
- dev-fix/rc-status-line-2992
- dev-feat/icbper-beta
- dev-ci/nuget-release
- dev-release/0.1.0-beta.7
- dev-feat/diagnosis-bindings-b
- dev-feat/diagnosis-bindings-c
- dev-feat/diagnosis-bindings-a
- dev-feat/gre-loading-event
- dev-feat/diagnosis-python-ffi
- dev-release/0.1.0-beta.6
- dev-feat/r0-dated-rules
- dev-feat/icbper
- dev-feat/global-allowances
- dev-feat/retention-beta
- dev-feat/diagnosis-bindings
- dev-feat/retention-20
- dev-feat/diagnosis
- dev-release/0.1.0-beta.5
- dev-ci/publish-crates
- dev-docs/changelog-semver
- dev-feat/php-summary-ticket
- dev-fix/cdr-observation-below-4000
- dev-feat/crates-io
- dev-feat/swift
- dev-fix/ruby-check-gems
- dev-feat/legacy-windows
- dev-feat/perl
- dev-feat/ruby
- dev-feat/java
- dev-feat/dotnet
- dev-feat/c-abi
This package is auto-updated.
Last update: 2026-11-11 09:34:37 UTC
README
Un SDK para emitir comprobantes electrónicos ante la SUNAT desde Rust, JavaScript y TypeScript, Python, PHP y Swift, o desde la línea de comandos. Tu sistema decide qué emitir y calcula los importes; el SDK convierte esos datos en el archivo XML que pide la SUNAT, lo firma con tu certificado digital, lo envía y te dice si la SUNAT lo aceptó. Sirve para facturas, boletas, notas de crédito y de débito, liquidaciones de compra, resúmenes diarios de boletas y comunicaciones de baja para anular comprobantes ya enviados. Es para quien programa el sistema de facturación de una empresa.
SUNAT (Peru) electronic invoicing SDK for Rust, JavaScript/TypeScript, Python, PHP and Swift.
Todos los lenguajes usan el mismo motor por dentro, así que los mismos datos dan el mismo comprobante firmado en cualquiera de ellos. Guías y ejemplos: https://fe.kembec.com
Elige tu lenguaje
| Lenguaje | Paquete | Instalación | Cómo corre |
|---|---|---|---|
| JavaScript y TypeScript (Node 18 o posterior, navegador) | @kembec/facturacion-electronica-sunat-sdk |
npm install @kembec/facturacion-electronica-sunat-sdk@beta |
WebAssembly, sin binarios ni procesos aparte |
| PHP 7.4 o posterior | kembec/facturacion-electronica-sunat-sdk |
composer require kembec/facturacion-electronica-sunat-sdk:^0.1@beta |
ejecuta el binario sunat-sdk una vez por llamada |
| Python 3.10 o posterior | facturacion-electronica-sunat-sdk |
pip install --pre facturacion-electronica-sunat-sdk |
wheels con el motor compilado adentro |
| Swift 5.9 o posterior (macOS, iOS, Linux) | el producto KembecSunatSDK de este repositorio |
SwiftPM por URL, exact: "1.0.0-rc.9" (cómo) |
xcframework de bibliotecas estáticas, enlazado en tu binario; en Linux, libsunat_sdk_ffi.so |
| Rust | los paquetes de crates/ |
desde este repositorio, abajo | crates, sin red |
| Línea de comandos | el programa sunat-sdk |
descarga en GitHub (Linux x86_64), o compílalo con cargo build |
binario estático |
La versión publicada es la 1.0.0-rc.9. Qué trae, en Versiones.
Tu primer comprobante firmado
Baja a una carpeta vacía una factura de ejemplo y el certificado de prueba del repositorio. Ese certificado es autogenerado y no sirve para emitir.
base=https://raw.githubusercontent.com/Kembec/facturacion-electronica-sunat-sdk/v1.0.0-rc.9 curl -fsSLO "$base/fixtures/01-invoice-taxed-cash.json" curl -fsSLO "$base/tests/vectors/test-cert/test-cert.pem" curl -fsSLO "$base/tests/vectors/test-cert/test-key.pem"
Cada ejemplo de abajo arma esa factura, la firma e imprime el nombre del archivo y la huella de la firma
(digest_value). Si ves 20123456789-01-F001-1 +te61a2ub5MlTFf8tKTPNGWEXKI=, el SDK funciona: es la misma salida en
todos los lenguajes. Tus propios documentos siguen la forma del modelo de entrada.
JavaScript y TypeScript
// primer-comprobante.mjs, después de npm install @kembec/facturacion-electronica-sunat-sdk@beta import { readFileSync } from 'node:fs'; import { load } from '@kembec/facturacion-electronica-sunat-sdk'; const read = (file) => readFileSync(file, 'utf8'); const sdk = await load(); const { file_name, xml } = JSON.parse(sdk.build(read('01-invoice-taxed-cash.json'))); const signed = JSON.parse(sdk.sign(xml, read('test-cert.pem'), read('test-key.pem'))); console.log(file_name, signed.digest_value); // 20123456789-01-F001-1 +te61a2ub5MlTFf8tKTPNGWEXKI=
Los tipos vienen en el paquete. El envío a la SUNAT, el uso en el navegador y en Cloudflare Workers están en el README del paquete.
PHP
El paquete de PHP ejecuta el binario sunat-sdk. En Linux x86_64, vendor/bin/sunat-sdk-install baja el binario de
su misma versión, comprueba su SHA-256 e imprime SUNAT_SDK_BIN=<ruta>. En otra plataforma, compílalo como en
Línea de comandos.
composer require kembec/facturacion-electronica-sunat-sdk:^0.1@beta export "$(vendor/bin/sunat-sdk-install)"
<?php // primer-comprobante.php require 'vendor/autoload.php'; use Kembec\SunatSdk\SigningKey; use Kembec\SunatSdk\SunatSdk; $sdk = new SunatSdk(getenv('SUNAT_SDK_BIN'), 120.0); // ruta del binario y tiempo máximo por llamada, en segundos $built = $sdk->build(file_get_contents('01-invoice-taxed-cash.json')); $signed = $sdk->sign($built->getXml(), new SigningKey('test-cert.pem', 'test-key.pem')); echo $built->getFileName(), ' ', $signed->getDigestValue(), PHP_EOL; // 20123456789-01-F001-1 +te61a2ub5MlTFf8tKTPNGWEXKI=
El envío, la consulta de la CDR y los errores están en el README del cliente PHP.
Python
# primer_comprobante.py import sunat_sdk def read(file): with open(file, encoding="utf-8") as f: return f.read() built = sunat_sdk.build(read("01-invoice-taxed-cash.json")) signed = sunat_sdk.sign(built["xml"], read("test-cert.pem"), read("test-key.pem")) print(built["file_name"], signed["digest_value"]) # 20123456789-01-F001-1 +te61a2ub5MlTFf8tKTPNGWEXKI=
La instalación y el envío a la SUNAT (exchange) están en el README del paquete de Python.
Rust
Desde la 0.1.0-beta.5 los crates se publican en crates.io como kembec-sunat-*; también se usan como dependencia git
del tag, con la clave corta para que el código diga sunat_model::…. sunat-model lee y valida el documento,
sunat-ubl arma el XML y sunat-xmldsig lo firma.
[dependencies] sunat-model = { package = "kembec-sunat-model", git = "https://github.com/Kembec/facturacion-electronica-sunat-sdk", tag = "v1.0.0-rc.9" } sunat-ubl = { package = "kembec-sunat-ubl", git = "https://github.com/Kembec/facturacion-electronica-sunat-sdk", tag = "v1.0.0-rc.9" } sunat-xmldsig = { package = "kembec-sunat-xmldsig", git = "https://github.com/Kembec/facturacion-electronica-sunat-sdk", tag = "v1.0.0-rc.9" }
use std::fs; fn main() -> Result<(), Box<dyn std::error::Error>> { let document = sunat_model::Document::from_json(&fs::read_to_string("01-invoice-taxed-cash.json")?)?; let unsigned = sunat_ubl::build(&document)?; let signed = sunat_xmldsig::sign( &unsigned, &fs::read_to_string("test-cert.pem")?, &fs::read_to_string("test-key.pem")?, )?; fs::write(format!("{}.xml", document.file_name()), signed)?; // 20123456789-01-F001-1.xml Ok(()) }
Los crates no hacen red: sunat-transport arma el ZIP y la petición SOAP y lee la respuesta, y el POST HTTP lo haces
tú. Lo que expone cada crate está en la arquitectura.
Línea de comandos
En Linux x86_64, el binario estático del release:
url=https://github.com/Kembec/facturacion-electronica-sunat-sdk/releases/download/v1.0.0-rc.9 curl -fsSLO "$url/sunat-sdk-x86_64-unknown-linux-musl" curl -fsSLO "$url/sunat-sdk-x86_64-unknown-linux-musl.sha256" sha256sum -c sunat-sdk-x86_64-unknown-linux-musl.sha256 install -m 755 sunat-sdk-x86_64-unknown-linux-musl /usr/local/bin/sunat-sdk sunat-sdk --version
En otra plataforma, compílalo con Rust 1.94.1:
git clone https://github.com/Kembec/facturacion-electronica-sunat-sdk.git
cargo build --manifest-path facturacion-electronica-sunat-sdk/Cargo.toml -p sunat-sdk-cli --release --locked
# el binario queda en facturacion-electronica-sunat-sdk/target/release/sunat-sdk
sunat-sdk build < 01-invoice-taxed-cash.json \ | sunat-sdk sign --cert test-cert.pem --key test-key.pem \ | grep -o '"digest_value":"[^"]*"' # "digest_value":"+te61a2ub5MlTFf8tKTPNGWEXKI="
Cada comando lee un JSON por la entrada estándar y escribe un JSON por la salida. send, summary, ticket y
status toman la URL del web service y las credenciales SOL de opciones o del entorno, nunca del código. El contrato
completo está en docs/cli.md.
Qué cubre
| Documento | Formato | Qué hace el SDK |
|---|---|---|
| Factura (01), boleta (03), nota de crédito (07) y nota de débito (08) | UBL 2.1 | arma, firma, comprime, envía (sendBill) y consulta la CDR (getStatusCdr) |
| Resumen diario (RC) de boletas y sus notas | UBL 2.0 | arma, firma, envía (sendSummary) y consulta el ticket (getStatus) |
| Comunicación de baja (RA) de facturas y sus notas | UBL 2.0 | arma, firma, envía (sendSummary) y consulta el ticket (getStatus) |
| Liquidación de compra (04) | UBL 2.1 | arma, firma y envía con send (abajo) |
| Resumen de reversión (RR) de liquidaciones de compra | UBL 2.0 | arma, firma, envía con summary al servicio otroscpe y consulta su ticket (abajo) |
| Guía de remisión remitente (09) | UBL 2.1 | arma, firma y envía por la API REST de la SUNAT (abajo) |
| Comprobante de retención (20) | UBL 2.0 | arma, firma y valida con los códigos de la SUNAT; la CLI lo envía a la beta (abajo) |
La CDR es la constancia con la que la SUNAT responde a cada envío.
- IGV con las tasas de 18 % y 10,5 %: operaciones gravadas, exoneradas, inafectas, de exportación y gratuitas (afectaciones 10 a 16, 20, 21, 30 a 37 y 40), en soles y en dólares, al contado o a crédito en cuotas.
- Resumen diario con la tasa de IGV y el nombre del cliente en cada línea, como lo pide la SUNAT desde el 01/08/2026, y con IVAP, ISC, otros tributos y la percepción del IGV en las líneas de boletas.
- Descuentos y cargos del catálogo 53, por línea y globales, en facturas y boletas.
- ICBPER (bolsas plásticas, tributo 7152), en comprobantes, notas y líneas del resumen diario.
- Anticipos en facturas y boletas: el comprobante del anticipo y el final que lo descuenta.
- Exportación de bienes y servicios (tipos de operación 0200 a 0208), en facturas y boletas.
- Firma XML-DSig enveloped como la pide la SUNAT (RSA-SHA1, C14N inclusiva), con tu certificado y tu llave en PEM; la llave, RSA sin cifrar.
- Respuesta de la SUNAT: el SDK lee la CDR y la clasifica (aceptada, observada, rechazada, excepción). Un error SOAP es un resultado, no una excepción.
- Sin respuesta: si una consulta no recibe respuesta, el resultado es
no_verdict; vuelve a consultar. Con el ticket de un resumen o una baja, nunca reenvíes el archivo: el ticket prueba que llegó. Si una factura, boleta o nota enviada sola sigue sin veredicto, reenvía el mismo XML firmado con el mismo número mientras el plazo de envío siga abierto, porque la SUNAT lo mide al recibirlo; según sus reglas de validación, si ya lo tenía no lo registra dos veces y contesta 1033, 1032 o 0140. Unsummarysin respuesta no deja ticket: revisa los comprobantes convalidityy reenvía el mismo archivo con el mismo nombre (contrato de la CLI). - Estado de un comprobante ya enviado en la Consulta Integrada de la SUNAT (
validity: aceptado, anulado o no informado), para facturas, boletas y notas.
Cómo llega cada lenguaje a la SUNAT:
| Arma, firma, ZIP y CDR | Envía y consulta | |
|---|---|---|
| Línea de comandos | build, sign, zip, cdr |
send, summary, ticket, status, validity, con HTTP y TLS verificado |
| PHP | build, sign, zip, cdr |
send, status y validity; resúmenes y bajas, con la CLI |
| JavaScript y TypeScript | build, sign, zip, cdr, en memoria |
soapRequest arma el POST, tu fetch lo envía y soapResponse lee la respuesta |
| Python | build, sign, zip, cdr, en memoria |
exchange hace el POST con TLS verificado; soap_request y soap_response para tu propio cliente HTTP |
| Rust | sunat-model, sunat-ubl, sunat-xmldsig, sunat-transport |
sunat_sdk::exchange arma la petición y lee la respuesta; el HTTP es tuyo |
Alcance
Los importes los calcula tu sistema. Llegan como texto ("118.00"); el SDK los valida y los escribe, no calcula
impuestos. Con descuento por línea, unit_value es el valor antes del descuento y line_value es unit_value ×
quantity menos los descuentos. Si mandas un unit_value ya neto del descuento, la SUNAT rechaza la factura (3271) y
observa la boleta (4288).
Lo que el SDK no cubre lo rechaza con un error explícito, en vez de generar un XML que la SUNAT rechazaría:
- en comprobantes: ISC y otros tributos, descuentos y cargos en notas de crédito y débito, los códigos del catálogo 53 que no son de descuentos y cargos, anticipos en notas y percepción;
- en la liquidación de compra: el tipo de operación 0502 (anticipos) y
totals.prepaid, las series que empiezan con un dígito (las impresas), las líneas gratuitas (9996) y otros tributos (9999).
Liquidación de compra (04): la emite el comprador, con su RUC, por una compra a una persona natural sin RUC; tipos
de operación 0501 y 0503 (compra de oro). Va con kind: "self_billed", el vendedor (seller: DNI, carné de
extranjería o pasaporte), el lugar de la operación (operation_place) y la retención del impuesto a la renta en cada
línea. El SDK aplica las reglas de la SUNAT que solo leen el documento; las que consultan sus padrones (la condición
del vendedor, el tope de 75 UIT) las valida la SUNAT. Para darla de baja, el resumen de reversión (RR) es
kind: "reversal": el nombre es RUC-RR-YYYYMMDD-N y la CLI lo envía con summary y lo consulta con ticket en el
servicio otroscpe, no en el de facturas, que lo rechaza con 0151. La SUNAT da 5 días desde la CDR de la liquidación;
el SDK solo ve la fecha de generación, así que rechaza con 2958 pasados 6 días, y un resumen enviado el día 6 aún puede
ser rechazado por ella. Los campos, en el modelo de entrada.
Referencias: la orden de compra (order_reference) y las guías de remisión que el comprobante cita
(despatch_references, tipo 09 o 31), en facturas, boletas y notas. Antes de enviar, el SDK rechaza lo que la SUNAT
observa (4005, 4006, 4233) y la guía citada dos veces (2364).
Detracción: en facturas y boletas con operación 1001 a 1004 (detraction: código de bien o servicio, porcentaje,
monto en soles, cuenta del Banco de la Nación y medio de pago). La 1002 (recursos hidrobiológicos) lleva en cada línea
la descarga (fishing: matrícula y nombre de la embarcación, especie, lugar, fecha y toneladas) y la 1004 (transporte
de carga), el viaje (freight: origen, destino, detalle y los tres valores referenciales). El monto lo calcula tu sistema; el SDK exige la
detracción con esas operaciones y la rechaza con cualquier otra.
IVAP: venta de arroz pilado en facturas, boletas y notas, con todas las líneas en afectación 17 y los totales
ivap_base e ivap. Las notas de crédito y débito van con el tipo 12, «Ajustes afectos al IVAP».
Retención del IGV informada en el comprobante: en facturas, boletas y notas (igv_retention: factor, importe y
base, cargo 62 del catálogo 53). El cliente debe tener RUC; que sea agente de retención lo valida la SUNAT contra su
padrón solo en producción.
Guía de remisión remitente (09): se arma, se firma y se envía por la API REST de la SUNAT: en la CLI
(gre-send, que pide el token con la clave SOL y sube el zip con su hashZip, y gre-ticket, que consulta el ticket
hasta tener la CDR), en PHP (greSend, greTicket), en Python (gre_send, gre_ticket) y en npm, que arma cada
llamada y lee su respuesta para que tu fetch la haga (greTokenRequest, greSendRequest, greTicketRequest y sus
*Response). La SUNAT no tiene ambiente de pruebas para la GRE, y ninguna guía de este SDK figura como aceptada
por la SUNAT. Quedan fuera los traslados de importación y exportación y la guía del transportista (31).
El comprobante de percepción no forma parte del SDK. El detalle de cada campo está en el modelo de entrada.
Versiones
La 1.0.0-rc.9 agrega, sin cambiar ninguna salida:
- en npm, el módulo WebAssembly también se exporta como
./sdk.wasm, para que un Cloudflare Worker lo cargue con la regla por defecto de wrangler;./wasmsigue apuntando al mismo archivo.
La 1.0.0-rc.8 agrega, sin cambiar ninguna salida que ya existía:
- en facturas (01) y boletas (03), la detracción de recursos hidrobiológicos (1002) y la de transporte de carga
(1004), con los datos de línea que pide la SUNAT para cada una (
lines[].fishingylines[].freight). La SUNAT beta aceptó las tres de prueba con código 0 y sin observaciones; - en el resumen diario (RC), líneas con IVAP en lugar de IGV, ISC, otros tributos y la percepción del IGV. La SUNAT beta aceptó los cuatro de prueba con código 0; el de IVAP, con la observación 4019, la misma línea que escribe Greenter;
- en npm,
npm installsin versión instala esta candidata mientras no haya una versión estable.
La 1.0.0-rc.7 cambia frente a la 1.0.0-rc.6 solo en los registros: la página del paquete en npm, Packagist, NuGet y Maven enlaza a fe.kembec.com. El SDK, su JSON de entrada y sus salidas no cambian.
La 1.0.0-rc.6 cambia frente a la 1.0.0-rc.5, sin campos nuevos en el JSON de entrada:
- en facturas (01) y boletas (03), el SDK revisa antes de firmar tres reglas de línea de la hoja de la SUNAT del
24/4/2026, con tolerancia de 1: en una boleta,
line_value=unit_value×quantity− descuentos00+ cargos47(observación 4288); en toda línea,unit_price= (line_value+total_taxes− descuentos01+ cargos48) ÷quantity, salvo las gratuitas (3270 en factura, 4287 en boleta); y en toda línea con afectación 10 a 17,igv=igv_base×igv_rate(3103 en ambas). Antes se firmaban y la SUNAT las rechazaba o las aceptaba con observación; ahora se rechazan aquí, y los montos se corrigen en el origen. Cambio que rompe compatibilidad (CHANGELOG); - cada factura y boleta que el SDK arma desde
fixtures/se valida, firmada, contra el esquema UBL 2.1Invoicede OASIS, como ya se hacía con las guías de remisión.
La 1.0.0-rc.5 cambia frente a la 1.0.0-rc.4, sin campos nuevos en el JSON de entrada:
- en la guía de remisión remitente (09), el SDK rechaza antes de firmar el texto libre que la SUNAT observa por formato (la nota, las razones sociales, la descripción del motivo 13, el transportista y su registro MTC, los conductores, las direcciones de partida y llegada, y la descripción y el código de cada línea) cuando trae un salto de línea, un tabulador u otro espacio que no sea el normal, o se sale del largo de la SUNAT. La SUNAT no tiene ambiente de prueba para guías, así que la fuente es su hoja de validaciones. El valor se rechaza con su campo, nunca se reescribe. Cambio que rompe compatibilidad (CHANGELOG);
- las notas de crédito (07) y débito (08) se revisan antes de firmar con
total_taxes=igv+ivap+icbper, con tolerancia de 1, como las facturas y boletas (3294 y 4301). Una nota con el total descuadrado ya no se firma. Cambio que rompe compatibilidad (CHANGELOG); sunat-sdk --version(o-V) imprime la versión en una línea y sale con 0.
La 1.0.0-rc.4 cambia frente a la 1.0.0-rc.3, sin campos nuevos en el JSON de entrada:
- en facturas, boletas y notas, el SDK rechaza antes de firmar la razón social, el nombre comercial, las partes de la dirección del emisor y el código de producto de una línea que traen un salto de línea, un tabulador u otro espacio que no sea el normal, o que se salen del largo que fija la SUNAT (4092, 4094 a 4098, 4338, 4234 y 4269). Antes llegaban a la SUNAT y volvían aceptados con una observación. El valor se rechaza con su campo, nunca se reescribe: se corrige en el origen. Cambio que rompe compatibilidad (CHANGELOG);
- en el resumen diario, el IGV de las ventas gratuitas (9996) lleva la tasa de la línea; esas ventas entran al resumen desde el 1/1/2027, así que ningún resumen de hoy cambia.
La 1.0.0-rc.3 suma frente a la 1.0.0-rc.2; el JSON de entrada solo gana los tipos self_billed y reversal:
- la liquidación de compra (04), con los tipos de operación 0501 y 0503; la beta de la SUNAT aceptó con código 0 los dos vectores válidos;
- su baja por resumen de reversión (RR), que va por el servicio
otroscpe: en la beta, el de facturas contestó 0151 yotroscpedio un ticket con estado 0; - el cierre de la exportación: los tipos de operación 0203 a 0207 con vectores propios, que la beta aceptó con código 0
y sin notas; el catálogo 04 es la lista ISO 3166-1 alpha-2 vigente, así que un código como
ZZ,ANoCSahora se rechaza (3099 y 4280); yigv_baseigual aline_valueen todos los tipos de operación (3272 en facturas, 4294 en boletas); - en toda factura y boleta,
totals.total_taxesigual aigv+ivap+icbperantes de firmar (3294 en facturas, 4301 en boletas); antes solo se revisaba en exportaciones y con ICBPER, y una factura común con el total descuadrado se firmaba y la SUNAT la rechazaba; - el release adjunta
SunatSDKFFI.xcframework.zip, el archivo cuya URL y huella nombra elPackage.swiftdel tag, para Swift Package Manager; - en el modelo de Rust,
Documentgana las variantesSelfBilledyReversal(Voided): unmatchsobreDocumentnecesita sus brazos (o_). Cambio que rompe compatibilidad (CHANGELOG).
La 1.0.0-rc.2 suma frente a la 1.0.0-rc.1, sin cambiar el contrato JSON:
- la factura y la boleta de exportación en los nueve tipos de operación (0200 a 0208), con el país de uso del servicio
y los datos del huésped en el hospedaje; la beta de la SUNAT aceptó los cuatro vectores válidos. En el modelo de
Rust,
LineeInvoiceganan campos; - la respuesta de un OSE se lee como la de la SUNAT: número, código, observaciones y
diagnosis.
La 1.0.0-rc.1 es la candidata a la primera versión estable: el mismo contrato que la 0.1.0-beta.8, con el paquete .NET
ya en NuGet (Kembec.SunatSdk). La 1.0.0 sale tras 15 días de uso en producción por un emisor real, con sus CDR
vigiladas y todo rechazo causado por el SDK corregido (política de versiones).
La 0.1.0-beta.8 había sumado frente a la 0.1.0-beta.7:
- anticipos en facturas y boletas: la del anticipo y la final que lo descuenta (
advances,totals.prepaid), con los códigos de la SUNAT; la beta de la SUNAT aceptó los cuatro vectores válidos. En el modelo de Rust,InvoiceyTotalsganan campos; - en el resumen diario,
igv_rateobligatorio en cada línea: sin ella el SDK responde 2992 antes de enviar. Cambio que rompe compatibilidad; - cuando la SUNAT rechaza un resumen, la CLI devuelve su código y
diagnosisen vez detransport error: Bad Status; - el ICBPER, probado en la beta de la SUNAT (cinco vectores aceptados).
La 0.1.0-beta.7 había sumado frente a la 0.1.0-beta.6:
- el ICBPER (tributo 7152) en facturas, boletas, notas y resúmenes diarios;
- reglas con fecha de entrada en vigor del 01/01/2027 en el resumen diario y en la nota de débito 13;
- en la guía de remisión remitente (09) de transporte público, la fecha de entrega al transportista
(
carrier_handover_date), obligatoria: sin ella el SDK responde 3617. Cambio que rompe compatibilidad; diagnosis—el campo de entrada al que apunta cada código de la SUNAT— en todos los lenguajes: PHP, JavaScript, Python, .NET, Java, Ruby, Perl, Swift y Objective-C, y en la CLI (el CHANGELOG detalla cada uno).
La 0.1.0-beta.6 había sumado frente a la 0.1.0-beta.5:
- el comprobante de retención (20): armado, firma y validación con los códigos de la SUNAT, y envío por la CLI a la beta de la SUNAT, donde se aceptaron los tres vectores válidos;
- descuentos y cargos del catálogo 53 en facturas y boletas, por línea y globales;
- en el modelo de Rust,
Line,InvoiceyTotalsganan campos; el JSON de entrada no cambia sin descuentos ni cargos (CHANGELOG).
La 0.1.0-beta.5, publicada en npm, PyPI, Packagist y GitHub, había sumado frente a la 0.1.0-beta.4:
- el envío de la guía de remisión remitente (09) por la API REST de la SUNAT, en la CLI, PHP, Python y JavaScript;
- en PHP, los resúmenes diarios y las comunicaciones de baja (
summary,ticket); - la ABI en C y, sobre ella, .NET, Java, Ruby, Perl, Swift, Objective-C, VB6, Visual FoxPro y PowerBuilder; ninguno está todavía en su registro (NuGet, Maven Central, RubyGems, CPAN) ni se agrega por URL con SwiftPM;
- dos cambios que rompen compatibilidad: los crates se llaman
kembec-sunat-*, y una CDR con código 0 y cualquier nota esobserved. Cómo migrar, en el CHANGELOG.
La 0.1.0-beta.4 había sumado frente a la 0.1.0-beta.3:
- la consulta de estado de comprobantes (
validity, Consulta Integrada) para facturas, boletas y notas, en la CLI, PHP, JavaScript y Python; - la detracción en facturas y boletas con operación 1001 y 1003;
- la retención del IGV informada en el comprobante (cargo 62 del catálogo 53);
- el IVAP, en facturas, boletas y notas;
- la orden de compra y las guías de remisión relacionadas;
- la guía de remisión remitente (09): armado, firma y validación, sin envío.
La 0.1.0-beta.3 había sumado los resúmenes diarios y las comunicaciones de baja, el descuento por línea, el envío y las
consultas desde JavaScript, la CDR observada (código 0 con una nota de 4000 o más), no_verdict y el paquete de Python.
Cómo se comprueba
Un tipo de documento entra al SDK cuando cumple tres condiciones:
- su XML coincide con los vectores de
tests/vectors/; - su firma da exactamente el mismo
DigestValuey el mismoSignatureValue, y la acepta un verificador independiente; - la beta de la SUNAT lo acepta.
Qué aceptó la SUNAT. El 16/10/2026, la beta de la SUNAT aceptó 36 de los 38 documentos de
fixtures/, enviados con la CLI: los 29 comprobantes, con código 0 y sin observaciones, y los 7 resúmenes
diarios y comunicaciones de baja, procesados con código 0. Las 2 guías de remisión no pasan por la beta, porque la
SUNAT no tiene ambiente de pruebas para la GRE: se contrastan con sus vectores, el esquema OASIS y las reglas de la
SUNAT, y su envío se probó solo contra respuestas del manual de la SUNAT y de un cliente de terceros.
En cada cambio, el CI arma y firma los 38 documentos por cada vía —la CLI, PHP, el paquete npm en Node 18, Node 22 y
Chrome, y el paquete de Python instalado— y exige el DigestValue y el SignatureValue exactos de su vector. Un
verificador en Python que no comparte código con el núcleo acepta las firmas.
Sin red: el envío y las consultas desde JavaScript y Python se prueban con respuestas escritas a mano; comparten con la CLI el código que arma la petición y lee la respuesta.
Sin probar contra la SUNAT: la consulta de la CDR (status), que la SUNAT solo ofrece en producción y que sigue el
ejemplo de su manual, y el envío desde el navegador, que depende de que la SUNAT permita CORS.
Documentación y soporte
- Guías y ejemplos: https://fe.kembec.com
- Contratos: arquitectura, CLI y modelo de entrada.
- Cambios de cada versión y qué rompe el contrato:
CHANGELOG.mdy la política de versiones. - Errores y preguntas: issues de GitHub.
Derechos y licencia
- Código de este repositorio: licencia MIT, © 2026 Manuel Benancio (ver
LICENSE). - La documentación técnica, los catálogos y los esquemas de la SUNAT le pertenecen a la SUNAT. Este repositorio no los redistribuye.
- Proyecto independiente: no está afiliado a la SUNAT, y no es un PSE ni un OSE. Quien lo usa sigue siendo el emisor electrónico y responde por sus comprobantes.
- Se entrega sin garantía, según la licencia MIT.