Visión General
El sistema se autentica con SUNAT mediante credenciales SOL y certificados digitales PEM para firmar documentos XML.
El sistema soporta modo Beta (ambiente de pruebas) y Producción con cambio automático de credenciales.
Credenciales SOL
Usuario SOL (Clave SOL)
SUNAT asigna a cada empresa:
RUC : Número de identificación tributaria (11 dígitos)
Usuario SOL : Generalmente el RUC + usuario secundario
Clave SOL : Contraseña del sistema
Configuración en el Sistema
'beta' => [
'ruc' => '20000000001' ,
'usuario_sol' => 'MODDATOS' ,
'clave_sol' => 'moddatos' ,
],
Las credenciales de prueba MODDATOS/moddatos son públicas y funcionan en el ambiente Beta de SUNAT.
Certificado Digital
Ubicación del Certificado
Los certificados PEM se almacenan en:
storage/app/sunat/certificados/
├── {RUC}-cert.pem # Certificado por empresa
└── cert.pem # Certificado global de prueba
Carga del Certificado
public function getCertificate ( Empresa $empresa ) : string
{
$certPath = storage_path ( "app/sunat/certificados/{ $empresa -> ruc }-cert.pem" );
if ( file_exists ( $certPath )) {
return file_get_contents ( $certPath );
}
// Fallback a certificado global de prueba
$globalCert = config ( 'sunat.certificado_prueba' );
if ( file_exists ( $globalCert )) {
return file_get_contents ( $globalCert );
}
throw new \RuntimeException ( 'No se encontró certificado PEM para la empresa ' . $empresa -> ruc );
}
En producción, cada empresa DEBE tener su propio certificado digital válido emitido por una entidad certificadora autorizada por SUNAT.
Inicialización de Greenter
La clase SunatService configura el cliente Greenter:
public function getSee ( Empresa $empresa , string $tipoDoc = 'facturacion' ) : See
{
$see = new See ();
// Configurar endpoint según tipo de documento y modo
$endpoint = $this -> getEndpoint ( $empresa , $tipoDoc );
$see -> setService ( $endpoint );
// Cargar certificado
$certificate = $this -> getCertificate ( $empresa );
$see -> setCertificate ( $certificate );
// Configurar credenciales según modo
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 SUNAT
Facturación (SOAP)
'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' ,
],
],
Usado para:
Facturas (01)
Boletas (03)
Notas de Crédito (07)
Notas de Débito (08)
Resumen Diario
Comunicación de Baja
Guías de Remisión (SOAP Legacy)
'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' ,
],
Usado para guías de remisión en formato antiguo (deprecado).
GRE (Guías de Remisión Electrónica) REST API
'gre' => [
'auth' => 'https://api-seguridad-test.sunat.gob.pe/v1' ,
'cpe' => 'https://api-cpe-test.sunat.gob.pe/v1' ,
'client_id' => env ( 'SUNAT_GRE_CLIENT_ID' , 'TU_ID_DE_PRUEBA' ),
'client_secret' => env ( 'SUNAT_GRE_CLIENT_SECRET' , 'TU_SECRET_DE_PRUEBA' ),
],
Usado para guías de remisión en formato nuevo (2022+) mediante API REST.
La GRE requiere credenciales OAuth adicionales (client_id y client_secret) obtenidas del portal SUNAT.
Selección de Endpoint
public function getEndpoint ( Empresa $empresa , string $tipoDoc = 'facturacion' ) : string
{
$modo = $empresa -> modo !== 'production' ? 'beta' : 'production' ;
return config ( "sunat.endpoints.{ $tipoDoc }.{ $modo }" );
}
Tipos de documento soportados:
facturacion: Facturas, boletas, notas
guia: Guías de remisión SOAP
gre: Guías de remisión REST
Autenticación GRE (OAuth 2.0)
Para envío de guías electrónicas modernas:
$authConfig = new Configuration ();
$authConfig -> setHost ( config ( 'sunat.endpoints.gre.auth' ));
$authApi = new AuthApi ( null , $authConfig );
$username = $empresa -> modo !== 'production'
? config ( 'sunat.beta.ruc' ) . config ( 'sunat.beta.usuario_sol' )
: $empresa -> ruc . $empresa -> user_sol ;
$password = $empresa -> modo !== 'production'
? config ( 'sunat.beta.clave_sol' )
: $empresa -> clave_sol ;
$token = $authApi -> getToken (
'password' ,
'https://api-cpe.sunat.gob.pe' ,
$empresa -> gre_client_id ?: config ( 'sunat.endpoints.gre.client_id' ),
$empresa -> gre_client_secret ?: config ( 'sunat.endpoints.gre.client_secret' ),
$username ,
$password
);
$cpeConfig = new Configuration ();
$cpeConfig -> setHost ( config ( 'sunat.endpoints.gre.cpe' ));
$cpeConfig -> setAccessToken ( $token -> getAccessToken ());
El token OAuth tiene una validez corta (generalmente 5-10 minutos). Se debe solicitar un nuevo token para cada envío.
Configuración de Empresa
En la tabla empresas:
CREATE TABLE empresas (
id_empresa INT PRIMARY KEY ,
ruc VARCHAR ( 11 ) NOT NULL ,
razon_social VARCHAR ( 255 ),
user_sol VARCHAR ( 50 ), -- Usuario SOL (sin RUC)
clave_sol VARCHAR ( 255 ), -- Contraseña encriptada
gre_client_id VARCHAR ( 100 ), -- OAuth Client ID para GRE
gre_client_secret VARCHAR ( 255 ), -- OAuth Secret para GRE
modo VARCHAR ( 20 ) DEFAULT 'beta' -- 'beta' o 'production'
);
Ejemplo de inserción:
Empresa :: create ([
'ruc' => '20612706702' ,
'razon_social' => 'SANTO DOMINGO SAC' ,
'user_sol' => 'USUARIO01' , // Solo el usuario, sin RUC
'clave_sol' => bcrypt ( 'mi_clave_segura' ),
'modo' => 'beta' ,
]);
NUNCA almacene la clave_sol en texto plano. Use bcrypt() o Hash::make() para encriptarla.
Cambio de Modo Beta a Producción
Para activar el modo producción:
$empresa -> update ([ 'modo' => 'production' ]);
El sistema automáticamente:
Cambiará al endpoint de producción
Usará el RUC y credenciales reales de la empresa
Requerirá certificado digital válido en storage/app/sunat/certificados/{RUC}-cert.pem
Obtención de Certificado Digital
Pasos para obtener certificado:
Solicitar certificado
Contactar una entidad certificadora autorizada (ej: RENIEC, eCert)
Validar identidad
Presentar documentos legales de la empresa
Recibir certificado
Generalmente se entrega en formato .PFX o .P12
Convertir a PEM
Usar OpenSSL para convertir: openssl pkcs12 -in certificado.pfx -out cert.pem -nodes
Subir al servidor
Colocar en storage/app/sunat/certificados/{RUC}-cert.pem
Validación de Credenciales
Para probar las credenciales antes de envío:
try {
$see = $sunatService -> getSee ( $empresa );
// Si no lanza excepción, las credenciales son válidas
return [ 'success' => true , 'message' => 'Credenciales válidas' ];
} catch ( \ Exception $e ) {
return [ 'success' => false , 'message' => $e -> getMessage ()];
}
Logs de Autenticación
El sistema registra errores de autenticación:
Log :: error ( 'SUNAT - Error de autenticación' , [
'empresa' => $empresa -> ruc ,
'modo' => $empresa -> modo ,
'error' => $exception -> getMessage (),
]);
Los logs se almacenan en storage/logs/laravel.log.
Troubleshooting
Error: No se encontró certificado PEM
Causa : Falta el archivo de certificado.Solución :
Verificar que existe storage/app/sunat/certificados/{RUC}-cert.pem
O configurar certificado global de prueba en config/sunat.php
Error: Credenciales inválidas (SOAP-ENV:Client)
Causa : Usuario SOL o contraseña incorrectos.Solución :
Verificar user_sol en tabla empresas (sin incluir el RUC)
Verificar que clave_sol esté correctamente encriptada
En beta, usar MODDATOS/moddatos
Error: Client ID inválido (GRE)
Causa : Credenciales OAuth incorrectas para GRE.Solución :
Obtener client_id y client_secret desde el portal SUNAT
Configurar en .env o en tabla empresas
Error: Certificado expirado
Causa : El certificado digital venció.Solución :
Renovar certificado con la entidad certificadora
Reemplazar archivo PEM con el nuevo certificado
Variables de Entorno
En .env:
# SUNAT Configuration
SUNAT_IGV = 0.18
# GRE OAuth Credentials (prueba)
SUNAT_GRE_CLIENT_ID = test-client-id
SUNAT_GRE_CLIENT_SECRET = test-client-secret
# Paths
SUNAT_CERT_PATH = storage/app/sunat/certificados/cert.pem