Kaynağa Gözat

Merge branch 'ValidateLoadArchives' of ITTEC/SistemaMantenimiento into master

Funcionalidad del módulo Carga de archivos relacionados. Funcionalidad asincrona con dos procesos: validación y carga en temp, sistema asincrono que extrae de temp y coloca en final
EmilianoOrtiz 2 ay önce
ebeveyn
işleme
ea8349a18c
20 değiştirilmiş dosya ile 3617 ekleme ve 253 silme
  1. 3 0
      sistema-mantenimiento-back/.gitignore
  2. 10 27
      sistema-mantenimiento-back/app/Http/Controllers/AcquisitionManagementController.php
  3. 615 0
      sistema-mantenimiento-back/app/Http/Controllers/AsyncValidateLoadArchivesController.php
  4. 6 6
      sistema-mantenimiento-back/app/Http/Controllers/ControlPanelController.php
  5. 13 13
      sistema-mantenimiento-back/app/Http/Controllers/CorrectiveMaintenanceController.php
  6. 2 3
      sistema-mantenimiento-back/app/Http/Controllers/DocumentManagementController.php
  7. 6 10
      sistema-mantenimiento-back/app/Http/Controllers/EquipmentManagementController.php
  8. 8 21
      sistema-mantenimiento-back/app/Http/Controllers/FailureAnalysisController.php
  9. 4 0
      sistema-mantenimiento-back/app/Http/Controllers/FunctionsController.php
  10. 11 11
      sistema-mantenimiento-back/app/Http/Controllers/PreventiveMaintenanceController.php
  11. 31 31
      sistema-mantenimiento-back/app/Http/Controllers/SystemAdministratorController.php
  12. 2373 0
      sistema-mantenimiento-back/app/Http/Controllers/TemplatesManagementController.php
  13. 216 0
      sistema-mantenimiento-back/app/Jobs/ValidateLoadArchives.php
  14. 303 0
      sistema-mantenimiento-back/bash/validateLoadArchives.php
  15. 5 2
      sistema-mantenimiento-back/composer.json
  16. 0 32
      sistema-mantenimiento-back/database/migrations/2014_10_12_000000_create_users_table.php
  17. 0 28
      sistema-mantenimiento-back/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php
  18. 0 32
      sistema-mantenimiento-back/database/migrations/2019_08_19_000000_create_failed_jobs_table.php
  19. 0 33
      sistema-mantenimiento-back/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
  20. 11 4
      sistema-mantenimiento-back/routes/api.php

+ 3 - 0
sistema-mantenimiento-back/.gitignore

@@ -18,3 +18,6 @@ yarn-error.log
 /.fleet
 /.fleet
 /.idea
 /.idea
 /.vscode
 /.vscode
+example
+public_files
+.gitignore

+ 10 - 27
sistema-mantenimiento-back/app/Http/Controllers/AcquisitionManagementController.php

@@ -4002,11 +4002,8 @@ class AcquisitionManagementController extends Controller
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
 
 
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        } else {
-            $ubic = "C:\inetpub\wwwroot\sam\storage\app\\" . $ubic;
-        }
+        
+        $ubic = $this->functionsController->getBasePath() . "\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$user]);
         $usac = json_encode([$user]);
 
 
@@ -13067,12 +13064,9 @@ class AcquisitionManagementController extends Controller
         $noar = 'ficha_de_adquisición_' . $arrOrder['ORCO_NUOR'];
         $noar = 'ficha_de_adquisición_' . $arrOrder['ORCO_NUOR'];
         $exte = 'xlsx';
         $exte = 'xlsx';
 
 
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $filePath = 'C:/ITTEC/SAM/Dev/SistemaMantenimiento/sistema-mantenimiento-back/public/public_files/';    // API JEAN
-        } else {
-            $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';     // API QA
-        }
-
+        
+        $filePath = $this->functionsController->getBasePath() . '/public/public_files/';    // API JEAN
+        
         $fileName = $nuli.'-'.$como.'-'.$cldo.'-'.$fecr.'-'.$nuse.'='.$nuve.'='.$noar.'.'.$exte;
         $fileName = $nuli.'-'.$como.'-'.$cldo.'-'.$fecr.'-'.$nuse.'='.$nuve.'='.$noar.'.'.$exte;
 
 
         $tempFile = $filePath.$fileName;
         $tempFile = $filePath.$fileName;
@@ -13106,11 +13100,7 @@ class AcquisitionManagementController extends Controller
 
 
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        } else {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        }
+        $ubic = $this->functionsController->getBasePath() . "\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$user]);
         $usac = json_encode([$user]);
 
 
@@ -13649,12 +13639,8 @@ class AcquisitionManagementController extends Controller
         $exte = 'pdf';        
         $exte = 'pdf';        
         
         
         
         
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $filePath = 'C:/ITTEC/SAM/Dev/SistemaMantenimiento/sistema-mantenimiento-back/public/public_files/';    // API JEAN
-        } else {
-            $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';     // API QA
-        }
-
+        $filePath = $this->functionsController->getBasePath() . '/public/public_files/';    // API JEAN
+        
         $fileName = $nuli.'-'.$como.'-'.$cldo.'-'.$fecr.'-'.$nuse.'='.$nuve.'='.$noar.'.'.$exte;
         $fileName = $nuli.'-'.$como.'-'.$cldo.'-'.$fecr.'-'.$nuse.'='.$nuve.'='.$noar.'.'.$exte;
         
         
         $tempFile = $filePath . $fileName;
         $tempFile = $filePath . $fileName;
@@ -13670,11 +13656,8 @@ class AcquisitionManagementController extends Controller
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
 
 
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        } else {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        }
+        $ubic = $this->functionsController->getBasePath() . "\storage\app\\" . $ubic;
+        
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$user]);
         $usac = json_encode([$user]);
 
 

+ 615 - 0
sistema-mantenimiento-back/app/Http/Controllers/AsyncValidateLoadArchivesController.php

