Skip to main content

Requisitos para Facturación Electrónica

Para emitir comprobantes electrónicos válidos ante SUNAT se requiere:
  1. Certificado Digital (.pem): Para firmar electrónicamente los XML
  2. Credenciales SOL: Usuario y clave para autenticación en servicios SUNAT
  3. RUC activo con el sistema de emisión electrónica habilitado
Importante: Los certificados tienen fecha de expiración (generalmente 1-2 años). Si el certificado expira, todos los envíos a SUNAT fallarán con error de firma inválida.

Obtención del Certificado Digital

Opción 1: SUNAT (Gratuito)

1

Solicitar certificado en SUNAT

  1. Ingrese a SUNAT Operaciones en Línea con Clave SOL
  2. Vaya a Comprobantes de Pago → Certificados Digitales
  3. Seleccione Solicitar Certificado Digital Gratuito
  4. Complete el formulario con datos del representante legal
  5. Descargue el certificado en formato .pfx (archivo con contraseña)
2

Convertir .pfx a .pem

El sistema requiere el certificado en formato .pem. Use OpenSSL para convertir:
# Extraer certificado (parte pública)
openssl pkcs12 -in certificado.pfx -clcerts -nokeys -out certificado.pem

# Extraer llave privada
openssl pkcs12 -in certificado.pfx -nocerts -out llave-privada.pem

# Combinar en un solo archivo .pem
cat certificado.pem llave-privada.pem > certificado-completo.pem
Al ejecutar estos comandos, se le solicitará:
  1. Import Password: Contraseña del .pfx (proporcionada por SUNAT)
  2. PEM pass phrase: Nueva contraseña para la llave privada (puede dejarla vacía para uso en servidor)

Opción 2: Entidad Certificadora (Pagado)

Certificados de mayor seguridad emitidos por:
  • RENIEC (Perú)
  • eCertiPeruana
  • Digicert
  • GlobalSign
Estos ya vienen en formato compatible con firma electrónica.

Instalación del Certificado en el Sistema

1

Ubicar directorio de certificados

Los certificados se almacenan en:
storage/app/sunat/certificados/
Esta ruta está configurada en config/sunat.php (línea 40):
'storage' => [
    'xml' => storage_path('app/sunat/xml'),
    'cdr' => storage_path('app/sunat/cdr'),
    'certificados' => storage_path('app/sunat/certificados'),
],
2

Subir el archivo .pem

Suba el certificado con el siguiente formato de nombre:
{RUC}-cert.pem
Ejemplo:
20612706702-cert.pem
El sistema buscará automáticamente el certificado de la empresa en base a su RUC.
Permisos de archivo: Asegúrese de que el archivo tenga permisos de lectura:
chmod 600 storage/app/sunat/certificados/20612706702-cert.pem
chown www-data:www-data storage/app/sunat/certificados/20612706702-cert.pem
Esto previene acceso no autorizado al certificado.
3

Verificar lectura del certificado

El servicio SunatService::getCertificate() (línea 67-81) busca el certificado en este orden:
  1. Certificado específico de la empresa:
    $certPath = storage_path("app/sunat/certificados/{$empresa->ruc}-cert.pem");
    
  2. Certificado de prueba global (fallback):
    $globalCert = config('sunat.certificado_prueba');
    
Si ninguno existe, lanza excepción:
RuntimeException: No se encontró certificado PEM para la empresa 20612706702

Configuración de Credenciales SOL

En Modo Producción

1

Acceder a configuración de empresa

Vaya a Configuración → Datos de Empresa (o edite directamente en la base de datos).
2

Completar credenciales SUNAT

{
  "ruc": "20612706702",
  "razon_social": "SANTO DOMINGO SAC",
  "user_sol": "MODDATOS",
  "clave_sol": "moddatos",
  "modo": "production"
}
Seguridad: Almacene las credenciales SOL de forma segura. Considere encriptar el campo clave_sol en la base de datos usando Laravel Encryption.

En Modo Beta (Pruebas)

Para usar el entorno de pruebas de SUNAT, configure:
{
  "modo": "beta"
}
El sistema usará automáticamente las credenciales de prueba definidas en config/sunat.php (línea 14-18):
'beta' => [
    'ruc' => '20000000001',
    'usuario_sol' => 'MODDATOS',
    'clave_sol' => 'moddatos',
],
En modo beta, el RUC real de la empresa se reemplaza por 20000000001 en los XML generados. Los documentos NO tienen validez tributaria.

Configuración Especial para Guías de Remisión (GRE)

Las guías de remisión electrónicas usan la API REST de SUNAT GRE que requiere credenciales OAuth separadas.

Obtener Client ID y Client Secret

  1. Solicite las credenciales GRE en SUNAT Operaciones en Línea
  2. Configure en la tabla empresas:
{
  "gre_client_id": "test-85e5b0ae-255c-4891-a595-0b98c65c9854",
  "gre_client_secret": "test-Hty/M6QshYvPgItX2P0+Kw=="
}

Fallback a Configuración Global

Si la empresa no tiene credenciales GRE propias, el sistema usa las configuradas en .env:
SUNAT_GRE_CLIENT_ID=test-85e5b0ae-255c-4891-a595-0b98c65c9854
SUNAT_GRE_CLIENT_SECRET=test-Hty/M6QshYvPgItX2P0+Kw==
Estas se leen en config/sunat.php (línea 31-34):
'gre' => [
    'client_id' => env('SUNAT_GRE_CLIENT_ID'),
    'client_secret' => env('SUNAT_GRE_CLIENT_SECRET'),
],

