Certificado Digital
El certificado digital es obligatorio para firmar electrónicamente los comprobantes XML antes de enviarlos a SUNAT.
Obtener Certificado Digital
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.
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
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).
Verificar formato actual
# Ver información del certificado
openssl pkcs12 -info -in certificado.pfx -noout
Extraer certificado
openssl pkcs12 -in certificado.pfx -clcerts -nokeys -out cert.pem
Te pedirá la contraseña del archivo .pfx.
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.
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.
Para producción , nombra el certificado con el RUC de tu empresa: storage/app/sunat/certificados/ {TU_RUC} -cert.pem
Ejemplo: storage/app/sunat/certificados/20612706702-cert.pem
El sistema busca primero el certificado específico del RUC. Si no existe, usa el global.
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
Ingresar a SUNAT
Accede a https://www.sunat.gob.pe
Ingresa con tu Clave SOL (representante legal)
Ve a SUNAT Operaciones en Línea → Factura Electrónica
Registrar usuario secundario (opcional)
Puedes crear un usuario secundario específico para el sistema:
SUNAT Operaciones → Usuarios y Contraseñas
Nuevo Usuario Secundario
Asignar permisos de Emisor Electrónico
El usuario secundario tiene formato: {RUC}{CODIGO} (ej: 20612706702ADMINFE)
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:
Desde Panel Admin
Desde Tinker
Desde SQL
Ingresa al sistema como administrador
Configuración → Empresas
Edita la empresa
Completa:
RUC : 20612706702
Usuario SOL : 20612706702ADMINFE
Clave SOL : tu_clave_segura
Modo : production
$empresa = App\Models\ Empresa :: find ( 1 );
$empresa -> ruc = '20612706702' ;
$empresa -> user_sol = '20612706702ADMINFE' ;
$empresa -> clave_sol = bcrypt ( 'tu_clave_sol' );
$empresa -> modo = 'production' ;
$empresa -> save ();
UPDATE empresas SET
ruc = '20612706702' ,
user_sol = '20612706702ADMINFE' ,
clave_sol = '$2y$12$...' , -- Usa bcrypt
modo = 'production'
WHERE id_empresa = 1 ;
Modo Beta vs Producción
Beta (Pruebas)
Production
Cuando modo = 'beta', el sistema usa automáticamente las credenciales de prueba de SUNAT: '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.
Cuando modo = 'production', usa las credenciales reales de la BD: app/Services/SunatService.php
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 );
}
Características:
Usa tus credenciales reales
Envía a e-factura.sunat.gob.pe
Documentos tienen validez legal
Consume tus series oficiales
CDRs oficiales de SUNAT
Solo cambia a producción cuando hayas probado exhaustivamente en beta.
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
Acceder al portal GRE
Ingresa a https://www.sunat.gob.pe con Clave SOL
Factura Electrónica → Guía de Remisión Electrónica
Configuración → Credenciales de API
Generar credenciales OAuth2
Click en Generar Cliente OAuth2
Completa los datos:
Nombre de aplicación: “Sistema Facturación Santo Domingo”
URL de callback (no usado por este sistema)
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.
Configurar en .env
SUNAT_GRE_CLIENT_ID = abc123def456...
SUNAT_GRE_CLIENT_SECRET = xyz789uvw012...
Endpoints GRE
El sistema usa diferentes endpoints según el ambiente:
'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
$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
Error: No se encontró certificado PEM
RuntimeException: No se encontró certificado PEM para la empresa 20612706702
Soluciones:
Verifica que el archivo existe:
ls -la storage/app/sunat/certificados/
Verifica el nombre:
Beta: cert.pem
Producción: {RUC}-cert.pem
Verifica permisos:
chmod 600 storage/app/sunat/certificados/ * .pem
Error: El certificado no contiene clave privada
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
Error de autenticación SOL
SoapFault: El Usuario y/o Contraseña son incorrectos
Soluciones:
Verifica que el usuario SOL tiene formato correcto: {RUC}{CODIGO}
Verifica que la clave es correcta
Verifica que el usuario tiene permisos de Emisor Electrónico
Si estás en beta, verifica que modo = 'beta' en la BD
Unauthorized: invalid_client
Soluciones:
Verifica que las credenciales GRE están en .env:
Regenera las credenciales en el portal SUNAT
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:
No subir a Git
Añade al .gitignore: storage/app/sunat/certificados/*.pem
storage/app/sunat/certificados/*.pfx
.env
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
Rotar credenciales
Cambia las credenciales SOL cada 6 meses:
Crea nuevo usuario SOL en SUNAT
Actualiza en la BD
Elimina usuario antiguo
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