<?php

use dokuwiki\Extension\AdminPlugin;

/**
 * DokuWiki Plugin siedeployer (Admin Component)
 *
 * Interfaz web para aprovisionamiento de nuevos sistemas SIE.
 * Muestra lista de despliegues y 16 pasos independientes en accordion.
 *
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
 * @author  SIE Systems <soporte@systemsie.site>
 */
class admin_plugin_siedeployer extends AdminPlugin
{
    /** @var helper_plugin_siedeployer */
    protected $helper;

    /** @var string */
    protected $message = '';

    /** @var string */
    protected $messageType = '';

    public function __construct()
    {
        $this->helper = plugin_load('helper', 'siedeployer');
    }

    public function getMenuText($language)
    {
        return 'SIE Deployer';
    }

    public function getMenuSort()
    {
        return 50;
    }

    public function getMenuIcon()
    {
        return DOKU_INC . 'lib/images/menu/settings.svg';
    }

    public function forAdminOnly()
    {
        return true;
    }

    public function handle()
    {
        global $INPUT, $ID;

        if (!auth_isadmin()) {
            $this->message = 'No tiene permisos para acceder a esta herramienta.';
            $this->messageType = 'error';
            return;
        }

        // Crear despliegue via POST normal (no AJAX)
        if ($INPUT->post->has('create_deployment') && checkSecurityToken()) {
            $name = trim($INPUT->post->str('deploy_name'));
            $sourcePath = trim($INPUT->post->str('deploy_source'));
            $targetPath = trim($INPUT->post->str('deploy_target'));
            $subdomain = trim($INPUT->post->str('deploy_subdomain'));

            if (empty($name) || empty($sourcePath) || empty($targetPath)) {
                $this->message = 'Nombre, ruta origen y ruta destino son obligatorios.';
                $this->messageType = 'error';
                return;
            }

            $mode = trim($INPUT->post->str('deploy_mode', 'new'));
            $domain = trim($INPUT->post->str('deploy_domain', ''));
            $user = $_SERVER['REMOTE_USER'] ?? 'unknown';
            $result = $this->helper->createDeployment($name, $sourcePath, $targetPath, $subdomain, $user, $mode, $domain);

            if (is_array($result) && isset($result['error'])) {
                $this->message = $result['error'];
                $this->messageType = 'error';
            } elseif ($result) {
                // Redirigir automaticamente al dashboard de 16 pasos
                $url = wl($ID, ['do' => 'admin', 'page' => 'siedeployer', 'deployment_id' => $result, 'migkey' => $INPUT->str('migkey', '')], true, '&');
                send_redirect($url);
                return;
            } else {
                $this->message = 'Error al crear el despliegue.';
                $this->messageType = 'error';
            }
        }
    }

    public function html()
    {
        global $INPUT, $ID;

        echo '<div id="siedeployer_admin">';

        if (!auth_isadmin()) {
            echo '<div class="sd-msg sd-error"><p>No tiene permisos para acceder a esta herramienta.</p></div>';
            echo '</div>';
            return;
        }

        // Verificar clave de acceso interna
        $migkey = $INPUT->str('migkey', '');
        if ($migkey !== self::$migrationKey) {
            echo '<div class="sd-card" style="max-width:420px;margin:40px auto;">';
            echo '<h2 style="margin-top:0;">SIE Deployer</h2>';
            echo '<p style="color:#475569;">Ingrese la clave de acceso para continuar.</p>';
            if ($migkey !== '') {
                echo '<p style="color:#dc2626;font-weight:600;">Clave incorrecta.</p>';
            }
            echo '<form method="get" action="' . wl($ID) . '">';
            echo '<input type="hidden" name="do" value="admin" />';
            echo '<input type="hidden" name="page" value="siedeployer" />';
            echo '<div style="display:flex;gap:8px;align-items:center;">';
            echo '<input type="password" name="migkey" placeholder="Clave de acceso" style="padding:8px 12px;border:1px solid #cbd5e1;border-radius:6px;font-size:1em;flex:1;" autocomplete="off" autofocus />';
            echo '<button type="submit" class="sd-btn sd-btn-primary">Ingresar</button>';
            echo '</div>';
            echo '</form>';
            echo '</div>';
            echo '</div>';
            return;
        }

        // Token de seguridad global (para AJAX en cualquier vista)
        echo '<input type="hidden" id="sd_sectok_global" value="' . getSecurityToken() . '" />';

        // Mensaje de estado
        if ($this->message) {
            $cls = $this->messageType === 'success' ? 'sd-success' : 'sd-error';
            echo '<div class="sd-msg ' . $cls . '"><p>' . hsc($this->message) . '</p></div>';
        }

        $deploymentId = $INPUT->int('deployment_id', 0);
        $view = $INPUT->str('view', '');

        if ($deploymentId > 0) {
            $dep = $this->helper->getDeployment($deploymentId);
            if ($dep) {
                $this->htmlStepsDashboard($dep);
            } else {
                echo '<div class="sd-msg sd-error"><p>Despliegue no encontrado.</p></div>';
                $this->htmlDeploymentList();
            }
        } elseif ($view === 'migration') {
            $this->htmlMigrationView();
        } elseif ($view === 'updates') {
            $this->htmlUpdatesView();
        } elseif ($view === 'diagnostics') {
            $this->htmlDiagnosticsView();
        } else {
            $this->htmlDeploymentList();
        }

        $this->printScripts();
        echo '</div>';
    }

    // =========================================================================
    //  VISTA: LISTA DE DESPLIEGUES
    // =========================================================================

    protected function htmlDeploymentList()
    {
        global $ID, $INPUT;

        echo '<h1>SIE Deployer - Aprovisionamiento de Sistemas</h1>';
        echo '<p class="sd-subtitle">Herramienta para crear nuevos sistemas SIE a partir de uno existente.</p>';

        $this->htmlNavTabs('deployments');

        // Formulario de creacion
        echo '<div class="sd-card sd-card-create">';
        echo '<h2>Nuevo Despliegue</h2>';
        echo '<form method="post" action="' . wl($ID, ['do' => 'admin', 'page' => 'siedeployer', 'migkey' => $INPUT->str('migkey', '')]) . '" onsubmit="return validateCreateForm()">';
        echo '<input type="hidden" name="sectok" value="' . getSecurityToken() . '" />';
        echo '<input type="hidden" name="create_deployment" value="1" />';
        echo '<input type="hidden" id="deploy_mode" name="deploy_mode" value="new" />';

        // Selector de modo
        echo '<div class="sd-field" style="margin-bottom:16px;">';
        echo '<label style="font-weight:600;font-size:1.05em;">Tipo de operacion</label>';
        echo '<div style="display:flex;gap:20px;margin-top:8px;">';
        echo '<label class="sd-check" style="font-weight:normal;font-size:1em;">';
        echo '<input type="radio" name="deploy_mode_radio" value="new" checked onchange="toggleDeployMode()" /> Nuevo Sistema';
        echo '</label>';
        echo '<label class="sd-check" style="font-weight:normal;font-size:1em;">';
        echo '<input type="radio" name="deploy_mode_radio" value="modify" onchange="toggleDeployMode()" /> Modificar Sistema Existente';
        echo '</label>';
        echo '</div>';
        echo '</div>';

        // Advertencia modo Modificar
        echo '<div id="deploy_modify_warning" style="display:none;background:#fef3c7;border:1px solid #f59e0b;border-radius:8px;padding:14px;margin-bottom:16px;">';
        echo '<p style="color:#92400e;font-weight:700;margin:0 0 6px;font-size:1em;">&#9888; Advertencia - Modo Modificar Sistema Existente</p>';
        echo '<p style="color:#92400e;margin:0;font-size:0.9em;">Los cambios se aplicaran directamente sobre el sistema seleccionado. No se realizara duplicacion. Asegurese de tener una copia de seguridad antes de continuar.</p>';
        echo '</div>';

        echo '<div class="sd-form-grid">';

        echo '<div class="sd-field">';
        echo '<label for="deploy_name">Razon Social</label>';
        echo '<input type="text" id="deploy_name" name="deploy_name" placeholder="ej: ACME S.A.S - Sistema de Gestion de Calidad" required />';
        echo '<p id="deploy_name_hint" style="display:none;margin:4px 0 0;font-size:0.82em;color:#64748b;">Se detectara automaticamente al seleccionar un sistema</p>';
        echo '</div>';

        echo '<div class="sd-field">';
        echo '<label for="deploy_source" id="deploy_source_label">Ruta origen (sistema a duplicar)</label>';
        echo '<div class="sd-input-group">';
        echo '<input type="text" id="deploy_source" name="deploy_source" placeholder="ej: /public_html/prueba" required />';
        echo '<button type="button" class="sd-btn sd-btn-sm" onclick="scanServer()">Escanear</button>';
        echo '</div>';
        echo '<p style="margin:4px 0 0;font-size:0.82em;color:#64748b;">Ruta base detectada: <code>' . hsc($this->helper->getBasePath()) . '</code></p>';
        echo '<div id="scan_results" class="sd-scan-results" style="display:none;"></div>';
        echo '</div>';

        // Subdominio + selector de dominio: solo visible en modo Nuevo
        echo '<div class="sd-field" id="deploy_subdomain_field">';
        echo '<label for="deploy_subdomain">Subdominio</label>';
        echo '<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">';
        echo '<input type="text" id="deploy_subdomain" name="deploy_subdomain" placeholder="ej: acme" required style="flex:1;min-width:120px;" />';
        echo '<span style="color:#64748b;font-weight:600;">.</span>';
        echo '<select id="deploy_domain" name="deploy_domain" style="padding:8px 10px;border:1px solid #cbd5e1;border-radius:8px;font-size:0.95em;" onchange="updateTargetFromSubdomain()">';
        $availDomains = helper_plugin_siedeployer::$availableDomains;
        $defaultDomain = helper_plugin_siedeployer::$rootDomain;
        foreach ($availDomains as $dom) {
            $sel = ($dom === $defaultDomain) ? ' selected' : '';
            echo '<option value="' . hsc($dom) . '"' . $sel . '>' . hsc($dom) . '</option>';
        }
        echo '</select>';
        echo '</div>';
        echo '<p style="margin:4px 0 0;font-size:0.82em;color:#64748b;">Se usara como: <code><span id="subdomain_preview">acme</span>.<span id="domain_preview">' . hsc($defaultDomain) . '</span></code></p>';
        echo '</div>';

        // Ruta destino: solo visible en modo Nuevo
        echo '<div class="sd-field" id="deploy_target_field">';
        echo '<label>Ruta destino (nuevo sistema)</label>';
        echo '<label class="sd-check" style="color:#334155;font-weight:normal;">';
        echo '<input type="checkbox" id="deploy_target_auto" checked onchange="toggleTargetAuto()" />';
        echo '<span id="deploy_target_label">' . hsc($this->helper->getBasePath()) . '<strong id="deploy_target_name_display">...</strong></span>';
        echo '</label>';
        echo '<input type="hidden" id="deploy_target" name="deploy_target" value="" />';
        echo '<p style="margin:4px 0 0;font-size:0.82em;color:#64748b;">Generado desde el campo <strong>Subdominio</strong></p>';
        echo '</div>';

        echo '</div>'; // grid

        echo '<button type="submit" id="deploy_submit_btn" class="sd-btn sd-btn-primary">Crear Despliegue</button>';
        echo '</form>';
        echo '</div>';

        // Lista de despliegues existentes
        $deployments = $this->helper->listDeployments();

        if (!empty($deployments)) {
            echo '<div class="sd-card">';
            echo '<h2>Despliegues Existentes</h2>';
            echo '<table class="sd-table">';
            echo '<thead><tr>';
            echo '<th>ID</th><th>Razon Social</th><th>Subdominio</th><th>Modo</th><th>Progreso</th><th>Estado</th><th>Creado</th><th>Acciones</th>';
            echo '</tr></thead><tbody>';

            foreach ($deployments as $dep) {
                $done = intval($dep['steps_done']);
                $total = intval($dep['steps_total']);
                $pct = $total > 0 ? round($done / $total * 100) : 0;
                $statusCls = $dep['status'] === 'completed' ? 'sd-badge-success' : 'sd-badge-progress';

                echo '<tr>';
                echo '<td>' . hsc($dep['id']) . '</td>';
                echo '<td><strong>' . hsc($dep['name']) . '</strong></td>';
                echo '<td>' . hsc($dep['subdomain']) . '</td>';
                $modeLabel = ($dep['mode'] ?? 'new') === 'modify' ? 'Modificar' : 'Nuevo';
                $modeStyle = ($dep['mode'] ?? 'new') === 'modify' ? 'background:#fef3c7;color:#92400e;' : 'background:#d1fae5;color:#065f46;';
                echo '<td><span class="sd-badge" style="' . $modeStyle . '">' . $modeLabel . '</span></td>';
                echo '<td>';
                echo '<div class="sd-progress"><div class="sd-progress-bar" style="width:' . $pct . '%"></div></div>';
                echo '<span class="sd-progress-text">' . $done . '/' . $total . '</span>';
                echo '</td>';
                echo '<td><span class="sd-badge ' . $statusCls . '">' . hsc($dep['status']) . '</span></td>';
                echo '<td>' . hsc($dep['created_at']) . '</td>';
                echo '<td style="white-space:nowrap;">';
                echo '<a href="' . wl($ID, ['do' => 'admin', 'page' => 'siedeployer', 'deployment_id' => $dep['id'], 'migkey' => $INPUT->str('migkey', '')]) . '" class="sd-btn sd-btn-sm">Gestionar</a> ';
                echo '<button type="button" class="sd-btn sd-btn-sm" style="color:#dc2626;border-color:#dc2626;" onclick="deleteDeployment(' . intval($dep['id']) . ', \'' . addslashes(hsc($dep['name'])) . '\')">Eliminar</button>';
                echo '</td>';
                echo '</tr>';
            }

            echo '</tbody></table>';
            echo '</div>';
        }
    }

    // =========================================================================
    //  NAVEGACION POR TABS
    // =========================================================================

    protected function htmlNavTabs($activeTab = 'deployments')
    {
        global $ID, $INPUT;

        $key = $INPUT->str('migkey', '');
        echo '<div class="sd-tabs">';

        $tabs = [
            'deployments' => ['label' => 'Despliegues', 'url' => wl($ID, ['do' => 'admin', 'page' => 'siedeployer', 'migkey' => $key])],
            'migration'   => ['label' => 'Migracion de Dominios', 'url' => wl($ID, ['do' => 'admin', 'page' => 'siedeployer', 'view' => 'migration', 'migkey' => $key])],
            'updates'     => ['label' => 'Actualizaciones', 'url' => wl($ID, ['do' => 'admin', 'page' => 'siedeployer', 'view' => 'updates', 'migkey' => $key])],
            'diagnostics' => ['label' => 'Test de Control', 'url' => wl($ID, ['do' => 'admin', 'page' => 'siedeployer', 'view' => 'diagnostics', 'migkey' => $key])],
        ];

        foreach ($tabs as $tabKey => $tab) {
            $cls = $tabKey === $activeTab ? 'sd-tab sd-tab-active' : 'sd-tab';
            echo '<a href="' . $tab['url'] . '" class="' . $cls . '">' . $tab['label'] . '</a>';
        }

        echo '</div>';
    }

    // =========================================================================
    //  VISTA: MIGRACION DE DOMINIOS
    // =========================================================================

    /** @var string Clave de acceso para el plugin */
    private static $migrationKey = 'nicc2026migrar';

    protected function htmlMigrationView()
    {
        global $ID;

        echo '<h1>SIE Deployer - Migracion de Dominios</h1>';
        echo '<p class="sd-subtitle">Migre subdominios entre dominios manteniendo la misma carpeta y datos del sistema.</p>';

        $this->htmlNavTabs('migration');

        // --- Card: Escaneo y configuracion ---
        echo '<div class="sd-card sd-card-create">';
        echo '<h2>Escaner de Subdominios</h2>';

        // Info sobre dominios disponibles
        echo '<div style="background:#eff6ff;border:1px solid #3b82f6;border-radius:8px;padding:14px;margin-bottom:16px;">';
        echo '<p style="color:#1e40af;font-weight:700;margin:0 0 6px;">Dominios disponibles para migracion</p>';
        echo '<div style="display:flex;flex-wrap:wrap;gap:8px;">';
        foreach (helper_plugin_siedeployer::$availableDomains as $d) {
            echo '<code style="background:#dbeafe;padding:4px 10px;border-radius:4px;color:#1e40af;">' . hsc($d) . '</code>';
        }
        echo '</div>';
        echo '</div>';

        echo '<div style="display:flex;gap:8px;flex-wrap:wrap;">';
        echo '<button class="sd-btn sd-btn-primary" onclick="migrationScan()">Escanear Todos los Subdominios</button>';
        echo '<button class="sd-btn sd-btn-sm sd-btn-outline" onclick="migrationCheckDomains()">Verificar Dominios Configurados</button>';
        echo '</div>';

        // Area de estado de dominios
        echo '<div id="migration_domain_status" style="margin-top:12px;"></div>';

        // Area de resultados del escaner
        echo '<div id="migration_results" style="margin-top:16px;"></div>';
        echo '</div>';

        // --- Card: Fase 1 - Crear nuevos ---
        echo '<div class="sd-card" id="migration_phase1_card" style="display:none;border-left:4px solid #16a34a;">';
        echo '<h2 style="color:#16a34a;">Fase 1: Crear Subdominios en Nuevo Dominio</h2>';
        echo '<p style="color:#475569;font-size:0.9em;">Se crearan los subdominios bajo el dominio destino seleccionado, apuntando a la misma carpeta del sistema. Tambien se actualizara el <code>baseurl</code> en <code>conf/local.php</code> de cada sistema SIE.</p>';
        echo '<div id="migration_phase1_log" style="margin:12px 0;"></div>';
        echo '<button class="sd-btn sd-btn-primary" onclick="migrationPhase1()" style="background:#16a34a;">Ejecutar Fase 1: Crear Subdominios</button>';
        echo '</div>';

        // --- Card: Fase 2 - Eliminar antiguos ---
        echo '<div class="sd-card" id="migration_phase2_card" style="display:none;border-left:4px solid #dc2626;">';
        echo '<h2 style="color:#dc2626;">Fase 2: Eliminar Subdominios Antiguos</h2>';
        echo '<div class="sd-warn">Esta accion eliminara los subdominios antiguos de cPanel. La carpeta y datos del sistema NO se eliminan. Asegurese de que los nuevos subdominios funcionen correctamente antes de continuar.</div>';
        echo '<label class="sd-check"><input type="checkbox" id="migration_phase2_confirm" /> Confirmo que he verificado que los nuevos subdominios funcionan correctamente</label>';
        echo '<div id="migration_phase2_log" style="margin:12px 0;"></div>';
        echo '<button class="sd-btn sd-btn-danger" onclick="migrationPhase2()">Ejecutar Fase 2: Eliminar Subdominios Antiguos</button>';
        echo '</div>';
    }

    // =========================================================================
    //  VISTA: ACTUALIZACIONES
    // =========================================================================