Código de Lectura de Certificado

En SunatService.php (línea 67-81):
public function getCertificate(Empresa $empresa): string
{
    // Buscar certificado específico de la empresa
    $certPath = storage_path("app/sunat/certificados/{$empresa->ruc}-cert.pem");
    
    if (file_exists($certPath)) {
        return file_get_contents($certPath);
    }
    
    // Fallback a certificado de prueba global
    $globalCert = config('sunat.certificado_prueba');
    if (file_exists($globalCert)) {
        return file_get_contents($globalCert);
    }
    
    // Error si no hay certificado
    throw new \RuntimeException(
        'No se encontró certificado PEM para la empresa ' . $empresa->ruc
    );
}

Configuración del Objeto See (Greenter)

En SunatService.php (línea 42-59), el servicio configura Greenter con el certificado:
public function getSee(Empresa $empresa, string $tipoDoc = 'facturacion'): See
{
    $see = new See();
    
    // Configurar endpoint (beta o producción)
    $endpoint = $this->getEndpoint($empresa, $tipoDoc);
    $see->setService($endpoint);
    
    // Cargar certificado
    $certificate = $this->getCertificate($empresa);
    $see->setCertificate($certificate);
    
    // Configurar credenciales SOL
    if ($empresa->modo !== 'production') {
        $beta = config('sunat.beta');
        $see->setClaveSOL($beta['ruc'], $beta['usuario_sol'], $beta['clave_sol']);
    } else {
        $see->setClaveSOL($empresa->ruc, $empresa->user_sol, $empresa->clave_sol);
    }
    
    return $see;
}

Endpoints de SUNAT

Configurados en config/sunat.php (línea 20-29):
'endpoints' => [
    'facturacion' => [
        'beta' => 'https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService',
        'production' => 'https://e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService',
    ],
    'guia' => [
        'beta' => 'https://e-beta.sunat.gob.pe/ol-ti-itemision-guia-gem-beta/billService',
        'production' => 'https://e-guiaremision.sunat.gob.pe/ol-ti-itemision-guia-gem/billService',
    ],
    'gre' => [
        'auth' => 'https://api-seguridad.sunat.gob.pe',
        'cpe' => 'https://api-cpe.sunat.gob.pe',
    ],
],

Validación del Certificado

Para verificar que el certificado está correctamente configurado:

1. Verificar fecha de expiración

openssl x509 -in storage/app/sunat/certificados/20612706702-cert.pem -noout -dates
Salida esperada:
notBefore=Jan  1 00:00:00 2025 GMT
notAfter=Dec 31 23:59:59 2026 GMT

2. Verificar sujeto del certificado

openssl x509 -in storage/app/sunat/certificados/20612706702-cert.pem -noout -subject
Salida esperada:
subject=CN=20612706702, O=SANTO DOMINGO SAC, C=PE

3. Probar firma de un XML

Genere un XML de prueba para verificar que la firma funciona:
use App\Services\SunatService;
use App\Models\Empresa;

$empresa = Empresa::find(1);
$sunatService = app(SunatService::class);

try {
    $see = $sunatService->getSee($empresa);
    echo "Certificado cargado correctamente\n";
} catch (\Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

Solución de Problemas

Causa: El archivo .pem no existe en la ruta esperada.Solución:
  1. Verifique que el archivo exista:
    ls -l storage/app/sunat/certificados/20612706702-cert.pem
    
  2. Verifique que el nombre sea correcto (RUC exacto + -cert.pem)
  3. Verifique permisos de lectura:
    chmod 600 storage/app/sunat/certificados/20612706702-cert.pem
    
Causa: El certificado no coincide con el RUC o está mal formado.Solución:
  1. Verifique que el certificado corresponda al RUC de la empresa:
    openssl x509 -in certificado.pem -noout -subject
    
    El CN debe ser el RUC correcto.
  2. Verifique que el certificado no haya expirado:
    openssl x509 -in certificado.pem -noout -dates
    
  3. Asegúrese de que el .pem incluya tanto el certificado como la llave privada:
    grep -c "BEGIN CERTIFICATE" certificado.pem  # Debe mostrar 1
    grep -c "BEGIN PRIVATE KEY" certificado.pem  # Debe mostrar 1
    
Causa: El certificado fue revocado por SUNAT o la entidad emisora.Solución:
  1. Verifique el estado en SUNAT Operaciones en Línea
  2. Solicite un nuevo certificado
  3. Actualice el archivo .pem en el servidor
Causa: Problema de conexión al endpoint de SUNAT.Solución:
  1. Verifique que el servidor tenga acceso a internet
  2. Verifique que el endpoint esté correcto en config/sunat.php
  3. Pruebe la conectividad:
    curl -I https://e-factura.sunat.gob.pe
    

Renovación del Certificado

Los certificados vencen. Para renovar:
1

Solicitar nuevo certificado

Antes de que expire el actual, solicite uno nuevo en SUNAT.
2

Convertir a .pem

Siga el proceso de conversión .pfx → .pem descrito arriba.
3

Reemplazar archivo

# Backup del certificado actual
cp storage/app/sunat/certificados/20612706702-cert.pem \
   storage/app/sunat/certificados/20612706702-cert-OLD.pem

# Subir nuevo certificado
cp nuevo-certificado.pem storage/app/sunat/certificados/20612706702-cert.pem
4

Probar envío

Emita un documento de prueba para verificar que el nuevo certificado funciona.
Plazo crítico: Renueve el certificado al menos 15 días antes del vencimiento para evitar interrupciones en la facturación.

Próximos Pasos

Build docs developers (and LLMs) love