all(), [ 'placa' => 'required_without:vin|string|nullable', 'vin' => 'required_without:placa|string|nullable', ], [ 'placa.required_without' => 'Debe proporcionar al menos placa o VIN', 'vin.required_without' => 'Debe proporcionar al menos placa o VIN', ]); if ($validator->fails()) { return ApiResponse::INTERNAL_ERROR->response([ 'success' => false, 'message' => 'Datos inválidos', 'errors' => $validator->errors() ]); } try { $placa = $request->placa; $vin = $request->vin; // Consultar padrón estatal (obtener datos completos del vehículo) Log::info('Consultando padrón estatal', ['placa' => $placa, 'vin' => $vin]); // Si viene VIN, usar VIN, si no usar placa $criterio = $vin ?: $placa; $datosEstatal = $this->consultaEstatal->consultarPorEpc($criterio); if (!$datosEstatal || (!$datosEstatal['vin'] && !$datosEstatal['placa'])) { return ApiResponse::BAD_REQUEST->response([ 'success' => false, 'message' => 'No se encontró información del vehículo en el padrón estatal' ]); } // Obtener VIN y Placa completos $vinCompleto = $datosEstatal['vin'] ?: $vin; $placaCompleta = $datosEstatal['placa'] ?: $placa; Log::info('Datos del padrón estatal obtenidos', [ 'vin' => $vinCompleto, 'placa' => $placaCompleta ]); // Consultar REPUVE $reporteRobo = $this->reporteRobo->consultarPorVin($vinCompleto, $placaCompleta); $tieneReporte = $reporteRobo['tiene_reporte'] ?? false; // Si tiene reporte de robo → Guardar en Redis if ($tieneReporte) { $epc = 'MANUAL_' . ($vinCompleto ?: $placaCompleta); $this->guardarEnRedis($epc, $datosEstatal, $reporteRobo['datos']); Log::warning('¡VEHÍCULO CON REPORTE DE ROBO REGISTRADO!', [ 'vin' => $vinCompleto, 'placa' => $placaCompleta, 'autoridad' => $reporteRobo['datos']['autoridad'] ?? null ]); return ApiResponse::OK->response([ 'success' => true, 'tiene_reporte_robo' => true, 'message' => '¡ALERTA! El vehículo tiene reporte de robo', 'vehiculo' => [ 'vin' => $vinCompleto, 'placa' => $placaCompleta, ], 'reporte_robo' => $reporteRobo['datos'] ]); } // No tiene reporte de robo Log::info('Vehículo consultado sin reporte de robo', [ 'vin' => $vinCompleto, 'placa' => $placaCompleta ]); return ApiResponse::OK->response([ 'success' => true, 'tiene_reporte_robo' => false, 'message' => 'Vehículo sin reporte de robo', 'vehiculo' => [ 'vin' => $vinCompleto, 'placa' => $placaCompleta, ] ]); } catch (\Exception $e) { Log::error('Error al consultar vehículo', [ 'placa' => $request->placa, 'vin' => $request->vin, 'error' => $e->getMessage() ]); return ApiResponse::INTERNAL_ERROR->response([ 'success' => false, 'message' => 'Error al consultar vehículo', 'error' => $e->getMessage() ]); } } /** * Guardar vehículo robado en Redis */ private function guardarEnRedis(string $epc, array $datosEstatal, array $datosRobo): void { $key = "vehiculo:robado:{$epc}"; $datos = [ 'epc' => $epc, 'vin' => $datosEstatal['vin'], 'placa' => $datosEstatal['placa'], 'fecha_robo' => $datosRobo['fecha_robo'] ?? null, 'autoridad' => $datosRobo['autoridad'] ?? null, 'acta' => $datosRobo['acta'] ?? null, 'denunciante' => $datosRobo['denunciante'] ?? null, 'fecha_acta' => $datosRobo['fecha_acta'] ?? null, 'primera_deteccion' => now()->toIso8601String(), 'ultima_deteccion' => now()->toIso8601String(), 'detecciones' => 0, 'origen' => 'CONSULTA_MANUAL' ]; Redis::set($key, json_encode($datos)); } /** * Dar de baja (registrar como recuperado) un vehículo por PLACA o VIN */ public function recuperarVehiculo(Request $request): JsonResponse { $validator = Validator::make($request->all(), [ 'placa' => 'required_without:vin|string|nullable', 'vin' => 'required_without:placa|string|nullable', ], [ 'placa.required_without' => 'Debe proporcionar al menos placa o VIN', 'vin.required_without' => 'Debe proporcionar al menos placa o VIN', ]); if ($validator->fails()) { return ApiResponse::BAD_REQUEST->response([ 'success' => false, 'message' => 'Datos inválidos', 'errors' => $validator->errors() ]); } try { $placa = $request->placa; $vin = $request->vin; Log::info('Buscando vehículo robado para recuperar', ['placa' => $placa, 'vin' => $vin]); // 1. Buscar en Redis (vehículos robados activos) $vehiculoEncontrado = $this->buscarEnRedis($vin, $placa); if (!$vehiculoEncontrado) { return ApiResponse::NOT_FOUND->response([ 'success' => false, 'message' => 'El vehículo no se encuentra en la lista de robados activos' ]); } $datosRedis = $vehiculoEncontrado['datos']; $epc = $vehiculoEncontrado['epc']; Log::info('Vehículo encontrado en Redis', [ 'epc' => $epc, 'vin' => $datosRedis['vin'], 'placa' => $datosRedis['placa'] ]); // 2. Guardar en MySQL como recuperado $vehiculoRecuperado = Vehicle::create([ 'epc' => $epc, 'vin' => $datosRedis['vin'], 'placa' => $datosRedis['placa'], 'fecha_robo' => $datosRedis['fecha_robo'], 'autoridad_robo' => $datosRedis['autoridad'], 'acta_robo' => $datosRedis['acta'], 'denunciante' => $datosRedis['denunciante'], 'fecha_acta' => $datosRedis['fecha_acta'] ?? null, 'fecha_recuperacion' => now(), 'datos_robo_original' => $datosRedis ]); // 3. Eliminar de Redis Redis::del("vehiculo:robado:{$epc}"); Log::info('Vehículo registrado como recuperado', [ 'id' => $vehiculoRecuperado->id, 'vin' => $vehiculoRecuperado->vin, 'placa' => $vehiculoRecuperado->placa ]); return ApiResponse::OK->response([ 'success' => true, 'message' => 'Vehículo registrado como recuperado exitosamente', 'vehiculo' => [ 'id' => $vehiculoRecuperado->id, 'epc' => $vehiculoRecuperado->epc, 'vin' => $vehiculoRecuperado->vin, 'placa' => $vehiculoRecuperado->placa, 'fecha_robo' => $vehiculoRecuperado->fecha_robo, 'fecha_recuperacion' => $vehiculoRecuperado->fecha_recuperacion, 'autoridad_robo' => $vehiculoRecuperado->autoridad_robo, 'acta_robo' => $vehiculoRecuperado->acta_robo, ] ]); } catch (\Exception $e) { Log::error('Error al recuperar vehículo', [ 'placa' => $request->placa, 'vin' => $request->vin, 'error' => $e->getMessage() ]); return ApiResponse::INTERNAL_ERROR->response([ 'success' => false, 'message' => 'Error al registrar vehículo como recuperado', 'error' => $e->getMessage() ]); } } /** * Buscar vehículo en Redis por VIN o Placa */ private function buscarEnRedis(?string $vin, ?string $placa): ?array { $keys = Redis::keys('vehiculo:robado:*'); foreach ($keys as $key) { $datos = Redis::get($key); if (!$datos) { continue; } $vehiculo = json_decode($datos, true); // Buscar por VIN o Placa if (($vin && $vehiculo['vin'] === $vin) || ($placa && $vehiculo['placa'] === $placa)) { // Extraer el EPC del key $epc = str_replace('vehiculo:robado:', '', $key); return [ 'epc' => $epc, 'datos' => $vehiculo ]; } } return null; } /** * Listar todos los vehículos robados activos * GET /api/vehicles/robados */ public function listarRobados(Request $request) { try { $vehiculos = $this->vehicleService->listarVehiculosRobados(); $collection = collect($vehiculos); $perPage = $request->input('per_page', config('app.pagination', 15)); $page = $request->input('page', 1); $paginatedVehiculos = new \Illuminate\Pagination\LengthAwarePaginator( $collection->forPage($page, $perPage), $collection->count(), $perPage, $page, ['path' => $request->url(), 'query' => $request->query()] ); return ApiResponse::OK->response([ 'success' => true, 'total' => $collection->count(), 'vehiculos' => $paginatedVehiculos ]); } catch (\Exception $e) { return ApiResponse::BAD_REQUEST->response([ 'success' => false, 'message' => 'Error al listar vehículos robados', 'error' => $e->getMessage() ]); } } public function listarRecuperados() { $vehiculos = Vehicle::orderBy('fecha_recuperacion', 'desc') ->paginate(config('app.pagination')); return ApiResponse::OK->response([ 'success' => true, 'vehiculos' => $vehiculos ]); } public function listarDetecciones() { $detecciones = Detection::orderBy('fecha_deteccion', 'desc') ->paginate(config('app.pagination')); return ApiResponse::OK->response([ 'success' => true, 'detecciones' => $detecciones ]); } }