    protected function htmlUpdatesView()
    {
        echo '<h1>SIE Deployer - Actualizaciones</h1>';
        echo '<p class="sd-subtitle">Parches masivos y actualizacion del sistema base.</p>';

        $this->htmlNavTabs('updates');

        // =============================================
        // SECCION A: PARCHES MASIVOS
        // =============================================
        echo '<h2 style="margin-top:24px;">Parches Masivos</h2>';
        echo '<p style="color:#475569;margin-bottom:16px;">Ejecute parches en todos los subdominios del servidor.</p>';

        echo '<div class="sd-updates-grid">';

        // --- Card 1: Limpieza ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #ef4444;">';
        echo '<h3>&#128465; Limpieza</h3></div>';
        echo '<p>Eliminar archivos obsoletos y temporales de todos los subdominios.</p>';
        echo '<div id="patch_log_limpieza" class="sd-patch-log" style="display:none;"></div>';
        echo '<button class="sd-btn sd-btn-primary sd-btn-sm" onclick="runPatch(\'limpieza\')">Ejecutar Limpieza</button>';
        echo '</div>';

        // --- Card 2: Parche Masivo ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #3b82f6;">';
        echo '<h3>&#128230; Parche Masivo</h3></div>';
        echo '<p>Distribuir actualizaciones de archivos a todos los subdominios.</p>';
        echo '<div class="sd-field" style="margin:8px 0;">';
        echo '<label><input type="checkbox" id="patch_masivo_exclude" /> Excluir subdominios especificos</label>';
        echo '<input type="text" id="patch_masivo_exclude_list" placeholder="sub1,sub2,sub3" style="margin-top:4px;width:100%;padding:6px 10px;border:1px solid #cbd5e1;border-radius:4px;display:none;" />';
        echo '</div>';
        echo '<div id="patch_log_parche_masivo" class="sd-patch-log" style="display:none;"></div>';
        echo '<button class="sd-btn sd-btn-primary sd-btn-sm" onclick="runPatch(\'parche_masivo\')">Ejecutar Parche</button>';
        echo '</div>';

        // --- Card 3: Tokens IA ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #8b5cf6;">';
        echo '<h3>&#128273; Tokens IA</h3></div>';
        echo '<p>Generar tokens JWT para todos los subdominios con sistema SIE.</p>';
        echo '<div id="patch_log_tokens" class="sd-patch-log" style="display:none;"></div>';
        echo '<button class="sd-btn sd-btn-primary sd-btn-sm" onclick="runPatch(\'tokens\')">Generar Tokens</button>';
        echo '</div>';

        // --- Card 4: Mantenimiento ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #f59e0b;">';
        echo '<h3>&#128679; Mantenimiento</h3></div>';
        echo '<p>Activar o desactivar modo mantenimiento en todos los subdominios.</p>';
        echo '<div id="patch_log_mantenimiento" class="sd-patch-log" style="display:none;"></div>';
        echo '<div style="display:flex;gap:8px;">';
        echo '<button class="sd-btn sd-btn-primary sd-btn-sm" onclick="runPatch(\'mantenimiento\', {action:\'activar\'})">Activar</button>';
        echo '<button class="sd-btn sd-btn-secondary sd-btn-sm" onclick="runPatch(\'mantenimiento\', {action:\'desactivar\'})">Desactivar</button>';
        echo '</div>';
        echo '</div>';

        // --- Card 5: Backup SIE ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #16a34a;">';
        echo '<h3>&#128190; Backup SIE</h3></div>';
        echo '<p>Crear respaldos de todos los subdominios con sistema SIE.</p>';
        echo '<div id="patch_log_backup" class="sd-patch-log" style="display:none;"></div>';
        echo '<button class="sd-btn sd-btn-primary sd-btn-sm" onclick="runPatch(\'backup\')">Crear Backups</button>';
        echo '</div>';

        // --- Card 6: Ajuste Individual ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #0ea5e9;">';
        echo '<h3>&#9881; Ajuste Individual</h3></div>';
        echo '<p>Actualizar UN subdominio desde la carpeta sistema, protegiendo archivos propios del cliente.</p>';

        // Acceso rapido: sistema prueba
        echo '<div style="margin:8px 0;padding:8px 12px;background:#f0fdf4;border:1px solid #86efac;border-radius:6px;">';
        echo '<label style="cursor:pointer;display:flex;align-items:center;gap:8px;">';
        echo '<input type="checkbox" id="individual_quick_prueba" onchange="individualQuickPrueba(this)" />';
        echo '<span style="font-size:0.88em;"><strong>prueba</strong> <span style="color:#64748b;">(prueba.sistemavirtualdegestion.com)</span></span>';
        echo '</label>';
        echo '</div>';

        // Boton escanear + lista de resultados
        echo '<div style="margin:8px 0;">';
        echo '<button class="sd-btn sd-btn-sm sd-btn-secondary" onclick="individualScan()">Escanear Sistemas</button>';
        echo '</div>';
        echo '<div id="individual_scan_results" style="max-height:200px;overflow-y:auto;display:none;border:1px solid #e2e8f0;border-radius:6px;margin-bottom:8px;"></div>';

        // Sistema seleccionado
        echo '<div id="individual_selected" style="display:none;padding:8px 12px;background:#f0f9ff;border:1px solid #0ea5e9;border-radius:6px;margin-bottom:8px;">';
        echo '<strong>Seleccionado:</strong> <span id="individual_selected_name"></span>';
        echo '<input type="hidden" id="individual_selected_path" />';
        echo '</div>';

        // Log
        echo '<div id="patch_log_individual" class="sd-patch-log" style="display:none;"></div>';

        // Boton aplicar
        echo '<button class="sd-btn sd-btn-primary sd-btn-sm" onclick="individualApply()" id="btn_individual_apply" disabled>Aplicar Ajuste</button>';
        echo '</div>';

        // --- Card 7: Fix Home Button v2.0 ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #ec4899;">';
        echo '<h3>&#127968; Fix Home Button v2.0</h3></div>';
        echo '<p>Corregir el boton casita (home) en topbar.php para que apunte al inicio de DokuWiki.</p>';
        echo '<p style="font-size:0.8em;color:#64748b;">Escanea sistemas, permite probar en uno solo y luego aplicar a todos. Incluye reversion con backup.</p>';
        echo '<div id="patch_log_fix_home" class="sd-patch-log" style="display:none;"></div>';
        echo '<button class="sd-btn sd-btn-primary sd-btn-sm" onclick="runPatch(\'fix_home\')">Abrir Fix Home</button>';
        echo '</div>';

        // --- Card 8: Migracion Masiva Wasabi S3 ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #06b6d4;">';
        echo '<h3>&#9889; Migracion Masiva Wasabi S3</h3></div>';
        echo '<p>Cargar todos los archivos de <code>data/media/</code> de 230+ subdominios a Wasabi S3, verificar integridad y truncar a 0 bytes para liberar espacio.</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>5 fases secuenciales:</strong></p>';
        echo '<ol style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li><strong>Inventario</strong> — Escanea archivos, detecta grandes</li>';
        echo '<li><strong>Upload</strong> — Sube a Wasabi S3 (streaming, resumible)</li>';
        echo '<li><strong>Verificar</strong> — HEAD check integridad</li>';
        echo '<li><strong>Registrar</strong> — Inserta en SQLite de cada subdominio</li>';
        echo '<li><strong>Truncar</strong> — Convierte a 0 bytes (solo si 100% OK)</li>';
        echo '</ol>';
        echo '<p style="margin:0 0 6px;"><strong>Caracteristicas:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li>Probar en un solo subdominio con <code>?sub=prueba</code></li>';
        echo '<li>Procesamiento por lotes (configurable 1-5000)</li>';
        echo '<li>Deteccion y omision de archivos grandes (configurable)</li>';
        echo '<li>Dashboard con progreso por subdominio</li>';
        echo '<li>Resumible — tracking DB SQLite persistente</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Solo lectura hasta fase 5. Truncar requiere 100% verificacion. Credenciales Wasabi persistentes.</p>';
        echo '</div>';

        echo '<a href="' . hsc(DOKU_BASE) . 'lib/plugins/siedeployer/diagnosticos/migracion_masiva_wasabi.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Migracion Wasabi</a>';
        echo '</div>';

        // --- Card: Backup a Wasabi S3 ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #059669;">';
        echo '<h3>&#9729; Backup a Wasabi S3</h3></div>';
        echo '<p>Copia de seguridad completa de todos los wikis al bucket <code>nicc-sistema</code>. Excluye <code>data/media/</code> (ya en nicc-archivos).</p>';
        echo '<p style="font-size:0.8em;color:#64748b;">Sube primero, luego borra obsoletos. Ejecucion manual o programada por cron.</p>';
        echo '<div id="patch_log_backup_wasabi" class="sd-patch-log" style="display:none;"></div>';
        echo '<a href="' . hsc(DOKU_BASE) . 'lib/plugins/siedeployer/patches/backup_wasabi/aplicar.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Backup Wasabi</a>';
        echo '</div>';

        // --- Card: Fix local.php ---
        echo '<div class="sd-update-card">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #d97706;">';
        echo '<h3>&#128736; Fix local.php</h3></div>';
        echo '<p>Corregir <code>basedir</code>, actualizar <code>groq_api_key</code> y regenerar <code>local.php</code> en todos los wikis.</p>';
        echo '<p style="font-size:0.8em;color:#64748b;">Modo Groq Key: solo cambia la API key. Modo Completo: regenera todo el archivo preservando datos unicos.</p>';
        echo '<div id="patch_log_fix_localphp" class="sd-patch-log" style="display:none;"></div>';
        echo '<a href="' . hsc(DOKU_BASE) . 'lib/plugins/siedeployer/patches/fix_localphp/aplicar.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Fix local.php</a>';
        echo '</div>';

        echo '</div>'; // .sd-updates-grid

        // =============================================
        // SECCION B: ACTUALIZAR SISTEMA BASE
        // =============================================
        echo '<h2 style="margin-top:32px;">Actualizar Sistema Base</h2>';
        echo '<p style="color:#475569;margin-bottom:16px;">Gestione el contenido de <code>/public_html/sistema/</code>, la carpeta base que se duplica para nuevos sistemas.</p>';

        echo '<div class="sd-card" style="border-left:4px solid #0ea5e9;">';

        // Botón verificar estado
        echo '<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px;">';
        echo '<button class="sd-btn sd-btn-primary" onclick="updatesCheckSistema()">&#128269; Verificar Estado</button>';
        echo '<button class="sd-btn sd-btn-danger" onclick="updatesCleanSistema()">&#128465; Limpiar Carpeta</button>';
        echo '</div>';

        // Resultado del listado
        echo '<div id="updates_sistema_list" style="margin-bottom:16px;"></div>';

        // Subir ZIP
        echo '<div class="sd-field">';
        echo '<label>Subir ZIP de actualizacion del sistema base</label>';
        echo '<input type="file" id="updates_sistema_zip" accept=".zip" />';
        echo '</div>';
        echo '<button class="sd-btn sd-btn-primary" onclick="updatesUploadSistema()">&#128228; Subir y Descomprimir</button>';

        // Log de resultado de subida
        echo '<div id="updates_sistema_upload_log" style="margin-top:12px;"></div>';

        echo '</div>'; // .sd-card
    }

    // =========================================================================
    //  VISTA: TEST DE CONTROL Y DIAGNOSTICO
    // =========================================================================

