Skip to main content

Certificado Digital

El certificado digital es obligatorio para firmar electrónicamente los comprobantes XML antes de enviarlos a SUNAT.

Obtener Certificado Digital

1

Adquirir certificado

Obtén un certificado digital de una entidad certificadora autorizada por INDECOPI/SUNAT:Proveedores en Perú:
El costo promedio es S/ 200 - S/ 400 anuales. El proceso demora 3-7 días hábiles.
2

Documentos requeridos

Para obtener el certificado necesitas:
  • Ficha RUC actualizada
  • DNI del representante legal
  • Poder vigente (si aplica)
  • Correo electrónico corporativo
  • Número de celular
3

Recibir certificado

El certificado se entrega en formato .pfx o .p12 con una contraseña de protección.
Guarda la contraseña del certificado. La necesitarás para convertirlo a formato PEM.

Convertir Certificado a PEM

El sistema requiere el certificado en formato PEM (texto plano).
1

Verificar formato actual

# Ver información del certificado
openssl pkcs12 -info -in certificado.pfx -noout
2

Extraer certificado

openssl pkcs12 -in certificado.pfx -clcerts -nokeys -out cert.pem
Te pedirá la contraseña del archivo .pfx.
3

Extraer clave privada

openssl pkcs12 -in certificado.pfx -nocerts -nodes -out key.pem
El flag -nodes (no DES) guarda la clave sin encriptar. Esto es necesario para que Laravel pueda usarla automáticamente.
4

Combinar en un solo archivo

cat cert.pem key.pem > certificado-completo.pem
El archivo final debe contener:
-----BEGIN CERTIFICATE-----
MIIFfTCCBGWgAwIBAgIQD...
...
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0...
...
-----END PRIVATE KEY-----

Instalar Certificado en el Sistema

