<?php
/**
 * Herramienta de Limpieza NICC v1 - limpiar.php
 * ================================================
 * Elimina archivos obsoletos de cada subdominio DokuWiki.
 * Ejecutar ANTES del parche de distribucion (backup sie).
 *
 * Flujo:
 * 1. Autenticacion con token de seguridad
 * 2. Seleccion de letras (A-Z + #) para filtrar subdominios
 * 3. Escaneo AJAX de subdominios por letra
 * 4. Limpieza AJAX subdominio por subdominio
 * 5. Progreso en tiempo real con opcion de cancelar
 *
 * Proteccion contra symlinks (3 capas):
 * - Capa 1: is_link() detecta y salta symlinks
 * - Capa 2: isProtected() verifica lista de rutas protegidas
 * - Capa 3: realpath() verifica que la ruta esta dentro del sitio
 */

require_once __DIR__ . '/config.php';

set_time_limit(MAX_EXECUTION_TIME);
ini_set('memory_limit', MEMORY_LIMIT);

// Iniciar sesion para compartir autenticacion con estado.php
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// ============================================================
// FUNCIONES DE ESCANEO (patron de backup sie/copiar.php)
// ============================================================

/**
 * Detecta si una carpeta es una instalacion DokuWiki
 */
function isDokuWiki($path) {
    return file_exists($path . '/doku.php')
        && (file_exists($path . '/conf/local.php') || file_exists($path . '/conf/dokuwiki.php'))
        && is_dir($path . '/data');
}

/**
 * Escanea SOLO el nivel inmediato de SEARCH_PATH buscando instalaciones DokuWiki.
 *
 * Los subdominios siempre estan directamente bajo public_html.
 * NO se recursa en profundidad para evitar:
 * - Detectar el mismo subdominio multiples veces via symlinks/alias
 * - Encontrar backups internos con estructura DokuWiki
 * - Contar carpetas anidadas (ej: addon domains apuntando al mismo sitio)
 *
 * @param string $path Ruta base (SEARCH_PATH = public_html)
 * @return array Lista de rutas absolutas de instalaciones DokuWiki
 */
function scanDirectory($path) {
    $sites = [];
    if (!is_dir($path)) return $sites;

    $items = @scandir($path);
    if ($items === false) return $sites;

    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        if ($item[0] === '_') continue;               // Ignorar carpetas internas (_siedeployer_patch, etc.)
        $fullPath = $path . '/' . $item;
        if (!is_dir($fullPath)) continue;
        if (is_link($fullPath)) continue;              // Ignorar symlinks (alias cPanel)

        if (isDokuWiki($fullPath)) {
            $sites[] = $fullPath;
        }
    }
    return $sites;
}

/**
 * Verifica si un subdominio esta en la lista de exclusion
 * Retorna el nombre que matcheo o false
 */
function isExcludedByList($sitePath) {
    global $EXCLUSION_LIST;
    if (empty($EXCLUSION_LIST)) return false;
    $siteName = strtolower(basename($sitePath));
    foreach ($EXCLUSION_LIST as $excluded) {
        if ($siteName === strtolower($excluded)) {
            return $excluded;
        }
    }
    return false;
}

/**
 * Obtiene la letra inicial de un nombre de subdominio
 * Retorna A-Z o # para numeros/especiales
 */
function getLetraFromNombre($nombre) {
    $first = strtoupper(substr($nombre, 0, 1));
    if (ctype_alpha($first)) return $first;
    return '#';
}

// ============================================================
// FUNCIONES DE UTILIDAD
// ============================================================

function formatBytes($bytes) {
    if ($bytes >= 1073741824) return number_format($bytes / 1073741824, 2) . ' GB';
    if ($bytes >= 1048576) return number_format($bytes / 1048576, 2) . ' MB';
    if ($bytes >= 1024) return number_format($bytes / 1024, 2) . ' KB';
    return $bytes . ' bytes';
}

function writeLog($message) {
    if (!is_dir(LOG_DIR)) {
        @mkdir(LOG_DIR, 0755, true);
    }
    $logFile = LOG_DIR . '/limpieza_' . date('Y-m-d') . '.log';
    file_put_contents($logFile, date('[Y-m-d H:i:s] ') . $message . "\n", FILE_APPEND);
}

function loadProgress() {
    if (file_exists(PROGRESS_FILE)) {
        $data = json_decode(file_get_contents(PROGRESS_FILE), true);
        if (is_array($data)) return $data;
    }
    return [
        'inicio'            => date('Y-m-d H:i:s'),
        'procesados'        => [],
        'total_eliminados'  => 0,
        'total_protegidos'  => 0,
        'total_errores'     => 0,
        'total_symlinks'    => 0,
    ];
}