    protected function htmlDiagnosticsView()
    {
        echo '<h1>SIE Deployer - Test de Control y Diagnostico</h1>';
        echo '<p class="sd-subtitle">Herramientas de diagnostico para detectar y corregir problemas en los 230+ subdominios SIE.</p>';

        $this->htmlNavTabs('diagnostics');

        // URL base: los scripts estan dentro del plugin, accesibles desde el mismo subdominio
        $diagUrl = DOKU_BASE . 'lib/plugins/siedeployer/diagnosticos';

        // =============================================
        // NOTA GENERAL
        // =============================================
        echo '<div style="background:#eff6ff;border:1px solid #93c5fd;border-radius:10px;padding:18px;margin:20px 0;">';
        echo '<h3 style="color:#1e40af;margin:0 0 8px;">&#128203; Informacion General</h3>';
        echo '<p style="color:#334155;font-size:0.88em;margin:0 0 6px;">Los scripts de diagnostico estan dentro del plugin: <code>lib/plugins/siedeployer/diagnosticos/</code>. Se acceden desde el mismo subdominio donde esta instalado DokuWiki.</p>';
        echo '<p style="color:#334155;font-size:0.88em;margin:0 0 6px;">Todos funcionan en <strong>modo lectura</strong> (solo analizan) excepto <code>corregir_basedir.php</code> que tiene modo de correccion masiva.</p>';
        echo '<p style="color:#334155;font-size:0.88em;margin:0;">Parametro comun: <code>?sub=nombre_subdominio</code> para analizar un sistema individual, sin parametros escanea todos.</p>';
        echo '</div>';

        echo '<div class="sd-updates-grid" style="grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));">';

        // =============================================
        // CARD 1: VERIFICAR ENLACES
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #3b82f6;">';
        echo '<h3>&#128279; 1. Verificar Enlaces</h3></div>';
        echo '<p style="font-weight:600;color:#1e40af;">verificar_enlaces.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Detecta errores en los hipervinculos del boton "ACCESO AL SISTEMA" de las paginas web de cada subdominio.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Que analiza:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li>Archivos <code>web/index.html</code>, <code>web/index.php</code> e <code>index.html</code></li>';
        echo '<li>Todos los <code>href</code> que apunten a <code>doku.php</code></li>';
        echo '<li>Compara el subdominio del href vs el nombre real de la carpeta</li>';
        echo '<li>Compara el dominio del href vs los dominios validos del sistema</li>';
        echo '<li>Tambien revisa <code>conf/local.php</code> y <code>conf/local_act.php</code> para cruces de baseurl</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Errores que detecta:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><span style="color:#dc2626;font-weight:600;">ERROR_SUBDOMINIO</span> &mdash; El href apunta a otro subdominio (ej: carpeta "audicon" pero href dice "rhcadvisers")</li>';
        echo '<li><span style="color:#dc2626;font-weight:600;">ERROR_DOMINIO</span> &mdash; El href usa un dominio incorrecto (ej: usa systemsie.site cuando deberia ser sistemavirtualdegestion.com)</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Resultado:</strong> Tabla filtrable con resumen de errores, exportable a JSON/CSV. En la ultima ejecucion detecto <strong>14 errores en 6 subdominios</strong>.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/verificar_enlaces.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Verificador</a>';
        echo '</div>';

        // =============================================
        // CARD 2: TEST DE RENDIMIENTO
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #f59e0b;">';
        echo '<h3>&#9889; 2. Test de Rendimiento</h3></div>';
        echo '<p style="font-weight:600;color:#92400e;">test_rendimiento.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Diagnostica por que las paginas web (carpeta <code>web/</code>) tardan en cargar.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Que analiza:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li>Recursos externos bloqueantes (Google Fonts, reCAPTCHA, CDNs)</li>';
        echo '<li>Cantidad y tamano de archivos CSS/JS locales</li>';
        echo '<li>Tamano de imagenes (detecta las mayores a 500KB)</li>';
        echo '<li>Configuracion de <code>.htaccess</code> (compresion GZIP, cache headers)</li>';
        echo '<li>Scripts sin <code>async</code>/<code>defer</code> que bloquean el renderizado</li>';
        echo '<li>Meta refresh redirects en index.html</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?sub=nombre</code> &mdash; Analisis local del filesystem de un subdominio</li>';
        echo '<li><code>?sub=https://sub.dominio.com</code> &mdash; Acepta URL completa tambien</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Resultado:</strong> Reporte con puntuacion de rendimiento, lista de problemas ordenados por impacto, y recomendaciones especificas de optimizacion.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/test_rendimiento.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Test</a>';
        echo '</div>';

        // =============================================
        // CARD 3: DIAGNOSTICO LOGIN
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #dc2626;">';
        echo '<h3>&#128274; 3. Diagnostico de Login</h3></div>';
        echo '<p style="font-weight:600;color:#991b1b;">diagnostico_login.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Investiga por que DokuWiki pide loguearse dos veces (problema de doble login).</p>';
        echo '<p style="margin:0 0 6px;"><strong>Causas que detecta:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><span style="color:#dc2626;font-weight:600;">DOMINIO_CRUZADO</span> &mdash; La pagina web apunta a un dominio diferente al de DokuWiki (cookies no se comparten entre dominios)</li>';
        echo '<li><span style="color:#dc2626;font-weight:600;">BASEURL_DIFERENTE</span> &mdash; El baseurl configurado no coincide con la URL real de acceso</li>';
        echo '<li><span style="color:#dc2626;font-weight:600;">SESSION_PATH_RAIZ</span> &mdash; La sesion se guarda en /tmp (compartida con otros DokuWiki del mismo servidor)</li>';
        echo '<li><span style="color:#d97706;font-weight:600;">HTTP_HTTPS_MIXTO</span> &mdash; Mezcla HTTP/HTTPS que produce cookies con nombre diferente</li>';
        echo '<li><span style="color:#d97706;font-weight:600;">COOKIE_CONFLICT</span> &mdash; Multiples DokuWiki comparten nombre de cookie "DokuWiki"</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Que hace:</strong> Simula el calculo de <code>DOKU_COOKIE</code> (<code>DW + md5(DOKU_REL + port)</code>), verifica archivos de sesion, compara dominios de web vs doku.</p>';
        echo '<p style="margin:0;"><strong>Resultado:</strong> Lista de problemas encontrados con nivel de severidad (CRITICO/ADVERTENCIA) y sugerencias de correccion. Incluye script embebido <code>test_cookies.php</code> para pruebas en vivo.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/diagnostico_login.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Diagnostico</a>';
        echo '</div>';

        // =============================================
        // CARD 4: VERIFICAR BASEDIR
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #8b5cf6;">';
        echo '<h3>&#128270; 4. Verificar Basedir</h3></div>';
        echo '<p style="font-weight:600;color:#5b21b6;">verificar_basedir.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Detecta subdominios donde DokuWiki se muestra sin formato (CSS/JS no cargan) por mala configuracion de <code>basedir</code>.</p>';
        echo '<p style="margin:0 0 6px;"><strong>El problema:</strong> Cuando <code>basedir = \'/nombre_subdominio/\'</code> o no esta definido, DokuWiki genera URLs duplicadas como <code>https://sub.dominio.com/<strong>sub</strong>/lib/exe/css.php</code> que retornan 503/404.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Errores que detecta:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><span style="color:#dc2626;font-weight:600;">BASEDIR_DUPLICADO</span> &mdash; basedir coincide con nombre de carpeta (causa CSS roto)</li>';
        echo '<li><span style="color:#dc2626;font-weight:600;">BASEDIR_NO_DEFINIDO</span> &mdash; basedir ausente, DokuWiki auto-detecta mal</li>';
        echo '<li><span style="color:#d97706;font-weight:600;">BASEURL_SUBDOMINIO</span> &mdash; baseurl apunta a subdominio incorrecto</li>';
        echo '<li><span style="color:#d97706;font-weight:600;">BASEURL_HTTP</span> &mdash; baseurl usa HTTP en vez de HTTPS</li>';
        echo '<li><span style="color:#dc2626;font-weight:600;">TEMPLATE_NO_EXISTE</span> &mdash; template configurado no esta instalado</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Resultado:</strong> Tabla con todos los subdominios, agrupados por tipo de error, con resumen y conteo.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/verificar_basedir.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Verificador</a>';
        echo '</div>';

        // =============================================
        // CARD 5: VERIFICAR TEMPLATES
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #ec4899;">';
        echo '<h3>&#127912; 5. Verificar Templates</h3></div>';
        echo '<p style="font-weight:600;color:#9d174d;">verificar_templates.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Detecta subdominios donde el template DokuWiki (20cones/21cones) no existe, esta incompleto, o tiene archivos danados.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Que verifica:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li>Que el template configurado en <code>local.php</code> exista en <code>lib/tpl/</code></li>';
        echo '<li>Archivos criticos presentes: <code>main.php</code>, <code>style.ini</code>, <code>detail.php</code></li>';
        echo '<li>Directorios criticos: <code>css/</code>, <code>images/</code></li>';
        echo '<li>Que <code>style.ini</code> sea parseable y referencie CSS/LESS existentes</li>';
        echo '<li>Permisos de lectura en archivos del template</li>';
        echo '<li>Cache de DokuWiki escribible (<code>data/cache/</code>)</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Resultado:</strong> Lista de subdominios con template roto o incompleto, indicando exactamente que archivo falta o esta corrupto.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/verificar_templates.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Verificador</a>';
        echo '</div>';

        // =============================================
        // CARD 6: CORREGIR BASEDIR (CORRECTOR)
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #16a34a;">';
        echo '<h3>&#128295; 6. Corregir Basedir</h3></div>';
        echo '<p style="font-weight:600;color:#166534;">corregir_basedir.php</p>';

        echo '<div style="background:#f0fdf4;border:1px solid #86efac;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>&#9888; UNICA HERRAMIENTA QUE MODIFICA ARCHIVOS</strong></p>';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Detecta Y corrige problemas de <code>local.php</code> reemplazandolo con una plantilla estandar.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Modos de operacion:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> (default) &mdash; Solo detecta, NO modifica nada</li>';
        echo '<li><code>?modo=corregir</code> &mdash; Corrige TODOS los sistemas afectados (con confirmacion)</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Analizar/corregir un solo sistema</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Que hace al corregir:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li>Crea <strong>backup</strong> automatico: <code>local.php.bak_YYYYMMDD_HHMMSS</code></li>';
        echo '<li>Reemplaza <code>local.php</code> con plantilla estandar SIE</li>';
        echo '<li>Conserva del original: <code>title</code>, <code>template</code>, <code>notify</code>, <code>registernotify</code>, <code>mailfrom</code>, <code>mailreturnpath</code>, config IA (groq)</li>';
        echo '<li>Fija: <code>basedir = \'/\'</code>, ajusta <code>baseurl</code> al dominio correcto</li>';
        echo '<li>Tambien corrige <code>local_act.php</code> si tiene basedir/baseurl incorrecto</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Resultado:</strong> Tabla con todos los cambios realizados, backups creados, y estado final de cada sistema.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/corregir_basedir.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Corrector</a>';
        echo '</div>';

        // =============================================
        // CARD 7: TEST DOKUWIKI
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #0ea5e9;">';
        echo '<h3>&#128300; 7. Test DokuWiki</h3></div>';
        echo '<p style="font-weight:600;color:#0369a1;">test_dokuwiki.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Diagnostico profundo de un DokuWiki individual. Se sube a la <strong>raiz del subdominio</strong> que falla (junto a doku.php).</p>';
        echo '<p style="margin:0 0 6px;"><strong>&#9888; Uso especial:</strong> NO se ejecuta desde <code>/diagnosticos/</code>. Se copia al subdominio problematico, se ejecuta, y se elimina despues.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Que verifica:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li>Archivos criticos de DokuWiki: <code>doku.php</code>, <code>conf/dokuwiki.php</code>, <code>inc/init.php</code></li>';
        echo '<li>Directorios criticos: <code>conf/</code>, <code>data/</code>, <code>lib/</code>, <code>inc/</code></li>';
        echo '<li>Permisos de escritura en <code>data/</code> y subdirectorios</li>';
        echo '<li>Configuracion de PHP: version, extensiones, limites de memoria</li>';
        echo '<li>Parseo real del archivo <code>conf/local.php</code> y validacion de valores</li>';
        echo '<li>Estado del template configurado y sus dependencias</li>';
        echo '<li>Plugins instalados y su estado</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Resultado:</strong> Reporte completo del estado interno del DokuWiki: archivos, permisos, configuracion, plugins. Ideal para cuando un sistema especifico no funciona y no se sabe por que.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/test_dokuwiki.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Test DokuWiki</a>';
        echo '</div>';

        // =============================================
        // CARD 8: CORREGIR RENDIMIENTO
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #f59e0b;">';
        echo '<h3>&#9889; 8. Corregir Rendimiento Web</h3></div>';
        echo '<p style="font-weight:600;color:#b45309;">corregir_rendimiento.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Corrige masivamente los problemas de rendimiento detectados por el Test de Rendimiento (#2) en todos los subdominios.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Que corrige:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><strong>FIX 1 - Google Fonts:</strong> Agrega <code>&lt;link rel="preconnect"&gt;</code> y <code>display=swap</code> para que las fuentes no bloqueen el renderizado</li>';
        echo '<li><strong>FIX 2 - Scripts IE:</strong> Elimina bloques <code>&lt;!--[if lt IE 9]&gt;</code> con html5shiv.js y respond.js (obsoletos desde 2016)</li>';
        echo '<li><strong>FIX Rutas:</strong> Corrige <code>index.html</code> para que redirija correctamente (boton aparte). Detecta automaticamente si el sistema tiene <code>web/</code> o solo DokuWiki</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> - Solo muestra que se cambiaria (NO modifica nada)</li>';
        echo '<li><code>?modo=corregir</code> - Aplica FIX 1 + FIX 2 con backup automatico</li>';
        echo '<li><code>?modo=rutas</code> - Corrige index.html (boton aparte en la interfaz)</li>';
        echo '<li><code>?sub=nombre</code> - Solo un subdominio especifico</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Resultado:</strong> Progreso en tiempo real de cada subdominio corregido. Backup automatico de cada archivo antes de modificar.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/corregir_rendimiento.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Corrector de Rendimiento</a>';
        echo '</div>';

        // =============================================
        // CARD 9: FIX HOME BUTTON v2.0
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #ec4899;">';
        echo '<h3>&#127968; 9. Fix Home Button v2.0</h3></div>';
        echo '<p style="font-weight:600;color:#9d174d;">patches/fix_home/aplicar.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Corrige el boton casita (home) en <code>topbar.php</code> de templates 20cones y 21cones para que apunte al inicio de DokuWiki en vez de la raiz del dominio.</p>';
        echo '<p style="margin:0 0 6px;"><strong>El problema:</strong> El boton home tiene <code>href="/"</code> que lleva a la pagina web del dominio, no al inicio de DokuWiki.</p>';
        echo '<p style="margin:0 0 6px;"><strong>El fix:</strong> Cambia <code>href="/"</code> por <code>href="&lt;?php echo wl(); ?&gt;"</code> que genera <code>doku.php?id=inicio</code>.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Modos de operacion:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> &mdash; Lista todos los subdominios con estado actual (OK/ROTO/NO EXISTE)</li>';
        echo '<li><code>?modo=probar&amp;sub=nombre</code> &mdash; Aplica fix a UN solo subdominio (prueba individual)</li>';
        echo '<li><code>?modo=todos</code> &mdash; Aplica fix a TODOS los subdominios de una sola vez</li>';
        echo '<li><code>?modo=revertir&amp;sub=nombre</code> &mdash; Restaura backup <code>.bak_home</code> de un subdominio</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Flujo recomendado:</strong> Escanear &rarr; Probar en uno &rarr; Verificar casita &rarr; Aplicar a todos.</p>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Crea backup automatico de cada archivo antes de modificar. Incluye reversion individual.</p>';
        echo '</div>';

        echo '<div id="patch_log_fix_home" class="sd-patch-log" style="display:none;"></div>';
        echo '<button class="sd-btn sd-btn-primary sd-btn-sm" onclick="runPatch(\'fix_home\')">Abrir Fix Home</button>';
        echo '</div>';

        // =============================================
        // CARD 10: CORREGIR defer_js
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #f59e0b;">';
        echo '<h3>&#9889; 10. Corregir defer_js</h3></div>';
        echo '<p style="font-weight:600;color:#92400e;">diagnosticos/corregir_defer_js.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Cambia <code>defer_js = 0</code> a <code>defer_js = 1</code> en <code>conf/local.php</code> de todos los subdominios. Sin defer, los 13+ archivos JS bloquean el renderizado de la pagina.</p>';
        echo '<p style="margin:0 0 6px;"><strong>El problema:</strong> Con <code>defer_js = 0</code> el navegador espera descargar y ejecutar CADA archivo JavaScript antes de pintar la pagina. Esto causa retraso visible al usuario.</p>';
        echo '<p style="margin:0 0 6px;"><strong>El fix:</strong> Con <code>defer_js = 1</code> DokuWiki agrega el atributo <code>defer</code> a los tags <code>&lt;script&gt;</code>, permitiendo que el HTML se renderice sin esperar los JS.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> &mdash; Lista todos los subdominios con estado actual (defer=1/defer=0/no definido)</li>';
        echo '<li><code>?modo=corregir</code> &mdash; Aplica el cambio a TODOS los subdominios con backup automatico</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Solo un subdominio especifico</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Crea backup <code>local.php.bak_deferjs_*</code> antes de modificar cada archivo. Solo cambia la linea defer_js, no toca nada mas del local.php.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/corregir_defer_js.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Corrector defer_js</a>';
        echo '</div>';

        // =============================================
        // CARD 11: MIGRAR COMENTARIOS DISCUSSION
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #7c3aed;">';
        echo '<h3>&#128172; 11. Migrar Comentarios Discussion</h3></div>';
        echo '<p style="font-weight:600;color:#5b21b6;">diagnosticos/migrar_comentarios.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Migra comentarios del plugin Discussion desde paginas antiguas a paginas nuevas. Los archivos <code>.comments</code> en <code>data/meta/</code> se copian/fusionan al destino.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Mapeos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li><code>varios:menu:1</code> &rarr; <code>proyectos:administrativos:1:1_responsabilidades</code></li>';
        echo '<li><code>varios:menu:2</code> &rarr; <code>proyectos:administrativos:1:2_requisitos_eticos</code></li>';
        echo '<li><code>varios:menu:3</code> &rarr; <code>proyectos:administrativos:1:3_recursos_humanos</code></li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> &mdash; Detecta que subdominios tienen comentarios pendientes de migrar</li>';
        echo '<li><code>?modo=corregir</code> &mdash; Migra comentarios en TODOS los subdominios pendientes</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Solo un subdominio especifico</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Merge:</strong> Si el destino ya tiene comentarios, se fusionan sin perder ninguno. Crea backup <code>.bak_migcom_*</code> antes de modificar.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/migrar_comentarios.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Migrador Comentarios</a>';
        echo '</div>';

        // =============================================
        // CARD 12: MIGRAR CHECKBOXES (PLUGIN DO)
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #059669;">';
        echo '<h3>&#9745; 12. Migrar Checkboxes (plugin do)</h3></div>';
        echo '<p style="font-weight:600;color:#065f46;">diagnosticos/migrar_checks.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Migra el estado de checkboxes/tareas del plugin <code>do</code> desde paginas antiguas a paginas nuevas. El estado se almacena en la BD SQLite <code>do.sqlite3</code>.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Mapeos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li><code>varios:menu:1</code> &rarr; <code>proyectos:administrativos:1:1_responsabilidades</code></li>';
        echo '<li><code>varios:menu:2</code> &rarr; <code>proyectos:administrativos:1:2_requisitos_eticos</code></li>';
        echo '<li><code>varios:menu:3</code> &rarr; <code>proyectos:administrativos:1:3_recursos_humanos</code></li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> &mdash; Detecta que subdominios tienen tareas pendientes de migrar</li>';
        echo '<li><code>?modo=corregir</code> &mdash; Migra tareas en TODOS los subdominios pendientes</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Solo un subdominio especifico</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Anti-duplicado:</strong> Verifica por md5 Y por texto. Crea backup <code>do.sqlite3.bak_migchk_*</code> antes de modificar.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/migrar_checks.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Migrador Checkboxes</a>';
        echo '</div>';

        // =============================================
        // CARD 13: CREDENCIALES WASABI
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #8b5cf6;">';
        echo '<h3>&#9729; 13. Credenciales Wasabi S3</h3></div>';
        echo '<p style="font-weight:600;color:#6d28d9;">credenciales_wasabi.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Configura masivamente las credenciales del plugin <code>wasabisync</code> en todos los subdominios. Escribe las claves Access Key, Secret Key, bucket, region y endpoint en <code>conf/local.php</code> de cada wiki.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Que configura:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><strong>wasabi_key:</strong> Access Key de Wasabi</li>';
        echo '<li><strong>wasabi_secret:</strong> Secret Key de Wasabi</li>';
        echo '<li><strong>wasabi_bucket:</strong> Nombre del bucket (ej: nicc-archivos)</li>';
        echo '<li><strong>wasabi_region:</strong> Region (ej: us-east-1)</li>';
        echo '<li><strong>wasabi_endpoint:</strong> Endpoint S3 (ej: s3.wasabisys.com)</li>';
        echo '<li><strong>enabled:</strong> Activa el plugin wasabisync</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>Escanear</code> &mdash; Muestra estado de credenciales en cada subdominio (sin modificar)</li>';
        echo '<li><code>Aplicar Credenciales</code> &mdash; Escribe las credenciales en todos los subdominios pendientes</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Crea backup <code>local.php.bak_wasabi_*</code> antes de modificar. Las credenciales se envian por POST (nunca en URL).</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/credenciales_wasabi.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Credenciales Wasabi</a>';
        echo '</div>';

        // =============================================
        // CARD 14: CORREGIR ACL
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #dc2626;">';
        echo '<h3>&#128274; 14. Corregir ACL (Permisos)</h3></div>';
        echo '<p style="font-weight:600;color:#991b1b;">diagnosticos/corregir_acl.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Verifica y corrige el archivo <code>conf/acl.auth.php</code> de todos los subdominios. Asegura que existan las 5 reglas minimas de permisos del sistema SIE.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Reglas minimas:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li><code>* @ALL 0</code> &mdash; Denegar acceso a usuarios anonimos</li>';
        echo '<li><code>* @sie 16</code> &mdash; Grupo SIE con permisos totales</li>';
        echo '<li><code>* @lectura 8</code> &mdash; Grupo lectura con permisos de upload</li>';
        echo '<li><code>* @editartareas 2</code> &mdash; Grupo editartareas con permisos de edicion</li>';
        echo '<li><code>@lidertareas :task coordinator</code> &mdash; Coordinador de tareas</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> &mdash; Lista todos los subdominios con estado actual de ACL</li>';
        echo '<li><code>?modo=corregir</code> &mdash; Agrega las reglas faltantes en TODOS los subdominios</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Solo un subdominio especifico</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Crea backup <code>acl.auth.php.bak_acl_*</code> antes de modificar. Solo agrega reglas faltantes, NO elimina reglas existentes.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/corregir_acl.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Corrector ACL</a>';
        echo '</div>';

        // =============================================
        // CARD 15: ANALIZAR PESO DE SISTEMAS
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #7c3aed;">';
        echo '<h3>&#128202; 15. Analizar Peso de Sistemas</h3></div>';
        echo '<p style="font-weight:600;color:#5b21b6;">diagnosticos/analizar_peso.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Escanea cada subdominio DokuWiki y calcula el peso total desglosado por carpetas principales (media, cache, attic, pages, plugins, etc.).</p>';
        echo '<p style="margin:0 0 6px;"><strong>Caracteristicas:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li>Top 5 sistemas mas pesados</li>';
        echo '<li>Barra de distribucion visual por sistema</li>';
        echo '<li>Deteccion de stubs (0 bytes) de Wasabi</li>';
        echo '<li>Ordenar por peso total, media, cache o nombre</li>';
        echo '<li>Click en subdominio para ver desglose detallado</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?orden=peso</code> &mdash; Ordenar por peso total (default)</li>';
        echo '<li><code>?orden=media</code> &mdash; Ordenar por tamaño de media</li>';
        echo '<li><code>?orden=cache</code> &mdash; Ordenar por tamaño de cache</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Detalle de un subdominio</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Solo lectura. No modifica ningun archivo.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/analizar_peso.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Analizador de Peso</a>';
        echo '</div>';

        // =============================================
        // CARD 16: LIMPIAR CARPETAS WEB VACIAS
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #e11d48;">';
        echo '<h3>&#128465; 16. Limpiar Carpetas Web Vacias</h3></div>';
        echo '<p style="font-weight:600;color:#9f1239;">diagnosticos/limpiar_web_vacias.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Detecta subdominios que tienen carpeta <code>web/</code> pero sin contenido real (solo <code>.well-known</code>, <code>cgi-bin</code>, <code>.htaccess</code>). Estos sistemas NO tienen pagina web propia.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Que hace:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li>Escanea todos los subdominios y clasifica el contenido de <code>web/</code></li>';
        echo '<li>Identifica las carpetas que solo tienen archivos residuales</li>';
        echo '<li>Crea backup <code>web_backup_*.tar.gz</code> antes de eliminar</li>';
        echo '<li>Elimina la carpeta <code>web/</code> completa</li>';
        echo '<li>Corrige <code>index.html</code> para redirigir a <code>doku.php</code></li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> &mdash; Solo muestra cuales carpetas web/ estan vacias (default)</li>';
        echo '<li><code>?modo=limpiar</code> &mdash; Elimina las carpetas web/ vacias con backup</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Solo un subdominio especifico</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Crea backup comprimido <code>.tar.gz</code> antes de eliminar. Solo actua cuando NO hay contenido real en web/.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/limpiar_web_vacias.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Limpiador Web</a>';
        echo '</div>';

        // =============================================
        // CARD 17: DIAGNOSTICO ERROR 520 CLOUDFLARE
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #f97316;">';
        echo '<h3>&#9889; 17. Diagnostico Error 520 Cloudflare</h3></div>';
        echo '<p style="font-weight:600;color:#ea580c;">diagnosticos/diagnostico_520.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Identificar la causa de errores 520 (Web server returned an unknown error) reportados por Cloudflare en subdominios SIE.</p>';
        echo '<p style="margin:0 0 6px;"><strong>11 verificaciones por subdominio:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li>Archivos criticos (doku.php, index.php, conf/local.php)</li>';
        echo '<li>.htaccess, error_log, permisos de directorios</li>';
        echo '<li>Sintaxis conf/local.php, bloqueos SQLite</li>';
        echo '<li>Disco, sesiones PHP, DokuWiki data/log/, .user.ini</li>';
        echo '<li>PHP fatals recientes, HTTP directo vs Cloudflare</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?sub=nombre</code> &mdash; Diagnostico de un solo subdominio</li>';
        echo '<li>Sin parametros &mdash; Escanea todos los subdominios</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Solo lectura. Compara respuestas HTTP directas vs Cloudflare.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/diagnostico_520.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Diagnostico 520</a>';
        echo '</div>';

        // =============================================
        // CARD 18: CAMBIO MASIVO PASSWORD SIE2
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #dc2626;">';
        echo '<h3>&#128274; 18. Cambio Masivo Password sie2</h3></div>';
        echo '<p style="font-weight:600;color:#dc2626;">diagnosticos/cambiar_password_sie2.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Cambiar la contraseña del usuario <code>sie2</code> en todos los subdominios de forma masiva, con bcrypt hash.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Caracteristicas:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li>Escanea <code>conf/users.auth.php</code> en cada subdominio</li>';
        echo '<li>Crea backup antes de modificar</li>';
        echo '<li>Hash bcrypt seguro via <code>password_hash()</code></li>';
        echo '<li>Selector de subdominio individual o masivo</li>';
        echo '<li>Indicador visual de fortaleza de contraseña</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>modo=escanear</code> &mdash; Solo muestra estado actual (default)</li>';
        echo '<li><code>modo=corregir</code> &mdash; Aplica el cambio de contraseña</li>';
        echo '<li><code>sub=nombre</code> &mdash; Solo un subdominio</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Requiere nueva contraseña. Crea backup automatico de users.auth.php.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/cambiar_password_sie2.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Cambio Password</a>';
        echo '</div>';

        // =============================================
        // CARD 19: MIGRACION MASIVA WASABI
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #06b6d4;">';
        echo '<h3>&#9889; 19. Migracion Masiva Wasabi S3</h3></div>';
        echo '<p style="font-weight:600;color:#0891b2;">diagnosticos/migracion_masiva_wasabi.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Migrar 260+ GB de archivos media de 230+ subdominios a Wasabi S3. Dashboard completo con 5 fases secuenciales.</p>';
        echo '<p style="margin:0 0 6px;"><strong>5 fases:</strong> Inventario &rarr; Upload &rarr; Verificar &rarr; Registrar DB &rarr; Truncar</p>';
        echo '<p style="margin:0 0 6px;"><strong>Caracteristicas:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;font-size:0.95em;">';
        echo '<li>Probar en un solo subdominio antes de masivo</li>';
        echo '<li>Deteccion de archivos grandes (configurable MB)</li>';
        echo '<li>Resumible con tracking DB SQLite</li>';
        echo '<li>Streaming upload, reintento automatico</li>';
        echo '<li>Truncar solo con 100% verificacion</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Solo lectura hasta fase 5. Dashboard visual. Log completo.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/migracion_masiva_wasabi.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Migracion Wasabi</a>';
        echo '</div>';

        // ─── Card 20: Corrector de Landing Pages ───
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #f59e0b;">';
        echo '<h3>&#128640; 20. Corrector de Landing Pages</h3></div>';
        echo '<p style="font-weight:600;color:#d97706;">diagnosticos/corregir_landing.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Optimiza el rendimiento de las landing pages (carpeta web/) en todos los subdominios.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Solo aplica a subdominios con carpeta <code>web/</code></strong></p>';
        echo '<p style="margin:0 0 4px;"><strong>Correcciones (6 fixes):</strong></p>';
        echo '<ul style="margin:0;padding-left:16px;font-size:0.95em;">';
        echo '<li>Google Fonts: carga no bloqueante (media=print + onload)</li>';
        echo '<li>CSS no criticos: carga diferida (animate, prettyPhoto, owl, etc.)</li>';
        echo '<li>JS con defer: todos excepto jQuery</li>';
        echo '<li>reCAPTCHA: lazy load al hacer scroll al formulario</li>';
        echo '<li>HTML roto: tags mal formados, DOCTYPE obsoleto</li>';
        echo '<li>.htaccess: agrega GZIP + cache del navegador</li>';
        echo '</ul>';
        echo '<p style="margin:6px 0 0;"><strong>Archivos:</strong> web/index.html y web/index.php (ambos si existen). Crea backup antes de modificar.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/corregir_landing.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Corrector Landing</a>';
        echo '</div>';

        // =============================================
        // CARD 21: CONFIGURAR GRUPO BEZ ADMIN
        // =============================================
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #6366f1;">';
        echo '<h3>&#128272; 21. Configurar Grupo BEZ Admin</h3></div>';
        echo '<p style="font-weight:600;color:#4f46e5;">diagnosticos/configurar_bez_admin.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Configura masivamente el grupo administrador del plugin BEZ en todos los subdominios. Permite que usuarios de un grupo especifico tengan control total del plugin BEZ sin ser superusuarios de DokuWiki.</p>';
        echo '<p style="margin:0 0 6px;"><strong>Que configura:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><strong>OP 1 - bez admin_group:</strong> Agrega o actualiza <code>$conf[\'plugin\'][\'bez\'][\'admin_group\']</code> en conf/local.php</li>';
        echo '<li><strong>OP 2 - Directivas DokuWiki:</strong> Establece <code>defaultgroup=lectura</code>, <code>superuser=@admin</code>, <code>manager=manager</code> en conf/local.php</li>';
        echo '<li><strong>OP 3 - users.auth.php:</strong> Busca usuarios con grupo <code>sie</code> y les agrega el grupo BEZ admin (sin quitar ningun grupo existente)</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> &mdash; Muestra estado actual de cada subdominio (NO modifica nada)</li>';
        echo '<li><code>?modo=corregir</code> &mdash; Aplica los cambios a todos los subdominios con backup automatico</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Solo un subdominio especifico</li>';
        echo '<li><code>?grupo=nombre</code> &mdash; Nombre personalizado del grupo (default: bez_admin)</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> Crea backup <code>.bak_bezadmin_*</code> antes de modificar. Permite probar en un solo sistema primero. Nombre del grupo configurable desde la interfaz.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/configurar_bez_admin.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Configurador BEZ Admin</a>';
        echo '</div>';

        // ---- Card #22: Limpiar Espacio en Disco ----
        echo '<div class="sd-update-card" style="min-height:auto;">';
        echo '<div class="sd-update-card-header" style="border-left:4px solid #dc2626;">';
        echo '<h3>&#128465; 22. Limpiar Espacio en Disco</h3></div>';
        echo '<p style="font-weight:600;color:#dc2626;">diagnosticos/limpiar_espacio.php</p>';

        echo '<div style="background:#f8fafc;border-radius:6px;padding:12px;margin:8px 0;font-size:0.84em;">';
        echo '<p style="margin:0 0 6px;"><strong>Proposito:</strong> Detecta y elimina basura acumulada en todos los subdominios DokuWiki para liberar espacio en disco (cache, historial antiguo, temporales, logs, backups de diagnosticos).</p>';
        echo '<p style="margin:0 0 6px;"><strong>Categorias de limpieza:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><strong>Cache:</strong> Se regenera automaticamente (riesgo bajo)</li>';
        echo '<li><strong>Attic:</strong> Historial de revisiones antiguas &gt;N dias (riesgo medio)</li>';
        echo '<li><strong>Media Attic:</strong> Versiones anteriores de archivos subidos (riesgo medio)</li>';
        echo '<li><strong>Temporales, Logs, Backups, error_log:</strong> Basura segura de eliminar (riesgo bajo)</li>';
        echo '</ul>';
        echo '<p style="margin:0 0 6px;"><strong>Modos:</strong></p>';
        echo '<ul style="margin:0 0 6px;padding-left:18px;">';
        echo '<li><code>?modo=escanear</code> &mdash; Solo muestra espacio recuperable (NO borra nada)</li>';
        echo '<li><code>?modo=limpiar&amp;cats=cache,tmp,log</code> &mdash; Ejecuta limpieza de categorias seleccionadas</li>';
        echo '<li><code>?sub=nombre</code> &mdash; Solo un subdominio</li>';
        echo '<li><code>?attic_dias=90</code> &mdash; Solo borrar attic mas viejo que N dias (default 90)</li>';
        echo '</ul>';
        echo '<p style="margin:0;"><strong>Seguridad:</strong> NUNCA toca data/pages ni data/media (archivos reales). Interfaz con seleccion de categorias y confirmacion antes de borrar.</p>';
        echo '</div>';

        echo '<a href="' . hsc($diagUrl) . '/limpiar_espacio.php" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm">Abrir Limpiador de Espacio</a>';
        echo '</div>';

        echo '</div>'; // .sd-updates-grid

        // =============================================
        // RESUMEN DE USO RAPIDO
        // =============================================
        echo '<div style="background:#fff;border-radius:10px;padding:20px;margin-top:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08);">';
        echo '<h2 style="margin:0 0 12px;">&#128218; Guia Rapida de Uso</h2>';

        echo '<table style="width:100%;border-collapse:collapse;font-size:0.84em;">';
        echo '<thead><tr>';
        echo '<th style="text-align:left;padding:8px;border-bottom:2px solid #e2e8f0;">Situacion</th>';
        echo '<th style="text-align:left;padding:8px;border-bottom:2px solid #e2e8f0;">Herramienta</th>';
        echo '<th style="text-align:left;padding:8px;border-bottom:2px solid #e2e8f0;">URL</th>';
        echo '</tr></thead><tbody>';

        $diagRel = 'lib/plugins/siedeployer/diagnosticos';
        $guia = [
            ['Un sistema se ve sin formato (sin CSS)', 'Verificar Basedir (#4) + Corregir Basedir (#6)', $diagRel . '/verificar_basedir.php?sub=nombre'],
            ['El boton "ACCESO AL SISTEMA" va a otro sitio', 'Verificar Enlaces (#1)', $diagRel . '/verificar_enlaces.php'],
            ['Un usuario debe loguearse dos veces', 'Diagnostico Login (#3)', $diagRel . '/diagnostico_login.php?sub=nombre'],
            ['La pagina web tarda mucho en cargar', 'Test Rendimiento (#2)', $diagRel . '/test_rendimiento.php?sub=nombre'],
            ['Un template se ve raro o incompleto', 'Verificar Templates (#5)', $diagRel . '/verificar_templates.php?sub=nombre'],
            ['Un sistema no funciona y no se sabe por que', 'Test DokuWiki (#7) - copiar al subdominio', 'Copiar test_dokuwiki.php al subdominio'],
            ['Correccion masiva de basedir/baseurl', 'Corregir Basedir (#6)', $diagRel . '/corregir_basedir.php?modo=escanear'],
            ['Google Fonts bloquea la carga de la pagina', 'Corregir Rendimiento (#8)', $diagRel . '/corregir_rendimiento.php?modo=escanear'],
            ['Scripts IE obsoletos (html5shiv, respond.js)', 'Corregir Rendimiento (#8)', $diagRel . '/corregir_rendimiento.php?modo=escanear'],
            ['El index.html no redirige correctamente', 'Corregir Rendimiento (#8) - boton Rutas', $diagRel . '/corregir_rendimiento.php?modo=escanear'],
            ['El boton casita (home) lleva a la pagina web en vez de DokuWiki', 'Fix Home Button (#9) - via Actualizaciones', 'Actualizaciones > Fix Home Button v2.0'],
            ['Los JS bloquean el renderizado (MUCHOS_JS)', 'Corregir defer_js (#10)', $diagRel . '/corregir_defer_js.php?modo=escanear'],
            ['Migrar comentarios Discussion de paginas antiguas a nuevas', 'Migrar Comentarios (#11)', $diagRel . '/migrar_comentarios.php?modo=escanear'],
            ['Migrar checkboxes/tareas del plugin do entre paginas', 'Migrar Checkboxes (#12)', $diagRel . '/migrar_checks.php?modo=escanear'],
            ['Configurar credenciales Wasabi S3 en todos los wikis', 'Credenciales Wasabi (#13)', $diagRel . '/credenciales_wasabi.php'],
            ['Permisos ACL faltan o estan incompletos (No ACL setup yet)', 'Corregir ACL (#14)', $diagRel . '/corregir_acl.php?modo=escanear'],
            ['Identificar sistemas pesados, ver distribucion de espacio', 'Analizar Peso (#15)', $diagRel . '/analizar_peso.php'],
            ['Subdominio tiene carpeta web/ sin pagina real (solo .htaccess, cgi-bin)', 'Limpiar Web Vacias (#16)', $diagRel . '/limpiar_web_vacias.php?modo=escanear'],
            ['Error 520 de Cloudflare en un subdominio', 'Diagnostico 520 (#17)', $diagRel . '/diagnostico_520.php?sub=nombre'],
            ['Cambiar contraseña de sie2 en todos los subdominios', 'Cambio Password sie2 (#18)', $diagRel . '/cambiar_password_sie2.php'],
            ['Migrar archivos media a Wasabi S3 (260+ GB)', 'Migracion Wasabi (#19)', $diagRel . '/migracion_masiva_wasabi.php'],
            ['Landing page lenta (Google Fonts, JS bloqueante, sin GZIP)', 'Corrector Landing (#20)', $diagRel . '/corregir_landing.php'],
            ['Disco lleno, limpiar cache/attic/temporales/logs masivamente', 'Limpiar Espacio (#22)', $diagRel . '/limpiar_espacio.php'],
        ];
        foreach ($guia as $g) {
            echo '<tr>';
            echo '<td style="padding:8px;border-bottom:1px solid #f1f5f9;">' . $g[0] . '</td>';
            echo '<td style="padding:8px;border-bottom:1px solid #f1f5f9;font-weight:600;">' . $g[1] . '</td>';
            echo '<td style="padding:8px;border-bottom:1px solid #f1f5f9;"><code style="font-size:0.9em;">' . hsc($g[2]) . '</code></td>';
            echo '</tr>';
        }
        echo '</tbody></table>';
        echo '</div>';
    }

    // =========================================================================
    //  VISTA: DASHBOARD DE PASOS
    // =========================================================================

