Skip to main content

Tipos de Errores

Los errores de SUNAT se clasifican en:
  • Serie 2000: Errores de validación de datos
  • Serie 3000: Errores de certificados y autenticación
  • Serie 4000: Errores de formato XML
  • Serie 5000: Errores de lógica de negocio
Los errores aparecen en el campo mensaje_sunat de la venta y se registran en storage/logs/laravel.log.

Errores Comunes (Serie 2000)

2010: El RUC del cliente no existe

Mensaje completo:
2010 - El RUC/DNI del receptor no existe o no se encuentra afecto
Causa:
  • El RUC o DNI ingresado no está en el padrón de SUNAT
  • El documento tiene dígitos incorrectos
Solución:
  1. Verifique el número de documento en la consulta RUC de SUNAT:
    https://e-consultaruc.sunat.gob.pe/cl-ti-itmrconsruc/FrameCriterioBusquedaWeb.jsp
    
  2. Si el RUC es correcto pero SUNAT no lo reconoce, use tipo de documento 0 (sin documento) para ventas menores:
    $client->setTipoDoc('0')->setNumDoc('00000000');
    
  3. Corrija el documento en el cliente y regenere el XML:
    PATCH /api/clientes/{id}
    { "documento": "20123456789" }
    
    POST /api/ventas/{id}/generar-xml
    POST /api/ventas/{id}/enviar-sunat
    
Aceptado con observaciones: Si el comprobante fue aceptado con código 0001 y este mensaje como observación, el comprobante es VÁLIDO. Solo es una advertencia.

2024: El tipo de documento del cliente no es válido

Mensaje completo:
2024 - El tipo de documento del cliente no corresponde al número de documento
Causa:
  • RUC (11 dígitos) con tipo de documento DNI (1)
  • DNI (8 dígitos) con tipo de documento RUC (6)
Solución: En SunatService::buildClient() (línea 127-157) se detecta automáticamente:
if (strlen($documento) === 11) {
    $tipoDoc = '6'; // RUC
} elseif (strlen($documento) === 8) {
    $tipoDoc = '1'; // DNI
} else {
    $tipoDoc = '4'; // Carnet de extranjería
}
Verifique que el campo tipo_doc en la tabla clientes sea correcto o null (para detección automática).

2100: El monto no coincide con la suma de los ítems

Mensaje completo:
2100 - La suma de los valores de venta por item no coincide con el importe total
Causa:
  • Error de redondeo en el cálculo de IGV
  • Descuentos aplicados incorrectamente
Solución: Verifique el cálculo en SunatService::buildSaleDetails() (línea 956-999):
$valorUnitario = round($precio / ($igvRate + 1), 2);  // Base imponible por unidad
$valorVenta = round($precio * $cantidad / ($igvRate + 1), 2);  // Base imponible total
$igvItem = round($precio * $cantidad / ($igvRate + 1) * $igvRate, 2);  // IGV del item

// La suma debe ser:
// sum($valorVenta) = $invoice->getMtoOperGravadas()
// sum($igvItem) = $invoice->getMtoIGV()
// sum($precio * $cantidad) = $invoice->getMtoImpVenta()
Si hay descrepancia:
  1. Recalcule los montos en la venta
  2. Regenere el XML
  3. Reenvíe

2335: El certificado no es válido

Mensaje completo:
2335 - El certificado digital no es válido o no está vigente
Causa:
  • Certificado .pem expirado
  • Certificado no corresponde al RUC
  • Certificado mal formado
Solución:
  1. Verificar vigencia:
    openssl x509 -in storage/app/sunat/certificados/20612706702-cert.pem -noout -dates
    
    Salida:
    notBefore=Jan  1 00:00:00 2025 GMT
    notAfter=Dec 31 23:59:59 2026 GMT  ← Verificar que no haya pasado
    
  2. Verificar RUC del certificado:
    openssl x509 -in storage/app/sunat/certificados/20612706702-cert.pem -noout -subject
    
    Salida:
    subject=CN=20612706702  ← Debe coincidir con el RUC de la empresa
    
  3. Si expiró: Renueve el certificado y reemplace el archivo .pem Ver: Configurar Certificados

2800: El comprobante ya fue informado

Mensaje completo:
2800 - El comprobante {serie}-{numero} ya fue informado anteriormente
Causa:
  • Se intentó enviar el mismo comprobante dos veces
  • Ya existe en SUNAT con el mismo RUC, serie y número