function saveProgress($progress) {
    file_put_contents(
        PROGRESS_FILE,
        json_encode($progress, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
    );
}

// ============================================================
// FUNCION CRITICA: isProtected()
// Determina si una ruta relativa debe ser protegida
// ============================================================

/**
 * Verifica si una ruta relativa (al subdominio) esta protegida
 *
 * 3 niveles de verificacion:
 * 1. Dentro de un directorio protegido completo ($PROTECTED_DIRS)
 * 2. Coincide con un archivo protegido exacto ($PROTECTED_FILES)
 * 3. Extension protegida ($PROTECTED_EXTENSIONS)
 *
 * NOTA: Los directorios ancestros (ej: lib/plugins cuando lib/plugins/bez/tpl/thread.php
 * esta protegido) NO se bloquean aqui. Se manejan en limpiarRecursivo() via
 * isAncestorOfProtected() para permitir recursion dentro del directorio y borrar
 * solo los archivos no protegidos.
 *
 * @param string $relPath Ruta relativa al subdominio (ej: "data/meta/do.sqlite3")
 * @return bool true si debe protegerse (no borrar)
 */
function isProtected($relPath) {
    global $PROTECTED_DIRS, $PROTECTED_FILES, $PROTECTED_EXTENSIONS;

    // Normalizar: barras hacia adelante, sin barra inicial
    $relPath = str_replace('\\', '/', $relPath);
    $relPath = ltrim($relPath, '/');

    // Check 1: Esta dentro de un directorio protegido completo?
    foreach ($PROTECTED_DIRS as $protDir) {
        $protDir = rtrim($protDir, '/');
        if ($relPath === $protDir || strpos($relPath, $protDir . '/') === 0) {
            return true;
        }
    }

    // Check 2: Coincide con un archivo protegido exacto?
    if (in_array($relPath, $PROTECTED_FILES, true)) {
        return true;
    }

    // Check 3: Extension protegida?
    if (!empty($PROTECTED_EXTENSIONS)) {
        $ext = strtolower(pathinfo($relPath, PATHINFO_EXTENSION));
        if (in_array($ext, $PROTECTED_EXTENSIONS, true)) {
            return true;
        }
    }

    return false;
}

/**
 * Verifica si una ruta es directorio ancestro de algo protegido
 *
 * Ej: "lib/plugins" es ancestro de "lib/plugins/bez/tpl/thread.php" -> true
 * Ej: "data" es ancestro de "data/media" -> true
 * Ej: "inc" no tiene nada protegido dentro -> false
 *
 * Estos directorios NO deben ser saltados: deben ser recursados para que
 * los archivos individuales sean evaluados por isProtected().
 * El directorio en si se preserva porque no hacemos rmdir.
 *
 * @param string $relPath Ruta relativa al subdominio
 * @return bool true si contiene algo protegido mas abajo
 */
function isAncestorOfProtected($relPath) {
    global $PROTECTED_DIRS, $PROTECTED_FILES;

    $relPath = str_replace('\\', '/', $relPath);
    $relPath = ltrim($relPath, '/');
    $checkPath = $relPath . '/';

    // Tiene archivos protegidos dentro?
    foreach ($PROTECTED_FILES as $pf) {
        if (strpos($pf, $checkPath) === 0) {
            return true;
        }
    }

    // Tiene directorios protegidos dentro?
    foreach ($PROTECTED_DIRS as $pd) {
        $pd = rtrim($pd, '/');
        if (strpos($pd . '/', $checkPath) === 0) {
            return true;
        }
    }

    return false;
}

// ============================================================
// MOTOR DE BORRADO RECURSIVO (3 capas de proteccion symlink)
// ============================================================

/**
 * Elimina recursivamente el contenido de un directorio
 * respetando archivos protegidos y symlinks.
 *
 * CAPA 1: is_link() - detecta symlinks, los salta
 * CAPA 2: isProtected() - lista de rutas protegidas
 * CAPA 3: realpath() boundary - verifica ruta dentro del sitio
 *
 * @param string $fullPath      Ruta absoluta del item
 * @param string $relPath       Ruta relativa al subdominio
 * @param string $siteRoot      Ruta absoluta del subdominio (original)
 * @param string $resolvedRoot  Ruta real del subdominio (pre-resuelta con realpath, normalizada)
 * @param array  &$stats        Contadores: eliminados, protegidos, symlinks, errores, detalles[]
 */
function limpiarRecursivo($fullPath, $relPath, $siteRoot, $resolvedRoot, &$stats) {
    // Normalizar rutas
    $fullPath = str_replace('\\', '/', $fullPath);
    $relPath  = str_replace('\\', '/', $relPath);
    $relPath  = ltrim($relPath, '/');

    // -------------------------------------------------------
    // CAPA 1: Deteccion de symlinks - NUNCA seguir
    // Critico para ckgedit: lib/plugins/ckgedit/fckeditor/userfiles/{flash,image,media,file} -> data/media/
    // -------------------------------------------------------
    if (is_link($fullPath)) {
        $stats['symlinks']++;
        $target = @readlink($fullPath);
        $stats['detalles'][] = "SYMLINK saltado: $relPath -> $target";
        return;
    }

    // -------------------------------------------------------
    // CAPA 2: Verificacion de proteccion
    // -------------------------------------------------------
    if (isProtected($relPath)) {
        $stats['protegidos']++;
        return;
    }

    // Si es directorio ancestro de algo protegido, NO bloquearlo:
    // recursar dentro para borrar lo no protegido.
    // Ej: lib/plugins contiene bez/tpl/thread.php (protegido),
    // pero el resto de plugins SI debe borrarse.
    // isAncestorOfProtected() solo informa, no bloquea.
    // La recursion en la seccion de directorios se encarga del resto.

    // -------------------------------------------------------
    // CAPA 3: Boundary check con realpath()
    // Verifica que la ruta real este dentro del subdominio
    // $resolvedRoot ya viene pre-resuelta desde limpiarSubdominio()
    // -------------------------------------------------------
    $realPath = @realpath($fullPath);
    if ($realPath !== false) {
        $realNorm = str_replace('\\', '/', $realPath);
        if (strpos($realNorm, $resolvedRoot) !== 0) {
            $stats['symlinks']++;
            $stats['detalles'][] = "BOUNDARY: $relPath apunta fuera del sitio ($realNorm)";
            return;
        }
    }

    // -------------------------------------------------------
    // Procesamiento segun tipo
    // -------------------------------------------------------
    if (is_file($fullPath)) {
        if (@unlink($fullPath)) {
            $stats['eliminados']++;
        } else {
            $stats['errores']++;
            $stats['detalles'][] = "ERROR unlink: $relPath";
        }
        return;
    }

    if (is_dir($fullPath)) {
        $items = @scandir($fullPath);
        if ($items === false) {
            $stats['errores']++;
            $stats['detalles'][] = "ERROR scandir: $relPath";
            return;
        }

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') continue;
            $childFull = $fullPath . '/' . $item;
            $childRel  = ($relPath === '') ? $item : $relPath . '/' . $item;
            limpiarRecursivo($childFull, $childRel, $siteRoot, $resolvedRoot, $stats);
        }

        // Intentar eliminar el directorio si quedo vacio despues de la limpieza.
        // @rmdir() solo funciona en directorios VACIOS, asi que es seguro:
        // - Si aun contiene archivos protegidos → rmdir falla silenciosamente → dir preservado
        // - Si quedo completamente vacio → rmdir lo elimina → limpieza completa
        // Esto permite que la limpieza bottom-up borre toda la rama de directorios
        // vacios desde las hojas hasta la raiz (ej: lib/plugins/acl/admin/ → acl/ → plugins/)
        @rmdir($fullPath);
    }
}

// ============================================================
// ORQUESTADOR: Limpia un subdominio completo
// ============================================================

/**
 * Ejecuta la limpieza completa de un subdominio DokuWiki
 *
 * Fase 1: Eliminar archivos raiz ($DELETE_ROOT_FILES)
 * Fase 2: Limpiar directorios recursivamente ($DELETE_DIRS)
 *
 * @param string $sitePath Ruta absoluta del subdominio
 * @return array Stats: eliminados, protegidos, symlinks, errores, detalles[]
 */
