input('folio'); $tagId = $request->input('tag_id'); // Buscar Tag y validar que NO tenga vehículo asignado $tag = Tag::findOrFail($tagId); if ($tag->vehicle_id) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El tag ya está asignado a un vehículo. Use actualizar en su lugar.', 'tag_id' => $tagId, 'vehicle_id' => $tag->vehicle_id, ]); } // Validar que el folio del tag coincida if ($tag->folio !== $folio) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El folio no coincide con el tag RFID proporcionado', 'folio_request' => $folio, 'folio_tag' => $tag->folio, ]); } // Verificar robo (API Repuve Nacional) $isStolen = $this->checkIfStolen($folio); if ($isStolen) { return ApiResponse::FORBIDDEN->response([ 'folio' => $folio, 'tag_id' => $tagId, 'stolen' => true, 'message' => 'El vehículo reporta robo. No se puede continuar con la inscripción.', ]); } // Iniciar transacción DB::beginTransaction(); // Obtener 37 datos de API Estatal $vehicleData = $this->getVehicle(); $ownerData = $this->getOwner(); // Crear propietario $owner = Owner::updateOrCreate( ['rfc' => $ownerData['rfc']], [ 'name' => $ownerData['name'], 'paternal' => $ownerData['paternal'], 'maternal' => $ownerData['maternal'], 'curp' => $ownerData['curp'], 'address' => $ownerData['address'], ] ); // Crear vehículo $vehicle = Vehicle::create([ 'anio_placa' => $vehicleData['ANIO_PLACA'], 'placa' => $vehicleData['PLACA'], 'numero_serie' => $vehicleData['NO_SERIE'], 'rfc' => $vehicleData['RFC'], 'folio' => $folio, // Folio del request 'vigencia' => $vehicleData['VIGENCIA'], 'fecha_impresion' => $vehicleData['FECHA_IMPRESION'], 'qr_hash' => $vehicleData['QR_HASH'], 'valido' => $vehicleData['VALIDO'], 'nombre' => $vehicleData['NOMBRE'], 'nombre2' => $vehicleData['NOMBRE2'], 'municipio' => $vehicleData['MUNICIPIO'], 'localidad' => $vehicleData['LOCALIDAD'], 'calle' => $vehicleData['CALLE'], 'calle2' => $vehicleData['CALLE2'], 'tipo' => $vehicleData['TIPO'], 'tipo_servicio' => $vehicleData['TIPO_SERVICIO'], 'marca' => $vehicleData['MARCA'], 'linea' => $vehicleData['LINEA'], 'sublinea' => $vehicleData['SUBLINEA'], 'modelo' => $vehicleData['MODELO'], 'numero_motor' => $vehicleData['NUMERO_MOTOR'], 'descripcion_origen' => $vehicleData['DESCRIPCION_ORIGEN'], 'color' => $vehicleData['COLOR'], 'codigo_postal' => $vehicleData['CODIGO_POSTAL'], 'serie_folio' => $vehicleData['SERIE_FOLIO'], 'sfolio' => $vehicleData['SFOLIO'], 'nrpv' => $vehicleData['NUMERO_SERIE'], 'owner_id' => $owner->id, ]); // Asignar Tag al vehículo $tag->update([ 'vehicle_id' => $vehicle->id, 'folio' => $folio, ]); // Crear registro $record = Record::create([ 'folio' => $folio, 'vehicle_id' => $vehicle->id, 'user_id' => Auth::id(), ]); // Procesar archivos $uploadedFiles = []; if ($request->hasFile('files')) { $files = $request->file('files'); $fileNames = $request->input('names', []); foreach ($files as $index => $file) { $fileName = uniqid() . '_' . time() . '_' . $file->getClientOriginalName(); $path = $file->storeAs('records', $fileName, 'public'); $md5 = md5_file($file->getRealPath()); $fileRecord = File::create([ 'name' => $fileNames[$index] ?? "Archivo " . ($index + 1), 'path' => $path, 'md5' => $md5, 'record_id' => $record->id, ]); $uploadedFiles[] = [ 'id' => $fileRecord->id, 'name' => $fileRecord->name, 'path' => $fileRecord->path, 'url' => $fileRecord->url, ]; } } // Enviar a API Repuve Nacional $apiResponse = $this->sendToRepuveNacional($folio, $tagId, $vehicleData); // Procesar respuesta if (isset($apiResponse['has_error']) && $apiResponse['has_error']) { // Si hay error, buscar el error en la BD $error = Error::where('code', $apiResponse['error_code'])->first(); if (!$error) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => 'Código de error no encontrado en el catálogo', 'error_code' => $apiResponse['error_code'], 'error_message' => $apiResponse['error_message'], ]); } // Guardar error en Record $record->update([ 'error_id' => $error->id, 'api_response' => $apiResponse, 'error_occurred_at' => now(), ]); DB::commit(); // Retornar con error return ApiResponse::OK->response([ 'message' => 'Vehículo inscrito con error. Corrija los datos usando la función de actualización.', 'has_error' => true, 'can_update' => true, 'record_id' => $record->id, 'error' => [ 'code' => $error->code, 'description' => $error->description, 'occurred_at' => $record->error_occurred_at->toDateTimeString(), ], 'record' => [ 'id' => $record->id, 'folio' => $record->folio, ], 'vehicle' => $vehicleData, 'owner' => $ownerData, 'files' => $uploadedFiles, 'total_files' => count($uploadedFiles), ]); } // Si NO hay error, guardar respuesta exitosa $record->update([ 'error_id' => null, 'api_response' => $apiResponse, 'error_occurred_at' => null, ]); DB::commit(); // Responder con éxito return ApiResponse::OK->response([ 'message' => 'Vehículo inscrito exitosamente', 'has_error' => false, 'stolen' => false, 'record' => [ 'id' => $record->id, 'folio' => $record->folio, 'vehicle_id' => $vehicle->id, 'user_id' => $record->user_id, 'created_at' => $record->created_at->toDateTimeString(), ], 'vehicle' => [ 'id' => $vehicle->id, 'placa' => $vehicle->placa, 'numero_serie' => $vehicle->numero_serie, 'marca' => $vehicle->marca, 'modelo' => $vehicle->modelo, 'color' => $vehicle->color, ], 'owner' => [ 'id' => $owner->id, 'full_name' => $owner->full_name, 'rfc' => $owner->rfc, ], 'tag' => [ 'id' => $tag->id, 'folio' => $tag->folio, ], 'files' => $uploadedFiles, 'total_files' => count($uploadedFiles), ]); } catch (\Exception $e) { DB::rollBack(); Log::error('Error en inscripcionVehiculo: ' . $e->getMessage(), [ 'folio' => $folio ?? null, 'tag_id' => $tagId ?? null, 'trace' => $e->getTraceAsString() ]); return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error al procesar la inscripción del vehículo', 'error' => $e->getMessage(), ]); } } private function checkIfStolen(string $folio): bool { // Aquí api servicio de REPUVE Nacional // simulamos con random return (bool) rand(0, 1); } private function sendToRepuveNacional(string $folio, int $tagId, array $vehicleData): array { // Enviar datos a API Repuve Nacional // Aquí se haría la llamada real a la API de Repuve Nacional // Por ahora simulamos respuestas aleatorias usando la tabla errors $hasError = (bool) rand(0, 1); if ($hasError) { // Obtener un error aleatorio de la tabla errors $error = Error::inRandomOrder()->first(); if (!$error) { // Si no hay errores en la tabla, retornar error genérico return [ 'has_error' => true, 'error_code' => 'ERR_UNKNOWN', 'error_message' => 'No hay errores registrados en el catálogo', 'timestamp' => now()->toDateTimeString(), 'folio' => $folio, 'tag_id' => $tagId, 'response_data' => null, ]; } return [ 'has_error' => true, 'error_code' => $error->code, 'error_message' => $error->description, 'timestamp' => now()->toDateTimeString(), 'folio' => $folio, 'tag_id' => $tagId, 'response_data' => null, ]; } // Respuesta exitosa return [ 'has_error' => false, 'error_code' => null, 'error_message' => null, 'timestamp' => now()->toDateTimeString(), 'folio' => $folio, 'tag_id' => $tagId, 'response_data' => [ 'status' => 'success', 'repuve_id' => 'REPUVE-' . strtoupper(uniqid()), 'validated' => true, ], ]; } public function searchRecord(Request $request) { $request->validate([ 'folio' => 'nullable|string', 'placa' => 'nullable|string', 'niv' => 'nullable|string', 'per_page' => 'nullable|integer|min:1|max:20', ], [ 'required_without_all' => 'Debe proporcionar al menos uno de los siguientes: folio, placa o NIV.' ]); if (!$request->filled('folio') && !$request->filled('placa') && !$request->filled('niv')) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'Debe proporcionar al menos uno de los siguientes parámetros: folio, placa o niv.' ]); } $query = Record::with(['vehicle.owner', 'vehicle.tag', 'files', 'user', 'error'])->orderBy('created_at', 'desc'); if ($request->filled('folio')) { $query->where('folio', 'LIKE', '%' . $request->input('folio') . '%'); } elseif ($request->filled('placa')) { $query->whereHas('vehicle', function ($q) use ($request) { $q->where('placa', 'LIKE', '%' . $request->input('placa') . '%'); }); } elseif ($request->filled('niv')) { $query->whereHas('vehicle', function ($q) use ($request) { $q->where('numero_serie', 'LIKE', '%' . $request->input('niv') . '%'); }); } $perPage = $request->input('per_page', 20); $records = $query->paginate($perPage); if ($records->isEmpty()) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontraron expedientes con los criterios proporcionados.', 'records' => [], 'pagination' => [ 'current_page' => 1, 'total_pages' => 0, 'total_records' => 0, 'per_page' => $perPage, ], ]); } return ApiResponse::OK->response([ 'message' => 'Expedientes encontrados exitosamente', 'records' => $records->map(function ($record) { return [ 'id' => $record->id, 'folio' => $record->folio, 'vehicle' => [ 'id' => $record->vehicle->id, 'placa' => $record->vehicle->placa, 'numero_serie' => $record->vehicle->numero_serie, 'marca' => $record->vehicle->marca, 'modelo' => $record->vehicle->modelo, 'color' => $record->vehicle->color, 'tipo' => $record->vehicle->tipo, ], 'owner' => [ 'id' => $record->vehicle->owner->id, 'full_name' => $record->vehicle->owner->full_name, 'rfc' => $record->vehicle->owner->rfc, ], 'files' => $record->files->map(function ($file) { return [ 'id' => $file->id, 'name' => $file->name, 'path' => $file->path, 'url' => $file->url, 'md5' => $file->md5, ]; }), ]; }), 'pagination' => [ 'current_page' => $records->currentPage(), 'total_pages' => $records->lastPage(), 'total_records' => $records->total(), 'per_page' => $records->perPage(), 'from' => $records->firstItem(), 'to' => $records->lastItem(), ], ]); } private function getVehicle(): array { return [ "ANIO_PLACA" => "2020", "PLACA" => "WNU700B", "NO_SERIE" => "LSGHD52H0ND032457", "RFC" => "GME111116GJA", "FOLIO" => "EXP-2025-201030", "VIGENCIA" => "2025", "FECHA_IMPRESION" => "10-01-2025", "QR_HASH" => "Vu5TF4kYsbbltzjDdGQyenKfZoIk2wro34a5Gkh9JVh0CFxfPlrd92YEWK21JF.nLjQNyzKmqRvWYuPiS.kU7A--", "VALIDO" => true, "FOLIOTEMP" => false, "NOMBRE" => "GOLSYSTEMS DE MEXICO S DE RL DE CV", "NOMBRE2" => "GOLS*MS DXICOE RL*CV", "MUNICIPIO" => "CENTRO", "LOCALIDAD" => "VILLAHERMOSA", "CALLE" => "C BUGAMBILIAS 118 ", "CALLE2" => "C BU*ILIA*18 ", "TIPO" => "SEDAN", "TIPO_SERVICIO" => "PARTICULAR", "MARCA" => "CHEVROLET G.M.C.", "LINEA" => "AVEO", "SUBLINEA" => "PAQ. \"A\" LS", "MODELO" => 2022, "NUMERO_SERIE" => "LSGHD52H0ND032457", "NUMERO_MOTOR" => "H. EN WUHANLL,SGM", "DESCRIPCION_ORIGEN" => "IMPORTADO", "COLOR" => "BLANCO", "CODIGO_POSTAL" => "86179", "SERIE_FOLIO" => "D3962243", "SFOLIO" => "3962243" ]; } private function getOwner(): array { return [ 'name' => 'Nicolas', 'paternal' => 'Hernandez', 'maternal' => 'Castillo', 'rfc' => 'HECN660509HTCRSC01', 'curp' => 'HECN660509HTCRSC01', 'address' => 'Fracc Pomoca, Calle Armadillo MZ9 LT28', ]; } /** * Listar TAGs filtrados por status */ public function listTags(Request $request) { try { $request->validate([ 'status' => 'required|in:available,assigned,cancelled,lost', 'per_page' => 'nullable|integer|min:1|max:100', ], [ 'status.required' => 'El parámetro status es requerido', 'status.in' => 'El status debe ser uno de: available, assigned, cancelled, lost', ]); $status = $request->input('status'); $perPage = $request->input('per_page', 20); // Query base $query = Tag::where('status', $status); // Cargar relaciones según el status if ($status === 'assigned') { // Si está asignado, cargar vehículo y propietario $query->with(['vehicle.owner', 'package']); } else { // Si no está asignado, solo cargar paquete $query->with('package'); } // Ordenar por más reciente $query->orderBy('created_at', 'desc'); // Paginar $tags = $query->paginate($perPage); if ($tags->isEmpty()) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontraron tags con el status: ' . $status, 'tags' => [], 'pagination' => [ 'current_page' => 1, 'total_pages' => 0, 'total' => 0, 'per_page' => $perPage, ], ]); } return ApiResponse::OK->response([ 'message' => 'Tags encontrados exitosamente', 'status_filter' => $status, 'tags' => $tags->map(function ($tag) use ($status) { $tagData = [ 'id' => $tag->id, 'folio' => $tag->folio, 'status' => $tag->status, 'package' => $tag->package ? [ 'id' => $tag->package->id, 'lot' => $tag->package->lot, 'box_number' => $tag->package->box_number, ] : null, 'created_at' => $tag->created_at->format('Y-m-d H:i:s'), 'updated_at' => $tag->updated_at->format('Y-m-d H:i:s'), ]; // Si el tag está asignado, agregar información del vehículo if ($status === 'assigned' && $tag->vehicle) { $tagData['vehicle'] = [ 'id' => $tag->vehicle->id, 'placa' => $tag->vehicle->placa, 'numero_serie' => $tag->vehicle->numero_serie, 'marca' => $tag->vehicle->marca, 'modelo' => $tag->vehicle->modelo, 'color' => $tag->vehicle->color, 'owner' => $tag->vehicle->owner ? [ 'id' => $tag->vehicle->owner->id, 'full_name' => $tag->vehicle->owner->full_name, 'rfc' => $tag->vehicle->owner->rfc, ] : null, ]; } else { $tagData['vehicle'] = null; } return $tagData; }), 'pagination' => [ 'current_page' => $tags->currentPage(), 'total_pages' => $tags->lastPage(), 'total' => $tags->total(), 'per_page' => $tags->perPage(), 'from' => $tags->firstItem(), 'to' => $tags->lastItem(), ], ]); } catch (\Illuminate\Validation\ValidationException $e) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error de validación', 'errors' => $e->errors(), ]); } catch (\Exception $e) { Log::error('Error al listar tags: ' . $e->getMessage(), [ 'trace' => $e->getTraceAsString() ]); return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al listar tags', 'error' => $e->getMessage(), ]); } } }