Solución:
  1. Si fue enviado por error: No requiere acción. El comprobante ya está aceptado.
  2. Si necesita modificar el comprobante:
    • NO se puede modificar un comprobante ya aceptado
    • Debe emitir una Nota de Crédito para anularlo
    • Luego emitir un nuevo comprobante con número diferente
  3. Verificar en la base de datos:
    SELECT * FROM ventas 
    WHERE serie = 'F001' AND numero = 45 AND estado_sunat = '1';
    
    Si ya está marcado como aceptado, actualice el estado en el sistema sin reenviar.

Errores de Certificado (Serie 3000)

3001: La firma digital es inválida

Mensaje completo:
3001 - La firma digital del documento es inválida
Causa:
  • El certificado no incluye la llave privada
  • El archivo .pem está corrupto
  • El XML fue modificado después de firmarse
Solución:
  1. Verificar contenido del .pem:
    grep -c "BEGIN CERTIFICATE" storage/app/sunat/certificados/20612706702-cert.pem
    # Debe mostrar: 1
    
    grep -c "BEGIN PRIVATE KEY" storage/app/sunat/certificados/20612706702-cert.pem
    # Debe mostrar: 1
    
  2. Si falta la llave privada: Regenere el .pem desde el .pfx:
    openssl pkcs12 -in certificado.pfx -clcerts -nokeys -out cert.pem
    openssl pkcs12 -in certificado.pfx -nocerts -out key.pem
    cat cert.pem key.pem > certificado-completo.pem
    
  3. Reemplace el archivo y reintente

3100: Error de autenticación SOL

Mensaje completo:
3100 - Usuario o contraseña SOL incorrectos
Causa:
  • Credenciales user_sol o clave_sol incorrectas
  • Cuenta SOL bloqueada por intentos fallidos
Solución:
  1. Verifique credenciales en la tabla empresas:
    SELECT ruc, user_sol, clave_sol FROM empresas WHERE id_empresa = 1;
    
  2. Verifique en SUNAT Operaciones en Línea:
    • Intente ingresar con las mismas credenciales
    • Si está bloqueada, restablezca la clave
  3. En modo beta: Verifique que use las credenciales de prueba:
    // config/sunat.php
    'beta' => [
        'ruc' => '20000000001',
        'usuario_sol' => 'MODDATOS',
        'clave_sol' => 'moddatos',
    ],
    

Errores de Formato XML (Serie 4000)

4000: El formato del XML es inválido

Mensaje completo:
4000 - El formato del archivo XML es incorrecto
Causa:
  • XML mal formado (tags sin cerrar, caracteres especiales sin escapar)
  • Estructura no cumple con el estándar UBL 2.1
Solución:
  1. Validar el XML generado:
    xmllint --noout storage/app/sunat/xml/{ruc}/{archivo}.xml
    
    Si hay errores, mostrará la línea específica.
  2. Caracteres especiales en descripciones:
    // Escapar caracteres especiales
    $descripcion = htmlspecialchars($producto->descripcion, ENT_XML1, 'UTF-8');
    
  3. Validar contra el XSD de SUNAT: Descargue los esquemas XSD desde SUNAT y valide:
    xmllint --schema UBL-Invoice-2.1.xsd --noout archivo.xml
    

4100: Campo obligatorio faltante

Mensaje completo:
4100 - El campo {nombre_campo} es obligatorio
Causa:
  • Falta información requerida (ej: dirección del cliente, motivo de nota de crédito)
Solución: Identifique el campo faltante y complételo:
// Ejemplo: Dirección del cliente
$client = new Client();
$client->setTipoDoc('6')
       ->setNumDoc('20123456789')
       ->setRznSocial('CLIENTE SAC')
       ->setAddress((new Address())  // ← Obligatorio para facturas
           ->setDireccion('Av. Principal 123'));
Reglas por tipo de documento:
  • Facturas: Dirección del cliente obligatoria
  • Boletas: Dirección opcional (solo si monto > S/. 700)
  • Notas de Crédito: Motivo obligatorio (código SUNAT + descripción)

Errores de Lógica (Serie 5000)

5000: Operación no permitida

Mensaje completo:
5000 - La operación no está permitida en este momento
Causa:
  • Horario no permitido (comunicaciones de baja tienen horarios restringidos)
  • RUC suspendido temporalmente
  • Mantenimiento de SUNAT
Solución:
  1. Verificar horarios:
    • Resúmenes diarios: 24/7
    • Comunicaciones de baja: No entre 22:00 - 01:00
  2. Verificar estado del RUC: Consulte en SUNAT si el RUC está activo y habilitado para emitir comprobantes.
  3. Reintentar más tarde

