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"; } }