input('record_id'); // Buscar vehículo por folio y tag $record = Record::with(['vehicle.owner', 'vehicle.tag', 'files', 'error'])->find($recordId); // Validar que el tag if (!$record) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'No se encontró el expediente', 'record_id' => $recordId, ]); } $vehicle = $record->vehicle; $tag = $vehicle->tag; if (!$tag) { return ApiResponse::NOT_FOUND->response([ 'message' => 'El vehículo no tiene tag asociado', 'vehicle_id' => $vehicle->placa, ]); } // Consultar API Repuve Nacional (verificar robo) $isStolen = $this->checkIfStolen($record->folio); if ($isStolen) { return ApiResponse::FORBIDDEN->response([ 'record_id' => $recordId, 'tag_number' => $tag->tag_number, 'stolen' => true, 'message' => 'El vehículo reporta robo. No se puede continuar con la actualización.', ]); } // Iniciar transacción DB::beginTransaction(); // Obtener datos del vehículo de API Estatal $vehicleData = $this->getVehicle2(); $ownerData = $this->getOwner(); // Actualizar propietario $owner = Owner::updateOrCreate( ['rfc' => $ownerData['rfc']], [ 'name' => $ownerData['name'], 'paternal' => $ownerData['paternal'], 'maternal' => $ownerData['maternal'], 'curp' => $ownerData['curp'], 'address' => $ownerData['address'], ] ); // Actualizar vehículo $vehicle->update([ 'anio_placa' => $vehicleData['ANIO_PLACA'], // NO actualizar 'placa' - es UNIQUE // NO actualizar 'numero_serie' - es UNIQUE (NIV/VIN) 'rfc' => $vehicleData['RFC'], // NO actualizar 'folio' => $folio, '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'], // NO actualizar 'serie_folio' - es UNIQUE // NO actualizar 'sfolio' - es UNIQUE // NO actualizar 'nrpv' - es UNIQUE (mantener el original) 'owner_id' => $owner->id, ]); // Procesar archivos si existen $uploadedFiles = []; if ($request->hasFile('files')) { $files = $request->file('files'); $fileNames = $request->input('names', []); foreach ($files as $index => $file) { $customName = $fileNames[$index] ?? "archivo_" . ($index + 1); $customName = str_replace(' ', '_', $customName); $extension = $file->getClientOriginalExtension(); $fileName = $customName . '_' . time() . '.' . $extension; $path = $file->storeAs('/', $fileName, 'records'); $md5 = md5_file($file->getRealPath()); $fileRecord = File::create([ 'name' => $customName, 'path' => $path, 'md5' => $md5, 'record_id' => $record->id, ]); $uploadedFiles[] = [ 'id' => $fileRecord->id, 'name' => $fileRecord->name, 'path' => $fileRecord->path, 'url' => $fileRecord->url, ]; } } // Enviar datos a API Repuve Nacional $apiResponse = $this->sendToRepuveNacional($vehicle->numero_serie); $apiResponse["repuve_response"]["folio_ci"] = $record->folio; $apiResponse["repuve_response"]["identificador_ci"] = $tag->tag_number; // Procesar respuesta de la API if (isset($apiResponse['has_error']) && $apiResponse['has_error']) { // Si hay error, busca 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'], ]); } // guarda error $record->update([ 'error_id' => $error->id, 'api_response' => $apiResponse, 'error_occurred_at' => now(), ]); DB::commit(); // Retornar datos por error para que el usuario corrija // Al volver a llamar esta función con datos corregidos, se repetirá el ciclo return ApiResponse::OK->response([ 'message' => 'Datos guardados con error. Corrija los datos y vuelva a enviar.', 'has_error' => true, 'can_update' => true, '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, 'new_files' => $uploadedFiles, 'total_new_files' => count($uploadedFiles), 'existing_files' => $record->files->map(function ($file) { return [ 'id' => $file->id, 'name' => $file->name, 'path' => $file->path, 'url' => $file->url, ]; }), ]); } // Si NO hay error, guarda registro OK y quita errores previos $record->update([ 'error_id' => null, 'api_response' => $apiResponse, 'error_occurred_at' => null, ]); DB::commit(); return ApiResponse::OK->response([ 'message' => 'Vehículo actualizado exitosamente', 'has_error' => false, 'record' => [ 'id' => $record->id, 'folio' => $record->folio, 'vehicle_id' => $vehicle->id, 'user_id' => $record->user_id, 'updated_at' => $record->updated_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, 'tag_number' => $tag->tag_number, 'status' => $tag->status, ], 'new_files' => $uploadedFiles, 'total_new_files' => count($uploadedFiles), 'existing_files' => $record->files->map(function ($file) { return [ 'id' => $file->id, 'name' => $file->name, 'url' => $file->url, ]; }), 'total_files' => $record->files->count() + count($uploadedFiles), ]); } catch (\Exception $e) { DB::rollBack(); return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error al actualizar el 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 $vin): array { // Enviar datos a API Repuve Nacional // Aquí se haría la llamada real a la API de Repuve Nacional // Por ahora simulamos con mock igual que InscriptionController // Respuesta exitosa mockup REPUVE $mockResponse = "OK:SIN INFORMACION|OTROS|SIN INFORMACION|10/07/2023|CENTRO|$vin|WSA548B|HR16777934V|2023|BLANCO|ADVANCE|TABASCO|NISSAN|MARCH|PARTICULAR|AUTOMOVIL|ACTIVO|null||null|0|null|0|10337954|E2003412012BB0C130FAD04D|Sin observaciones "; // Parsear la cadena a JSON usando los campos oficiales $fields = [ 'marca', 'submarca', 'tipo_vehiculo', 'fecha_expedicion', 'oficina', 'niv', 'placa', 'motor', 'modelo', 'color', 'version', 'entidad', 'marca_padron', 'submarca_padron', 'tipo_uso_padron', 'tipo_vehiculo_padron', 'estatus_registro', 'aduana', 'nombre_aduana', 'patente', 'pedimento', 'fecha_pedimento', 'clave_importador', 'folio_ci', 'identificador_ci', 'observaciones' ]; $values = explode('|', str_replace('OK:', '', $mockResponse)); $jsonResponse = []; foreach ($fields as $i => $field) { $jsonResponse[$field] = $values[$i] ?? null; } return [ 'has_error' => false, 'error_code' => null, 'error_message' => null, 'timestamp' => now()->toDateTimeString(), 'vin' => $vin, 'repuve_response' => $jsonResponse, ]; } private function getVehicle2(): array { return [ "ANIO_PLACA" => "2027", "PLACA" => "WNU730X", "NO_SERIE" => "EXP-2025-201030", "RFC" => "GME111116GJA", "FOLIO" => "EXP-2025-201030", "VIGENCIA" => "2026", "FECHA_IMPRESION" => "10-01-2025", "QR_HASH" => "Vu5TF4kYsbbltzjDdGQyenKfZoIk2wro34a5Gkh9JVh0CFxfPlrd92YEWK21JF.nLjQNyzKmqRvWYuPiS.kU7A--", "VALIDO" => true, "NOMBRE" => "GOLSYSTEMS DE MEXICO S DE RL DE CV", "NOMBRE2" => "GOLS*MS DXICOE RL*CV", "MUNICIPIO" => "CENTRO", "LOCALIDAD" => "VILLAHERMOSA", "CALLE" => "C BUGAMBILIAS 119 ", "CALLE2" => "C BU*ILIA*18 ", "TIPO" => "SEDAN", "TIPO_SERVICIO" => "PARTICULAR", "MARCA" => "CHEVROLET G.M.C.", "LINEA" => "AVEO", "SUBLINEA" => "PAQ. \"A\" LS", "MODELO" => 2023, "NUMERO_SERIE" => "EXP-2025-201030", "NUMERO_MOTOR" => "H. EN WUHANLL,SGM", "DESCRIPCION_ORIGEN" => "IMPORTADO", "COLOR" => "AZUL", "CODIGO_POSTAL" => "86181", "SERIE_FOLIO" => "D3962242", "SFOLIO" => "EXP-2025-201030" ]; } private function getOwner(): array { return [ 'name' => 'Nicolas', 'paternal' => 'Hernandez', 'maternal' => 'Castillo', 'rfc' => 'HECN660509HTCRSC01', 'curp' => 'HECN660509HTCRSC01', 'address' => 'Fracc Pomoca, Calle Armadillo MZ9 LT28', ]; } }