'D', 'category' => 'CÓDIGO DE EQUIPO'], ['letter' => 'H', 'category' => 'FICHA DE SEGURIDAD DE PRODUCTOS QUÍMICOS'], ['letter' => 'J', 'category' => 'FICHA TÉCNICA'], ['letter' => 'L', 'category' => 'FOTOGRAFÍAS - DIAGRAMAS'], ['letter' => 'N', 'category' => 'DATOS DEL PROVEEDOR'], ['letter' => 'P', 'category' => 'MANUALES DE OPERACIÓN'], ['letter' => 'R', 'category' => 'MANUALES DE MANTENIMIENTO PREVENTIVO'], ['letter' => 'T', 'category' => 'MANUALES DE MANTENIMIENTO CORRECTIVO'], ['letter' => 'V', 'category' => 'DOCUMENTOS ADICIONALES'] ]; // Mapping of file extensions to their types for validation private $extensionTypes = [ // Archivos de almacenamiento 'zip' => 'STORAGE', 'rar' => 'STORAGE', 'tar' => 'STORAGE', '7z' => 'STORAGE', // Formatos de audio 'mp3' => 'AUDIO', 'mpeg' => 'AUDIO', 'wav' => 'AUDIO', 'ogg' => 'AUDIO', 'opus' => 'AUDIO', // Archivos de imagen 'jpeg' => 'IMG', 'jpg' => 'IMG', 'png' => 'IMG', 'gif' => 'IMG', 'bmp' => 'IMG', 'tiff' => 'IMG', 'svg' => 'IMG', // Archivos de texto 'txt' => 'TEXT', // Archivos de video 'webm' => 'VIDEO', 'mpeg4' => 'VIDEO', 'mp4' => 'VIDEO', '3gpp' => 'VIDEO', 'mov' => 'VIDEO', 'avi' => 'VIDEO', 'wmv' => 'VIDEO', 'flv' => 'VIDEO', // Planos de Auto CAD 'dwg' => 'CAD', 'dxf' => 'CAD', // Modelos de 3D Studio '3ds' => '3D', // Dibujos de Illustrator 'ai' => 'ILLUSTRATOR', // Imágenes de Photoshop 'psd' => 'PHOTOSHOP', // Formato de documento portátil 'pdf' => 'PDF', // Hoja de cálculo en Excel 'xls' => 'EXCEL', 'xlsx' => 'EXCEL', // Presentaciones de PowerPoint 'ppt' => 'POWERPOINT', 'pptx' => 'POWERPOINT', // Documentos de texto de Word 'doc' => 'WORD', 'docx' => 'WORD', // Dibujos de Visio 'vsd' => 'VISIO', 'vsdx' => 'VISIO' ]; public function __construct() { $this->responseController = new ResponseController(); $this->encryptionController = new EncryptionController(); $this->documentManagementController = new DocumentManagementController(); $this->functionsController = new FunctionsController(); } /** * Main validation endpoint that validates both Excel and ZIP files * Checks Excel headers, ZIP integrity, file listings, and compares them */ public function validateFiles(Request $request) { ini_set('memory_limit', '-1'); Log::info('ValidateFiles: Iniciando validación de archivos'); // Validate request inputs $validator = Validator::make($request->all(), [ 'id_user' => 'required|string', 'linea' => 'required|integer', 'excel_file' => 'required|file|mimes:xlsx,xls', 'zip_file' => 'required|file|mimes:zip' ]); if ($validator->fails()) { return $this->responseController->makeResponse( true, 'Se encontraron uno o más errores.', $this->responseController->makeErrors($validator->errors()->messages()), 400 ); } $idUser = '0000000001'; // $idUser = $this->encryptionController->decrypt($request->input('id_user')); // if(!$idUser){ // return $this->responseController->makeResponse(true, "El id del usuario que realizó la petición no fue encriptado correctamente", [], 400); // } // Validate Excel file headers structure $excelValidation = $this->validateExcelHeaders($request->file('excel_file')); if ($excelValidation['error']) { return $this->responseController->makeResponse(true, $excelValidation['message'], [], 400); } Log::info('ValidateFiles: Headers Excel validados correctamente'); // Validate ZIP file integrity $zipValidation = $this->validateZipFile($request->file('zip_file')); if ($zipValidation['error']) { return $this->responseController->makeResponse(true, $zipValidation['message'], [], 400); } Log::info('ValidateFiles: Archivo ZIP validado correctamente'); // Extract and validate file listings from Excel $filesValidation = $this->extractAndValidateFiles($request->file('excel_file')); if ($filesValidation['error']) { return $this->responseController->makeResponse(true, $filesValidation['message'], [], 400); } Log::info('ValidateFiles: Archivos extraídos del Excel', ['count' => count($filesValidation['files'])]); // Compare Excel file listings with ZIP contents $comparison = $this->compareExcelWithZip($filesValidation['files'], $request->file('zip_file')); // Check for file size validation errors if (isset($comparison['error'])) { return $this->responseController->makeResponse(true, $comparison['error'], [], 400); } Log::info('ValidateFiles: Comparación Excel-ZIP completada', ['valid' => $comparison['valid']]); // Upload temp files if validation is successful if ($comparison['valid']) { $tempFiles = $this->uploadTempFiles($request); if ($tempFiles['error']) { Log::error('ValidateFiles: Error subiendo archivos temporales', ['message' => $tempFiles['message']]); return $this->responseController->makeResponse(true, $tempFiles['message'], [], 400); } Log::info('ValidateFiles: Archivos temporales subidos correctamente'); // Extract and save individual files as temp $individualTempFiles = $this->extractAndSaveIndividualFilesAsTemp($request->file('zip_file'), $request->input('linea'), $idUser); if ($individualTempFiles === false) { Log::error('ValidateFiles: Error procesando archivos individuales'); return $this->responseController->makeResponse(true, 'Error procesando archivos individuales', [], 400); } Log::info('ValidateFiles: Archivos individuales procesados', ['count' => count($individualTempFiles)]); $comparison['temp_files'] = $tempFiles['files']; $comparison['individual_temp_files'] = $individualTempFiles; } Log::info('ValidateFiles: Validación completada exitosamente'); return $this->responseController->makeResponse(false, 'Validación completada.', $comparison); } /** * Validates that Excel file has the correct headers in row 7 * Each column must match the expected category name exactly */ private function validateExcelHeaders($file) { try { $spreadsheet = IOFactory::load($file->getPathname()); $worksheet = $spreadsheet->getActiveSheet(); // Check each expected header in row 7 foreach ($this->expectedHeaders as $header) { $cellValue = $worksheet->getCell($header['letter'] . '7')->getValue(); if (trim($cellValue) !== $header['category']) { return [ 'error' => true, 'message' => "El encabezado en la columna {$header['letter']} no coincide. Se esperaba: '{$header['category']}', se encontró: '{$cellValue}'" ]; } } Log::info('ValidateExcelHeaders: Todos los headers validados correctamente'); return ['error' => false]; } catch (\Exception $e) { return ['error' => true, 'message' => 'Error al procesar el archivo Excel: ' . $e->getMessage()]; } } /** * Validates ZIP file integrity and ensures it's not empty */ private function validateZipFile($file) { $zip = new ZipArchive(); $result = $zip->open($file->getPathname()); if ($result !== TRUE) { return ['error' => true, 'message' => 'No se pudo abrir el archivo ZIP.']; } if ($zip->numFiles === 0) { $zip->close(); return ['error' => true, 'message' => 'El archivo ZIP está vacío.']; } Log::info('ValidateZipFile: ZIP válido', ['num_files' => $zip->numFiles]); $zip->close(); return ['error' => false]; } /** * Extracts file listings from Excel starting from row 8 * Validates file extensions and creates structured file list * Only processes rows with equipment code (column D) */ private function extractAndValidateFiles($file) { try { $spreadsheet = IOFactory::load($file->getPathname()); $worksheet = $spreadsheet->getActiveSheet(); $highestRow = $worksheet->getHighestRow(); $files = []; Log::info('ExtractAndValidateFiles: Iniciando extracción', ['total_rows' => $highestRow]); // Process each row starting from row 8 (data rows) for ($row = 8; $row <= $highestRow; $row++) { $equipmentCode = trim($worksheet->getCell('D' . $row)->getValue()); if (empty($equipmentCode)) continue; // Skip rows without equipment code $hasFiles = false; $rowFiles = []; // Check each file category column (excluding equipment code column D) foreach ($this->expectedHeaders as $header) { if ($header['letter'] === 'D') continue; $cellValue = trim($worksheet->getCell($header['letter'] . $row)->getValue()); if (empty($cellValue)) continue; // Handle multiple files separated by commas $fileNames = explode(',', $cellValue); foreach ($fileNames as $fileName) { $fileName = trim($fileName); if (empty($fileName)) continue; // Validate file extension $extension = $file->extension(); if (!isset($this->extensionTypes[$extension])) { return [ 'error' => true, 'message' => "Extensión inválida '{$extension}' en archivo '{$fileName}' para equipo '{$equipmentCode}'" ]; } $rowFiles[] = [ 'equipment_code' => $equipmentCode, 'file_name' => $fileName, 'type' => $this->getFileType($extension) ]; $hasFiles = true; } } if ($hasFiles) { $files = array_merge($files, $rowFiles); } } Log::info('ExtractAndValidateFiles: Extracción completada', ['files_found' => count($files)]); return ['error' => false, 'files' => $files]; } catch (\Exception $e) { Log::error('ExtractAndValidateFiles: Excepción', ['error' => $e->getMessage()]); return ['error' => true, 'message' => 'Error al extraer archivos del Excel: ' . $e->getMessage()]; } } /** * Compares file listings from Excel with actual files in ZIP * Returns validation status and lists of missing/extra files */ private function compareExcelWithZip($excelFiles, $zipFile) { $zip = new ZipArchive(); $zip->open($zipFile->getPathname()); Log::info('CompareExcelWithZip: Iniciando comparación'); // Extract all file names and sizes from ZIP $zipFiles = []; $zipFileSizes = []; for ($i = 0; $i < $zip->numFiles; $i++) { $fullPath = $zip->getNameIndex($i); // Skip directories (entries ending with /) if (substr($fullPath, -1) === '/') { continue; } // Extract filename and size $fileName = basename($fullPath); if (!empty($fileName)) { $zipFiles[] = $fileName; $zipFileSizes[$fileName] = $zip->statIndex($i)['size']; } } $zip->close(); // Validate file sizes using FunctionsController foreach ($zipFileSizes as $fileName => $size) { $extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); if (!$this->functionsController->checkFileSize($extension, $size)) { $sizeMB = round($size / (1024 * 1024), 2); return [ 'valid' => false, 'error' => "El archivo '{$fileName}' excede el límite de tamaño. Tamaño: {$sizeMB}MB" ]; } } // Get file names from Excel listings $excelFileNames = array_column($excelFiles, 'file_name'); // Find discrepancies between Excel and ZIP $missingInZip = array_diff($excelFileNames, $zipFiles); $extraInZip = array_diff($zipFiles, $excelFileNames); $isValid = empty($missingInZip) && empty($extraInZip); Log::info('CompareExcelWithZip: Comparación completada', [ 'valid' => $isValid, 'missing_count' => count($missingInZip), 'extra_count' => count($extraInZip) ]); return [ 'valid' => $isValid, 'missing_in_zip' => array_values($missingInZip), 'extra_in_zip' => array_values($extraInZip) ]; } /** * Process Functions start here */ /** * Process endpoint that moves temp files to final storage */ public function processLoadArchives(Request $request) { $validator = Validator::make($request->all(), [ 'id_user' => 'required|string', 'linea' => 'required|integer', 'temp_files' => 'required|array', 'temp_files.excel' => 'required|string', 'temp_files.zip' => 'required|string', 'individual_temp_files' => 'required|array' ]); if ($validator->fails()) { return $this->responseController->makeResponse( true, 'Se encontraron uno o más errores.', $this->responseController->makeErrors($validator->errors()->messages()), 400 ); } $form = $request->all(); $idUser = '0000000001'; // $idUser = $this->encryptionController->decrypt($form['id_user']); // if (!$idUser) { // return $this->responseController->makeResponse(true, 'ID de usuario inválido.', [], 400); // } $finalFiles = []; try { // Move Excel to final $excelFinal = $this->moveToFinal($form['temp_files']['excel'], $form['linea'], $idUser); if (!$excelFinal) { return $this->responseController->makeResponse(true, 'Error procesando Excel', [], 400); } $finalFiles['excel'] = $excelFinal; // Move ZIP to final $zipFinal = $this->moveToFinal($form['temp_files']['zip'], $form['linea'], $idUser); if (!$zipFinal) { return $this->responseController->makeResponse(true, 'Error procesando ZIP', [], 400); } $finalFiles['zip'] = $zipFinal; // Move individual temp files to final storage $individualFiles = []; foreach ($form['individual_temp_files'] as $tempFile) { $finalFileId = $this->moveToFinal($tempFile['temp_id'], $form['linea'], $idUser); if ($finalFileId) { $individualFiles[] = [ 'original_name' => $tempFile['original_name'], 'final_id' => $finalFileId ]; } } $finalFiles['individual_files'] = $individualFiles; return $this->responseController->makeResponse(false, 'Archivos procesados exitosamente.', $finalFiles); } catch (\Exception $e) { return $this->responseController->makeResponse(true, 'Error interno: ' . $e->getMessage(), [], 500); } } /** * Uploads Excel and ZIP files as temporary files using DocumentManagementController */ private function uploadTempFiles(Request $request) { try { Log::info('UploadTempFiles: Iniciando subida de archivos temporales'); $tempFiles = []; // Upload Excel file $excelRequest = new Request(); $excelRequest->files->set('file', $request->file('excel_file')); $excelRequest->merge([ 'id_user' => $request->input('id_user'), 'linea' => $request->input('linea') ]); $excelResponse = $this->documentManagementController->uploadTempFile($excelRequest); $excelData = json_decode($excelResponse->getContent()); if ($excelData->error) { $errorMsg = isset($excelData->message) ? $excelData->message : 'Error desconocido subiendo Excel'; Log::error('UploadTempFiles: Error subiendo Excel', ['message' => $errorMsg]); return ['error' => true, 'message' => 'Error subiendo Excel: ' . $errorMsg]; } $tempFiles['excel'] = $excelData->response->idArchivo; // Upload ZIP file $zipRequest = new Request(); $zipRequest->files->set('file', $request->file('zip_file')); $zipRequest->merge([ 'id_user' => $request->input('id_user'), 'linea' => $request->input('linea') ]); $zipResponse = $this->documentManagementController->uploadTempFile($zipRequest); $zipData = json_decode($zipResponse->getContent()); if ($zipData->error) { $errorMsg = isset($zipData->message) ? $zipData->message : 'Error desconocido subiendo ZIP'; Log::error('UploadTempFiles: Error subiendo ZIP', ['message' => $errorMsg]); return ['error' => true, 'message' => 'Error subiendo ZIP: ' . $errorMsg]; } $tempFiles['zip'] = $zipData->response->idArchivo; Log::info('UploadTempFiles: Archivos temporales subidos exitosamente', $tempFiles); return ['error' => false, 'files' => $tempFiles]; } catch (\Exception $e) { Log::error('UploadTempFiles: Excepción', ['error' => $e->getMessage()]); return ['error' => true, 'message' => 'Error en uploadTempFiles: ' . $e->getMessage()]; } } /** * Moves a temporary file to final storage using DocumentManagementController */ private function moveToFinal($tempId, $line, $idUser) { try { $tempIdDec = $this->encryptionController->decrypt($tempId); if (!$tempIdDec) { return false; } $tempFile = DB::table('S002V01TARTE')->where('ARTE_IDAR', $tempIdDec)->first(); if (!$tempFile) { return false; } $result = $this->documentManagementController->moveFinalFile($line, 'ADSI', 'LA', $tempFile, $idUser); return $result[0] ? $result[1] : false; } catch (\Exception $e) { return false; } } /** * Extracts individual files from ZIP and saves them as temporary files * Files are extracted to first level to avoid long paths and improve performance */ private function extractAndSaveIndividualFilesAsTemp($zipFile, $line, $idUser) { try { $zip = new ZipArchive(); if ($zip->open($zipFile->getPathname()) !== TRUE) { return false; } $tempFiles = []; $tempDir = storage_path('app/tempFiles/extracted_' . time()); mkdir($tempDir, 0755, true); for ($i = 0; $i < $zip->numFiles; $i++) { $fullPath = $zip->getNameIndex($i); // Skip directories if (substr($fullPath, -1) === '/') { continue; } $fileName = basename($fullPath); if (empty($fileName)) continue; // Extract file directly to first level using file content $fileContent = $zip->getFromIndex($i); if ($fileContent !== false) { $finalExtractPath = $tempDir . '/' . $fileName; // Write file content directly to avoid directory structure if (file_put_contents($finalExtractPath, $fileContent) !== false) { // Upload as temp file $tempFileId = $this->uploadExtractedFileAsTemp($finalExtractPath, $fileName, $line, $idUser); if ($tempFileId) { $tempFiles[] = [ 'original_name' => $fileName, 'temp_id' => $tempFileId ]; } unlink($finalExtractPath); } } } $zip->close(); $this->removeDirectory($tempDir); return $tempFiles; } catch (\Exception $e) { return false; } } /** * Uploads an extracted file as temporary file */ private function uploadExtractedFileAsTemp($filePath, $fileName, $line, $idUser) { try { $extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $mimeType = $this->getMimeType($extension); // Create a temporary uploaded file object $tempFile = new \Illuminate\Http\UploadedFile( $filePath, $fileName, $mimeType, null, true ); $request = new Request(); $request->files->set('file', $tempFile); $request->merge([ 'id_user' => $this->encryptionController->encrypt($idUser), 'linea' => $line ]); $response = $this->documentManagementController->uploadTempFile($request); $data = json_decode($response->getContent()); return $data->error ? false : $data->response->idArchivo; } catch (\Exception $e) { return false; } } /** * Gets MIME type for file extension */ private function getMimeType($extension) { $mimeTypes = [ 'pdf' => 'application/pdf', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xls' => 'application/vnd.ms-excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ppt' => 'application/vnd.ms-powerpoint', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'bmp' => 'image/bmp', 'txt' => 'text/plain', 'dwg' => 'application/acad', 'dxf' => 'application/dxf' ]; return $mimeTypes[$extension] ?? 'application/octet-stream'; } /** * Recursively removes a directory and its contents */ private function removeDirectory($dir) { if (!is_dir($dir)) return; $files = array_diff(scandir($dir), ['.', '..']); foreach ($files as $file) { $path = $dir . '/' . $file; is_dir($path) ? $this->removeDirectory($path) : unlink($path); } rmdir($dir); } /** * Gets file type based on extension */ private function getFileType($extension){ $typeMap = [ 'zip' => 'STORAGE', 'rar' => 'STORAGE', 'tar' => 'STORAGE', '7z' => 'STORAGE', 'mp3' => 'AUDIO', 'mpeg' => 'AUDIO', 'wav' => 'AUDIO', 'ogg' => 'AUDIO', 'opus' => 'AUDIO', 'jpeg' => 'IMG', 'jpg' => 'IMG', 'png' => 'IMG', 'gif' => 'IMG', 'bmp' => 'IMG', 'tiff' => 'IMG', 'svg' => 'IMG', 'txt' => 'TEXT', 'webm' => 'VIDEO', 'mpeg4' => 'VIDEO', 'mp4' => 'VIDEO', '3gpp' => 'VIDEO', 'mov' => 'VIDEO', 'avi' => 'VIDEO', 'wmv' => 'VIDEO', 'flv' => 'VIDEO', 'dwg' => 'CAD', 'dxf' => 'CAD', '3ds' => '3D', 'ai' => 'ILLUSTRATOR', 'psd' => 'PHOTOSHOP', 'pdf' => 'PDF', 'xls' => 'EXCEL', 'xlsx' => 'EXCEL', 'ppt' => 'POWERPOINT', 'pptx' => 'POWERPOINT', 'doc' => 'WORD', 'docx' => 'WORD', 'vsd' => 'VISIO', 'vsdx' => 'VISIO' ]; return $typeMap[$extension] ?? 'UNKNOWN'; } }