function limpiarSubdominio($sitePath) {
    global $KEEP_ROOT_FILES, $DELETE_FILES_IN_DIRS, $DELETE_FULL_DIRS;

    $sitePath = str_replace('\\', '/', rtrim($sitePath, '/\\'));

    $stats = [
        'eliminados' => 0,
        'protegidos' => 0,
        'symlinks'   => 0,
        'errores'    => 0,
        'detalles'   => [],
    ];

    // Pre-resolver realpath del sitio UNA SOLA VEZ
    $resolvedRoot = realpath($sitePath);
    if ($resolvedRoot === false) {
        $stats['errores']++;
        $stats['detalles'][] = "ERROR CRITICO: No se pudo resolver realpath de $sitePath";
        return $stats;
    }
    $resolvedRoot = str_replace('\\', '/', $resolvedRoot);

    // -------------------------------------------------------
    // FASE 1: Limpiar raiz — solo conservar archivos de la lista blanca
    // Escanea la raiz del subdominio. Cualquier ARCHIVO que no este
    // en $KEEP_ROOT_FILES ni en $PROTECTED_FILES se elimina.
    // Las carpetas NO se tocan aqui (solo archivos sueltos).
    // -------------------------------------------------------
    $stats['detalles'][] = "=== FASE 1: Limpieza raiz de $sitePath ===";
    $rootItems = @scandir($sitePath);
    if ($rootItems !== false) {
        foreach ($rootItems as $item) {
            if ($item === '.' || $item === '..') continue;
            $fullPath = $sitePath . '/' . $item;

            // Solo archivos, no carpetas
            if (!is_file($fullPath) && !is_link($fullPath)) continue;

            // Symlink en raiz → saltar
            if (is_link($fullPath)) {
                $stats['symlinks']++;
                $target = @readlink($fullPath);
                $stats['detalles'][] = "SYMLINK raiz saltado: $item -> $target";
                continue;
            }

            // ¿Esta en la lista blanca?
            if (in_array($item, $KEEP_ROOT_FILES, true)) {
                continue;
            }

            // ¿Esta protegido por $PROTECTED_FILES?
            if (isProtected($item)) {
                $stats['protegidos']++;
                continue;
            }

            // Boundary check
            $realFile = @realpath($fullPath);
            if ($realFile !== false) {
                $realFileNorm = str_replace('\\', '/', $realFile);
                if (strpos($realFileNorm, $resolvedRoot) !== 0) {
                    $stats['symlinks']++;
                    $stats['detalles'][] = "BOUNDARY raiz: $item apunta fuera ($realFileNorm)";
                    continue;
                }
            }

            // Eliminar
            if (@unlink($fullPath)) {
                $stats['eliminados']++;
            } else {
                $stats['errores']++;
                $stats['detalles'][] = "ERROR raiz: $item";
            }
        }
    }

    // -------------------------------------------------------
    // FASE 2: Borrar solo ARCHIVOS dentro de directorios
    // La carpeta se conserva como cascaron vacio.
    // Solo borra archivos del nivel inmediato (no recursa).
    // -------------------------------------------------------
    $stats['detalles'][] = "=== FASE 2: " . count($DELETE_FILES_IN_DIRS) . " directorios a limpiar ===";
    foreach ($DELETE_FILES_IN_DIRS as $dir) {
        $dirPath = $sitePath . '/' . $dir;
        if (!is_dir($dirPath)) continue;

        if (is_link($dirPath)) {
            $stats['symlinks']++;
            $stats['detalles'][] = "SYMLINK dir saltado (F2): $dir";
            continue;
        }

        $items = @scandir($dirPath);
        if ($items === false) continue;

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') continue;
            $fileFull = $dirPath . '/' . $item;
            $fileRel  = $dir . '/' . $item;

            if (!is_file($fileFull)) continue;          // Solo archivos, no subdirectorios

            if (is_link($fileFull)) {
                $stats['symlinks']++;
                continue;
            }

            if (isProtected($fileRel)) {
                $stats['protegidos']++;
                continue;
            }

            // Boundary check
            $realFile = @realpath($fileFull);
            if ($realFile !== false) {
                $realFileNorm = str_replace('\\', '/', $realFile);
                if (strpos($realFileNorm, $resolvedRoot) !== 0) {
                    $stats['symlinks']++;
                    $stats['detalles'][] = "BOUNDARY (F2): $fileRel apunta fuera";
                    continue;
                }
            }

            if (@unlink($fileFull)) {
                $stats['eliminados']++;
            } else {
                $stats['errores']++;
                $stats['detalles'][] = "ERROR (F2): $fileRel";
            }
        }
    }

    // -------------------------------------------------------
    // FASE 3: Borrar carpetas COMPLETAS con todo su contenido
    // Usa limpiarRecursivo() que respeta protecciones y symlinks.
    // Luego intenta eliminar la carpeta si quedo vacia.
    // -------------------------------------------------------
    $stats['detalles'][] = "=== FASE 3: " . count($DELETE_FULL_DIRS) . " carpetas a procesar ===";
    foreach ($DELETE_FULL_DIRS as $dir) {
        $fullPath = $sitePath . '/' . $dir;

        if (!is_dir($fullPath)) {
            $stats['detalles'][] = "F3-SKIP: $dir (no existe)";
            continue;
        }

        if (is_link($fullPath)) {
            $stats['symlinks']++;
            $stats['detalles'][] = "SYMLINK dir saltado (F3): $dir";
            continue;
        }

        $preElim = $stats['eliminados'];
        $stats['detalles'][] = "F3-INICIO: $dir (existe, procesando...)";

        // Borrar contenido recursivamente (respeta protecciones)
        limpiarRecursivo($fullPath, $dir, $sitePath, $resolvedRoot, $stats);

        $borrados = $stats['eliminados'] - $preElim;
        $dirExiste = is_dir($fullPath) ? 'aun existe' : 'eliminado';

        // Intentar eliminar la carpeta si quedo vacia
        @rmdir($fullPath);
        $dirFinal = is_dir($fullPath) ? 'permanece (tiene contenido protegido)' : 'BORRADO';

        $stats['detalles'][] = "F3-FIN: $dir -> $borrados archivos borrados, dir $dirFinal";
    }

    // Limitar detalles a 500 entradas para diagnostico
    if (count($stats['detalles']) > 500) {
        $total = count($stats['detalles']);
        $stats['detalles'] = array_slice($stats['detalles'], 0, 500);
        $stats['detalles'][] = "... y " . ($total - 500) . " eventos mas";
    }

    return $stats;
}

// ============================================================
// HANDLER AJAX
// ============================================================