    protected function htmlStepsDashboard($dep)
    {
        global $ID, $INPUT;

        $steps = $this->helper->getStepsStatus($dep['id']);
        $done = 0;
        foreach ($steps as $s) {
            if ($s['status'] === 'completed') $done++;
        }

        // Header
        echo '<div class="sd-header">';
        echo '<a href="' . wl($ID, ['do' => 'admin', 'page' => 'siedeployer', 'migkey' => $INPUT->str('migkey', '')]) . '" class="sd-back">&larr; Volver</a>';
        echo '<div style="display:flex;align-items:center;gap:12px;">';
        echo '<h1 style="margin:0;">' . hsc($dep['subdomain']) . '</h1>';
        echo '<button class="sd-btn sd-btn-sm sd-btn-outline" style="color:#dc2626;border-color:#dc2626;" onclick="resetDeployment(' . intval($dep['id']) . ')">Reiniciar Pasos</button>';
        echo '</div>';
        echo '<div class="sd-meta">';
        echo '<span><strong>Razon Social:</strong> ' . hsc($dep['name']) . '</span>';
        echo '<span><strong>Subdominio:</strong> ' . hsc($dep['subdomain']) . '.systemsie.site</span>';
        echo '<span><strong>Origen:</strong> ' . hsc($dep['source_path']) . '</span>';
        echo '<span><strong>Destino:</strong> ' . hsc($dep['target_path']) . '</span>';
        $modeLabel = ($dep['mode'] ?? 'new') === 'modify' ? 'Modificar Sistema' : 'Nuevo Sistema';
        echo '<span><strong>Modo:</strong> ' . $modeLabel . '</span>';
        $stepsTotal = count($steps);
        echo '<span><strong>Progreso:</strong> ' . $done . '/' . $stepsTotal . ' pasos</span>';
        echo '</div>';
        echo '<div class="sd-progress sd-progress-lg"><div class="sd-progress-bar" style="width:' . ($stepsTotal > 0 ? round($done / $stepsTotal * 100) : 0) . '%"></div></div>';
        echo '</div>';

        // Token de seguridad para AJAX
        echo '<input type="hidden" id="sd_sectok" value="' . getSecurityToken() . '" />';
        echo '<input type="hidden" id="sd_deployment_id" value="' . intval($dep['id']) . '" />';
        echo '<input type="hidden" id="sd_deployment_name" value="' . hsc($dep['name']) . '" />';
        echo '<input type="hidden" id="sd_deployment_subdomain" value="' . hsc($dep['subdomain']) . '" />';
        echo '<input type="hidden" id="sd_deployment_mode" value="' . hsc($dep['mode'] ?? 'new') . '" />';
        // BUG8-FIX: campo para dominio raiz dinamico — usar el dominio guardado en BD del despliegue
        $depRootDomain = !empty($dep['root_domain']) ? $dep['root_domain'] : helper_plugin_siedeployer::$rootDomain;
        echo '<input type="hidden" id="sd_deployment_rootdomain" value="' . hsc($depRootDomain) . '" />';

        // Los 16 pasos como accordion
        echo '<div class="sd-steps">';

        foreach ($steps as $step) {
            $num = intval($step['step_number']);
            $status = $step['status'];
            $statusIcon = $this->getStatusIcon($status);
            $statusClass = 'sd-step-' . $status;

            echo '<div class="sd-step ' . $statusClass . '" data-step="' . $num . '">';

            // Header del paso (clickeable)
            echo '<div class="sd-step-header" onclick="toggleStep(' . $num . ')">';
            echo '<span class="sd-step-num">' . $num . '</span>';
            echo '<span class="sd-step-icon">' . $statusIcon . '</span>';
            echo '<span class="sd-step-title">' . hsc($step['step_name']) . '</span>';
            if ($step['executed_at']) {
                echo '<span class="sd-step-date">' . hsc($step['executed_at']) . ' por ' . hsc($step['executed_by']) . '</span>';
            }
            echo '<span class="sd-step-chevron">&#9660;</span>';
            echo '</div>';

            // Body del paso (oculto por defecto)
            echo '<div class="sd-step-body" id="step_body_' . $num . '" style="display:none;">';
            $this->htmlStepContent($num, $dep['id'], $step, $dep['mode'] ?? 'new');
            echo '</div>';

            echo '</div>'; // sd-step
        }

        echo '</div>'; // sd-steps
    }

    protected function getStatusIcon($status)
    {
        switch ($status) {
            case 'completed': return '<span class="sd-icon-ok">&#10004;</span>';
            case 'running':   return '<span class="sd-icon-run">&#9654;</span>';
            case 'failed':    return '<span class="sd-icon-err">&#10008;</span>';
            default:          return '<span class="sd-icon-pend">&#9679;</span>';
        }
    }

    // =========================================================================
    //  CONTENIDO DE CADA PASO
    // =========================================================================

    protected function htmlStepContent($num, $deploymentId, $step, $mode = 'new')
    {
        // Obtener datos del despliegue para defaults
        $dep = $this->helper->getDeployment($deploymentId);
        $depName = $dep ? $dep['name'] : '';
        $depSubdomain = $dep ? $dep['subdomain'] : '';

        echo '<div class="sd-step-form" id="step_form_' . $num . '">';

        switch ($num) {
            case 1:
                if ($mode === 'modify') {
                    echo '<p style="color:#16a34a;font-weight:600;">&#10004; No aplica en modo Modificar Sistema Existente.</p>';
                    echo '<p>En este modo se trabaja directamente sobre el sistema seleccionado, sin duplicacion.</p>';
                } else {
                    echo '<p>Duplica el sistema origen a la ruta destino configurada en este despliegue.</p>';
                    echo '<p class="sd-warn">Este paso puede tomar varios minutos dependiendo del tamano del sistema.</p>';
                    echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(1)">Ejecutar Duplicacion</button>';
                }
                break;

            case 2:
                // Defaults: titulo = razon social, URL = https://subdominio.systemsie.site/
                $defaultTitle = $depName;
                $defaultUrl = $depSubdomain ? 'https://' . $depSubdomain . '.systemsie.site/' : '';
                $savedTitle = $defaultTitle;
                $savedUrl = $defaultUrl;
                $savedTemplate = '20cones'; // default
                if ($step['input_data']) {
                    $saved = json_decode($step['input_data'], true);
                    $savedTitle = $saved['title'] ?? $defaultTitle;
                    $savedUrl = $saved['baseurl'] ?? $defaultUrl;
                    $savedTemplate = $saved['template'] ?? '20cones';
                }
                // Detectar template actual del sistema destino
                $targetLocalPhp = $depTarget . '/conf/local.php';
                if (file_exists($targetLocalPhp) && $savedTemplate === '20cones' && empty($step['input_data'])) {
                    $localContent = @file_get_contents($targetLocalPhp);
                    if ($localContent && preg_match('/\$conf\s*\[\s*[\'"]template[\'"]\s*\]\s*=\s*[\'"]([^\'"]+)[\'"]/', $localContent, $tplMatch)) {
                        $savedTemplate = $tplMatch[1];
                    }
                }
                echo '<div class="sd-field"><label>Titulo del sistema</label>';
                echo '<input type="text" id="step2_title" value="' . hsc($savedTitle) . '" placeholder="ej: ACME S.A.S - SISTEMA DE GESTION DE CALIDAD" /></div>';
                echo '<p style="font-size:0.82em;color:#64748b;margin-top:4px;">Predeterminado desde Razon Social: <code>' . hsc($depName) . '</code></p>';
                echo '<div class="sd-field"><label>URL base (subdominio)</label>';
                echo '<input type="text" id="step2_baseurl" value="' . hsc($savedUrl) . '" placeholder="ej: https://acme.systemsie.site/" /></div>';
                echo '<div class="sd-field"><label>Template</label>';
                echo '<select id="step2_template">';
                echo '<option value="20cones"' . ($savedTemplate === '20cones' ? ' selected' : '') . '>20cones</option>';
                echo '<option value="21cones"' . ($savedTemplate === '21cones' ? ' selected' : '') . '>21cones</option>';
                echo '</select></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(2)">Guardar Titulo, URL y Template</button>';
                break;

            case 3:
                // Default: {subdominio}sgc@gmail.com
                $defaultEmail = $depSubdomain . 'sgc@gmail.com';
                $savedEmail = $defaultEmail;
                if ($step['input_data']) {
                    $saved = json_decode($step['input_data'], true);
                    $savedEmail = $saved['email'] ?? $defaultEmail;
                }
                echo '<div class="sd-field"><label>Email (se aplica a notify, registernotify, mailfrom, mailreturnpath)</label>';
                echo '<input type="email" id="step3_email" value="' . hsc($savedEmail) . '" placeholder="ej: acmesgc@gmail.com" /></div>';
                echo '<p style="font-size:0.82em;color:#64748b;margin-top:4px;">Predeterminado: <code>' . hsc($depSubdomain) . 'sgc@gmail.com</code></p>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(3)">Guardar Correos</button>';
                break;

            case 4:
                echo '<div class="sd-field"><label>Token IA (JWT)</label>';
                echo '<div class="sd-input-group">';
                echo '<input type="text" id="step4_token" placeholder="Token JWT" />';
                echo '<button type="button" class="sd-btn sd-btn-sm" onclick="generateToken()">Generar</button>';
                echo '</div>';
                echo '<p id="step4_status" style="font-size:0.82em;color:#64748b;margin-top:4px;">Proyecto: <code>' . hsc($depSubdomain) . '</code></p>';
                echo '</div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(4)">Guardar Token</button>';
                break;

            case 5:
                if ($mode === 'modify') {
                    echo '<div style="background:#fef3c7;border:1px solid #f59e0b;border-radius:8px;padding:14px;margin-bottom:12px;">';
                    echo '<p style="color:#92400e;font-weight:700;margin:0 0 6px;">&#9888; Este paso solo aplica al modo Nuevo Sistema</p>';
                    echo '<p style="color:#92400e;margin:0;font-size:0.9em;">En modo Modificar Sistema Existente, la base de datos no se limpia para conservar los datos existentes.</p>';
                    echo '</div>';
                } else {
                    echo '<p>Limpia toda la base de datos BEZ del sistema destino (tareas, proyectos, hilos, calendario tributario, etc.).</p>';
                    echo '<p>Tambien elimina las bases auxiliares: <code>do.sqlite3</code>, <code>docmaster.sqlite3</code>, <code>struct.sqlite3</code> (se recrean al primer uso).</p>';
                    echo '<p class="sd-warn">Esta accion elimina TODOS los datos de proyectos del sistema destino.</p>';
                    echo '<label class="sd-check"><input type="checkbox" id="step5_confirm" /> Confirmo que deseo limpiar la base de datos</label>';
                    echo '<button class="sd-btn sd-btn-danger" onclick="executeStepConfirm(5, \'step5_confirm\')">Limpiar Base de Datos</button>';
                }
                break;

            case 6:
                if ($mode === 'modify') {
                    echo '<button class="sd-btn sd-btn-secondary" onclick="downloadStepFiles(6)" style="margin-bottom:10px;">';
                    echo '<span style="margin-right:4px;">&#11015;</span> Descargar logos actuales</button>';
                }
                echo '<div class="sd-field"><label>logo.png</label>';
                echo '<input type="file" id="step6_logo_png" accept=".png" /></div>';
                echo '<div class="sd-field"><label>logo.gif</label>';
                echo '<input type="file" id="step6_logo_gif" accept=".gif" /></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(6)">Subir Logos</button>';
                break;

            case 7:
                if ($mode === 'modify') {
                    echo '<div style="background:#fef3c7;border:1px solid #f59e0b;border-radius:8px;padding:14px;margin-bottom:12px;">';
                    echo '<p style="color:#92400e;font-weight:700;margin:0 0 6px;">&#9888; Este paso solo aplica al modo Nuevo Sistema</p>';
                    echo '<p style="color:#92400e;margin:0;font-size:0.9em;">En modo Modificar Sistema Existente, los datos del sistema no se eliminan.</p>';
                    echo '</div>';
                } else {
                    echo '<p>Limpia las carpetas data/ del sistema destino (pages, media, cache, etc.), conservando la estructura y b3p.sqlite3.</p>';
                    echo '<p class="sd-warn">Esta accion elimina TODO el contenido de las carpetas de datos.</p>';
                    echo '<label class="sd-check"><input type="checkbox" id="step7_confirm" /> Confirmo que deseo limpiar la carpeta data</label>';
                    echo '<button class="sd-btn sd-btn-danger" onclick="executeStepConfirm(7, \'step7_confirm\')">Limpiar Data</button>';
                }
                break;

            case 8:
                echo '<p>El <strong>favicon.ico</strong> se copiara a 5 ubicaciones: data/media/wiki/, lib/tpl/20cones/, lib/tpl/21cones/ y la raiz del sistema. <em>(Incluye Pasos 11 y 13)</em></p>';
                if ($mode === 'modify') {
                    echo '<button class="sd-btn sd-btn-secondary" onclick="downloadStepFiles(8)" style="margin-bottom:10px;">';
                    echo '<span style="margin-right:4px;">&#11015;</span> Descargar imagenes actuales</button>';
                }
                echo '<div class="sd-field"><label>dokuwiki-128.png</label>';
                echo '<input type="file" id="step8_dokuwiki_png" accept=".png" /></div>';
                echo '<div class="sd-field"><label>favicon.ico (se copia a todas las ubicaciones)</label>';
                echo '<input type="file" id="step8_favicon_ico" accept=".ico" /></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(8)">Subir Imagenes y Favicons</button>';
                break;

            case 9:
                if ($mode === 'modify') {
                    echo '<div style="background:#dbeafe;border:1px solid #3b82f6;border-radius:8px;padding:14px;margin-bottom:12px;">';
                    echo '<p style="color:#1e40af;font-weight:700;margin:0 0 6px;">&#128274; Modo Modificar: se creara un respaldo automatico</p>';
                    echo '<p style="color:#1e40af;margin:0;font-size:0.9em;">Antes de aplicar cambios, se guardara una copia de <code>acl.auth.php</code> con extension <code>.bak.[fecha]</code>.</p>';
                    echo '</div>';
                    echo '<button class="sd-btn sd-btn-secondary" onclick="downloadStepFiles(9)" style="margin-bottom:10px;">';
                    echo '<span style="margin-right:4px;">&#11015;</span> Descargar acl.auth.php actual</button>';
                    // En modo modificar, cargar ACL actual del sistema
                    $aclFile = $dep['target_path'] . '/conf/acl.auth.php';
                    $defaultAcl = file_exists($aclFile) ? file_get_contents($aclFile) : $this->helper->getDefaultACL();
                } else {
                    $defaultAcl = $this->helper->getDefaultACL();
                }
                echo '<div class="sd-field"><label>Contenido de acl.auth.php</label>';
                echo '<textarea id="step9_acl" rows="8" class="sd-textarea">' . hsc($defaultAcl) . '</textarea></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(9)">Guardar ACL</button>';
                break;

            case 10:
                if ($mode === 'modify') {
                    echo '<div style="background:#dbeafe;border:1px solid #3b82f6;border-radius:8px;padding:14px;margin-bottom:12px;">';
                    echo '<p style="color:#1e40af;font-weight:700;margin:0 0 6px;">&#128274; Modo Modificar: se creara un respaldo automatico</p>';
                    echo '<p style="color:#1e40af;margin:0;font-size:0.9em;">Antes de aplicar cambios, se guardara una copia de <code>users.auth.php</code> con extension <code>.bak.[fecha]</code>.</p>';
                    echo '</div>';
                    echo '<button class="sd-btn sd-btn-secondary" onclick="downloadStepFiles(10)" style="margin-bottom:10px;">';
                    echo '<span style="margin-right:4px;">&#11015;</span> Descargar users.auth.php actual</button>';
                    // En modo modificar, cargar usuarios actuales del sistema
                    $usersFile = $dep['target_path'] . '/conf/users.auth.php';
                    $defaultUsers = file_exists($usersFile) ? file_get_contents($usersFile) : $this->helper->getDefaultUsers();
                } else {
                    $defaultUsers = $this->helper->getDefaultUsers();
                }
                echo '<div class="sd-field"><label>Contenido de users.auth.php</label>';
                echo '<textarea id="step10_users" rows="14" class="sd-textarea">' . hsc($defaultUsers) . '</textarea></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(10)">Guardar Usuarios</button>';
                break;

            case 11:
                echo '<p>Favicon para <code>lib/tpl/20cones/</code>. Tambien se ejecuta automaticamente con el Paso 8.</p>';
                echo '<div class="sd-field"><label>Seleccione favicon.ico o un ZIP que lo contenga</label>';
                echo '<input type="file" id="step11_file" accept=".ico,.zip" /></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(11)">Subir Favicon</button>';
                break;

            case 12:
                echo '<p>Archivos para /lib/tpl/20cones/images/: icon.png, logo_sie_asesorias.png, page-gradient.png, header-gradient.png, favicon.ico, apple-touch-icon.png, dokuwiki-128.png</p>';
                echo '<div class="sd-field"><label>Seleccione imagenes individuales o un ZIP con las imagenes</label>';
                echo '<input type="file" id="step12_files" multiple accept=".png,.ico,.zip" /></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(12)">Subir Imagenes</button>';
                break;

            case 13:
                echo '<p>Favicon para <code>lib/tpl/21cones/</code>. Tambien se ejecuta automaticamente con el Paso 8.</p>';
                echo '<div class="sd-field"><label>Seleccione favicon.ico o un ZIP que lo contenga</label>';
                echo '<input type="file" id="step13_file" accept=".ico,.zip" /></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(13)">Subir Favicon</button>';
                break;

            case 14:
                echo '<p>Archivos para /lib/tpl/21cones/images/: icon.png, logo_sie_asesorias.png, page-gradient.png, favicon.ico, apple-touch-icon.png, dokuwiki-128.png</p>';
                echo '<div class="sd-field"><label>Seleccione imagenes individuales o un ZIP con las imagenes</label>';
                echo '<input type="file" id="step14_files" multiple accept=".png,.ico,.zip" /></div>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(14)">Subir Imagenes</button>';
                break;

            case 15:
                echo '<p>Suba la documentacion en formato ZIP:</p>';

                // Toggle modo
                echo '<div class="sd-field" style="margin-bottom:12px;">';
                echo '<label class="sd-check" style="font-weight:normal;">';
                echo '<input type="radio" name="step15_mode" value="individual" checked onchange="toggleStep15Mode()" /> Un ZIP por cada carpeta';
                echo '</label> &nbsp; ';
                echo '<label class="sd-check" style="font-weight:normal;">';
                echo '<input type="radio" name="step15_mode" value="unified" onchange="toggleStep15Mode()" /> Un solo ZIP con todas las carpetas';
                echo '</label>';
                echo '</div>';

                // Modo individual: un ZIP por carpeta
                echo '<div id="step15_individual">';
                $folders = ['administrativa', 'comercial', 'compartidos', 'operativo', 'rrhh'];
                foreach ($folders as $f) {
                    echo '<div class="sd-field"><label>ZIP para: ' . ucfirst($f) . '</label>';
                    echo '<input type="file" id="step15_zip_' . $f . '" accept=".zip" /></div>';
                }
                echo '</div>';

                // Modo unificado: un solo ZIP
                echo '<div id="step15_unified" style="display:none;">';
                echo '<div class="sd-field"><label>ZIP unico (debe contener carpetas: administrativa/, comercial/, compartidos/, operativo/, rrhh/)</label>';
                echo '<input type="file" id="step15_zip_all" accept=".zip" /></div>';
                echo '<p style="font-size:0.82em;color:#64748b;margin-top:4px;">El ZIP debe tener las carpetas en la raiz o dentro de una carpeta contenedora.</p>';
                echo '</div>';

                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(15)">Subir y Extraer ZIPs</button>';
                break;

            case 16:
                // Selector de dominio (compartido por ambos modos)
                $availDomains = helper_plugin_siedeployer::$availableDomains;
                // Usar el dominio guardado en BD si existe, sino el default
                $currentRoot = !empty($dep['root_domain']) ? $dep['root_domain'] : helper_plugin_siedeployer::$rootDomain;

                if ($mode === 'modify') {
                    echo '<div style="background:#fef3c7;border:1px solid #f59e0b;border-radius:8px;padding:14px;margin-bottom:12px;">';
                    echo '<p style="color:#92400e;font-weight:700;margin:0 0 6px;">&#9888; Eliminar Subdominio y Certificado SSL</p>';
                    echo '<p style="color:#92400e;margin:0 0 8px;font-size:0.9em;">Esta accion elimina el subdominio y su certificado SSL de cPanel/WHM. <strong>La carpeta y los datos del sistema NO seran borrados.</strong></p>';
                    echo '<p style="color:#92400e;margin:0 0 8px;font-size:0.9em;">Adicionalmente se comentara la linea <code>baseurl</code> en <code>conf/local.php</code> del sistema.</p>';
                    echo '</div>';

                    echo '<div style="background:#eff6ff;border:1px solid #3b82f6;border-radius:8px;padding:14px;margin-bottom:12px;">';
                    echo '<p style="color:#1e40af;font-weight:700;margin:0 0 6px;">&#128274; Configuracion actual</p>';
                    echo '<table style="font-size:0.9em;color:#1e40af;">';
                    echo '<tr><td style="padding:2px 12px 2px 0;"><strong>Subdominio:</strong></td><td><code>' . hsc($depSubdomain) . '.' . hsc($currentRoot) . '</code></td></tr>';
                    echo '<tr><td style="padding:2px 12px 2px 0;"><strong>Carpeta:</strong></td><td><code>' . hsc($dep['target_path']) . '</code> (NO se eliminara)</td></tr>';
                    echo '</table>';
                    echo '</div>';

                    echo '<label class="sd-check"><input type="checkbox" id="step16_delete_confirm" /> Confirmo que deseo eliminar el subdominio y certificado SSL</label>';
                    echo '<button class="sd-btn sd-btn-danger" onclick="deleteSubdomain()">Eliminar Subdominio y SSL</button>';
                    echo '<hr style="border:none;border-top:1px solid #e2e8f0;margin:16px 0;" />';
                }

                // ── Crear subdominio en dominio seleccionado ──
                echo '<div style="background:#f0fdf4;border:1px solid #22c55e;border-radius:8px;padding:14px;margin-bottom:12px;">';
                echo '<p style="color:#166534;font-weight:700;margin:0 0 8px;">&#127760; Crear Subdominio en Dominio</p>';
                echo '<table style="font-size:0.9em;color:#166534;width:100%;">';
                echo '<tr><td style="padding:4px 12px 4px 0;white-space:nowrap;"><strong>Subdominio:</strong></td><td><code>' . hsc($depSubdomain) . '</code></td></tr>';
                echo '<tr><td style="padding:4px 12px 4px 0;white-space:nowrap;"><strong>Dominio:</strong></td><td>';
                echo '<select id="step16_domain" style="padding:6px 10px;border:1px solid #86efac;border-radius:6px;font-size:0.95em;min-width:260px;" onchange="step16UpdatePreview()">';
                foreach ($availDomains as $dom) {
                    $sel = ($dom === $currentRoot) ? ' selected' : '';
                    echo '<option value="' . hsc($dom) . '"' . $sel . '>' . hsc($dom) . '</option>';
                }
                echo '</select>';
                echo '</td></tr>';
                echo '<tr><td style="padding:4px 12px 4px 0;white-space:nowrap;"><strong>Resultado:</strong></td><td><code id="step16_preview">' . hsc($depSubdomain) . '.' . hsc($currentRoot) . '</code></td></tr>';
                echo '<tr><td style="padding:4px 12px 4px 0;white-space:nowrap;"><strong>Carpeta:</strong></td><td><code>' . hsc($dep['target_path']) . '</code> (apunta a la misma carpeta)</td></tr>';
                echo '</table>';
                echo '</div>';

                echo '<p style="font-size:0.88em;color:#64748b;">Se ejecutaran dos comandos:</p>';
                echo '<ol style="font-size:0.88em;color:#64748b;margin:4px 0 16px;">';
                echo '<li><code>cpapi2</code> para crear el subdominio en el dominio seleccionado</li>';
                echo '<li><code>whmapi1 start_autossl_check</code> para solicitar el certificado SSL</li>';
                echo '</ol>';
                echo '<p class="sd-warn">El certificado SSL puede tardar 5-15 minutos en generarse despues de ejecutar este paso.</p>';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(16)">Crear Subdominio y Solicitar SSL</button>';
                echo '<hr style="border:none;border-top:1px solid #e2e8f0;margin:16px 0;" />';
                echo '<button class="sd-btn sd-btn-primary" onclick="requestSSL()" style="background:#0ea5e9;">&#128274; Solicitar Certificado SSL</button>';
                echo '<p style="font-size:0.82em;color:#64748b;margin-top:6px;">Solicitar/renovar certificado SSL independientemente (sin crear subdominio).</p>';
                break;

            case 17:
                echo '<p>Genera un archivo ZIP con la configuracion y datos del sistema para respaldo.</p>';
                echo '<p style="font-size:0.85em;color:#64748b;margin-bottom:8px;">Contenido del backup: <code>conf/</code>, <code>data/media/</code>, <code>data/meta/</code>, <code>data/pages/</code>, favicons e imagenes de templates, logos smileys.</p>';

                // Estimacion de tamano
                echo '<div id="step17_estimate" style="margin-bottom:12px;"></div>';
                echo '<button class="sd-btn sd-btn-sm sd-btn-outline" onclick="estimateBackup()" style="margin-bottom:12px;">Estimar Tamano</button> ';
                echo '<button class="sd-btn sd-btn-primary" onclick="executeStep(17)">Crear Copia de Seguridad</button>';

                // Div para mostrar resultado de backups individuales
                echo '<div id="step17_result" style="margin-top:10px;"></div>';

                // --- Seccion Plugins ---
                echo '<div style="margin-top:16px;padding-top:14px;border-top:1px solid #e2e8f0;">';
                echo '<h4 style="margin:0 0 8px;color:#334155;">&#128268; Copia de Plugins</h4>';
                echo '<p style="font-size:0.85em;color:#64748b;margin-bottom:8px;">Genera un ZIP con el contenido completo de <code>lib/plugins/</code>.</p>';
                echo '<div id="step17_estimate_plugins" style="margin-bottom:12px;"></div>';
                echo '<button class="sd-btn sd-btn-sm sd-btn-outline" onclick="estimateBackupPlugins()" style="margin-bottom:12px;">Estimar Tamano</button> ';
                echo '<button class="sd-btn sd-btn-primary" onclick="backupPlugins()">Crear Copia Plugins</button>';
                echo '</div>';

                // --- Seccion Configuracion ---
                echo '<div style="margin-top:16px;padding-top:14px;border-top:1px solid #e2e8f0;">';
                echo '<h4 style="margin:0 0 8px;color:#334155;">&#9881; Copia de Configuracion</h4>';
                echo '<p style="font-size:0.85em;color:#64748b;margin-bottom:8px;">Genera un ZIP con: <code>conf/</code>, logos, favicons, imagenes de templates y <code>data/meta/</code>.</p>';
                echo '<div id="step17_estimate_config" style="margin-bottom:12px;"></div>';
                echo '<button class="sd-btn sd-btn-sm sd-btn-outline" onclick="estimateBackupConfig()" style="margin-bottom:12px;">Estimar Tamano</button> ';
                echo '<button class="sd-btn sd-btn-primary" onclick="backupConfig()">Crear Copia Configuracion</button>';
                echo '</div>';

                // --- Seccion Pagina Web ---
                echo '<div style="margin-top:16px;padding-top:14px;border-top:1px solid #e2e8f0;">';
                echo '<h4 style="margin:0 0 8px;color:#334155;">&#127760; Pagina Web</h4>';
                echo '<p style="font-size:0.85em;color:#64748b;margin-bottom:8px;">Genera un ZIP de la carpeta <code>web/</code> del subdominio.</p>';
                echo '<div id="step17_estimate_web" style="margin-bottom:12px;"></div>';
                echo '<button class="sd-btn sd-btn-sm sd-btn-outline" onclick="estimateBackupWeb()" style="margin-bottom:12px;">Estimar Tamano</button> ';
                echo '<button class="sd-btn sd-btn-primary" onclick="backupWeb()">Crear Copia Web</button>';
                echo '</div>';

                // Lista de backups existentes
                echo '<div style="margin-top:20px;border-top:1px solid #e2e8f0;padding-top:16px;">';
                echo '<h4 style="margin:0 0 10px;color:#334155;">Copias de seguridad disponibles</h4>';
                echo '<div id="step17_backup_list"><p style="color:#94a3b8;font-size:0.88em;">Cargando...</p></div>';
                echo '</div>';
                break;

            case 18:
                // ZONA DE PELIGRO
                $depTarget = $dep['target_path'] ?? '';
                $depModeCleanup = $dep['mode'] ?? 'new';
                $fullDomainCleanup = $depSubdomain . '.' . helper_plugin_siedeployer::$rootDomain;

                echo '<p style="color:#991b1b;font-size:0.92em;margin:0 0 14px;">Estas acciones son <strong>irreversibles</strong>. Seleccione las operaciones que desea realizar:</p>';

                // Checkbox: Eliminar subdominio
                echo '<div style="margin-bottom:10px;">';
                echo '<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer;">';
                echo '<input type="checkbox" id="cleanup_subdomain" style="margin-top:3px;" />';
                echo '<div>';
                echo '<strong style="color:#991b1b;">Eliminar subdominio de cPanel</strong>';
                echo '<p style="color:#7f1d1d;font-size:0.84em;margin:2px 0 0;">Elimina <code>' . hsc($fullDomainCleanup) . '</code> de cPanel y comenta la linea <code>baseurl</code> en conf/local.php. El certificado SSL se invalida automaticamente.</p>';
                echo '</div></label></div>';

                // Checkbox: Eliminar carpeta
                echo '<div style="margin-bottom:10px;">';
                echo '<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer;">';
                echo '<input type="checkbox" id="cleanup_folder" style="margin-top:3px;" />';
                echo '<div>';
                echo '<strong style="color:#991b1b;">Eliminar carpeta del sistema</strong>';
                echo '<p style="color:#7f1d1d;font-size:0.84em;margin:2px 0 0;">Elimina permanentemente <code>' . hsc($depTarget) . '</code> y todo su contenido (paginas, media, base de datos, configuracion).</p>';
                echo '</div></label></div>';

                // Checkbox: Eliminar registro
                echo '<div style="margin-bottom:14px;">';
                echo '<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer;">';
                echo '<input type="checkbox" id="cleanup_record" style="margin-top:3px;" />';
                echo '<div>';
                echo '<strong style="color:#991b1b;">Eliminar registro del despliegue</strong>';
                echo '<p style="color:#7f1d1d;font-size:0.84em;margin:2px 0 0;">Elimina este despliegue y sus logs de la base de datos del plugin. Se redirigira a la lista de despliegues.</p>';
                echo '</div></label></div>';

                // Confirmacion
                echo '<div style="border-top:1px solid #fca5a5;padding-top:14px;margin-top:14px;">';
                echo '<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin-bottom:10px;">';
                echo '<input type="checkbox" id="cleanup_confirm" />';
                echo '<strong style="color:#dc2626;">Confirmo que entiendo que estas acciones son irreversibles</strong>';
                echo '</label>';

                echo '<button class="sd-btn sd-btn-danger" onclick="executeCleanup()" id="cleanup_btn" disabled>Ejecutar Limpieza</button>';
                echo '<div id="cleanup_log" style="margin-top:12px;display:none;"></div>';
                echo '</div>';
                break;

            case 19:
                // EDITOR DOCUMENTAL
                echo '<p>Editor masivo de datos corporativos. Lee las variables del <strong>Manual de Calidad</strong> del sistema desplegado y permite modificarlas en todos los documentos de forma masiva.</p>';
                echo '<p style="font-size:0.85em;color:#64748b;margin-bottom:12px;">Carpetas escaneadas: administrativa, comercial, compartidos, manual_funciones, operativo, rrhh, administrativo, hse, hseq, ambiental, calidad, financiero, gerencia, seguridad, sistemas, sst, ssta, produccion, contabilidad.</p>';

                echo '<button class="sd-btn sd-btn-primary" onclick="loadDocEditorData(' . (int)$deploymentId . ')">Cargar datos del sistema</button>';

                echo '<div id="step19_editor" style="margin-top:16px;"></div>';
                echo '<div id="step19_results" style="margin-top:16px;"></div>';
                break;
        }

        echo '</div>'; // sd-step-form

        // Area de log
        echo '<div class="sd-step-log" id="step_log_' . $num . '"></div>';

        // Boton re-ejecutar si ya esta completado
        if ($step['status'] === 'completed' || $step['status'] === 'failed') {
            echo '<button class="sd-btn sd-btn-sm sd-btn-outline" onclick="loadStepLog(' . $num . ')">Ver Log</button>';
        }
    }

