En beta de SUNATProductos y APIsIntegraciones

ComprobiaFacturación electrónica SUNAT como servicio

Servicio multi-tenant que emite comprobantes electrónicos ante SUNAT por API —facturas, boletas, notas y guías—, con cada emisor firmando con su propio certificado y el punto de venta sin esperar nunca a SUNAT.

Busco algo como esto
comprobia.stackealabs.com
Comprobia: Sitio comercial
pruebas en Pest, con el aislamiento multi-tenant contra PostgreSQL real
~2,000
la API responde al instante; el envío a SUNAT va en cola
202
commits en seis semanas, desde el 1 de septiembre
382
el plan libre: 100 comprobantes al mes
S/ 0

Comprobia se llamaba NexoCPE hasta octubre de 2026. Hoy es un producto de Stackea Labs.

Problema

Todo sistema que vende en el Perú tiene que emitir comprobantes electrónicos, y casi todos lo resuelven igual: conectándose a un facturador comercial. Funciona, hasta que se mira de cerca lo que pasa en la integración. El punto de venta espera a SUNAT dentro de la misma petición. Un reintento puede duplicar un comprobante. Un error de negocio llega como un 500 genérico.

Mis sistemas emitían así. Y no era mi primer encuentro con el dominio: en 2021 desarrollé la facturación electrónica de Mi Factura Perú. Comprobia es esa experiencia convertida en producto: un servicio de emisión que se integra por API y que no le hace pagar al punto de venta los problemas de SUNAT.

No reemplaza al facturador que ya funciona donde está. Es un servicio aparte, para lo que se integra de aquí en adelante.

Contexto

Comprobia emite bajo la modalidad SEE – Del Contribuyente. La distinción importa más de lo que parece: no es un OSE ni un PSE. Cada emisor firma con su propio certificado digital. Centralizar la firma con un certificado nuestro obligaría a inscribirse como PSE, con un capital mínimo de 150 UIT y certificación ISO 27001.

Y el ambiente de pruebas de SUNAT engaña. Beta valida estructura, no reglas de negocio: acepta cualquier certificado, incluso autofirmado, y no comprueba credenciales. Que un comprobante pase en beta no significa que producción lo acepte. Además, está prohibido usarla para pruebas de carga: SUNAT bloquea la IP.

Arquitectura

API → 202 Accepted: el punto de venta sigue
    → correlativo → XML UBL 2.1 → firma XMLDSig → PDF + QR
    → cola → SOAP → CDR de SUNAT
PiezaQué hace
API v1Responde 202 Accepted en menos de un segundo. Idempotency-Key obligatoria, rate limit y permisos por clave. Lotes de hasta 50, con la idempotencia por comprobante: si el proxy corta a los 60 s, el reintento no duplica la mitad ya emitida
CorrelativoSELECT … FOR UPDATE sobre la fila de la serie, en la misma transacción que crea el comprobante
XML y firmaUBL 2.1 firmado con XMLDSig. La librería de firma vive encapsulada en un solo módulo, detrás de una capa de abstracción
ColaHorizon sobre Redis: escalera de reintentos de 46 h, circuit breaker con contingencia y un proceso que recoge los envíos huérfanos
PDFA4 y ticket de 80 mm con el QR del manual, renderizados con Chrome headless
AislamientoRow Level Security de PostgreSQL. La aplicación conecta con un rol sin superusuario, porque un superusuario se salta la RLS siempre
Panel y portalPanel Filament multi-emisor, y un portal público para que el adquirente consulte su comprobante

Qué resuelve

  • Los cuatro comprobantes de venta: factura, boleta, nota de crédito y nota de débito. Las boletas van por resumen diario y se anulan con ConditionCode = 3; las facturas, por comunicación de baja. El sistema elige el mecanismo solo, y ese es el error de diseño más común de los puentes propios.
  • Los casos que complican el XML: detracción, percepción (en soles y en dólares), factura al crédito con cuotas, anticipos, ISC, exportación e ICBPER.
  • Guías de remisión, de remitente y de transportista, por la API REST de SUNAT.
  • El ciclo del certificado. Los .pfx se guardan cifrados con una clave derivada por emisor y nunca se descifran a disco; se rotan dentro de una transacción y avisan de su vencimiento a los 60, 30, 15, 7 y 1 días.
  • El alta del emisor, con el RUC comprobado por módulo 11 y un asistente que termina en «listo para BETA».
  • Partners: una sola clave para que un integrador o un estudio contable emita por toda su cartera de RUC, y nada fuera de ella.
  • Avisos al operador por correo y por Telegram, y un plugin para WooCommerce.