if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['accion'])) {
    header('Content-Type: application/json; charset=utf-8');

    // Verificar token en CADA peticion
    $token = isset($_POST['token']) ? $_POST['token'] : '';
    if ($token !== SECURITY_TOKEN) {
        http_response_code(403);
        echo json_encode(['error' => 'Token de seguridad invalido']);
        exit;
    }

    // Marcar sesion como autenticada (para estado.php)
    $_SESSION['limpieza_auth'] = true;

    $accion = $_POST['accion'];

    // ---------------------------------------------------------
    // ESCANEAR: Obtener lista de sistemas por letras seleccionadas
    // ---------------------------------------------------------
    if ($accion === 'escanear') {
        $letrasSeleccionadas = isset($_POST['letras']) ? $_POST['letras'] : [];
        if (!is_array($letrasSeleccionadas)) {
            $letrasSeleccionadas = [$letrasSeleccionadas];
        }

        // Toggle de exclusiones
        $usarExclusiones = true;
        if (isset($_POST['usarExclusiones'])) {
            $usarExclusiones = ($_POST['usarExclusiones'] === '1');
        }

        $sites = scanDirectory(SEARCH_PATH);
        sort($sites);

        $resultado = [];
        $excluidos = 0;
        $excluidosLista = [];

        foreach ($sites as $sitePath) {
            $nombre = basename($sitePath);
            $letra = getLetraFromNombre($nombre);

            if (!empty($letrasSeleccionadas) && !in_array($letra, $letrasSeleccionadas)) continue;

            // Verificar exclusion (solo si el toggle esta activado)
            $excludedBy = isExcludedByList($sitePath);
            $hasExclFile = file_exists($sitePath . '/' . EXCLUSION_FILE);

            if ($usarExclusiones && ($excludedBy || $hasExclFile)) {
                $excluidos++;
                $motivo = $excludedBy ? "lista ($excludedBy)" : 'exclusion.txt';
                $excluidosLista[] = ['nombre' => $nombre, 'motivo' => $motivo];
                continue;
            }

            $resultado[] = [
                'path'   => $sitePath,
                'nombre' => $nombre,
                'letra'  => $letra,
            ];
        }

        echo json_encode([
            'ok'              => true,
            'sistemas'        => $resultado,
            'total'           => count($resultado),
            'excluidos'       => $excluidos,
            'excluidos_lista' => $excluidosLista,
        ]);
        exit;
    }

    // ---------------------------------------------------------
    // LIMPIAR: Procesar un subdominio
    // ---------------------------------------------------------
    if ($accion === 'limpiar') {
        $sitePath = isset($_POST['sitePath']) ? $_POST['sitePath'] : '';

        // Validar que la ruta esta dentro de SEARCH_PATH
        $realSearch = realpath(SEARCH_PATH);
        $realSite   = realpath($sitePath);
        if (!$realSite || !$realSearch || strpos($realSite, $realSearch) !== 0) {
            echo json_encode(['error' => 'Ruta invalida o fuera del alcance']);
            exit;
        }

        // Validar que es DokuWiki
        if (!isDokuWiki($sitePath)) {
            echo json_encode(['error' => 'No es una instalacion DokuWiki valida']);
            exit;
        }

        set_time_limit(MAX_EXECUTION_TIME);

        $timeStart = microtime(true);
        $stats = limpiarSubdominio($sitePath);
        $elapsed = round(microtime(true) - $timeStart, 2);

        // Log
        $nombre = basename($sitePath);
        writeLog("$nombre | eliminados:{$stats['eliminados']} protegidos:{$stats['protegidos']} symlinks:{$stats['symlinks']} errores:{$stats['errores']} tiempo:{$elapsed}s");

        // Actualizar progreso
        $progress = loadProgress();
        $progress['procesados'][$sitePath] = [
            'eliminados' => $stats['eliminados'],
            'protegidos' => $stats['protegidos'],
            'symlinks'   => $stats['symlinks'],
            'errores'    => $stats['errores'],
            'tiempo'     => $elapsed,
            'fecha'      => date('Y-m-d H:i:s'),
        ];
        $progress['total_eliminados'] += $stats['eliminados'];
        $progress['total_protegidos'] += $stats['protegidos'];
        $progress['total_errores']    += $stats['errores'];
        $progress['total_symlinks']   += $stats['symlinks'];
        saveProgress($progress);

        echo json_encode([
            'ok'      => true,
            'nombre'  => $nombre,
            'stats'   => $stats,
            'tiempo'  => $elapsed,
        ]);
        exit;
    }

    // ---------------------------------------------------------
    // DIAGNOSTICO: Verificar que PHP puede borrar archivos
    // ---------------------------------------------------------
    if ($accion === 'diagnostico') {
        $sitePath = isset($_POST['sitePath']) ? $_POST['sitePath'] : '';
        $diag = [
            'php_user'         => get_current_user(),
            'php_uid'          => function_exists('posix_getuid') ? posix_getuid() : 'N/A',
            'disable_functions' => ini_get('disable_functions'),
            'open_basedir'     => ini_get('open_basedir') ?: '(ninguno)',
            'unlink_disponible' => !in_array('unlink', array_map('trim', explode(',', ini_get('disable_functions')))),
            'rmdir_disponible'  => !in_array('rmdir', array_map('trim', explode(',', ini_get('disable_functions')))),
            'scandir_disponible' => !in_array('scandir', array_map('trim', explode(',', ini_get('disable_functions')))),
            'sitePath'          => $sitePath,
            'sitePath_existe'   => is_dir($sitePath),
            'sitePath_writable' => is_writable($sitePath),
            'dirs_check'        => [],
            'test_delete'       => 'no ejecutado',
        ];

        // Verificar cada directorio de $DELETE_FULL_DIRS
        global $DELETE_FULL_DIRS;
        foreach ($DELETE_FULL_DIRS as $dir) {
            $fp = $sitePath . '/' . $dir;
            $diag['dirs_check'][$dir] = [
                'existe'   => is_dir($fp),
                'es_link'  => is_link($fp),
                'writable' => is_writable($fp),
            ];
        }

        // Test: crear y borrar un archivo temporal
        if (is_dir($sitePath) && is_writable($sitePath)) {
            $testFile = $sitePath . '/_test_delete_' . time() . '.tmp';
            if (@file_put_contents($testFile, 'test')) {
                if (@unlink($testFile)) {
                    $diag['test_delete'] = 'OK - PHP puede crear y borrar archivos';
                } else {
                    $diag['test_delete'] = 'FALLO - PHP puede crear pero NO borrar';
                    @unlink($testFile); // intentar limpiar
                }
            } else {
                $diag['test_delete'] = 'FALLO - PHP no puede crear archivos en la raiz';
            }
        } else {
            $diag['test_delete'] = 'FALLO - sitePath no existe o no es writable';
        }

        echo json_encode(['ok' => true, 'diagnostico' => $diag], JSON_PRETTY_PRINT);
        exit;
    }

    // ---------------------------------------------------------
    // RESET: Reiniciar progreso
    // ---------------------------------------------------------
    if ($accion === 'reset') {
        if (file_exists(PROGRESS_FILE)) @unlink(PROGRESS_FILE);
        echo json_encode(['ok' => true, 'mensaje' => 'Progreso reiniciado']);
        exit;
    }

    echo json_encode(['error' => 'Accion desconocida: ' . $accion]);
    exit;
}

