<?php
/**
 * ASISMA CRM - Panel de Avance por Modulos (para gerencia)
 *
 * Lee "Nuevo CRM/documento_consolidado.md" en vivo, calcula el avance de cada
 * modulo automaticamente y permite editar el documento desde el mismo visor
 * (con respaldo automatico en avance/respaldos/ en cada guardado).
 *
 * Acceso: http://asismacrm.duckdns.org/avance/  (clave abajo, cambiarla si se quiere)
 */
// Sesion propia (no choca con la del CRM) y con vida larga: editar el
// documento puede tomar mas de la vida por defecto de PHP (24 min) y si la
// sesion moria a mitad de edicion, el guardado fallaba.
session_name('avance_sesion');
ini_set('session.gc_maxlifetime', 28800); // 8 horas
session_set_cookie_params(28800);
session_start();

define('CLAVE_ACCESO', 'asisma2026');
define('RUTA_DOCUMENTO', dirname(dirname(__FILE__)) . '/Nuevo CRM/documento_consolidado.md');
define('DIR_RESPALDOS', dirname(__FILE__) . '/respaldos');

// ── Autenticacion simple ────────────────────────────────────────────────────
if (isset($_POST['clave_acceso'])) {
    if ($_POST['clave_acceso'] === CLAVE_ACCESO) {
        $_SESSION['avance_autorizado'] = true;
    } else {
        $errorClave = 'Clave incorrecta.';
    }
}
if (isset($_GET['salir'])) {
    unset($_SESSION['avance_autorizado']);
    header('Location: ./');
    exit;
}
$autorizado = !empty($_SESSION['avance_autorizado']);