Decisiones clave

  • El punto de venta nunca espera a SUNAT. La emisión responde 202 y el envío ocurre en cola. Una caída de SUNAT es un comprobante pendiente, no una caja parada.
  • El XML firmado se guarda antes de enviarse, y después no se toca. Un reintento reenvía exactamente los mismos bytes. Volver a firmar está prohibido —cambiaría la firma y rompería la trazabilidad—, y cualquier alteración posterior, hasta reindentar, es el error 2335.
  • Un rechazo quema el número, y el sistema lo sabe. Un rechazo del rango 2000–3999 no se reintenta: se emite un comprobante nuevo. Los errores 0100–1999, los timeouts y el 0111 sí se reintentan con el mismo correlativo.
  • Los estados solo cambian con la respuesta de SUNAT. Una anulación no anula nada hasta que SUNAT la acepta.
  • El total lo calcula Comprobia, no el cliente. Cálculo por línea con un tipo Monto, y lo que manda el punto de venta se compara al céntimo, no se emite. Si no coinciden, el ticket impreso no cuadraría con el comprobante.
  • Ningún secreto en un log. Ni la clave del certificado, ni la clave SOL, ni un token. La librería SOAP abría el TLS sin verificar el par si no se le decía nada, con la clave SOL viajando en el sobre: el transporte lo sobrescribe.

Hueco o quemado. Un número que el contador entregó y no tiene comprobante es un hueco: un defecto nuestro, que no debería poder pasar. Un comprobante rechazado, anulado o vencido es un número quemado: el sistema funcionando, pero el auditor va a preguntar por él. El reporte de saltos los separa, y falla solo ante un hueco. Un reporte que grita en falso deja de leerse, y entonces el hueco de verdad pasa desapercibido.

Cómo se construye

382 commits en seis semanas, desde el 1 de septiembre de 2026, y va por la versión 0.37. Casi 2,000 casos de prueba en Pest: los de dominio corren sin base de datos, sin red y sin certificados, y la suite de aislamiento multi-tenant corre contra PostgreSQL real y es bloqueante. Ningún test puede salir a la red: un transporte de prueba hace fallar a cualquiera que lo intente.

No hay CI: el repositorio es privado y la puerta es local —Pint, PHPStan y la suite, en ese orden—. Por eso está escrita como regla y no como costumbre.

Lo nuevo

  • SIRE: los registros de ventas y compras de SUNAT, en lectura.
  • Retenciones, percepciones y liquidaciones de compra, además de las guías de remisión del transportista.
  • Carga masiva desde Excel, CSV o TXT, por el panel o por API, y un conector para sistemas antiguos que emite lo que aparece en una carpeta: el ERP de siempre factura sin tocar su código.
  • Emisión manual desde el panel —factura, boleta y notas— para quien todavía no tiene sistema.
  • Un portal para desarrolladores con sandbox, un servidor MCP y herramientas gratis: consultar un RUC, revisar un certificado o validar un XML.

Qué queda por delante

  • Pasar a producción. Ya existe un RUC real con su certificado y su usuario SOL, pero se sigue en beta a propósito. El criterio de salida del núcleo es un mes emitiendo contra ese RUC, y el procedimiento del día D está escrito paso a paso.
  • El piloto con Podoplus, hoy en beta: las sedes están cargadas tal como SUNAT las tiene registradas en cada RUC, con una clave de API por sede.
  • Las reglas que entran en vigor el 1 de enero de 2027, ya empezadas.

Aprendizajes

Lo que más tiempo costó no estaba en ningún manual. El endpoint que lista el manual no es el del servicio: termina en ?wsdl, y si se postea el sobre ahí, SUNAT responde un error interno sin código. El código de error se pierde por el camino: PHP convierte el Client.3244 en un Client a secas, y un error que se reintenta se vuelve indistinguible de un bug propio. Y el texto del error 3244 habla del tipo de transacción, pero lo que falta es la forma de pago.

La lección de fondo fue sobre las fuentes. Contrastando la especificación contra la hoja oficial de reglas de SUNAT aparecieron nueve citas equivocadas, y una de ellas rechazaba facturas válidas. Por eso la regla del proyecto es una: no se inventan datos de SUNAT. La verdad está en la hoja oficial, no en la especificación ni en el código de otro sistema.

Y una de despliegue: de los seis contenedores, el que más se olvida es el scheduler. Sin él, las boletas se emiten y nunca se informan a SUNAT, los plazos vencen y los certificados caducan sin aviso. Todo con la API respondiendo 202 tan contenta.

Resultado

El núcleo emisor está completo y verificado contra beta, con CDR aceptada: factura, boleta, nota de crédito, resumen diario —incluida una anulación—, comunicación de baja y una boleta con ICBPER. Encima tiene panel, portal del adquirente, API de partners y plugin de WooCommerce. Lo que falta para producción no es código: es encender un RUC real.

3 capturas · toca una para verla en grande

Cuéntame qué quieres resolver. Te respondo en 24–48 h con una primera idea de cómo lo construiría, y si no soy la persona indicada, te lo digo.

Proyectos relacionados

En producción

Productos y APIs · Integraciones

Acredita

API de RUC, DNI y tipo de cambio que responde, en cada consulta, de dónde salió el dato y de qué fecha es.

Para: Sistemas que registran clientes y proveedores en el Perú

  • PHP 8.4
  • Laravel 13
  • Octane + FrankenPHP
  • PostgreSQL 16
Ver el caso
Acceso anticipado

Productos y APIs · Integraciones

Avisa

Tu sistema reporta «cita creada» y Avisa decide qué mensaje de WhatsApp enviar, cuándo y cuánto cuesta.

Para: Clínicas y negocios que agendan, cobran y avisan por WhatsApp

  • PHP 8.4
  • Laravel 13
  • Octane + FrankenPHP
  • PostgreSQL 16
Ver el caso