    // =========================================================================
    //  JAVASCRIPT
    // =========================================================================

    protected function printScripts()
    {
        ?>
        <script>
        // Garantizar que DOKU_BASE exista (puede no estar definido si defer_js=1)
        if (typeof DOKU_BASE === 'undefined') { var DOKU_BASE = "<?php echo DOKU_BASE; ?>"; }
        var SD = {
            sectok: (document.getElementById("sd_sectok") || document.getElementById("sd_sectok_global") || {}).value || "",
            deploymentId: document.getElementById("sd_deployment_id") ? document.getElementById("sd_deployment_id").value : 0,
            ajaxUrl: "<?php echo DOKU_BASE; ?>lib/exe/ajax.php",
            basePath: "<?php echo addslashes($this->helper->getBasePath()); ?>",
            deploymentName: document.getElementById("sd_deployment_name") ? document.getElementById("sd_deployment_name").value : "",
            deploymentSubdomain: document.getElementById("sd_deployment_subdomain") ? document.getElementById("sd_deployment_subdomain").value : "",
            deploymentMode: document.getElementById("sd_deployment_mode") ? document.getElementById("sd_deployment_mode").value : "new"
        };

        // === AUTO-FILL: subdominio -> ruta destino + preview ===
        function updateTargetFromSubdomain() {
            var subInput = document.getElementById("deploy_subdomain");
            if (!subInput) return;
            var sub = subInput.value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "");
            // Actualizar preview del subdominio
            var preview = document.getElementById("subdomain_preview");
            if (preview) preview.textContent = sub || "acme";
            // Actualizar preview del dominio
            var domSel = document.getElementById("deploy_domain");
            var domPreview = document.getElementById("domain_preview");
            if (domSel && domPreview) domPreview.textContent = domSel.value;
            // Actualizar display de ruta destino (siempre desde Subdominio)
            var display = document.getElementById("deploy_target_name_display");
            if (display) display.textContent = sub || "...";
            // Actualizar hidden input
            var targetInput = document.getElementById("deploy_target");
            var cb = document.getElementById("deploy_target_auto");
            if (cb && cb.checked && targetInput) {
                targetInput.value = sub ? (SD.basePath + sub) : "";
            }
        }
        (function() {
            var subInput = document.getElementById("deploy_subdomain");
            if (!subInput) return;

            // Escuchar multiples eventos para robustez
            subInput.addEventListener("input", updateTargetFromSubdomain);
            subInput.addEventListener("change", updateTargetFromSubdomain);
            subInput.addEventListener("keyup", updateTargetFromSubdomain);
        })();

        // === TOGGLE MODO: Nuevo vs Modificar ===
        function toggleDeployMode() {
            var radios = document.getElementsByName("deploy_mode_radio");
            var mode = "new";
            for (var i = 0; i < radios.length; i++) {
                if (radios[i].checked) { mode = radios[i].value; break; }
            }

            var modeInput = document.getElementById("deploy_mode");
            if (modeInput) modeInput.value = mode;

            var warning = document.getElementById("deploy_modify_warning");
            var subField = document.getElementById("deploy_subdomain_field");
            var targetField = document.getElementById("deploy_target_field");
            var nameHint = document.getElementById("deploy_name_hint");
            var submitBtn = document.getElementById("deploy_submit_btn");
            var sourceLabel = document.getElementById("deploy_source_label");

            if (mode === "modify") {
                if (warning) warning.style.display = "block";
                if (subField) subField.style.display = "none";
                if (targetField) targetField.style.display = "none";
                if (nameHint) nameHint.style.display = "block";
                if (submitBtn) submitBtn.textContent = "Iniciar Modificacion";
                if (sourceLabel) sourceLabel.textContent = "Sistema a modificar";
            } else {
                if (warning) warning.style.display = "none";
                if (subField) subField.style.display = "block";
                if (targetField) targetField.style.display = "block";
                if (nameHint) nameHint.style.display = "none";
                if (submitBtn) submitBtn.textContent = "Crear Despliegue";
                if (sourceLabel) sourceLabel.textContent = "Ruta origen (sistema a duplicar)";
            }
        }