// ── API interna: leer y guardar el documento ────────────────────────────────
if (isset($_GET['accion'])) {
    header('Content-Type: application/json; charset=utf-8');

    // Reingreso de clave sin recargar (para cuando la sesion expiro editando)
    if ($_GET['accion'] === 'entrar' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $clave = isset($_POST['clave_acceso']) ? $_POST['clave_acceso'] : '';
        if ($clave === CLAVE_ACCESO) {
            $_SESSION['avance_autorizado'] = true;
            echo json_encode(array('success' => true));
        } else {
            echo json_encode(array('success' => false, 'message' => 'Clave incorrecta.'));
        }
        exit;
    }

    // Si la sesion expiro, la API responde JSON (nunca la pagina de login)
    if (!$autorizado) {
        http_response_code(401);
        echo json_encode(array('success' => false, 'sesion_expirada' => true,
                               'message' => 'La sesion expiro. Ingresa la clave de nuevo — tus cambios siguen en pantalla.'));
        exit;
    }

    if ($_GET['accion'] === 'leer') {
        if (!file_exists(RUTA_DOCUMENTO)) {
            echo json_encode(array('success' => false, 'message' => 'No se encontro el documento consolidado.'));
            exit;
        }
        echo json_encode(array(
            'success'   => true,
            'contenido' => file_get_contents(RUTA_DOCUMENTO),
            'modificado' => date('d-m-Y H:i', filemtime(RUTA_DOCUMENTO))
        ));
        exit;
    }

    if ($_GET['accion'] === 'guardar' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $contenido = isset($_POST['contenido']) ? $_POST['contenido'] : '';
        if (trim($contenido) === '') {
            echo json_encode(array('success' => false, 'message' => 'El contenido viene vacio, no se guardo nada.'));
            exit;
        }
        // Respaldo automatico antes de sobreescribir (se conservan los ultimos 30)
        if (!is_dir(DIR_RESPALDOS)) mkdir(DIR_RESPALDOS, 0755, true);
        if (file_exists(RUTA_DOCUMENTO)) {
            copy(RUTA_DOCUMENTO, DIR_RESPALDOS . '/documento_consolidado_' . date('Ymd_His') . '.md');
            $respaldos = glob(DIR_RESPALDOS . '/documento_consolidado_*.md');
            if (count($respaldos) > 30) {
                sort($respaldos);
                foreach (array_slice($respaldos, 0, count($respaldos) - 30) as $viejo) @unlink($viejo);
            }
        }
        // Normalizar saltos de linea y guardar en UTF-8
        $contenido = str_replace("\r\n", "\n", $contenido);
        if (file_put_contents(RUTA_DOCUMENTO, $contenido) === false) {
            echo json_encode(array('success' => false, 'message' => 'No se pudo escribir el archivo (permisos).'));
            exit;
        }
        echo json_encode(array('success' => true, 'message' => 'Documento guardado. Respaldo creado.'));
        exit;
    }

    echo json_encode(array('success' => false, 'message' => 'Accion desconocida.'));
    exit;
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ASISMA CRM — Avance del Proyecto</title>
<style>
    :root {
        --azul: #1d4ed8; --azul-oscuro: #1e3a8a; --fondo: #f4f6fb; --carta: #ffffff;
        --texto: #1e293b; --texto-suave: #64748b; --borde: #e2e8f0;
        --verde: #16a34a; --ambar: #d97706; --rojo: #dc2626; --gris: #94a3b8;
    }
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Segoe UI', system-ui, sans-serif; background: var(--fondo); color: var(--texto); }
    .contenedor { max-width: 1180px; margin: 0 auto; padding: 24px 20px 60px; }

    /* Encabezado */
    .encabezado { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; }
    .encabezado h1 { font-size: 26px; color: var(--azul-oscuro); letter-spacing: -0.3px; }
    .encabezado .sub { color: var(--texto-suave); font-size: 13px; margin-top: 4px; }
    .botones { display: flex; gap: 8px; }
    .boton { border: 1px solid var(--borde); background: var(--carta); color: var(--texto); padding: 9px 16px;
             border-radius: 10px; font-size: 13px; font-weight: 600; cursor: pointer; text-decoration: none; }
    .boton:hover { border-color: var(--azul); color: var(--azul); }
    .boton.primario { background: var(--azul); border-color: var(--azul); color: #fff; }
    .boton.primario:hover { background: var(--azul-oscuro); color: #fff; }

    /* Resumen ejecutivo */
    .resumen { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 14px; margin-bottom: 10px; }
    .kpi { background: var(--carta); border: 1px solid var(--borde); border-radius: 14px; padding: 16px 18px; }
    .kpi .valor { font-size: 30px; font-weight: 800; line-height: 1.1; }
    .kpi .nombre { font-size: 11px; text-transform: uppercase; letter-spacing: 0.6px; color: var(--texto-suave); margin-top: 4px; font-weight: 600; }
    .barra-global { background: var(--carta); border: 1px solid var(--borde); border-radius: 14px; padding: 16px 18px; margin-bottom: 22px; }
    .barra-global .titulo-barra { display: flex; justify-content: space-between; font-size: 13px; font-weight: 700; margin-bottom: 8px; }
    .pista { background: #e8edf5; border-radius: 999px; height: 14px; overflow: hidden; }
    .relleno { height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--azul), #3b82f6); transition: width .6s; }

    /* Tarjetas de modulos */
    .grilla { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 14px; }
    .tarjeta { background: var(--carta); border: 1px solid var(--borde); border-radius: 14px; padding: 16px 18px; cursor: pointer; transition: box-shadow .15s, transform .15s; }
    .tarjeta:hover { box-shadow: 0 6px 20px rgba(30, 58, 138, 0.10); transform: translateY(-1px); }
    .tarjeta .fila-titulo { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 6px; }
    .tarjeta h3 { font-size: 15px; }
    .tarjeta .numero { color: var(--texto-suave); font-weight: 700; margin-right: 4px; }
    .chip { font-size: 10.5px; font-weight: 700; padding: 3px 9px; border-radius: 999px; white-space: nowrap; text-transform: uppercase; letter-spacing: .4px; }
    .chip.listo { background: #dcfce7; color: #166534; }
    .chip.curso { background: #dbeafe; color: #1e40af; }
    .chip.noexiste { background: #fee2e2; color: #991b1b; }
    .chip.bloqueado { background: #fef3c7; color: #92400e; }
    .chip.reducido { background: #ede9fe; color: #5b21b6; }
    .tarjeta .como-va { font-size: 12.5px; color: var(--texto-suave); margin-bottom: 12px; min-height: 32px; }
    .fila-avance { display: flex; align-items: center; gap: 10px; }
    .fila-avance .pista { flex: 1; height: 10px; }
    .fila-avance .pct { font-size: 15px; font-weight: 800; min-width: 44px; text-align: right; }
    .pie-tarjeta { display: flex; justify-content: space-between; align-items: center; margin-top: 10px; font-size: 11.5px; color: var(--texto-suave); }
    .validacion { display: flex; gap: 4px; }
    .punto { width: 10px; height: 10px; border-radius: 50%; background: #e2e8f0; }
    .punto.ok { background: var(--verde); }

    /* Detalle (modal) */
    .velo { position: fixed; inset: 0; background: rgba(15, 23, 42, 0.5); display: none; align-items: flex-start; justify-content: center; padding: 40px 16px; z-index: 50; overflow-y: auto; }
    .velo.abierto { display: flex; }
    .detalle { background: var(--carta); border-radius: 16px; max-width: 760px; width: 100%; padding: 26px 30px; }
    .detalle h2 { color: var(--azul-oscuro); font-size: 20px; margin-bottom: 4px; }
    .detalle .como-va-det { color: var(--texto-suave); font-size: 13.5px; margin-bottom: 16px; }
    .detalle h4 { font-size: 12px; text-transform: uppercase; letter-spacing: .6px; margin: 16px 0 8px; color: var(--azul-oscuro); }
    .detalle ul { list-style: none; }
    .detalle li { font-size: 13.5px; padding: 5px 0 5px 26px; position: relative; border-bottom: 1px dashed #eef2f7; }
    .detalle li:before { position: absolute; left: 2px; font-weight: 700; }
    .detalle li.hecho:before { content: '✓'; color: var(--verde); }
    .detalle li.pendiente:before { content: '○'; color: var(--rojo); }
    .detalle li.marcado:before { content: '✓'; color: var(--verde); }
    .detalle li.criterio:before { content: '›'; color: var(--azul); }
    .cerrar-detalle { float: right; background: none; border: none; font-size: 22px; color: var(--texto-suave); cursor: pointer; }
    .nota-doc { background: #f8fafc; border-left: 3px solid var(--gris); padding: 8px 12px; font-size: 12.5px; color: var(--texto-suave); margin: 8px 0; border-radius: 0 8px 8px 0; }
    .tiempo { display: inline-block; background: #eef2ff; color: var(--azul-oscuro); font-size: 10.5px; font-weight: 700;
              padding: 1px 8px; border-radius: 999px; margin-left: 6px; white-space: nowrap; }

    /* Editor */
    #vistaEditor { display: none; }
    #areaEdicion { width: 100%; height: calc(100vh - 220px); min-height: 420px; font-family: Consolas, monospace; font-size: 13px;
                   padding: 16px; border: 1px solid var(--borde); border-radius: 12px; resize: vertical; line-height: 1.55; background: #fff; color: var(--texto); }
    .aviso-editor { font-size: 12px; color: var(--texto-suave); margin: 8px 0 14px; }

    /* Login */
    .caja-login { max-width: 380px; margin: 12vh auto; background: var(--carta); border: 1px solid var(--borde); border-radius: 16px; padding: 34px; text-align: center; }
    .caja-login h1 { font-size: 20px; color: var(--azul-oscuro); margin-bottom: 6px; }
    .caja-login p { font-size: 13px; color: var(--texto-suave); margin-bottom: 18px; }
    .caja-login input { width: 100%; padding: 11px 14px; border: 1px solid var(--borde); border-radius: 10px; font-size: 14px; margin-bottom: 12px; }
    .caja-login .boton { width: 100%; }
    .error-clave { color: var(--rojo); font-size: 12.5px; margin-bottom: 10px; }

    /* Impresion */
    @media print {
        .botones, .pie-tarjeta .validacion { display: none !important; }
        body { background: #fff; }
        .tarjeta { break-inside: avoid; box-shadow: none; }
    }
</style>
</head>
<body>

<?php if (!$autorizado): ?>
<div class="caja-login">
    <h1>Avance del Proyecto CRM</h1>
    <p>Panel para gerencia — ingresa la clave para continuar.</p>
    <?php if (!empty($errorClave)): ?><div class="error-clave"><?php echo $errorClave; ?></div><?php endif; ?>
    <form method="POST">
        <input type="password" name="clave_acceso" placeholder="Clave de acceso" autofocus>
        <button type="submit" class="boton primario">Entrar</button>
    </form>
</div>
<?php else: ?>

<div class="contenedor">

    <!-- Vista principal (dashboard) -->
    <div id="vistaPanel">
        <div class="encabezado">
            <div>
                <h1>ASISMA CRM — Avance del Proyecto</h1>
                <div class="sub">Basado en el documento consolidado · Ultima edicion del documento: <span id="fechaDoc">…</span></div>
            </div>
            <div class="botones">
                <button class="boton" onclick="window.print()">Imprimir / PDF</button>
                <button class="boton primario" onclick="abrirEditor()">Editar documento</button>
                <a class="boton" href="?salir=1">Salir</a>
            </div>
        </div>

        <div class="resumen" id="kpis"></div>

        <div class="barra-global">
            <div class="titulo-barra"><span>Avance global del proyecto</span><span id="pctGlobal">0%</span></div>
            <div class="pista"><div class="relleno" id="rellenoGlobal" style="width:0%"></div></div>
            <div style="font-size:11.5px;color:var(--texto-suave);margin-top:8px;">
                El avance de cada modulo se calcula automatico: lo construido versus lo que falta (limitante). Los puntos verdes son la validacion: Pruebas por Ivan · Validado por Ivan · Validado final.
            </div>
        </div>

        <div class="grilla" id="grilla"></div>
    </div>

    <!-- Vista editor -->
    <div id="vistaEditor">
        <div class="encabezado">
            <div>
                <h1>Editar documento consolidado</h1>
                <div class="sub">Se guarda directo al archivo .md — se crea un respaldo automatico en cada guardado.</div>
            </div>
            <div class="botones">
                <button class="boton" onclick="cerrarEditor()">Cancelar</button>
                <button class="boton primario" onclick="guardarDocumento()">Guardar cambios</button>
            </div>
        </div>
        <div class="aviso-editor">Tip: los checkboxes se marcan cambiando <b>- [ ]</b> por <b>- [x]</b>. Los tiempos van en MINUTOS al final de cada tarea, ej: <b>(240m)</b> — el panel los suma y los muestra en horas. Al guardar, todo se recalcula solo.</div>
        <textarea id="areaEdicion" spellcheck="false"></textarea>
    </div>

</div>

<!-- Modal de detalle de modulo -->
<div class="velo" id="velo" onclick="if(event.target===this)cerrarDetalle()">
    <div class="detalle" id="cuerpoDetalle"></div>
</div>

<script>
var contenidoActual = '';

// ── Parser del documento consolidado ────────────────────────────────────────
function analizarDocumento(md) {
    var modulos = [];
    var bloques = md.split(/\n(?=## )/);
    bloques.forEach(function(bloque) {
        var titulo = (bloque.match(/^##\s*(.+)$/m) || [null, ''])[1].trim();
        var m = titulo.match(/^(\d+)\.\s*(.+)$/);
        if (!m) return; // secciones que no son modulos (decisiones, resumen)
        var mod = {
            numero: parseInt(m[1], 10),
            nombre: m[2].replace(/[✅⬜🔄]/g, '').trim(),
            comoVa: '', hechos: [], limitantes: [], noLimitantes: [], criterios: [], notas: [],
            checks: { pruebas: false, validadoIvan: false, validado: false }
        };
        var cv = bloque.match(/\*\*Como va:\*\*\s*(.+)/);
        if (cv) mod.comoVa = cv[1].trim();

        // Checks de seguimiento (primeras lineas de checkbox con las palabras clave)
        var lineas = bloque.split('\n');
        var seccion = '';
        lineas.forEach(function(linea) {
            var l = linea.trim();
            if (/^\*\*Hecho\*\*/.test(l)) { seccion = 'hecho'; return; }
            if (/^\*\*Alcance vigente/.test(l)) { seccion = 'hecho'; return; }
            if (/^\*\*Falta \(Limitante\)/.test(l)) { seccion = 'limitante'; return; }
            if (/^\*\*Falta \(No limitante\)/.test(l)) { seccion = 'nolimitante'; return; }
            if (/^\*\*Criterio de aceptacion/.test(l)) { seccion = 'criterio'; return; }
            if (/^\*\*(Flujo definido|Descartado|Definiciones pendientes|Duda)/.test(l)) { seccion = 'otra'; return; }
            if (/^\*\*Falta:\*\*/.test(l)) { seccion = ''; return; }
            if (/^## /.test(l)) { seccion = ''; return; }

            var chk = l.match(/^-\s*\[([ xX])\]\s*(.+)$/);
            var bala = l.match(/^-\s+(?!\[)(.+)$/);
            var cita = l.match(/^>\s*(.+)$/);

            if (cita) { mod.notas.push(cita[1]); return; }

            if (chk) {
                var marcado = chk[1].toLowerCase() === 'x';
                var texto = chk[2].trim();
                // Checks de seguimiento del modulo
                if (/^Pruebas? por Ivan/i.test(texto)) { mod.checks.pruebas = marcado; return; }
                if (/^Validado por Ivan/i.test(texto)) { mod.checks.validadoIvan = marcado; return; }
                if (/^Validado/i.test(texto) && !/por Ivan/i.test(texto)) { mod.checks.validado = marcado; return; }
                // Tiempo estimado: minutos entre parentesis al final, ej (240m); acepta tambien (4h) y (2d)
                var minutos = null;
                var coincidencias = texto.match(/\((\d+(?:[.,]\d+)?)\s*(m|min|h|d)\)/gi);
                if (coincidencias) {
                    var ultima = coincidencias[coincidencias.length - 1].match(/\((\d+(?:[.,]\d+)?)\s*(m|min|h|d)\)/i);
                    var valor = parseFloat(ultima[1].replace(',', '.'));
                    var unidad = ultima[2].toLowerCase();
                    minutos = unidad === 'd' ? valor * 480 : (unidad === 'h' ? valor * 60 : valor);
                }
                var item = { texto: texto, hecho: marcado, minutos: minutos };
                if (seccion === 'limitante') mod.limitantes.push(item);
                else if (seccion === 'nolimitante') mod.noLimitantes.push(item);
                else if (seccion === 'otra' || seccion === '') mod.limitantes.push(item);
                return;
            }
            if (bala) {
                var textoBala = bala[1].trim();
                if (seccion === 'hecho') mod.hechos.push(textoBala);
                else if (seccion === 'criterio') mod.criterios.push(textoBala);
            }
        });

        // Estado y porcentaje
        if (!mod.comoVa) mod.comoVa = 'Ver el detalle del modulo.';
        var comoVaMayus = mod.comoVa.toUpperCase();
        if (comoVaMayus.indexOf('LISTO Y VALIDADO') !== -1) mod.estado = 'listo';
        else if (comoVaMayus.indexOf('NO EXISTE') !== -1) mod.estado = 'noexiste';
        else if (comoVaMayus.indexOf('BLOQUEADO') !== -1) mod.estado = 'bloqueado';
        else if (/^CAMBIO DE ALCANCE/.test(comoVaMayus)) mod.estado = 'reducido';
        else mod.estado = 'curso';

        var pendientes = mod.limitantes.filter(function(f) { return !f.hecho; }).length;
        var completadas = mod.limitantes.length - pendientes;
        var totalTrabajo = mod.hechos.length + mod.limitantes.length;
        if (mod.estado === 'listo') mod.pct = 100;
        else if (totalTrabajo === 0) mod.pct = 0;
        else mod.pct = Math.round(((mod.hechos.length + completadas) / totalTrabajo) * 100);
        // Un modulo que "no existe" como flujo no puede mostrar avance alto
        // aunque tenga base tecnica construida
        if (mod.estado === 'noexiste') mod.pct = Math.min(mod.pct, 25);
        mod.pendientes = pendientes;

        // Tiempo restante del modulo: suma de los minutos de las tareas
        // limitantes pendientes (las "(ver modulo X)" suman en su modulo dueño)
        mod.minutosPendientes = 0;
        mod.sinEstimar = 0;
        mod.limitantes.forEach(function(f) {
            if (f.hecho) return;
            if (f.minutos !== null) mod.minutosPendientes += f.minutos;
            else if (!/\(ver modulo|\(definicion de negocio|\(la alerta se construye/i.test(f.texto)) mod.sinEstimar++;
        });
        mod.minutosNoLimitantes = 0;
        mod.noLimitantes.forEach(function(f) {
            if (!f.hecho && f.minutos !== null) mod.minutosNoLimitantes += f.minutos;
        });
        modulos.push(mod);
    });
    return modulos;
}

// ── Render del panel ────────────────────────────────────────────────────────
function esc(t) { var d = document.createElement('div'); d.textContent = t; return d.innerHTML; }
function textoPlano(t) { return esc(t.replace(/\*\*/g, '').replace(/~~/g, '')); }

// Los minutos se muestran siempre en horas (ej: 90 -> "1,5 h")
function formatoHoras(minutos) {
    if (!minutos) return '0 h';
    var horas = Math.round((minutos / 60) * 10) / 10;
    return String(horas).replace('.', ',') + ' h';
}
// Etiqueta de tiempo de una tarea y limpieza del "(240m)" crudo del texto
function etiquetaTiempo(f) {
    if (f.minutos === null || f.minutos === undefined) return '';
    return ' <span class="tiempo">' + formatoHoras(f.minutos) + '</span>';
}
function textoSinTiempo(t) {
    return textoPlano(t.replace(/\s*\((\d+(?:[.,]\d+)?)\s*(m|min|h|d)\)\s*$/i, ''));
}

var etiquetasEstado = { listo: 'Listo', curso: 'En construccion', noexiste: 'No existe', bloqueado: 'Bloqueado', reducido: 'Alcance reducido' };

function renderizar(modulos) {
    // KPIs
    var listos = modulos.filter(function(m) { return m.estado === 'listo'; }).length;
    var noExisten = modulos.filter(function(m) { return m.estado === 'noexiste'; }).length;
    var pendTotales = modulos.reduce(function(a, m) { return a + m.pendientes; }, 0);
    var minutosTotales = modulos.reduce(function(a, m) { return a + m.minutosPendientes; }, 0);
    var global = Math.round(modulos.reduce(function(a, m) { return a + m.pct; }, 0) / (modulos.length || 1));

    document.getElementById('kpis').innerHTML =
        kpi(modulos.length, 'Modulos totales', 'var(--texto)') +
        kpi(listos, 'Listos y validados', 'var(--verde)') +
        kpi(modulos.length - listos - noExisten, 'En construccion', 'var(--azul)') +
        kpi(pendTotales, 'Faltantes limitantes', 'var(--ambar)') +
        kpi(formatoHoras(minutosTotales), 'Horas de trabajo restantes', 'var(--rojo)');

    document.getElementById('pctGlobal').textContent = global + '%';
    document.getElementById('rellenoGlobal').style.width = global + '%';

    // Tarjetas
    var html = '';
    modulos.forEach(function(m, i) {
        var color = m.pct >= 100 ? 'var(--verde)' : (m.pct >= 60 ? 'var(--azul)' : (m.pct >= 30 ? 'var(--ambar)' : 'var(--rojo)'));
        html += '<div class="tarjeta" onclick="abrirDetalle(' + i + ')">' +
            '<div class="fila-titulo"><h3><span class="numero">' + m.numero + '.</span>' + esc(m.nombre) + '</h3>' +
            '<span class="chip ' + m.estado + '">' + etiquetasEstado[m.estado] + '</span></div>' +
            '<div class="como-va">' + textoPlano(m.comoVa) + '</div>' +
            '<div class="fila-avance"><div class="pista"><div class="relleno" style="width:' + m.pct + '%;background:' + color + '"></div></div>' +
            '<div class="pct" style="color:' + color + '">' + m.pct + '%</div></div>' +
            '<div class="pie-tarjeta"><span>' +
            (m.pendientes
                ? m.pendientes + ' faltante(s) · <b style="color:var(--azul-oscuro)">≈ ' + formatoHoras(m.minutosPendientes) + '</b>' +
                  (m.sinEstimar ? ' <span title="tareas sin tiempo estimado">+' + m.sinEstimar + ' sin estimar</span>' : '')
                : 'Sin faltantes limitantes') + '</span>' +
            '<span class="validacion" title="Pruebas por Ivan / Validado por Ivan / Validado">' +
            '<span class="punto' + (m.checks.pruebas ? ' ok' : '') + '"></span>' +
            '<span class="punto' + (m.checks.validadoIvan ? ' ok' : '') + '"></span>' +
            '<span class="punto' + (m.checks.validado ? ' ok' : '') + '"></span></span></div></div>';
    });
    document.getElementById('grilla').innerHTML = html;
    window._modulos = modulos;
}
function kpi(valor, nombre, color) {
    return '<div class="kpi"><div class="valor" style="color:' + color + '">' + valor + '</div><div class="nombre">' + nombre + '</div></div>';
}

// ── Detalle de un modulo ────────────────────────────────────────────────────
function abrirDetalle(i) {
    var m = window._modulos[i];
    var html = '<button class="cerrar-detalle" onclick="cerrarDetalle()">×</button>' +
        '<h2>' + m.numero + '. ' + esc(m.nombre) + ' — ' + m.pct + '%</h2>' +
        '<div class="como-va-det">' + textoPlano(m.comoVa) + '</div>' +
        '<div class="pista"><div class="relleno" style="width:' + m.pct + '%"></div></div>';

    if (m.hechos.length) {
        html += '<h4>Hecho (' + m.hechos.length + ')</h4><ul>';
        m.hechos.forEach(function(h) { html += '<li class="hecho">' + textoPlano(h) + '</li>'; });
        html += '</ul>';
    }
    if (m.limitantes.length) {
        html += '<h4>Falta — Limitante (' + m.limitantes.filter(function(f){return !f.hecho;}).length + ' pendiente(s) · ≈ ' + formatoHoras(m.minutosPendientes) + ')</h4><ul>';
        m.limitantes.forEach(function(f) { html += '<li class="' + (f.hecho ? 'marcado' : 'pendiente') + '">' + textoSinTiempo(f.texto) + etiquetaTiempo(f) + '</li>'; });
        html += '</ul>';
    }
    if (m.noLimitantes.length) {
        html += '<h4>Falta — No limitante (mejoras' + (m.minutosNoLimitantes ? ' · ≈ ' + formatoHoras(m.minutosNoLimitantes) : '') + ')</h4><ul>';
        m.noLimitantes.forEach(function(f) { html += '<li class="' + (f.hecho ? 'marcado' : 'pendiente') + '">' + textoSinTiempo(f.texto) + etiquetaTiempo(f) + '</li>'; });
        html += '</ul>';
    }
    if (m.criterios.length) {
        html += '<h4>Criterio de aceptacion</h4><ul>';
        m.criterios.forEach(function(c) { html += '<li class="criterio">' + textoPlano(c) + '</li>'; });
        html += '</ul>';
    }
    if (m.notas.length) {
        html += '<h4>Notas</h4>';
        m.notas.forEach(function(n) { html += '<div class="nota-doc">' + textoPlano(n) + '</div>'; });
    }
    document.getElementById('cuerpoDetalle').innerHTML = html;
    document.getElementById('velo').classList.add('abierto');
}
function cerrarDetalle() { document.getElementById('velo').classList.remove('abierto'); }

// ── Editor integrado ────────────────────────────────────────────────────────
function abrirEditor() {
    var texto = contenidoActual;
    // Si quedo un borrador sin guardar (por caida o sesion expirada), ofrecer recuperarlo
    try {
        var borrador = localStorage.getItem('avance_borrador');
        if (borrador && borrador !== contenidoActual &&
            confirm('Hay un borrador sin guardar de una edicion anterior. ¿Quieres recuperarlo?')) {
            texto = borrador;
        }
    } catch (e) {}
    document.getElementById('areaEdicion').value = texto;
    document.getElementById('vistaPanel').style.display = 'none';
    document.getElementById('vistaEditor').style.display = 'block';
}
function cerrarEditor() {
    if (document.getElementById('areaEdicion').value !== contenidoActual &&
        !confirm('Hay cambios sin guardar. ¿Salir igual?')) return;
    document.getElementById('vistaEditor').style.display = 'none';
    document.getElementById('vistaPanel').style.display = 'block';
}
function guardarDocumento(esReintento) {
    var datos = new FormData();
    datos.append('contenido', document.getElementById('areaEdicion').value);
    fetch('?accion=guardar', { method: 'POST', body: datos })
        .then(function(r) {
            return r.json().then(function(j) { return { estado: r.status, json: j }; })
                           .catch(function() { return { estado: r.status, json: null }; });
        })
        .then(function(r) {
            // Sesion expirada: pedir la clave ahi mismo y reintentar — los cambios no se pierden
            if (r.estado === 401 || (r.json && r.json.sesion_expirada)) {
                if (esReintento) { alert('No se pudo renovar la sesion. Tus cambios siguen en pantalla y ademas quedaron respaldados en este navegador.'); return; }
                var clave = prompt('La sesion expiro mientras editabas. Ingresa la clave para guardar (tus cambios estan a salvo):');
                if (clave === null) return;
                var datosClave = new FormData();
                datosClave.append('clave_acceso', clave);
                fetch('?accion=entrar', { method: 'POST', body: datosClave })
                    .then(function(x) { return x.json(); })
                    .then(function(x) {
                        if (x.success) guardarDocumento(true);
                        else alert(x.message || 'Clave incorrecta. Tus cambios siguen en pantalla.');
                    })
                    .catch(function() { alert('No se pudo reconectar. Tus cambios siguen en pantalla y respaldados en este navegador.'); });
                return;
            }
            if (!r.json) { alert('El servidor respondio algo inesperado (HTTP ' + r.estado + '). Tus cambios siguen en pantalla.'); return; }
            if (!r.json.success) { alert(r.json.message); return; }
            try { localStorage.removeItem('avance_borrador'); } catch (e) {}
            document.getElementById('vistaEditor').style.display = 'none';
            document.getElementById('vistaPanel').style.display = 'block';
            cargar();
        })
        .catch(function() { alert('No se pudo guardar (sin conexion con el servidor). Tus cambios siguen en pantalla y respaldados en este navegador.'); });
}

// Borrador automatico: mientras se edita, una copia queda en el navegador
document.getElementById('areaEdicion').addEventListener('input', function() {
    try { localStorage.setItem('avance_borrador', this.value); } catch (e) {}
});

// ── Carga inicial ───────────────────────────────────────────────────────────
function cargar() {
    fetch('?accion=leer')
        .then(function(r) {
            if (r.status === 401) { location.reload(); return null; }
            return r.json();
        })
        .then(function(r) {
            if (!r) return;
            if (!r.success) { alert(r.message); return; }
            contenidoActual = r.contenido;
            document.getElementById('fechaDoc').textContent = r.modificado;
            renderizar(analizarDocumento(r.contenido));
        })
        .catch(function() { alert('No se pudo leer el documento (sin conexion con el servidor).'); });
}
cargar();
</script>
<?php endif; ?>
</body>
</html>
