diff --git a/Docker/Dev/dockerfile b/Docker/Dev/dockerfile index ad39309..315e6be 100644 --- a/Docker/Dev/dockerfile +++ b/Docker/Dev/dockerfile @@ -17,6 +17,7 @@ RUN apk add --no-cache \ mysql-client \ libreoffice \ ttf-dejavu \ + supervisor \ && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip \ && echo "upload_max_filesize=150M" > /usr/local/etc/php/conf.d/uploads.ini \ && echo "post_max_size=150M" >> /usr/local/etc/php/conf.d/uploads.ini @@ -32,6 +33,9 @@ COPY . . COPY entrypoint-dev.sh /usr/local/bin/entrypoint-dev.sh RUN chmod +x /usr/local/bin/entrypoint-dev.sh +COPY Docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +RUN mkdir -p /var/log/supervisor + RUN mkdir -p storage/app/keys storage/logs bootstrap/cache RUN chown -R www-data:www-data /var/www/repuve-backend-v1/storage /var/www/repuve-backend-v1/bootstrap/cache @@ -40,4 +44,4 @@ RUN chmod -R 775 /var/www/repuve-backend-v1/storage /var/www/repuve-backend-v1/b EXPOSE 9000 ENTRYPOINT ["/usr/local/bin/entrypoint-dev.sh"] -CMD ["php-fpm"] +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/Docker/Prod/dockerfile b/Docker/Prod/dockerfile index f3600e2..95b7b08 100644 --- a/Docker/Prod/dockerfile +++ b/Docker/Prod/dockerfile @@ -14,6 +14,7 @@ RUN apk add --no-cache \ bash \ libreoffice \ ttf-dejavu \ + supervisor \ && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip \ && echo "upload_max_filesize=150M" > /usr/local/etc/php/conf.d/uploads.ini \ && echo "post_max_size=150M" >> /usr/local/etc/php/conf.d/uploads.ini @@ -29,6 +30,9 @@ COPY . . COPY entrypoint-prod.sh /usr/local/bin/entrypoint-prod.sh RUN chmod +x /usr/local/bin/entrypoint-prod.sh +COPY Docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +RUN mkdir -p /var/log/supervisor + RUN mkdir -p storage/app/keys storage/logs bootstrap/cache RUN chown -R www-data:www-data /var/www/repuve-backend-v1/storage /var/www/repuve-backend-v1/bootstrap/cache @@ -37,4 +41,4 @@ RUN chmod -R 775 /var/www/repuve-backend-v1/storage /var/www/repuve-backend-v1/b EXPOSE 9000 ENTRYPOINT ["/usr/local/bin/entrypoint-prod.sh"] -CMD ["php-fpm"] +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/Docker/supervisor/supervisord.conf b/Docker/supervisor/supervisord.conf new file mode 100644 index 0000000..a5d189d --- /dev/null +++ b/Docker/supervisor/supervisord.conf @@ -0,0 +1,30 @@ +[supervisord] +nodaemon=true +user=root +logfile=/dev/null +logfile_maxbytes=0 +pidfile=/var/run/supervisord.pid + +[program:php-fpm] +command=php-fpm +autostart=true +autorestart=true +priority=1 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:queue-worker] +command=php /var/www/repuve-backend-v1/artisan queue:work --tries=3 --timeout=300 --sleep=3 --max-time=3600 +autostart=true +autorestart=true +priority=2 +user=www-data +numprocs=1 +stopwaitsecs=60 +stdout_logfile=/var/www/repuve-backend-v1/storage/logs/worker.log +stdout_logfile_maxbytes=50MB +stdout_logfile_backups=5 +stderr_logfile=/var/www/repuve-backend-v1/storage/logs/worker.log +stderr_logfile_maxbytes=0 diff --git a/app/Http/Controllers/Repuve/InscriptionController.php b/app/Http/Controllers/Repuve/InscriptionController.php index 7345ea8..8369295 100644 --- a/app/Http/Controllers/Repuve/InscriptionController.php +++ b/app/Http/Controllers/Repuve/InscriptionController.php @@ -19,6 +19,7 @@ use App\Services\RepuveService; use App\Services\PadronEstatalService; use App\Jobs\ProcessRepuveResponse; +use App\Supports\SoapParallelExecutor; use Illuminate\Routing\Controllers\HasMiddleware; class InscriptionController extends Controller implements HasMiddleware @@ -119,8 +120,17 @@ public function vehicleInscription(VehicleStoreRequest $request) ]); } - // Consultar REPUVE Nacional para corroborar el vehículo y obtener folio_CI - $repuveNacionalData = $this->repuveService->consultarVehiculo($niv, $placa); + // Consultar REPUVE Nacional y verificar robo en paralelo + $parallelRequests = [ + 'repuve' => $this->repuveService->prepareConsultarVehiculoRequest($niv, $placa), + 'robo' => $this->repuveService->prepareVerificarRoboRequest($niv, $placa), + ]; + $parallelResults = SoapParallelExecutor::execute($parallelRequests); + + // Parsear respuesta REPUVE Nacional + $repuveNacionalData = $this->repuveService->parseConsultarVehiculoResponse( + $parallelResults['repuve']['response'] ?: '' + ); // Verificar si hubo error en la consulta a REPUVE Nacional if ($repuveNacionalData['has_error'] ?? false) { @@ -136,8 +146,11 @@ public function vehicleInscription(VehicleStoreRequest $request) $folioRepuve = $repuveNacionalData['folio_CI'] ?? null; $actionType = empty($folioRepuve) ? 'sustitucion_primera_vez' : 'sustitucion'; - // Verificar robo - $roboResult = $this->checkIfStolen($niv, $placa); + // Parsear respuesta de robo + $roboResult = $this->repuveService->parseRoboResponse( + $parallelResults['robo']['response'] ?: '', + $niv ?: $placa ?: 'N/A' + ); // Solo bloquear si está marcado como robado if ($roboResult['is_robado'] ?? false) { DB::rollBack(); diff --git a/app/Http/Controllers/Repuve/UpdateController.php b/app/Http/Controllers/Repuve/UpdateController.php index 06a9780..0f090e0 100644 --- a/app/Http/Controllers/Repuve/UpdateController.php +++ b/app/Http/Controllers/Repuve/UpdateController.php @@ -21,6 +21,7 @@ use Exception; use Illuminate\Support\Facades\Auth; use App\Jobs\ProcessRepuveResponse; +use App\Supports\SoapParallelExecutor; use App\Models\CatalogTagStatus; use Illuminate\Routing\Controllers\HasMiddleware; @@ -141,10 +142,20 @@ public function tagSubstitution(Request $request) ]); } - // Validar vehículo en PadronEstatal antes de proceder con la sustitución + // Consultar PadronEstatal, verificar robo y consultar REPUVE en paralelo + $parallelRequests = [ + 'padron' => $this->padronEstatalService->prepareConsultarPadronRequest('placa', $vehicle->placa), + 'robo' => $this->repuveService->prepareVerificarRoboRequest($vehicle->niv, null), + 'repuve' => $this->repuveService->prepareConsultarVehiculoRequest($vehicle->niv, $vehicle->placa), + ]; + $parallelResults = SoapParallelExecutor::execute($parallelRequests); + + // Parsear respuesta del Padrón Estatal try { - $datosEstatal = $this->padronEstatalService->getVehiculoByPlaca($vehicle->placa); - $vehicleDataEstatal = $this->padronEstatalService->extraerDatosVehiculo($datosEstatal); + $datosEstatalRaw = $this->padronEstatalService->parsearRespuesta( + $parallelResults['padron']['response'] ?: '' + ); + $vehicleDataEstatal = $this->padronEstatalService->extraerDatosVehiculo($datosEstatalRaw); } catch (PadronEstatalException $e) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'No se pudo validar el vehículo en el Padrón Estatal.', @@ -161,7 +172,11 @@ public function tagSubstitution(Request $request) ]); } - $roboResult = $this->checkIfStolen($vehicle->niv); + // Parsear respuesta de robo + $roboResult = $this->repuveService->parseRoboResponse( + $parallelResults['robo']['response'] ?: '', + $vehicle->niv + ); // Solo bloquear si explícitamente está marcado como robado if ($roboResult['is_robado'] ?? false) { @@ -170,8 +185,10 @@ public function tagSubstitution(Request $request) ]); } - // Obtener folio_CI de REPUVE antes de cancelar (folio anterior) - $repuveData = $this->repuveService->consultarVehiculo($vehicle->niv, $vehicle->placa); + // Parsear respuesta REPUVE para obtener folio_CI anterior + $repuveData = $this->repuveService->parseConsultarVehiculoResponse( + $parallelResults['repuve']['response'] ?: '' + ); $folioAnterior = $repuveData['folio_CI'] ?? null; DB::beginTransaction(); diff --git a/app/Services/PadronEstatalService.php b/app/Services/PadronEstatalService.php index 6771016..7dfa67e 100644 --- a/app/Services/PadronEstatalService.php +++ b/app/Services/PadronEstatalService.php @@ -31,7 +31,35 @@ public function getVehiculoByFolio(string $folio): array /** * Consulta el padrón vehicular estatal + * Prepara la petición SOAP sin ejecutarla. + * Retorna ['url', 'body', 'headers'] para usar con SoapParallelExecutor. */ + public function prepareConsultarPadronRequest(string $tipo, string $valor): array + { + $data = json_encode(['tipo' => $tipo, 'valor' => $valor]); + + $soapBody = << + + + + {$data} + + + + XML; + + return [ + 'url' => $this->soapUrl, + 'body' => $soapBody, + 'headers' => [ + 'Content-Type: text/xml; charset=utf-8', + 'SOAPAction: ""', + 'Content-Length: ' . strlen($soapBody), + ], + ]; + } + private function consultarPadron(string $tipo, string $valor): array { $logger = Log::channel('padron_estatal'); @@ -114,7 +142,7 @@ private function consultarPadron(string $tipo, string $valor): array /** * Parsea la respuesta del padrón estatal */ - private function parsearRespuesta(string $soapResponse): array + public function parsearRespuesta(string $soapResponse): array { $logger = Log::channel('padron_estatal'); diff --git a/app/Services/RepuveService.php b/app/Services/RepuveService.php index b7b65ba..96565fa 100644 --- a/app/Services/RepuveService.php +++ b/app/Services/RepuveService.php @@ -470,6 +470,84 @@ public function consultarVehiculo(?string $niv = null, ?string $placa = null) } } + /** + * Prepara la petición SOAP de consultarVehiculo sin ejecutarla. + * Retorna ['url', 'body', 'headers'] para usar con SoapParallelExecutor. + */ + public function prepareConsultarVehiculoRequest(?string $niv = null, ?string $placa = null): array + { + $this->asegurarCargaCredenciales(); + + $url = $this->baseUrl . '/jaxws-consultarpv/ConsultaRpv'; + + $campos = array_fill(0, 8, ''); + $campos[0] = $niv ?? ''; + $campos[2] = $placa ?? ''; + $arg2 = implode('|', $campos); + + $soapBody = << + + + + {$this->username} + {$this->password} + {$arg2} + + + + XML; + + return [ + 'url' => $url, + 'body' => $soapBody, + 'headers' => [ + 'Content-Type: text/xml; charset=utf-8', + 'SOAPAction: "doConsRPV"', + 'Content-Length: ' . strlen($soapBody), + ], + ]; + } + + /** + * Prepara la petición SOAP de verificarRobo sin ejecutarla. + * Retorna ['url', 'body', 'headers'] para usar con SoapParallelExecutor. + */ + public function prepareVerificarRoboRequest(?string $niv = null, ?string $placa = null): array + { + $this->asegurarCargaCredenciales(); + + $url = $this->baseUrl . $this->roboEndpoint; + + $campos = array_fill(0, 8, ''); + $campos[0] = $niv ?? ''; + $campos[2] = $placa ?? ''; + $arg2 = implode('|', $campos); + + $soapBody = << + + + + {$this->username} + {$this->password} + {$arg2} + + + + XML; + + return [ + 'url' => $url, + 'body' => $soapBody, + 'headers' => [ + 'Content-Type: text/xml; charset=utf-8', + 'SOAPAction: "doConsRepRobo"', + 'Content-Length: ' . strlen($soapBody), + ], + ]; + } + public function inscribirVehiculo(array $datos) { $this->asegurarCargaCredenciales(); @@ -674,7 +752,7 @@ private function parsearRespuestaInscripcion(string $soapResponse) ]; } - private function parseRoboResponse(string $soapResponse, string $valor): array + public function parseRoboResponse(string $soapResponse, string $valor): array { // Extraer contenido del tag preg_match('/(.*?)<\/return>/s', $soapResponse, $matches); diff --git a/app/Supports/SoapParallelExecutor.php b/app/Supports/SoapParallelExecutor.php new file mode 100644 index 0000000..9e02748 --- /dev/null +++ b/app/Supports/SoapParallelExecutor.php @@ -0,0 +1,53 @@ + ['url' => string, 'body' => string, 'headers' => array] + * @return array Mapa de clave => ['response' => string|false, 'http_code' => int, 'curl_error' => string] + */ + public static function execute(array $requests): array + { + $multiHandle = curl_multi_init(); + $handles = []; + + foreach ($requests as $key => $req) { + $ch = curl_init($req['url']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $req['body']); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_HTTPHEADER, $req['headers']); + curl_multi_add_handle($multiHandle, $ch); + $handles[$key] = $ch; + } + + $running = null; + do { + $status = curl_multi_exec($multiHandle, $running); + if ($running) { + curl_multi_select($multiHandle); + } + } while ($running > 0 && $status === CURLM_OK); + + $results = []; + foreach ($handles as $key => $ch) { + $results[$key] = [ + 'response' => curl_multi_getcontent($ch), + 'http_code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), + 'curl_error' => curl_error($ch), + ]; + curl_multi_remove_handle($multiHandle, $ch); + curl_close($ch); + } + + curl_multi_close($multiHandle); + + return $results; + } +}