false, 'message' => 'Usuario no autenticado']); exit; } // Verificar que sea una petición POST if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['success' => false, 'message' => 'Método no permitido']); exit; } // Verificar que se haya subido un archivo (individual o múltiple) if (!isset($_FILES['archivo']) && !isset($_FILES['archivos'])) { error_log("ERROR: No se encontraron archivos. FILES: " . print_r($_FILES, true)); http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'No se ha recibido ningún archivo válido', 'debug' => $_FILES ]); exit; } class ImportadorPedimentos { private $conn; private $estadisticas = [ 'total_lineas' => 0, 'pedimentos_procesados' => 0, 'facturas_procesadas' => 0, 'partidas_procesadas' => 0, 'errores' => 0, 'duplicados' => 0 ]; private $errores_detalle = []; private $pedimentos_creados = []; public function __construct($connection) { $this->conn = $connection; } public function procesarArchivo($archivo_path, $validar_duplicados = true) { // Reiniciar estadísticas y acumuladores por archivo para evitar sobreconteo entre múltiples archivos $this->estadisticas = [ 'total_lineas' => 0, 'pedimentos_procesados' => 0, 'facturas_procesadas' => 0, 'partidas_procesadas' => 0, 'errores' => 0, 'duplicados' => 0 ]; $this->errores_detalle = []; $this->pedimentos_creados = []; $inicio_tiempo = time(); $nombre_archivo = basename($archivo_path); try { $contenido = file_get_contents($archivo_path); if ($contenido === false) { throw new Exception("No se pudo leer el archivo"); } // Convertir encoding si es necesario (muchos archivos julianos usan Latin1) if (!mb_check_encoding($contenido, 'UTF-8')) { $contenido = mb_convert_encoding($contenido, 'UTF-8', 'ISO-8859-1'); } $lineas = explode("\n", $contenido); $this->estadisticas['total_lineas'] = count($lineas); $pedimento_actual = null; $facturas_pedimento = []; $partidas_pedimento = []; foreach ($lineas as $numero_linea => $linea) { $linea = trim($linea); if (empty($linea)) continue; try { $codigo = substr($linea, 0, 3); switch ($codigo) { case '500': // Línea de header - ignorar por ahora break; case '501': // Si hay un pedimento anterior, procesarlo if ($pedimento_actual) { $this->procesarPedimento($pedimento_actual, $facturas_pedimento, $partidas_pedimento, $validar_duplicados); $facturas_pedimento = []; $partidas_pedimento = []; } $pedimento_actual = $this->parsearLinea501Real($linea); break; case '505': if ($pedimento_actual) { try { $factura = $this->parsearLinea505Real($linea); $facturas_pedimento[] = $factura; error_log("Factura 505 procesada exitosamente: " . $factura['numero_factura']); } catch (Exception $e) { error_log("ERROR procesando línea 505: " . $e->getMessage()); error_log("Línea problemática: " . $linea); $this->estadisticas['errores']++; // Continuar con la siguiente línea } } break; case '551': if ($pedimento_actual) { try { $partida = $this->parsearLinea551Real($linea); $partidas_pedimento[] = $partida; error_log("Partida 551 procesada exitosamente: secuencia " . $partida['secuencia']); } catch (Exception $e) { error_log("ERROR procesando línea 551: " . $e->getMessage()); error_log("Línea problemática: " . $linea); $this->estadisticas['errores']++; // Continuar con la siguiente línea en lugar de fallar completamente } } break; case '506': case '509': case '510': case '511': case '553': case '554': case '556': case '557': case '558': case '800': case '801': // Otros códigos del formato real - ignorar por ahora break; } } catch (Exception $e) { $this->errores_detalle[] = "Línea " . ($numero_linea + 1) . ": " . $e->getMessage(); $this->estadisticas['errores']++; } } // Procesar el último pedimento if ($pedimento_actual) { $this->procesarPedimento($pedimento_actual, $facturas_pedimento, $partidas_pedimento, $validar_duplicados); } // Registrar log de importación $tiempo_procesamiento = time() - $inicio_tiempo; $this->registrarLog($nombre_archivo, $tiempo_procesamiento, count($this->errores_detalle) > 0 ? 'con_errores' : 'exitoso'); return [ 'success' => true, 'estadisticas' => $this->estadisticas, 'errores' => $this->errores_detalle, 'pedimentos_creados' => $this->pedimentos_creados ]; } catch (Exception $e) { // Registrar log de error $tiempo_procesamiento = time() - $inicio_tiempo; $this->registrarLog($nombre_archivo, $tiempo_procesamiento, 'fallido', $e->getMessage()); return [ 'success' => false, 'message' => $e->getMessage() ]; } } private function registrarLog($archivo_nombre, $tiempo_procesamiento, $estado, $error_mensaje = null) { try { $sql = "INSERT INTO importacion_logs ( usuario_id, archivo_nombre, total_lineas, pedimentos_procesados, facturas_procesadas, partidas_procesadas, errores, duplicados, tiempo_procesamiento, estado, detalles_errores ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; $detalles_errores = null; if (!empty($this->errores_detalle)) { $detalles_errores = implode("\n", $this->errores_detalle); } else if ($error_mensaje) { $detalles_errores = $error_mensaje; } $params = [ isset($_SESSION['usuario_id']) ? $_SESSION['usuario_id'] : null, $archivo_nombre, $this->estadisticas['total_lineas'], $this->estadisticas['pedimentos_procesados'], $this->estadisticas['facturas_procesadas'], $this->estadisticas['partidas_procesadas'], $this->estadisticas['errores'], $this->estadisticas['duplicados'], $tiempo_procesamiento, $estado, $detalles_errores ]; $stmt = sqlsrv_query($this->conn, $sql, $params); if ($stmt) { sqlsrv_free_stmt($stmt); } } catch (Exception $e) { // No hacer nada si falla el log, no queremos interrumpir el proceso principal } } private function parsearLinea501($linea) { // Formato: 501|numero_pedimento|patente|aduana|anio|clave_documento|rfc_importador|... $campos = explode('|', $linea); if (count($campos) < 7) { throw new Exception("Formato inválido en línea 501"); } return [ 'numero_pedimento' => trim($campos[1]), 'patente' => trim($campos[2]), 'aduana' => trim($campos[3]), 'anio' => trim($campos[4]), 'clave_documento' => trim($campos[5]), 'rfc_importador' => trim($campos[6]), 'fecha_creacion' => date('Y-m-d H:i:s'), 'usuario_id' => $_SESSION['usuario_id'] ]; } private function parsearLinea501Real($linea) { // Formato real: 501|patente|numero_pedimento|aduana|tipo|clave_documento|aduana2||rfc_importador|nombre_importador|... $campos = explode('|', $linea); if (count($campos) < 10) { throw new Exception("Formato inválido en línea 501 real - campos insuficientes: " . count($campos)); } error_log("Parseando 501 real: " . $linea); error_log("Campos: " . print_r(array_slice($campos, 0, 10), true)); return [ 'numero_pedimento' => trim($campos[2]), // Campo 2: numero de pedimento 'patente' => trim($campos[1]), // Campo 1: patente 'aduana' => trim($campos[3]), // Campo 3: aduana 'anio' => date('Y'), // Usar año actual 'clave_documento' => trim($campos[5]), // Campo 5: clave documento 'rfc_importador' => trim($campos[8]), // Campo 8: RFC importador 'fecha_creacion' => date('Y-m-d H:i:s'), 'usuario_id' => $_SESSION['usuario_id'] ]; } private function parsearLinea505($linea) { // Formato: 505|numero_factura|fecha_factura|valor_dolares|valor_factura|... $campos = explode('|', $linea); if (count($campos) < 5) { throw new Exception("Formato inválido en línea 505"); } return [ 'numero_factura' => trim($campos[1]), 'fecha_factura' => $this->convertirFecha(trim($campos[2])), 'valor_dolares' => floatval(trim($campos[3])), 'valor_factura' => floatval(trim($campos[4])) ]; } private function parsearLinea505Real($linea) { // Formato oficial 505: 505|numero_pedimento|fecha_cfdi|numero_cfdi_cove|termino_facturacion|moneda|valor_dolares|valor_total|pais|entidad_federativa|rfc_proveedor|nombre_proveedor|calle|num_int|num_ext|cp|municipio|... $campos = explode('|', $linea); if (count($campos) < 12) { throw new Exception("Formato inválido en línea 505 - campos insuficientes: " . count($campos) . " (mínimo: 12)"); } error_log("DEBUG 505 - Total campos: " . count($campos)); error_log("DEBUG 505 - Primeros 12 campos: " . implode(' | ', array_slice($campos, 0, 12))); // Extraer datos según documentación oficial SAT $numero_pedimento = trim($campos[1]); // Campo 1: Número de Pedimento $fecha_cfdi = trim($campos[2]); // Campo 2: Fecha de CFDI $numero_cfdi_cove = trim($campos[3]); // Campo 3: Número de CFDI (COVE) - 40 caracteres max $termino_facturacion = trim($campos[4]); // Campo 4: Término de Facturación - 3 caracteres $moneda = trim($campos[5]); // Campo 5: Moneda - 3 caracteres $valor_dolares = floatval(trim($campos[6])); // Campo 6: Valor Total en Dólares USD $valor_total = floatval(trim($campos[7])); // Campo 7: Valor Total en moneda del CFDI $pais = trim($campos[8]); // Campo 8: País del CFDI - 3 caracteres $entidad_federativa = trim($campos[9]); // Campo 9: Entidad Federativa - 3 caracteres $rfc_proveedor = trim($campos[10]); // Campo 10: RFC Proveedor - 30 caracteres max $nombre_proveedor = trim($campos[11]); // Campo 11: Nombre Proveedor - 120 caracteres max error_log("DEBUG 505 - Datos oficiales extraídos:"); error_log(" - Número CFDI/COVE: '$numero_cfdi_cove'"); error_log(" - Fecha CFDI: '$fecha_cfdi'"); error_log(" - Término facturación: '$termino_facturacion'"); error_log(" - Moneda: '$moneda'"); error_log(" - Valor USD: $valor_dolares"); error_log(" - Valor total: $valor_total"); error_log(" - RFC Proveedor: '$rfc_proveedor'"); error_log(" - Nombre Proveedor: '$nombre_proveedor'"); return [ 'numero_factura' => $numero_cfdi_cove, // Campo 3: Número de CFDI/COVE 'fecha_factura' => $this->convertirFechaReal($fecha_cfdi), // Campo 2: Fecha CFDI 'valor_dolares' => $valor_dolares, // Campo 6: Valor en USD 'valor_factura' => $valor_total, // Campo 7: Valor total en moneda CFDI 'cove' => $numero_cfdi_cove, // Campo 3: COVE (mismo que número factura) 'moneda' => $moneda, // Campo 5: Moneda 'proveedor' => $nombre_proveedor // Campo 11: Nombre del proveedor ]; } private function parsearLinea551($linea) { // Formato: 551|secuencia|fraccion_arancelaria|descripcion|cantidad|unidad|valor_unitario|... $campos = explode('|', $linea); if (count($campos) < 7) { throw new Exception("Formato inválido en línea 551"); } return [ 'secuencia' => intval(trim($campos[1])), 'fraccion_arancelaria' => trim($campos[2]), 'descripcion' => trim($campos[3]), 'cantidad' => floatval(trim($campos[4])), 'unidad' => trim($campos[5]), 'valor_unitario' => floatval(trim($campos[6])) ]; } private function parsearLinea551Real($linea) { // Formato oficial 551: 551|numero_pedimento|fraccion_arancelaria|numero_partida|subdivision|descripcion|precio_unitario|valor_aduana|valor_comercial|valor_dolares|cantidad_umc|unidad_comercial|... $campos = explode('|', $linea); if (count($campos) < 12) { throw new Exception("Formato inválido en línea 551 - campos insuficientes: " . count($campos) . " (mínimo: 12)"); } error_log("DEBUG 551 - Total campos: " . count($campos)); error_log("DEBUG 551 - Primeros 12 campos: " . implode(' | ', array_slice($campos, 0, 12))); // Extraer datos según documentación oficial $numero_pedimento = isset($campos[1]) ? trim($campos[1]) : ''; $fraccion_arancelaria = isset($campos[2]) ? trim($campos[2]) : ''; $numero_partida = isset($campos[3]) ? intval(trim($campos[3])) : 0; $subdivision = isset($campos[4]) ? trim($campos[4]) : ''; $descripcion = isset($campos[5]) ? trim($campos[5]) : ''; $precio_unitario = isset($campos[6]) ? floatval(trim($campos[6])) : 0; $valor_aduana = isset($campos[7]) ? floatval(trim($campos[7])) : 0; $valor_comercial = isset($campos[8]) ? floatval(trim($campos[8])) : 0; $valor_dolares = isset($campos[9]) ? floatval(trim($campos[9])) : 0; $cantidad_umc = isset($campos[10]) ? floatval(trim($campos[10])) : 0; $unidad_comercial = isset($campos[11]) ? trim($campos[11]) : ''; // Aplicar valores por defecto según documentación oficial if (empty($fraccion_arancelaria)) { error_log("WARNING 551: Fracción arancelaria vacía, usando valor por defecto"); $fraccion_arancelaria = 'PENDIENTE'; } if ($numero_partida <= 0) { error_log("WARNING 551: Número de partida inválido, generando secuencial"); static $contador_partida = 1; $numero_partida = $contador_partida++; } if (empty($descripcion)) { error_log("WARNING 551: Descripción vacía, usando valor por defecto"); $descripcion = 'DESCRIPCIÓN PENDIENTE'; } if ($cantidad_umc <= 0) { error_log("WARNING 551: Cantidad UMC inválida ($cantidad_umc), usando 1.0000"); $cantidad_umc = 1.0000; } if (empty($unidad_comercial)) { error_log("WARNING 551: Unidad comercial vacía, usando 'PZ'"); $unidad_comercial = 'PZ'; // Pieza como unidad por defecto } // Calcular precio unitario si no existe pero hay valores if ($precio_unitario == 0) { if ($valor_dolares > 0 && $cantidad_umc > 0) { $precio_unitario = $valor_dolares / $cantidad_umc; error_log("DEBUG 551: Precio unitario calculado: $precio_unitario"); } elseif ($valor_comercial > 0 && $cantidad_umc > 0) { $precio_unitario = $valor_comercial / $cantidad_umc; error_log("DEBUG 551: Precio unitario calculado desde valor comercial: $precio_unitario"); } } error_log("DEBUG 551 - Datos procesados:"); error_log(" - Partida: $numero_partida"); error_log(" - Fracción: '$fraccion_arancelaria'"); error_log(" - Descripción: '" . substr($descripcion, 0, 50) . "...'"); error_log(" - Cantidad: $cantidad_umc"); error_log(" - Unidad: '$unidad_comercial'"); error_log(" - Precio unitario: $precio_unitario"); error_log(" - Valor dólares: $valor_dolares"); return [ 'secuencia' => $numero_partida, 'fraccion_arancelaria' => $fraccion_arancelaria, 'descripcion' => $descripcion, 'cantidad' => $cantidad_umc, 'unidad' => $unidad_comercial, 'valor_unitario' => $precio_unitario, 'peso_neto' => null, 'peso_bruto' => null ]; } private function procesarPedimento($pedimento, $facturas, $partidas, $validar_duplicados) { try { error_log("Procesando pedimento: " . $pedimento['numero_pedimento']); error_log("Validar duplicados: " . ($validar_duplicados ? 'SÍ' : 'NO')); // Validar duplicados si está habilitado if ($validar_duplicados && $this->existePedimento($pedimento['numero_pedimento'])) { error_log("Pedimento {$pedimento['numero_pedimento']} marcado como duplicado, omitiendo..."); $this->estadisticas['duplicados']++; return; } error_log("Insertando pedimento nuevo: " . $pedimento['numero_pedimento']); // Iniciar transacción sqlsrv_begin_transaction($this->conn); // Insertar pedimento $sql = "INSERT INTO pedimentos (numero_pedimento, patente, aduana, anio, clave_documento, rfc_importador, fecha_creacion, usuario_id, estado) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'activo'); SELECT SCOPE_IDENTITY() AS id;"; $params = [ $pedimento['numero_pedimento'], $pedimento['patente'], $pedimento['aduana'], $pedimento['anio'], $pedimento['clave_documento'], $pedimento['rfc_importador'], $pedimento['fecha_creacion'], $pedimento['usuario_id'] ]; $stmt = sqlsrv_query($this->conn, $sql, $params); if ($stmt === false) { throw new Exception("Error al insertar pedimento: " . print_r(sqlsrv_errors(), true)); } // Obtener el ID del pedimento insertado sqlsrv_next_result($stmt); sqlsrv_fetch($stmt); $pedimento_id = sqlsrv_get_field($stmt, 0); sqlsrv_free_stmt($stmt); // Insertar facturas foreach ($facturas as $factura) { $sql = "INSERT INTO pedimento_facturas (pedimento_id, numero_factura, fecha_factura, valor_dolares, valor_factura, cove, moneda, proveedor) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; $params = [ $pedimento_id, $factura['numero_factura'], $factura['fecha_factura'], $factura['valor_dolares'], $factura['valor_factura'], isset($factura['cove']) ? $factura['cove'] : null, isset($factura['moneda']) ? $factura['moneda'] : 'USD', isset($factura['proveedor']) ? $factura['proveedor'] : null ]; $stmt = sqlsrv_query($this->conn, $sql, $params); if ($stmt === false) { throw new Exception("Error al insertar factura: " . print_r(sqlsrv_errors(), true)); } sqlsrv_free_stmt($stmt); $this->estadisticas['facturas_procesadas']++; } // Insertar partidas foreach ($partidas as $partida) { error_log("INSERTANDO PARTIDA: " . print_r($partida, true)); $sql = "INSERT INTO pedimento_partidas (pedimento_id, secuencia, fraccion_arancelaria, descripcion, cantidad, unidad, valor_unitario, peso_neto, peso_bruto) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; $params = [ $pedimento_id, $partida['secuencia'], $partida['fraccion_arancelaria'], $partida['descripcion'], $partida['cantidad'], $partida['unidad'], $partida['valor_unitario'], isset($partida['peso_neto']) ? $partida['peso_neto'] : null, isset($partida['peso_bruto']) ? $partida['peso_bruto'] : null ]; error_log("PARÁMETROS SQL: " . print_r($params, true)); $stmt = sqlsrv_query($this->conn, $sql, $params); if ($stmt === false) { $error = print_r(sqlsrv_errors(), true); error_log("ERROR AL INSERTAR PARTIDA: " . $error); throw new Exception("Error al insertar partida: " . $error); } sqlsrv_free_stmt($stmt); $this->estadisticas['partidas_procesadas']++; error_log("Partida insertada exitosamente - Total procesadas: " . $this->estadisticas['partidas_procesadas']); } // Confirmar transacción sqlsrv_commit($this->conn); $this->estadisticas['pedimentos_procesados']++; $this->pedimentos_creados[] = $pedimento['numero_pedimento']; } catch (Exception $e) { sqlsrv_rollback($this->conn); $this->errores_detalle[] = "Error procesando pedimento {$pedimento['numero_pedimento']}: " . $e->getMessage(); $this->estadisticas['errores']++; } } private function existePedimento($numero_pedimento) { $sql = "SELECT id, fecha_creacion FROM pedimentos WHERE numero_pedimento = ?"; $stmt = sqlsrv_query($this->conn, $sql, [$numero_pedimento]); if ($stmt === false) { error_log("Error al verificar duplicado: " . print_r(sqlsrv_errors(), true)); return false; } $row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC); sqlsrv_free_stmt($stmt); if ($row) { error_log("Pedimento duplicado encontrado: {$numero_pedimento} (ID: {$row['id']})"); return true; } return false; } private function convertirFecha($fecha_str) { // Convertir formato DDMMYYYY a YYYY-MM-DD if (strlen($fecha_str) === 8) { $dia = substr($fecha_str, 0, 2); $mes = substr($fecha_str, 2, 2); $anio = substr($fecha_str, 4, 4); return "$anio-$mes-$dia"; } return date('Y-m-d'); // Fecha por defecto } private function convertirFechaReal($fecha_str) { // Convertir fecha del archivo real - puede venir en formato YYYYMMDD o DDMMYYYY if (empty($fecha_str) || strlen($fecha_str) !== 8) { return date('Y-m-d'); // Fecha por defecto } // Intentar formato YYYYMMDD primero if (substr($fecha_str, 0, 2) === '20') { $anio = substr($fecha_str, 0, 4); $mes = substr($fecha_str, 4, 2); $dia = substr($fecha_str, 6, 2); } else { // Formato DDMMYYYY $dia = substr($fecha_str, 0, 2); $mes = substr($fecha_str, 2, 2); $anio = substr($fecha_str, 4, 4); } // Validar fecha if (checkdate($mes, $dia, $anio)) { return "$anio-$mes-$dia"; } return date('Y-m-d'); // Fecha por defecto si no es válida } } // Procesamiento de archivos (individual o múltiple) try { error_log("=== PROCESAMIENTO DE ARCHIVOS ==="); error_log("POST: " . print_r($_POST, true)); error_log("FILES: " . print_r($_FILES, true)); $validar_duplicados = isset($_POST['validar_duplicados']) && $_POST['validar_duplicados'] === 'on'; error_log("Validar duplicados: " . ($validar_duplicados ? 'SÍ' : 'NO')); // Validar variables de entorno requeridas ANTES de intentar conectar (evita die() dentro de getConnection) $requiredEnv = ['DB_HOST','DB_DATABASE','DB_USERNAME','DB_PASSWORD']; foreach ($requiredEnv as $envKey) { if (!isset($_ENV[$envKey]) || $_ENV[$envKey] === '') { throw new Exception("Falta configurar la variable de entorno $envKey en el archivo .env"); } } // Obtener conexión a SQL Server $conn = getConnection(); $importador = new ImportadorPedimentos($conn); $resultado_final = null; // Verificar si es archivo individual if (isset($_FILES['archivo'])) { error_log("=== PROCESAMIENTO INDIVIDUAL ==="); $archivo_temporal = $_FILES['archivo']['tmp_name']; $nombre_archivo = $_FILES['archivo']['name']; $resultado_final = $importador->procesarArchivo($archivo_temporal, $validar_duplicados); } else if (isset($_FILES['archivos']) && is_array($_FILES['archivos']['name'])) { error_log("=== PROCESAMIENTO MÚLTIPLE FORMATO NORMAL ==="); $archivos = $_FILES['archivos']; $estadisticas_globales = [ 'pedimentos_procesados' => 0, 'facturas_procesadas' => 0, 'partidas_procesadas' => 0, 'duplicados' => 0, 'errores' => 0, 'total_lineas' => 0 ]; $archivos_procesados = 0; $archivos_con_errores = []; // Procesar cada archivo individualmente for ($i = 0; $i < count($archivos['name']); $i++) { if ($archivos['error'][$i] !== UPLOAD_ERR_OK) { $archivos_con_errores[] = [ 'archivo' => $archivos['name'][$i], 'error' => 'Error en la subida del archivo (código: ' . $archivos['error'][$i] . ')' ]; continue; } $nombre_archivo = $archivos['name'][$i]; $archivo_temporal = $archivos['tmp_name'][$i]; error_log("Procesando archivo: $nombre_archivo"); try { $resultado = $importador->procesarArchivo($archivo_temporal, $validar_duplicados); if ($resultado['success']) { $estadisticas_globales['pedimentos_procesados'] += $resultado['estadisticas']['pedimentos_procesados']; $estadisticas_globales['facturas_procesadas'] += $resultado['estadisticas']['facturas_procesadas']; $estadisticas_globales['partidas_procesadas'] += $resultado['estadisticas']['partidas_procesadas']; $estadisticas_globales['duplicados'] += $resultado['estadisticas']['duplicados']; $estadisticas_globales['errores'] += $resultado['estadisticas']['errores']; $estadisticas_globales['total_lineas'] += $resultado['estadisticas']['total_lineas']; $archivos_procesados++; error_log("Archivo procesado exitosamente: $nombre_archivo"); } else { $archivos_con_errores[] = [ 'archivo' => $nombre_archivo, 'error' => $resultado['message'] ?? 'Error desconocido' ]; } } catch (Exception $e) { $archivos_con_errores[] = [ 'archivo' => $nombre_archivo, 'error' => $e->getMessage() ]; error_log("Excepción procesando archivo: $nombre_archivo - " . $e->getMessage()); } } $resultado_final = [ 'success' => $archivos_procesados > 0, 'estadisticas' => $estadisticas_globales, 'archivos_procesados' => $archivos_procesados, 'total_archivos' => count($archivos['name']), 'archivos_con_errores' => $archivos_con_errores ]; if ($archivos_procesados === 0) { $resultado_final['message'] = 'No se pudo procesar ningún archivo correctamente'; } } else { throw new Exception("No se recibieron archivos para procesar"); } // Cerrar conexión sqlsrv_close($conn); // Enviar respuesta echo json_encode($resultado_final); } catch (Exception $e) { error_log("ERROR GENERAL: " . $e->getMessage()); http_response_code(500); echo json_encode([ 'success' => false, 'message' => 'Error del servidor: ' . $e->getMessage() ]); } ?>