PHPackages                             esolutions/xml - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. esolutions/xml

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

esolutions/xml
==============

Generacion, firma, validacion y envio SUNAT/OSE de XML UBL para comprobantes electronicos (Peru)

v2.9.0(2w ago)030↓25%proprietaryXSLTPHP ^8.2

Since Mar 18Pushed 1w agoCompare

[ Source](https://github.com/eriquegasparcarlos/esolutions-xml)[ Packagist](https://packagist.org/packages/esolutions/xml)[ RSS](/packages/esolutions-xml/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (8)Versions (18)Used By (0)

esolutions/xml
==============

[](#esolutionsxml)

Generación, firma, validación y envío SUNAT/OSE de comprobantes electrónicos peruanos (XML UBL) para proyectos Laravel. **Independiente del proyecto consumidor**: la entrada es un array payload documentado y la configuración entra por objetos propios — sin dependencias a modelos Eloquent externos.

Tipos de documento soportados
-----------------------------

[](#tipos-de-documento-soportados)

DocumentoCódigoPlantillaUBLEnvíoFactura / Boleta01 / 03`invoice`2.1`sendBill` (síncrono, CDR inmediato)Liquidación de compra04`purchase-settlement`2.1 (SelfBilledInvoice)`sendBill`Nota de crédito07`credit-note`2.1`sendBill`Nota de débito08`debit-note`2.1`sendBill`Guía de remisión — remitente09`despatch`2.1**REST GRE 2022** (`GreRestClient`)Guía de remisión — transportista31`despatch-carrier`2.1**REST GRE 2022**Retención20`retention`2.0`sendBill` (endpoint propio)Percepción40`perception`2.0`sendBill` (endpoint propio)Resumen diarioRC`summary`2.0`sendSummary` → ticket → `getStatus`Comunicación de bajaRA`voided`2.0`sendSummary` → ticket → `getStatus`Reversión (baja de retención/percepción)RR`voided` (mismo XML)2.0`sendSummary` → ticket → `getStatus` (endpoint retenciones)**Envíos verificados en homologación** (SUNAT beta / Nubefact): factura/boleta, guía remitente (todas las variantes de motivo + múltiples conductores/vehículos), guía transportista (pipeline), exportación con DAM, resumen, baja, retención, percepción y reversión — todos aceptados con CDR. Ver [`docs/sending-verified.md`](docs/sending-verified.md).

Dos entradas: contrato interno o API español
--------------------------------------------

[](#dos-entradas-contrato-interno-o-api-español)

```
use ESolutions\Xml\Contracts\XmlDocumentGeneratorContract;

$gen = app(XmlDocumentGeneratorContract::class);

// 1) Contrato interno (todos los tipos)
$res = $gen->generate('01', $payload);

// 2) API en español, estilo Greenter/camelCase (todos los tipos)
$res = $gen->generateFromEs('01', $payloadEs);   // tipoDoc, serie, client{}, details[]...

if ($res->isOk()) {
    $signedXml = $res->xml;
} else {
    $errores = $res->validation->errors;   // [PAYLOAD] / [XSD] / reglas
}
```

El payload interno se documenta por tipo en [`docs/payloads/`](docs/payloads/); el camelCase del API español, en [`docs/spanish-api.md`](docs/spanish-api.md). El mapeo desde tus modelos vive en **tu** proyecto (patrón anti-corruption).

Validación (XSD + reglas SUNAT + reconciliaciones)
--------------------------------------------------

[](#validación-xsd--reglas-sunat--reconciliaciones)

- **XSD** oficial UBL 2.0/2.1.
- **Reglas SUNAT del cliente** (`SunatRulesValidator`): extraídas del XSLT del SFS de SUNAT (motor de primitivas + catálogos), por tipo de documento.
- **`OwnRules`**: reconciliaciones server-side que el XSLT no trae (3294 sumatoria de impuestos, 3305 total precio venta) y reglas **fechadas** (ver abajo).
- **Catálogo de códigos**: `error-codes.php` (2077 códigos oficiales del Excel de SUNAT) superpuesto sobre `CatalogoErrores.xml`.

### Observaciones no bloqueantes (`GenerationResult::$warnings`)

[](#observaciones-no-bloqueantes-generationresultwarnings)

El comprobante se firma y se envía igual, pero conviene revisarlas. Vienen de dos lados:

- **`PreSignGate`** — observaciones SUNAT (códigos ≥ 4000) cuando corre con `block_on_observations=false`.
- **`SilentDropDetector`** — datos que el emisor **sí cargó** y la plantilla descarta por una regla de SUNAT. Hoy aplica a la **GRE remitente (09)**: en un vehículo de categoría **M1/L**, SUNAT no admite placa ni conductor, así que la plantilla los omite. La omisión es correcta —sin ella SUNAT rechaza con **2567**— pero antes era **silenciosa**: se mandaba una placa y el XML salía sin ella, sin ninguna señal.

```
$res = $generator->generate('09', $payload);

foreach ($res->warnings as $w) {
    // XML_SILENT_DROP: "Vehículo M1/L: SUNAT no admite placa ni conductor…
    //                   se omitieron del XML los datos enviados (placa 6617C)…"
    echo "[{$w->code}] {$w->message}\n";
}
```

Se revisaron las 10 plantillas: este descarte solo ocurre en `despatch`. En el resto, los `@if` emiten según la presencia del propio dato o una regla legítima — ninguno pisa un valor cargado por el emisor.

Enviar a SUNAT / OSE
--------------------

[](#enviar-a-sunat--ose)

```
use ESolutions\Xml\Sending\{DocumentSender, SenderConfig, FilenameBuilder};

$config = SenderConfig::fromArray([
    'provider' => 'sunat',            // 'sunat' | 'nubefact'
    'environment' => 'demo',          // 'demo' (beta) | 'production'
    'username' => '20123456789MODDATOS',
    'password' => 'moddatos',
    'gre_client_id' => '...',         // solo GRE (guías); en demo usa las beta públicas
    'gre_client_secret' => '...',
]);

$sender = new DocumentSender($config);
$result = $sender->send($filename, $signedXml);   // resuelve sendBill vs sendSummary

if ($result->isAccepted()) {
    $cdrXml = $result->getCdrXml();
} elseif ($result->isRejected()) {
    $motivo = $result->getMessage();
}
```

- **Resúmenes/bajas** (async): `sendSummary()` → ticket → `getStatus($ticket)` (`isInProcess()` = 98).
- **Guías (GRE 2022)**: `GreRestClient::fromSenderConfig($config)->sendDespatch($filename, $xml)` → ticket → `getStatus()`. Ver [`docs/gre.md`](docs/gre.md).
- **Reconsulta de CDR** (caso 1033): `ConsultCdrService::getStatusCdr(...)`.

### Origen del error (conexión / sistema / SUNAT)

[](#origen-del-error-conexión--sistema--sunat)

Los results exponen `errorSource()` para decidir la acción sin combinar banderas:

```
$result->errorSource();
// 'conexion' → no llegó a SUNAT       → REINTENTAR
// 'sunat'    → respondió y rechazó     → CORREGIR el comprobante
// 'sistema'  → llegó pero falló parseo → REVISAR
// null       → aceptado / en proceso
```

Bucle de mejora de validaciones (feedback)
------------------------------------------

[](#bucle-de-mejora-de-validaciones-feedback)

`RejectionAnalyzer` + `RejectionSink` (JSONL o Eloquent propio) capturan los rechazos **reales** de SUNAT y marcan si el validador local los habría atrapado — los que no, son los huecos a agregar a `OwnRules`. Ver [`docs/feedback-loop.md`](docs/feedback-loop.md).

Cambios de reglas SUNAT fechados (date-gating)
----------------------------------------------

[](#cambios-de-reglas-sunat-fechados-date-gating)

SUNAT publica cambios con **fecha de vigencia** y valida cada comprobante según su **fecha de emisión**. El paquete convive ambas eras en una sola versión: cada regla/campo nuevo se aplica solo a documentos con `fechaEmision >= vigencia`. Las fechas son **configurables** (`config('esolutions_xml.rule_dates')`) por si SUNAT posterga. Los cambios con vigencia 2026-08-01 (código de producto obligatorio, tipo 13 de ND, gratuitas desagregadas en el resumen, etc.) ya están implementados. Ver [`docs/sunat-changes-2026-08.md`](docs/sunat-changes-2026-08.md).

La fuente autoritativa es el Excel oficial de reglas de SUNAT; [`tools/extract_from_excel.php`](tools/extract_from_excel.php) extrae los códigos de retorno y hace inventario de reglas por documento — re-correr en cada Excel nuevo + `git diff`.

Tests
-----

[](#tests)

Suite PHPUnit + Orchestra Testbench que recorre los fixtures JSON (que también son ejemplos de payload) y valida generación + XSD + reglas por cada tipo:

```
composer install
composer test        # o vendor/bin/phpunit
```

Ver [`docs/testing.md`](docs/testing.md). Los fixtures viven en `tests/fixtures/payloads/` (interno) y `tests/fixtures/payloads-es/` (español).

Firma
-----

[](#firma)

XMLDSig enveloped (RSA-SHA1 + C14N) en el `` vacío del template. Certificado por `config('esolutions_xml.signing')`; sin configurar usa el **demo de SUNAT beta** (RUC 10417844398, solo homologación). Los `.pfx/.p12` se convierten a PEM al vuelo.

### Leer un certificado sin firmar nada

[](#leer-un-certificado-sin-firmar-nada)

`Signed::inspect()` devuelve titular, emisor, vigencia y huellas de un certificado — útil para mostrarlo en una pantalla de alta, sin necesitar un XML que firmar:

```
$info = (new Signed())->inspect($path, $password);   // .pem / .pfx / .p12

$info['common_name'];       // titular (CN del subject)
$info['valid_from_time_t']; // timestamp de emisión
$info['valid_to_time_t'];   // timestamp de vencimiento
$info['is_expired'];        // bool
$info['days_left'];         // int|null
$info['fingerprint_sha256'];
```

Lanza `InvalidArgumentException` (o la excepción de OpenSSL) si la contraseña de un PFX/P12 es incorrecta, o si un PEM no trae la clave privada — la misma validación que aplicaría `xmlSigned()`, pero antes de guardar el certificado, no al emitir el primer comprobante.

Índice de documentación
-----------------------

[](#índice-de-documentación)

- [`docs/spanish-api.md`](docs/spanish-api.md) — API en español (camelCase) + roadmap de cobertura.
- [`docs/gre.md`](docs/gre.md) — Guías de remisión REST (GRE 2022).
- [`docs/sending-verified.md`](docs/sending-verified.md) — matriz de envíos probados en homologación.
- [`docs/feedback-loop.md`](docs/feedback-loop.md) — captura de rechazos → mejora de reglas.
- [`docs/sunat-changes-2026-08.md`](docs/sunat-changes-2026-08.md) — cambios fechados + date-gating.
- [`docs/testing.md`](docs/testing.md) — suite PHPUnit + fixtures.
- [`docs/payloads/`](docs/payloads/) — contrato de payload interno por tipo.

Requisitos e instalación
------------------------

[](#requisitos-e-instalación)

```
composer require esolutions/xml
php artisan vendor:publish --tag=esolutions-xml-config   # opcional
```

PHP `^8.2`, Laravel `^11|^12|^13`, extensiones `dom`, `libxml`, `openssl`, `soap`, `zip`. Provider por auto-discovery.

Limitaciones conocidas
----------------------

[](#limitaciones-conocidas)

- **Guía transportista (31)** en homologación: el pipeline (envío/ticket/CDR) está probado, pero la aceptación requiere un RUC registrado como transportista con autorización MTC (dato real, no fabricable en beta demo).
- **Importación (08)** de guía: estructura DAM completa; la aceptación requiere un establecimiento aduanero de tercero registrado en SUNAT.
- Los certificados `.pem` empaquetados son los **demo públicos de SUNAT beta** — nunca en producción.
- El sender nunca lanza excepciones de conexión (retorna `errorSource() = 'conexion'`); el consumidor decide el reintento (no hay cola integrada).

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance97

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~9 days

Recently: every ~4 days

Total

17

Last Release

16d ago

Major Versions

v1.0.0 → v2.x-dev2026-07-15

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/57302658?v=4)[eriquegasparcarlos](/maintainers/eriquegasparcarlos)[@eriquegasparcarlos](https://github.com/eriquegasparcarlos)

---

Top Contributors

[![eriquegasparcarlos](https://avatars.githubusercontent.com/u/57302658?v=4)](https://github.com/eriquegasparcarlos "eriquegasparcarlos (54 commits)")

---

Tags

xmlfacturacion-electronicaublsunatperucpe

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/esolutions-xml/health.svg)

```
[![Health](https://phpackages.com/badges/esolutions-xml/health.svg)](https://phpackages.com/packages/esolutions-xml)
```

###  Alternatives

[statamic-rad-pack/runway

Eloquently manage your database models in Statamic.

138249.0k8](/packages/statamic-rad-pack-runway)[duncanmcclean/statamic-cargo

Comprehensive e-commerce addon for Statamic. Build bespoke e-commerce sites without the complexity.

3622.8k](/packages/duncanmcclean-statamic-cargo)[ecotone/laravel

Ecotone for Laravel — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Laravel Queue, via PHP attributes.

21336.4k4](/packages/ecotone-laravel)[greenter/xml

XML generación de documentos electrónicos, Facturacion Electrónica SUNAT - Perú

1169.9k4](/packages/greenter-xml)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