Errores de Guías de Remisión (GRE)

Error: “El ubigeo de partida no es válido”

Causa:
  • Código de ubigeo incorrecto (debe ser 6 dígitos)
Solución: Verifique el ubigeo en SunatService::generarGuiaRemisionXml() (línea 590-591):
$shipment->setPartida(new Direction(
    $guia->ubigeo_partida,  // Debe ser formato: 150101 (Lima-Lima-Lima)
    $guia->dir_partida
));
Consulte ubigeos válidos: https://www.inei.gob.pe/media/MenuRecursivo/publicaciones_digitales/Est/Lib1541/directorio.pdf

Error: “Datos del conductor incompletos”

Causa:
  • Falta licencia de conducir o tipo de documento
Solución: Para modalidad de transporte privado (02), todos estos campos son obligatorios:
$driver = (new Driver())
    ->setTipo('Principal')
    ->setTipoDoc('1')  // DNI
    ->setNroDoc('12345678')
    ->setNombres('Juan')
    ->setApellidos('Pérez López')
    ->setLicencia('Q12345678');  // ← Obligatorio

Reintentos y Recuperación

Estrategia de Reintentos

// En un Job de cola
class ReintentarEnvioSunat implements ShouldQueue
{
    public $tries = 3;  // Máximo 3 intentos
    public $backoff = [60, 300, 900];  // 1min, 5min, 15min
    
    public function handle(SunatService $sunatService)
    {
        $venta = Venta::find($this->ventaId);
        
        try {
            $resultado = $sunatService->enviarComprobante($venta);
            
            if ($resultado['success']) {
                // Exitoso - no reintentar
                $this->delete();
            } else {
                // Analizar código de error
                $codigo = $resultado['codigo'];
                
                if (in_array($codigo, ['2335', '3001', '3100'])) {
                    // Errores críticos - no reintentar
                    $this->fail(new \Exception($resultado['message']));
                } else {
                    // Reintentable - dejar que el job se reintente
                    throw new \Exception($resultado['message']);
                }
            }
        } catch (\Exception $e) {
            if ($this->attempts() >= $this->tries) {
                // Último intento fallido - notificar admin
                \Log::error("Fallo definitivo en envío SUNAT: Venta {$venta->id_venta}");
                // Enviar email/notificación
            }
            
            throw $e;  // Disparar reintento
        }
    }
}

Panel de Monitoreo de Errores

Consulte comprobantes con errores:
GET /api/ventas/con-errores-sunat
Authorization: Bearer {token}
Respuesta:
{
  "success": true,
  "data": [
    {
      "id_venta": 45,
      "numero_completo": "F001-00000045",
      "estado_sunat": "3",
      "codigo_sunat": "2010",
      "mensaje_sunat": "El RUC del cliente no existe",
      "intentos": 2,
      "fecha_ultimo_intento": "2026-03-06T14:30:00"
    }
  ]
}

Logs y Depuración

Habilitar Logging Detallado

En .env:
LOG_LEVEL=debug
En SunatService.php, los errores se registran automáticamente:
Log::error('SUNAT - Comprobante rechazado', [
    'venta' => $venta->serie . '-' . $venta->numero,
    'codigo' => $error->getCode(),
    'mensaje' => $error->getMessage(),
]);

Revisar Logs

tail -f storage/logs/laravel.log | grep "SUNAT"
Ejemplo de entrada:
[2026-03-06 14:30:45] local.ERROR: SUNAT - Comprobante rechazado
{
  "venta": "F001-00000045",
  "codigo": "2010",
  "mensaje": "El RUC del cliente no existe"
}

Recursos Adicionales

Documentación Oficial de SUNAT

Contacto SUNAT

  • Mesa de Ayuda: 0-801-12-100
  • Email: [email protected]
  • Chat en línea: Disponible en SUNAT Virtual

Checklist de Diagnóstico

Antes de contactar soporte:
  • Verifique que el certificado .pem exista y no haya expirado
  • Verifique que las credenciales SOL sean correctas
  • Revise el mensaje de error completo en storage/logs/laravel.log
  • Valide el XML generado con xmllint
  • Verifique conectividad a https://e-factura.sunat.gob.pe
  • Consulte el estado del RUC en SUNAT
  • Revise que los datos del cliente sean correctos
  • Verifique los montos y cálculos del comprobante

Próximos Pasos

Build docs developers (and LLMs) love