config->getRemoteDirForPresupuesto($presupuestoId); if (!$this->ftp->exists($remoteDir)) { if (!$this->ftp->mkdir($remoteDir, true)) { return [ 'success' => false, 'message' => "No se pudo crear el directorio remoto: $remoteDir" ]; } $this->ftp->chmod($remoteDir, 0755); } $files = $this->fileModel->getFiles($presupuestoId); $results = []; foreach ($files as $file) { $filename = basename($file->file_path); $localPath = WRITEPATH . $file->file_path; $remotePath = $remoteDir . '/' . $filename; if (!file_exists($localPath)) { $results[] = [ 'file' => $file->nombre, 'remotePath' => $remotePath, 'success' => false, 'error' => 'Archivo local no encontrado' ]; continue; } $ok = $this->ftp->upload($localPath, $remotePath); $results[] = [ 'file' => $file->nombre, 'remotePath' => $remotePath, 'success' => $ok ]; } $allOk = !in_array(false, array_column($results, 'success')); return [ 'success' => $allOk, 'files' => $results ]; } /** * Elimina todos los archivos actuales del presupuesto del SFTP. */ public function removeFromRemote(int $presupuestoId): array { $remoteDir = $this->config->getRemoteDirForPresupuesto($presupuestoId); $files = $this->fileModel->getFiles($presupuestoId); $results = []; foreach ($files as $file) { $filename = basename($file->file_path); $remotePath = $remoteDir . '/' . $filename; if ($this->ftp->exists($remotePath)) { $deleted = $this->ftp->delete($remotePath); $results[] = [ 'file' => $file->nombre, 'remotePath' => $remotePath, 'success' => $deleted, 'message' => $deleted ? 'Eliminado correctamente' : 'Falló al eliminar' ]; } else { $results[] = [ 'file' => $file->nombre, 'remotePath' => $remotePath, 'success' => false, 'message' => 'Archivo no encontrado en el SFTP' ]; } } return $results; } /** * Elimina del SFTP los archivos que ya no existen en la lista permitida. */ public function removeMissingFromRemote(int $presupuestoId, array $keepFileNames): void { $remoteDir = $this->config->getRemoteDirForPresupuesto($presupuestoId); $files = $this->fileModel->getFiles($presupuestoId); foreach ($files as $file) { if (!in_array($file->nombre, $keepFileNames)) { $remotePath = $remoteDir . '/' . basename($file->file_path); if ($this->ftp->exists($remotePath)) { $this->ftp->delete($remotePath); } } } } /** * Descarga archivos de SFTP y genera un ZIP temporal para el presupuesto dado. * * @param int $presupuestoId ID del presupuesto. * @param string|null $prefijo Prefijo para los nombres de los archivos. * @return array Estructura: ['success' => bool, 'message' => string, 'zipPath' => string|null] */ public function downloadZip(int $presupuestoId, ?string $prefijo = null): array { $files = $this->fileModel->getFiles($presupuestoId); if (empty($files)) { return [ 'success' => false, 'message' => "No hay archivos en la base de datos para el presupuesto ID {$presupuestoId}.", 'zipPath' => null ]; } $remoteDir = $this->config->getRemoteDirForPresupuesto($presupuestoId); if (!$this->ftp->exists($remoteDir)) { return [ 'success' => false, 'message' => "El directorio remoto no existe: {$remoteDir}", 'zipPath' => null ]; } $localTempDir = WRITEPATH . 'zip_presupuestos/' . uniqid("presupuesto_"); if (!is_dir($localTempDir) && !mkdir($localTempDir, 0777, true)) { return [ 'success' => false, 'message' => "No se pudo crear el directorio temporal en: {$localTempDir}", 'zipPath' => null ]; } $erroresDescarga = []; foreach ($files as $file) { $originalName = $file->nombre ?? basename($file->file_path ?? ''); $prefixedName = $prefijo ? $prefijo . '_' . $originalName : $originalName; $localFile = $localTempDir . '/' . $prefixedName; $remoteFile = $remoteDir . '/' . basename($file->file_path ?? ''); if (!$this->ftp->get($remoteFile, $localFile)) { $erroresDescarga[] = "Error al descargar: {$remoteFile}"; } } if (count($erroresDescarga) === count($files)) { return [ 'success' => false, 'message' => "Fallo al descargar todos los archivos:\n" . implode("\n", $erroresDescarga), 'zipPath' => null ]; } $zipPath = $localTempDir . '.zip'; $zip = new \ZipArchive(); if (!$zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { return [ 'success' => false, 'message' => "No se pudo crear el archivo ZIP: {$zipPath}", 'zipPath' => null ]; } foreach (glob($localTempDir . '/*') as $localFile) { $zip->addFile($localFile, basename($localFile)); } $zip->close(); foreach (glob($localTempDir . '/*') as $localFile) { unlink($localFile); } rmdir($localTempDir); if (!file_exists($zipPath)) { return [ 'success' => false, 'message' => "El ZIP no fue generado correctamente.", 'zipPath' => null ]; } return [ 'success' => true, 'message' => "ZIP generado correctamente.", 'zipPath' => $zipPath ]; } public function importarArchivosDesdeUrlsBubok(int $presupuestoId, array $urls): array { $resultados = []; $errores = []; foreach ($urls as $tipo => $url) { if (empty($url)) { $errores[] = "URL vacía para tipo: {$tipo}"; continue; } try { // Intenta descargar el contenido del archivo desde la URL $contenido = @file_get_contents($url); // Si no se puede descargar, se genera un archivo de error local (no se guarda en BBDD) if ($contenido === false || strlen($contenido) === 0) { $mensajeError = "ERROR: No se pudo descargar el archivo remoto para $tipo desde la URL: $url"; $nombreError = 'ERROR_' . strtoupper($tipo) . '.txt'; // Ruta local en la estructura estándar (no BBDD) $rutaError = $this->fileModel->getAbsolutePath($presupuestoId, $nombreError); // Crear el directorio si no existe $directorio = dirname($rutaError); if (!is_dir($directorio)) { mkdir($directorio, 0755, true); } // Guardar el archivo con el mensaje de error file_put_contents($rutaError, $mensajeError); // Registrar el error en el array de errores $errores[] = $mensajeError; continue; } // Nombre original y extensión del archivo descargado $nombreOriginal = basename(parse_url($url, PHP_URL_PATH)); $extension = pathinfo($nombreOriginal, PATHINFO_EXTENSION); // Registrar el archivo en la base de datos y obtener el nombre limpio $nombreLimpio = $this->fileModel->saveFileInBBDD( $presupuestoId, $nombreOriginal, $extension, auth()->id() ); if (!$nombreLimpio) { $errores[] = "No se pudo registrar '$nombreOriginal' en la base de datos."; continue; } // Obtener la ruta completa donde se guardará el archivo localmente $rutaAbsoluta = $this->fileModel->getAbsolutePath($presupuestoId, $nombreLimpio); // Crear el directorio si no existe $directorio = dirname($rutaAbsoluta); if (!is_dir($directorio)) { mkdir($directorio, 0755, true); } // Guardar el archivo en el sistema de archivos local file_put_contents($rutaAbsoluta, $contenido); // Añadir el archivo a la lista de resultados $resultados[] = [ 'nombre' => $nombreLimpio, 'tipo' => $tipo, 'ruta_local' => $rutaAbsoluta ]; } catch (\Throwable $e) { $errores[] = "Error inesperado procesando '$tipo': " . $e->getMessage(); } } // Subida de todos los archivos válidos al servidor remoto SFTP try { $this->uploadToRemote($presupuestoId); } catch (\Throwable $e) { $errores[] = "Error al subir archivos al SFTP: " . $e->getMessage(); } // Devolver el resumen del proceso return [ 'success' => empty($errores), 'archivos_subidos' => $resultados, 'errores' => $errores ]; } }