Browse Source

flujo funcional. Faltan pruebas de extracciones

EmilianoOrtiz 3 months ago
parent
commit
6d93fef558

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

@@ -8,6 +8,8 @@ use PhpOffice\PhpSpreadsheet\IOFactory;
 use ZipArchive;
 use ZipArchive;
 use App\Jobs\ValidateLoadArchives;
 use App\Jobs\ValidateLoadArchives;
 use Illuminate\Support\Str;
 use Illuminate\Support\Str;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 
 
 /**
 /**
  * Async Controller for validating Excel and ZIP files for equipment documentation loading
  * Async Controller for validating Excel and ZIP files for equipment documentation loading
@@ -72,6 +74,132 @@ class AsyncValidateLoadArchivesController extends Controller
         $this->documentManagementController = new DocumentManagementController();
         $this->documentManagementController = new DocumentManagementController();
         $this->functionsController = new FunctionsController();
         $this->functionsController = new FunctionsController();
     }
     }
+
+    /**
+     * 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);
+        }
+
+        
+        if (!$comparison['valid']) {
+            return $this->responseController->makeResponse(
+                true, 
+                'Los archivos no coinciden entre Excel y ZIP', 
+                $comparison, 
+                400
+            );
+        }
+
+        // Upload temp files
+        $tempFiles = $this->uploadTempFiles($request);
+        if ($tempFiles['error']) {
+            return $this->responseController->makeResponse(true, $tempFiles['message'], [], 400);
+        }
+
+        // Log::info($tempFiles);
+
+        $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);
+        }
+
+        Log::info($idFileZip);
+
+        //Recuperar objeto del registro del zip
+         $zipFile = DB::table('S002V01TARTE')->where([
+            ['ARTE_IDAR', '=', $idFileZip],
+            ['ARTE_NULI', '=', $request['linea']]
+        ])->first();
+
+        //hay que usar la funcion de registerActivity para registrar en bd los movimiento  como el de registrar un nuevo archivo en tmep
+        
+        $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);
+        }
+
+        Log::info($idFileExcel);
+
+        //Recuperar objeto del registro del excel
+         $excelFile = DB::table('S002V01TARTE')->where([
+            ['ARTE_IDAR', '=', $idFileExcel],
+            ['ARTE_NULI', '=', $request['linea']]
+        ])->first();
+        
+
+        // Guardar en bd::validation_jobs 
+        $jobId = DB::table('validation_jobs')->insertGetId([
+            'job_id' => $jobId,
+            'user_id' => $userId,
+            'linea' => $request->input('linea'),
+            'excel_temp_path' => $excelFile->ARTE_UBTE,
+            'zip_temp_path' => $zipFile->ARTE_UBTE,
+            'created_at' => now(),
+            'updated_at' => now()
+        ]);
+        
+        return $this->responseController->makeResponse(
+            false, 
+            'Validación iniciada', 
+            ['job_id' => $jobId, 'status' => 'processing']
+        );
+    }
+
         
         
     /**
     /**
      * Async validation endpoint that dispatches validation to queue
      * Async validation endpoint that dispatches validation to queue

+ 0 - 33
sistema-mantenimiento-back/app/Services/WebSocketService.php

@@ -1,33 +0,0 @@
-<?php
-
-namespace App\Services;
-
-use Illuminate\Support\Facades\Log;
-use ElephantIO\Client;
-
-class WebSocketService
-{
-    public function emitToUser($userId, $event, $data)
-    {
-        try {
-            $url = 'http://localhost:3200';
-            $socketClient = new Client(Client::engine(Client::CLIENT_4X, $url));
-            $socketClient->initialize();
-            $socketClient->of('/');
-            
-            // Emitir evento con userId para que app.js lo distribuya
-            $socketClient->emit('laravel_emit', [
-                'event' => $event,
-                'data' => $data,
-                'userId' => $userId
-            ]);
-            
-            $socketClient->close();
-            
-            return true;
-        } catch (\Exception $e) {
-            Log::error('WebSocket emit failed: ' . $e->getMessage());
-            return false;
-        }
-    }
-}

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

@@ -0,0 +1,196 @@
+<?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 o en progreso
+    $pdo->beginTransaction();
+
+    // Primero buscar un job que quedó a medias
+    $stmt = $pdo->prepare("
+        SELECT * FROM validation_jobs
+        WHERE status = 'processing' AND progress_percentage < 100
+        ORDER BY created_at ASC
+        LIMIT 1
+        FOR UPDATE
+    ");
+    $stmt->execute();
+    $job = $stmt->fetch(PDO::FETCH_ASSOC);
+
+    // Si no hay, tomar uno en cola
+    if (!$job) {
+        $stmt = $pdo->prepare("
+            SELECT * FROM validation_jobs
+            WHERE status = 'queued'
+            ORDER BY created_at ASC
+            LIMIT 1
+            FOR UPDATE
+        ");
+        $stmt->execute();
+        $job = $stmt->fetch(PDO::FETCH_ASSOC);
+
+        if ($job) {
+            $update = $pdo->prepare("
+                UPDATE validation_jobs
+                SET status = 'processing', updated_at = NOW()
+                WHERE id = ?
+            ");
+            $update->execute([$job['id']]);
+        }
+    }
+
+    $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['id']}\n";
+    
+    // Enviar notificación WebSocket de inicio
+    sendWebSocketNotification($job['user_id'], 'processing', $job['id']);
+
+    // 4. Determinar archivos ya procesados
+    $results = json_decode($job['results'], true) ?? ['processed_files' => []];
+    $procesados = $results['processed_files'] ?? [];
+
+    // 5. Abrir ZIP y obtener lista completa de archivos
+    $zipPath = $job['zip_temp_path'];
+    if (!file_exists($zipPath)) {
+        throw new Exception("ZIP no encontrado: {$zipPath}");
+    }
+
+    $zip = new ZipArchive();
+    if ($zip->open($zipPath) !== TRUE) {
+        throw new Exception("No se pudo abrir el ZIP: {$zipPath}");
+    }
+
+    $totalArchivos = $zip->numFiles;
+    $faltantes = [];
+
+    for ($i = 0; $i < $totalArchivos; $i++) {
+        $nombreArchivo = $zip->getNameIndex($i);
+        if (!in_array($nombreArchivo, $procesados)) {
+            $faltantes[] = $nombreArchivo;
+        }
+    }
+
+    echo "Archivos totales: $totalArchivos, ya procesados: " . count($procesados) . ", faltantes: " . count($faltantes) . "\n";
+
+    // 6. Procesar faltantes
+    foreach ($faltantes as $nombreArchivo) {
+        echo "Procesando: {$nombreArchivo}\n";
+
+        // Extraer contenido del archivo
+        $contenido = $zip->getFromName($nombreArchivo);
+
+        // Marcar archivo como procesado
+        $procesados[] = $nombreArchivo;
+        $progress = intval((count($procesados) / $totalArchivos) * 100);
+
+        $results['processed_files'] = $procesados;
+        $stmt = $pdo->prepare("
+            UPDATE validation_jobs
+            SET results = ?, progress_percentage = ?, updated_at = NOW()
+            WHERE id = ?
+        ");
+        $stmt->execute([json_encode($results), $progress, $job['id']]);
+        
+        // Enviar notificación de progreso
+        sendWebSocketNotification($job['user_id'], 'progress', $job['id'], $progress);
+
+        echo "Progreso: {$progress}%\n";
+    }
+
+    $zip->close();
+
+    // 7. Marcar como completado
+    $stmt = $pdo->prepare("
+        UPDATE validation_jobs
+        SET status = 'completed', progress_percentage = 100, completed_at = NOW(), updated_at = NOW()
+        WHERE id = ?
+    ");
+    $stmt->execute([$job['id']]);
+    
+    // Enviar notificación de completado
+    sendWebSocketNotification($job['user_id'], 'completed', $job['id']);
+
+    echo "Job completado: {$job['id']}\n";
+
+} catch (Exception $e) {
+    echo "ERROR: " . $e->getMessage() . "\n";
+    
+    // Enviar notificación de error si hay job activo
+    if (isset($job)) {
+        $stmt = $pdo->prepare("UPDATE validation_jobs SET status = 'failed', error_message = ? WHERE id = ?");
+        $stmt->execute([$e->getMessage(), $job['id']]);
+        sendWebSocketNotification($job['user_id'], 'failed', $job['id']);
+    }
+} finally {
+    // 8. Eliminar lock global
+    if (file_exists($lockFile)) {
+        unlink($lockFile);
+    }
+}
+
+function sendWebSocketNotification($userId, $status, $jobId, $progress = null) {
+    try {
+        require_once __DIR__ . '/../vendor/autoload.php';
+        
+        $client = new \ElephantIO\Client(\ElephantIO\Client::engine(\ElephantIO\Client::CLIENT_4X, 'http://localhost:3200'));
+        $client->initialize();
+        $client->of('/');
+        
+        $data = [
+            'jobId' => $jobId,
+            'status' => $status,
+            'message' => getStatusMessage($status)
+        ];
+        
+        if ($progress !== null) {
+            $data['progress'] = $progress;
+        }
+        
+        $client->emit('laravel_emit', [
+            'event' => 'validation_status',
+            'data' => $data,
+            'userId' => (string)$userId
+        ]);
+        
+        $client->close();
+        echo "WebSocket: $status enviado a usuario $userId\n";
+    } catch (Exception $e) {
+        echo "WebSocket error: " . $e->getMessage() . "\n";
+    }
+}
+
+function getStatusMessage($status) {
+    $messages = [
+        'queued' => 'En cola de validación',
+        'processing' => 'Procesando archivos',
+        'progress' => 'Validando archivos',
+        'completed' => 'Validación completada',
+        'failed' => 'Error en validación'
+    ];
+    return $messages[$status] ?? 'Estado desconocido';
+}

+ 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');
-    }
-};

+ 1 - 1
sistema-mantenimiento-back/database/migrations/2024_01_01_000001_create_validation_jobs_table.php

@@ -9,7 +9,7 @@ return new class extends Migration
     public function up(): void
     public function up(): void
     {
     {
         Schema::create('validation_jobs', function (Blueprint $table) {
         Schema::create('validation_jobs', function (Blueprint $table) {
-            $table->id();
+            $table->id()->autoIncrement();
             $table->string('job_id')->unique();
             $table->string('job_id')->unique();
             $table->string('user_id');
             $table->string('user_id');
             $table->integer('linea');
             $table->integer('linea');

+ 0 - 32
sistema-mantenimiento-back/database/migrations/2025_08_06_165633_create_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('jobs', function (Blueprint $table) {
-            $table->bigIncrements('id');
-            $table->string('queue')->index();
-            $table->longText('payload');
-            $table->unsignedTinyInteger('attempts');
-            $table->unsignedInteger('reserved_at')->nullable();
-            $table->unsignedInteger('available_at');
-            $table->unsignedInteger('created_at');
-        });
-    }
-
-    /**
-     * Reverse the migrations.
-     */
-    public function down(): void
-    {
-        Schema::dropIfExists('jobs');
-    }
-};