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() ]); } $datosVehiculo = null; if ($request->vin) { $datosVehiculo = $this->vehicleService->consultarVehiculoPorTag($request->vin); } if (!$datosVehiculo && $request->placa) { $datosVehiculo = $this->vehicleService->consultarVehiculoPorTag($request->placa); } if (!$datosVehiculo || !isset($datosVehiculo['tag_number'])) { return ApiResponse::NOT_FOUND->response([ 'success' => false, 'message' => 'No se encontró el vehículo en el sistema' ]); } $fastId = $datosVehiculo['tag_number']; // Verificar si ya está en Redis $key = "vehiculo:robado:{$fastId}"; if (Redis::exists($key)) { return ApiResponse::BAD_REQUEST->response([ 'success' => false, 'message' => 'El vehículo ya está registrado como robado' ]); } // Guardar en Redis usando la estructura del VehicleService $datos = [ 'fast_id' => $fastId, 'vin' => $datosVehiculo['vin'] ?? null, 'placa' => $datosVehiculo['placa'] ?? null, 'marca' => $datosVehiculo['marca'] ?? null, 'modelo' => $datosVehiculo['modelo'] ?? null, 'color' => $datosVehiculo['color'] ?? null, 'fecha_robo' => $request->fecha_robo, 'autoridad' => $request->autoridad, 'acta' => $request->acta, 'denunciante' => $request->denunciante, 'fecha_acta' => $request->fecha_acta, 'primera_deteccion' => now()->toIso8601String(), 'ultima_deteccion' => now()->toIso8601String(), 'detecciones' => 0 ]; Redis::set($key, json_encode($datos)); return ApiResponse::OK->response([ 'success' => true, 'message' => 'Vehículo consultado y registrado como robado exitosamente', 'vehiculo' => $datos ]); } /** * Dar de baja (registrar como recuperado) */ 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; // 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']; // 2. Guardar en MySQL como recuperado $vehiculoRecuperado = Vehicle::create([ 'fast_id' => $datosRedis['fast_id'] ?? null, 'vin' => $datosRedis['vin'] ?? null, 'placa' => $datosRedis['placa'] ?? null, 'fecha_robo' => $datosRedis['fecha_robo'] ?? null, 'acta_robo' => $datosRedis['acta'] ?? null, 'denunciante' => $datosRedis['denunciante'] ?? null, 'fecha_acta' => $datosRedis['fecha_acta'] ?? null, 'fecha_recuperacion' => now(), 'datos_robo_original' => $datosRedis ]); // 3. Eliminar de Redis $fastId = $vehiculoEncontrado['fast_id']; Redis::del("vehiculo:robado:{$fastId}"); return ApiResponse::OK->response([ 'success' => true, 'message' => 'Vehículo registrado como recuperado exitosamente', 'vehiculo' => [ 'id' => $vehiculoRecuperado->id, 'fast_id' => $vehiculoRecuperado->fast_id, 'vin' => $vehiculoRecuperado->vin, 'placa' => $vehiculoRecuperado->placa, 'fecha_robo' => $vehiculoRecuperado->fecha_robo, 'fecha_recuperacion' => $vehiculoRecuperado->fecha_recuperacion, '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 */ 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 fast_id del key $fastId = str_replace('vehiculo:robado:', '', $key); return [ 'fast_id' => $fastId, '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(Request $request) { $query = Detection::query(); // Filtro por placa if ($request->has('placa') && !empty($request->placa)) { $query->where('placa', 'like', '%' . $request->placa . '%'); } if ($request->has('vin') && !empty($request->vin)) { $query->where('vin', 'like', '%' . $request->vin . '%'); } $detecciones = $query->orderBy('fecha_deteccion', 'desc') ->paginate(config('app.pagination')); return ApiResponse::OK->response([ 'success' => true, 'detecciones' => $detecciones ]); } /** * Listar detecciones del día desde Redis * GET /api/vehicles/detecciones/dia */ public function listarDeteccionesDelDia(Request $request) { $fecha = $request->input('fecha'); // Formato: Y-m-d (opcional) if ($fecha && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha)) { return ApiResponse::BAD_REQUEST->response([ 'success' => false, 'message' => 'Formato de fecha inválido. Use YYYY-MM-DD' ]); } try { $detecciones = $this->vehicleService->listarDeteccionesDelDia($fecha); return ApiResponse::OK->response([ 'success' => true, 'fecha' => $fecha ?? now()->format('Y-m-d'), 'total' => count($detecciones), 'detecciones' => $detecciones ]); } catch (\Exception $e) { return ApiResponse::INTERNAL_ERROR->response([ 'success' => false, 'message' => 'Error al obtener detecciones del día', 'error' => $e->getMessage() ]); } } /** * Buscar vehículo por placa o VIN en el servicio externo * GET /api/vehicles/detectar * Consulta datos sin dar de alta como robado */ public function buscarVehiculo(Request $request) { $validated = $request->validate([ 'placa' => 'required_without:vin|string|nullable', 'vin' => 'required_without:placa|string|nullable', ]); $criterio = $validated['vin'] ?? $validated['placa']; // Consultar en el servicio externo $datosVehiculo = $this->vehicleService->consultarVehiculoPorTag($criterio); if (!$datosVehiculo) { return ApiResponse::NOT_FOUND->response([ 'success' => false, 'message' => 'No se encontró el vehículo en el sistema' ]); } return ApiResponse::OK->response([ 'success' => true, 'vehiculo' => [ 'fast_id' => $datosVehiculo['tag_number'] ?? null, 'folio_tag' => $datosVehiculo['folio_tag'] ?? null, 'vin' => $datosVehiculo['vin'] ?? null, 'placa' => $datosVehiculo['placa'] ?? null, 'marca' => $datosVehiculo['marca'] ?? null, 'modelo' => $datosVehiculo['modelo'] ?? null, 'linea' => $datosVehiculo['linea'] ?? null, 'sublinea' => $datosVehiculo['sublinea'] ?? null, 'color' => $datosVehiculo['color'] ?? null, 'clase_veh' => $datosVehiculo['clase_veh'] ?? null, 'tipo_servicio' => $datosVehiculo['tipo_servicio'] ?? null, 'propietario' => $datosVehiculo['propietario'] ?? null, ] ]); } public function buscarVehiculoRobado(Request $request) { $validated = $request->validate([ 'placa' => 'required_without:vin|string|nullable', 'vin' => 'required_without:placa|string|nullable', ]); $placa = $validated['placa'] ?? null; $vin = $validated['vin'] ?? null; // Buscar en Redis $vehiculoEncontrado = $this->buscarEnRedis($vin, $placa); if (!$vehiculoEncontrado) { return ApiResponse::NOT_FOUND->response([ 'success' => false, 'message' => 'El vehículo no está registrado como robado' ]); } return ApiResponse::OK->response([ 'success' => true, 'message' => 'Vehículo encontrado en lista de robados', 'vehiculo' => $vehiculoEncontrado['datos'] ]); } /** * Procesar detección de tag RFID * POST /api/vehicles/buscar * Requiere autenticación mediante token de arco */ public function buscarPorTag(Request $request) { $validated = $request->validate([ 'fast_id' => 'required|string', 'antena' => 'nullable|string', 'timestamp' => 'nullable|date' ]); // Obtener el arco autenticado del middleware $arco = $request->get('arco_autenticado'); $resultado = $this->vehicleService->procesarDeteccion( $validated['fast_id'], $arco->ip_address, $validated['antena'] ?? null ); if (!$resultado['success']) { return ApiResponse::NOT_FOUND->response([ 'success' => false, 'message' => $resultado['message'] ?? 'No se encontró el vehículo' ]); } return ApiResponse::OK->response($resultado); } }