// ============================================================
// INTERFAZ WEB (HTML + CSS + JavaScript)
// ============================================================
?><!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Limpieza NICC v1</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', Tahoma, sans-serif; background: #1a1a2e; color: #e0e0e0; padding: 20px; }
        .container { max-width: 950px; margin: 0 auto; }
        h1 { color: #ff6b6b; margin-bottom: 5px; font-size: 1.4em; }
        h2 { color: #ffd93d; font-size: 1.1em; margin: 15px 0 8px; }
        .subtitle { color: #888; font-size: 0.85em; margin-bottom: 15px; }

        /* Barra de progreso */
        .progress-bar { background: #2a2a4a; border-radius: 8px; height: 30px; margin: 10px 0; overflow: hidden; }
        .progress-fill { height: 100%; border-radius: 8px; transition: width 0.3s; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 0.85em; }
        .fill-ok { background: linear-gradient(90deg, #00b894, #00cec9); }
        .fill-danger { background: linear-gradient(90deg, #e17055, #d63031); }

        /* Stats */
        .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin: 15px 0; }
        .stat-box { background: #2a2a4a; padding: 12px; border-radius: 8px; text-align: center; }
        .stat-value { font-size: 1.5em; font-weight: bold; }
        .stat-label { font-size: 0.8em; color: #888; }
        .val-red { color: #ff6b6b; }
        .val-green { color: #00b894; }
        .val-yellow { color: #ffd93d; }
        .val-blue { color: #74b9ff; }
        .val-purple { color: #a29bfe; }
        .val-orange { color: #e17055; }

        /* Botones */
        .btn { display: inline-block; padding: 8px 18px; border-radius: 6px; text-decoration: none; font-weight: bold; font-size: 0.9em; margin: 5px 3px; cursor: pointer; border: none; color: white; }
        .btn-primary { background: #0984e3; }
        .btn-warning { background: #f39c12; }
        .btn-danger { background: #d63031; }
        .btn-success { background: #00b894; }
        .btn-info { background: #6c5ce7; }
        .btn-start { background: linear-gradient(90deg, #e17055, #d63031); color: #fff; font-size: 1.1em; padding: 12px 30px; }
        .btn:hover { opacity: 0.9; }
        .btn:disabled { opacity: 0.4; cursor: not-allowed; }

        /* Cajas */
        .site-current { background: #2a2a4a; border-left: 4px solid #74b9ff; padding: 12px; margin: 10px 0; border-radius: 0 8px 8px 0; }
        .site-name { color: #74b9ff; font-weight: bold; }
        .log-box { background: #0d0d1a; border: 1px solid #333; padding: 10px; border-radius: 8px; max-height: 350px; overflow-y: auto; font-family: monospace; font-size: 0.78em; line-height: 1.6; margin: 10px 0; }
        .log-ok { color: #00b894; }
        .log-skip { color: #ffd93d; }
        .log-err { color: #ff6b6b; }
        .log-info { color: #74b9ff; }
        .log-sym { color: #a29bfe; }
        .complete { background: #0d3320; border: 2px solid #00b894; padding: 20px; border-radius: 10px; text-align: center; margin: 20px 0; }
        .complete h2 { color: #00b894; font-size: 1.3em; }
        .cancelled { background: #3d3d00; border: 2px solid #ffd93d; }
        .cancelled h2 { color: #ffd93d; }
        .error-box { background: #3d0d0d; border: 2px solid #ff6b6b; padding: 15px; border-radius: 10px; text-align: center; margin: 20px 0; }
        .warning-box { background: #3d1f00; border: 1px solid #f39c12; padding: 10px; border-radius: 8px; margin: 10px 0; font-size: 0.85em; }

        /* Token gate */
        .token-gate { background: #1a2a4a; border: 2px solid #74b9ff; padding: 30px; border-radius: 12px; margin: 30px 0; text-align: center; }
        .token-gate h2 { color: #74b9ff; margin-bottom: 15px; }
        .token-input { background: #0d0d1a; border: 2px solid #444; color: #fff; padding: 10px 15px; border-radius: 6px; font-size: 1em; width: 300px; text-align: center; }
        .token-input:focus { border-color: #74b9ff; outline: none; }
        .token-error { color: #ff6b6b; margin-top: 10px; display: none; }

        /* Grid de letras */
        .letter-grid { display: flex; flex-wrap: wrap; gap: 6px; margin: 15px 0; justify-content: center; }
        .btn-letter { width: 44px; height: 44px; border-radius: 8px; border: 2px solid #444; background: #2a2a4a; color: #888; font-size: 1em; font-weight: bold; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center; }
        .btn-letter:hover { border-color: #74b9ff; color: #74b9ff; }
        .btn-letter-selected { border-color: #00b894; background: #0d3320; color: #00b894; }
        .letter-actions { text-align: center; margin: 10px 0; }

        /* Scan results */
        .scan-result { background: #1a2a4a; border: 1px solid #74b9ff; padding: 15px; border-radius: 8px; margin: 15px 0; }
        .scan-list { max-height: 200px; overflow-y: auto; margin: 10px 0; font-size: 0.85em; }
        .scan-item { padding: 3px 0; color: #ccc; }
        .scan-item .letra-tag { display: inline-block; width: 22px; color: #74b9ff; font-weight: bold; }

        /* Secciones ocultables */
        .section { display: none; }
        .section.active { display: block; }

        /* Procesando animacion */
        @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }
        .processing { animation: pulse 1.5s infinite; color: #ffd93d; }

        /* Info banner */
        .info-banner { background: #1a3a1a; border: 1px solid #00b894; padding: 10px; border-radius: 8px; margin: 10px 0; font-size: 0.85em; color: #00b894; text-align: center; }

        /* Toggle exclusiones */
        .option-row { display: flex; align-items: center; justify-content: center; gap: 10px; margin: 15px 0; font-size: 1em; }
        .option-row label { cursor: pointer; user-select: none; }
        .toggle { position: relative; display: inline-block; width: 50px; height: 26px; }
        .toggle input { opacity: 0; width: 0; height: 0; }
        .toggle .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #636e72; border-radius: 26px; transition: .3s; }
        .toggle .slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 3px; bottom: 3px; background-color: white; border-radius: 50%; transition: .3s; }
        .toggle input:checked + .slider { background-color: #00b894; }
        .toggle input:checked + .slider:before { transform: translateX(24px); }
        .excl-hint { color: #888; font-size: 0.82em; margin-top: 5px; text-align: center; }
    </style>
</head>
<body>
<div class="container">
    <h1>Herramienta de Limpieza NICC v1</h1>
    <p class="subtitle">Elimina archivos obsoletos de subdominios DokuWiki antes del parche de distribucion</p>

    <!-- ============================================================ -->
    <!-- SECCION 1: GATE DE TOKEN                                      -->
    <!-- ============================================================ -->
    <div id="secToken" class="section active">
        <div class="token-gate">
            <h2>Autenticacion Requerida</h2>
            <p style="color: #888; margin-bottom: 15px;">Ingrese el token de seguridad para continuar</p>
            <div>
                <input type="password" id="inputToken" class="token-input" placeholder="Token de seguridad"
                       onkeydown="if(event.key==='Enter') verificarToken()">
            </div>
            <div style="margin-top: 15px;">
                <button class="btn btn-primary" onclick="verificarToken()">Verificar</button>
            </div>
            <div id="tokenError" class="token-error">Token invalido</div>
        </div>
    </div>

    <!-- ============================================================ -->
    <!-- SECCION 2: SELECCION DE LETRAS                                -->
    <!-- ============================================================ -->
    <div id="secLetras" class="section">
        <div class="info-banner">Token verificado correctamente</div>

        <h2>Seleccionar Letras</h2>
        <p style="color: #888; font-size: 0.85em; margin-bottom: 10px;">
            Seleccione las letras iniciales de los subdominios a limpiar. # = subdominios que comienzan con numero.
        </p>

        <div class="letter-grid" id="letterGrid"></div>

        <div class="letter-actions">
            <button class="btn btn-info" onclick="seleccionarTodos()" style="font-size: 0.8em; padding: 5px 12px;">Todos</button>
            <button class="btn btn-warning" onclick="seleccionarNinguno()" style="font-size: 0.8em; padding: 5px 12px;">Ninguno</button>
        </div>

        <!-- Toggle de exclusiones -->
        <div class="option-row">
            <label class="toggle">
                <input type="checkbox" id="chkExcl" checked>
                <span class="slider"></span>
            </label>
            <label for="chkExcl"><strong>Aplicar exclusiones</strong> (respetar exclusion.txt y lista)</label>
        </div>
        <div class="excl-hint">
            Activado: salta subdominios con <code>exclusion.txt</code> o en la lista de exclusion.<br>
            Desactivado: limpia <strong>todos</strong> los subdominios sin excepcion.
        </div>

        <div style="text-align: center; margin: 20px 0;">
            <button id="btnEscanear" class="btn btn-primary" onclick="escanear()" disabled style="font-size: 1em; padding: 10px 25px;">
                ESCANEAR SISTEMAS
            </button>
        </div>

        <!-- Resultado del escaneo -->
        <div id="scanResult" style="display: none;"></div>

        <!-- Boton de iniciar limpieza -->
        <div id="startSection" style="display: none; text-align: center; margin: 20px 0;">
            <div class="warning-box" style="text-align: left; margin-bottom: 15px;">
                <strong style="color: #ff6b6b;">ADVERTENCIA:</strong> Esta operacion ELIMINA archivos de forma permanente.
                Se protegen: data/media/, data/pages/, data/meta/, web/, archivos .comments,
                conf/local.php, conf/acl.auth.php, conf/users.auth.php, imagenes de templates 20cones/21cones,
                y archivos criticos de plugins bez/niccproyectos.
            </div>
            <button id="btnIniciar" class="btn btn-start" onclick="iniciarLimpieza()">
                INICIAR LIMPIEZA
            </button>
        </div>

        <div style="margin-top: 15px;">
            <a href="estado.php" class="btn btn-info" style="font-size: 0.8em;">Ver Estado</a>
            <button class="btn btn-warning" onclick="resetProgreso()" style="font-size: 0.8em;">Reiniciar Progreso</button>
            <button class="btn btn-success" onclick="ejecutarDiagnostico()" style="font-size: 0.8em;">Diagnostico</button>
        </div>
        <div id="diagResult" style="display:none; margin-top:10px;"></div>
    </div>

    <!-- ============================================================ -->
    <!-- SECCION 3: PROCESAMIENTO                                      -->
    <!-- ============================================================ -->
    <div id="secProceso" class="section">
        <h2>Limpieza en Progreso</h2>

        <div class="progress-bar">
            <div id="progressFill" class="progress-fill fill-danger" style="width: 0%">0%</div>
        </div>

        <div id="currentSystem" class="site-current" style="display: none;">
            <span class="site-name processing" id="currentName">...</span>
        </div>

        <div class="stats">
            <div class="stat-box">
                <div class="stat-value val-blue" id="statTotal">0</div>
                <div class="stat-label">Total sistemas</div>
            </div>
            <div class="stat-box">
                <div class="stat-value val-green" id="statProcesados">0</div>
                <div class="stat-label">Procesados</div>
            </div>
            <div class="stat-box">
                <div class="stat-value val-red" id="statEliminados">0</div>
                <div class="stat-label">Archivos eliminados</div>
            </div>
            <div class="stat-box">
                <div class="stat-value val-green" id="statProtegidos">0</div>
                <div class="stat-label">Protegidos</div>
            </div>
            <div class="stat-box">
                <div class="stat-value val-purple" id="statSymlinks">0</div>
                <div class="stat-label">Symlinks</div>
            </div>
            <div class="stat-box">
                <div class="stat-value val-orange" id="statErrores">0</div>
                <div class="stat-label">Errores</div>
            </div>
        </div>

        <h2>Log de Operaciones</h2>
        <div id="logBox" class="log-box"></div>

        <div style="margin-top: 15px; text-align: center;">
            <button id="btnCancelar" class="btn btn-danger" onclick="cancelar()">CANCELAR</button>
        </div>
    </div>

    <!-- ============================================================ -->
    <!-- SECCION 4: COMPLETADO                                         -->
    <!-- ============================================================ -->
    <div id="secCompleto" class="section">
        <div id="resumenBox" class="complete">
            <h2 id="resumenTitulo">LIMPIEZA COMPLETADA</h2>
            <p id="resumenTexto"></p>
        </div>

        <div class="stats" id="resumenStats"></div>

        <h2>Log Final</h2>
        <div id="logFinal" class="log-box"></div>

        <div style="margin-top: 15px; text-align: center;">
            <a href="estado.php" class="btn btn-info">Ver Estado Detallado</a>
            <button class="btn btn-primary" onclick="nuevaLimpieza()">Nueva Limpieza</button>
        </div>
    </div>
</div>

<!-- ============================================================ -->
<!-- JAVASCRIPT                                                     -->
<!-- ============================================================ -->
<script>
// ---- Estado global ----
let token = '';
let sistemas = [];
let currentIndex = 0;
let cancelled = false;
let totalStats = { eliminados: 0, protegidos: 0, symlinks: 0, errores: 0 };
const letrasSeleccionadas = new Set();

// ---- Escapar HTML para prevenir XSS ----
function escapeHtml(text) {
    const div = document.createElement('div');
    div.appendChild(document.createTextNode(text));
    return div.innerHTML;
}

// ---- Inicializacion ----
(function init() {
    const grid = document.getElementById('letterGrid');
    const letras = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#'.split('');
    letras.forEach(function(letra) {
        const btn = document.createElement('button');
        btn.className = 'btn-letter';
        btn.textContent = letra;
        btn.setAttribute('data-letra', letra);
        btn.onclick = function() { toggleLetra(letra, btn); };
        grid.appendChild(btn);
    });
})();

// ---- Funciones de seccion ----
function showSection(id) {
    document.querySelectorAll('.section').forEach(function(s) { s.classList.remove('active'); });
    document.getElementById(id).classList.add('active');
}

// ---- Token ----
function verificarToken() {
    const input = document.getElementById('inputToken');
    token = input.value.trim();
    if (!token) return;

    // Validar token haciendo un escaneo con letras vacias
    const fd = new FormData();
    fd.append('accion', 'escanear');
    fd.append('token', token);

    fetch('limpiar.php', { method: 'POST', body: fd })
        .then(function(r) {
            if (r.status === 403) throw new Error('Token invalido');
            return r.json();
        })
        .then(function(data) {
            if (data.error && data.error.indexOf('Token') !== -1) {
                throw new Error('Token invalido');
            }
            showSection('secLetras');
        })
        .catch(function() {
            document.getElementById('tokenError').style.display = 'block';
            input.value = '';
            input.focus();
        });
}

// ---- Letras ----
function toggleLetra(letra, btn) {
    if (letrasSeleccionadas.has(letra)) {
        letrasSeleccionadas.delete(letra);
        btn.classList.remove('btn-letter-selected');
    } else {
        letrasSeleccionadas.add(letra);
        btn.classList.add('btn-letter-selected');
    }
    document.getElementById('btnEscanear').disabled = (letrasSeleccionadas.size === 0);
    // Ocultar resultado anterior
    document.getElementById('scanResult').style.display = 'none';
    document.getElementById('startSection').style.display = 'none';
}

function seleccionarTodos() {
    document.querySelectorAll('.btn-letter').forEach(function(btn) {
        const letra = btn.getAttribute('data-letra');
        letrasSeleccionadas.add(letra);
        btn.classList.add('btn-letter-selected');
    });
    document.getElementById('btnEscanear').disabled = false;
}

function seleccionarNinguno() {
    document.querySelectorAll('.btn-letter').forEach(function(btn) {
        letrasSeleccionadas.delete(btn.getAttribute('data-letra'));
        btn.classList.remove('btn-letter-selected');
    });
    document.getElementById('btnEscanear').disabled = true;
    document.getElementById('scanResult').style.display = 'none';
    document.getElementById('startSection').style.display = 'none';
}

// ---- Escaneo ----
function escanear() {
    const btn = document.getElementById('btnEscanear');
    btn.disabled = true;
    btn.textContent = 'Escaneando...';

    const fd = new FormData();
    fd.append('accion', 'escanear');
    fd.append('token', token);
    fd.append('usarExclusiones', document.getElementById('chkExcl').checked ? '1' : '0');
    letrasSeleccionadas.forEach(function(l) { fd.append('letras[]', l); });

    fetch('limpiar.php', { method: 'POST', body: fd })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            btn.disabled = false;
            btn.textContent = 'ESCANEAR SISTEMAS';

            if (data.error) {
                alert('Error: ' + data.error);
                return;
            }

            sistemas = data.sistemas;
            const totalSistemas = data.total;
            const excluidos = data.excluidos;

            // Mostrar resultado
            let html = '<div class="scan-result">';
            html += '<h2 style="margin-top:0;">Resultado del Escaneo</h2>';
            html += '<p><strong style="color:#74b9ff;">' + totalSistemas + '</strong> sistemas encontrados para limpieza';
            if (excluidos > 0) {
                html += ' (<strong style="color:#ffd93d;">' + excluidos + '</strong> excluidos)';
            }
            html += '</p>';

            if (totalSistemas > 0) {
                // Agrupar por letra
                const grouped = {};
                sistemas.forEach(function(s) {
                    if (!grouped[s.letra]) grouped[s.letra] = [];
                    grouped[s.letra].push(s.nombre);
                });

                html += '<div class="scan-list">';
                Object.keys(grouped).sort().forEach(function(letra) {
                    grouped[letra].forEach(function(nombre) {
                        html += '<div class="scan-item"><span class="letra-tag">' + escapeHtml(letra) + '</span> ' + escapeHtml(nombre) + '</div>';
                    });
                });
                html += '</div>';
            }

            // Excluidos
            if (data.excluidos_lista && data.excluidos_lista.length > 0) {
                html += '<details style="margin-top:10px;"><summary style="color:#ffd93d;cursor:pointer;">Ver excluidos (' + excluidos + ')</summary>';
                html += '<div class="scan-list" style="max-height:150px;">';
                data.excluidos_lista.forEach(function(e) {
                    html += '<div class="scan-item" style="color:#ffd93d;">' + escapeHtml(e.nombre) + ' (' + escapeHtml(e.motivo) + ')</div>';
                });
                html += '</div></details>';
            }

            html += '</div>';
            document.getElementById('scanResult').innerHTML = html;
            document.getElementById('scanResult').style.display = 'block';

            // Mostrar boton de iniciar si hay sistemas
            if (totalSistemas > 0) {
                document.getElementById('startSection').style.display = 'block';
            } else {
                document.getElementById('startSection').style.display = 'none';
            }
        })
        .catch(function(e) {
            btn.disabled = false;
            btn.textContent = 'ESCANEAR SISTEMAS';
            alert('Error de conexion: ' + e.message);
        });
}

// ---- Limpieza ----
async function iniciarLimpieza() {
    // Doble confirmacion (Riesgo 4)
    if (!confirm('ATENCION: Se van a ELIMINAR archivos de ' + sistemas.length + ' sistemas.\n\nEsta accion NO se puede deshacer.\n\nContinuar?')) {
        return;
    }

    const typed = prompt('SEGUNDA CONFIRMACION\n\nEscriba LIMPIAR para continuar:');
    if (typed !== 'LIMPIAR') {
        alert('Limpieza cancelada. Debe escribir exactamente "LIMPIAR".');
        return;
    }

    // Iniciar
    cancelled = false;
    currentIndex = 0;
    totalStats = { eliminados: 0, protegidos: 0, symlinks: 0, errores: 0 };

    showSection('secProceso');
    document.getElementById('statTotal').textContent = sistemas.length;
    document.getElementById('logBox').innerHTML = '';
    addLog('Iniciando limpieza de ' + sistemas.length + ' sistemas...', 'info');
    addLog('Letras seleccionadas: ' + Array.from(letrasSeleccionadas).sort().join(', '), 'info');
    addLog('Exclusiones: ' + (document.getElementById('chkExcl').checked ? 'SI (activadas)' : 'NO (desactivadas - TODOS los sistemas)'), 'info');
    addLog('---', 'info');

    for (let i = 0; i < sistemas.length; i++) {
        if (cancelled) {
            addLog('CANCELADO por el usuario en sistema #' + (i + 1), 'skip');
            break;
        }

        currentIndex = i;
        const sistema = sistemas[i];

        // Actualizar UI
        updateProgress(i, sistemas.length);
        document.getElementById('currentSystem').style.display = 'block';
        document.getElementById('currentName').textContent = 'Procesando: ' + sistema.nombre + '...';

        try {
            const fd = new FormData();
            fd.append('accion', 'limpiar');
            fd.append('token', token);
            fd.append('sitePath', sistema.path);

            const resp = await fetch('limpiar.php', { method: 'POST', body: fd });
            const data = await resp.json();

            if (data.error) {
                addLog('ERROR: ' + sistema.nombre + ' - ' + data.error, 'err');
                totalStats.errores++;
            } else {
                totalStats.eliminados += data.stats.eliminados;
                totalStats.protegidos += data.stats.protegidos;
                totalStats.symlinks   += data.stats.symlinks;
                totalStats.errores    += data.stats.errores;

                let logClass = data.stats.errores > 0 ? 'skip' : 'ok';
                addLog(
                    (logClass === 'ok' ? 'OK' : 'PARCIAL') + ': ' + sistema.nombre +
                    ' | -' + data.stats.eliminados +
                    ' prot:' + data.stats.protegidos +
                    (data.stats.symlinks > 0 ? ' sym:' + data.stats.symlinks : '') +
                    (data.stats.errores > 0 ? ' err:' + data.stats.errores : '') +
                    ' (' + data.tiempo + 's)',
                    logClass
                );

                // Mostrar TODOS los detalles para diagnostico
                if (data.stats.detalles) {
                    data.stats.detalles.forEach(function(d) {
                        if (d.indexOf('ERROR') !== -1) {
                            addLog('  ' + d, 'err');
                        } else if (d.indexOf('SYMLINK') !== -1 || d.indexOf('BOUNDARY') !== -1) {
                            addLog('  ' + d, 'sym');
                        } else if (d.indexOf('===') !== -1) {
                            addLog('  ' + d, 'info');
                        } else if (d.indexOf('F3-') !== -1 || d.indexOf('F3-SKIP') !== -1) {
                            addLog('  ' + d, d.indexOf('SKIP') !== -1 ? 'skip' : 'ok');
                        } else {
                            addLog('  ' + d, 'info');
                        }
                    });
                }
            }
        } catch(e) {
            addLog('FETCH ERROR: ' + sistema.nombre + ' - ' + e.message, 'err');
            totalStats.errores++;
        }

        // Actualizar contadores
        document.getElementById('statProcesados').textContent = i + 1;
        document.getElementById('statEliminados').textContent = totalStats.eliminados.toLocaleString();
        document.getElementById('statProtegidos').textContent = totalStats.protegidos.toLocaleString();
        document.getElementById('statSymlinks').textContent = totalStats.symlinks;
        document.getElementById('statErrores').textContent = totalStats.errores;
    }

    // Completar
    updateProgress(sistemas.length, sistemas.length);
    document.getElementById('currentSystem').style.display = 'none';
    document.getElementById('btnCancelar').disabled = true;

    // Mostrar resumen
    showResumen(cancelled);
}

function cancelar() {
    if (confirm('Cancelar la limpieza? El sistema actual terminara de procesarse.')) {
        cancelled = true;
        addLog('Cancelacion solicitada...', 'skip');
        document.getElementById('btnCancelar').disabled = true;
    }
}

// ---- UI helpers ----
function updateProgress(current, total) {
    const pct = total > 0 ? Math.round((current / total) * 100) : 0;
    const fill = document.getElementById('progressFill');
    fill.style.width = pct + '%';
    fill.textContent = pct + '% (' + current + '/' + total + ')';
}

function addLog(message, type) {
    const box = document.getElementById('logBox');
    const div = document.createElement('div');
    div.className = 'log-' + (type || 'info');
    div.textContent = message;
    box.appendChild(div);
    box.scrollTop = box.scrollHeight;
}

function showResumen(wasCancelled) {
    // Copiar log
    document.getElementById('logFinal').innerHTML = document.getElementById('logBox').innerHTML;

    // Titulo
    const box = document.getElementById('resumenBox');
    const titulo = document.getElementById('resumenTitulo');
    const texto = document.getElementById('resumenTexto');

    if (wasCancelled) {
        box.className = 'complete cancelled';
        titulo.textContent = 'LIMPIEZA CANCELADA';
        texto.textContent = 'Se procesaron ' + currentIndex + ' de ' + sistemas.length + ' sistemas antes de cancelar.';
    } else if (totalStats.errores > 0) {
        box.className = 'complete';
        box.style.borderColor = '#ffd93d';
        box.style.background = '#3d3d00';
        titulo.style.color = '#ffd93d';
        titulo.textContent = 'LIMPIEZA COMPLETADA CON ADVERTENCIAS';
        texto.textContent = 'Se procesaron ' + sistemas.length + ' sistemas. ' + totalStats.errores + ' errores encontrados.';
    } else {
        box.className = 'complete';
        titulo.textContent = 'LIMPIEZA COMPLETADA';
        texto.textContent = 'Se procesaron exitosamente ' + sistemas.length + ' sistemas.';
    }

    // Stats del resumen
    const statsHtml = '<div class="stats">' +
        '<div class="stat-box"><div class="stat-value val-blue">' + sistemas.length + '</div><div class="stat-label">Sistemas procesados</div></div>' +
        '<div class="stat-box"><div class="stat-value val-red">' + totalStats.eliminados.toLocaleString() + '</div><div class="stat-label">Archivos eliminados</div></div>' +
        '<div class="stat-box"><div class="stat-value val-green">' + totalStats.protegidos.toLocaleString() + '</div><div class="stat-label">Archivos protegidos</div></div>' +
        '<div class="stat-box"><div class="stat-value val-purple">' + totalStats.symlinks + '</div><div class="stat-label">Symlinks detectados</div></div>' +
        '<div class="stat-box"><div class="stat-value val-orange">' + totalStats.errores + '</div><div class="stat-label">Errores</div></div>' +
        '</div>';
    document.getElementById('resumenStats').innerHTML = statsHtml;

    showSection('secCompleto');
}

function nuevaLimpieza() {
    sistemas = [];
    currentIndex = 0;
    cancelled = false;
    totalStats = { eliminados: 0, protegidos: 0, symlinks: 0, errores: 0 };
    document.getElementById('btnCancelar').disabled = false;
    document.getElementById('scanResult').style.display = 'none';
    document.getElementById('startSection').style.display = 'none';
    showSection('secLetras');
}

function ejecutarDiagnostico() {
    if (sistemas.length === 0) {
        alert('Primero escanee los sistemas para poder diagnosticar');
        return;
    }
    const sitePath = sistemas[0].path;
    const fd = new FormData();
    fd.append('accion', 'diagnostico');
    fd.append('token', token);
    fd.append('sitePath', sitePath);

    fetch('limpiar.php', { method: 'POST', body: fd })
        .then(function(r) { return r.text(); })
        .then(function(text) {
            let html = '<div class="log-box" style="max-height:400px;"><pre style="color:#00b894;white-space:pre-wrap;word-break:break-all;">';
            try {
                const data = JSON.parse(text);
                html += escapeHtml(JSON.stringify(data, null, 2));
            } catch(e) {
                html += '<span style="color:#ff6b6b;">RESPUESTA NO-JSON (posible error PHP):</span>\n' + escapeHtml(text);
            }
            html += '</pre></div>';
            document.getElementById('diagResult').innerHTML = html;
            document.getElementById('diagResult').style.display = 'block';
        })
        .catch(function(e) {
            document.getElementById('diagResult').innerHTML = '<div class="error-box">Error: ' + escapeHtml(e.message) + '</div>';
            document.getElementById('diagResult').style.display = 'block';
        });
}

function resetProgreso() {
    if (!confirm('Reiniciar el progreso? Se borra el historial de limpiezas anteriores.')) return;

    const fd = new FormData();
    fd.append('accion', 'reset');
    fd.append('token', token);

    fetch('limpiar.php', { method: 'POST', body: fd })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (data.ok) alert('Progreso reiniciado');
            else alert('Error: ' + (data.error || 'desconocido'));
        })
        .catch(function(e) { alert('Error: ' + e.message); });
}
</script>
</body>
</html>
