repuve-backend-v1/app/Services/RepuveService.php
2026-03-09 16:40:12 -06:00

848 lines
31 KiB
PHP

<?php
namespace App\Services;
use Exception;
use App\Models\Error;
use App\Models\Setting;
use App\Helpers\EncryptionHelper;
class RepuveService
{
private string $baseUrl;
private string $roboEndpoint;
private string $inscripcionEndpoint;
private ?string $username = null;
private ?string $password = null;
private bool $credentialsLoaded = false;
public function __construct()
{
$this->baseUrl = config('services.repuve_federal.base_url');
$this->roboEndpoint = config('services.repuve_federal.robo_endpoint');
$this->inscripcionEndpoint = config('services.repuve_federal.inscripcion_endpoint');
}
/**
* Asegurar que las credenciales estén cargadas (lazy loading)
*/
private function asegurarCargaCredenciales(): void
{
if ($this->credentialsLoaded) {
return; // Ya están cargadas
}
$this->loadCredentials();
$this->credentialsLoaded = true;
}
/**
* Cargar credenciales desde BD
*/
private function loadCredentials(): void
{
try {
// Obtener credenciales encriptadas desde BD
$encryptedCredentials = Setting::value('repuve_federal_credentials');
if (!$encryptedCredentials) {
throw new Exception('Credenciales REPUVE no configuradas en el sistema');
}
$credentials = EncryptionHelper::decryptData($encryptedCredentials);
if (!$credentials || !isset($credentials['username'], $credentials['password'])) {
throw new Exception('Error al desencriptar credenciales REPUVE');
}
$this->username = $credentials['username'];
$this->password = $credentials['password'];
logger()->info('RepuveService: Credenciales cargadas correctamente desde BD');
} catch (Exception $e) {
logger()->error('RepuveService: Error al cargar credenciales', [
'error' => $e->getMessage()
]);
throw new Exception('No se pudieron cargar las credenciales REPUVE: ' . $e->getMessage());
}
}
/**
* Ejecuta una solicitud cURL con logging completo de conexión, datos enviados,
* tiempo de respuesta y respuesta recibida.
*
* @return array{response: string|false, http_code: int, curl_error: string, elapsed_ms: float}
*/
private function ejecutarSolicitudSoap(\CurlHandle $ch, string $operacion, string $url, string $soapBody, array $contexto = []): array
{
logger()->info("REPUVE Nacional [{$operacion}]: Enviando solicitud al servidor", array_merge([
'url' => $url,
'soap_body' => $soapBody,
], $contexto));
$inicio = microtime(true);
$response = curl_exec($ch);
$elapsedMs = round((microtime(true) - $inicio) * 1000, 2);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
if ($curlError) {
logger()->error("REPUVE Nacional [{$operacion}]: Sin conexión con el servidor", array_merge([
'url' => $url,
'curl_error' => $curlError,
'elapsed_ms' => $elapsedMs,
], $contexto));
} else {
logger()->info("REPUVE Nacional [{$operacion}]: Respuesta recibida", array_merge([
'url' => $url,
'http_code' => $httpCode,
'elapsed_ms' => $elapsedMs,
'response_length' => strlen($response ?: ''),
'response' => $response,
], $contexto));
}
return [
'response' => $response,
'http_code' => $httpCode,
'curl_error' => $curlError,
'elapsed_ms' => $elapsedMs,
];
}
public function consultarPadron(string $niv)
{
$this->asegurarCargaCredenciales();
$url = $this->baseUrl . $this->roboEndpoint;
$arg2 = $niv . '|||||||';
$soapBody = <<<XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
<soapenv:Header/>
<soapenv:Body>
<wsdl:doConsPadron>
<arg0>{$this->username}</arg0>
<arg1>{$this->password}</arg1>
<arg2>{$arg2}</arg2>
</wsdl:doConsPadron>
</soapenv:Body>
</soapenv:Envelope>
XML;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $soapBody);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: text/xml; charset=utf-8',
'SOAPAction: "doConsPadron"',
'Content-Length: ' . strlen($soapBody),
]);
try {
$result = $this->ejecutarSolicitudSoap($ch, 'doConsPadron', $url, $soapBody, ['niv' => $niv]);
$response = $result['response'];
$httpCode = $result['http_code'];
$error = $result['curl_error'];
if ($error) {
throw new Exception("Error en la petición SOAP: {$error}");
}
if ($httpCode !== 200) {
throw new Exception("Error al consultar REPUVE: Código HTTP {$httpCode}");
}
return $this->parseVehicleResponse($response, $niv);
} finally {
unset($ch);
}
}
private function parseVehicleResponse(string $soapResponse, string $niv)
{
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
if (!isset($matches[1])) {
$errorFromDb = Error::where('code', '108')->first();
return [
'has_error' => true,
'error_code' => '108',
'error_name' => $errorFromDb?->name,
'error_message' => $errorFromDb?->description ?? 'Error al parsear respuesta',
'timestamp' => now()->toDateTimeString(),
'niv' => $niv,
'repuve_response' => null,
];
}
$contenido = trim($matches[1]);
// Verificar si hay error de REPUVE Nacional (cualquier formato con ERR o ERROR)
if (preg_match('/(ERR|ERROR|err|error):(-?\d+)/i', $contenido, $errorMatch)) {
$errorCode = $errorMatch[2];
// Buscar el error completo en la base de datos
$errorFromDb = Error::where('code', $errorCode)->first();
return [
'has_error' => true,
'error_code' => $errorCode,
'error_name' => $errorFromDb?->name,
'error_message' => $errorFromDb?->description ?? "Error código {$errorCode} - no catalogado",
'timestamp' => now()->toDateTimeString(),
'niv' => $niv,
'repuve_response' => $contenido,
];
}
// Si empieza con OK:, parsear los datos
if (str_starts_with($contenido, 'OK:')) {
$datos = str_replace('OK:', '', $contenido);
$valores = explode('|', $datos);
$campos = [
'marca',
'submarca',
'tipo_vehiculo',
'fecha_expedicion',
'oficina',
'niv',
'placa',
'motor',
'modelo',
'color',
'version',
'entidad',
'marca_padron',
'submarca_padron',
'tipo_uso_padron',
'tipo_vehiculo_padron',
'estatus_registro',
'aduana',
'nombre_aduana',
'patente',
'pedimento',
'fecha_pedimento',
'clave_importador',
'observaciones'
];
$jsonResponse = [];
foreach ($campos as $i => $campo) {
$jsonResponse[$campo] = $valores[$i] ?? null;
}
return [
'has_error' => false,
'error_code' => null,
'error_message' => null,
'timestamp' => now()->toDateTimeString(),
'niv' => $niv,
'repuve_response' => $jsonResponse,
];
}
$errorFromDb = Error::where('code', '108')->first();
return [
'has_error' => true,
'error_code' => '108',
'error_name' => $errorFromDb?->name,
'error_message' => $errorFromDb?->description ?? 'Error al parsear respuesta',
'timestamp' => now()->toDateTimeString(),
'niv' => $niv,
'repuve_response' => null,
];
}
public function verificarRobo(?string $niv = null, ?string $placa = null): array
{
try {
$this->asegurarCargaCredenciales();
if (empty($niv) && empty($placa)) {
logger()->warning('REPUVE verificarRobo: No se proporcionó NIV ni PLACA');
return [
'is_robado' => false,
'has_error' => true,
'error_message' => 'Debe proporcionar al menos NIV o PLACA para verificar robo',
];
}
$url = $this->baseUrl . $this->roboEndpoint;
// Construir arg2 según los parámetros enviados
if (!empty($niv) && !empty($placa)) {
$arg2 = $niv . '|' . $placa . str_repeat('|', 5);
} elseif (!empty($niv)) {
$arg2 = $niv . str_repeat('|', 7);
} else {
$arg2 = '||' . $placa . str_repeat('|', 5);
}
logger()->info('REPUVE verificarRobo: Cadena construida', [
'niv' => $niv,
'placa' => $placa,
'arg2' => $arg2,
'total_pipes' => substr_count($arg2, '|'),
'ejemplo_visual' => str_replace('|', ' | ', $arg2),
]);
$soapBody = <<<XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
<soapenv:Header/>
<soapenv:Body>
<wsdl:doConsRepRobo>
<arg0>{$this->username}</arg0>
<arg1>{$this->password}</arg1>
<arg2>{$arg2}</arg2>
</wsdl:doConsRepRobo>
</soapenv:Body>
</soapenv:Envelope>
XML;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $soapBody);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: text/xml; charset=utf-8',
'SOAPAction: "doConsRepRobo"',
'Content-Length: ' . strlen($soapBody),
]);
try {
$result = $this->ejecutarSolicitudSoap($ch, 'doConsRepRobo', $url, $soapBody, ['niv' => $niv, 'placa' => $placa]);
$response = $result['response'];
$httpCode = $result['http_code'];
$error = $result['curl_error'];
// Si hay error de conexión, retornar error
if ($error) {
return [
'is_robado' => false,
'has_error' => true,
'error_message' => 'Error de conexión con el servicio REPUVE',
];
}
// Si hay error HTTP, retornar error
if ($httpCode !== 200) {
return [
'is_robado' => false,
'has_error' => true,
'error_message' => "Error HTTP {$httpCode} del servicio REPUVE",
];
}
// Parsear respuesta
$valorBuscado = $niv ?: $placa ?: 'N/A';
$resultado = $this->parseRoboResponse($response, $valorBuscado);
// Si hubo error al parsear, loguear pero retornar el resultado completo
if ($resultado['has_error'] ?? false) {
logger()->warning('REPUVE verificarRobo: Error al parsear respuesta', [
'niv' => $niv,
'placa' => $placa,
'error' => $resultado['error_message'] ?? 'Desconocido',
]);
}
// Retornar el array completo con toda la información
return $resultado;
} finally {
unset($ch);
}
} catch (Exception $e) {
logger()->error('REPUVE verificarRobo: Excepción capturada', [
'niv' => $niv,
'placa' => $placa,
'exception' => $e->getMessage(),
]);
return [
'is_robado' => false,
'has_error' => true,
'error_message' => 'Excepción capturada: ' . $e->getMessage(),
];
}
}
public function consultarVehiculo(?string $niv = null, ?string $placa = null)
{
try {
$this->asegurarCargaCredenciales();
$url = $this->baseUrl . '/jaxws-consultarpv/ConsultaRpv';
// Construir arg2: NIV||||||||
if ($placa) {
$arg2 = ($niv ?? '') . '|' . $placa . str_repeat('|', 5);
} else {
$arg2 = ($niv ?? '') . str_repeat('|', 7);
}
$soapBody = <<<XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
<soapenv:Header/>
<soapenv:Body>
<wsdl:doConsRPV>
<arg0>{$this->username}</arg0>
<arg1>{$this->password}</arg1>
<arg2>{$arg2}</arg2>
</wsdl:doConsRPV>
</soapenv:Body>
</soapenv:Envelope>
XML;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $soapBody);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: text/xml; charset=utf-8',
'SOAPAction: "doConsRPV"',
'Content-Length: ' . strlen($soapBody),
]);
try {
$result = $this->ejecutarSolicitudSoap($ch, 'doConsRPV', $url, $soapBody, ['niv' => $niv, 'placa' => $placa]);
$response = $result['response'];
$httpCode = $result['http_code'];
$error = $result['curl_error'];
if ($error) {
return [
'success' => false,
'has_error' => true,
'error_message' => 'Error de conexión con el servicio REPUVE',
];
}
if ($httpCode !== 200) {
return [
'success' => false,
'has_error' => true,
'error_message' => "Error HTTP {$httpCode} del servicio REPUVE",
];
}
// Parsear respuesta
$resultado = $this->parseConsultarVehiculoResponse($response);
if ($resultado['has_error'] ?? false) {
logger()->warning('REPUVE consultarVehiculo: Error al parsear', [
'niv' => $niv,
'placa' => $placa,
'error' => $resultado['error_message'] ?? 'Desconocido',
]);
}
return $resultado;
} finally {
unset($ch);
}
} catch (Exception $e) {
logger()->error('REPUVE consultarVehiculo: Excepción', [
'niv' => $niv,
'placa' => $placa,
'exception' => $e->getMessage(),
]);
return [
'success' => false,
'has_error' => true,
'error_message' => 'Excepción: ' . $e->getMessage(),
];
}
}
public function inscribirVehiculo(array $datos)
{
$this->asegurarCargaCredenciales();
$url = $this->baseUrl . $this->inscripcionEndpoint;
$arg2 = implode('|', [
$datos['ent_fed'] ?? '', // 1. Entidad federativa
$datos['ofcexp'] ?? '', // 2. Oficina expedición
$datos['fechaexp'] ?? '', // 3. Fecha expedición
$datos['placa'] ?? '', // 4. Placa
$datos['tarjetacir'] ?? '', // 5. Tarjeta circulación
$datos['marca'] ?? '', // 6. Marca
$datos['submarca'] ?? '', // 7. Submarca
$datos['version'] ?? '', // 8. Versión
$datos['clase_veh'] ?? '', // 9. Clase vehículo
$datos['tipo_veh'] ?? '', // 10. Tipo vehículo
$datos['tipo_uso'] ?? '', // 11. Tipo uso
$datos['modelo'] ?? '', // 12. Modelo (año)
$datos['color'] ?? '', // 13. Color
$datos['motor'] ?? '', // 14. Número motor
$datos['niv'] ?? '', // 15. NIV
$datos['rfv'] ?? '', // 16. RFV
$datos['numptas'] ?? '', // 17. Número puertas
$datos['observac'] ?? '', // 18. Observaciones
$datos['tipopers'] ?? '', // 19. Tipo persona
$datos['curp'] ?? '', // 20. CURP
$datos['rfc'] ?? '', // 21. RFC
$datos['pasaporte'] ?? '', // 22. Pasaporte
$datos['licencia'] ?? '', // 23. Licencia
$datos['nombre'] ?? '', // 24. Nombre
$datos['ap_paterno'] ?? '', // 25. Apellido paterno
$datos['ap_materno'] ?? '', // 26. Apellido materno
$datos['ent_fed'] ?? '', // 27. Entidad federativa propietario
$datos['munic'] ?? '', // 28. Municipio
$datos['callep'] ?? '', // 29. Calle principal
$datos['num_ext'] ?? '', // 30. Número exterior
$datos['num_int'] ?? '', // 31. Número interior
$datos['colonia'] ?? '', // 32. Colonia
$datos['cp'] ?? '', // 33. Código postal
$datos['cve_vehi'] ?? '', // 34. Clave vehículo
$datos['nrpv'] ?? '', // 35. NRPV
$datos['fe_act'] ?? '', // 36. Fecha actualización
$datos['tipo_mov'] ?? '', // 37. Tipo movimiento
]);
// Construir el cuerpo SOAP
$soapBody = <<<XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://inscripcion.org/wsdl">
<soapenv:Header/>
<soapenv:Body>
<wsdl:inscribe>
<arg0>{$this->username}</arg0>
<arg1>{$this->password}</arg1>
<arg2>{$arg2}</arg2>
</wsdl:inscribe>
</soapenv:Body>
</soapenv:Envelope>
XML;
// Configurar cURL
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $soapBody);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: text/xml; charset=utf-8',
'SOAPAction: "inscribe"',
'Content-Length: ' . strlen($soapBody),
]);
try {
$result = $this->ejecutarSolicitudSoap($ch, 'inscribe', $url, $soapBody, [
'niv' => $datos['niv'] ?? null,
'placa' => $datos['placa'] ?? null,
'nrpv' => $datos['nrpv'] ?? null,
]);
$response = $result['response'];
$httpCode = $result['http_code'];
$curlError = $result['curl_error'];
if ($curlError) {
$errorFromDb = Error::where('code', '103')->first();
return [
'has_error' => true,
'error_code' => '103',
'error_name' => $errorFromDb?->name,
'error_message' => $errorFromDb?->description ?? "Error de conexión: {$curlError}",
'timestamp' => now()->toDateTimeString(),
'http_code' => $httpCode,
'raw_response' => $response,
];
}
if ($httpCode !== 200) {
$errorFromDb = Error::where('code', '-1')->first();
return [
'has_error' => true,
'error_code' => '-1',
'error_name' => $errorFromDb?->name,
'error_message' => $errorFromDb?->description ?? "Error interno HTTP {$httpCode}",
'timestamp' => now()->toDateTimeString(),
'http_code' => $httpCode,
'raw_response' => $response,
];
}
// Parsear la respuesta
return $this->parsearRespuestaInscripcion($response);
} finally {
unset($ch);
}
}
/**
* Parsea la respuesta
*/
private function parsearRespuestaInscripcion(string $soapResponse)
{
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
if (!isset($matches[1])) {
$errorFromDb = Error::where('code', '108')->first();
return [
'has_error' => true,
'error_code' => '108',
'error_name' => $errorFromDb?->name,
'error_message' => $errorFromDb?->description ?? 'Error al parsear respuesta',
'timestamp' => now()->toDateTimeString(),
'raw_response' => $soapResponse,
'repuve_response' => null,
];
}
$contenido = trim($matches[1]);
// Buscar patrones de error: ERR:, ERROR:, err:, error:
if (preg_match('/(ERR|ERROR|err|error):(-?\d+)/i', $contenido, $errorMatch)) {
$errorCode = $errorMatch[2];
// Buscar el error completo en la base de datos
$errorFromDb = Error::where('code', $errorCode)->first();
if ($errorFromDb) {
// Retornar nombre y descripción de la BD
return [
'has_error' => true,
'error_code' => $errorCode,
'error_name' => $errorFromDb->name,
'error_message' => $errorFromDb->description,
'timestamp' => now()->toDateTimeString(),
'raw_response' => $soapResponse,
'repuve_response' => $contenido,
];
}
// Si no existe en BD, retornar el código sin descripción
return [
'has_error' => true,
'error_code' => $errorCode,
'error_name' => null,
'error_message' => "Error código {$errorCode} - no catalogado",
'timestamp' => now()->toDateTimeString(),
'raw_response' => $soapResponse,
'repuve_response' => $contenido,
];
}
// Si empieza con OK: es éxito
if (preg_match('/^OK:/i', $contenido)) {
$datos = preg_replace('/^OK:/i', '', $contenido);
return [
'has_error' => false,
'error_code' => null,
'error_message' => null,
'timestamp' => now()->toDateTimeString(),
'raw_response' => $soapResponse,
'repuve_response' => [
'status' => 'OK',
'data' => $datos,
],
];
}
// Si no hay ERR/ERROR y no es OK, asumir que es respuesta exitosa
return [
'has_error' => false,
'error_code' => null,
'error_name' => null,
'error_message' => null,
'timestamp' => now()->toDateTimeString(),
'raw_response' => $soapResponse,
'repuve_response' => [
'status' => 'OK',
'data' => $contenido,
],
];
}
private function parseRoboResponse(string $soapResponse, string $valor): array
{
// Extraer contenido del tag <return>
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
if (!isset($matches[1])) {
logger()->error('REPUVE parseRoboResponse: No se encontró tag <return>', [
'soap_response' => substr($soapResponse, 0, 500),
'valor' => $valor,
]);
return [
'has_error' => true,
'is_robado' => false,
'error_message' => 'Respuesta SOAP inválida',
];
}
$contenido = trim($matches[1]);
// Verificar si hay error de REPUVE Nacional (ERR: o ERROR:)
if (preg_match('/(ERR|ERROR|err|error):(-?\d+)/i', $contenido, $errorMatch)) {
$errorCode = $errorMatch[2];
logger()->warning('REPUVE parseRoboResponse: Servicio retornó error', [
'error_code' => $errorCode,
'contenido' => $contenido,
'valor' => $valor,
]);
return [
'has_error' => true,
'is_robado' => false,
'error_code' => $errorCode,
'error_message' => "Error REPUVE código {$errorCode}",
'raw_response' => $contenido,
];
}
// Si empieza con OK:, parsear los datos de robo
if (str_starts_with($contenido, 'OK:')) {
$datos = str_replace('OK:', '', $contenido);
$valores = explode('|', $datos);
// 1 = robado, 0 = no robado
$indicador = $valores[0] ?? '0';
$isRobado = ($indicador === '1');
$roboData = [
'has_error' => false,
'is_robado' => $isRobado,
'indicador' => $indicador,
'fecha_robo' => $valores[1] ?? null,
'placa' => $valores[2] ?? null,
'niv' => $valores[3] ?? null,
'autoridad' => $valores[4] ?? null,
'acta' => $valores[5] ?? null,
'denunciante' => $valores[6] ?? null,
'fecha_acta' => $valores[7] ?? null,
'raw_response' => $contenido,
];
// Log importante si está robado
if ($isRobado) {
logger()->warning('REPUVE: Vehículo reportado como ROBADO', [
'valor_buscado' => $valor,
'niv' => $roboData['niv'],
'placa' => $roboData['placa'],
'autoridad' => $roboData['autoridad'],
'acta' => $roboData['acta'],
'denunciante' => $roboData['denunciante'],
]);
} else {
logger()->info('REPUVE: Vehículo NO reportado como robado', [
'valor_buscado' => $valor,
]);
}
return $roboData;
}
// Si no tiene formato reconocido
logger()->error('REPUVE parseRoboResponse: Formato desconocido', [
'contenido' => $contenido,
'valor' => $valor,
]);
return [
'has_error' => true,
'is_robado' => false,
'error_message' => 'Formato de respuesta no reconocido',
'raw_response' => $contenido,
];
}
public function parseConsultarVehiculoResponse(string $xmlResponse): array
{
try {
$xml = simplexml_load_string($xmlResponse);
if ($xml === false) {
return [
'success' => false,
'has_error' => true,
'error_message' => 'Error al parsear XML',
'raw_response' => $xmlResponse,
];
}
$xml->registerXPathNamespace('ns2', 'http://consultaRpv.org/wsdl');
$return = $xml->xpath('//ns2:doConsRPVResponse/return');
if (empty($return)) {
return [
'success' => false,
'has_error' => true,
'error_message' => 'No se encontró elemento return en la respuesta',
'raw_response' => $xmlResponse,
];
}
$contenido = trim((string)$return[0]);
// Verificar si la respuesta es OK
if (!str_starts_with($contenido, 'OK:')) {
return [
'success' => false,
'has_error' => true,
'error_message' => $contenido,
'raw_response' => $xmlResponse,
];
}
// Remover "OK:" del inicio
$data = substr($contenido, 3);
// Separar por |
$campos = explode('|', $data);
return [
'success' => true,
'has_error' => false,
'entidad_federativa' => $campos[0] ?? null,
'oficina' => $campos[1] ?? null,
'folio_tarjeta' => $campos[2] ?? null,
'niv' => $campos[3] ?? null,
'fecha_expedicion' => $campos[4] ?? null,
'hora_expedicion' => $campos[5] ?? null,
'procedencia' => $campos[6] ?? null,
'origen' => $campos[7] ?? null,
'clave_vehicular' => $campos[8] ?? null,
'fecha_emplacado' => $campos[9] ?? null,
'municipio' => $campos[10] ?? null,
'serie' => $campos[11] ?? null,
'placa' => $campos[12] ?? null,
'tipo_vehiculo' => $campos[13] ?? null,
'modelo' => $campos[14] ?? null,
'color' => $campos[15] ?? null,
'version' => $campos[16] ?? null,
'entidad_placas' => $campos[17] ?? null,
'marca' => $campos[18] ?? null,
'linea' => $campos[19] ?? null,
'uso' => $campos[20] ?? null,
'clase' => $campos[21] ?? null,
'estatus' => $campos[22] ?? null,
'observaciones' => $campos[23] ?? null,
'raw_response' => $contenido,
];
} catch (Exception $e) {
return [
'success' => false,
'has_error' => true,
'error_message' => 'Excepción al parsear: ' . $e->getMessage(),
'raw_response' => $xmlResponse,
];
}
}
}