@@ -0,0 +1,615 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Validator;
+use PhpOffice\PhpSpreadsheet\IOFactory;
+use ZipArchive;
+use Illuminate\Support\Str;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use ElephantIO\Client;
+
+
+/**
+ * Async Controller for validating Excel and ZIP files for equipment documentation loading
+ * Validates Excel headers structure and compares file listings with ZIP contents using queues
+ */
+class AsyncValidateLoadArchivesController extends Controller
+{
+    private $responseController;
+    private $encryptionController;
+    private $documentManagementController;
+    private $functionsController;
+    private $notificationsController;
+    private $socketClient;
+    
+    // Expected headers in row 7 of Excel file with their corresponding column letters
+    private $expectedHeaders = [
+        ['letter' => '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();
+        $this->notificationsController = new NotificationsController();
+
+        $url = 'http://localhost:3200';
+        $this->socketClient = new Client(Client::engine(Client::CLIENT_4X, $url));
+        $this->socketClient->initialize();
+        $this->socketClient->of('/');
+    }
+
+
+    /**
+     * EmitNotification use  
+     */
+    public function emitNotification(Request $request):bool{
+        $module = $request['module'];
+        $title = $request['title'];
+        $content = $request['content'];
+        $actions = is_string($request['actions']) ? json_decode($request['actions'], true) : $request['actions'];
+        $audience = [$request['audience']];
+        $idUser = $request['idUser'] ?? $request['audience'];
+        $line = $request['line'] ?? 1;
+        $idOrder = $request['idOrder'] ?? null;
+        $orderType = $request['orderType'] ?? null;
+
+        $this->notificationsController->emitNotification($module, $title, $content, $actions, $audience, $idUser, $line, $this->socketClient ,$idOrder, $orderType);
+
+        return true;
+
+    }
+
+
+    /***
+     * Encryption endpoint
+     *
+     */
+
+    public function encrypt(Request $request){
+        $encryptString = $this->encryptionController->encrypt($request['value']);
+         if(!$encryptString){
+             return $this->responseController->makeResponse(true, "La cadena no fue encriptada correctamente", [], 400);
+         }
+         return $this->responseController->makeResponse(false, "Cadena encriptada correctamente", ['encrypted' => $encryptString], 200);
+    }
+
+    /**
+     * Validation endpoint that dispatches file process to bat job
+     */
+
+    public function validateFilesWithBat(Request $request){
+        $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
+            );
+        }
+        
+        $jobId = Str::uuid();
+        $userId = '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);
+        }
+
+        
+        // Validate ZIP file integrity
+        $zipValidation = $this->validateZipFile($request->file('zip_file'));
+        if ($zipValidation['error']) {
+            return $this->responseController->makeResponse(true, $zipValidation['message'], [], 400);
+        }
+
+
+        // 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);
+        }
+
+
+        // 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);
+        }
+
+        
+        // Upload temp files
+        $tempFiles = $this->uploadTempFiles($request);
+        if ($tempFiles['error']) {
+            return $this->responseController->makeResponse(
+                true, 
+                $tempFiles['message'], 
+                [
+                    'valid' => false,
+                    'missing_in_zip' => $comparison['missing_in_zip'] ?? [],
+                    'extra_in_zip' => $comparison['extra_in_zip'] ?? []
+                ], 
+                400
+            );
+        }
+
+        if (!$comparison['valid']) {
+            return $this->responseController->makeResponse(
+                true, 
+                'Los archivos no coinciden entre Excel y ZIP', 
+                [
+                    'valid' => false,
+                    'missing_in_zip' => $comparison['missing_in_zip'],
+                    'extra_in_zip' => $comparison['extra_in_zip']
+                ], 
+                400
+            );
+        }
+
+
+         $idFileZip = $this->encryptionController->decrypt($tempFiles['files']['zip']);
+         if(!$idFileZip){
+             return $this->responseController->makeResponse(true, "El id del archivo que desea eliminar no fue encriptado correctamente", [], 400);
+         }
+        
+         $idFileExcel = $this->encryptionController->decrypt($tempFiles['files']['excel']);
+         if(!$idFileExcel){
+             return $this->responseController->makeResponse(true, "El id del archivo que desea eliminar no fue encriptado correctamente", [], 400);
+        }
+
+        // Guardar en bd::S002V01TVAJO 
+        $validationJobId = DB::table('S002V01TVAJO')->insertGetId([
+            'VAJO_COJO' => $jobId,
+            'VAJO_IDUS' => $userId,
+            'VAJO_NULI' => $request->input('linea'),
+            'VAJO_ESTA' => 'queued',
+            'VAJO_PRPE' => 0,
+            'VAJO_EXTE' => $idFileExcel,
+            'VAJO_ZITE' => $idFileZip,
+            'VAJO_FERE' => now(),
+            'VAJO_FEMO' => now()
+        ]);
+        
+        return $this->responseController->makeResponse(
+            false, 
+            'Validación iniciada', 
+            [
+                'job_id' => $validationJobId, 
+                'status' => 'queued',
+                'valid' => $comparison['valid'],
+                'missing_in_zip' => $comparison['missing_in_zip'],
+                'extra_in_zip' => $comparison['extra_in_zip']
+            ]
+        );
+    }
+
+        
+     /**
+     * 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 {
+            // Verificar estado de archivos antes de procesarlos
+            $tempFiles = [$form['temp_files']['excel'], $form['temp_files']['zip']];
+            foreach ($form['individual_temp_files'] as $tempFile) {
+                $tempFiles[] = $tempFile['temp_id'];
+            }
+            
+            foreach ($tempFiles as $tempId) {
+                $tempIdDec = $this->encryptionController->decrypt($tempId);
+                if ($tempIdDec) {
+                    $tempFile = DB::table('S002V01TARTE')->where('ARTE_IDAR', $tempIdDec)->first();
+                    if ($tempFile && $tempFile->ARTE_ESTA === 'Eliminado') {
+                        return $this->responseController->makeResponse(
+                            true, 
+                            'La acción no puede completarse porque uno o más archivos han sido eliminados lógicamente.', 
+                            [], 
+                            400
+                        );
+                    }
+                }
+            }
+            
+            // 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);
+        }
+    }
+
+      /**
+     * 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;
+            }
+            
+            // Validar estado del archivo temporal
+            if ($tempFile->ARTE_ESTA === 'Eliminado') {
+                return false;
+            }
+            
+            $result = $this->documentManagementController->moveFinalFile($line, 'ADSI', 'LA', $tempFile, $idUser);
+            return $result[0] ? $result[1] : false;
+            
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 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}'"
+                    ];
+                }
+            }
+            
+
+            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.'];
+        }
+        
+
+        $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 = [];
+            
+
+            
+            // 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 = strtolower(pathinfo($fileName, PATHINFO_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);
+                }
+            }
+            
+
+            return ['error' => false, 'files' => $files];
+        } catch (\Exception $e) {
+
+            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());
+        
+
+        
+        // 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);
+
+        
+        return [
+            'valid' => $isValid,
+            'missing_in_zip' => array_values($missingInZip),
+            'extra_in_zip' => array_values($extraInZip)
+        ];
+    }
+
+    /**
+     * Uploads Excel and ZIP files as temporary files using DocumentManagementController
+     */
+    private function uploadTempFiles(Request $request)
+    {
+        try {
+
+            $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';
+
+                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';
+
+                return ['error' => true, 'message' => 'Error subiendo ZIP: ' . $errorMsg];
+            }
+            
+            $tempFiles['zip'] = $zipData->response->idArchivo;
+            
+
+            return ['error' => false, 'files' => $tempFiles];
+            
+        } catch (\Exception $e) {
+
+            return ['error' => true, 'message' => 'Error en uploadTempFiles: ' . $e->getMessage()];
+        }
+    }
+    
+
+
+    /**
+     * 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';
+    }
+}

+ 6 - 6
sistema-mantenimiento-back/app/Http/Controllers/ControlPanelController.php

@@ -27,7 +27,7 @@ class ControlPanelController extends Controller{
         $this->responseController = new ResponseController();
         $this->responseController = new ResponseController();
         $this->encryptionController = new EncryptionController();
         $this->encryptionController = new EncryptionController();
         $this->functionsController = new FunctionsController();
         $this->functionsController = new FunctionsController();
-        $this->filesUbic = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\public\global_resources\\';
+        $this->filesUbic = $this->functionsController->getBasePath() . '\storage\app\public\global_resources\\';
     }
     }
 
 
     public function getPanels($idUser, $line) {
     public function getPanels($idUser, $line) {
@@ -866,7 +866,7 @@ class ControlPanelController extends Controller{
         }
         }
 
 
         $line = $line < 10 ? "0$line" : "$line";
         $line = $line < 10 ? "0$line" : "$line";
-        $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';
+        $filePath = $this->functionsController->getBasePath() . '\public_files\\';
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         
         
         $tempFile = $filePath . $fileName;
         $tempFile = $filePath . $fileName;
@@ -878,7 +878,7 @@ class ControlPanelController extends Controller{
         $writer->save($tempFile);
         $writer->save($tempFile);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() . "\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);
@@ -1588,12 +1588,12 @@ class ControlPanelController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado', [], 404);
         }
         }
 
 
-        $exportableSpreadsheetsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\public\global_resources\spreadsheets.sam');
+        $exportableSpreadsheetsExists = file_exists($this->functionsController->getBasePath() . '\storage\app\public\global_resources\spreadsheets.sam');
         if(!$exportableSpreadsheetsExists){
         if(!$exportableSpreadsheetsExists){
             return $this->responseController->makeResponse(true, 'El archivo de hojas de excel exportables no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de hojas de excel exportables no fue encontrado.', [], 500);
         }
         }
 
 
-        $exportableSpreadsheetsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\public\global_resources\spreadsheets.sam');
+        $exportableSpreadsheetsStr = file_get_contents($this->functionsController->getBasePath() . '\storage\app\public\global_resources\spreadsheets.sam');
         $exportableSpreadsheetsDec = $this->encryptionController->decrypt($exportableSpreadsheetsStr);
         $exportableSpreadsheetsDec = $this->encryptionController->decrypt($exportableSpreadsheetsStr);
         $exportableSpreadsheetsArr = json_decode($exportableSpreadsheetsDec, true);
         $exportableSpreadsheetsArr = json_decode($exportableSpreadsheetsDec, true);
 
 
@@ -3081,7 +3081,7 @@ class ControlPanelController extends Controller{
         }
         }
 
 
         $line = $line < 10 ? "0$line" : "$line";
         $line = $line < 10 ? "0$line" : "$line";
-        $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';
+        $filePath = $this->functionsController->getBasePath() . '\public_files\\';
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         
         
         $tempFile = $filePath . $fileName;
         $tempFile = $filePath . $fileName;

+ 13 - 13
sistema-mantenimiento-back/app/Http/Controllers/CorrectiveMaintenanceController.php

@@ -35,7 +35,7 @@ class CorrectiveMaintenanceController extends Controller{
         $this->functionsController = new FunctionsController();
         $this->functionsController = new FunctionsController();
         $this->documentManagementController = new DocumentManagementController();
         $this->documentManagementController = new DocumentManagementController();
         $this->notificationsController = new NotificationsController();
         $this->notificationsController = new NotificationsController();
-        $this->templatesUbic = "C:\inetpub\wwwroot\sam\storage\app\public\pdf_templates\\01_04_GMCO\\";
+        $this->templatesUbic = $this->functionsController->getBasePath() . "\storage\app\public\pdf_templates\\01_04_GMCO\\";
     }
     }
 
 
     private function getSocketClient(){
     private function getSocketClient(){
@@ -2750,12 +2750,12 @@ class CorrectiveMaintenanceController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
         }
         }
 
 
-        $systemParamsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $systemParamsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         if(!$systemParamsExists){
         if(!$systemParamsExists){
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
         }
         }
 
 
-        $paramsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $paramsStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         $paramsArr = json_decode($paramsStr, true);
         $paramsArr = json_decode($paramsStr, true);
 
 
         $ordersBuilder = DB::table('S002V01TOTCO')->select([
         $ordersBuilder = DB::table('S002V01TOTCO')->select([
@@ -3065,7 +3065,7 @@ class CorrectiveMaintenanceController extends Controller{
         }
         }
 
 
         $line = $form['linea'] < 10 ? "0$form[linea]" : "$form[linea]";
         $line = $form['linea'] < 10 ? "0$form[linea]" : "$form[linea]";
-        $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';
+        $filePath = $this->functionsController->getBasePath() .'\public_files\\';
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         
         
         $tempFile = $filePath . $fileName;
         $tempFile = $filePath . $fileName;
@@ -3077,7 +3077,7 @@ class CorrectiveMaintenanceController extends Controller{
         $writer->save($tempFile);
         $writer->save($tempFile);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() ."\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);
@@ -3396,12 +3396,12 @@ class CorrectiveMaintenanceController extends Controller{
         }
         }
 
 
         $priorityDec = $this->encryptionController->decrypt($form['priority']);
         $priorityDec = $this->encryptionController->decrypt($form['priority']);
-        $systemParamsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $systemParamsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         if(!$systemParamsExists){
         if(!$systemParamsExists){
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
         }
         }
 
 
-        $paramsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $paramsStr = file_get_contents($this->functionsController->getBasePath() . '\storage\app\files\system-params.json');
         $paramsArr = json_decode($paramsStr, true);
         $paramsArr = json_decode($paramsStr, true);
         $orderPriorities = $paramsArr['order_priorities'];
         $orderPriorities = $paramsArr['order_priorities'];
 
 
@@ -5591,10 +5591,10 @@ class CorrectiveMaintenanceController extends Controller{
 
 
         $reportDataArr = json_decode($reportDataDec, true);
         $reportDataArr = json_decode($reportDataDec, true);
         $html = '';
         $html = '';
-        $crrcLogo = file_get_contents("C:\inetpub\wwwroot\sam\storage\app\public\global_resources\logo_crrc.webp");
-        $ingeropLogo = file_get_contents("C:\inetpub\wwwroot\sam\storage\app\public\global_resources\logo_ingerop.png");
-        $stcLogo = file_get_contents("C:\inetpub\wwwroot\sam\storage\app\public\global_resources\logo_stc.webp");
-        $stcShortLogo = file_get_contents("C:\inetpub\wwwroot\sam\storage\app\public\global_resources\logo_stc_small.png");
+        $crrcLogo = file_get_contents($this->functionsController->getBasePath() . "\storage\app\public\global_resources\logo_crrc.webp");
+        $ingeropLogo = file_get_contents($this->functionsController->getBasePath() ."\storage\app\public\global_resources\logo_ingerop.png");
+        $stcLogo = file_get_contents($this->functionsController->getBasePath() ."\storage\app\public\global_resources\logo_stc.webp");
+        $stcShortLogo = file_get_contents($this->functionsController->getBasePath() ."\storage\app\public\global_resources\logo_stc_small.png");
 
 
         $crrcLogoStr = "data:image/svg+xml;base64," . base64_encode($crrcLogo);
         $crrcLogoStr = "data:image/svg+xml;base64," . base64_encode($crrcLogo);
         $ingeropLogoStr = "data:image/svg+xml;base64," . base64_encode($ingeropLogo);
         $ingeropLogoStr = "data:image/svg+xml;base64," . base64_encode($ingeropLogo);
@@ -5671,7 +5671,7 @@ class CorrectiveMaintenanceController extends Controller{
             $nuve = "0$nuve";
             $nuve = "0$nuve";
         }
         }
 
 
-        $filePath = 'C:\inetpub\wwwroot\sam\public_files\\';
+        $filePath = $this->functionsController->getBasePath() .'\public_files\\';
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
 
 
         $dompdf = new Dompdf();
         $dompdf = new Dompdf();
@@ -5689,7 +5689,7 @@ class CorrectiveMaintenanceController extends Controller{
         file_put_contents($tempFile, $output);
         file_put_contents($tempFile, $output);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\inetpub\wwwroot\sam\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() ."\sam\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);

+ 2 - 3
sistema-mantenimiento-back/app/Http/Controllers/DocumentManagementController.php

@@ -428,7 +428,8 @@ class DocumentManagementController extends Controller{
         }
         }
 
 
         $form = $request->all();
         $form = $request->all();
-        $idUser = $this->encryptionController->decrypt($form['id_user']);
+        //$idUser = $this->encryptionController->decrypt($form['id_user']);
+        $idUser = "0000000001";
         if(!$idUser){
         if(!$idUser){
             return $this->responseController->makeResponse(true, "El id del usuario que realizó la petición no fue encriptado correctamente", [], 400);
             return $this->responseController->makeResponse(true, "El id del usuario que realizó la petición no fue encriptado correctamente", [], 400);
         }
         }
@@ -1484,11 +1485,9 @@ class DocumentManagementController extends Controller{
             copy($file->AFAL_UBIC, $publicUbi);
             copy($file->AFAL_UBIC, $publicUbi);
         }
         }
 
 
-        // $apiURI = 'http://192.168.100.105:8000/';
         $apiURI = $this->functionsController->getApiURI();
         $apiURI = $this->functionsController->getApiURI();
 
 
         $publicUbiStr = str_replace('C:\inetpub\wwwroot\\', $apiURI, $publicUbi);
         $publicUbiStr = str_replace('C:\inetpub\wwwroot\\', $apiURI, $publicUbi);
-        // $publicUbiStr = str_replace('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public\\', $apiURI, $publicUbi);
         $publicUbiStr = str_replace('\\', '/', $publicUbiStr);
         $publicUbiStr = str_replace('\\', '/', $publicUbiStr);
 
 
         $arrResponse = [
         $arrResponse = [

+ 6 - 10
sistema-mantenimiento-back/app/Http/Controllers/EquipmentManagementController.php

@@ -35,11 +35,7 @@ class EquipmentManagementController extends Controller{
         $this->responseController = new ResponseController();
         $this->responseController = new ResponseController();
         $this->encryptionController = new EncryptionController();
         $this->encryptionController = new EncryptionController();
         $this->functionsController = new FunctionsController();
         $this->functionsController = new FunctionsController();
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $this->templatesUbic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\public\pdf_templates\\01_05_GEEQ\\";
-        } else {
-            $this->templatesUbic = "C:\inetpub\wwwroot\sam\storage\app\public\pdf_templates\\01_05_GEEQ\\";
-        }
+        $this->templatesUbic = $this->functionsController->getBasePath() . "\storage\app\public\pdf_templates\\01_05_GEEQ\\";
         $this->documentManagementController = new DocumentManagementController();
         $this->documentManagementController = new DocumentManagementController();
 
 
         $this->pccValidElements = [
         $this->pccValidElements = [
@@ -5792,7 +5788,7 @@ class EquipmentManagementController extends Controller{
             }
             }
 
 
             if($levelIndex == 1){
             if($levelIndex == 1){
-                $logo = file_get_contents("C:\inetpub\wwwroot\sam\storage\app\public\global_resources\sam-short-logo.png");
+                $logo = file_get_contents($this->functionsController->getBasePath() ."\storage\app\public\global_resources\sam-short-logo.png");
                 $logoStr = "data:image/svg+xml;base64," . base64_encode($logo);
                 $logoStr = "data:image/svg+xml;base64," . base64_encode($logo);
                 $tableBody .= "
                 $tableBody .= "
                     <tr>
                     <tr>
@@ -5929,7 +5925,7 @@ class EquipmentManagementController extends Controller{
             $nuve = "0$nuve";
             $nuve = "0$nuve";
         }
         }
 
 
-        $filePath = 'C:\inetpub\wwwroot\sam\public_files\\';
+        $filePath = $this->functionsController->getBasePath() . '\public_files\\';
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
 
 
         $dompdf = new Dompdf();
         $dompdf = new Dompdf();
@@ -5947,7 +5943,7 @@ class EquipmentManagementController extends Controller{
         file_put_contents($tempFile, $output);
         file_put_contents($tempFile, $output);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\inetpub\wwwroot\sam\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() ."\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);
@@ -6168,7 +6164,7 @@ class EquipmentManagementController extends Controller{
         }
         }
 
 
         $line = $form['linea'] < 10 ? "0$form[linea]" : "$form[linea]";
         $line = $form['linea'] < 10 ? "0$form[linea]" : "$form[linea]";
-        $filePath = 'C:\inetpub\wwwroot\sam\public_files\\';
+        $filePath = $this->functionsController->getBasePath() .'\public_files\\';
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         
         
         $tempFile = $filePath . $fileName;
         $tempFile = $filePath . $fileName;
@@ -6186,7 +6182,7 @@ class EquipmentManagementController extends Controller{
         $writer->save($tempFile);
         $writer->save($tempFile);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\inetpub\wwwroot\sam\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() ."\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);

+ 8 - 21
sistema-mantenimiento-back/app/Http/Controllers/FailureAnalysisController.php

@@ -1127,12 +1127,8 @@ class FailureAnalysisController extends Controller
         $noar = 'ficha_de_falla_'.$arrFailureLog['NUMERO_FALLA'];
         $noar = 'ficha_de_falla_'.$arrFailureLog['NUMERO_FALLA'];
         $exte = 'xlsx';
         $exte = 'xlsx';
 
 
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $filePath = 'C:/ITTEC/SAM/Dev/SistemaMantenimiento/sistema-mantenimiento-back/public/public_files/';    // API JEAN
-        } else {
-            $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';     // API QA
-        }
-        
+        $filePath = $this->functionsController->getBasePath() .'/public/public_files/';    // API JEAN
+
         $fileName = $nuli.'-'.$como.'-'.$cldo.'-'.$fecr.'-'.$nuse.'='.$nuve.'='.$noar.'.'.$exte;
         $fileName = $nuli.'-'.$como.'-'.$cldo.'-'.$fecr.'-'.$nuse.'='.$nuve.'='.$noar.'.'.$exte;
 
 
         $tempFile = $filePath.$fileName;
         $tempFile = $filePath.$fileName;
@@ -1165,11 +1161,8 @@ class FailureAnalysisController extends Controller
 
 
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        } else {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        }
+        $ubic = $this->functionsController->getBasePath() . "\storage\app\\" . $ubic;
+        
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$user]);
         $usac = json_encode([$user]);
 
 
@@ -1440,11 +1433,8 @@ class FailureAnalysisController extends Controller
         $exte = 'pdf';        
         $exte = 'pdf';        
         
         
         
         
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $filePath = 'C:/ITTEC/SAM/Dev/SistemaMantenimiento/sistema-mantenimiento-back/public/public_files/';    // API JEAN
-        } else {
-            $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';     // API QA
-        }
+        $filePath = $this->functionsController->getBasePath() . '/public/public_files/';    // API JEAN
+        
 
 
         $fileName = $nuli.'-'.$como.'-'.$cldo.'-'.$fecr.'-'.$nuse.'='.$nuve.'='.$noar.'.'.$exte;
         $fileName = $nuli.'-'.$como.'-'.$cldo.'-'.$fecr.'-'.$nuse.'='.$nuve.'='.$noar.'.'.$exte;
         
         
@@ -1461,11 +1451,8 @@ class FailureAnalysisController extends Controller
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
 
 
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        if ($_SERVER['SERVER_NAME'] === '192.168.100.105') {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        } else {
-            $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
-        }
+        $ubic = $this->functionsController->getBasePath() . "\storage\app\\" . $ubic;
+        
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$user]);
         $usac = json_encode([$user]);
 
 

+ 4 - 0
sistema-mantenimiento-back/app/Http/Controllers/FunctionsController.php

@@ -328,6 +328,10 @@ class FunctionsController extends Controller{
         return "http://git.ittec.mx/";
         return "http://git.ittec.mx/";
     }
     }
 
 
+    public function getBasePath(): string {
+        return "C:\SistemaMantenimiento\sistema-mantenimiento-back";
+    }
+
     public function validPhoneNumber(string $number) : bool {
     public function validPhoneNumber(string $number) : bool {
         if(intval($number) <= 0) return false;
         if(intval($number) <= 0) return false;
 
 

+ 11 - 11
sistema-mantenimiento-back/app/Http/Controllers/PreventiveMaintenanceController.php

@@ -27,7 +27,7 @@ class PreventiveMaintenanceController extends Controller{
         $this->responseController = new ResponseController();
         $this->responseController = new ResponseController();
         $this->encryptionController = new EncryptionController();
         $this->encryptionController = new EncryptionController();
         $this->functionsController = new FunctionsController();
         $this->functionsController = new FunctionsController();
-        $this->templatesUbic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\public\pdf_templates\\01_03_GMPR\\";
+        $this->templatesUbic = $this->functionsController->getBasePath() ."\storage\app\public\pdf_templates\\01_03_GMPR\\";
         $this->documentManagementController = new DocumentManagementController();
         $this->documentManagementController = new DocumentManagementController();
     }
     }
 
 
@@ -3222,7 +3222,7 @@ class PreventiveMaintenanceController extends Controller{
         ";
         ";
 
 
         foreach($content as $item){
         foreach($content as $item){
-            $imgContent = file_get_contents("C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\files\\$item[icon].png");
+            $imgContent = file_get_contents($this->functionsController->getBasePath() . "\storage\app\\files\\$item[icon].png");
             $imgB64 = "data:image/png;base64, " . base64_encode($imgContent);
             $imgB64 = "data:image/png;base64, " . base64_encode($imgContent);
             
             
             $html .= "
             $html .= "
@@ -3266,7 +3266,7 @@ class PreventiveMaintenanceController extends Controller{
             </body>
             </body>
         </html>";
         </html>";
 
 
-        $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';
+        $filePath = $this->functionsController->getBasePath() .'\public_files\\';
         $noar = "simulacion_orden_$idOrder";
         $noar = "simulacion_orden_$idOrder";
         $exte = "pdf";
         $exte = "pdf";
 
 
@@ -3324,7 +3324,7 @@ class PreventiveMaintenanceController extends Controller{
         file_put_contents($tempFile, $output);
         file_put_contents($tempFile, $output);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() ."\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);
@@ -3644,7 +3644,7 @@ class PreventiveMaintenanceController extends Controller{
         </html>
         </html>
         ";
         ";
 
 
-        $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';
+        $filePath = $this->functionsController->getBasePath() .'\public_files\\';
         $noar = "detalles_orden_$idOrder";
         $noar = "detalles_orden_$idOrder";
         $exte = "pdf";
         $exte = "pdf";
 
 
@@ -3702,7 +3702,7 @@ class PreventiveMaintenanceController extends Controller{
         file_put_contents($tempFile, $output);
         file_put_contents($tempFile, $output);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() ."\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);
@@ -3789,7 +3789,7 @@ class PreventiveMaintenanceController extends Controller{
         }
         }
 
 
         $html = file_get_contents($this->templatesUbic . "01-GMPR-PL-010101-000003=01=PDF_PLAN_MANTENIMIENTO.html");
         $html = file_get_contents($this->templatesUbic . "01-GMPR-PL-010101-000003=01=PDF_PLAN_MANTENIMIENTO.html");
-        $logo = file_get_contents("C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\public\global_resources\sam-short-logo.png");
+        $logo = file_get_contents($this->functionsController->getBasePath() ."\storage\app\public\global_resources\sam-short-logo.png");
         $logoStr = "data:image/svg+xml;base64," . base64_encode($logo);
         $logoStr = "data:image/svg+xml;base64," . base64_encode($logo);
 
 
         $html = str_replace("%stcImage%", $logoStr, $html);
         $html = str_replace("%stcImage%", $logoStr, $html);
@@ -3920,7 +3920,7 @@ class PreventiveMaintenanceController extends Controller{
             $nuve = "0$nuve";
             $nuve = "0$nuve";
         }
         }
 
 
-        $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';
+        $filePath = $this->functionsController->getBasePath() .'\public_files\\';
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
 
 
         $dompdf = new Dompdf();
         $dompdf = new Dompdf();
@@ -3938,7 +3938,7 @@ class PreventiveMaintenanceController extends Controller{
         file_put_contents($tempFile, $output);
         file_put_contents($tempFile, $output);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() ."\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);
@@ -4299,7 +4299,7 @@ class PreventiveMaintenanceController extends Controller{
         }
         }
 
 
         $line = $line < 10 ? "0$line" : "$line";
         $line = $line < 10 ? "0$line" : "$line";
-        $filePath = 'C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\public_files\\';
+        $filePath = $this->functionsController->getBasePath() .'\public_files\\';
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         $fileName = "$line-$como-$cldo-$fecr-$nuse=$nuve=$noar.$exte";
         
         
         $tempFile = $filePath . $fileName;
         $tempFile = $filePath . $fileName;
@@ -4332,7 +4332,7 @@ class PreventiveMaintenanceController extends Controller{
         $writer->save($tempFile);
         $writer->save($tempFile);
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = Storage::putFile('files', new File($tempFile));
         $ubic = str_replace("/", "\\", $ubic);
         $ubic = str_replace("/", "\\", $ubic);
-        $ubic = "C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\\" . $ubic;
+        $ubic = $this->functionsController->getBasePath() ."\storage\app\\" . $ubic;
         $tama = filesize($ubic);
         $tama = filesize($ubic);
         $usac = json_encode([$idUser]);
         $usac = json_encode([$idUser]);
         unlink($tempFile);
         unlink($tempFile);

+ 31 - 31
sistema-mantenimiento-back/app/Http/Controllers/SystemAdministratorController.php

@@ -613,7 +613,7 @@ class SystemAdministratorController extends Controller{
             "FECHA" => $now->timestamp
             "FECHA" => $now->timestamp
         ];
         ];
         
         
-        copy($catalogue->AFAL_UBIC, 'C:\\ITTEC\\SAM\\Dev\\SistemaMantenimiento\\sistema-mantenimiento-back\\public_files\\' . $id);
+        copy($catalogue->AFAL_UBIC, $this->functionsController->getBasePath() . '/public_files/' . $id);
 
 
         $actions = DB::getQueryLog();
         $actions = DB::getQueryLog();
         $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
         $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
@@ -1370,12 +1370,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
         }
         }
 
 
-        $filePoliticsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json');
+        $filePoliticsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json');
         if(!$filePoliticsExists){
         if(!$filePoliticsExists){
             return $this->responseController->makeResponse(true, 'El archivo de politicas de seguridad no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de politicas de seguridad no fue encontrado.', [], 500);
         }
         }
 
 
-        $politicsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json');
+        $politicsStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json');
         $politicsArr = json_decode($politicsStr, true);
         $politicsArr = json_decode($politicsStr, true);
         $passwordFormat = $politicsArr['password_format'];
         $passwordFormat = $politicsArr['password_format'];
         $formatBKP = $politicsStr;
         $formatBKP = $politicsStr;
@@ -1390,7 +1390,7 @@ class SystemAdministratorController extends Controller{
 
 
         $politicsArr['password_format'] = $passwordFormat;
         $politicsArr['password_format'] = $passwordFormat;
         $finalStr = json_encode($politicsArr); 
         $finalStr = json_encode($politicsArr); 
-        file_put_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json', $finalStr);
+        file_put_contents($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json', $finalStr);
 
 
         $now = $this->functionsController->now();
         $now = $this->functionsController->now();
         $nowStr = $now->toDateTimeString();
         $nowStr = $now->toDateTimeString();
@@ -1500,12 +1500,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
         }
         }
 
 
-        $filePoliticsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json');
+        $filePoliticsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json');
         if(!$filePoliticsExists){
         if(!$filePoliticsExists){
             return $this->responseController->makeResponse(true, 'El archivo de politicas de seguridad no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de politicas de seguridad no fue encontrado.', [], 500);
         }
         }
 
 
-        $politicsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json');
+        $politicsStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json');
         $politicsArr = json_decode($politicsStr, true);
         $politicsArr = json_decode($politicsStr, true);
         $sessionsDuration = $politicsArr['sessions_duration'];
         $sessionsDuration = $politicsArr['sessions_duration'];
         $formatBKP = $politicsStr;
         $formatBKP = $politicsStr;
@@ -1517,7 +1517,7 @@ class SystemAdministratorController extends Controller{
 
 
         $politicsArr['sessions_duration'] = $sessionsDuration;
         $politicsArr['sessions_duration'] = $sessionsDuration;
         $finalStr = json_encode($politicsArr); 
         $finalStr = json_encode($politicsArr); 
-        file_put_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json', $finalStr);
+        file_put_contents($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json', $finalStr);
 
 
         $now = $this->functionsController->now();
         $now = $this->functionsController->now();
         $nowStr = $now->toDateTimeString();
         $nowStr = $now->toDateTimeString();
@@ -1624,18 +1624,18 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
         }
         }
 
 
-        $filePoliticsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json');
+        $filePoliticsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json');
         if(!$filePoliticsExists){
         if(!$filePoliticsExists){
             return $this->responseController->makeResponse(true, 'El archivo de politicas de seguridad no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de politicas de seguridad no fue encontrado.', [], 500);
         }
         }
 
 
-        $politicsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json');
+        $politicsStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json');
         $politicsArr = json_decode($politicsStr, true);
         $politicsArr = json_decode($politicsStr, true);
         $formatBKP = $politicsStr;
         $formatBKP = $politicsStr;
         
         
         $politicsArr['active_sessions_number'] = intval($info['sessions']);
         $politicsArr['active_sessions_number'] = intval($info['sessions']);
         $finalStr = json_encode($politicsArr); 
         $finalStr = json_encode($politicsArr); 
-        file_put_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\security-politics.json', $finalStr);
+        file_put_contents($this->functionsController->getBasePath() .'\storage\app\files\security-politics.json', $finalStr);
 
 
         $now = $this->functionsController->now();
         $now = $this->functionsController->now();
         $nowStr = $now->toDateTimeString();
         $nowStr = $now->toDateTimeString();
@@ -1699,12 +1699,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
         }
         }
 
 
-        $maintenanceModeExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json');
+        $maintenanceModeExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\maintenance_mode.json');
         if(!$maintenanceModeExists){
         if(!$maintenanceModeExists){
             return $this->responseController->makeResponse(true, 'El archivo de mantenimiento no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de mantenimiento no fue encontrado.', [], 500);
         }
         }
         
         
-        $maintenanceStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json');
+        $maintenanceStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\maintenance_mode.json');
         $maintenanceArr = json_decode($maintenanceStr, true);
         $maintenanceArr = json_decode($maintenanceStr, true);
 
 
         if($maintenanceArr['activated']){
         if($maintenanceArr['activated']){
@@ -1723,7 +1723,7 @@ class SystemAdministratorController extends Controller{
         $maintenanceArr['activated'] = true;
         $maintenanceArr['activated'] = true;
         $maintenanceArr['last_activation_id'] = $idMant;
         $maintenanceArr['last_activation_id'] = $idMant;
         $finalStr = json_encode($maintenanceArr);
         $finalStr = json_encode($maintenanceArr);
-        file_put_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json', $finalStr);
+        file_put_contents( $this->functionsController->getBasePath() . '\storage\app\files\maintenance_mode.json', $finalStr);
 
 
         $actions = DB::getQueryLog();
         $actions = DB::getQueryLog();
         $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
         $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
@@ -1777,12 +1777,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
         }
         }
 
 
-        $maintenanceModeExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json');
+        $maintenanceModeExists = file_exists($this->functionsController->getBasePath() . '\storage\app\files\maintenance_mode.json');
         if(!$maintenanceModeExists){
         if(!$maintenanceModeExists){
             return $this->responseController->makeResponse(true, 'El archivo de mantenimiento no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de mantenimiento no fue encontrado.', [], 500);
         }
         }
 
 
-        $maintenanceStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json');
+        $maintenanceStr = file_get_contents($this->functionsController->getBasePath() . '\storage\app\files\maintenance_mode.json');
         $maintenanceArr = json_decode($maintenanceStr, true);
         $maintenanceArr = json_decode($maintenanceStr, true);
 
 
         if(!$maintenanceArr['activated']){
         if(!$maintenanceArr['activated']){
@@ -1797,7 +1797,7 @@ class SystemAdministratorController extends Controller{
         if(!is_null($maintenanceDB->HMSA_USDE)){
         if(!is_null($maintenanceDB->HMSA_USDE)){
             $maintenanceArr['activated'] = false;
             $maintenanceArr['activated'] = false;
             $finalStr = json_encode($maintenanceArr);
             $finalStr = json_encode($maintenanceArr);
-            file_put_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json', $finalStr);
+            file_put_contents($this->functionsController->getBasePath() . '\storage\app\files\maintenance_mode.json', $finalStr);
 
 
             return $this->responseController->makeResponse(true, 'El modo mantenimiento ya fue desactivado en la base de datos.', [], 500);
             return $this->responseController->makeResponse(true, 'El modo mantenimiento ya fue desactivado en la base de datos.', [], 500);
         }
         }
@@ -1816,7 +1816,7 @@ class SystemAdministratorController extends Controller{
         
         
         $maintenanceArr['activated'] = false;
         $maintenanceArr['activated'] = false;
         $finalStr = json_encode($maintenanceArr);
         $finalStr = json_encode($maintenanceArr);
-        file_put_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json', $finalStr);
+        file_put_contents($this->functionsController->getBasePath() . '\storage\app\files\maintenance_mode.json', $finalStr);
 
 
         $actions = DB::getQueryLog();
         $actions = DB::getQueryLog();
         $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
         $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
@@ -1944,12 +1944,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado', [], 404);
         }
         }
 
 
-        $maintenanceModeExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json');
+        $maintenanceModeExists = file_exists($this->functionsController->getBasePath() . '\storage\app\files\maintenance_mode.json');
         if(!$maintenanceModeExists){
         if(!$maintenanceModeExists){
             return $this->responseController->makeResponse(true, 'El archivo de mantenimiento no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de mantenimiento no fue encontrado.', [], 500);
         }
         }
 
 
-        $maintenanceStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\maintenance_mode.json');
+        $maintenanceStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\maintenance_mode.json');
         $maintenanceArr = json_decode($maintenanceStr, true);
         $maintenanceArr = json_decode($maintenanceStr, true);
 
 
         $now = $this->functionsController->now();
         $now = $this->functionsController->now();
@@ -3148,7 +3148,7 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado.', [], 404);
         }
         }
 
 
-        $iconsStr = file_get_contents("C:\\ITTEC\\SAM\\Dev\\SistemaMantenimiento\\sistema-mantenimiento-back\\storage\\app\\files\\icons.json");
+        $iconsStr = file_get_contents($this->functionsController->getBasePath() . '\storage\app\files\icons.json');
         $iconsArr = json_decode($iconsStr, true);
         $iconsArr = json_decode($iconsStr, true);
         $icons = $iconsArr['icons'];
         $icons = $iconsArr['icons'];
 
 
@@ -3199,12 +3199,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado', [], 404);
         }
         }
 
 
-        $systemParamsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $systemParamsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         if(!$systemParamsExists){
         if(!$systemParamsExists){
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
         }
         }
 
 
-        $paramsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $paramsStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         $paramsArr = json_decode($paramsStr, true);
         $paramsArr = json_decode($paramsStr, true);
 
 
         $now = $this->functionsController->now();
         $now = $this->functionsController->now();
@@ -3262,12 +3262,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
         }
         }
 
 
-        $systemParamsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $systemParamsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         if(!$systemParamsExists){
         if(!$systemParamsExists){
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
         }
         }
 
 
-        $paramsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $paramsStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         $paramsArr = json_decode($paramsStr, true);
         $paramsArr = json_decode($paramsStr, true);
 
 
         $newPriorities = json_decode($form['priorities'], true);
         $newPriorities = json_decode($form['priorities'], true);
@@ -3277,7 +3277,7 @@ class SystemAdministratorController extends Controller{
 
 
         $paramsArr['order_priorities'] = $newPriorities;
         $paramsArr['order_priorities'] = $newPriorities;
         $paramsStr = json_encode($paramsArr);
         $paramsStr = json_encode($paramsArr);
-        file_put_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json', $paramsStr);
+        file_put_contents($this->functionsController->getBasePath() .'\storage\app\files\system-params.json', $paramsStr);
 
 
         $now = $this->functionsController->now();
         $now = $this->functionsController->now();
         $nowStr = $now->toDateTimeString();
         $nowStr = $now->toDateTimeString();
@@ -3316,12 +3316,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la consulta no está registrado', [], 404);
         }
         }
 
 
-        $systemParamsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $systemParamsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         if(!$systemParamsExists){
         if(!$systemParamsExists){
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
         }
         }
 
 
-        $paramsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $paramsStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         $paramsArr = json_decode($paramsStr, true);
         $paramsArr = json_decode($paramsStr, true);
 
 
         $now = $this->functionsController->now();
         $now = $this->functionsController->now();
@@ -3379,12 +3379,12 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
             return $this->responseController->makeResponse(true, 'El usuario que realizó la petición no existe.', [], 404);
         }
         }
 
 
-        $systemParamsExists = file_exists('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $systemParamsExists = file_exists($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         if(!$systemParamsExists){
         if(!$systemParamsExists){
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de parámetros del sistema no fue encontrado.', [], 500);
         }
         }
 
 
-        $paramsStr = file_get_contents('C:\ITTEC\SAM\Dev\SistemaMantenimiento\sistema-mantenimiento-back\storage\app\files\system-params.json');
+        $paramsStr = file_get_contents($this->functionsController->getBasePath() .'\storage\app\files\system-params.json');
         $paramsArr = json_decode($paramsStr, true);
         $paramsArr = json_decode($paramsStr, true);
         
         
         $actualImage = $paramsArr["login_params"]["login_$form[tipo]"];
         $actualImage = $paramsArr["login_params"]["login_$form[tipo]"];
@@ -3403,7 +3403,7 @@ class SystemAdministratorController extends Controller{
             return $this->responseController->makeResponse(true, 'El archivo de la imagen enviada no existe.', [], 500);
             return $this->responseController->makeResponse(true, 'El archivo de la imagen enviada no existe.', [], 500);
         }
         }
 
 
-        $ubiImgAct = "C:\\ITTEC\\SAM\\Dev\\SistemaMantenimiento\\sistema-mantenimiento-back\\public\\assets\\$actualImage";
+        $ubiImgAct = $this->functionsController->getBasePath() . '\public\assets\$actualImage';
         if(!file_exists($ubiImgAct)){
         if(!file_exists($ubiImgAct)){
             return $this->responseController->makeResponse(true, 'La imagen actual no existe.', [], 500);
             return $this->responseController->makeResponse(true, 'La imagen actual no existe.', [], 500);
         }
         }
@@ -3411,7 +3411,7 @@ class SystemAdministratorController extends Controller{
         $now = $this->functionsController->now();
         $now = $this->functionsController->now();
         $timestamp = $now->timestamp;
         $timestamp = $now->timestamp;
 
 
-        rename($ubiImgAct, "C:\\ITTEC\\SAM\\Dev\\SistemaMantenimiento\\sistema-mantenimiento-back\\public\\assets\\UPDATED_$timestamp.png");
+        rename($ubiImgAct, $this->functionsController->getBasePath() . '\public\assets\UPDATED_$timestamp.png');
         copy($ubiImgTmp, $ubiImgAct);
         copy($ubiImgTmp, $ubiImgAct);
 
 
         $nowStr = $now->toDateTimeString();
         $nowStr = $now->toDateTimeString();

+ 2373 - 0
sistema-mantenimiento-back/app/Http/Controllers/TemplatesManagementController.php

@@ -0,0 +1,2373 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Support\Carbon;
+use Exception;
+use Illuminate\Support\Facades\Log;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\IOFactory;
+use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+
+
+
+
+
+class TemplatesManagementController extends Controller {
+
+    private $responseController;
+    private $encryptionController;
+    private $functionsController;
+    private $resourcesController;
+    private $documentManagementController;
+    private $templatesUbic;
+
+    public function __construct(){
+        $this->responseController = new ResponseController();
+        $this->encryptionController = new EncryptionController();
+        $this->functionsController = new FunctionsController();
+        $this->resourcesController = new ResourcesController();
+        $this->documentManagementController = new DocumentManagementController();
+        $this->templatesUbic = app_path('Http/Controllers');
+    }
+
+    /**
+     * Validar y procesar plantilla Excel de equipamientos
+     * Recibe el ID del archivo temporal ya cargado
+     */
+public function validateAndProcessExcelTemplate(Request $request) {
+    DB::enableQueryLog();
+    
+    $validator = Validator::make($request->all(), [
+        'id_user' => 'required|string',
+        'id_file' => 'required|string',
+        'linea' => 'required|integer',
+        'auto_transfer' => 'boolean' // Nuevo parámetro opcional
+    ]);
+
+    if($validator->fails()){
+        return $this->responseController->makeResponse(
+            true,
+            "Se encontraron uno o más errores.",
+            $this->responseController->makeErrors($validator->errors()->messages()),
+            401
+        );
+    }
+
+    $form = $request->all();
+    $idUser = '0000000001'; // Usar ID fijo por ahora
+    $autoTransfer = $form['auto_transfer'] ?? false; // Por defecto false
+    
+    if(!$idUser){
+        return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
+    }
+
+    $usr = DB::table('S002V01TUSUA')->where([
+        ['USUA_IDUS', '=', $idUser],
+        ['USUA_NULI', '=', $form['linea']]
+    ])->first();
+
+    if(is_null($usr)){
+        return $this->responseController->makeResponse(true, 'El usuario no está registrado', [], 404);
+    }
+
+    // Obtener archivo temporal
+    $fileIdDecrypted = $this->encryptionController->decrypt($form['id_file']);
+    $tempFile = DB::table('S002V01TARTE')->where([
+        ['ARTE_IDAR', '=', $fileIdDecrypted],
+        ['ARTE_NULI', '=', $form['linea']]
+    ])->first();
+    
+    if(is_null($tempFile)){
+        return $this->responseController->makeResponse(true, 'El archivo temporal no fue encontrado', [], 404);
+    }
+
+    try {
+        // **NUEVA VALIDACIÓN: Verificar que el frontend haya generado los códigos**
+        error_log("🔍 [Validation] Verificando códigos generados por frontend...");
+        $codeValidation = $this->validateFrontendCodes($tempFile->ARTE_UBTE);
+        
+        if(!$codeValidation['valid']) {
+            error_log(" [Validation] Códigos del frontend faltantes: " . $codeValidation['message']);
+            return $this->responseController->makeResponse(
+                true, 
+                $codeValidation['message'], 
+                ['details' => $codeValidation['details']], 
+                400
+            );
+        }
+        
+        error_log(" [Validation] Códigos del frontend validados correctamente");
+        
+        // Validar estructura del Excel
+        $structureValidation = $this->validateExcelStructure($tempFile->ARTE_UBTE);
+        if(!$structureValidation['valid']) {
+            return $this->responseController->makeResponse(true, $structureValidation['message'], [], 400);
+        }
+
+        // Procesar contenido del Excel (AHORA usa códigos del frontend)
+        $spreadsheet = IOFactory::load($tempFile->ARTE_UBTE);
+        $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ'];
+        $acronymMappings = $this->extractAcronymMappings($spreadsheet);
+        $cargaMap = $acronymMappings['carga'];
+        $lruMap = $acronymMappings['lru'];
+
+        $sheetsToProcess = [
+            'CASO 1', 'CASO 2', 'CASO 3', 
+            'CASO 4', 'CASO 5', 'CASO 6'
+        ];
+
+        $processedData = [];
+        $errors = [];
+        $successCount = 0;
+        
+        DB::beginTransaction();
+        
+        foreach($sheetsToProcess as $sheetName) {
+            if(!in_array($sheetName, $spreadsheet->getSheetNames())) {
+                $errors[] = "Hoja requerida no encontrada: $sheetName";
+                continue;
+            }
+            
+            $sheetConfig = $config['worksheets'][$sheetName] ?? null;
+            if(!$sheetConfig) {
+                $errors[] = "Configuración no encontrada para hoja: $sheetName";
+                continue;
+            }
+
+            $worksheet = $spreadsheet->getSheetByName($sheetName);
+            $result = $this->processWorksheet(
+                $worksheet, 
+                $sheetConfig, 
+                $sheetName, 
+                $form['linea'], 
+                $idUser,
+                $cargaMap,
+                $lruMap
+            );
+            
+            $processedData = array_merge($processedData, $result['data']);
+            $errors = array_merge($errors, $result['errors']);
+            $successCount += $result['count'];
+        }
+        
+        if(!empty($errors) && empty($processedData)) {
+            DB::rollBack();
+            return $this->responseController->makeResponse(true, "Errores en el procesamiento: " . implode('; ', array_slice($errors, 0, 5)), [], 400);
+        }
+        
+        // **LOG FINAL: Verificar qué códigos se van a insertar**
+        error_log("[Final Insert] Resumen de códigos a insertar:");
+        foreach($processedData as $index => $data) {
+            error_log("   [{$index}] PCEQ_CPGE: '" . $data['PCEQ_CPGE'] . "'");
+            if($index >= 4) { // Mostrar solo los primeros 5
+                error_log("   ... y " . (count($processedData) - 5) . " más");
+                break;
+            }
+        }
+        
+        // Insertar datos procesados en la tabla temporal de equipamientos
+        foreach($processedData as $data) {
+            DB::table('S002V01TPCEQ')->insert($data);
+        }
+        
+        // **NUEVA FUNCIONALIDAD: Transferencia automática a tabla final**
+        $transferResult = null;
+        if ($autoTransfer) {
+            error_log("  [Auto Transfer] Iniciando transferencia automática a tabla final...");
+            $transferResult = $this->transferEquipmentsToFinal($form['linea'], $idUser);
+            
+            if (!$transferResult['success']) {
+                // Si falla la transferencia, hacer rollback completo
+                DB::rollBack();
+                return $this->responseController->makeResponse(
+                    true, 
+                    "Error en transferencia automática: " . $transferResult['message'], 
+                    [], 
+                    500
+                );
+            }
+            
+            error_log("  [Auto Transfer] Transferencia automática completada: {$transferResult['transferred_count']} equipos transferidos");
+        }
+        
+        DB::commit();
+        
+        // Registrar actividad
+        $nowStr = Carbon::now('America/Mexico_city')->toDateTimeString();
+        $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
+        
+        $actions = DB::getQueryLog();
+        $activityMessage = "El usuario $name (" . $usr->USUA_IDUS . ") procesó $successCount equipos desde el archivo {$tempFile->ARTE_NOAR} usando códigos generados por frontend.";
+        
+        if ($autoTransfer && $transferResult) {
+            $activityMessage .= " Se transfirieron automáticamente {$transferResult['transferred_count']} equipos a la tabla final.";
+        }
+        
+        $idac = $this->functionsController->registerActivity(
+            $form['linea'],
+            'S002V01M07GEEQ',
+            'S002V01F01ADEQ',
+            'S002V01P11REEQ',
+            'Registro',
+            $activityMessage,
+            $idUser,
+            $nowStr,
+        );
+        
+        $this->functionsController->registerLog($actions, $idUser, $nowStr, $idac, $form['linea']);
+        
+        $responseData = [
+            'equipos_procesados' => $successCount,
+            'archivo' => $tempFile->ARTE_NOAR,
+            'id_archivo' => $form['id_file'],
+            'codigos_frontend_validados' => true
+        ];
+        
+        // Agregar información de transferencia si se realizó
+        if ($autoTransfer && $transferResult) {
+            $responseData['transferencia_automatica'] = [
+                'realizada' => true,
+                'equipos_transferidos' => $transferResult['transferred_count'],
+                'total_equipos_temporales' => $transferResult['total_temp_equipments']
+            ];
+            
+            if (!empty($transferResult['errors'])) {
+                $responseData['transferencia_automatica']['errores'] = $transferResult['errors'];
+            }
+        }
+        
+        if(!empty($errors)) {
+            $responseData['advertencias'] = array_slice($errors, 0, 10);
+        }
+        
+        $successMessage = "Procesamiento exitoso usando códigos del frontend";
+        if ($autoTransfer && $transferResult) {
+            $successMessage .= " y transferencia automática completada";
+        }
+        
+        error_log("  [Validation Complete] $successMessage");
+        
+        return $this->responseController->makeResponse(false, $successMessage, $responseData);
+        
+    } catch(Exception $e) {
+        DB::rollBack();
+        error_log(" [Validation Error] " . $e->getMessage());
+        return $this->responseController->makeResponse(true, "Error al procesar el archivo: " . $e->getMessage(), [], 500);
+    }
+}
+
+
+private function extractAcronymMappings($spreadsheet) {
+    $mappings = [
+        'carga' => [
+            'equipos' => [], // [acronimo_equipo][acronimo_modelo] = tipo_completo
+            'modelos' => [], // [acronimo_modelo] = modelo_completo
+            'details' => []  // **NUEVO: [acronimo_equipo][acronimo_modelo] = [CARA, COBA, etc.]
+        ], 
+        'lru' => [
+            'equipos' => [],
+            'modelos' => [],
+            'details' => []  // **NUEVO: [acronimo_equipo][acronimo_modelo] = [CARA, COBA, etc.]
+        ]
+    ];
+    
+    // 1. Encontrar hoja EQUIPAMIENTO (case-insensitive)
+    $cargaSheet = null;
+    foreach ($spreadsheet->getAllSheets() as $sheet) {
+        if (strtolower(trim($sheet->getTitle())) === 'equipamiento') {
+            $cargaSheet = $sheet;
+            break;
+        }
+    }
+
+    // 2. Procesar hoja EQUIPAMIENTO si existe
+    if ($cargaSheet) {
+        $highestRow = $cargaSheet->getHighestRow();
+        for ($row = 9; $row <= $highestRow; $row++) {
+            $acronimoEquipo = $this->getCellValue($cargaSheet, 'D', $row);
+            $tipoCompleto = $this->getCellValue($cargaSheet, 'C', $row);
+            $modeloCompleto = $this->getCellValue($cargaSheet, 'E', $row);
+            $acronimoModelo = $this->getCellValue($cargaSheet, 'F', $row);
+            
+            // **NUEVO: Extraer campos adicionales según el field_mapping**
+            $codigoEquivalente = $this->getCellValue($cargaSheet, 'B', $row); // PCEQ_OTCO
+            $numeroSerie = $this->getCellValue($cargaSheet, 'H', $row);        // PCEQ_NUSE
+            $codigoBarras = $this->getCellValue($cargaSheet, 'I', $row);       // PCEQ_COBA
+            $caracter = $this->getCellValue($cargaSheet, 'J', $row);           // PCEQ_CARA
+            $fechaVencimiento = $this->getCellValue($cargaSheet, 'K', $row);   // PCEQ_FVAR
+            
+            if (!empty($acronimoEquipo) && !empty($acronimoModelo)) {
+                // Almacenar combinación equipo+modelo para tipos completos
+                if (!isset($mappings['carga']['equipos'][$acronimoEquipo])) {
+                    $mappings['carga']['equipos'][$acronimoEquipo] = [];
+                }
+                $mappings['carga']['equipos'][$acronimoEquipo][$acronimoModelo] = $tipoCompleto;
+                
+                // **NUEVO: Almacenar detalles del equipo**
+                if (!isset($mappings['carga']['details'][$acronimoEquipo])) {
+                    $mappings['carga']['details'][$acronimoEquipo] = [];
+                }
+                $mappings['carga']['details'][$acronimoEquipo][$acronimoModelo] = [
+                    'PCEQ_OTCO' => $codigoEquivalente,
+                    'PCEQ_NUSE' => $numeroSerie,
+                    'PCEQ_COBA' => $codigoBarras,
+                    'PCEQ_CARA' => $caracter,
+                    'PCEQ_FVAR' => $fechaVencimiento
+                ];
+                
+                error_log("[EQUIPAMIENTO] {$acronimoEquipo}/{$acronimoModelo}: CARA='{$caracter}', COBA='{$codigoBarras}'");
+            }
+            
+            if (!empty($acronimoModelo) && !empty($modeloCompleto)) {
+                $mappings['carga']['modelos'][$acronimoModelo] = $modeloCompleto;
+            }
+        }
+    }
+
+    // 3. Procesar hoja LRU
+    $lruSheet = null;
+    foreach ($spreadsheet->getAllSheets() as $sheet) {
+        if (strtolower(trim($sheet->getTitle())) === 'lru') {
+            $lruSheet = $sheet;
+            break;
+        }
+    }
+
+    if ($lruSheet) {
+        $highestRow = $lruSheet->getHighestRow();
+        for ($row = 9; $row <= $highestRow; $row++) {
+            $acronimoEquipo = $this->getCellValue($lruSheet, 'D', $row);
+            $tipoCompleto = $this->getCellValue($lruSheet, 'C', $row);
+            $modeloCompleto = $this->getCellValue($lruSheet, 'E', $row);
+            $acronimoModelo = $this->getCellValue($lruSheet, 'F', $row);
+            
+            // **NUEVO: Extraer campos adicionales para LRU según field_mapping**
+            $codigoEquivalente = $this->getCellValue($lruSheet, 'B', $row); // PCEQ_OTCO
+            $numeroSerie = $this->getCellValue($lruSheet, 'H', $row);        // PCEQ_NUSE
+            $codigoBarras = $this->getCellValue($lruSheet, 'I', $row);       // PCEQ_COBA
+            $caracter = $this->getCellValue($lruSheet, 'J', $row);           // PCEQ_CARA
+            $fechaVencimiento = $this->getCellValue($lruSheet, 'K', $row);   // PCEQ_FTGA (en LRU es diferente)
+            
+            if (!empty($acronimoEquipo) && !empty($acronimoModelo)) {
+                if (!isset($mappings['lru']['equipos'][$acronimoEquipo])) {
+                    $mappings['lru']['equipos'][$acronimoEquipo] = [];
+                }
+                $mappings['lru']['equipos'][$acronimoEquipo][$acronimoModelo] = $tipoCompleto;
+                
+                // **NUEVO: Almacenar detalles del LRU**
+                if (!isset($mappings['lru']['details'][$acronimoEquipo])) {
+                    $mappings['lru']['details'][$acronimoEquipo] = [];
+                }
+                $mappings['lru']['details'][$acronimoEquipo][$acronimoModelo] = [
+                    'PCEQ_OTCO' => $codigoEquivalente,
+                    'PCEQ_NUSE' => $numeroSerie,
+                    'PCEQ_COBA' => $codigoBarras,
+                    'PCEQ_CARA' => $caracter,
+                    'PCEQ_FVAR' => $fechaVencimiento
+                ];
+                
+                error_log("[LRU] {$acronimoEquipo}/{$acronimoModelo}: CARA='{$caracter}', COBA='{$codigoBarras}'");
+            }
+            
+            if (!empty($acronimoModelo) && !empty($modeloCompleto)) {
+                $mappings['lru']['modelos'][$acronimoModelo] = $modeloCompleto;
+            }
+        }
+    }
+
+    return $mappings;
+}
+
+// Nueva función para obtener valores de celda robusta
+    private function getCellValue($worksheet, $column, $row) {
+        try {
+            $cell = $worksheet->getCell($column . $row);
+            return trim($cell->getFormattedValue());
+        } catch (\Exception $e) {
+            // \Log::error("Error leyendo celda $column$row: " . $e->getMessage());
+            return '';
+        }
+    }
+
+
+
+    /**
+     * Validar solo la estructura del Excel (sin procesar datos)
+     */
+    public function validateExcelStructureOnly(Request $request) {
+        $validator = Validator::make($request->all(), [
+            'id_user' => 'required|string',
+            'id_file' => 'required|string',
+            'linea' => 'required|integer',
+        ]);
+
+        if($validator->fails()){
+            return $this->responseController->makeResponse(
+                true,
+                "Se encontraron uno o más errores.",
+                $this->responseController->makeErrors($validator->errors()->messages()),
+                401
+            );
+        }
+
+        $form = $request->all();
+        $idUser = $this->encryptionController->decrypt($form['id_user']);
+        
+        if(!$idUser){
+            return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
+        }
+
+        // Obtener archivo temporal
+        $fileIdDecrypted = $this->encryptionController->decrypt($form['id_file']);
+        $tempFile = DB::table('S002V01TARTE')->where([
+            ['ARTE_IDAR', '=', $fileIdDecrypted],
+            ['ARTE_NULI', '=', $form['linea']]
+        ])->first();
+        
+        if(is_null($tempFile)){
+            return $this->responseController->makeResponse(true, 'El archivo temporal no fue encontrado', [], 404);
+        }
+
+        // Validar solo la estructura
+        $structureValidation = $this->validateExcelStructure($tempFile->ARTE_UBTE);
+        if(!$structureValidation['valid']) {
+            return $this->responseController->makeResponse(true, $structureValidation['message'], [], 400);
+        }
+
+        return $this->responseController->makeResponse(false, "Estructura del archivo válida", [
+            'archivo' => $tempFile->ARTE_NOAR,
+            'validado' => true
+        ]);
+    }
+
+    private function validateExcelStructure($filePath) {
+        try {
+            $spreadsheet = IOFactory::load($filePath);
+            $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ'];
+            
+            $requiredSheets = array_keys($config['worksheets']);
+            $existingSheets = $spreadsheet->getSheetNames();
+            
+            // Verificar que existan las hojas requeridas
+            $missingSheets = array_diff($requiredSheets, $existingSheets);
+            if(!empty($missingSheets)) {
+                return [
+                    'valid' => false,
+                    'message' => 'Tu documento no cumple con las hojas requeridas de la plantilla'
+                ];
+            }
+
+            // Validar headers de cada hoja
+            foreach($config['worksheets'] as $sheetName => $sheetConfig) {
+                if(!in_array($sheetName, $existingSheets)) continue;
+                
+                $worksheet = $spreadsheet->getSheetByName($sheetName);
+                $headerValidation = $this->validateSheetHeaders($worksheet, $sheetConfig, $sheetName);
+                
+                if(!$headerValidation['valid']) {
+                    return [
+                        'valid' => false,
+                        'message' => 'Tu documento tiene error en los Headers, revísalos e intentalo de nuevo'
+                    ];
+                }
+
+                // Validar que tenga datos (solo para CARGA DE EQUIPOS)
+                if($sheetName === 'EQUIPAMIENTO') {
+                    $dataValidation = $this->validateSheetHasData($worksheet, $sheetName, $sheetConfig);
+                    if(!$dataValidation['valid']) {
+                        return $dataValidation;
+                    }
+                }
+            }
+
+            return ['valid' => true, 'message' => 'Estructura válida'];
+            
+        } catch(Exception $e) {
+            return [
+                'valid' => false,
+                'message' => 'Error al validar la estructura del archivo: ' . $e->getMessage()
+            ];
+        }
+    }
+
+    private function validateSheetHeaders($worksheet, $sheetConfig, $sheetName) {
+        // Para CATÁLOGOS usar validación especial
+        if($sheetName === 'CATÁLOGOS') {
+            return $this->validateCatalogosSheet($worksheet);
+        }
+        
+        $headerRow = $sheetConfig['header_row'] ?? 4;
+        $fieldMapping = $sheetConfig['field_mapping'];
+        
+        // Definir headers esperados según la configuración de Angular
+        $expectedHeaders = $this->getExpectedHeaders($sheetName);
+        
+        if(empty($expectedHeaders)) {
+            return ['valid' => true, 'message' => "No hay headers específicos para validar en $sheetName"];
+        }
+
+        foreach($expectedHeaders as $column => $expectedHeader) {
+            $cellValue = $worksheet->getCell($column . $headerRow)->getCalculatedValue();
+            $actualHeader = $cellValue ? trim((string)$cellValue) : '';
+            
+            if($actualHeader !== $expectedHeader) {
+                return [
+                    'valid' => false,
+                    'message' => "Error en header de $sheetName, columna $column: esperado '$expectedHeader', encontrado '$actualHeader'"
+                ];
+            }
+        }
+        
+        return ['valid' => true, 'message' => "Headers válidos para $sheetName"];
+    }
+
+    private function validateCatalogosSheet($worksheet) {
+        $expectedHeaders = [
+            'B' => 'FAMILIA',
+            'C' => 'ACRÓNIMO',
+            'E' => 'SUBFAMILIA',
+            'F' => 'ACRÓNIMO',
+            'G' => 'SUBFAMILY',
+            'I' => 'UBICACIONES (FRENTES)',
+            'J' => 'CÓDIGO',
+            'L' => 'RMS',
+            'M' => 'ELEMENTO',
+            'N' => 'CÓDIGO',
+            'P' => 'OCUPACIÓN',
+            'Q' => 'CÓDIGO',
+            'S' => 'ESTADO',
+            'T' => 'ACRÓNIMO'
+        ];
+
+        foreach($expectedHeaders as $column => $expectedHeader) {
+            $cellValue = $worksheet->getCell($column . '5')->getCalculatedValue();
+$actualHeader = $cellValue ? trim((string)$cellValue) : '';
+            
+            if($actualHeader !== $expectedHeader) {
+                return [
+                    'valid' => false,
+                    'message' => "Error en CATÁLOGOS, fila 5, columna $column: esperado '$expectedHeader', encontrado '$actualHeader'"
+                ];
+            }
+        }
+
+        return ['valid' => true, 'message' => 'Headers válidos para CATÁLOGOS'];
+    }
+
+    private function validateSheetHasData($worksheet, $sheetName, $sheetConfig) {
+        try {
+            $range = $worksheet->calculateWorksheetDimension();
+            $highestRow = $worksheet->getHighestRow();
+            
+            $dataStartRow = $sheetConfig['date_start_row'] ?? 9;
+            
+            if($highestRow < $dataStartRow) {
+                return [
+                    'valid' => false,
+                    'message' => "La hoja '$sheetName' no contiene datos. Se requiere al menos un registro con información."
+                ];
+            }
+
+            // Verificar que hay al menos una fila con datos
+            $hasData = false;
+            $mainColumns = ['B', 'D', 'F', 'G']; // Columnas principales para verificar
+            
+            for($row = $dataStartRow; $row <= $highestRow; $row++) {
+                foreach($mainColumns as $col) {
+                    $cellValue = $worksheet->getCell($col . $row)->getCalculatedValue();
+                    
+if($cellValue !== null && $cellValue !== '' && trim((string)$cellValue) !== '') {
+                        $hasData = true;
+                        break 2;
+                    }
+                }
+            }
+            
+            if(!$hasData) {
+                return [
+                    'valid' => false,
+                    'message' => "La hoja '$sheetName' no contiene datos. Se requiere al menos un registro con información."
+                ];
+            }
+            
+            return ['valid' => true, 'message' => 'Datos encontrados'];
+            
+        } catch(Exception $e) {
+            return [
+                'valid' => false,
+                'message' => "Error al validar datos en '$sheetName': " . $e->getMessage()
+            ];
+        }
+    }
+
+    private function getExpectedHeaders($sheetName) {
+
+        $headers = [
+            'EQUIPAMIENTO' => [
+                'B' => 'CÓDIGO EQUIVALENTE',
+                'C' => 'TIPO / DESCRIPCIÓN',
+                'D' => 'ACRÓNIMO DEL EQUIPO',
+                'E' => 'MODELO COMPLETO',
+                'F' => 'ACRÓNIMO DEL MODELO',
+                'G' => 'ID',
+                'H' => 'NO.SERIE',
+                'I' => 'NO. CÓDIGO DE BARRAS',
+                'J' => 'CARÁCTER',
+                'K' => 'FECHA DE VENCIMIENTO DEL ARTÍCULO',
+                'L' => 'ETIQUETA FINAL DEL EQUIPO',
+            ],
+            'LRU' => [
+                'B' => 'CÓDIGO EQUIVALENTE',
+                'C' => 'TIPO / DESCRIPCIÓN',
+                'D' => 'ACRÓNIMO DEL EQUIPO',
+                'E' => 'MODELO COMPLETO',
+                'F' => 'ACRÓNIMO DEL MODELO',
+                'G' => 'ID',
+                'H' => 'NO.SERIE',
+                'I' => 'NO. CÓDIGO DE BARRAS',
+                'J' => 'CARÁCTER',
+                'K' => 'FECHA DE VENCIMIENTO DEL ARTÍCULO',
+                'L' => 'ETIQUETA FINAL DEL EQUIPO'
+            ],
+'CASO 1'=> [ 
+      'B'=> 'LÍNEA',
+      'D'=> 'UBICACIÓN',
+      'F'=> 'NIVEL',
+      'H'=> 'OCUPACIÓN',
+      'J'=> 'ELEMENTO',
+      'L'=> 'COORDENADAS PLANO GENERAL',
+      'M'=> 'COORDENADAS DETALLE',
+      'N'=> 'COORDENADAS DE POSICIÓN',
+      'P'=> 'FAMILIA',
+      'R'=> 'SUBFAMILIA',
+      'T'=> 'ESTADO',
+      'V'=> 'TIPO',
+      'X'=> 'MODELO',
+      'Z'=> 'ID',
+      'AB'=> 'TIPO',
+      'AD'=> 'MODELO',
+      'AF'=> 'ID',
+      'AH'=> 'CÓDIGO COMPLETO SAM',
+      'AI'=> 'CÓDIGO EQUIVALENTE',
+],
+    'CASO 2'=> [ 
+      'B'=> 'LÍNEA',
+      'D'=> 'UBICACIÓN',
+      'F'=> 'NIVEL',
+      'H'=> 'OCUPACIÓN',
+      'J'=> 'ELEMENTO',
+      'L'=> 'COORDENADAS PLANO GENERAL',
+      'M'=> 'COORDENADAS DE POSICIÓN',
+      'N'=> 'POSICIÓN EN RACK',
+      'P'=> 'FAMILIA',
+      'R'=> 'SUBFAMILIA',
+      'T'=> 'ESTADO',
+      'V'=> 'TIPO',
+      'X'=> 'MODELO',
+      'Z'=> 'ID',
+      'AB'=> 'TIPO',
+      'AD'=> 'MODELO',
+      'AF'=> 'ID',
+      'AH'=> 'CÓDIGO COMPLETO',
+      'AI'=> 'CÓDIGO EQUIVALENTE',
+    ],
+    'CASO 3'=> [ 
+      'B'=> 'LÍNEA',
+      'D'=> 'UBICACIÓN ORIGEN',
+      'F'=> 'NIVEL ORIGEN',
+      'H'=> 'OCUPACIÓN ORIGEN',
+      'J'=> 'ELEMENTO ORIGEN',
+      'L'=> 'PK ORIGEN',
+      'N'=> 'UBICACIÓN DESTINO',
+      'P'=> 'NIVEL DESTINO',
+      'R'=> 'OCUPACIÓN DESTINO',
+      'T'=> 'ELEMENTO DESTINO',
+      'V'=> 'PK DESTINO',
+      'X'=> 'FAMILIA',
+      'Z'=> 'SUBFAMILIA',
+      'AB'=> 'ESTADO',
+      'AD'=> 'TIPO',
+      'AF'=> 'MODELO',
+      'AH'=> 'ID',
+      'AJ'=> 'TIPO',
+      'AL'=> 'MODELO',
+      'AN'=> 'ID',
+      'AP'=> 'CÓDIGO COMPLETO',
+      'AQ'=> 'CÓDIGO EQUIVALENTE'
+    ],
+    'CASO 4'=> [ 
+      'B'=> 'LÍNEA',
+      'D'=> 'UBICACIÓN ORIGEN',
+      'F'=> 'NIVEL ORIGEN',
+      'H'=> 'OCUPACIÓN ORIGEN',
+      'J'=> 'ELEMENTO ORIGEN',
+      'L'=> 'SECUENCIAL ORIGEN',
+      'N'=> 'COORDENADAS PLANO',
+      'O'=> 'COORDENADAS DETALLE',
+      'P'=> 'COORDENADAS DE POSICIÓN',
+      'R'=> 'UBICACIÓN DESTINO',
+      'T'=> 'NIVEL DESTINO',
+      'V'=> 'OCUPACIÓN DESTINO',
+      'X'=> 'ELEMENTO DESTINO',
+      'Z'=> 'SECUENCIAL DESTINO',
+      'AB'=> 'COORDENADAS PLANO',
+      'AC'=> 'COORDENADAS DETALLE',
+      'AD'=> 'COORDENADAS DE POSICIÓN',
+      'AF'=> 'FAMILIA',
+      'AH'=> 'SUBFAMILIA',
+      'AJ'=> 'ESTADO',
+      'AL'=> 'TIPO',
+      'AN'=> 'MODELO',
+      'AP'=> 'ID',
+      'AR'=> 'TIPO',
+      'AT'=> 'MODELO',
+      'AV'=> 'ID',
+      'AX'=> 'CÓDIGO COMPLETO',
+      'AY'=> 'CÓDIGO EQUIVALENTE',
+    ],
+    'CASO 5'=> [ 
+      'B'=> 'LÍNEA',
+      'D'=> 'UBICACIÓN',
+      'F'=> 'NIVEL',
+      'H'=> 'OCUPACIÓN',
+      'J'=> 'ÁREA',
+      'L'=> 'ELEMENTO',
+      'N'=> 'FAMILIA',
+      'P'=> 'SUBFAMILIA',
+      'R'=> 'ESTADO',
+      'T'=> 'TIPO',
+      'V'=> 'MODELO',
+      'X'=> 'ID',
+      'Z'=> 'TIPO',
+      'AB'=> 'MODELO',
+      'AD'=> 'ID',
+      'AF'=> 'CÓDIGO COMPLETO',
+      'AG'=> 'CÓDIGO EQUIVALENTE', 
+    ],
+    'CASO 6' => [ 
+      'B'=> 'LÍNEA',
+      'D'=> 'UBICACIÓN',
+      'F'=> 'NIVEL',
+      'H'=> 'OCUPACIÓN',
+      'J'=> 'ELEMENTO',
+      'L'=> 'POSICIÓN',
+      'N'=> 'FAMILIA',
+      'P'=> 'SUBFAMILIA',
+      'R'=> 'ESTADO',
+      'T'=> 'TIPO',
+      'V'=> 'MODELO',
+      'X'=> 'ID',
+      'Z'=> 'TIPO',
+      'AB'=> 'MODELO',
+      'AD'=> 'ID',
+      'AF'=> 'CÓDIGO COMPLETO',
+      'AG'=> 'CÓDIGO EQUIVALENTE' 
+    ]
+    ];
+
+        return $headers[$sheetName] ?? [];
+    }
+
+
+private function extractRowData($worksheet, $row, $fieldMapping, $sheetName) {
+    $rowData = [];
+    
+    foreach($fieldMapping as $column => $field) {
+        if (!empty($field) && $field !== '.' && $field !== '-' && $field !== '_' && $field !== '+') {
+            $cellValue = $worksheet->getCell($column . $row)->getCalculatedValue();
+            $rowData[$field] = $cellValue;
+        }
+    }
+
+    // **CAMBIO PRINCIPAL: Usar SIEMPRE el código generado por el frontend**
+    $frontendCode = $this->extractFrontendGeneratedCode($worksheet, $row, $sheetName);
+    
+    if (!empty($frontendCode)) {
+        $rowData['PCEQ_CPGE'] = trim($frontendCode);
+        
+        // Log para debug
+        error_log("[Frontend Code] {$sheetName} Fila {$row}: Usando código del frontend: '{$frontendCode}'");
+    } else {
+        error_log("[Frontend Code] {$sheetName} Fila {$row}: No se encontró código generado por frontend");
+        
+        // Como fallback, generar código básico pero con advertencia
+        $rowData['PCEQ_CPGE'] = $this->generateFallbackCode($rowData, $sheetName, $row);
+    }
+    
+    // Manejar concatenaciones especiales para coordenadas si es necesario
+    $this->handleCoordinateConcatenation($rowData, $worksheet, $row, $sheetName);
+    
+    // Inicializar campos de acrónimos (para compatibilidad)
+    $rowData['PCEQ_TIEQ_ACRONIMO'] = '';
+    $rowData['PCEQ_MOEQ_ACRONIMO'] = '';
+    $rowData['PCEQ_TIEQ_HIJO_ACRONIMO'] = '';
+    $rowData['PCEQ_MOEQ_HIJO_ACRONIMO'] = '';
+
+    return $rowData;
+}
+
+private function generateFallbackCode($rowData, $sheetName, $row) {
+    error_log("  [Fallback Code] Generando código de respaldo para {$sheetName} Fila {$row}");
+    
+    // Para hojas de casos, usar un formato básico
+    if (in_array($sheetName, ['CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6'])) {
+        $linea = $rowData['PCEQ_NULI'] ?? '';
+        $tipo = $rowData['PCEQ_TIEQ'] ?? '';
+        $modelo = $rowData['PCEQ_MOEQ'] ?? '';
+        $timestamp = date('YmdHis');
+        
+        return "FALLBACK_{$linea}_{$tipo}_{$modelo}_{$timestamp}_{$row}";
+    }
+    
+    // Para EQUIPAMIENTO y LRU, usar ID simple
+    return $this->generateUniqueHashId($rowData['PCEQ_NULI'] ?? '1');
+}
+
+
+private function extractFrontendGeneratedCode($worksheet, $row, $sheetName) {
+    $codeColumns = [
+        'CASO 1' => 'AH',    // Columna AH para CASO 1
+        'CASO 2' => 'AH',    // Columna AH para CASO 2  
+        'CASO 3' => 'AP',    // Columna AP para CASO 3
+        'CASO 4' => 'AX',    // Columna AX para CASO 4
+        'CASO 5' => 'AF',    // Columna AF para CASO 5
+        'CASO 6' => 'AF',    // Columna AF para CASO 6
+        // Para CARGA DE EQUIPOS y LRU, no hay código complejo generado
+        'EQUIPAMIENTO' => null,
+        'LRU' => null
+    ];
+    
+    $columnLetter = $codeColumns[$sheetName] ?? null;
+    
+    if ($columnLetter) {
+        try {
+            $cellValue = $worksheet->getCell($columnLetter . $row)->getCalculatedValue();
+            $code = $cellValue ? trim((string)$cellValue) : '';
+            
+            // Validar que el código no esté vacío y tenga formato esperado
+            if (!empty($code) && strlen($code) > 5) {  // Códigos válidos suelen ser largos
+                error_log("  [Code Extract] {$sheetName} Fila {$row}: Código encontrado en {$columnLetter}: '{$code}'");
+                return $code;
+            } else {
+                error_log("⚠️ [Code Extract] {$sheetName} Fila {$row}: Código en {$columnLetter} está vacío o muy corto: '{$code}'");
+            }
+        } catch (\Exception $e) {
+            error_log(" [Code Extract] {$sheetName} Fila {$row}: Error leyendo {$columnLetter}: " . $e->getMessage());
+        }
+    }
+    
+    return null;
+}
+
+private function handleCoordinateConcatenation(&$rowData, $worksheet, $row, $sheetName) {
+    switch($sheetName) {
+        case 'CASO 1':
+        case 'CASO 2':
+            $coordL = $this->getCellValue($worksheet, 'L', $row);
+            $coordM = $this->getCellValue($worksheet, 'M', $row);
+            $coordN = $this->getCellValue($worksheet, 'N', $row);
+            $rowData['PCEQ_COOR'] = $coordL . $coordM . $coordN;
+            break;
+            
+        case 'CASO 4':
+            // Coordenadas origen
+            $coordN = $this->getCellValue($worksheet, 'N', $row);
+            $coordO = $this->getCellValue($worksheet, 'O', $row);
+            $coordP = $this->getCellValue($worksheet, 'P', $row);
+            $rowData['PCEQ_COOR_ORIGEN'] = $coordN . $coordO . $coordP;
+            
+            // Coordenadas destino
+            $coordAB = $this->getCellValue($worksheet, 'AB', $row);
+            $coordAC = $this->getCellValue($worksheet, 'AC', $row);
+            $coordAD = $this->getCellValue($worksheet, 'AD', $row);
+            $rowData['PCEQ_COOR_DESTINO'] = $coordAB . $coordAC . $coordAD;
+            break;
+    }
+}
+
+
+private function logCodeComparison($frontendCode, $backendCode, $sheetName, $row) {
+    if ($frontendCode !== $backendCode) {
+        // \Log::info("Comparación de códigos en $sheetName fila $row:");
+        // \Log::info("Frontend: $frontendCode");
+        // \Log::info("Backend:  $backendCode");
+        
+        // Analizar diferencias
+        $frontParts = explode('.', $frontendCode);
+        $backParts = explode('.', $backendCode);
+        
+        for ($i = 0; $i < max(count($frontParts), count($backParts)); $i++) {
+            $front = $frontParts[$i] ?? '[MISSING]';
+            $back = $backParts[$i] ?? '[MISSING]';
+            if ($front !== $back) {
+                // \Log::info("Diferencia en parte $i: Frontend='$front' vs Backend='$back'");
+            }
+        }
+    }
+}
+
+
+
+    private function isEmptyRow($rowData) {
+        $requiredFields = ['PCEQ_TIEQ', 'PCEQ_MOEQ']; // Campos mínimos
+        
+        foreach($requiredFields as $field) {
+if(isset($rowData[$field]) && !empty(trim((string)$rowData[$field]))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+   private function validateRowData($rowData, $sheetName, $row) {
+        $errors = [];
+        $valid = true;
+        
+        // Para hojas de casos 1-6
+        if (in_array($sheetName, ['CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6'])) {
+            $tienePrimerConjunto = 
+                !empty(trim($rowData['PCEQ_TIEQ'] ?? '')) && 
+                !empty(trim($rowData['PCEQ_MOEQ'] ?? ''));
+            
+            $tieneSegundoConjunto = 
+                !empty(trim($rowData['PCEQ_TIEQ_HIJO'] ?? '')) && 
+                !empty(trim($rowData['PCEQ_MOEQ_HIJO'] ?? ''));
+            
+            if (!$tienePrimerConjunto && !$tieneSegundoConjunto) {
+                $errors[] = 'Se requiere al menos un conjunto completo de datos (Tipo/Modelo)';
+                $valid = false;
+            }
+        } else {
+            if(empty($rowData['PCEQ_TIEQ'] ?? '')) {
+                $errors[] = 'Tipo de equipo requerido';
+                $valid = false;
+            }
+            
+            if(empty($rowData['PCEQ_MOEQ'] ?? '')) {
+                $errors[] = 'Modelo requerido';
+                $valid = false;
+            }
+        }
+        
+        // Validar fechas si existen
+        $dateFields = ['PCEQ_FEAD', 'PCEQ_FIGA', 'PCEQ_FTGA'];
+        foreach($dateFields as $dateField) {
+            if(isset($rowData[$dateField]) && !empty($rowData[$dateField])) {
+                try {
+                    Carbon::parse($rowData[$dateField]);
+                } catch(Exception $e) {
+                    $errors[] = "Fecha inválida en campo $dateField";
+                    $valid = false;
+                }
+            }
+        }
+        
+        return ['valid' => $valid, 'errors' => $errors];
+    }
+
+
+private function noDate($value)
+{
+    if (empty($value) || 
+        strtoupper(trim($value)) === 'NA' || 
+        trim($value) === '.' || 
+        trim($value) === '-') {
+        return now()->format('Y-m-d');
+    }
+    
+    if (is_numeric($value)) {
+        try {
+            return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value)->format('Y-m-d');
+        } catch (Exception $e) {
+            return null;
+        }
+    }
+    
+    if (is_string($value)) {
+        try {
+            $date = new \DateTime($value);
+            return $date->format('Y-m-d');
+        } catch (Exception $e) {
+            return null;
+        }
+    }
+    
+    return null;
+}
+
+
+private function prepareEquipmentData($rowData, $linea, $idUser, $sheetName) {
+    $nowStr = Carbon::now('America/Mexico_city')->toDateTimeString();
+    
+    // **MODIFICADO: Determinar si es hijo ANTES de procesar los campos**
+    $tieneSegundoConjunto = 
+        !empty(trim($rowData['PCEQ_TIEQ_HIJO'] ?? '')) && 
+        !empty(trim($rowData['PCEQ_MOEQ_HIJO'] ?? ''));
+    
+    $isHijo = $tieneSegundoConjunto;
+    
+    // **NUEVO: Obtener PCEQ_CARA y PCEQ_COBA según jerarquía**
+    if ($isHijo) {
+        // Para HIJO: usar los campos con sufijo _HIJO
+        $caracter = $this->normalizeCaracter($rowData['PCEQ_CARA_HIJO'] ?? null);
+        $codigoBarras = $rowData['PCEQ_COBA_HIJO'] ?? '';
+        $numeroSerie = $rowData['PCEQ_NUSE_HIJO'] ?? '';
+        $codigoEquivalente = $rowData['PCEQ_OTCO_HIJO'] ?? '';
+        $fechaVencimiento = $this->noDate($rowData['PCEQ_FVAR_HIJO'] ?? null);
+        
+        error_log("[Equipment Data] HIJO - Usando: CARA='{$caracter}', COBA='{$codigoBarras}'");
+    } else {
+        // Para PADRE: usar los campos normales
+        $caracter = $this->normalizeCaracter($rowData['PCEQ_CARA'] ?? null);
+        $codigoBarras = $rowData['PCEQ_COBA'] ?? '';
+        $numeroSerie = $rowData['PCEQ_NUSE'] ?? '';
+        $codigoEquivalente = $rowData['PCEQ_OTCO'] ?? '';
+        $fechaVencimiento = $this->noDate($rowData['PCEQ_FVAR'] ?? null);
+        
+        error_log("[Equipment Data] PADRE - Usando: CARA='{$caracter}', COBA='{$codigoBarras}'");
+    }
+    
+    // Procesar otras fechas
+    $fechaInicioGarantia = $this->parseDate($rowData['PCEQ_FIGA'] ?? null);
+    $fechaFinGarantia = $this->parseDate($rowData['PCEQ_FTGA'] ?? null);
+    $fechaAdquisicion = $this->parseDate($rowData['PCEQ_FEAD'] ?? null);
+    
+    // Procesar OTCO
+    $otcoArray = [];
+    if (!empty(trim($codigoEquivalente))) {
+        if (is_string($codigoEquivalente) && json_decode($codigoEquivalente) !== null) {
+            $otcoArray = json_decode($codigoEquivalente, true);
+        } else {
+            $otcoArray = [$codigoEquivalente];
+        }
+    }
+    
+    $equipmentData = [
+        'PCEQ_FIGA' => $fechaInicioGarantia ?? now()->format('Y-m-d'),
+        'PCEQ_FEAD' => $fechaAdquisicion ?? now()->format('Y-m-d'),
+        'PCEQ_FTGA' => $fechaFinGarantia ?? now()->format('Y-m-d'),
+        'PCEQ_NULI' => $linea,
+        'PCEQ_UBOR' => $rowData['PCEQ_UBOR'] ?? '',
+        'PCEQ_NIOR' => $rowData['PCEQ_NIOR'] ?? '',
+        'PCEQ_OCOR' => $rowData['PCEQ_OCOR'] ?? '',
+        'PCEQ_ELOR' => $rowData['PCEQ_ELOR'] ?? '',
+        'PCEQ_COOR' => $rowData['PCEQ_COOR'] ?? '',
+        'PCEQ_FAMI' => $rowData['PCEQ_FAMI'] ?? '',
+        'PCEQ_SUBF' => $rowData['PCEQ_SUBF'] ?? '',
+        'PCEQ_ESEQ' => $rowData['PCEQ_ESEQ'] ?? 'A',
+        'PCEQ_TICO' => $this->getCodeTypeFromSheet($sheetName),
+        'PCEQ_JERA' => $isHijo ? 'Hijo' : 'Padre',
+        'PCEQ_EQPA' => null,
+        
+        // **CAMPOS OBTENIDOS SEGÚN JERARQUÍA**
+        'PCEQ_NUSE' => $numeroSerie,
+        'PCEQ_COBA' => $codigoBarras,
+        'PCEQ_CARA' => $caracter,
+        'PCEQ_FVAR' => $fechaVencimiento ?? now()->format('Y-m-d'),
+        'PCEQ_OTCO' => json_encode($otcoArray),
+        
+        // Campos fijos
+        'PCEQ_PREQ' => $rowData['PCEQ_PREQ'] ?? 0,
+        'PCEQ_GAIM' => json_encode([]),
+        'PCEQ_DORE' => json_encode([]),
+        'PCEQ_ESRE' => 'Revisión',
+        'PCEQ_USRE' => $idUser,
+        'PCEQ_FERE' => $nowStr,
+        'PCEQ_IDPR' => $this->generateNumericUniqueId($linea),
+        'PCEQ_KIOR' => $rowData['PCEQ_KIOR'] ?? '',
+        'PCEQ_ELDE' => $rowData['PCEQ_ELDE'] ?? '',
+        
+        // **CÓDIGO DEL FRONTEND (SIN CAMBIOS)**
+        'PCEQ_CPGE' => $rowData['PCEQ_CPGE'] ?? '',
+        'PCEQ_EQPA' => ''
+    ];
+
+    // Manejo de jerarquía para código padre
+    if ($equipmentData['PCEQ_JERA'] === 'Hijo') {
+        $fullCode = $equipmentData['PCEQ_CPGE'] ?? '';
+        
+        // Extraer el código padre eliminando el último segmento
+        $parts = explode('.', $fullCode);
+        if (count($parts) > 1) {
+            array_pop($parts); // Eliminar segmento hijo
+            $equipmentData['PCEQ_EQPA'] = implode('.', $parts);
+            
+            error_log("👨‍👦 [Hierarchy] {$sheetName} - Código padre extraído: " . $equipmentData['PCEQ_EQPA']);
+        }
+    }
+
+    // Validación final del código
+    if (empty($equipmentData['PCEQ_CPGE'])) {
+        error_log("[Equipment Data] ADVERTENCIA: PCEQ_CPGE está vacío para fila de {$sheetName}");
+        $equipmentData['PCEQ_CPGE'] = $this->generateFallbackCode($rowData, $sheetName, 0);
+        error_log("[Equipment Data] Usando código de respaldo: " . $equipmentData['PCEQ_CPGE']);
+    }
+
+    // Lógica de nombres según jerarquía
+    if (in_array($sheetName, ['CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6'])) {
+        if ($isHijo) {
+            // Para HIJO: Usar nombres completos
+            $equipmentData['PCEQ_TIEQ'] = $rowData['PCEQ_TIEQ_HIJO_COMPLETO'] ?? $rowData['PCEQ_TIEQ_HIJO'] ?? '';
+            $equipmentData['PCEQ_MOEQ'] = $rowData['PCEQ_MOEQ_HIJO_COMPLETO'] ?? $rowData['PCEQ_MOEQ_HIJO'] ?? '';
+        } else {
+            // Para PADRE: Usar nombres completos
+            $equipmentData['PCEQ_TIEQ'] = $rowData['PCEQ_TIEQ_COMPLETO'] ?? $rowData['PCEQ_TIEQ'] ?? '';
+            $equipmentData['PCEQ_MOEQ'] = $rowData['PCEQ_MOEQ_COMPLETO'] ?? $rowData['PCEQ_MOEQ'] ?? '';
+        }
+    } else {
+        // Para EQUIPAMIENTO y LRU: También usar nombres completos si están disponibles
+        $equipmentData['PCEQ_TIEQ'] = $rowData['PCEQ_TIEQ_COMPLETO'] ?? $rowData['PCEQ_TIEQ'] ?? '';
+        $equipmentData['PCEQ_MOEQ'] = $rowData['PCEQ_MOEQ_COMPLETO'] ?? $rowData['PCEQ_MOEQ'] ?? '';
+    }
+    
+    // Agregar campos específicos según el tipo de hoja
+    $this->addSheetSpecificFields($equipmentData, $rowData, $sheetName);
+    
+    return $equipmentData;
+}
+
+
+private function parseDate($value) {
+    if (!$value) return null;
+    
+    try {
+        // Intentar como fecha de Excel
+        if (is_numeric($value)) {
+            return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($value)
+                ->format('Y-m-d');
+        }
+        
+        // Intentar como cadena de fecha
+        return Carbon::createFromFormat('d/m/Y', $value)->format('Y-m-d');
+    } catch (\Exception $e) {
+        return null;
+    }
+}
+
+
+
+
+// Función para manejar jerarquía
+private function handleHierarchy(&$equipmentData, $rowData, $sheetName) {
+    if (!in_array($sheetName, ['CASO 1','CASO 2','CASO 3','CASO 4','CASO 5','CASO 6'])) {
+        return;
+    }
+
+    // Determinar si es hijo
+    $isHijo = !empty($rowData['PCEQ_TIEQ_HIJO']) || !empty($rowData['PCEQ_MOEQ_HIJO']);
+    
+    if ($isHijo) {
+        $equipmentData['PCEQ_JERA'] = 'Hijo';
+        $equipmentData['PCEQ_TIEQ'] = $rowData['PCEQ_TIEQ_HIJO'] ?? '';
+        $equipmentData['PCEQ_MOEQ'] = $rowData['PCEQ_MOEQ_HIJO'] ?? '';
+    }
+}
+
+private function normalizeCaracter($value) {
+    if (!$value) return null;
+    
+    $value = strtoupper(trim($value));
+    $mapping = [
+        'REPARABLE' => 'REPARABLE',
+        'REP' => 'REPARABLE',
+        'R' => 'REPARABLE',
+        'CONSUMIBLE' => 'CONSUMIBLE',
+        'CONSUM' => 'CONSUMIBLE',
+        'CONS' => 'CONSUMIBLE',
+        'C' => 'CONSUMIBLE',
+        'DESECHABLE' => 'CONSUMIBLE'
+    ];
+    
+    return $mapping[$value] ?? null;
+}
+
+// MÉTODO PARA GENERAR PCEQ_IDPR: Solo ID numérico único
+private function generateNumericUniqueId($linea, $increment = true) {
+    static $lastId = []; 
+    static $initialized = []; 
+    
+    // Solo inicializar una vez por línea
+    if (!isset($initialized[$linea])) {
+        // Obtener el máximo ID existente en la base de datos
+        $maxId = DB::table('S002V01TPCEQ')
+            ->where('PCEQ_NULI', $linea)
+            ->max('PCEQ_IDPR');
+        
+        if ($maxId) {
+            // Si hay registros, convertir a entero (removiendo padding)
+            $lastId[$linea] = (int)ltrim($maxId, '0');
+        } else {
+            // Si NO hay registros, empezar desde 0
+            $lastId[$linea] = 0;
+        }
+        
+        $initialized[$linea] = true;
+        
+        // Log para debug
+    }
+    
+    // Solo incrementar si se solicita (por defecto true)
+    if ($increment) {
+        $lastId[$linea]++;
+    }
+    
+    // FORMATEAR A 6 DÍGITOS
+    return str_pad($lastId[$linea], 6, '0', STR_PAD_LEFT);
+}
+
+// MÉTODO PARA GENERAR PCEQ_CPGE: El código concatenado complejo (solo CASOS 1-6)
+
+
+private function getAcronymForCode($data, $type) {
+    // Determinar si usar padre o hijo según PCEQ_JERA
+    $isHijo = ($data['PCEQ_JERA'] ?? 'Padre') === 'Hijo';
+    
+    if ($type === 'TIEQ') {
+        if ($isHijo) {
+            return $data['PCEQ_TIEQ_HIJO_ACRONIMO'] ?? $data['PCEQ_TIEQ_ACRONIMO'] ?? '';
+        } else {
+            return $data['PCEQ_TIEQ_ACRONIMO'] ?? '';
+        }
+    } elseif ($type === 'MOEQ') {
+        if ($isHijo) {
+            return $data['PCEQ_MOEQ_HIJO_ACRONIMO'] ?? $data['PCEQ_MOEQ_ACRONIMO'] ?? '';
+        } else {
+            return $data['PCEQ_MOEQ_ACRONIMO'] ?? '';
+        }
+    }
+    
+    return '';
+}
+
+// MÉTODO ALTERNATIVO: Usar timestamp + hash para garantizar unicidad (para CARGA DE EQUIPOS y LRU)
+private function generateUniqueHashId($linea) {
+    // Crear ID único basado en timestamp + random para CARGA DE EQUIPOS y LRU
+    return $linea . '_' . date('YmdHis') . '_' . uniqid();
+}
+
+// MÉTODO DE RESPALDO: Si falla todo, usar este ID de emergencia
+private function generateFallbackId($linea) {
+    return 'FALLBACK_' . $linea . '_' . microtime(true) . '_' . rand(10000, 99999);
+}
+
+// SOLUCIÓN MEJORADA: Verificar duplicados de PCEQ_IDPR antes de insertar
+private function insertEquipmentWithDuplicateHandling($equipmentData) {
+    $maxAttempts = 5;
+    $attempt = 0;
+    
+    while($attempt < $maxAttempts) {
+        try {
+            // Verificar si ya existe este PCEQ_IDPR para esta línea
+            $exists = DB::table('S002V01TPCEQ')
+                ->where('PCEQ_NULI', $equipmentData['PCEQ_NULI'])
+                ->where('PCEQ_IDPR', $equipmentData['PCEQ_IDPR'])
+                ->exists();
+            
+            if($exists) {
+                // Regenerar PCEQ_IDPR numérico único
+                $equipmentData['PCEQ_IDPR'] = $this->generateNumericUniqueId($equipmentData['PCEQ_NULI']);
+                
+                // Si es un CASO 1-6, regenerar también el PCEQ_CPGE con el nuevo IDPR
+                if(isset($equipmentData['PCEQ_CPGE']) && strpos($equipmentData['PCEQ_CPGE'], '.') !== false) {
+                    $sheetName = $this->getSheetNameFromCodeType($equipmentData['PCEQ_TICO']);
+                    if($sheetName) {
+                        $equipmentData['PCEQ_CPGE'] = $this->generateConcatenatedCode($equipmentData, $sheetName);
+                    }
+                }
+                
+                $attempt++;
+                continue;
+            }
+            
+            // Intentar insertar
+            return DB::table('S002V01TPCEQ')->insert($equipmentData);
+            
+        } catch(\Illuminate\Database\QueryException $e) {
+            // Si es error de clave duplicada, regenerar ID
+            if($e->getCode() == 23000 || strpos($e->getMessage(), 'Duplicate entry') !== false) {
+                $equipmentData['PCEQ_IDPR'] = $this->generateNumericUniqueId($equipmentData['PCEQ_NULI']);
+                $attempt++;
+                continue;
+            }
+            
+            // Si es otro tipo de error, re-lanzar
+            throw $e;
+        }
+    }
+    
+    throw new Exception("No se pudo insertar el registro después de $maxAttempts intentos");
+}
+
+// MÉTODO AUXILIAR: Obtener nombre de hoja basado en tipo de código
+private function getSheetNameFromCodeType($codeType) {
+    $codeTypes = [
+        '1' => 'CASO 1',
+        '2' => 'CASO 2', 
+        '3' => 'CASO 3',
+        '4' => 'CASO 4',
+        '5' => 'CASO 5',
+        '6' => 'CASO 6'
+    ];
+    
+    return $codeTypes[$codeType] ?? null;
+}
+
+// SOLUCIÓN 3: Usar upsert (insertar o actualizar)
+private function upsertEquipmentData($equipmentData) {
+    return DB::table('S002V01TPCEQ')->updateOrInsert(
+        [
+            'PCEQ_NULI' => $equipmentData['PCEQ_NULI'],
+            'PCEQ_TICO' => $equipmentData['PCEQ_TICO'],
+            'PCEQ_IDPR' => $equipmentData['PCEQ_IDPR']
+        ],
+        $equipmentData
+    );
+}
+
+// MÉTODO MODIFICADO PARA PROCESAR WORKSHEET CON MANEJO DE DUPLICADOS
+private function processWorksheet($worksheet, $sheetConfig, $sheetName, $linea, $idUser, $cargaMap, $lruMap) {
+    $processedData = [];
+    $errors = [];
+    $count = 0;
+    
+    $highestRow = $worksheet->getHighestRow();
+    $startRow = $sheetConfig['date_start_row'];
+    $fieldMapping = $sheetConfig['field_mapping'];
+
+    error_log("  [Process Worksheet] Procesando {$sheetName}: Filas {$startRow}-{$highestRow}");
+
+    for($row = $startRow; $row <= $highestRow; $row++) {
+        $rowData = $this->extractRowData($worksheet, $row, $fieldMapping, $sheetName);
+        
+        if($this->isEmptyRow($rowData)) {
+            continue;
+        }
+        
+        // **VALIDACIÓN CRÍTICA: Verificar que el código del frontend esté presente**
+        if (empty($rowData['PCEQ_CPGE']) || strlen(trim($rowData['PCEQ_CPGE'])) < 3) {
+            error_log("[Process Worksheet] {$sheetName} Fila {$row}: Código PCEQ_CPGE inválido: '" . ($rowData['PCEQ_CPGE'] ?? 'NULL') . "'");
+            // Continuar procesamiento pero con advertencia
+        } else {
+            error_log("  [Process Worksheet] {$sheetName} Fila {$row}: Código PCEQ_CPGE válido: '" . $rowData['PCEQ_CPGE'] . "'");
+        }
+        
+        // Resolver nombres completos y acrónimos
+        $this->resolveEquipmentNames($rowData, $sheetName, $cargaMap, $lruMap);
+
+        $validation = $this->validateRowData($rowData, $sheetName, $row);
+        
+        if(!$validation['valid']) {
+            $errors[] = "Hoja: $sheetName, Fila: $row - " . implode(', ', $validation['errors']);
+            continue;
+        }
+        
+        // Preparar datos del equipo (AHORA usa el código del frontend)
+        $equipmentData = $this->prepareEquipmentData($rowData, $linea, $idUser, $sheetName);
+        
+        // **LOG FINAL: Confirmar qué código se va a insertar**
+        error_log("[Final Data] {$sheetName} Fila {$row}: PCEQ_CPGE final = '" . $equipmentData['PCEQ_CPGE'] . "'");
+        
+        $processedData[] = $equipmentData;
+        $count++;
+    }
+    
+    error_log("  [Process Worksheet] {$sheetName} completado: {$count} equipos procesados, " . count($errors) . " errores");
+    
+    return [
+        'data' => $processedData,
+        'errors' => $errors,
+        'count' => $count
+    ];
+}
+
+
+public function validateFrontendCodes($filePath) {
+    try {
+        $spreadsheet = IOFactory::load($filePath);
+        $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ'];
+        
+        $codeValidation = [
+            'valid' => true,
+            'message' => '',
+            'details' => []
+        ];
+        
+        $sheetsToValidate = ['CASO 1', 'CASO 2', 'CASO 3', 'CASO 4', 'CASO 5', 'CASO 6'];
+        
+        foreach($sheetsToValidate as $sheetName) {
+            if(!in_array($sheetName, $spreadsheet->getSheetNames())) {
+                continue;
+            }
+            
+            $worksheet = $spreadsheet->getSheetByName($sheetName);
+            $sheetConfig = $config['worksheets'][$sheetName] ?? null;
+            
+            if(!$sheetConfig) continue;
+            
+            $highestRow = $worksheet->getHighestRow();
+            $startRow = $sheetConfig['date_start_row'];
+            
+            $emptyCodeCount = 0;
+            $totalRows = 0;
+            
+            for($row = $startRow; $row <= $highestRow; $row++) {
+                // Verificar si la fila tiene datos
+                $hasData = false;
+                foreach(['B', 'D', 'F', 'H'] as $col) {
+                    $cellValue = $worksheet->getCell($col . $row)->getCalculatedValue();
+                    if(!empty($cellValue)) {
+                        $hasData = true;
+                        break;
+                    }
+                }
+                
+                if(!$hasData) continue;
+                
+                $totalRows++;
+                
+                // Verificar código generado por frontend
+                $frontendCode = $this->extractFrontendGeneratedCode($worksheet, $row, $sheetName);
+                
+                if(empty($frontendCode)) {
+                    $emptyCodeCount++;
+                    $codeValidation['details'][] = "Hoja {$sheetName}, Fila {$row}: Código faltante";
+                }
+            }
+            
+            if($emptyCodeCount > 0) {
+                $codeValidation['valid'] = false;
+                $percentage = round(($emptyCodeCount / $totalRows) * 100, 1);
+                $codeValidation['details'][] = "Hoja {$sheetName}: {$emptyCodeCount} de {$totalRows} filas sin código ({$percentage}%)";
+            }
+        }
+        
+        if(!$codeValidation['valid']) {
+            $codeValidation['message'] = 'Faltan códigos generados por el frontend. Asegúrate de procesar el archivo en el preview antes de validar.';
+        } else {
+            $codeValidation['message'] = 'Todos los códigos del frontend están presentes';
+        }
+        
+        return $codeValidation;
+        
+    } catch(Exception $e) {
+        return [
+            'valid' => false,
+            'message' => 'Error al validar códigos del frontend: ' . $e->getMessage(),
+            'details' => []
+        ];
+    }
+}
+
+// Obtener todos los equipos de CARGA DE EQUIPOS
+private function getAllCargaEquipments($spreadsheet) {
+        $equipments = [];
+        $sheet = $spreadsheet->getSheetByName('EQUIPAMIENTO');
+        
+        if(!$sheet) return $equipments;
+        
+        $highestRow = $sheet->getHighestRow();
+        $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ']['worksheets']['EQUIPAMIENTO'];
+        $startRow = $config['date_start_row'];
+        
+        for($row = $startRow; $row <= $highestRow; $row++) {
+            $equipment = $this->extractRowData($sheet, $row, $config['field_mapping'], 'EQUIPAMIENTO');
+            
+            if(!$this->isEmptyRow($equipment)) {
+                $equipments[] = $equipment;
+            }
+        }
+        
+        return $equipments;
+    }
+
+// Obtener todos los equipos de LRU
+  private function getAllLruEquipments($spreadsheet) {
+        $equipments = [];
+        $sheet = $spreadsheet->getSheetByName('LRU');
+        
+        if(!$sheet) return $equipments;
+        
+        $highestRow = $sheet->getHighestRow();
+        $config = ExcelTemplateConfig::getTemplateConfigs()['TPCEQ']['worksheets']['LRU'];
+        $startRow = $config['date_start_row'];
+        
+        for($row = $startRow; $row <= $highestRow; $row++) {
+            $equipment = $this->extractRowData($sheet, $row, $config['field_mapping'], 'LRU');
+            
+            if(!$this->isEmptyRow($equipment)) {
+                $equipments[] = $equipment;
+            }
+        }
+        
+        return $equipments;
+    }
+
+// Buscar equipo por tipo y modelo
+private function findEquipment($equipments, $tipo, $modelo) {
+    $normalize = function($value) {
+        return trim(strtoupper($value));
+    };
+    
+    $tipoNorm = $normalize($tipo);
+    $modeloNorm = $normalize($modelo);
+    
+    foreach($equipments as $equipment) {
+        $eqTipo = $normalize($equipment['PCEQ_TIEQ'] ?? '');
+        $eqModelo = $normalize($equipment['PCEQ_MOEQ'] ?? '');
+        
+        // Buscar coincidencia exacta en tipo y modelo
+        if($eqTipo === $tipoNorm && $eqModelo === $modeloNorm) {
+            return $equipment;
+        }
+    }
+    
+    return null;
+}
+
+
+private function resolveEquipmentNames(&$rowData, $sheetName, $cargaMap, $lruMap) {
+    if (!in_array($sheetName, ['CASO 1','CASO 2','CASO 3','CASO 4','CASO 5','CASO 6'])) {
+        return;
+    }
+
+    // Resolver nombres completos y campos adicionales para equipo principal
+    $acronimoTipo = $rowData['PCEQ_TIEQ'] ?? '';
+    $acronimoModelo = $rowData['PCEQ_MOEQ'] ?? '';
+    
+    if ($acronimoTipo && $acronimoModelo) {
+        // Obtener nombre completo desde los mapas
+        $tipoCompleto = $cargaMap['equipos'][$acronimoTipo][$acronimoModelo] ?? $acronimoTipo;
+        $modeloCompleto = $cargaMap['modelos'][$acronimoModelo] ?? $acronimoModelo;
+        
+        // Preservar ambos valores
+        $rowData['PCEQ_TIEQ_COMPLETO'] = $tipoCompleto;  // Nombre completo para BD
+        $rowData['PCEQ_MOEQ_COMPLETO'] = $modeloCompleto; // Nombre completo para BD
+        $rowData['PCEQ_TIEQ_ACRONIMO'] = $acronimoTipo;  // Acrónimo para generación de código
+        $rowData['PCEQ_MOEQ_ACRONIMO'] = $acronimoModelo; // Acrónimo para generación de código
+        
+        // **NUEVO: Obtener PCEQ_CARA y PCEQ_COBA desde EQUIPAMIENTO**
+        $equipmentDetails = $this->findEquipmentDetailsByCodes($cargaMap, $acronimoTipo, $acronimoModelo);
+        if ($equipmentDetails) {
+            $rowData['PCEQ_CARA'] = $equipmentDetails['PCEQ_CARA'] ?? '';
+            $rowData['PCEQ_COBA'] = $equipmentDetails['PCEQ_COBA'] ?? '';
+            $rowData['PCEQ_NUSE'] = $equipmentDetails['PCEQ_NUSE'] ?? '';
+            $rowData['PCEQ_FVAR'] = $equipmentDetails['PCEQ_FVAR'] ?? '';
+            $rowData['PCEQ_OTCO'] = $equipmentDetails['PCEQ_OTCO'] ?? '';
+            
+        } else {
+            error_log("[Equipment Details] No se encontraron detalles para Padre - {$acronimoTipo}/{$acronimoModelo}");
+        }
+    }
+
+    // Resolver nombres completos y campos adicionales para equipo hijo
+    $acronimoTipoHijo = $rowData['PCEQ_TIEQ_HIJO'] ?? '';
+    $acronimoModeloHijo = $rowData['PCEQ_MOEQ_HIJO'] ?? '';
+    
+    if ($acronimoTipoHijo && $acronimoModeloHijo) {
+        $tipoHijoCompleto = $lruMap['equipos'][$acronimoTipoHijo][$acronimoModeloHijo] ?? $acronimoTipoHijo;
+        $modeloHijoCompleto = $lruMap['modelos'][$acronimoModeloHijo] ?? $acronimoModeloHijo;
+        
+        $rowData['PCEQ_TIEQ_HIJO_COMPLETO'] = $tipoHijoCompleto;
+        $rowData['PCEQ_MOEQ_HIJO_COMPLETO'] = $modeloHijoCompleto;
+        $rowData['PCEQ_TIEQ_HIJO_ACRONIMO'] = $acronimoTipoHijo;
+        $rowData['PCEQ_MOEQ_HIJO_ACRONIMO'] = $acronimoModeloHijo;
+        
+        // **NUEVO: Obtener PCEQ_CARA y PCEQ_COBA desde LRU para equipo hijo**
+        $equipmentDetailsHijo = $this->findEquipmentDetailsByCodes($lruMap, $acronimoTipoHijo, $acronimoModeloHijo);
+        if ($equipmentDetailsHijo) {
+            // Para equipos hijo, usar sufijo _HIJO para evitar sobreescribir los del padre
+            $rowData['PCEQ_CARA_HIJO'] = $equipmentDetailsHijo['PCEQ_CARA'] ?? '';
+            $rowData['PCEQ_COBA_HIJO'] = $equipmentDetailsHijo['PCEQ_COBA'] ?? '';
+            $rowData['PCEQ_NUSE_HIJO'] = $equipmentDetailsHijo['PCEQ_NUSE'] ?? '';
+            $rowData['PCEQ_FVAR_HIJO'] = $equipmentDetailsHijo['PCEQ_FVAR'] ?? '';
+            $rowData['PCEQ_OTCO_HIJO'] = $equipmentDetailsHijo['PCEQ_OTCO'] ?? '';
+            
+            error_log("  [Equipment Details] Hijo - {$acronimoTipoHijo}/{$acronimoModeloHijo}: CARA='{$equipmentDetailsHijo['PCEQ_CARA']}', COBA='{$equipmentDetailsHijo['PCEQ_COBA']}'");
+        } else {
+            error_log("[Equipment Details] No se encontraron detalles para Hijo - {$acronimoTipoHijo}/{$acronimoModeloHijo}");
+        }
+    }
+}
+
+/**
+ * Nueva función para encontrar detalles del equipo basado en códigos
+ */
+private function findEquipmentDetailsByCodes($equipmentMap, $acronimoTipo, $acronimoModelo) {
+    // Buscar en los datos crudos que se extrajeron originalmente
+    if (isset($equipmentMap['details'][$acronimoTipo][$acronimoModelo])) {
+        return $equipmentMap['details'][$acronimoTipo][$acronimoModelo];
+    }
+    
+    return null;
+}
+
+
+// // MÉTODO ALTERNATIVO: Limpiar datos duplicados antes del procesamiento
+// public function cleanDuplicateRecords(Request $request) {
+//     $validator = Validator::make($request->all(), [
+//         'id_user' => 'required|string',
+//         'linea' => 'required|integer',
+//     ]);
+
+//     if($validator->fails()) {
+//         return $this->responseController->makeResponse(
+//             true,
+//             "Se encontraron uno o más errores.",
+//             $this->responseController->makeErrors($validator->errors()->messages()),
+//             401
+//         );
+//     }
+
+//     $form = $request->all();
+//     $idUser = $this->encryptionController->decrypt($form['id_user']);
+    
+//     if(!$idUser) {
+//         return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
+//     }
+
+//     try {
+//         DB::beginTransaction();
+        
+//         // Eliminar registros duplicados manteniendo solo el más reciente
+//         $duplicatesDeleted = DB::statement("
+//             DELETE p1 FROM S002V01TPCEQ p1
+//             INNER JOIN S002V01TPCEQ p2 
+//             WHERE p1.PCEQ_NULI = p2.PCEQ_NULI 
+//             AND p1.PCEQ_TICO = p2.PCEQ_TICO
+//             AND p1.PCEQ_IDPR = p2.PCEQ_IDPR
+//             AND p1.PCEQ_FERE < p2.PCEQ_FERE
+//             AND p1.PCEQ_NULI = ?
+//         ", [$form['linea']]);
+        
+//         DB::commit();
+        
+//         return $this->responseController->makeResponse(false, "Duplicados eliminados exitosamente", [
+//             'linea' => $form['linea'],
+//             'duplicados_eliminados' => $duplicatesDeleted
+//         ]);
+        
+//     } catch(Exception $e) {
+//         DB::rollBack();
+//         return $this->responseController->makeResponse(true, "Error al limpiar duplicados: " . $e->getMessage(), [], 500);
+//     }
+// }
+
+    private function getCodeTypeFromSheet($sheetName) {
+        $codeTypes = [
+            'CASO 1' => '1',
+            'CASO 2' => '2', 
+            'CASO 3' => '3',
+            'CASO 4' => '4',
+            'CASO 5' => '5',
+            'CASO 6' => '6',
+
+        ];
+        
+        return $codeTypes[$sheetName] ?? '1';
+    }
+
+    private function addSheetSpecificFields(&$equipmentData, $rowData, $sheetName) {
+        switch($sheetName) {
+            case 'CASO 1':
+            case 'CASO 2':
+                $equipmentData['PCEQ_UBOR'] = $rowData['PCEQ_UBOR'] ?? '';
+                $equipmentData['PCEQ_NIOR'] = $rowData['PCEQ_NIOR'] ?? '';
+                $equipmentData['PCEQ_OCOR'] = $rowData['PCEQ_OCOR'] ?? '';
+                $equipmentData['PCEQ_ELOR'] = $rowData['PCEQ_ELOR'] ?? '';
+                $equipmentData['PCEQ_COOR'] = $rowData['PCEQ_COOR'] ?? '';
+                $equipmentData['PCEQ_FAMI'] = $rowData['PCEQ_FAMI'] ?? '';
+                $equipmentData['PCEQ_SUBF'] = $rowData['PCEQ_SUBF'] ?? '';
+                break;
+                
+            case 'CASO 3':
+                $equipmentData['PCEQ_UBOR'] = $rowData['PCEQ_UBOR'] ?? '';
+                $equipmentData['PCEQ_UBDE'] = $rowData['PCEQ_UBDE'] ?? '';
+                $equipmentData['PCEQ_KIOR'] = $rowData['PCEQ_KIOR'] ?? '';  // PK Origen
+                $equipmentData['PCEQ_KIDE'] = $rowData['PCEQ_KIDE'] ?? '';
+                break;
+                
+            case 'CASO 4':
+                $equipmentData['PCEQ_UBOR'] = $rowData['PCEQ_UBOR'] ?? '';
+                $equipmentData['PCEQ_UBDE'] = $rowData['PCEQ_UBDE'] ?? '';
+                $equipmentData['PCEQ_SEOR'] = $rowData['PCEQ_SEOR'] ?? 0;
+                $equipmentData['PCEQ_SEDE'] = $rowData['PCEQ_SEDE'] ?? 0;
+                $equipmentData['PCEQ_COOR'] = $rowData['PCEQ_COOR_ORIGEN'] ?? '';
+                $equipmentData['PCEQ_CODE'] = $rowData['PCEQ_COOR_DESTINO'] ?? '';
+                break;
+                
+            case 'CASO 5':
+            case 'CASO 6':
+                $equipmentData['PCEQ_UBOR'] = $rowData['PCEQ_UBOR'] ?? '';
+                $equipmentData['PCEQ_NIOR'] = $rowData['PCEQ_NIOR'] ?? '';
+                $equipmentData['PCEQ_OCOR'] = $rowData['PCEQ_OCOR'] ?? '';
+                $equipmentData['PCEQ_ARTR'] = $rowData['PCEQ_ARTR'] ?? '';
+                $equipmentData['PCEQ_ELOR'] = $rowData['PCEQ_ELOR'] ?? '';
+                break;
+        }
+    }
+
+    // Método para obtener equipos en revisión
+    public function getPendingEquipments(Request $request) {
+        $validator = Validator::make($request->all(), [
+            'id_user' => 'required|string',
+            'linea' => 'required|integer',
+        ]);
+
+        if($validator->fails()) {
+            return $this->responseController->makeResponse(
+                true,
+                "Se encontraron uno o más errores.",
+                $this->responseController->makeErrors($validator->errors()->messages()),
+                401
+            );
+        }
+
+        $form = $request->all();
+        $idUser = $this->encryptionController->decrypt($form['id_user']);
+        
+        if(!$idUser) {
+            return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
+        }
+
+        $pendingEquipments = DB::table('S002V01TPCEQ')
+            ->where('PCEQ_NULI', $form['linea'])
+            ->where('PCEQ_ESRE', 'Revisión')
+            ->orderBy('PCEQ_FERE', 'desc')
+            ->get();
+
+        $equipmentsArray = [];
+        foreach($pendingEquipments as $equipment) {
+            $equipmentsArray[] = [
+                'id' => $this->encryptionController->encrypt($equipment->PCEQ_IDPR),
+                'codigo' => $equipment->PCEQ_CPGE,
+                'tipo' => $equipment->PCEQ_TIEQ,
+                'modelo' => $equipment->PCEQ_MOEQ,
+                'familia' => $equipment->PCEQ_FAMI,
+                'subfamilia' => $equipment->PCEQ_SUBF,
+                'estado' => $equipment->PCEQ_ESEQ,
+                'fecha_registro' => $equipment->PCEQ_FERE,
+                'estado_revision' => $equipment->PCEQ_ESRE
+            ];
+        }
+
+        return $this->responseController->makeResponse(false, 'EXITO.', $equipmentsArray);
+    }
+
+
+
+
+
+
+
+//Métodos para mover a final ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+
+public function transferEquipmentsToFinal($linea, $idUser) {
+    try {
+        DB::beginTransaction();
+        
+        // Obtener equipos en estado "Revisión" desde la tabla temporal
+        $tempEquipments = DB::table('S002V01TPCEQ')
+            ->where('PCEQ_NULI', $linea)
+            ->where('PCEQ_ESRE', 'Revisión')
+            ->get();
+            
+        if ($tempEquipments->isEmpty()) {
+            return [
+                'success' => false,
+                'message' => 'No se encontraron equipos para transferir',
+                'transferred_count' => 0
+            ];
+        }
+        
+        $transferredCount = 0;
+        $errors = [];
+        
+        foreach ($tempEquipments as $tempEquip) {
+            try {
+                // Generar nuevo EQUI_IDEQ secuencial
+                $newEquiId = $this->generateNewEquiId($linea);
+                
+                // Preparar datos para la tabla final
+                $finalEquipmentData = [
+                    'EQUI_NULI' => $tempEquip->PCEQ_NULI,
+                    'EQUI_FAMI' => $tempEquip->PCEQ_FAMI,
+                    'EQUI_SUBF' => $tempEquip->PCEQ_SUBF,
+                    'EQUI_TIPO' => $tempEquip->PCEQ_TIEQ,
+                    'EQUI_MODE' => $tempEquip->PCEQ_MOEQ,
+                    'EQUI_IDEQ' => $newEquiId,
+                    'EQUI_ESEQ' => $tempEquip->PCEQ_ESEQ,
+                    'EQUI_COEQ' => $tempEquip->PCEQ_CPGE, // Era el problema principal del CASO 3
+                    
+                    'EQUI_JERA' => $tempEquip->PCEQ_JERA,
+                    'EQUI_EQPA' => $tempEquip->PCEQ_EQPA,
+                    'EQUI_TICO' => $tempEquip->PCEQ_TICO,
+                    
+                    // Campos de ubicación origen
+                    'EQUI_UBOR' => $tempEquip->PCEQ_UBOR,
+                    'EQUI_NIOR' => $tempEquip->PCEQ_NIOR,
+                    'EQUI_OCOR' => $tempEquip->PCEQ_OCOR,
+                    'EQUI_ELOR' => $tempEquip->PCEQ_ELOR,
+                    'EQUI_COOR' => $tempEquip->PCEQ_COOR,
+                    'EQUI_PCOR' => $tempEquip->PCEQ_PCOR ?? null,
+                    
+                    // Campos de almacén (CASO 7) - verificar si existen
+                    'EQUI_ALMA' => $tempEquip->PCEQ_ALMA ?? null,
+                    'EQUI_AREA' => $tempEquip->PCEQ_AREA ?? null,
+                    'EQUI_NIVE' => $tempEquip->PCEQ_NIVE ?? null,
+                    'EQUI_ZONA' => $tempEquip->PCEQ_ZONA ?? null,
+                    
+                    // Campos PCC - verificar si existen
+                    'EQUI_IPCC' => $tempEquip->PCEQ_IPCC ?? null,
+                    'EQUI_PPCC' => $tempEquip->PCEQ_PPCC ?? null,
+                    
+                    'EQUI_KIOR' => $this->parseVarcharValue($tempEquip->PCEQ_KIOR),
+                    'EQUI_SEOR' => $tempEquip->PCEQ_SEOR ?? null,
+                    'EQUI_ARTR' => $tempEquip->PCEQ_ARTR ?? null,
+                    'EQUI_TCOR' => $tempEquip->PCEQ_TCOR ?? null,
+                    'EQUI_CEOR' => $tempEquip->PCEQ_CEOR ?? null,
+                    
+                    // Campos de ubicación destino
+                    'EQUI_UBDE' => $tempEquip->PCEQ_UBDE ?? null,
+                    'EQUI_NIDE' => $tempEquip->PCEQ_NIDE ?? null,
+                    'EQUI_OCDE' => $tempEquip->PCEQ_OCDE ?? null,
+                    'EQUI_ELDE' => $tempEquip->PCEQ_ELDE ?? null,
+                    'EQUI_CODE' => $tempEquip->PCEQ_CODE ?? null,
+                    'EQUI_PCDE' => $tempEquip->PCEQ_PCDE ?? null,
+                    
+                    'EQUI_KIDE' => $this->parseVarcharValue($tempEquip->PCEQ_KIDE),
+                    'EQUI_SEDE' => $tempEquip->PCEQ_SEDE ?? null,
+                    'EQUI_TCDE' => $tempEquip->PCEQ_TCDE ?? null,
+                    'EQUI_CEDE' => $tempEquip->PCEQ_CEDE ?? null,
+                    
+                    // Campos de fechas y proveedor
+                    'EQUI_CRSE' => $tempEquip->PCEQ_CRSE ?? null,
+                    'EQUI_FEAD' => $tempEquip->PCEQ_FEAD,
+                    'EQUI_FIGA' => $tempEquip->PCEQ_FIGA,
+                    'EQUI_FTGA' => $tempEquip->PCEQ_FTGA,
+                    'EQUI_PREQ' => $tempEquip->PCEQ_PREQ ?? null,
+                    'EQUI_NUSE' => $tempEquip->PCEQ_NUSE ?? null,
+                    
+                    // 'EQUI_COBA' => $tempEquip->PCEQ_COBA ?? null, // Código de barras
+                    // 'EQUI_CARA' => $tempEquip->PCEQ_CARA ?? null, // Carácter
+                    // 'EQUI_FVAR' => $tempEquip->PCEQ_FVAR ?? null, // Fecha vencimiento
+                    
+                    // Campos de software
+                    'EQUI_DESO' => $tempEquip->PCEQ_DESO ?? null,
+                    'EQUI_VESO' => $tempEquip->PCEQ_VESO ?? null,
+                    
+                    // Campos JSON
+                    'EQUI_HICO' => json_encode([]), // Historial de códigos (nuevo campo)
+                    'EQUI_GAIM' => $tempEquip->PCEQ_GAIM ?? json_encode([]),
+                    'EQUI_DORE' => $tempEquip->PCEQ_DORE ?? json_encode([]),
+                    'EQUI_OTCO' => $tempEquip->PCEQ_OTCO ?? json_encode([]),
+                    
+                    // Estados y usuarios
+                    'EQUI_ESFU' => 'EF', // Estado funcionamiento por defecto
+                    'EQUI_USRE' => $tempEquip->PCEQ_USRE,
+                    'EQUI_FERE' => $tempEquip->PCEQ_FERE,
+                    'EQUI_USMO' => null,
+                    'EQUI_FEMO' => null
+                ];
+                
+                // Debug para verificar campos críticos
+                error_log(" [Transfer Debug] PCEQ_IDPR: {$tempEquip->PCEQ_IDPR}");
+                error_log("   - PCEQ_CPGE: '{$tempEquip->PCEQ_CPGE}' -> EQUI_COEQ");
+                error_log("   - PCEQ_KIOR: '{$tempEquip->PCEQ_KIOR}' -> EQUI_KIOR: " . ($finalEquipmentData['EQUI_KIOR'] ?? 'NULL'));
+                error_log("   - PCEQ_KIDE: '{$tempEquip->PCEQ_KIDE}' -> EQUI_KIDE: " . ($finalEquipmentData['EQUI_KIDE'] ?? 'NULL'));
+                
+                // Insertar en tabla final
+                DB::table('S002V01TEQUI')->insert($finalEquipmentData);
+                
+                // Marcar como aprobado en tabla temporal
+                DB::table('S002V01TPCEQ')
+                    ->where('PCEQ_IDPR', $tempEquip->PCEQ_IDPR)
+                    ->where('PCEQ_NULI', $linea)
+                    ->update([
+                        'PCEQ_ESRE' => 'Aprobado',
+                        'PCEQ_USMO' => $idUser,
+                        'PCEQ_FEMO' => now()->format('Y-m-d H:i:s')
+                    ]);
+                
+                $transferredCount++;
+                
+                error_log("[Transfer] Equipo transferido: PCEQ_IDPR={$tempEquip->PCEQ_IDPR} -> EQUI_IDEQ={$newEquiId}");
+                
+            } catch (Exception $e) {
+                $errors[] = "Error transfiriendo equipo PCEQ_IDPR {$tempEquip->PCEQ_IDPR}: " . $e->getMessage();
+                error_log(" [Transfer Error] PCEQ_IDPR={$tempEquip->PCEQ_IDPR}: " . $e->getMessage());
+                continue;
+            }
+        }
+        
+        DB::commit();
+        
+        return [
+            'success' => true,
+            'message' => "Equipos transferidos exitosamente",
+            'transferred_count' => $transferredCount,
+            'total_temp_equipments' => count($tempEquipments),
+            'errors' => $errors
+        ];
+        
+    } catch (Exception $e) {
+        DB::rollBack();
+        error_log(" [Transfer Error] Error general: " . $e->getMessage());
+        
+        return [
+            'success' => false,
+            'message' => 'Error al transferir equipos: ' . $e->getMessage(),
+            'transferred_count' => 0
+        ];
+    }
+}
+
+private function parseVarcharValue($value) {
+    if ($value === null || $value === '') {
+        return null;
+    }
+    
+    return trim((string)$value);
+}
+
+
+private function parseNumericValue($value) {
+    if ($value === null || $value === '') {
+        return null;
+    }
+    
+    // Si es string que representa un número
+    if (is_string($value) && is_numeric($value)) {
+        return (float)$value;
+    }
+    
+    // Si ya es numérico
+    if (is_numeric($value)) {
+        return (float)$value;
+    }
+    
+    return null;
+}
+
+
+private function generateNewEquiId($linea) {
+    $maxId = DB::table('S002V01TEQUI')
+        ->where('EQUI_NULI', $linea)
+        ->max('EQUI_IDEQ');
+    
+    return $maxId ? ($maxId + 1) : 1;
+}
+
+
+
+public function transferEquipmentsToFinalTable(Request $request) {
+    $validator = Validator::make($request->all(), [
+        'id_user' => 'required|string',
+        'linea' => 'required|integer',
+    ]);
+
+    if($validator->fails()) {
+        return $this->responseController->makeResponse(
+            true,
+            "Se encontraron uno o más errores.",
+            $this->responseController->makeErrors($validator->errors()->messages()),
+            401
+        );
+    }
+
+    $form = $request->all();
+    $idUser = $this->encryptionController->decrypt($form['id_user']);
+    
+    if(!$idUser) {
+        return $this->responseController->makeResponse(true, "El id del usuario no fue desencriptado correctamente", [], 400);
+    }
+
+    // Verificar que el usuario existe
+    $usr = DB::table('S002V01TUSUA')->where([
+        ['USUA_IDUS', '=', $idUser],
+        ['USUA_NULI', '=', $form['linea']]
+    ])->first();
+
+    if(is_null($usr)) {
+        return $this->responseController->makeResponse(true, 'El usuario no está registrado', [], 404);
+    }
+
+    try {
+        $transferResult = $this->transferEquipmentsToFinal($form['linea'], $idUser);
+        
+        if (!$transferResult['success']) {
+            return $this->responseController->makeResponse(true, $transferResult['message'], [], 500);
+        }
+
+        // Registrar actividad
+        $nowStr = Carbon::now('America/Mexico_city')->toDateTimeString();
+        $name = $this->functionsController->joinName($usr->USUA_NOMB, $usr->USUA_APPA, $usr->USUA_APMA);
+        
+        $this->functionsController->registerActivity(
+            $form['linea'],
+            'S002V01M07GEEQ',
+            'S002V01F01ADEQ',
+            'S002V01P11REEQ',
+            'Registro',
+            "El usuario $name (" . $usr->USUA_IDUS . ") transfirió {$transferResult['transferred_count']} equipos desde la tabla temporal a la tabla final.",
+            $idUser,
+            $nowStr
+        );
+
+        return $this->responseController->makeResponse(false, "Equipos transferidos exitosamente", [
+            'equipos_transferidos' => $transferResult['transferred_count'],
+            'total_equipos_temporales' => $transferResult['total_temp_equipments'],
+            'errores' => $transferResult['errors'] ?? []
+        ]);
+        
+    } catch (Exception $e) {
+        error_log(" [Manual Transfer Error] " . $e->getMessage());
+        return $this->responseController->makeResponse(true, "Error al transferir equipos: " . $e->getMessage(), [], 500);
+    }
+}
+
+
+
+   
+}
+
+class ExcelTemplateConfig {
+
+    public static function getTemplateConfigs() {
+        return [
+            'TPCEQ' => [
+                'model' => 'S002V01TPCEQ',
+                'worksheets' => [
+                    'EQUIPAMIENTO' => [
+                        'table_start' => 'B9',
+                        'table_end' => 'P9',
+                        'header_row' => 7,
+                        'date_start_row' => 9,
+                        'field_mapping' => [
+                            'A' => '',
+                            'B' => 'PCEQ_OTCO', // Código Equivalente
+                            'C' => 'PCEQ_TIEQ', // Tipo / Descripción
+                            'D' => '', // Acrónimo del equipo
+                            'E' => 'PCEQ_MOEQ', // Acrónimo del modelo
+                            'F' => '', // Acrónimo del modelo
+                            'G' => '', // Id
+                            'H' => 'PCEQ_NUSE', // No. serie 
+                            'I' => 'PCEQ_COBA', // No. código de barras
+                            'J' => 'PCEQ_CARA', // Carácter 
+                            'K' => 'PCEQ_FVAR', // Fecha inicio de garantía
+                            'L' => '', // Fecha de vencimiento del artículo
+                        ]
+                    ],
+                    'LRU' => [
+                        'table_start' => 'B9',
+                        'table_end' => 'L9',
+                        'header_row' => 7,
+                        'date_start_row' => 9,
+                        'field_mapping' => [
+                            'A' => '',
+                            'B' => 'PCEQ_OTCO', // Tipo / Descripción
+                            'C' => 'PCEQ_TIEQ', // Acrónimo del equipo
+                            'D' => '', // Modelo completo
+                            'E' => 'PCEQ_MOEQ', // Acrónimo del modelo
+                            'F' => '', // Id
+                            'G' => '', // (ID) - no se usa
+                            'H' => 'PCEQ_NUSE', // No. serie
+                            'I' => 'PCEQ_COBA', // No. código de barras
+                            'J' => 'PCEQ_CARA', // Carácter
+                            'K' => 'PCEQ_FTGA', // Fecha de vencimiento del artículo
+                            'L' => '', // Etiqueta final del equipo
+                        ]
+                    ],
+                    'CASO 1' => [
+                        'table_start' => 'B8',
+                        'table_end' => 'AI8',
+                        'header_row' => 7,
+                        'date_start_row' => 8,
+                        'field_mapping' => [
+                            'A' => '',
+                            'B' => 'PCEQ_NULI', // Línea
+                            'C' => '.', // .
+                            'D' => 'PCEQ_UBOR', // Ubicación
+                            'E' => '.', // .
+                            'F' => 'PCEQ_NIOR', // Nivel
+                            'G' => '.', // .
+                            'H' => 'PCEQ_OCOR', // Ocupación
+                            'I' => '.', // .
+                            'J' => 'PCEQ_ELOR', // Elemento
+                            'K' => '+', // +
+                            'L' => '', // Coordenadas plano
+                            'M' => '', // Coordenadas detalle
+                            'N' => '', // Coordenadas de posición
+                            'O' => '_', // _
+                            'P' => 'PCEQ_FAMI', // Familia
+                            'Q' => '.', // .
+                            'R' => 'PCEQ_SUBF', // Subfamilia
+                            'S' => '.', // .
+                            'T' => 'PCEQ_ESEQ', // Estado
+                            'U' => '.', // .
+                            'V' => 'PCEQ_TIEQ', // Tipo
+                            'W' => '-', // -
+                            'X' => 'PCEQ_MOEQ', // Modelo
+                            'Y' => '-', // -
+                            'Z' => '', // ID
+                            'AA' => '.', // .
+                            'AB' => 'PCEQ_TIEQ_HIJO', // Tipo
+                            'AC' => '-', // -
+                            'AD' => 'PCEQ_MOEQ_HIJO', // Modelo
+                            'AE' => '-', // -
+                            'AF' => '', // ID
+                            'AG' => '', // Vacío
+                            'AH' => 'PCEQ_CPGE', // Código completo SAM
+                            'AI' => 'PCEQ_OTCO', // Código equivalente
+                        ]
+                    ],
+                    'CASO 2' => [
+                        'table_start' => 'B8',
+                        'table_end' => 'AI8',
+                        'header_row' => 7,
+                        'date_start_row' => 8,
+                        'field_mapping' => [
+                            'A' => '',
+                            'B' => 'PCEQ_NULI', // Línea
+                            'C' => '.', // .
+                            'D' => 'PCEQ_UBOR', // Ubicación
+                            'E' => '.', // .
+                            'F' => 'PCEQ_NIOR', // Nivel
+                            'G' => '.', // .
+                            'H' => 'PCEQ_OCOR', // Ocupación
+                            'I' => '.', // .
+                            'J' => 'PCEQ_ELOR', // Elemento
+                            'K' => '+', // +
+                            'L' => '', // Coordenadas plano
+                            'M' => '', // Coordenadas detalle
+                            'N' => '', // Coordenadas de posición
+                            'O' => '_', // _
+                            'P' => 'PCEQ_FAMI', // Familia
+                            'Q' => '.', // .
+                            'R' => 'PCEQ_SUBF', // Subfamilia
+                            'S' => '.', // .
+                            'T' => 'PCEQ_ESEQ', // Estado
+                            'U' => '.', // .
+                            'V' => 'PCEQ_TIEQ', // Tipo
+                            'W' => '-', // -
+                            'X' => 'PCEQ_MOEQ', // Modelo
+                            'Y' => '-', // -
+                            'Z' => '', // ID
+                            'AA' => '.', // .
+                            'AB' => 'PCEQ_TIEQ_HIJO', // Tipo
+                            'AC' => '-', // -
+                            'AD' => 'PCEQ_MOEQ_HIJO', // Modelo
+                            'AE' => '-', // -
+                            'AF' => '', // ID
+                            'AG' => '', // Vacío
+                            'AH' => 'PCEQ_CPGE', // Código completo SAM
+                            'AI' => 'PCEQ_OTCO', // Código equivalente
+                        ]
+                    ],
+                    'CASO 3' => [
+                        'table_start' => 'B8',
+                        'table_end' => 'AQ8',
+                        'header_row' => 7,
+                        'date_start_row' => 8,
+                        'field_mapping' => [
+                            'A' => '',
+                            'B' => 'PCEQ_NULI', // Línea
+                            'C' => '.', // .
+                            'D' => 'PCEQ_UBOR', // Ubicación origen
+                            'E' => '.', // .
+                            'F' => 'PCEQ_NIOR', // Nivel origen
+                            'G' => '.', // .
+                            'H' => 'PCEQ_OCOR', // Ocupación origen
+                            'I' => '.', // .
+                            'J' => 'PCEQ_ELOR', // Elemento origen
+                            'K' => '.', // .
+                            'L' => 'PCEQ_KIOR', // PK origen
+                            'M' => ':', // :
+                            'N' => 'PCEQ_UBDE', // Ubicación destino
+                            'O' => '.', // .
+                            'P' => 'PCEQ_NIDE', // Nivel destino
+                            'Q' => '.', // .
+                            'R' => 'PCEQ_OCDE', // Ocupación destino
+                            'S' => '.', // .
+                            'T' => 'PCEQ_ELDE', // Elemento destino
+                            'U' => '.', // .
+                            'V' => 'PCEQ_KIDE', // PK destino
+                            'W' => '-', // -
+                            'X' => 'PCEQ_FAMI', // Familia
+                            'Y' => '-', // -
+                            'Z' => 'PCEQ_SUBF', // Subfamilia
+                            'AA' => '.', // .
+                            'AB' => 'PCEQ_ESEQ', // Estado
+                            'AC' => '-', // -
+                            'AD' => 'PCEQ_TIEQ', // Tipo
+                            'AE' => '-', // -
+                            'AF' => 'PCEQ_MOEQ', // Modelo
+                            'AG' => '-', // -
+                            'AH' => '', // ID
+                            'AI' => '.', // .
+                            'AJ' => 'PCEQ_TIEQ_HIJO', // Tipo
+                            'AK' => '-', // -
+                            'AL' => 'PCEQ_MOEQ_HIJO', // Modelo
+                            'AM' => '-', // -
+                            'AN' => 'PCEQ_IDPR', // ID
+                            'AO' => '-', // -
+                            'AP' => 'PCEQ_CPGE', // Código completo
+                            'AQ' => 'PCEQ_OTCO', // Código equivalente
+                        ]
+                    ],
+                    'CASO 4' => [
+                        'table_start' => 'B8',
+                        'table_end' => 'AY8',
+                        'header_row' => 7,
+                        'date_start_row' => 8,
+                        'field_mapping' => [
+                            'A' => '',
+                            'B' => 'PCEQ_NULI', // Línea
+                            'C' => '.', // .
+                            'D' => 'PCEQ_UBOR', // Ubicación origen
+                            'E' => '.', // .
+                            'F' => 'PCEQ_NIOR', // Nivel origen
+                            'G' => '.', // .
+                            'H' => 'PCEQ_OCOR', // Ocupación origen
+                            'I' => '.', // .
+                            'J' => 'PCEQ_ELOR', // Elemento origen
+                            'K' => '.', // .
+                            'L' => 'PCEQ_SEOR', // Secuencial origen
+                            'M' => '.', // .
+                            'N' => '', // Coordenadas plano (concatenar con O,P)
+                            'O' => '', // Coordenadas detalle
+                            'P' => '', // Coordenadas de posición
+                            'Q' => ':', // :
+                            'R' => 'PCEQ_UBDE', // Ubicación destino
+                            'S' => '.', // .
+                            'T' => 'PCEQ_NIDE', // Nivel destino
+                            'U' => '.', // .
+                            'V' => 'PCEQ_OCDE', // Ocupación destino
+                            'W' => '.', // .
+                            'X' => 'PCEQ_ELDE', // Elemento destino
+                            'Y' => '.', // .
+                            'Z' => 'PCEQ_SEDE', // Secuencial destino
+                            'AA' => '+', // +
+                            'AB' => '', // Coordenadas plano (concatenar con AC,AD)
+                            'AC' => '', // Coordenadas detalle
+                            'AD' => '', // Coordenadas de posición
+                            'AE' => '_', // _
+                            'AF' => 'PCEQ_FAMI', // Familia
+                            'AG' => '.', // .
+                            'AH' => 'PCEQ_SUBF', // Subfamilia
+                            'AI' => '.', // .
+                            'AJ' => 'PCEQ_ESEQ', // Estado
+                            'AK' => '.', // .
+                            'AL' => 'PCEQ_TIEQ', // Tipo
+                            'AM' => '-', // -
+                            'AN' => 'PCEQ_MOEQ', // Modelo
+                            'AO' => '-', // -
+                            'AP' => '', // ID
+                            'AQ' => '.', // .
+                            'AR' => 'PCEQ_TIEQ_HIJO', // Tipo
+                            'AS' => '-', // -
+                            'AT' => 'PCEQ_MOEQ_HIJO', // Modelo
+                            'AU' => '-', // -
+                            'AV' => '', // ID
+                            'AW' => '', // Vacío
+                            'AX' => 'PCEQ_CPGE', // Código completo
+                            'AY' => 'PCEQ_OTCO', // Código equivalente
+                        ]
+                    ],
+                    'CASO 5' => [
+                        'table_start' => 'B8',
+                        'table_end' => 'AG8',
+                        'header_row' => 7,
+                        'date_start_row' => 8,
+                        'field_mapping' => [
+                            'A' => '',
+                            'B' => 'PCEQ_NULI', // Línea
+                            'C' => '.', // .
+                            'D' => 'PCEQ_UBOR', // Ubicación
+                            'E' => '.', // .
+                            'F' => 'PCEQ_NIOR', // Nivel
+                            'G' => '.', // .
+                            'H' => 'PCEQ_OCOR', // Ocupación
+                            'I' => '.', // .
+                            'J' => 'PCEQ_ARTR', // Área
+                            'K' => '.', // .
+                            'L' => 'PCEQ_ELOR', // Elemento
+                            'M' => '_', // _
+                            'N' => 'PCEQ_FAMI', // Familia
+                            'O' => '.', // .
+                            'P' => 'PCEQ_SUBF', // Subfamilia
+                            'Q' => '.', // .
+                            'R' => 'PCEQ_ESEQ', // Estado
+                            'S' => '.', // .
+                            'T' => 'PCEQ_TIEQ', // Tipo
+                            'U' => '-', // -
+                            'V' => 'PCEQ_MOEQ', // Modelo
+                            'W' => '-', // -
+                            'X' => '', // ID
+                            'Y' => '.', // .
+                            'Z' => 'PCEQ_TIEQ_HIJO', // Tipo LRU
+                            'AA' => '-', // -
+                            'AB' => 'PCEQ_MOEQ_HIJO', // Modelo LRU
+                            'AC' => '-', // -
+                            'AD' => '', // ID LRU
+                            'AE' => '', // Vacío
+                            'AF' => 'PCEQ_CPGE', // Código completo
+                            'AG' => 'PCEQ_OTCO', // Código equivalente
+                        ]
+                    ],
+                    'CASO 6' => [
+                        'table_start' => '',
+                        'table_end' => '',
+                        'header_row' => 7,
+                        'date_start_row' => 8,
+                        'field_mapping' => [
+                            'A' => '', // Vacío 
+                            'B' => 'PCEQ_NULI', // Linea
+                            'C' => '.', // .
+                            'D' => 'PCEQ_UBOR', // Ubicación
+                            'E' => '.', // .
+                            'F' => 'PCEQ_NIOR', // Nivel
+                            'G' => '.', // .  
+                            'H' => 'PCEQ_OCOR', // Ocupación
+                            'I' => '.', // .
+                            'J' => 'PCEQ_ELOR', // Elemento
+                            'K' => '.', // .
+                            'L' => 'PCEQ_COOR', // Posición
+                            'M' => '_', // _
+                            'N' => 'PCEQ_FAMI', // Familia
+                            'O' => '.', // .
+                            'P' => 'PCEQ_SUBF', // Subfamilia
+                            'Q' => '.', // .
+                            'R' => 'PCEQ_ESEQ', // Estado
+                            'S' => '.', // .
+                            'T' => 'PCEQ_TIEQ', // Tipo
+                            'U' => '-', // -
+                            'V' => 'PCEQ_MOEQ', // Modelo
+                            'W' => '-', // -
+                            'X' => 'PCEQ_IDPR', // ID
+                            'Y' => '.', // .
+                            'Z' => 'PCEQ_TIEQ_HIJO', // Tipo
+                            'AA' => '-', // -
+                            'AB' => 'PCEQ_MOEQ_HIJO', // Modelo
+                            'AC' => '-', // -
+                            'AD' => 'PCEQ_LRID', // ID
+                            'AE' => '', // Vacío
+                            'AF' => 'PCEQ_CPGE', // Código completo
+                            'AG' => 'PCEQ_OTCO', // Código equivalente
+                        ]
+                    ]
+                ]
+            ],
+        ];
+    }
+
+
+};
+
+

+ 216 - 0
sistema-mantenimiento-back/app/Jobs/ValidateLoadArchives.php

@@ -0,0 +1,216 @@
+<?php
+
+namespace App\Jobs;
+
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Queue\Queueable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use App\Services\WebSocketService;
+use App\Http\Controllers\DocumentManagementController;
+use App\Http\Controllers\EncryptionController;
+use Illuminate\Http\Request;
+use Illuminate\Http\UploadedFile;
+use Illuminate\Support\Facades\DB;
+use ZipArchive;
+
+class ValidateLoadArchives implements ShouldQueue
+{
+    use Queueable, InteractsWithQueue, SerializesModels;
+
+    public $timeout = 300;
+    public $tries = 1;
+    
+    protected $requestData;
+    protected $userId;
+    protected $jobId;
+
+    public function __construct($requestData, $userId, $jobId)
+    {
+        $this->requestData = $requestData;
+        $this->userId = $userId;
+        $this->jobId = $jobId;
+    }
+
+    public function handle(): void
+    {
+        try {
+            // Etapa 1: Inicio (0%)
+            $this->broadcastProgress(0, 'processing', 'Iniciando validación de archivos...');
+            
+            // Procesar archivos
+            $result = $this->processFiles();
+            
+            // Etapa final: Completado (100%)
+            $this->broadcastProgress(100, 'completed', 'Archivos subidos exitosamente', $result);
+            
+        } catch (\Exception $e) {
+            $this->broadcastProgress(0, 'failed', 'Error en la validación: ' . $e->getMessage());
+        }
+    }
+
+    private function processFiles()
+    {        
+        if (!isset($this->requestData['temp_files'])) {
+            throw new \Exception('temp_files not found in request data');
+        }
+        
+        $tempFiles = $this->requestData['temp_files'];
+        
+        if (!isset($tempFiles['zip'])) {
+            throw new \Exception('ZIP file not found in temp_files');
+        }
+        
+        $this->broadcastProgress(50, 'processing', 'Extrayendo y subiendo archivos individuales...');
+        
+        $individualFiles = $this->extractAndUploadIndividualFiles($tempFiles['zip']);
+        
+        return [
+            'temp_files' => $tempFiles,
+            'individual_temp_files' => $individualFiles
+        ];
+    }
+
+    private function broadcastProgress($progress, $status, $message, $data = null)
+    {
+        $progressData = [
+            'progress' => $progress,
+            'status' => $status,
+            'message' => $message,
+            'data' => $data,
+            'jobId' => $this->jobId
+        ];
+        
+        $webSocketService = new WebSocketService();
+        $webSocketService->emitToUser($this->userId, 'file_validation_progress', $progressData);
+    }
+    
+
+    private function extractAndUploadIndividualFiles($zipTempId)
+    {
+        try {
+            // Get ZIP file from temp storage using DocumentManagementController
+            $encryptionController = new EncryptionController();
+            $tempIdDec = $encryptionController->decrypt($zipTempId);
+            
+            if (!$tempIdDec) {
+                return [];
+            }
+            
+            $tempFile = DB::table('S002V01TARTE')->where('ARTE_IDAR', $tempIdDec)->first();
+            if (!$tempFile) {
+                return [];
+            }
+            
+            $zipPath = $tempFile->ARTE_UBTE;
+            
+            if (!file_exists($zipPath)) {
+                return [];
+            }
+            $zip = new ZipArchive();
+            
+            if ($zip->open($zipPath) !== TRUE) {
+                return [];
+            }
+            
+            $tempFiles = [];
+            $tempDir = storage_path('app/temp_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;
+                
+                $fileContent = $zip->getFromIndex($i);
+                if ($fileContent !== false) {
+                    $extractPath = $tempDir . '/' . $fileName;
+                    
+                    if (file_put_contents($extractPath, $fileContent)) {
+                        $tempFileId = $this->uploadExtractedFileToTemp($extractPath, $fileName);
+                        if ($tempFileId) {
+                            $tempFiles[] = [
+                                'original_name' => $fileName,
+                                'temp_id' => $tempFileId
+                            ];
+                        }
+                        unlink($extractPath);
+                    }
+                }
+            }
+            
+            $zip->close();
+            $this->removeDirectory($tempDir);
+            
+            return $tempFiles;
+        } catch (\Exception $e) {
+            return [];
+        }
+    }
+    
+    private function uploadExtractedFileToTemp($filePath, $fileName)
+    {
+        try {
+            $extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
+            $mimeType = $this->getMimeType($extension);
+            
+            $uploadedFile = new UploadedFile(
+                $filePath,
+                $fileName,
+                $mimeType,
+                null,
+                true
+            );
+            
+            $encryptionController = new EncryptionController();
+            $request = new Request();
+            $request->files->set('file', $uploadedFile);
+            $request->merge([
+                'id_user' => $encryptionController->encrypt($this->requestData['id_user']),
+                'linea' => $this->requestData['linea']
+            ]);
+            
+            $documentController = new DocumentManagementController();
+            $response = $documentController->uploadTempFile($request);
+            $data = json_decode($response->getContent());
+            
+            return $data->error ? false : $data->response->idArchivo;
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+    
+    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';
+    }
+    
+    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);
+    }
+}

