Endpoint
POST /api/comprobantes/enviar/{ventaId}
Autenticación
Requiere token de autenticación Bearer:
Authorization: Bearer {token}
Descripción
Envía un comprobante electrónico (Factura o Boleta) a SUNAT para su validación. Realiza las siguientes operaciones:
Genera XML usando Greenter con los datos de la venta
Firma digitalmente con certificado PEM de la empresa
Envía a SUNAT vía SOAP (web service)
Recibe CDR (Constancia de Recepción) si es aceptado
Actualiza venta con hash, URLs y estado SUNAT
Este endpoint está implementado en ComprobanteElectronicoController y utiliza SunatService para la integración.
Parámetros de URL
ID de la venta a enviar (id_venta)
Flujo de Envío
Paso 1: Generación de XML
Se ejecuta automáticamente antes del envío:
// SunatService::generarXml()
- Carga venta con relaciones ( cliente , empresa , productos , cuotas )
- Construye objeto Invoice de Greenter
- Calcula montos gravados / exonerados según IGV
- Genera detalles de productos con códigos SUNAT
- Añade forma de pago ( contado / crédito con cuotas )
- Convierte monto a letras ( "SON QUINIENTOS SOLES" )
- Firma con certificado PEM
- Guarda XML en storage / app / sunat / xml / { ruc } /
Paso 2: Envío a SUNAT
// SunatService::enviarComprobante()
- Lee XML del storage
- Conecta a endpoint SOAP de SUNAT
- Usa credenciales SOL ( RUC + Usuario + Clave )
- Envía XML firmado
- Espera respuesta síncrona
Paso 3: Procesamiento de Respuesta
Si es aceptado:
Descarga CDR (ZIP con respuesta XML de SUNAT)
Guarda en storage/app/sunat/cdr/{ruc}/R-{nombre}.zip
Actualiza venta: estado_sunat = '1', codigo_sunat, mensaje_sunat, cdr_url
Si es rechazado:
Registra código y mensaje de error
Actualiza venta: estado_sunat = '3', incrementa intentos
Campos Generados en XML
Estructura del Comprobante
Emisor (Company):
RUC (modo producción o beta)
Razón social
Nombre comercial
Dirección completa con ubigeo
Cliente (Client):
Tipo de documento (1=DNI, 6=RUC, 4=CE, 0=Otros)
Número de documento
Razón social o nombre
Dirección (si existe)
Documento:
UBL Version: 2.1
Tipo de operación: 0101 (venta interna)
Tipo de documento: 01 (Factura) o 03 (Boleta)
Serie y correlativo
Fecha de emisión (timezone America/Lima)
Tipo de moneda: PEN o USD
Totales:
Monto operaciones gravadas (base imponible)
IGV (18% sobre base)
Total impuestos
Valor venta (subtotal)
Importe total de venta
Forma de Pago:
Contado: FormaPagoContado
Crédito: FormaPagoCredito + array de Cuotas con fechas y montos
Detalles (por cada producto):
Código de producto
Código SUNAT genérico: 10000000
Unidad de medida (NIU, ZZ, etc.)
Descripción
Cantidad
Valor unitario sin IGV
Valor de venta de línea sin IGV
Base IGV
Porcentaje IGV (18.00)
IGV de línea
Tipo de afectación: 10 (gravado) o 20 (exonerado)
Precio unitario con IGV
Leyendas:
Código 1000: Monto en letras (“SON QUINIENTOS SOLES”)
Cálculo de Montos con IGV
Con IGV (tipo_afectacion_igv = ‘10’):
$igvRate = 0.18 ; // 18%
$precioConIGV = 100.00 ;
$valorUnitario = round ( $precioConIGV / 1.18 , 2 ); // 84.75
$valorVenta = $valorUnitario * $cantidad ;
$igv = round ( $valorVenta * 0.18 , 2 ); // 15.25
$total = $valorVenta + $igv ; // 100.00
// XML:
$invoice -> setMtoOperGravadas ( $valorVenta )
-> setMtoIGV ( $igv )
-> setTotalImpuestos ( $igv )
-> setValorVenta ( $valorVenta )
-> setMtoImpVenta ( $total )
-> setSubTotal ( $total );
Sin IGV (tipo_afectacion_igv = ‘20’):
$precioSinIGV = 100.00 ;
// XML:
$invoice -> setMtoOperExoneradas ( $precioSinIGV )
-> setMtoIGV ( 0 )
-> setTotalImpuestos ( 0 )
-> setValorVenta ( $precioSinIGV )
-> setMtoImpVenta ( $precioSinIGV )
-> setSubTotal ( $precioSinIGV );
Respuesta Exitosa (200)
true si SUNAT aceptó el comprobante
Código de respuesta de SUNAT (generalmente “0” para aceptado)
Mensaje descriptivo de SUNAT (ej: “La Factura numero F001-349, ha sido aceptada”)
Ruta relativa al archivo CDR descargado Formato: sunat/cdr/{ruc}/R-{ruc}-{tipoDoc}-{serie}-{numero}.zip
Ejemplo de Respuesta Exitosa
{
"success" : true ,
"codigo" : "0" ,
"mensaje" : "La Factura numero F001-00000349, ha sido aceptada" ,
"cdr_url" : "sunat/cdr/20612706702/R-20612706702-01-F001-00000349.zip"
}
Errores
XML No Encontrado (400)
{
"success" : false ,
"message" : "XML no encontrado. Genere el XML primero."
}
Solución: Generar XML primero con POST /api/comprobantes/generar-xml/{ventaId}
Rechazo de SUNAT (200 con success=false)
{
"success" : false ,
"codigo" : "2324" ,
"message" : "El numero de RUC del emisor no existe"
}
Códigos de error comunes:
Código Descripción 2324 RUC del emisor no existe 2335 Numeración ya existe 2800 Serie no corresponde al tipo de documento 3035 Debe usar SOL de producción (no beta) 4000 Error en la estructura del XML 1033 Certificado revocado o inválido
Error de Conexión (500)
{
"success" : false ,
"message" : "Error al enviar el comprobante a SUNAT" ,
"codigo" : "HTTP_ERROR"
}
Ocurre cuando:
SUNAT no responde (downtime)
Timeout de conexión
Error de red
Actualización de Base de Datos
Si es aceptado:
$venta -> update ([
'estado_sunat' => '1' , // Aceptado
'hash_cpe' => $hashFromXml , // Hash del digest XML
'cdr_url' => 'sunat/cdr/...' , // Ruta del CDR
'codigo_sunat' => '0' , // Código de aceptación
'mensaje_sunat' => 'La Factura...' , // Mensaje de SUNAT
]);
Si es rechazado:
$venta -> update ([
'estado_sunat' => '3' , // Rechazado
'codigo_sunat' => $errorCode , // Código de error
'mensaje_sunat' => $errorMsg , // Mensaje de error
'intentos' => $intentos + 1 , // Incrementa contador
]);
Modo Beta vs Producción
Modo Beta (empresa.modo != ‘production’):
RUC : 20000000001
Usuario SOL : MODDATOS
Clave SOL : moddatos
Endpoint : https : //e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService
Modo Producción:
RUC : { empresa . ruc }
Usuario SOL : { empresa . user_sol }
Clave SOL : { empresa . clave_sol }
Endpoint : https : //e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService
Certificado Digital
El certificado PEM debe estar en:
storage/app/sunat/certificados/{ruc}-cert.pem
O usar certificado global de pruebas:
config ( 'sunat.certificado_prueba' )
Ejemplo de Solicitud
curl -X POST "https://facturacion.santodomingo.pe/api/comprobantes/enviar/126" \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..."
Requisitos Previos
✅ Venta creada con estado activo
✅ Empresa configurada con credenciales SOL
✅ Certificado PEM válido instalado
✅ Serie configurada en documentos_empresas
Consideraciones para Boletas
Las BOLETAS requieren un paso adicional:
Enviar a SUNAT: POST /api/comprobantes/enviar/{ventaId} ✅
Enviar Resumen Diario : POST /api/resumen-diario ⚠️
Sin el Resumen Diario, la boleta NO es válida ante SUNAT aunque el envío directo sea exitoso. Las FACTURAS solo requieren el envío directo.
Archivos Generados
XML Firmado:
storage/app/sunat/xml/{ruc}/{ruc}-{tipoDoc}-{serie}-{numero}.xml
Ejemplo: 20612706702-01-F001-00000349.xml
CDR (Constancia):
storage/app/sunat/cdr/{ruc}/R-{ruc}-{tipoDoc}-{serie}-{numero}.zip
Ejemplo: R-20612706702-01-F001-00000349.zip
El CDR contiene:
Respuesta XML firmada por SUNAT
Código de aceptación
Observaciones (si las hay)
Códigos de Tipo de Documento SUNAT
Código Descripción 01 Factura 03 Boleta de Venta 07 Nota de Crédito 08 Nota de Débito 09 Guía de Remisión Remitente 31 Guía de Remisión Transportista
Logging
Errores se registran en logs con contexto:
Log :: error ( 'SUNAT - Comprobante rechazado' , [
'venta' => $venta -> serie . '-' . $venta -> numero ,
'codigo' => $error -> getCode (),
'mensaje' => $error -> getMessage (),
]);
Código de Referencia
XML Generation: app/Services/SunatService.php:159 método generarXml()
Envío: app/Services/SunatService.php:249 método enviarComprobante()
Controller: app/Http/Controllers/ComprobanteElectronicoController.php método enviar()
Temas Relacionados