        function toggleTargetAuto() {
            var cb = document.getElementById("deploy_target_auto");
            var targetInput = document.getElementById("deploy_target");
            var subInput = document.getElementById("deploy_subdomain");
            if (cb.checked) {
                var sub = subInput ? subInput.value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "") : "";
                targetInput.value = sub ? (SD.basePath + sub) : "";
            }
        }

        // === VALIDACION FORMULARIO CREAR ===
        function validateCreateForm() {
            var nameInput = document.getElementById("deploy_name");
            var targetInput = document.getElementById("deploy_target");
            var sourceInput = document.getElementById("deploy_source");
            var subInput = document.getElementById("deploy_subdomain");
            var modeInput = document.getElementById("deploy_mode");
            var mode = modeInput ? modeInput.value : "new";

            if (!nameInput || !nameInput.value.trim()) {
                alert("La Razon Social es obligatoria.");
                return false;
            }
            if (!sourceInput || !sourceInput.value.trim()) {
                alert(mode === "modify"
                    ? "Seleccione un sistema a modificar. Use 'Escanear' para ver los sistemas disponibles."
                    : "La ruta origen es obligatoria. Use 'Escanear' para seleccionar un sistema.");
                return false;
            }

            if (mode === "modify") {
                // En modo modificar: target = source, subdomain = nombre carpeta
                targetInput.value = sourceInput.value;
                var parts = sourceInput.value.replace(/\/+$/, "").split("/");
                var folderName = parts[parts.length - 1] || "";
                if (subInput) subInput.value = folderName;
            } else {
                if (!subInput || !subInput.value.trim()) {
                    alert("El subdominio es obligatorio.");
                    return false;
                }
                // Asegurar que deploy_target tiene valor desde subdominio
                var cb = document.getElementById("deploy_target_auto");
                if (cb && cb.checked) {
                    var sub = subInput.value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "");
                    if (!sub) {
                        alert("El subdominio no es valido para generar la ruta destino.");
                        return false;
                    }
                    targetInput.value = SD.basePath + sub;
                }
                if (!targetInput || !targetInput.value.trim()) {
                    alert("La ruta destino es obligatoria.");
                    return false;
                }
            }

            return true;
        }

        // === ELIMINAR DESPLIEGUE ===
        function deleteDeployment(id, name) {
            if (!confirm("Eliminar el despliegue '" + name + "'?\n\nEsto solo elimina el registro de la base de datos, NO borra archivos del servidor.")) return;

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_delete_deployment");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", id);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        location.reload();
                    } else {
                        alert("Error: " + (data.error || "desconocido"));
                    }
                })
                .catch(function(err) {
                    alert("Error de conexion: " + err.message);
                });
        }

        function toggleStep(num) {
            var body = document.getElementById("step_body_" + num);
            if (body) {
                body.style.display = body.style.display === "none" ? "block" : "none";
            }
        }

        // === PASO 15: TOGGLE MODO ZIP ===
        function toggleStep15Mode() {
            var radios = document.getElementsByName("step15_mode");
            var mode = "individual";
            for (var i = 0; i < radios.length; i++) {
                if (radios[i].checked) { mode = radios[i].value; break; }
            }
            var indDiv = document.getElementById("step15_individual");
            var uniDiv = document.getElementById("step15_unified");
            if (indDiv) indDiv.style.display = mode === "individual" ? "block" : "none";
            if (uniDiv) uniDiv.style.display = mode === "unified" ? "block" : "none";
        }

        // === SCANNER ===
        function scanServer() {
            var box = document.getElementById("scan_results");
            box.innerHTML = "<p style='color:#f59e0b;font-weight:600;'>Escaneando sistemas...</p>";
            box.style.display = "block";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_scan_server");
            fd.append("sectok", SD.sectok);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.error) {
                        box.innerHTML = "<p style='color:#dc2626;'><strong>Error:</strong> " + data.error + "</p>"
                            + (data.base_path ? "<p style='font-size:0.85em;color:#64748b;'>Ruta base: <code>" + data.base_path + "</code></p>" : "")
                            + "<p style='font-size:0.85em;'>Configure en Configuracion &gt; siedeployer &gt; server_base_path</p>";
                        return;
                    }
                    if (!data.systems || data.systems.length === 0) {
                        box.innerHTML = "<p>No se encontraron sistemas en: <code>" + (data.base_path || "") + "</code></p>";
                        return;
                    }
                    var html = "<p><strong>" + data.systems.length + " sistemas encontrados:</strong></p><ul class='sd-scan-list'>";
                    data.systems.forEach(function(s) {
                        html += "<li><a href='#' data-path='" + s.path.replace(/'/g, "&#39;") + "'"
                            + " data-title='" + (s.title || "").replace(/'/g, "&#39;") + "'"
                            + " data-name='" + s.name.replace(/'/g, "&#39;") + "'"
                            + " onclick='selectSource(this);return false;'>"
                            + s.name + "</a> <span class='sd-scan-title'>" + (s.title || "") + "</span></li>";
                    });
                    html += "</ul>";
                    box.innerHTML = html;
                })
                .catch(function(err) {
                    box.innerHTML = "<p style='color:#dc2626;'>Error de conexion: " + err.message + "</p>";
                });
        }

        function selectSource(el) {
            var path = el.getAttribute("data-path");
            var title = el.getAttribute("data-title") || "";
            var name = el.getAttribute("data-name") || "";

            document.getElementById("deploy_source").value = path;
            document.getElementById("scan_results").style.display = "none";

            // Detectar modo actual
            var modeInput = document.getElementById("deploy_mode");
            var mode = modeInput ? modeInput.value : "new";

            // Auto-fill razon social desde title (parte antes de " - ")
            if (title) {
                var razonSocial = title;
                var dashIndex = title.indexOf(" - ");
                if (dashIndex > 0) {
                    razonSocial = title.substring(0, dashIndex).trim();
                }
                // Capitalizar cada palabra
                razonSocial = razonSocial.replace(/\w\S*/g, function(txt) {
                    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
                });
                document.getElementById("deploy_name").value = razonSocial;
            }

            if (mode === "modify") {
                // En modo modificar: subdominio = nombre carpeta, target = source
                var subInput = document.getElementById("deploy_subdomain");
                var targetInput = document.getElementById("deploy_target");
                if (subInput) subInput.value = name;
                if (targetInput) targetInput.value = path;
            }
        }

        // === GENERAR TOKEN IA ===
        function generateToken() {
            var statusEl = document.getElementById("step4_status");
            var tokenInput = document.getElementById("step4_token");
            statusEl.innerHTML = "<span style='color:#f59e0b;font-weight:600;'>Generando token...</span>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_generate_token");
            fd.append("sectok", SD.sectok);
            fd.append("project_name", SD.deploymentSubdomain || SD.deploymentName || "");

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success && data.token) {
                        tokenInput.value = data.token;
                        statusEl.innerHTML = "<span style='color:#16a34a;font-weight:600;'>Token generado (" + data.token.substring(0, 20) + "...)</span>";
                    } else {
                        statusEl.innerHTML = "<span style='color:#dc2626;'>Error: " + (data.error || "desconocido") + "</span>";
                    }
                })
                .catch(function(err) {
                    statusEl.innerHTML = "<span style='color:#dc2626;'>Error: " + err.message + "</span>";
                });
        }

        // === REINICIAR DESPLIEGUE ===
        function resetDeployment(id) {
            if (!confirm("Reiniciar TODOS los pasos de este despliegue? Los archivos ya copiados en el servidor NO se eliminan.")) return;

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_reset_deployment");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", id);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        location.reload();
                    } else {
                        alert("Error: " + (data.error || "desconocido"));
                    }
                });
        }

        // === DESCARGAR ARCHIVOS ACTUALES POR PASO ===
        function downloadStepFiles(num) {
            var url = SD.ajaxUrl
                + '?call=plugin_siedeployer_download_step_files'
                + '&deployment_id=' + SD.deploymentId
                + '&step_number=' + num
                + '&sectok=' + SD.sectok;
            window.location.href = url;
        }

        // === EJECUTAR PASOS ===
        function executeStep(num) {
            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_execute_step" + num);
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            switch (num) {
                case 2:
                    fd.append("title", document.getElementById("step2_title").value);
                    fd.append("baseurl", document.getElementById("step2_baseurl").value);
                    fd.append("template", document.getElementById("step2_template").value);
                    break;
                case 3:
                    fd.append("email", document.getElementById("step3_email").value);
                    break;
                case 4:
                    fd.append("token_ia", document.getElementById("step4_token").value);
                    break;
                case 6:
                    var lp = document.getElementById("step6_logo_png");
                    var lg = document.getElementById("step6_logo_gif");
                    if (lp.files[0]) fd.append("logo_png", lp.files[0]);
                    if (lg.files[0]) fd.append("logo_gif", lg.files[0]);
                    break;
                case 8:
                    var dp = document.getElementById("step8_dokuwiki_png");
                    var fi = document.getElementById("step8_favicon_ico");
                    if (dp.files[0]) fd.append("dokuwiki_png", dp.files[0]);
                    if (fi.files[0]) fd.append("favicon_ico", fi.files[0]);
                    break;
                case 9:
                    fd.append("acl_content", document.getElementById("step9_acl").value);
                    break;
                case 10:
                    fd.append("users_content", document.getElementById("step10_users").value);
                    break;
                case 11:
                    var f11 = document.getElementById("step11_file");
                    if (f11.files[0]) fd.append("favicon_ico", f11.files[0]);
                    break;
                case 12:
                    var f12 = document.getElementById("step12_files");
                    for (var i = 0; i < f12.files.length; i++) {
                        fd.append("file_" + i, f12.files[i]);
                    }
                    break;
                case 13:
                    var f13 = document.getElementById("step13_file");
                    if (f13.files[0]) fd.append("favicon_ico", f13.files[0]);
                    break;
                case 14:
                    var f14 = document.getElementById("step14_files");
                    for (var i = 0; i < f14.files.length; i++) {
                        fd.append("file_" + i, f14.files[i]);
                    }
                    break;
                case 15:
                    var step15Radios = document.getElementsByName("step15_mode");
                    var step15Mode = "individual";
                    for (var r = 0; r < step15Radios.length; r++) {
                        if (step15Radios[r].checked) { step15Mode = step15Radios[r].value; break; }
                    }
                    fd.append("step15_mode", step15Mode);
                    if (step15Mode === "unified") {
                        var zipAll = document.getElementById("step15_zip_all");
                        if (zipAll && zipAll.files[0]) fd.append("zip_all", zipAll.files[0]);
                    } else {
                        var folders = ["administrativa", "comercial", "compartidos", "operativo", "rrhh"];
                        folders.forEach(function(f) {
                            var inp = document.getElementById("step15_zip_" + f);
                            if (inp && inp.files[0]) fd.append("zip_" + f, inp.files[0]);
                        });
                    }
                    break;
                case 16:
                    var domSel = document.getElementById("step16_domain");
                    if (domSel) fd.append("root_domain", domSel.value);
                    break;
            }

            var logDiv = document.getElementById("step_log_" + num);
            logDiv.innerHTML = "<p class='sd-loading'>Ejecutando paso " + num + "...</p>";
            logDiv.style.display = "block";

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        logDiv.innerHTML = "<p class='sd-log-ok'>" + data.message + "</p>";
                        updateStepStatus(num, "completed");
                        // Paso 8 tambien completa 11 y 13
                        if (num === 8) {
                            updateStepStatus(11, "completed");
                            updateStepStatus(13, "completed");
                        }
                        // Paso 16: actualizar hidden field con el dominio usado
                        if (num === 16) {
                            var domSel16 = document.getElementById("step16_domain");
                            var rootDomHidden = document.getElementById("sd_deployment_rootdomain");
                            if (domSel16 && rootDomHidden) {
                                rootDomHidden.value = domSel16.value;
                            }
                        }
                        // Paso 17: recargar lista de backups
                        if (num === 17) {
                            loadBackupList();
                        }
                    } else {
                        logDiv.innerHTML = "<p class='sd-log-err'>Error: " + (data.error || "desconocido") + "</p>";
                        updateStepStatus(num, "failed");
                    }
                    loadStepLog(num);
                })
                .catch(function(err) {
                    logDiv.innerHTML = "<p class='sd-log-err'>Error de conexion: " + err.message + "</p>";
                });
        }

        function executeStepConfirm(num, checkboxId) {
            var cb = document.getElementById(checkboxId);
            if (!cb || !cb.checked) {
                alert("Debe confirmar marcando la casilla antes de ejecutar.");
                return;
            }
            executeStep(num);
        }

        function updateStepStatus(num, status) {
            var step = document.querySelector('.sd-step[data-step="' + num + '"]');
            if (step) {
                step.className = "sd-step sd-step-" + status;
                var icon = step.querySelector(".sd-step-icon");
                if (icon) {
                    if (status === "completed") icon.innerHTML = '<span class="sd-icon-ok">&#10004;</span>';
                    else if (status === "failed") icon.innerHTML = '<span class="sd-icon-err">&#10008;</span>';
                }
            }
        }

        // === ELIMINAR SUBDOMINIO ===
        function deleteSubdomain() {
            var cb = document.getElementById("step16_delete_confirm");
            if (!cb || !cb.checked) {
                alert("Debe confirmar marcando la casilla antes de ejecutar.");
                return;
            }

            var sub = SD.deploymentSubdomain || "";
            // BUG8-FIX: usar rootDomain dinamico del despliegue, no hardcodeado
            var rootDomainEl = document.getElementById("sd_deployment_rootdomain");
            var rootDomain = rootDomainEl ? rootDomainEl.value : "systemsie.site";
            if (!confirm("Eliminar el subdominio '" + sub + "." + rootDomain + "' y su certificado SSL?\n\nLa carpeta y datos del sistema NO seran eliminados.")) return;

            var logDiv = document.getElementById("step_log_16");
            logDiv.innerHTML = "<p class='sd-loading'>Eliminando subdominio...</p>";
            logDiv.style.display = "block";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_delete_subdomain");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        logDiv.innerHTML = "<p class='sd-log-ok'>" + data.message + "</p>";
                        updateStepStatus(16, "completed");
                    } else {
                        logDiv.innerHTML = "<p class='sd-log-err'>Error: " + (data.error || "desconocido") + "</p>";
                        updateStepStatus(16, "failed");
                    }
                    loadStepLog(16);
                })
                .catch(function(err) {
                    logDiv.innerHTML = "<p class='sd-log-err'>Error de conexion: " + err.message + "</p>";
                });
        }

        // === STEP 16: PREVIEW DOMINIO SELECCIONADO ===
        function step16UpdatePreview() {
            var domSel = document.getElementById("step16_domain");
            var preview = document.getElementById("step16_preview");
            if (domSel && preview) {
                preview.textContent = (SD.deploymentSubdomain || "sub") + "." + domSel.value;
            }
        }

        // === SOLICITAR CERTIFICADO SSL ===
        function requestSSL() {
            var logDiv = document.getElementById("step_log_16");
            logDiv.innerHTML = "<p class='sd-loading'>Solicitando certificado SSL via AutoSSL...</p>";
            logDiv.style.display = "block";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_request_ssl");
            fd.append("sectok", SD.sectok);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        logDiv.innerHTML = "<p class='sd-log-ok'>" + data.message + "</p><p style='font-size:0.85em;color:#64748b;'>El certificado puede tardar 5-15 minutos en generarse.</p>";
                    } else {
                        logDiv.innerHTML = "<p class='sd-log-err'>" + (data.message || data.error || "Error desconocido") + "</p>";
                    }
                })
                .catch(function(err) {
                    logDiv.innerHTML = "<p class='sd-log-err'>Error de conexion: " + err.message + "</p>";
                });
        }

        // === ZONA DE PELIGRO: LIMPIEZA COMPLETA ===
        (function() {
            var confirmCb = document.getElementById("cleanup_confirm");
            var cleanupBtn = document.getElementById("cleanup_btn");
            if (confirmCb && cleanupBtn) {
                confirmCb.addEventListener("change", function() {
                    cleanupBtn.disabled = !this.checked;
                });
            }
        })();

        function executeCleanup() {
            var confirmCb = document.getElementById("cleanup_confirm");
            if (!confirmCb || !confirmCb.checked) {
                alert("Debe confirmar que entiende que las acciones son irreversibles.");
                return;
            }

            var delSub = document.getElementById("cleanup_subdomain").checked;
            var delFolder = document.getElementById("cleanup_folder").checked;
            var delRecord = document.getElementById("cleanup_record").checked;

            if (!delSub && !delFolder && !delRecord) {
                alert("Seleccione al menos una opcion de limpieza.");
                return;
            }

            var actions = [];
            if (delSub) actions.push("<li>&#128308; Eliminar subdominio <strong>" + SD.deploymentSubdomain + "." + (document.getElementById("sd_deployment_rootdomain") ? document.getElementById("sd_deployment_rootdomain").value : "systemsie.site") + "</strong> de cPanel</li>");
            if (delFolder) actions.push("<li>&#128308; Eliminar carpeta <strong>" + SD.deploymentName + "</strong> y TODO su contenido</li>");
            if (delRecord) actions.push("<li>&#128308; Eliminar registro del despliegue de la base de datos</li>");

            // Crear modal de advertencia final
            var overlay = document.createElement("div");
            overlay.id = "cleanup_modal_overlay";
            overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:9999;display:flex;align-items:center;justify-content:center;";

            var modal = document.createElement("div");
            modal.style.cssText = "background:#fff;border-radius:12px;max-width:520px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,0.3);overflow:hidden;";

            modal.innerHTML = ""
                + "<div style='background:#dc2626;padding:16px 20px;display:flex;align-items:center;gap:10px;'>"
                + "  <span style='font-size:2em;'>&#9888;</span>"
                + "  <h3 style='color:white;margin:0;font-size:1.15em;'>ADVERTENCIA FINAL</h3>"
                + "</div>"
                + "<div style='padding:20px;'>"
                + "  <p style='color:#991b1b;font-weight:700;font-size:1em;margin:0 0 12px;'>Esta a punto de ejecutar acciones IRREVERSIBLES:</p>"
                + "  <ul style='list-style:none;padding:0;margin:0 0 16px;'>" + actions.join("") + "</ul>"
                + "  <div style='background:#fef2f2;border:1px solid #fca5a5;border-radius:8px;padding:12px;margin-bottom:16px;'>"
                + "    <p style='color:#991b1b;margin:0;font-size:0.9em;'><strong>&#9888; Los datos eliminados NO se pueden recuperar.</strong><br>Asegurese de tener respaldos si es necesario.</p>"
                + "  </div>"
                + "  <div style='display:flex;gap:10px;justify-content:flex-end;'>"
                + "    <button id='cleanup_modal_cancel' style='padding:10px 24px;border:1px solid #d1d5db;border-radius:8px;background:#fff;color:#374151;font-weight:600;cursor:pointer;font-size:0.95em;'>Cancelar</button>"
                + "    <button id='cleanup_modal_accept' style='padding:10px 24px;border:none;border-radius:8px;background:#dc2626;color:white;font-weight:700;cursor:pointer;font-size:0.95em;'>Eliminar Permanentemente</button>"
                + "  </div>"
                + "</div>";

            overlay.appendChild(modal);
            document.body.appendChild(overlay);

            // Cancelar
            document.getElementById("cleanup_modal_cancel").onclick = function() {
                document.body.removeChild(overlay);
            };
            overlay.onclick = function(e) {
                if (e.target === overlay) document.body.removeChild(overlay);
            };

            // Aceptar -> ejecutar
            document.getElementById("cleanup_modal_accept").onclick = function() {
                document.body.removeChild(overlay);
                _doCleanup(delSub, delFolder, delRecord);
            };

            return;
        }

        function _doCleanup(delSub, delFolder, delRecord) {
            var logDiv = document.getElementById("cleanup_log");
            logDiv.style.display = "block";
            logDiv.innerHTML = "<p class='sd-loading'>Ejecutando limpieza...</p>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_cleanup_deployment");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);
            fd.append("delete_subdomain", delSub ? "1" : "0");
            fd.append("delete_folder", delFolder ? "1" : "0");
            fd.append("delete_record", delRecord ? "1" : "0");

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    var html = "";
                    if (data.log && data.log.length) {
                        data.log.forEach(function(line) {
                            var cls = "sd-log-info";
                            if (line.indexOf("[OK]") === 0) cls = "sd-log-ok";
                            else if (line.indexOf("[ERROR]") === 0) cls = "sd-log-err";
                            else if (line.indexOf("[INFO]") === 0) cls = "sd-log-info";
                            html += "<p class='" + cls + "' style='margin:2px 0;font-size:0.88em;'>" + line + "</p>";
                        });
                    }
                    if (data.success) {
                        html += "<p class='sd-log-ok' style='font-weight:700;margin-top:8px;'>Limpieza completada exitosamente.</p>";
                    } else {
                        html += "<p class='sd-log-err' style='font-weight:700;margin-top:8px;'>Limpieza completada con errores.</p>";
                    }
                    logDiv.innerHTML = html;

                    // Si se elimino el registro, redirigir a la lista
                    if (delRecord && data.success) {
                        logDiv.innerHTML += "<p style='color:#64748b;font-size:0.85em;'>Redirigiendo a la lista de despliegues en 3 segundos...</p>";
                        setTimeout(function() {
                            window.location.href = SD.ajaxUrl.replace("lib/exe/ajax.php", "") + "doku.php?do=admin&page=siedeployer&migkey=" + (new URLSearchParams(window.location.search).get("migkey") || "");
                        }, 3000);
                    }
                })
                .catch(function(err) {
                    logDiv.innerHTML = "<p class='sd-log-err'>Error de conexion: " + err.message + "</p>";
                });
        }

        // === PASO 17: COPIA DE SEGURIDAD ===
        function estimateBackup() {
            var el = document.getElementById("step17_estimate");
            el.innerHTML = "<p style='color:#f59e0b;font-weight:600;'>Estimando tamano...</p>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_estimate_backup");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        var color = data.exceeds ? "#dc2626" : (data.size_mb > 2048 ? "#f59e0b" : "#16a34a");
                        var icon = data.exceeds ? "&#9888;" : "&#128230;";
                        var msg = icon + " Tamano estimado: <strong>" + data.size_mb + " MB</strong>";
                        if (data.size_gb >= 1) msg += " (" + data.size_gb + " GB)";
                        if (data.exceeds) {
                            msg += "<br><span style='color:#dc2626;font-weight:700;'>EXCEDE EL LIMITE DE 12 GB. No se puede crear el backup.</span>";
                        } else if (data.size_mb > 2048) {
                            msg += "<br><span style='color:#f59e0b;'>Backup grande. El proceso puede tomar varios minutos.</span>";
                        }
                        el.innerHTML = "<div style='background:" + (data.exceeds ? "#fef2f2;border:1px solid #fca5a5" : "#f0fdf4;border:1px solid #86efac") + ";border-radius:8px;padding:10px;font-size:0.9em;color:" + color + ";'>" + msg + "</div>";
                    } else {
                        el.innerHTML = "<p style='color:#dc2626;'>Error: " + (data.error || "desconocido") + "</p>";
                    }
                })
                .catch(function(err) {
                    el.innerHTML = "<p style='color:#dc2626;'>Error de conexion</p>";
                });
        }

        function loadBackupList() {
            var el = document.getElementById("step17_backup_list");
            if (!el) return;

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_list_backups");
            fd.append("sectok", SD.sectok);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (!data.success || !data.backups || data.backups.length === 0) {
                        el.innerHTML = "<p style='color:#94a3b8;font-size:0.88em;'>No hay copias de seguridad disponibles.</p>";
                        return;
                    }

                    var backupDir = data.backup_dir || "";
                    var html = "<table style='width:100%;font-size:0.88em;border-collapse:collapse;'>";
                    html += "<tr style='border-bottom:2px solid #e2e8f0;'><th style='text-align:left;padding:6px;'>Archivo</th><th style='text-align:right;padding:6px;'>Tamano</th><th style='text-align:left;padding:6px;'>Fecha</th><th style='text-align:center;padding:6px;'>Acciones</th></tr>";

                    data.backups.forEach(function(b) {
                        html += "<tr style='border-bottom:1px solid #f1f5f9;'>";
                        html += "<td style='padding:6px;'><code style='font-size:0.9em;'>" + b.filename + "</code></td>";
                        html += "<td style='padding:6px;text-align:right;'>" + b.size_mb + " MB</td>";
                        html += "<td style='padding:6px;color:#64748b;'>" + b.date + "</td>";
                        html += "<td style='padding:6px;text-align:center;white-space:nowrap;'>";
                        html += "<button class='sd-btn sd-btn-sm' onclick='downloadBackup(\"" + b.filename + "\")' title='Descargar'>&#11015; Descargar</button> ";
                        html += "<button class='sd-btn sd-btn-sm' style='color:#dc2626;border-color:#dc2626;' onclick='deleteBackup(\"" + b.filename + "\")' title='Eliminar'>&#10008;</button>";
                        html += "</td></tr>";
                    });

                    html += "</table>";
                    html += "<p style='font-size:0.8em;color:#94a3b8;margin-top:6px;'>Carpeta: " + backupDir + "</p>";
                    el.innerHTML = html;
                })
                .catch(function(err) {
                    el.innerHTML = "<p style='color:#dc2626;'>Error cargando lista</p>";
                });
        }

        function downloadBackup(filename) {
            // Descarga via AJAX endpoint (no URL directa, SD.basePath es ruta de filesystem)
            var url = SD.ajaxUrl
                + '?call=plugin_siedeployer_download_backup'
                + '&sectok=' + encodeURIComponent(SD.sectok)
                + '&filename=' + encodeURIComponent(filename);
            window.open(url, "_blank");
        }

        function deleteBackup(filename) {
            if (!confirm("Eliminar backup '" + filename + "'?\n\nEsta accion es irreversible.")) return;

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_delete_backup");
            fd.append("sectok", SD.sectok);
            fd.append("filename", filename);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        loadBackupList();
                    } else {
                        alert("Error: " + (data.error || "desconocido"));
                    }
                })
                .catch(function(err) {
                    alert("Error de conexion");
                });
        }

        // Cargar lista de backups al abrir paso 17
        (function() {
            var origToggle = window.toggleStep;
            window.toggleStep = function(num) {
                origToggle(num);
                if (num === 17) {
                    loadBackupList();
                }
            };
        })();

        // --- Backup Plugins ---

        function estimateBackupPlugins() {
            var el = document.getElementById("step17_estimate_plugins");
            el.innerHTML = "<p style='color:#f59e0b;font-weight:600;'>Estimando tamano de lib/plugins/...</p>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_estimate_backup_plugins");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        if (!data.exists) {
                            el.innerHTML = "<div style='background:#fef2f2;border:1px solid #fca5a5;border-radius:8px;padding:10px;font-size:0.9em;color:#dc2626;'>La carpeta lib/plugins/ no existe en este subdominio.</div>";
                            return;
                        }
                        var color = data.exceeds ? "#dc2626" : "#16a34a";
                        var msg = "&#128268; Tamano estimado: <strong>" + data.size_mb + " MB</strong>";
                        if (data.size_gb >= 1) msg += " (" + data.size_gb + " GB)";
                        if (data.exceeds) msg += "<br><span style='color:#dc2626;font-weight:700;'>EXCEDE EL LIMITE DE 12 GB.</span>";
                        el.innerHTML = "<div style='background:#f0fdf4;border:1px solid #86efac;border-radius:8px;padding:10px;font-size:0.9em;color:" + color + ";'>" + msg + "</div>";
                    } else {
                        el.innerHTML = "<p style='color:#dc2626;'>Error: " + (data.error || "desconocido") + "</p>";
                    }
                })
                .catch(function(err) {
                    el.innerHTML = "<p style='color:#dc2626;'>Error de conexion</p>";
                });
        }

        function backupPlugins() {
            if (!confirm("Crear copia de seguridad de lib/plugins/?\n\nEsto puede tomar unos minutos.")) return;

            var resultDiv = document.getElementById("step17_result");
            if (resultDiv) resultDiv.innerHTML = '<p class="sd-loading">Creando copia de plugins...</p>';

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_backup_plugins");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-ok">' + data.message + '</div>';
                        loadBackupList();
                    } else {
                        if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || data.message || 'desconocido') + '</div>';
                    }
                })
                .catch(function(err) {
                    if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-err">Error: ' + err.message + '</div>';
                });
        }

        // --- Backup Configuracion ---

        function estimateBackupConfig() {
            var el = document.getElementById("step17_estimate_config");
            el.innerHTML = "<p style='color:#f59e0b;font-weight:600;'>Estimando tamano de configuracion...</p>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_estimate_backup_config");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        if (!data.exists) {
                            el.innerHTML = "<div style='background:#fef2f2;border:1px solid #fca5a5;border-radius:8px;padding:10px;font-size:0.9em;color:#dc2626;'>No se encontraron archivos de configuracion en este subdominio.</div>";
                            return;
                        }
                        var color = data.exceeds ? "#dc2626" : "#16a34a";
                        var msg = "&#9881; Tamano estimado: <strong>" + data.size_mb + " MB</strong>";
                        if (data.size_gb >= 1) msg += " (" + data.size_gb + " GB)";
                        if (data.exceeds) msg += "<br><span style='color:#dc2626;font-weight:700;'>EXCEDE EL LIMITE DE 12 GB.</span>";
                        el.innerHTML = "<div style='background:#f0fdf4;border:1px solid #86efac;border-radius:8px;padding:10px;font-size:0.9em;color:" + color + ";'>" + msg + "</div>";
                    } else {
                        el.innerHTML = "<p style='color:#dc2626;'>Error: " + (data.error || "desconocido") + "</p>";
                    }
                })
                .catch(function(err) {
                    el.innerHTML = "<p style='color:#dc2626;'>Error de conexion</p>";
                });
        }

        function backupConfig() {
            if (!confirm("Crear copia de seguridad de configuracion?\n\nIncluye: conf/, logos, favicons, imagenes templates y data/meta/.\nEsto puede tomar unos minutos.")) return;

            var resultDiv = document.getElementById("step17_result");
            if (resultDiv) resultDiv.innerHTML = '<p class="sd-loading">Creando copia de configuracion...</p>';

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_backup_config");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-ok">' + data.message + '</div>';
                        loadBackupList();
                    } else {
                        if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || data.message || 'desconocido') + '</div>';
                    }
                })
                .catch(function(err) {
                    if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-err">Error: ' + err.message + '</div>';
                });
        }

        // --- Backup Pagina Web ---

        function estimateBackupWeb() {
            var el = document.getElementById("step17_estimate_web");
            el.innerHTML = "<p style='color:#f59e0b;font-weight:600;'>Estimando tamano de web/...</p>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_estimate_backup_web");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        if (!data.exists) {
                            el.innerHTML = "<div style='background:#fef2f2;border:1px solid #fca5a5;border-radius:8px;padding:10px;font-size:0.9em;color:#dc2626;'>La carpeta web/ no existe en este subdominio.</div>";
                            return;
                        }
                        var color = data.exceeds ? "#dc2626" : "#16a34a";
                        var msg = "&#128230; Tamano estimado: <strong>" + data.size_mb + " MB</strong>";
                        if (data.size_gb >= 1) msg += " (" + data.size_gb + " GB)";
                        if (data.exceeds) msg += "<br><span style='color:#dc2626;font-weight:700;'>EXCEDE EL LIMITE DE 12 GB.</span>";
                        el.innerHTML = "<div style='background:#f0fdf4;border:1px solid #86efac;border-radius:8px;padding:10px;font-size:0.9em;color:" + color + ";'>" + msg + "</div>";
                    } else {
                        el.innerHTML = "<p style='color:#dc2626;'>Error: " + (data.error || "desconocido") + "</p>";
                    }
                })
                .catch(function(err) {
                    el.innerHTML = "<p style='color:#dc2626;'>Error de conexion</p>";
                });
        }

        function backupWeb() {
            if (!confirm("Crear copia de seguridad de la carpeta web/?\n\nEsto puede tomar unos minutos.")) return;

            var resultDiv = document.getElementById("step17_result");
            if (resultDiv) resultDiv.innerHTML = '<p class="sd-loading">Creando copia de web/...</p>';

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_backup_web");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-ok">' + data.message + '</div>';
                        loadBackupList();
                    } else {
                        if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || data.message || 'desconocido') + '</div>';
                    }
                })
                .catch(function(err) {
                    if (resultDiv) resultDiv.innerHTML = '<div class="sd-log-err">Error: ' + err.message + '</div>';
                });
        }

        // =================================================================
        //  EDITOR DOCUMENTAL (Paso 19)
        // =================================================================

        function loadDocEditorData(deploymentId) {
            var container = document.getElementById("step19_editor");
            if (!container) return;
            container.innerHTML = "<p style='color:#64748b;'>Cargando datos del sistema...</p>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_doceditor_load");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", deploymentId);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (!data.success) {
                        container.innerHTML = "<p style='color:#dc2626;'>Error: " + escHtml(data.error || "No se pudo cargar") + "</p>";
                        return;
                    }
                    renderDocEditorForm(data.fields, deploymentId);
                    // Mostrar diagnóstico si hay debug
                    if (data.debug) {
                        renderDocEditorDebug(data.debug);
                    }
                })
                .catch(function(e) {
                    container.innerHTML = "<p style='color:#dc2626;'>Error de conexion al cargar datos: " + escHtml(e.message || "") + "</p>";
                });
        }

        function renderDocEditorForm(fields, deploymentId) {
            var container = document.getElementById("step19_editor");
            if (!container) return;

            if (!fields || Object.keys(fields).length === 0) {
                container.innerHTML = "<p style='color:#f59e0b;'>No se encontraron datos en el Manual de Calidad.</p>";
                return;
            }

            // Helper: renderiza una fila con DOBLE campo editable (origen + destino)
            function fieldRow(key, f, empty) {
                var val = empty ? "" : f.value;
                var r = "";
                r += "<div style='display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap;'>";
                r += "<label style='min-width:180px;max-width:180px;font-weight:600;color:#334155;font-size:0.85em;'>" + escHtml(f.label) + ":</label>";
                // Valor ORIGEN (editable — texto a buscar)
                r += "<input type='text' class='doceditor-old' data-field-old='" + key + "' value='" + escAttr(val) + "' placeholder='Texto a buscar...' title='Valor origen (texto a buscar en los documentos)' style='flex:1;min-width:180px;padding:6px 10px;border:1px solid #f59e0b;border-radius:4px;font-size:0.85em;background:#fffbeb;color:#1e293b;' />";
                // Flecha separadora
                r += "<span style='font-size:1.2em;color:#94a3b8;font-weight:bold;' title='Reemplazar por'>&#8594;</span>";
                // Valor DESTINO (editable — texto de reemplazo)
                r += "<input type='text' class='doceditor-new' data-field-new='" + key + "' value='" + escAttr(val) + "' placeholder='Nuevo valor...' title='Valor destino (nuevo texto de reemplazo)' style='flex:1;min-width:180px;padding:6px 10px;border:1px solid #3b82f6;border-radius:4px;font-size:0.85em;background:#eff6ff;color:#1e293b;' />";
                r += "</div>";
                return r;
            }

            var h = "";
            h += "<div style='border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;'>";

            // Encabezado de columnas
            h += "<div style='display:flex;align-items:center;gap:8px;padding:8px 16px;background:#e2e8f0;border-bottom:1px solid #cbd5e1;font-size:0.8em;font-weight:700;color:#475569;'>";
            h += "<span style='min-width:180px;max-width:180px;'>VARIABLE</span>";
            h += "<span style='flex:1;min-width:180px;text-align:center;'>BUSCAR (Origen)</span>";
            h += "<span style='width:20px;'></span>";
            h += "<span style='flex:1;min-width:180px;text-align:center;'>REEMPLAZAR POR (Destino)</span>";
            h += "</div>";

            // Sección: Datos Generales
            h += "<div style='background:#f0f9ff;padding:10px 16px;border-bottom:1px solid #e2e8f0;'>";
            h += "<strong style='color:#1e40af;'>Datos Generales del Manual de Calidad</strong>";
            h += "</div>";
            h += "<div style='padding:12px 16px;'>";

            var generalFields = ["nombre", "nit_cc", "tp", "socios", "direccion", "telefono", "correo", "nota_miembro"];
            generalFields.forEach(function(key) {
                if (!fields[key]) return;
                h += fieldRow(key, fields[key]);
            });
            h += "</div>";

            // Sección: Firmas Manual de Calidad
            var firmaFields = ["firmante_nombre", "firmante_cargo", "firmante_cc"];
            var hasFirma = firmaFields.some(function(k) { return !!fields[k]; });
            if (hasFirma) {
                h += "<div style='background:#fef3c7;padding:10px 16px;border-top:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0;'>";
                h += "<strong style='color:#92400e;'>Cuadro de Firmas - Manual de Calidad</strong>";
                h += "</div>";
                h += "<div style='padding:12px 16px;'>";
                firmaFields.forEach(function(key) {
                    if (!fields[key]) return;
                    h += fieldRow(key, fields[key]);
                });
                h += "</div>";
            }

            // Sección: Firmas Código de Ética
            var eticaFields = ["etica_firmante", "etica_nit"];
            var hasEtica = eticaFields.some(function(k) { return !!fields[k]; });
            if (hasEtica) {
                h += "<div style='background:#ecfdf5;padding:10px 16px;border-top:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0;'>";
                h += "<strong style='color:#065f46;'>Cuadro de Firmas - Código de Ética</strong>";
                h += "</div>";
                h += "<div style='padding:12px 16px;'>";
                eticaFields.forEach(function(key) {
                    if (!fields[key]) return;
                    h += fieldRow(key, fields[key]);
                });
                h += "</div>";
            }

            // =================================================================
            // Sección: Campos NO encontrados (para agregar manualmente)
            // =================================================================
            var allExpected = {
                nombre:     { label: "Nombre / Razón Social", group: "general" },
                nit_cc:     { label: "Nit / C.C.", group: "general" },
                tp:         { label: "T.P. (Tarjeta Profesional)", group: "general" },
                socios:     { label: "Socios", group: "general" },
                direccion:  { label: "Dirección", group: "general" },
                telefono:   { label: "Teléfono", group: "general" },
                correo:     { label: "Correo electrónico", group: "general" },
                nota_miembro: { label: "Nombre del miembro (NOTA)", group: "general" },
                firmante_nombre: { label: "Nombre firmante", group: "firma" },
                firmante_cargo:  { label: "Cargo firmante", group: "firma" },
                firmante_cc:     { label: "C.C. / NIT firmante", group: "firma" },
                etica_firmante:  { label: "Firmante Código de Ética", group: "etica" },
                etica_nit:       { label: "NIT/C.C. Código de Ética", group: "etica" }
            };

            var missing = [];
            for (var mk in allExpected) {
                if (!allExpected.hasOwnProperty(mk)) continue;
                if (!fields[mk]) {
                    missing.push({ key: mk, label: allExpected[mk].label });
                }
            }

            if (missing.length > 0) {
                h += "<div style='background:#fef2f2;padding:10px 16px;border-top:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0;'>";
                h += "<strong style='color:#dc2626;'>Campos no encontrados</strong>";
                h += "<span style='font-size:0.8em;color:#991b1b;margin-left:8px;'>(puede definirlos manualmente para forzar el reemplazo)</span>";
                h += "</div>";
                h += "<div style='padding:12px 16px;'>";
                missing.forEach(function(m) {
                    h += fieldRow(m.key, { label: m.label, value: "" }, true);
                });
                h += "</div>";
            }

            h += "</div>"; // border container

            // Botón aplicar
            h += "<div style='margin-top:16px;'>";
            h += "<button class='sd-btn sd-btn-primary' id='doceditor_apply_btn' onclick='applyDocEditorChanges(" + deploymentId + ")'>Aplicar cambios masivamente</button>";
            h += "<span id='doceditor_status' style='margin-left:12px;font-size:0.85em;color:#64748b;'></span>";
            h += "</div>";

            container.innerHTML = h;
        }

        function applyDocEditorChanges(deploymentId) {
            // Leer pares old/new de ambos campos editables
            var oldInputs = document.querySelectorAll(".doceditor-old");
            var newInputs = document.querySelectorAll(".doceditor-new");
            var changes = {};
            var hasChanges = false;

            oldInputs.forEach(function(oldInput) {
                var field = oldInput.getAttribute("data-field-old");
                var oldVal = oldInput.value.trim();
                var newInput = document.querySelector(".doceditor-new[data-field-new='" + field + "']");
                var newVal = newInput ? newInput.value.trim() : oldVal;

                // Solo incluir si: ambos tienen contenido Y son diferentes
                if (oldVal !== "" && newVal !== "" && oldVal !== newVal) {
                    changes[field] = { old: oldVal, new: newVal };
                    hasChanges = true;
                }
            });

            if (!hasChanges) {
                alert("No hay cambios para aplicar. Modifique al menos un campo destino (derecha) para que sea diferente del origen (izquierda).");
                return;
            }

            var changesList = [];
            for (var key in changes) {
                if (changes.hasOwnProperty(key)) {
                    changesList.push(key + ": \"" + changes[key].old + "\" -> \"" + changes[key].new + "\"");
                }
            }
            var msg = "Se aplicaran los siguientes reemplazos masivamente:\n\n" + changesList.join("\n") + "\n\nEsto buscara el texto ORIGEN y lo reemplazara por el texto DESTINO en TODOS los documentos.\nEsta accion no se puede deshacer facilmente.\n\nContinuar?";
            if (!confirm(msg)) return;

            var btn = document.getElementById("doceditor_apply_btn");
            var status = document.getElementById("doceditor_status");
            if (btn) { btn.disabled = true; btn.textContent = "Aplicando..."; }
            if (status) status.textContent = "Procesando documentos...";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_doceditor_apply");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", deploymentId);
            fd.append("changes", JSON.stringify(changes));

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (btn) { btn.disabled = false; btn.textContent = "Aplicar cambios masivamente"; }
                    if (status) status.textContent = "";

                    if (data.success) {
                        renderDocEditorResults(data);
                        // Después de aplicar, el campo origen pasa a tener el valor nuevo
                        oldInputs.forEach(function(oldInput) {
                            var fld = oldInput.getAttribute("data-field-old");
                            var newInput = document.querySelector(".doceditor-new[data-field-new='" + fld + "']");
                            if (newInput) {
                                oldInput.value = newInput.value.trim();
                            }
                        });
                        alert("Cambios aplicados:\n" + data.files_modified + " archivo(s) modificado(s) de " + data.files_processed + " procesado(s)" + (data.errors.length > 0 ? "\n" + data.errors.length + " error(es)" : ""));
                    } else {
                        alert("Error: " + (data.error || "No se pudieron aplicar los cambios"));
                    }
                })
                .catch(function() {
                    if (btn) { btn.disabled = false; btn.textContent = "Aplicar cambios masivamente"; }
                    if (status) status.textContent = "";
                    alert("Error de conexion al aplicar cambios");
                });
        }

        function renderDocEditorResults(data) {
            var container = document.getElementById("step19_results");
            if (!container) return;

            var h = "<div style='border:1px solid #6ee7b7;border-radius:8px;overflow:hidden;margin-top:16px;'>";
            h += "<div style='background:#ecfdf5;padding:10px 16px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px;'>";
            h += "<strong style='color:#065f46;'>Resultados del Editor Documental</strong>";
            h += "<div style='display:flex;gap:6px;flex-wrap:wrap;'>";
            h += "<span style='background:#d1fae5;color:#065f46;padding:2px 10px;border-radius:12px;font-size:0.8em;font-weight:600;'>" + data.files_modified + " modificados</span>";
            h += "<span style='background:#dbeafe;color:#1e40af;padding:2px 10px;border-radius:12px;font-size:0.8em;font-weight:600;'>" + data.files_processed + " procesados</span>";
            if (data.folders_scanned !== undefined) {
                h += "<span style='background:#e0e7ff;color:#3730a3;padding:2px 10px;border-radius:12px;font-size:0.8em;font-weight:600;'>" + data.folders_scanned + " carpetas</span>";
            }
            if (data.replacements !== undefined) {
                h += "<span style='background:#fef3c7;color:#92400e;padding:2px 10px;border-radius:12px;font-size:0.8em;font-weight:600;'>" + data.replacements + " reemplazos</span>";
            }
            if (data.errors && data.errors.length > 0) {
                h += "<span style='background:#fee2e2;color:#991b1b;padding:2px 10px;border-radius:12px;font-size:0.8em;font-weight:600;'>" + data.errors.length + " errores</span>";
            }
            h += "</div></div>";

            // Carpetas no encontradas
            if (data.folders_skipped && data.folders_skipped.length > 0) {
                h += "<div style='padding:8px 16px;background:#fffbeb;border-top:1px solid #fde68a;font-size:0.82em;'>";
                h += "<strong style='color:#92400e;'>Carpetas no encontradas:</strong> <span style='color:#78350f;'>" + data.folders_skipped.join(", ") + "</span>";
                h += "</div>";
            }

            if (data.detail && data.detail.length > 0) {
                h += "<div style='padding:12px 16px;font-size:0.84em;'>";
                h += "<strong style='color:#334155;'>Archivos modificados:</strong>";
                h += "<ul style='margin:6px 0 0;padding-left:20px;color:#16a34a;'>";
                data.detail.forEach(function(d) {
                    h += "<li>" + escHtml(d) + "</li>";
                });
                h += "</ul></div>";
            }

            if (data.errors && data.errors.length > 0) {
                h += "<div style='padding:12px 16px;border-top:1px solid #fca5a5;background:#fef2f2;font-size:0.84em;'>";
                h += "<strong style='color:#dc2626;'>Errores:</strong>";
                h += "<ul style='margin:6px 0 0;padding-left:20px;color:#991b1b;'>";
                data.errors.forEach(function(e) {
                    h += "<li>" + escHtml(e) + "</li>";
                });
                h += "</ul></div>";
            }

            h += "</div>";
            container.innerHTML = h;
        }

        function escAttr(s) {
            return String(s).replace(/&/g,"&amp;").replace(/'/g,"&#39;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
        }

        function renderDocEditorDebug(dbg) {
            var container = document.getElementById("step19_results");
            if (!container) return;
            var h = "<details style='margin-top:12px;border:1px solid #e2e8f0;border-radius:6px;'>";
            h += "<summary style='padding:8px 12px;cursor:pointer;font-size:0.82em;font-weight:600;color:#64748b;background:#f8fafc;'>Diagnostico de extraccion</summary>";
            h += "<div style='padding:10px 12px;font-size:0.8em;font-family:monospace;background:#f1f5f9;'>";
            for (var key in dbg) {
                if (!dbg.hasOwnProperty(key)) continue;
                var val = dbg[key];
                if (key === 'muestra_seccion') {
                    h += "<div style='margin-top:6px;'><strong>Muestra seccion GENERALIDADES:</strong></div>";
                    h += "<pre style='white-space:pre-wrap;max-height:200px;overflow:auto;background:#fff;padding:8px;border:1px solid #cbd5e1;border-radius:4px;font-size:0.9em;'>" + escHtml(String(val)) + "</pre>";
                } else if (key === 'firma_rows') {
                    h += "<div><strong>firma_rows:</strong> " + escHtml(JSON.stringify(val)) + "</div>";
                } else {
                    var color = (String(val).indexOf('OK') === 0) ? '#16a34a' : (String(val).indexOf('No ') === 0 ? '#dc2626' : '#334155');
                    h += "<div><strong>" + escHtml(key) + ":</strong> <span style='color:" + color + ";'>" + escHtml(String(val)) + "</span></div>";
                }
            }
            h += "</div></details>";
            container.innerHTML = h;
        }

        function loadStepLog(num) {
            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_get_log");
            fd.append("sectok", SD.sectok);
            fd.append("deployment_id", SD.deploymentId);
            fd.append("step_number", num);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    var logDiv = document.getElementById("step_log_" + num);
                    if (!data.logs || data.logs.length === 0) return;

                    var html = "<div class='sd-log-entries'><h4>Log de ejecucion:</h4><pre>";
                    data.logs.forEach(function(log) {
                        var cls = "sd-log-" + log.log_level;
                        // BUG5-FIX: Escapar log.message para prevenir XSS (contiene rutas y salidas de cpapi2)
                        html += "<span class='" + cls + "'>[" + escHtml(log.created_at) + "] " + escHtml(log.log_level.toUpperCase()) + ": " + escHtml(log.message) + "</span>\n";
                    });
                    html += "</pre></div>";
                    logDiv.innerHTML += html;
                    logDiv.style.display = "block";
                });
        }

        // =============================================================
        //  MIGRACION MASIVA DE DOMINIOS
        // =============================================================

        var MIG = {
            subdomains: [],
            configuredDomains: [],
            phase1Results: {},
            phase2Results: {},
            availableDomains: <?php echo json_encode(helper_plugin_siedeployer::$availableDomains); ?>
        };

        // BUG4+5-FIX: Funcion de escape HTML para prevenir XSS en datos del servidor
        function escHtml(s) {
            if (s === null || s === undefined) return '';
            return String(s)
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#039;');
        }

        function migrationScan() {
            var box = document.getElementById("migration_results");
            if (!box) return;
            box.innerHTML = "<p class='sd-loading'>Escaneando subdominios del servidor... esto puede tardar unos segundos.</p>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_migration_scan");
            fd.append("sectok", SD.sectok);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.error) {
                        box.innerHTML = "<p class='sd-log-err'>Error: " + data.error + "</p>";
                        return;
                    }
                    MIG.subdomains = data.subdomains || [];
                    renderMigrationTable();
                })
                .catch(function(err) {
                    box.innerHTML = "<p class='sd-log-err'>Error de conexion: " + err.message + "</p>";
                });
        }

        function migrationCheckDomains() {
            var box = document.getElementById("migration_domain_status");
            if (!box) return;
            box.innerHTML = "<p class='sd-loading'>Verificando dominios configurados en cPanel...</p>";

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_migration_check_domains");
            fd.append("sectok", SD.sectok);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.error) {
                        box.innerHTML = "<p class='sd-log-err'>Error: " + data.error + "</p>";
                        return;
                    }

                    MIG.configuredDomains = data.all_domains || [];

                    var html = '<div style="background:#f0fdf4;border:1px solid #6ee7b7;border-radius:8px;padding:14px;">';
                    html += '<p style="font-weight:700;color:#065f46;margin:0 0 8px;">Estado de dominios en cPanel:</p>';
                    html += '<table style="font-size:0.9em;">';

                    var available = data.available_configured || {};
                    for (var domain in available) {
                        var configured = available[domain];
                        var icon = configured ? "&#10004;" : "&#10008;";
                        var color = configured ? "#065f46" : "#991b1b";
                        var bg = configured ? "#d1fae5" : "#fee2e2";
                        var status = configured ? "Configurado" : "NO configurado";
                        html += '<tr><td style="padding:4px 12px 4px 0;"><span style="color:' + color + ';background:' + bg + ';padding:2px 8px;border-radius:4px;font-weight:600;">' + icon + " " + status + '</span></td>';
                        html += '<td style="padding:4px 0;"><code>' + domain + '</code></td></tr>';
                    }

                    html += '</table>';

                    if (data.main_domain) {
                        html += '<p style="margin:8px 0 0;font-size:0.85em;color:#065f46;">Dominio principal de la cuenta: <code>' + data.main_domain + '</code></p>';
                    }

                    // Advertencia si hay dominios no configurados
                    var notConfigured = [];
                    for (var d in available) {
                        if (!available[d]) notConfigured.push(d);
                    }
                    if (notConfigured.length > 0) {
                        html += '<div style="background:#fef3c7;border:1px solid #f59e0b;border-radius:6px;padding:10px;margin-top:10px;">';
                        html += '<p style="color:#92400e;font-weight:600;margin:0 0 4px;">&#9888; Dominios no configurados</p>';
                        html += '<p style="color:#92400e;margin:0;font-size:0.85em;">Los siguientes dominios deben agregarse como Addon Domain en WHM/cPanel antes de poder crear subdominios bajo ellos: <strong>' + notConfigured.join(", ") + '</strong></p>';
                        html += '</div>';
                    }

                    html += '</div>';
                    box.innerHTML = html;
                })
                .catch(function(err) {
                    box.innerHTML = "<p class='sd-log-err'>Error: " + err.message + "</p>";
                });
        }

        function renderMigrationTable() {
            var box = document.getElementById("migration_results");
            var subs = MIG.subdomains;

            if (subs.length === 0) {
                box.innerHTML = "<p>No se encontraron subdominios configurados en cPanel.</p>";
                return;
            }

            var sieCount = 0;
            subs.forEach(function(s) { if (s.is_sie) sieCount++; });

            var html = '<p style="font-weight:600;margin-bottom:8px;">' + subs.length + ' subdominios encontrados (' + sieCount + ' sistemas SIE)</p>';

            // Controles de filtro y dominio global
            html += '<div class="sd-mig-controls">';
            html += '<label style="font-size:0.9em;display:flex;align-items:center;gap:4px;"><input type="checkbox" id="mig_filter_sie" checked onchange="filterMigrationTable()" /> Solo sistemas SIE</label>';
            html += '<div style="display:flex;align-items:center;gap:6px;">';
            html += '<label style="font-size:0.9em;font-weight:600;">Dominio destino:</label>';
            html += '<select id="mig_global_target" onchange="setGlobalTarget()" style="padding:6px 10px;border-radius:6px;border:1px solid #cbd5e1;font-size:0.9em;">';
            html += '<option value="">-- Seleccionar para todos --</option>';
            MIG.availableDomains.forEach(function(d) {
                html += '<option value="' + d + '">' + d + '</option>';
            });
            html += '</select>';
            html += '</div>';
            html += '</div>';

            // Barra de scroll superior (sincronizada con la inferior)
            html += '<div id="mig_scroll_top" style="overflow-x:auto;margin-top:12px;" onscroll="document.getElementById(\'mig_scroll_bottom\').scrollLeft=this.scrollLeft">';
            html += '<div id="mig_scroll_top_inner" style="height:1px;"></div></div>';

            // Tabla con scroll inferior
            html += '<div id="mig_scroll_bottom" style="overflow-x:auto;" onscroll="document.getElementById(\'mig_scroll_top\').scrollLeft=this.scrollLeft">';
            html += '<table class="sd-table" id="mig_table">';
            html += '<thead><tr>';
            html += '<th style="width:30px;"><input type="checkbox" id="mig_check_all" onchange="toggleMigAll()" /></th>';
            html += '<th>Subdominio</th>';
            html += '<th>Dominio Actual</th>';
            html += '<th>Titulo Sistema</th>';
            html += '<th>Carpeta</th>';
            html += '<th>Dominio Destino</th>';
            html += '<th>Estado</th>';
            html += '</tr></thead><tbody>';

            subs.forEach(function(sub, idx) {
                var isSie = sub.is_sie;
                var filterSie = document.getElementById("mig_filter_sie");
                var showIt = !filterSie || !filterSie.checked || isSie;
                var display = showIt ? '' : ' style="display:none;"';

                html += '<tr data-idx="' + idx + '" data-sie="' + (isSie ? '1' : '0') + '"' + display + '>';
                html += '<td><input type="checkbox" class="mig-check" data-idx="' + idx + '" /></td>';
                // BUG4-FIX: Escapar todos los datos del servidor para prevenir XSS
                html += '<td><strong>' + escHtml(sub.subdomain) + '</strong></td>';
                html += '<td><code style="font-size:0.85em;">' + escHtml(sub.rootdomain) + '</code></td>';
                html += '<td>' + (sub.sie_title ? escHtml(sub.sie_title) : '<span style="color:#94a3b8;">-</span>') + '</td>';
                html += '<td style="font-size:0.82em;max-width:200px;overflow:hidden;text-overflow:ellipsis;"><code>' + escHtml(sub.dir) + '</code></td>';

                // Selector de dominio destino (excluir dominio actual)
                html += '<td>';
                html += '<select class="mig-target" data-idx="' + idx + '" style="padding:4px 8px;border-radius:4px;border:1px solid #cbd5e1;font-size:0.85em;max-width:200px;">';
                html += '<option value="">-- Seleccionar --</option>';
                MIG.availableDomains.forEach(function(d) {
                    if (d !== sub.rootdomain) {
                        html += '<option value="' + escHtml(d) + '">' + escHtml(d) + '</option>';
                    }
                });
                html += '</select>';
                html += '</td>';

                html += '<td id="mig_status_' + idx + '"><span style="color:#94a3b8;">Pendiente</span></td>';
                html += '</tr>';
            });

            html += '</tbody></table>';
            html += '</div>';

            box.innerHTML = html;

            // Sincronizar ancho de scrollbar superior con la tabla
            var tbl = document.getElementById("mig_table");
            var topInner = document.getElementById("mig_scroll_top_inner");
            if (tbl && topInner) { topInner.style.width = tbl.scrollWidth + "px"; }

            // BUG9-FIX: Mostrar solo Fase 1 al escanear. Fase 2 aparece solo tras exito de Fase 1.
            var p1 = document.getElementById("migration_phase1_card");
            var p2 = document.getElementById("migration_phase2_card");
            if (p1) p1.style.display = "block";
            if (p2) p2.style.display = "none"; // oculta hasta que Fase 1 tenga exitos
        }

        function filterMigrationTable() {
            var filterSie = document.getElementById("mig_filter_sie");
            var checked = filterSie ? filterSie.checked : false;
            var rows = document.querySelectorAll("#mig_table tbody tr");
            rows.forEach(function(row) {
                if (checked && row.getAttribute("data-sie") === "0") {
                    row.style.display = "none";
                } else {
                    row.style.display = "";
                }
            });
        }

        function toggleMigAll() {
            var checked = document.getElementById("mig_check_all").checked;
            var checkboxes = document.querySelectorAll(".mig-check");
            checkboxes.forEach(function(cb) {
                var row = cb.closest("tr");
                if (row && row.style.display !== "none") {
                    cb.checked = checked;
                }
            });
        }

        function setGlobalTarget() {
            var target = document.getElementById("mig_global_target").value;
            if (!target) return;

            // BUG7-FIX: Solo aplicar a filas visibles para no procesar no-SIE ocultos
            var selects = document.querySelectorAll(".mig-target");
            selects.forEach(function(sel) {
                var row = sel.closest("tr");
                if (row && row.style.display !== "none") {
                    for (var i = 0; i < sel.options.length; i++) {
                        if (sel.options[i].value === target) {
                            sel.value = target;
                            break;
                        }
                    }
                }
            });
        }

        function getSelectedMigrations() {
            var selected = [];
            var checkboxes = document.querySelectorAll(".mig-check:checked");
            checkboxes.forEach(function(cb) {
                var idx = parseInt(cb.getAttribute("data-idx"));
                var targetSel = document.querySelector('.mig-target[data-idx="' + idx + '"]');
                var target = targetSel ? targetSel.value : '';
                var sub = MIG.subdomains[idx];

                if (sub) {
                    selected.push({
                        idx: idx,
                        subdomain: sub.subdomain,
                        rootdomain: sub.rootdomain,
                        target_domain: target,
                        dir: sub.dir,
                        full_dir: sub.full_dir,
                        is_sie: sub.is_sie
                    });
                }
            });
            return selected;
        }

        async function migrationPhase1() {
            var selected = getSelectedMigrations();
            if (selected.length === 0) {
                alert("Seleccione al menos un subdominio.");
                return;
            }

            // Validar que todos tengan dominio destino
            var sinDestino = [];
            selected.forEach(function(s) {
                if (!s.target_domain) sinDestino.push(s.subdomain);
            });
            if (sinDestino.length > 0) {
                alert("Los siguientes subdominios no tienen dominio destino seleccionado:\n" + sinDestino.join(", "));
                return;
            }

            // Validar que no migran al mismo dominio
            var mismos = [];
            selected.forEach(function(s) {
                if (s.target_domain === s.rootdomain) mismos.push(s.subdomain);
            });
            if (mismos.length > 0) {
                alert("Los siguientes subdominios tienen el mismo dominio actual y destino:\n" + mismos.join(", "));
                return;
            }

            if (!confirm("Se crearan " + selected.length + " subdominios bajo los nuevos dominios.\n\nTambien se actualizara el baseurl en conf/local.php de cada sistema SIE.\n\nContinuar?")) return;

            var logDiv = document.getElementById("migration_phase1_log");
            logDiv.innerHTML = "";
            MIG.phase1Results = {};

            var success = 0;
            var errors = 0;

            for (var i = 0; i < selected.length; i++) {
                var item = selected[i];
                var statusEl = document.getElementById("mig_status_" + item.idx);
                statusEl.innerHTML = '<span style="color:#f59e0b;font-weight:600;">&#9654; Creando...</span>';

                logDiv.innerHTML = '<p class="sd-loading">Fase 1: ' + (i + 1) + '/' + selected.length + ' - Creando ' + item.subdomain + '.' + item.target_domain + '...</p>';

                try {
                    var fd = new FormData();
                    fd.append("call", "plugin_siedeployer_migration_create");
                    fd.append("sectok", SD.sectok);
                    fd.append("subdomain", item.subdomain);
                    fd.append("target_domain", item.target_domain);
                    fd.append("doc_root", item.full_dir);
                    fd.append("system_path", item.full_dir);
                    fd.append("update_baseurl", item.is_sie ? "1" : "0");

                    var response = await fetch(SD.ajaxUrl, { method: "POST", body: fd });
                    var data = await response.json();

                    if (data.success) {
                        var msg = '<span style="color:#16a34a;font-weight:600;">&#10004; ' + item.subdomain + '.' + item.target_domain + '</span>';
                        if (data.baseurl_updated && data.baseurl_updated.success) {
                            msg += '<br><span style="font-size:0.82em;color:#3b82f6;">baseurl: ' + (data.baseurl_updated.new_url || '') + '</span>';
                        }
                        if (data.already_exists) {
                            msg += '<br><span style="font-size:0.82em;color:#f59e0b;">Ya existia</span>';
                        }
                        statusEl.innerHTML = msg;
                        success++;
                        MIG.phase1Results[item.idx] = { success: true, item: item };
                    } else {
                        statusEl.innerHTML = '<span style="color:#dc2626;font-weight:600;">&#10008; Error: ' + (data.error || 'desconocido') + '</span>';
                        errors++;
                        MIG.phase1Results[item.idx] = { success: false, error: data.error };
                    }
                } catch (err) {
                    statusEl.innerHTML = '<span style="color:#dc2626;font-weight:600;">&#10008; Error: ' + err.message + '</span>';
                    errors++;
                }
            }

            // Solicitar SSL
            logDiv.innerHTML = '<p class="sd-loading">Solicitando certificados SSL via AutoSSL...</p>';

            try {
                var fdSSL = new FormData();
                fdSSL.append("call", "plugin_siedeployer_migration_ssl");
                fdSSL.append("sectok", SD.sectok);
                var sslResp = await fetch(SD.ajaxUrl, { method: "POST", body: fdSSL });
                var sslData = await sslResp.json();

                var sslMsg = sslData.success
                    ? '<p style="color:#16a34a;font-size:0.9em;">&#10004; AutoSSL solicitado. Los certificados pueden tardar 5-15 minutos.</p>'
                    : '<p style="color:#f59e0b;font-size:0.9em;">&#9888; ' + (sslData.message || 'Ejecute AutoSSL manualmente desde WHM.') + '</p>';

                logDiv.innerHTML = '<div class="sd-log-ok">Fase 1 completada: ' + success + '/' + selected.length + ' subdominios creados.</div>' + sslMsg;
            } catch (e) {
                logDiv.innerHTML = '<div class="sd-log-ok">Fase 1 completada: ' + success + '/' + selected.length + ' subdominios creados.</div>'
                    + '<p style="color:#f59e0b;font-size:0.9em;">&#9888; No se pudo iniciar AutoSSL automaticamente.</p>';
            }

            // BUG9-FIX: Mostrar Fase 2 solo si hubo al menos un exito en Fase 1
            if (success > 0) {
                var p2 = document.getElementById("migration_phase2_card");
                if (p2) p2.style.display = "block";
            }

            if (errors > 0) {
                logDiv.innerHTML += '<div class="sd-log-err">' + errors + ' errores encontrados. Revise los estados individuales.</div>';
            }
        }

        async function migrationPhase2() {
            var cb = document.getElementById("migration_phase2_confirm");
            if (!cb || !cb.checked) {
                alert("Debe confirmar que ha verificado que los nuevos subdominios funcionan correctamente.");
                return;
            }

            // Obtener items exitosos de fase 1
            var selected = [];
            for (var idx in MIG.phase1Results) {
                if (MIG.phase1Results[idx].success) {
                    selected.push(MIG.phase1Results[idx].item);
                }
            }

            if (selected.length === 0) {
                alert("No hay subdominios completados en Fase 1. Ejecute primero la Fase 1.");
                return;
            }

            if (!confirm("Se eliminaran " + selected.length + " subdominios antiguos.\n\nLas carpetas y datos de los sistemas NO se eliminan.\n\nEsta accion NO se puede deshacer. Continuar?")) return;

            var logDiv = document.getElementById("migration_phase2_log");
            logDiv.innerHTML = "";

            var success = 0;
            var errors = 0;

            for (var i = 0; i < selected.length; i++) {
                var item = selected[i];
                var statusEl = document.getElementById("mig_status_" + item.idx);
                statusEl.innerHTML = '<span style="color:#f59e0b;font-weight:600;">&#9654; Eliminando ' + item.rootdomain + '...</span>';

                logDiv.innerHTML = '<p class="sd-loading">Fase 2: ' + (i + 1) + '/' + selected.length + ' - Eliminando ' + item.subdomain + '.' + item.rootdomain + '...</p>';

                try {
                    var fd = new FormData();
                    fd.append("call", "plugin_siedeployer_migration_delete");
                    fd.append("sectok", SD.sectok);
                    fd.append("subdomain", item.subdomain);
                    fd.append("source_domain", item.rootdomain);

                    var response = await fetch(SD.ajaxUrl, { method: "POST", body: fd });
                    var data = await response.json();

                    if (data.success) {
                        statusEl.innerHTML = '<span style="color:#16a34a;font-weight:600;">&#10004; Migrado a ' + item.subdomain + '.' + item.target_domain + '</span>'
                            + '<br><span style="font-size:0.82em;color:#94a3b8;">Antiguo eliminado: ' + item.subdomain + '.' + item.rootdomain + '</span>';
                        success++;
                    } else {
                        statusEl.innerHTML = '<span style="color:#dc2626;font-weight:600;">&#10008; Error eliminando: ' + (data.error || 'desconocido') + '</span>';
                        errors++;
                    }
                } catch (err) {
                    statusEl.innerHTML = '<span style="color:#dc2626;font-weight:600;">&#10008; Error: ' + err.message + '</span>';
                    errors++;
                }
            }

            logDiv.innerHTML = '<div class="sd-log-ok">Fase 2 completada: ' + success + '/' + selected.length + ' subdominios antiguos eliminados.</div>';

            if (errors > 0) {
                logDiv.innerHTML += '<div class="sd-log-err">' + errors + ' errores encontrados. Revise los estados individuales.</div>';
            }
        }

        // =============================================
        // ACTUALIZACIONES - PARCHES MASIVOS
        // =============================================

        // Toggle exclusion list para parche masivo
        (function() {
            var cb = document.getElementById("patch_masivo_exclude");
            if (cb) {
                cb.addEventListener("change", function() {
                    var list = document.getElementById("patch_masivo_exclude_list");
                    if (list) list.style.display = this.checked ? "block" : "none";
                });
            }
        })();

        function runPatch(patchName, options) {
            options = options || {};

            if (!confirm("Desplegar parche '" + patchName + "'?\n\nSe abrira en una nueva pestana para que lo ejecute.")) return;

            var logDiv = document.getElementById("patch_log_" + patchName);
            if (!logDiv) return;
            logDiv.style.display = "block";
            logDiv.innerHTML = '<p class="sd-loading">Desplegando parche ' + patchName + '...</p>';

            // IMPORTANTE: Abrir ventana AHORA (en contexto de gesto del usuario)
            // para evitar que el navegador bloquee el popup.
            // Si se abre dentro del .then() de fetch, el navegador lo bloquea
            // silenciosamente porque ya no es un gesto directo del usuario.
            var patchWindow = window.open('about:blank', '_blank');

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_updates_run_patch");
            fd.append("sectok", SD.sectok);
            fd.append("patch_name", patchName);

            // Opciones adicionales
            if (options.action) {
                fd.append("patch_action", options.action);
            }

            // Exclusiones para parche masivo
            if (patchName === "parche_masivo") {
                var excludeCb = document.getElementById("patch_masivo_exclude");
                if (excludeCb && excludeCb.checked) {
                    var excludeList = document.getElementById("patch_masivo_exclude_list");
                    if (excludeList) fd.append("exclude_list", excludeList.value);
                }
            }

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success && data.url) {
                        var html = '<div class="sd-log-ok">' + (data.message || 'Parche desplegado') + '</div>';
                        html += '<div style="margin:10px 0;">';
                        html += '<a href="' + data.url + '" target="_blank" class="sd-btn sd-btn-primary sd-btn-sm" style="text-decoration:none;margin-right:8px;">&#128279; Abrir Parche</a>';
                        html += '<button class="sd-btn sd-btn-danger sd-btn-sm" onclick="cleanupPatch(\'' + patchName + '\')">&#128465; Limpiar archivos</button>';
                        html += '</div>';
                        if (data.log && data.log.length > 0) {
                            html += '<div class="sd-patch-detail"><pre>' + data.log.join("\n") + '</pre></div>';
                        }
                        logDiv.innerHTML = html;
                        // Navegar la ventana pre-abierta a la URL del parche
                        if (patchWindow && !patchWindow.closed) {
                            patchWindow.location.href = data.url;
                        } else {
                            // Fallback: si el popup fue bloqueado de todas formas, avisar
                            html += '<div style="margin-top:8px;padding:10px;background:#fef3c7;border:1px solid #f59e0b;border-radius:6px;color:#92400e;font-size:0.88em;">';
                            html += '<strong>&#9888; Popup bloqueado:</strong> Haga clic en "Abrir Parche" arriba para continuar.';
                            html += '</div>';
                            logDiv.innerHTML = html;
                        }
                    } else {
                        logDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || 'desconocido') + '</div>';
                        // Cerrar ventana vacia si hubo error
                        if (patchWindow && !patchWindow.closed) patchWindow.close();
                    }
                })
                .catch(function(err) {
                    logDiv.innerHTML = '<div class="sd-log-err">Error de conexion: ' + err.message + '</div>';
                    if (patchWindow && !patchWindow.closed) patchWindow.close();
                });
        }

        function cleanupPatch(patchName) {
            if (!confirm("Eliminar archivos temporales del parche?")) return;

            var logDiv = document.getElementById("patch_log_" + patchName);

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_updates_cleanup_patch");
            fd.append("sectok", SD.sectok);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        if (logDiv) {
                            logDiv.innerHTML = '<div class="sd-log-ok">' + (data.message || 'Limpieza completada') + '</div>';
                        }
                    } else {
                        if (logDiv) {
                            logDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || 'desconocido') + '</div>';
                        }
                    }
                })
                .catch(function(err) {
                    if (logDiv) {
                        logDiv.innerHTML = '<div class="sd-log-err">Error: ' + err.message + '</div>';
                    }
                });
        }

        // =============================================
        // ACTUALIZACIONES - AJUSTE INDIVIDUAL
        // =============================================

        function individualQuickPrueba(cb) {
            if (cb.checked) {
                individualSelect('/home/systeng3/public_html/prueba', 'prueba', 'Sistema de Prueba');
            } else {
                document.getElementById("individual_selected").style.display = "none";
                document.getElementById("individual_selected_path").value = "";
                document.getElementById("btn_individual_apply").disabled = true;
            }
        }

        function individualScan() {
            var box = document.getElementById("individual_scan_results");
            box.style.display = "block";
            box.innerHTML = '<p style="padding:8px;color:#f59e0b;font-weight:600;">Escaneando sistemas...</p>';

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_scan_server");
            fd.append("sectok", SD.sectok);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (!data.systems || data.systems.length === 0) {
                        box.innerHTML = '<p style="padding:8px;">No se encontraron sistemas.</p>';
                        return;
                    }
                    var html = '<ul class="sd-scan-list">';
                    data.systems.forEach(function(s) {
                        html += '<li><a href="#" onclick="individualSelect(\''
                            + s.path.replace(/'/g, "\\'") + '\', \''
                            + s.name.replace(/'/g, "\\'") + '\', \''
                            + (s.title || '').replace(/'/g, "\\'") + '\');return false;">'
                            + s.name + '</a> <span class="sd-scan-title">' + (s.title || '') + '</span></li>';
                    });
                    html += '</ul>';
                    box.innerHTML = html;
                })
                .catch(function(err) {
                    box.innerHTML = '<p style="padding:8px;color:#dc2626;">Error: ' + err.message + '</p>';
                });
        }

        function individualSelect(path, name, title) {
            document.getElementById("individual_selected_path").value = path;
            document.getElementById("individual_selected_name").textContent = name + (title ? ' (' + title + ')' : '');
            document.getElementById("individual_selected").style.display = "block";
            document.getElementById("individual_scan_results").style.display = "none";
            document.getElementById("btn_individual_apply").disabled = false;
        }

        function individualApply() {
            var path = document.getElementById("individual_selected_path").value;
            var name = document.getElementById("individual_selected_name").textContent;
            if (!path) { alert("Seleccione un sistema primero."); return; }
            if (!confirm("Aplicar ajuste individual a:\n\n" + name + "\n\nSe copiarán los archivos de sistema/ al subdominio, protegiendo archivos propios del cliente.\n\n¿Continuar?")) return;

            var logDiv = document.getElementById("patch_log_individual");
            logDiv.style.display = "block";
            logDiv.innerHTML = '<p class="sd-loading">Aplicando ajuste a ' + name + '...</p>';

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_individual_apply");
            fd.append("sectok", SD.sectok);
            fd.append("target_path", path);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        var html = '<div class="sd-log-ok">' + data.message + '</div>';
                        if (data.stats) {
                            html += '<div style="margin:8px 0;font-size:0.9em;">';
                            html += '<strong>Copiados:</strong> ' + data.stats.copied + ' archivos';
                            html += ' | <strong>Protegidos:</strong> ' + data.stats.skipped + ' archivos';
                            html += ' | <strong>Errores:</strong> ' + data.stats.errors;
                            if (data.stats.dirs_created > 0) {
                                html += ' | <strong>Dirs creados:</strong> ' + data.stats.dirs_created;
                            }
                            html += '</div>';
                        }
                        if (data.log && data.log.length > 0) {
                            html += '<div class="sd-patch-detail"><pre>' + data.log.join("\n") + '</pre></div>';
                        }
                        logDiv.innerHTML = html;
                    } else {
                        logDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || 'desconocido') + '</div>';
                    }
                })
                .catch(function(err) {
                    logDiv.innerHTML = '<div class="sd-log-err">Error: ' + err.message + '</div>';
                });
        }

        // =============================================
        // ACTUALIZACIONES - SISTEMA BASE
        // =============================================

        function updatesCheckSistema(subpath) {
            subpath = subpath || '';
            var listDiv = document.getElementById("updates_sistema_list");
            var displayPath = '/public_html/sistema/' + (subpath ? subpath + '/' : '');
            listDiv.innerHTML = '<p class="sd-loading">Verificando contenido de ' + displayPath + '...</p>';

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_updates_check_sistema");
            fd.append("sectok", SD.sectok);
            fd.append("subpath", subpath);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        if (data.items && data.items.length > 0) {
                            var html = '<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px;">';

                            // Breadcrumb de navegacion
                            html += '<div style="margin:0 0 8px;display:flex;align-items:center;gap:4px;flex-wrap:wrap;">';
                            html += '<span style="font-weight:600;">Ruta:</span> ';
                            html += '<a href="#" onclick="updatesCheckSistema(\'\');return false;" style="color:#0ea5e9;text-decoration:none;font-weight:600;">sistema/</a>';
                            if (subpath) {
                                var parts = subpath.split('/');
                                var accumulated = '';
                                for (var p = 0; p < parts.length; p++) {
                                    accumulated += (accumulated ? '/' : '') + parts[p];
                                    var linkPath = accumulated;
                                    html += '<span style="color:#94a3b8;">/</span>';
                                    if (p < parts.length - 1) {
                                        html += '<a href="#" onclick="updatesCheckSistema(\'' + linkPath + '\');return false;" style="color:#0ea5e9;text-decoration:none;">' + parts[p] + '</a>';
                                    } else {
                                        html += '<strong>' + parts[p] + '</strong>';
                                    }
                                }
                            }
                            html += ' <span style="color:#94a3b8;margin-left:8px;">(' + data.items.length + ' elementos)</span>';
                            html += '</div>';

                            // Boton volver si estamos en subcarpeta
                            if (subpath) {
                                var parentPath = subpath.indexOf('/') !== -1 ? subpath.substring(0, subpath.lastIndexOf('/')) : '';
                                html += '<div style="margin-bottom:6px;">';
                                html += '<a href="#" onclick="updatesCheckSistema(\'' + parentPath + '\');return false;" style="color:#0ea5e9;text-decoration:none;font-size:0.9em;">&larr; Volver</a>';
                                html += '</div>';
                            }

                            html += '<div style="max-height:400px;overflow-y:auto;font-family:monospace;font-size:0.85em;">';
                            for (var i = 0; i < data.items.length; i++) {
                                var item = data.items[i];
                                if (item.type === 'dir') {
                                    var childPath = subpath ? subpath + '/' + item.name : item.name;
                                    html += '<div style="padding:3px 0;">';
                                    html += '<a href="#" onclick="updatesCheckSistema(\'' + childPath + '\');return false;" style="color:#0ea5e9;text-decoration:none;display:inline-flex;align-items:center;gap:4px;">';
                                    html += '&#128193; <strong>' + item.name + '/</strong></a>';
                                    if (typeof item.count !== 'undefined') {
                                        html += ' <span style="color:#94a3b8;font-size:0.85em;">(' + item.count + ' items)</span>';
                                    }
                                    html += '</div>';
                                } else {
                                    html += '<div style="padding:3px 0;">&#128196; ' + item.name;
                                    if (item.size) html += ' <span style="color:#94a3b8;">(' + item.size + ')</span>';
                                    html += '</div>';
                                }
                            }
                            html += '</div></div>';
                            listDiv.innerHTML = html;
                        } else {
                            listDiv.innerHTML = '<div class="sd-log-err">La carpeta ' + displayPath + ' esta vacia o no existe.</div>';
                        }
                    } else {
                        listDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || 'desconocido') + '</div>';
                    }
                })
                .catch(function(err) {
                    listDiv.innerHTML = '<div class="sd-log-err">Error de conexion: ' + err.message + '</div>';
                });
        }

        function updatesCleanSistema() {
            if (!confirm("ATENCION: Se eliminara TODO el contenido de /public_html/sistema/.\n\nEsta accion NO se puede deshacer.\n\nContinuar?")) return;
            if (!confirm("SEGUNDA CONFIRMACION: Esta absolutamente seguro de eliminar TODO el contenido de la carpeta sistema base?")) return;

            var listDiv = document.getElementById("updates_sistema_list");
            listDiv.innerHTML = '<p class="sd-loading">Limpiando /public_html/sistema/...</p>';

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_updates_clean_sistema");
            fd.append("sectok", SD.sectok);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success) {
                        listDiv.innerHTML = '<div class="sd-log-ok">' + (data.message || 'Carpeta limpiada correctamente') + '</div>';
                    } else {
                        listDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || 'desconocido') + '</div>';
                    }
                })
                .catch(function(err) {
                    listDiv.innerHTML = '<div class="sd-log-err">Error de conexion: ' + err.message + '</div>';
                });
        }

        function updatesUploadSistema() {
            var fileInput = document.getElementById("updates_sistema_zip");
            if (!fileInput || !fileInput.files[0]) {
                alert("Seleccione un archivo ZIP primero.");
                return;
            }

            if (!confirm("Subir y descomprimir '" + fileInput.files[0].name + "' en /public_html/sistema/?")) return;

            var logDiv = document.getElementById("updates_sistema_upload_log");
            logDiv.innerHTML = '<p class="sd-loading">Subiendo y descomprimiendo ZIP... (puede tardar unos segundos)</p>';

            var fd = new FormData();
            fd.append("call", "plugin_siedeployer_updates_upload_sistema");
            fd.append("sectok", SD.sectok);
            fd.append("sistema_zip", fileInput.files[0]);

            fetch(SD.ajaxUrl, { method: "POST", body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.success || data.warn) {
                        var html = '';

                        // Mensaje principal
                        var msgClass = data.warn ? 'sd-log-err' : 'sd-log-ok';
                        html += '<div class="' + msgClass + '">' + (data.message || 'ZIP procesado') + '</div>';

                        // Detalles de extraccion
                        if (data.details) {
                            var d = data.details;
                            html += '<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-top:8px;font-size:0.85em;">';

                            // Resumen
                            html += '<p style="margin:0 0 6px;"><strong>Resumen:</strong> ' + d.extraidos_ok + '/' + d.total_en_zip + ' entradas extraidas</p>';

                            // Directorios en el ZIP
                            if (d.dirs_en_zip) {
                                html += '<p style="margin:0 0 4px;font-weight:600;">Directorios de primer nivel en el ZIP:</p>';
                                html += '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px;">';
                                for (var dir in d.dirs_en_zip) {
                                    html += '<span style="background:#e2e8f0;padding:2px 8px;border-radius:4px;font-family:monospace;font-size:0.9em;">' + dir + ' (' + d.dirs_en_zip[dir] + ')</span>';
                                }
                                html += '</div>';
                            }

                            // Directorios criticos
                            if (d.dirs_criticos) {
                                if (d.dirs_criticos.presentes && d.dirs_criticos.presentes.length > 0) {
                                    html += '<p style="margin:0 0 4px;color:#16a34a;font-weight:600;">&#9989; Directorios criticos presentes:</p>';
                                    html += '<ul style="margin:0 0 8px 20px;">';
                                    for (var i = 0; i < d.dirs_criticos.presentes.length; i++) {
                                        html += '<li>' + d.dirs_criticos.presentes[i] + '</li>';
                                    }
                                    html += '</ul>';
                                }
                                if (d.dirs_criticos.faltantes && d.dirs_criticos.faltantes.length > 0) {
                                    html += '<p style="margin:0 0 4px;color:#dc2626;font-weight:600;">&#10060; Directorios criticos FALTANTES:</p>';
                                    html += '<ul style="margin:0 0 8px 20px;color:#dc2626;">';
                                    for (var j = 0; j < d.dirs_criticos.faltantes.length; j++) {
                                        html += '<li><strong>' + d.dirs_criticos.faltantes[j] + '</strong></li>';
                                    }
                                    html += '</ul>';
                                }
                            }

                            // Errores de extraccion
                            if (d.errores && d.errores.length > 0) {
                                html += '<p style="margin:0 0 4px;color:#dc2626;font-weight:600;">&#9888; Errores de extraccion (' + d.errores.length + '):</p>';
                                html += '<div style="max-height:150px;overflow-y:auto;background:#fef2f2;border:1px solid #fecaca;border-radius:4px;padding:8px;font-family:monospace;font-size:0.85em;">';
                                for (var k = 0; k < d.errores.length; k++) {
                                    html += '<div>' + d.errores[k] + '</div>';
                                }
                                html += '</div>';
                            }

                            html += '</div>';
                        }

                        logDiv.innerHTML = html;
                        // Auto-verificar estado despues de subir
                        updatesCheckSistema();
                    } else {
                        logDiv.innerHTML = '<div class="sd-log-err">Error: ' + (data.error || 'desconocido') + '</div>';
                    }
                })
                .catch(function(err) {
                    logDiv.innerHTML = '<div class="sd-log-err">Error de conexion: ' + err.message + '</div>';
                });
        }

        </script>
        <?php
    }
}