Para pruebas en beta, coloca el certificado como:
storage/app/sunat/certificados/cert.pem
Este certificado se usa para todas las empresas en modo beta con el RUC de prueba 20000000001.
Permisos del archivo:
chmod 600 storage/app/sunat/certificados/*.pem
chown www-data:www-data storage/app/sunat/certificados/*.pem
Seguridad: El certificado contiene tu clave privada. NUNCA lo subas a repositorios públicos o lo compartas.

Verificar Certificado

# Ver información del certificado
openssl x509 -in cert.pem -text -noout

# Verificar fecha de expiración
openssl x509 -in cert.pem -noout -enddate

# Verificar que tiene clave privada
grep "BEGIN PRIVATE KEY" certificado-completo.pem && echo "OK" || echo "Falta clave privada"

Credenciales SOL

Las credenciales SOL (SUNAT Operaciones en Línea) se usan para autenticación en los servicios SOAP de facturación.

Obtener Credenciales SOL

1

Ingresar a SUNAT

  1. Accede a https://www.sunat.gob.pe
  2. Ingresa con tu Clave SOL (representante legal)
  3. Ve a SUNAT Operaciones en LíneaFactura Electrónica
2

Registrar usuario secundario (opcional)

Puedes crear un usuario secundario específico para el sistema:
  1. SUNAT OperacionesUsuarios y Contraseñas
  2. Nuevo Usuario Secundario
  3. Asignar permisos de Emisor Electrónico
El usuario secundario tiene formato: {RUC}{CODIGO} (ej: 20612706702ADMINFE)
3

Anotar credenciales

Necesitas:
  • RUC: 11 dígitos
  • Usuario SOL: RUC + código de usuario
  • Clave SOL: Contraseña del usuario
No uses tu usuario principal. Crea un usuario secundario dedicado para el sistema.

Configurar Credenciales en BD

Las credenciales SOL se guardan encriptadas en la tabla empresas:
  1. Ingresa al sistema como administrador
  2. ConfiguraciónEmpresas
  3. Edita la empresa
  4. Completa:
    • RUC: 20612706702
    • Usuario SOL: 20612706702ADMINFE
    • Clave SOL: tu_clave_segura
    • Modo: production

Modo Beta vs Producción

Cuando modo = 'beta', el sistema usa automáticamente las credenciales de prueba de SUNAT:
config/sunat.php
'beta' => [
    'ruc' => '20000000001',
    'usuario_sol' => 'MODDATOS',
    'clave_sol' => 'moddatos',
]
Características:
  • No requiere credenciales reales
  • Envía a e-beta.sunat.gob.pe
  • Documentos NO tienen validez legal
  • Puedes probar integración sin afectar producción
  • CDRs de prueba
Perfecto para desarrollo y testing.

Credenciales GRE (Guías de Remisión)

La API de Guías de Remisión Electrónica (GRE) usa OAuth2 para autenticación, con credenciales separadas de SOL.

Obtener Credenciales GRE

1

Acceder al portal GRE

  1. Ingresa a https://www.sunat.gob.pe con Clave SOL
  2. Factura ElectrónicaGuía de Remisión Electrónica
  3. ConfiguraciónCredenciales de API
2

Generar credenciales OAuth2

  1. Click en Generar Cliente OAuth2
  2. Completa los datos:
    • Nombre de aplicación: “Sistema Facturación Santo Domingo”
    • URL de callback (no usado por este sistema)
  3. Guarda las credenciales generadas:
    • Client ID: 32 caracteres alfanuméricos
    • Client Secret: 64 caracteres alfanuméricos
El Client Secret solo se muestra una vez. Guárdalo en un lugar seguro.
3

Configurar en .env

.env
SUNAT_GRE_CLIENT_ID=abc123def456...
SUNAT_GRE_CLIENT_SECRET=xyz789uvw012...

Endpoints GRE

El sistema usa diferentes endpoints según el ambiente:
config/sunat.php
'gre' => [
    // Testing
    'auth' => 'https://api-seguridad-test.sunat.gob.pe/v1',
    'cpe' => 'https://api-cpe-test.sunat.gob.pe/v1',
    
    // Production (cambiar manualmente en código si es necesario)
    // 'auth' => 'https://api-seguridad.sunat.gob.pe/v1',
    // 'cpe' => 'https://api-cpe.sunat.gob.pe/v1',
    
    'client_id' => env('SUNAT_GRE_CLIENT_ID'),
    'client_secret' => env('SUNAT_GRE_CLIENT_SECRET'),
]
Actualmente el sistema usa endpoints de test. Para producción, actualiza las URLs en config/sunat.php.

Flujo de Autenticación GRE

Código en SunatService:
app/Services/SunatService.php
public function enviarGuiaRemision(GuiaRemision $guia): array
{
    // 1. Obtener token OAuth2
    $authApi = new AuthApi();
    $authApi->setClient(new GuzzleClient(), new Configuration([
        'host' => config('sunat.endpoints.gre.auth')
    ]));
    
    $token = $authApi->getToken(
        config('sunat.endpoints.gre.client_id'),
        config('sunat.endpoints.gre.client_secret')
    );
    
    // 2. Enviar comprobante
    $cpeApi = new CpeApi();
    $cpeApi->getConfig()->setAccessToken($token->getAccessToken());
    
    $result = $cpeApi->enviarComprobante(
        $empresa->ruc,
        new CpeDocumentArchivo([
            'archivo' => base64_encode($xml),
            'nombre_archivo' => "{$ruc}-09-{$serie}-{$numero}.xml"
        ])
    );
    
    return [
        'ticket' => $result->getTicket(),
        'success' => true
    ];
}

Configuración Completa

Estructura de Archivos

storage/app/sunat/
├── certificados/
│   ├── cert.pem                    # Certificado global (beta)
│   └── 20612706702-cert.pem        # Certificado empresa (producción)
├── xml/
│   └── 20612706702/
│       ├── 20612706702-01-F001-00000001.xml
│       ├── 20612706702-03-B001-00000001.xml
│       └── 20612706702-09-T001-00000001.xml
└── cdr/
    └── 20612706702/
        ├── R-20612706702-01-F001-00000001.zip
        ├── R-20612706702-03-B001-00000001.zip
        └── R-20612706702-09-T001-00000001.zip

Permisos Requeridos

# Dar permisos de escritura
chmod -R 775 storage/app/sunat

# Owner correcto (usuario del servidor web)
chown -R www-data:www-data storage/app/sunat

# Solo lectura para certificados
chmod 600 storage/app/sunat/certificados/*.pem

Verificar Configuración

Test de Certificado

php artisan tinker
$empresa = App\Models\Empresa::find(1);
$service = new App\Services\SunatService();

try {
    $cert = $service->getCertificate($empresa);
    echo "Certificado cargado: " . strlen($cert) . " bytes\n";
    
    // Verificar que tiene clave privada
    if (strpos($cert, 'BEGIN PRIVATE KEY') !== false) {
        echo "Clave privada encontrada \u2705\n";
    } else {
        echo "Falta clave privada \u274c\n";
    }
} catch (\Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

Test de Credenciales SOL

$see = $service->getSee($empresa, 'facturacion');
echo "Endpoint: " . $service->getEndpoint($empresa, 'facturacion') . "\n";
echo "Modo: " . $empresa->modo . "\n";

Test de Credenciales GRE

$clientId = config('sunat.endpoints.gre.client_id');
$clientSecret = config('sunat.endpoints.gre.client_secret');

echo "Client ID: " . substr($clientId, 0, 8) . "...\n";
echo "Client Secret: " . (strlen($clientSecret) > 0 ? 'Configurado ✅' : 'Falta ❌') . "\n";

Troubleshooting

RuntimeException: No se encontró certificado PEM para la empresa 20612706702
Soluciones:
  1. Verifica que el archivo existe:
    ls -la storage/app/sunat/certificados/
    
  2. Verifica el nombre:
    • Beta: cert.pem
    • Producción: {RUC}-cert.pem
  3. Verifica permisos:
    chmod 600 storage/app/sunat/certificados/*.pem
    
RuntimeException: The certificate is not signed
Solución: El archivo PEM debe contener tanto el certificado como la clave privada:
# Verificar contenido
grep -c "BEGIN CERTIFICATE" cert.pem  # Debe ser 1
grep -c "BEGIN PRIVATE KEY" cert.pem  # Debe ser 1

# Si falta la clave, combinar:
cat cert.pem key.pem > certificado-completo.pem
SoapFault: El Usuario y/o Contraseña son incorrectos
Soluciones:
  1. Verifica que el usuario SOL tiene formato correcto: {RUC}{CODIGO}
  2. Verifica que la clave es correcta
  3. Verifica que el usuario tiene permisos de Emisor Electrónico
  4. Si estás en beta, verifica que modo = 'beta' en la BD
Unauthorized: invalid_client
Soluciones:
  1. Verifica que las credenciales GRE están en .env:
    grep SUNAT_GRE .env
    
  2. Regenera las credenciales en el portal SUNAT
  3. Verifica que no hay espacios extra en .env
RuntimeException: Certificate expired
Solución: Renueva el certificado digital:
# Verificar fecha de expiración
openssl x509 -in cert.pem -noout -enddate
Los certificados suelen vencer cada 1-2 años. Contacta a tu proveedor.

Seguridad

IMPORTANTE: Los certificados y credenciales son datos sensibles. Sigue estas prácticas:
1

No subir a Git

Añade al .gitignore:
storage/app/sunat/certificados/*.pem
storage/app/sunat/certificados/*.pfx
.env
2

Encriptar en backups

Si haces backups, encripta los certificados:
tar -czf backup.tar.gz storage/app/sunat/
gpg -c backup.tar.gz  # Pide contraseña
rm backup.tar.gz
3

Rotar credenciales

Cambia las credenciales SOL cada 6 meses:
  1. Crea nuevo usuario SOL en SUNAT
  2. Actualiza en la BD
  3. Elimina usuario antiguo
4

Monitorear expiración

Configura alertas para certificados próximos a expirar:
# Script de monitoreo
openssl x509 -in cert.pem -noout -checkend $((30*86400))
if [ $? -eq 1 ]; then
    echo "Certificado expira en menos de 30 días"
    # Enviar alerta
fi

Próximos Pasos

Inicio Rápido

Completa la instalación y prueba el sistema

Arquitectura

Comprende cómo funciona la integración SUNAT

Configuración

Configuración detallada del sistema

API Reference

Endpoints de facturación electrónica

Build docs developers (and LLMs) love