+ 303 - 0
sistema-mantenimiento-back/bash/validateLoadArchives.php

@@ -0,0 +1,303 @@
+<?php
+// Configuración de BD
+
+$host = 'localhost';
+$port = 3306;
+$database = 'samqa';
+$username = 'root';
+$password = 'root';
+
+
+// Archivo lock global
+$lockFile = __DIR__ . '/validation.lock';
+
+// 1. Verificar lock global
+if (file_exists($lockFile)) {
+    echo "[" . date('Y-m-d H:i:s') . "] Otro proceso está en ejecución. Saliendo...\n";
+    exit;
+}
+file_put_contents($lockFile, getmypid());
+
+// 2. Conectar BD
+try {
+    $pdo = new PDO("mysql:host=$host;port=$port;dbname=$database", $username, $password);
+    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+    // 3. Buscar job pendiente, en progreso o fallido
+    $pdo->beginTransaction();
+
+    // Primero buscar un job que quedó a medias
+    $stmt = $pdo->prepare("
+        SELECT * FROM S002V01TVAJO
+        WHERE VAJO_ESTA = 'processing' AND VAJO_PRPE < 100
+        ORDER BY VAJO_FERE ASC
+        LIMIT 1
+        FOR UPDATE
+    ");
+    $stmt->execute();
+    $job = $stmt->fetch(PDO::FETCH_ASSOC);
+
+    // Si no hay, buscar jobs fallidos para reintentar
+    if (!$job) {
+        $stmt = $pdo->prepare("
+            SELECT * FROM S002V01TVAJO
+            WHERE VAJO_ESTA = 'failed'
+            ORDER BY VAJO_FERE ASC
+            LIMIT 1
+            FOR UPDATE
+        ");
+        $stmt->execute();
+        $job = $stmt->fetch(PDO::FETCH_ASSOC);
+        
+        if ($job) {
+            echo "Reintentando job fallido: {$job['VAJO_IDJO']}\n";
+            $update = $pdo->prepare("
+                UPDATE S002V01TVAJO
+                SET VAJO_ESTA = 'processing', VAJO_ERME = NULL, VAJO_FEMO = NOW()
+                WHERE VAJO_IDJO = ?
+            ");
+            $update->execute([$job['VAJO_IDJO']]);
+        }
+    }
+
+    // Si no hay, tomar uno en cola
+    if (!$job) {
+        $stmt = $pdo->prepare("
+            SELECT * FROM S002V01TVAJO
+            WHERE VAJO_ESTA = 'queued'
+            ORDER BY VAJO_FERE ASC
+            LIMIT 1
+            FOR UPDATE
+        ");
+        $stmt->execute();
+        $job = $stmt->fetch(PDO::FETCH_ASSOC);
+
+        if ($job) {
+            $update = $pdo->prepare("
+                UPDATE S002V01TVAJO
+                SET VAJO_ESTA = 'processing', VAJO_FEMO = NOW()
+                WHERE VAJO_IDJO = ?
+            ");
+            $update->execute([$job['VAJO_IDJO']]);
+        }
+    }
+
+    $pdo->commit();
+
+    if (!$job) {
+        echo "[" . date('Y-m-d H:i:s') . "] No hay trabajos pendientes.\n";
+        unlink($lockFile);
+        exit;
+    }
+
+    echo "Procesando Job ID: {$job['VAJO_IDJO']}\n";
+
+    // 4. Determinar archivos ya procesados
+    $results = json_decode($job['VAJO_RESU'], true) ?? ['processed_files' => []];
+    $procesados = $results['processed_files'] ?? [];
+
+    // 5. Buscar ruta del ZIP en base de datos
+    $stmt = $pdo->prepare("SELECT ARTE_UBTE FROM s002v01tarte WHERE ARTE_IDAR = ?");
+    $stmt->execute([$job['VAJO_ZITE']]);
+    $zipData = $stmt->fetch(PDO::FETCH_ASSOC);
+    
+    if (!$zipData) {
+        throw new Exception("ZIP ID no encontrado: {$job['VAJO_ZITE']}");
+    }
+    
+    $zipPath = $zipData['ARTE_UBTE'];
+    if (!file_exists($zipPath)) {
+        throw new Exception("ZIP no encontrado: {$zipPath}");
+    }
+    
+    // Guardar IDs de archivos temporales
+    $tempFiles = [
+        'excel' => getEncrypt($job['VAJO_EXTE']),
+        'zip' => getEncrypt($job['VAJO_ZITE'])
+    ];
+    
+    $individualTempFiles = [];
+
+    $zip = new ZipArchive();
+    if ($zip->open($zipPath) !== TRUE) {
+        throw new Exception("No se pudo abrir el ZIP: {$zipPath}");
+    }
+
+    // Crear carpeta de extracción temporal
+    $extractDir = __DIR__ . '/../storage/app/tempFiles/extracted_' . time();
+    if (!mkdir($extractDir, 0755, true)) {
+        throw new Exception("No se pudo crear directorio de extracción: {$extractDir}");
+    }
+
+    // Obtener solo archivos (no directorios)
+    $archivos = [];
+    for ($i = 0; $i < $zip->numFiles; $i++) {
+        $stat = $zip->statIndex($i);
+        $nombreArchivo = $stat['name'];
+        
+        // Solo procesar archivos, no directorios
+        if (substr($nombreArchivo, -1) !== '/') {
+            $archivos[] = basename($nombreArchivo); // Solo nombre sin ruta
+        }
+    }
+
+    $totalArchivos = count($archivos);
+    $faltantes = array_diff($archivos, $procesados);
+
+    echo "Archivos totales: $totalArchivos, ya procesados: " . count($procesados) . ", faltantes: " . count($faltantes) . "\n";
+
+    // 6. Procesar faltantes
+    foreach ($faltantes as $nombreArchivo) {
+        echo "Procesando: {$nombreArchivo}\n";
+
+        // Extraer archivo del ZIP
+        $archivoExtraido = $extractDir . '/' . $nombreArchivo;
+        $contenido = null;
+        
+        // Buscar el archivo en el ZIP (puede estar en subdirectorios)
+        for ($i = 0; $i < $zip->numFiles; $i++) {
+            $stat = $zip->statIndex($i);
+            if (basename($stat['name']) === $nombreArchivo) {
+                $contenido = $zip->getFromIndex($i);
+                break;
+            }
+        }
+        
+        if ($contenido === null) {
+            echo "Archivo no encontrado en ZIP: {$nombreArchivo}\n";
+            continue;
+        }
+        
+        // Guardar archivo extraído
+        file_put_contents($archivoExtraido, $contenido);
+
+        // Enviar archivo al endpoint usando multipart/form-data
+        $ch = curl_init('http://192.168.2.21:8000/api/upload-temp-file');
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, [
+            'file' => new CURLFile($archivoExtraido, mime_content_type($archivoExtraido), $nombreArchivo),
+            'id_user' => $job['VAJO_IDUS'],
+            'linea' => $job['VAJO_NULI']
+        ]);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        $response = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        curl_close($ch);
+        
+        // Eliminar archivo temporal
+        unlink($archivoExtraido);
+        
+        // Solo marcar como procesado si el endpoint respondió exitosamente
+        if ($httpCode >= 200 && $httpCode < 300) {
+            $responseData = json_decode($response, true);
+            if (isset($responseData['response']['idArchivo'])) {
+                $individualTempFiles[] = [
+                    'original_name' => $nombreArchivo,
+                    'temp_id' => $responseData['response']['idArchivo']
+                ];
+            }
+            
+            $procesados[] = $nombreArchivo;
+            $progress = intval((count($procesados) / $totalArchivos) * 100);
+
+            $results['processed_files'] = $procesados;
+            $stmt = $pdo->prepare("
+                UPDATE S002V01TVAJO
+                SET VAJO_RESU = ?, VAJO_PRPE = ?, VAJO_FEMO = NOW()
+                WHERE VAJO_IDJO = ?
+            ");
+            $stmt->execute([json_encode($results), $progress, $job['VAJO_IDJO']]);
+        } else {
+            echo "Error al procesar archivo {$nombreArchivo}: HTTP {$httpCode}\n";
+            throw new Exception("Error al procesar archivo: {$nombreArchivo}");
+        }
+    }
+
+    $zip->close();
+    
+    // Eliminar directorio de extracción
+    rmdir($extractDir);
+
+    // 7. Marcar como completado
+    $stmt = $pdo->prepare("
+        UPDATE S002V01TVAJO
+        SET VAJO_ESTA = 'completed', VAJO_PRPE = 100, VAJO_FECO = NOW(), VAJO_FEMO = NOW()
+        WHERE VAJO_IDJO = ?
+    ");
+    $stmt->execute([$job['VAJO_IDJO']]);
+    
+    // Enviar notificación de completado
+    $parameters = [
+        'temp_files' => $tempFiles,
+        'individual_temp_files' => $individualTempFiles
+    ];
+    
+    emitNotification(
+        'S002V01M15IDUE',
+        'Carga de archivos temporales completada',
+        'Los archivos del submodulo Carga de documentos relacionados han sido subidos al servidor y están listos para completar el proceso.',
+        [['BOTON' => 'Completar proceso', 'FUNCION' => 'processLoadArchives', 'PARAMETROS' => json_encode($parameters)]],
+        [$job['VAJO_IDUS']]
+    );
+
+    echo "Job completado: {$job['VAJO_IDJO']}\n";
+
+} catch (Exception $e) {
+    echo "ERROR: " . $e->getMessage() . "\n";
+    
+    // Marcar job como fallido si hay job activo
+    if (isset($job)) {
+        $stmt = $pdo->prepare("UPDATE S002V01TVAJO SET VAJO_ESTA = 'failed', VAJO_ERME = ? WHERE VAJO_IDJO = ?");
+        $stmt->execute([$e->getMessage(), $job['VAJO_IDJO']]);
+    }
+} finally {
+    // 8. Limpiar y eliminar lock global
+    if (isset($extractDir) && is_dir($extractDir)) {
+        $files = glob("$extractDir/*");
+        foreach ($files as $file) {
+            if (is_file($file)) {
+                unlink($file);
+            }
+        }
+        rmdir($extractDir);
+    }
+    if (file_exists($lockFile)) {
+        unlink($lockFile);
+    }
+}
+
+
+
+function getEncrypt($value){
+    $ch = curl_init('http://192.168.2.21:8000/api/encrypt');
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, ['value' => $value]);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    $response = curl_exec($ch);
+    curl_close($ch);
+    
+    $responseData = json_decode($response, true);
+    return $responseData['response']['encrypted'];
+}
+
+function emitNotification($module, $title, $content, $actions, $audience) {
+    $ch = curl_init('http://192.168.2.21:8000/api/emitNotification');
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, [
+        'module' => $module,
+        'title' => $title,
+        'content' => $content,
+        'actions' => json_encode($actions),
+        'audience' => $audience
+    ]);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    $response = curl_exec($ch);
+    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+    curl_close($ch);
+    
+    if ($httpCode >= 200 && $httpCode < 300) {
+        echo "Notificación enviada exitosamente\n";
+    } else {
+        echo "Error al enviar notificación: HTTP $httpCode\n";
+    }
+}

+ 5 - 2
sistema-mantenimiento-back/composer.json

@@ -2,12 +2,15 @@
     "name": "laravel/laravel",
     "name": "laravel/laravel",
     "type": "project",
     "type": "project",
     "description": "The skeleton application for the Laravel framework.",
     "description": "The skeleton application for the Laravel framework.",
-    "keywords": ["laravel", "framework"],
+    "keywords": [
+        "laravel",
+        "framework"
+    ],
     "license": "MIT",
     "license": "MIT",
     "require": {
     "require": {
         "php": "^8.1",
         "php": "^8.1",
         "dompdf/dompdf": "^2.0",
         "dompdf/dompdf": "^2.0",
-        "elephantio/elephant.io": "*",
+        "elephantio/elephant.io": "4.2",
         "firebase/php-jwt": "^6.7",
         "firebase/php-jwt": "^6.7",
         "guzzlehttp/guzzle": "^7.2",
         "guzzlehttp/guzzle": "^7.2",
         "jenssegers/agent": "^2.6",
         "jenssegers/agent": "^2.6",

+ 0 - 32
sistema-mantenimiento-back/database/migrations/2014_10_12_000000_create_users_table.php

@@ -1,32 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-return new class extends Migration
-{
-    /**
-     * Run the migrations.
-     */
-    public function up(): void
-    {
-        Schema::create('users', function (Blueprint $table) {
-            $table->id();
-            $table->string('name');
-            $table->string('email')->unique();
-            $table->timestamp('email_verified_at')->nullable();
-            $table->string('password');
-            $table->rememberToken();
-            $table->timestamps();
-        });
-    }
-
-    /**
-     * Reverse the migrations.
-     */
-    public function down(): void
-    {
-        Schema::dropIfExists('users');
-    }
-};

+ 0 - 28
sistema-mantenimiento-back/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php

@@ -1,28 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-return new class extends Migration
-{
-    /**
-     * Run the migrations.
-     */
-    public function up(): void
-    {
-        Schema::create('password_reset_tokens', function (Blueprint $table) {
-            $table->string('email')->primary();
-            $table->string('token');
-            $table->timestamp('created_at')->nullable();
-        });
-    }
-
-    /**
-     * Reverse the migrations.
-     */
-    public function down(): void
-    {
-        Schema::dropIfExists('password_reset_tokens');
-    }
-};

+ 0 - 32
sistema-mantenimiento-back/database/migrations/2019_08_19_000000_create_failed_jobs_table.php

@@ -1,32 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-return new class extends Migration
-{
-    /**
-     * Run the migrations.
-     */
-    public function up(): void
-    {
-        Schema::create('failed_jobs', function (Blueprint $table) {
-            $table->id();
-            $table->string('uuid')->unique();
-            $table->text('connection');
-            $table->text('queue');
-            $table->longText('payload');
-            $table->longText('exception');
-            $table->timestamp('failed_at')->useCurrent();
-        });
-    }
-
-    /**
-     * Reverse the migrations.
-     */
-    public function down(): void
-    {
-        Schema::dropIfExists('failed_jobs');
-    }
-};

+ 0 - 33
sistema-mantenimiento-back/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php

@@ -1,33 +0,0 @@
-<?php
-
-use Illuminate\Database\Migrations\Migration;
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-
-return new class extends Migration
-{
-    /**
-     * Run the migrations.
-     */
-    public function up(): void
-    {
-        Schema::create('personal_access_tokens', function (Blueprint $table) {
-            $table->id();
-            $table->morphs('tokenable');
-            $table->string('name');
-            $table->string('token', 64)->unique();
-            $table->text('abilities')->nullable();
-            $table->timestamp('last_used_at')->nullable();
-            $table->timestamp('expires_at')->nullable();
-            $table->timestamps();
-        });
-    }
-
-    /**
-     * Reverse the migrations.
-     */
-    public function down(): void
-    {
-        Schema::dropIfExists('personal_access_tokens');
-    }
-};

+ 11 - 4
sistema-mantenimiento-back/routes/api.php

@@ -28,7 +28,7 @@ Route::post("/PRTG/save-report",
 Route::get("/download-file/{token}/{idUser}/{line}",                                    "App\Http\Controllers\DocumentManagementController@downloadFile");                  //
 Route::get("/download-file/{token}/{idUser}/{line}",                                    "App\Http\Controllers\DocumentManagementController@downloadFile");                  //
 Route::get("/print-order-details/{idOrder}/{idUser}/{line}",                            "App\Http\Controllers\PreventiveMaintenanceController@printOrderDetails");          //
 Route::get("/print-order-details/{idOrder}/{idUser}/{line}",                            "App\Http\Controllers\PreventiveMaintenanceController@printOrderDetails");          //
 
 
-Route::middleware(['jwt.auth'])->group(function(){
+//Route::middleware(['jwt.auth'])->group(function(){
     //Endpoints PRTG
     //Endpoints PRTG
     Route::get("/PRTG/get-report/{idReport}/{idUser}/{line}",                           "App\Http\Controllers\PRTGController@getReport");
     Route::get("/PRTG/get-report/{idReport}/{idUser}/{line}",                           "App\Http\Controllers\PRTGController@getReport");
     Route::get("/PRTG/get-last-reports/{idUser}/{line}",                                "App\Http\Controllers\PRTGController@getLastReports");
     Route::get("/PRTG/get-last-reports/{idUser}/{line}",                                "App\Http\Controllers\PRTGController@getLastReports");
@@ -585,7 +585,14 @@ Route::middleware(['jwt.auth'])->group(function(){
     Route::post('budget-management/update-payment-method/{idPayment}',                              "App\Http\Controllers\BudgetManagementController@updatePaymentMethod");
     Route::post('budget-management/update-payment-method/{idPayment}',                              "App\Http\Controllers\BudgetManagementController@updatePaymentMethod");
     Route::post('budget-management/delete-payment-method/{idPayment}',                              "App\Http\Controllers\BudgetManagementController@deletePaymentMethod");
     Route::post('budget-management/delete-payment-method/{idPayment}',                              "App\Http\Controllers\BudgetManagementController@deletePaymentMethod");
 
 
-});
-
-
+    // Módulo de carga de archivos 
+    Route::post("process-load-archives",                                                           "App\Http\Controllers\AsyncValidateLoadArchivesController@processLoadArchives");
+    Route::post("async-validate-load-archives",                                                    "App\Http\Controllers\AsyncValidateLoadArchivesController@validateFilesWithBat");
+    Route::post("encrypt",                                                                          "App\Http\Controllers\AsyncValidateLoadArchivesController@encrypt");
+    Route::post("emitNotification",                                                                 "App\Http\Controllers\AsyncValidateLoadArchivesController@emitNotification");
+    
+    //subida de archivo de equipamientos
+    Route::post('equipment-data/verify-template',                                                   'App\Http\Controllers\TemplatesManagementController@validateAndProcessExcelTemplate');
+    Route::post('equipment-data/upload-template',                                                   'App\Http\Controllers\TemplatesManagementController@processExcelEquipment');
+//});