repuveService = $repuveService; $this->padronEstatalService = $padronEstatalService; } public function vehicleData(Request $request) { try { $request->validate([ 'folio' => 'required|string|exists:records,folio', 'tag_number' => 'required|string|exists:tags,tag_number', 'niv' => 'required|string|exists:vehicle,niv' ]); $folio = $request->input('folio'); $tagNumber = $request->input('tag_number'); $niv = $request->input('niv'); $isStolen = $this->checkIfStolen($niv); if ($isStolen) { return ApiResponse::FORBIDDEN->response([ 'message' => 'El vehículo reporta robo. No se puede continuar con la actualización.', ]); } $record = Record::with([ 'vehicle:id,owner_id,placa,niv,marca,linea,modelo,color', 'vehicle.owner:id,name,paternal,maternal,rfc', 'vehicle.tag:id,vehicle_id,folio,tag_number,status_id', 'files:id,record_id,name_id,path,md5', 'files.catalogName:id,name', 'user:id,name,email', 'error:id,code,description' ]) ->select([ 'id', 'folio', 'vehicle_id', 'user_id', 'error_id', 'created_at', 'updated_at' ])->where('folio', $folio) ->first(); if (!$record) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontró el expediente', ]); } $vehicle = $record->vehicle; $tag = $vehicle->tag; if (!$tag || $tag->tag_number !== $tagNumber) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El tag_number no coincide con el registrado en el expediente', ]); } if ($vehicle->niv !== $niv) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El NIV no coincide con el registrado en el expediente', ]); } // Consultar REPUVE Estatal para obtener datos actualizados $vehicleDataEstatal = $this->getVehicle($niv); $ownerDataEstatal = $this->getOwner($niv); // Guardar en caché por 30 minutos Cache::put("update_vehicle_{$niv}", $vehicleDataEstatal, 1800); Cache::put("update_owner_{$niv}", $ownerDataEstatal, 1800); // Detectar si hay cambios entre BD y padrón estatal $hasVehicleChanges = $this->detectVehicleChanges($vehicle, $vehicleDataEstatal); $hasOwnerChanges = $this->detectOwnerChanges($vehicle->owner, $ownerDataEstatal); return ApiResponse::OK->response([ 'message' => 'Datos del vehículo obtenidos exitosamente', 'current_data' => $record, 'estatal_data' => [ 'vehicle' => $vehicleDataEstatal, 'owner' => $ownerDataEstatal, ], 'has_changes' => $hasVehicleChanges || $hasOwnerChanges, 'changes_detail' => [ 'vehicle' => $hasVehicleChanges, 'owner' => $hasOwnerChanges, ], ]); } catch (Exception $e) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error de validación', 'error' => $e->getMessage(), ]); } } public function vehicleUpdate(VehicleUpdateRequest $request) { try { $folio = $request->input('folio'); $tagNumber = $request->input('tag_number'); $niv = $request->input('niv'); $isStolen = $this->checkIfStolen($niv); if ($isStolen) { return ApiResponse::FORBIDDEN->response([ 'niv' => $niv, 'stolen' => true, 'message' => 'El vehículo reporta robo. No se puede continuar con la actualización.', ]); } $record = Record::with(['vehicle.owner', 'vehicle.tag', 'files', 'error']) ->where('folio', $folio) ->first(); if (!$record) { return ApiResponse::NOT_FOUND->response([ 'message' => 'No se encontró el expediente', 'folio' => $folio, ]); } $vehicle = $record->vehicle; $tag = $vehicle->tag; if (!$tag) { return ApiResponse::NOT_FOUND->response([ 'message' => 'El TAG no coincide con el registrado en el expediente', 'tag_number' => $tagNumber, ]); } if ($vehicle->niv !== $niv) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'El NIV no coincide con el registrado en el expediente', ]); } DB::beginTransaction(); $isManualUpdate = $request->has('vehicle') || $request->has('owner'); $hasVehicleChanges = false; $hasOwnerChanges = false; $owner = $vehicle->owner; if ($isManualUpdate) { if ($request->has('vehicle')) { $vehicleData = $request->input('vehicle', []); $allowedVehicleFields = [ 'placa', 'marca', 'linea', 'sublinea', 'modelo', 'color', 'numero_motor', 'clase_veh', 'tipo_servicio', 'rfv', 'ofcexpedicion', 'fechaexpedicion', 'tipo_veh', 'numptas', 'observac', 'cve_vehi', 'nrpv', 'tipo_mov' ]; $vehicleData = array_filter( array_intersect_key($vehicleData, array_flip($allowedVehicleFields)), fn($value) => $value !== null && $value !== '' ); if (!empty($vehicleData)) { $vehicle->update($vehicleData); $hasVehicleChanges = true; } } if ($request->has('owner')) { $ownerInput = $request->input('owner', []); $allowedOwnerFields = [ 'name', 'paternal', 'maternal', 'rfc', 'curp', 'address', 'tipopers', 'pasaporte', 'licencia', 'ent_fed', 'munic', 'callep', 'num_ext', 'num_int', 'colonia', 'cp', 'telefono' ]; $ownerData = array_filter( array_intersect_key($ownerInput, array_flip($allowedOwnerFields)), fn($value) => $value !== null && $value !== '' ); if (!empty($ownerData)) { // Si se cambió el RFC, buscar/crear otro propietario if (isset($ownerData['rfc']) && $ownerData['rfc'] !== $owner->rfc) { $owner = Owner::updateOrCreate( ['rfc' => $ownerData['rfc']], $ownerData ); $vehicle->update(['owner_id' => $owner->id]); } else { // Actualizar propietario existente $owner->update($ownerData); } $hasOwnerChanges = true; } } } else { // Intentar obtener datos del caché primero $vehicleDataEstatal = Cache::get("update_vehicle_{$niv}"); $ownerDataEstatal = Cache::get("update_owner_{$niv}"); // Si no hay if (!$vehicleDataEstatal || !$ownerDataEstatal) { $vehicleDataEstatal = $this->getVehicle($niv); $ownerDataEstatal = $this->getOwner($niv); } // Limpiar caché Cache::forget("update_vehicle_{$niv}"); Cache::forget("update_owner_{$niv}"); // Detectar si hay cambios $hasVehicleChanges = $this->detectVehicleChanges($vehicle, $vehicleDataEstatal); $hasOwnerChanges = $this->detectOwnerChanges($vehicle->owner, $ownerDataEstatal); // Actualizar vehículo solo si hay cambios if ($hasVehicleChanges) { $vehicle->update($vehicleDataEstatal); } // Actualizar propietario solo si hay cambios $owner = $vehicle->owner; if ($hasOwnerChanges) { $owner = Owner::updateOrCreate( ['rfc' => $ownerDataEstatal['rfc']], $ownerDataEstatal ); $vehicle->update(['owner_id' => $owner->id]); } } $uploadedFiles = []; $replacedFiles = []; if ($request->hasFile('files')) { $files = $request->file('files'); $nameIds = $request->input('name_id', []); if (!empty($nameIds)) { $validIds = CatalogNameImg::whereIn('id', $nameIds)->pluck('id')->toArray(); if (count($validIds) !== count($nameIds)) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => 'Algunos ids del catálogo de nombres no son válidos', 'provided_id' => $nameIds, 'valid_id' => $validIds, ]); } } foreach ($files as $indx => $file) { $nameId = $nameIds[$indx] ?? null; if ($nameId === null) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => "Falta el nombre para el archivo, busca en el catálogo de nombres", ]); } $existingFile = File::where('record_id', $record->id) ->where('name_id', $nameId) ->first(); if ($existingFile) { Storage::disk('public')->delete($existingFile->path); $replacedFiles[] = [ 'id' => $existingFile->id, 'name_id' => $nameId, 'old_path' => $existingFile->path, ]; $existingFile->delete(); } // Obtener el nombre del catálogo para el nombre del archivo $catalogName = CatalogNameImg::find($nameId); $extension = $file->getClientOriginalExtension(); $fileName = $catalogName->name . '_' . date('dmY_His') . '.' . $extension; $path = $file->storeAs("records/{$record->folio}", $fileName, 'public'); $md5 = md5_file($file->getRealPath()); $fileRecord = File::create([ 'name_id' => $nameId, 'path' => $path, 'md5' => $md5, 'record_id' => $record->id, ]); $uploadedFiles[] = [ 'id' => $fileRecord->id, 'name' => $catalogName->name, 'path' => $fileRecord->path, 'url' => $fileRecord->url, 'replaced' => $existingFile !== null, ]; } } if ($hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0) { VehicleTagLog::create([ 'vehicle_id' => $vehicle->id, 'tag_id' => $tag->id, 'action_type' => 'actualizacion', 'performed_by' => Auth::id() ]); } // Solo enviar a REPUVE Nacional si hay cambios if ($hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0) { // Preparar datos completos para REPUVE $datosCompletos = $this->prepararDatosParaInscripcion($niv); // Enviar job ProcessRepuveResponse::dispatch($record->id, $datosCompletos); } DB::commit(); $sentToRepuve = $hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0; $message = 'Vehículo actualizado exitosamente'; if (!$hasVehicleChanges && !$hasOwnerChanges && empty($uploadedFiles)) { $message = 'No se detectaron cambios. Los datos ya estaban actualizados.'; } elseif (($hasVehicleChanges || $hasOwnerChanges) && !empty($uploadedFiles)) { $message = 'Datos del vehículo/propietario y archivos actualizados exitosamente.'; } elseif (($hasVehicleChanges || $hasOwnerChanges) && empty($uploadedFiles)) { $message = 'Datos del vehículo/propietario actualizados exitosamente. No se subieron archivos.'; } elseif (!$hasVehicleChanges && !$hasOwnerChanges && !empty($uploadedFiles)) { $message = 'Archivos subidos exitosamente. No hubo cambios en los datos del vehículo/propietario.'; } return ApiResponse::OK->response([ 'message' => $message, 'has_error' => false, 'changes_made' => [ 'vehicle_updated' => $hasVehicleChanges, 'owner_updated' => $hasOwnerChanges, 'files_uploaded' => count($uploadedFiles) > 0, ], 'record' => [ 'id' => $record->id, 'folio' => $record->folio, 'updated_at' => $record->updated_at->toDateTimeString(), ], 'vehicle' => [ 'id' => $vehicle->id, 'placa' => $vehicle->placa, 'niv' => $vehicle->niv, '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, 'tag_number' => $tag->tag_number, 'status' => $tag->status->name, ], 'uploaded_files' => $uploadedFiles, 'replaced_count' => count($replacedFiles), 'total_files' => File::where('record_id', $record->id)->count(), ]); DB::commit(); $sentToRepuve = $hasVehicleChanges || $hasOwnerChanges || count($uploadedFiles) > 0; $message = 'Vehículo actualizado exitosamente'; if (!$hasVehicleChanges && !$hasOwnerChanges && empty($uploadedFiles)) { $message = 'No se detectaron cambios. Los datos ya estaban actualizados.'; } elseif (!$hasVehicleChanges && !$hasOwnerChanges && empty($uploadedFiles)) { $message = 'Solo se actualizaron archivos. Los datos del vehículo/propietario no cambiaron.'; } elseif (($hasVehicleChanges || $hasOwnerChanges) && empty($uploadedFiles)) { $message = 'Datos del vehículo/propietario actualizados exitosamente. No se subieron archivos.'; } elseif ((!$hasVehicleChanges && !$hasOwnerChanges) && !empty($uploadedFiles)) { $message = 'Archivos subidos exitosamente. No hubo cambios en los datos del vehículo/propietario.'; } return ApiResponse::OK->response([ 'message' => $message, 'has_error' => false, 'sent_to_repuve' => $sentToRepuve, 'changes_made' => [ 'vehicle_updated' => $hasVehicleChanges, 'owner_updated' => $hasOwnerChanges, 'files_uploaded' => count($uploadedFiles) > 0, ], 'record' => [ 'id' => $record->id, 'folio' => $record->folio, 'updated_at' => $record->updated_at->toDateTimeString(), ], 'vehicle' => [ 'id' => $vehicle->id, 'placa' => $vehicle->placa, 'niv' => $vehicle->niv, '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, 'tag_number' => $tag->tag_number, 'status' => $tag->status->name, ], 'uploaded_files' => $uploadedFiles, 'replaced_count' => count($replacedFiles), 'total_files' => File::where('record_id', $record->id)->count(), ]); } catch (Exception $e) { return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al actualizar el vehículo', 'error' => $e->getMessage(), ]); } } private function checkIfStolen(string $niv): bool { return $this->repuveService->verificarRobo($niv); } private function prepararDatosParaInscripcion(string $niv): array { $datos = $this->padronEstatalService->getVehiculoByNiv($niv); return [ 'ent_fed' => $datos['ent_fed'] ?? '', 'ofcexp' => $datos['ofcexp'] ?? '', 'fechaexp' => $datos['fechaexp'] ?? '', 'placa' => $datos['placa'] ?? '', 'tarjetacir' => $datos['tarjetacir'] ?? '', 'marca' => $datos['marca'] ?? '', 'submarca' => $datos['submarca'] ?? '', 'version' => $datos['version'] ?? '', 'clase_veh' => $datos['clase_veh'] ?? '', 'tipo_veh' => $datos['tipo_veh'] ?? '', 'tipo_uso' => $datos['tipo_uso'] ?? '', 'modelo' => $datos['modelo'] ?? '', 'color' => $datos['color'] ?? '', 'motor' => $datos['motor'] ?? '', 'niv' => $datos['niv'] ?? '', 'rfv' => $datos['rfv'] ?? '', 'numptas' => $datos['numptas'] ?? '', 'observac' => $datos['observac'] ?? '', 'tipopers' => $datos['tipopers'] ?? '', 'curp' => $datos['curp'] ?? '', 'rfc' => $datos['rfc'] ?? '', 'pasaporte' => $datos['pasaporte'] ?? '', 'licencia' => $datos['licencia'] ?? '', 'nombre' => $datos['nombre'] ?? '', 'ap_paterno' => $datos['ap_paterno'] ?? '', 'ap_materno' => $datos['ap_materno'] ?? '', 'munic' => $datos['munic'] ?? '', 'callep' => $datos['callep'] ?? '', 'num_ext' => $datos['num_ext'] ?? '', 'num_int' => $datos['num_int'] ?? '', 'colonia' => $datos['colonia'] ?? '', 'cp' => $datos['cp'] ?? '', 'cve_vehi' => $datos['cve_vehi'] ?? '', 'nrpv' => $datos['nrpv'] ?? '', 'tipo_mov' => $datos['tipo_mov'] ?? '', ]; } private function detectVehicleChanges($vehicle, array $vehicleDataEstatal): bool { $fieldsToCompare = [ 'placa', 'marca', 'linea', 'sublinea', 'modelo', 'color', 'numero_motor', 'clase_veh', 'tipo_servicio', 'rfv', 'ofcexpedicion', 'tipo_veh', 'numptas', 'observac', 'cve_vehi', 'nrpv', 'tipo_mov' ]; foreach ($fieldsToCompare as $field) { $bdValue = $vehicle->$field ?? null; $estatalValue = $vehicleDataEstatal[$field] ?? null; if (strval($bdValue) !== strval($estatalValue)) { return true; } } return false; } private function detectOwnerChanges($owner, array $ownerDataEstatal): bool { $fieldsToCompare = [ 'name', 'paternal', 'maternal', 'rfc', 'curp', 'address', 'tipopers', 'pasaporte', 'licencia', 'ent_fed', 'munic', 'callep', 'num_ext', 'num_int', 'colonia', 'cp' ]; foreach ($fieldsToCompare as $field) { $bdValue = $owner->$field ?? null; $estatalValue = $ownerDataEstatal[$field] ?? null; if (strval($bdValue) !== strval($estatalValue)) { return true; } } return false; } private function getVehicle(string $niv): array { $datos = $this->padronEstatalService->getVehiculoByNiv($niv); return $this->padronEstatalService->extraerDatosVehiculo($datos); } private function getOwner(string $niv): array { $datos = $this->padronEstatalService->getVehiculoByNiv($niv); return $this->padronEstatalService->extraerDatosPropietario($datos); } }