<?php
// Llamamos los plugins necesarios para la conexion.
namespace dokuwiki\plugin\bez\meta;
use \dokuwiki\plugin\bez;
use \dokuwiki\plugin\do;
use dokuwiki\plugin\bez\mdl\TaskFactory;
/** @var \helper_plugin_bez_db $db_helper */
/** @var helper_plugin_sqlite $sqlite */
/** @var helper_plugin_do $hlp */

if (!defined('DOKU_INC')) {
    define('DOKU_INC', realpath(__DIR__ . '/../../../../') . '/');
}

require_once DOKU_INC . 'inc/init.php';
require_once(__DIR__ . '/path_helper.php');

// ===== VERIFICACIÓN DE AUTENTICACIÓN =====
// Solo usuarios logueados pueden usar este endpoint.
if (empty($_SERVER['REMOTE_USER'])) {
    header("Content-Type: application/json");
    echo json_encode(["status" => "error", "error" => "No autorizado. Debe iniciar sesión."]);
    http_response_code(401);
    exit;
}

// ===== DETERMINACIÓN DE NIVEL BEZ =====
// Determinar permisos BEZ del usuario según sus grupos de DokuWiki.
// Niveles: 'admin' (manager/sie/director), 'sysadmin' (admin DW), 'leader' (bez_leader), 'user' (resto).
global $auth, $conf;
$_bez_user_level = 'user';
// Leer grupo admin configurable (default: 'bez_admin')
$_bez_admin_group = !empty($conf['plugin']['bez']['admin_group']) ? $conf['plugin']['bez']['admin_group'] : 'bez_admin';
if ($auth) {
    $userData = $auth->getUserData($_SERVER['REMOTE_USER']);
    if ($userData && isset($userData['grps'])) {
        $grps = $userData['grps'];
        // Manager = DIOS (solo grupo manager, asignado por managerprotect)
        if (in_array('manager', $grps)) {
            $_bez_user_level = 'admin';
        }
        // Director (admin_group) = líder de proyectos
        elseif (in_array($_bez_admin_group, $grps)) {
            $_bez_user_level = 'admin';
        }
        // Admin DokuWiki = gestiona sistema, NO proyectos
        elseif (in_array('admin', $grps)) {
            $_bez_user_level = 'sysadmin';
        }
        // bez_leader
        elseif (in_array('bez_leader', $grps)) {
            $_bez_user_level = 'leader';
        }
    }
}

// Acciones que requieren nivel LEADER o superior (operaciones destructivas/modificación masiva).
// SYSADMIN (grupo admin DW) está bloqueado de todas estas acciones.
$_bez_leader_actions = [
    'eliminarActividadProyecto',
    'finalizarActividadProyecto',
    'finalizarTodoProyecto',
    'recalcularCodigoProyecto',
    'recargarModulosProyecto',
    'crearAplicadorAdminControl',
    'actualizarProgresoAdminControl',
    'updateNombreActividadProyecto',
    'agregarActividadProyecto',
    'visualizarActividadProyecto',
    'activarEjecucionProyecto',
    'ajustarAvanceProyecto',
    'guardarResumenIAarchivo',
    'recalcularProgresoAdminControl',
    'exportarComentariosAdminControl',
    'finalizarAdminSeccion',
    'finalizarTodosRequisitosAdmin',
    'marcarPreferenciaAdmin',
    'editarAdminThread',
    'eliminarAdminThread',
    'scanCompartidos',
    'adminThreadTaskAdd',
    'adminThreadTaskToggle',
    'adminThreadTaskDelete',
    'adminThreadTaskNotify',
    'adminThreadCommentAdd',
    'adminThreadCommentEdit',
    'adminThreadCommentDelete',
    'adminThreadParticipantAdd',
    'adminThreadParticipantRemove',
];

// Acciones administrativas que también permiten grupos manager/sie R**G
$_bez_admin_finalizar_actions = ['finalizarAdminSeccion', 'finalizarTodosRequisitosAdmin', 'marcarPreferenciaAdmin', 'editarAdminThread', 'scanCompartidos'];

// Acciones de TAREAS ADMINISTRATIVAS: director NO tiene permisos.
// DIOS en admin_thread: manager, admin (grupo DW). Director queda excluido.
$_bez_admin_thread_actions = ['finalizarAdminSeccion', 'finalizarTodosRequisitosAdmin', 'marcarPreferenciaAdmin', 'editarAdminThread', 'eliminarAdminThread',
    'adminThreadTaskAdd', 'adminThreadTaskToggle', 'adminThreadTaskDelete', 'adminThreadTaskNotify',
    'adminThreadCommentAdd', 'adminThreadCommentEdit', 'adminThreadCommentDelete',
    'adminThreadParticipantAdd', 'adminThreadParticipantRemove'];

// Verificar permisos por acción ANTES de procesar.
// sysadmin (grupo admin DW) NO puede ejecutar acciones de proyecto
if (!empty($_POST['accion']) && in_array($_POST['accion'], $_bez_leader_actions)) {

    // ── Bloqueo director en acciones admin_thread ──
    // Director (bez_admin/director) NO tiene permisos en tareas administrativas.
    // Solo manager y admin(DW) son DIOS aquí.
    if (in_array($_POST['accion'], $_bez_admin_thread_actions) && $auth) {
        $userData = $auth->getUserData($_SERVER['REMOTE_USER']);
        $_grps = ($userData && isset($userData['grps'])) ? $userData['grps'] : [];
        $_isDirector = in_array('director', $_grps) || in_array($_bez_admin_group, $_grps);
        $_isAdminThreadGod = in_array('manager', $_grps) || in_array('admin', $_grps)
            || (class_exists('action_plugin_managerprotect') && action_plugin_managerprotect::shouldHaveManagerGroup($_SERVER['REMOTE_USER']));
        if ($_isDirector && !$_isAdminThreadGod) {
            header("Content-Type: application/json");
            echo json_encode(["status" => "error", "error" => "No tiene permisos en tareas administrativas."]);
            http_response_code(403);
            exit;
        }
    }

    if ($_bez_user_level === 'user' || $_bez_user_level === 'sysadmin') {
        // Excepción: acciones de admin_thread permiten admin(DW) como DIOS
        $isAllowed = false;
        if (in_array($_POST['accion'], $_bez_admin_thread_actions) && $auth) {
            $userData = $auth->getUserData($_SERVER['REMOTE_USER']);
            if ($userData && isset($userData['grps'])) {
                $isAllowed = in_array('admin', $userData['grps']) || in_array('manager', $userData['grps']);
            }
        }
        // Excepción: acciones de finalizar admin permiten grupos manager/sie
        if (!$isAllowed && in_array($_POST['accion'], $_bez_admin_finalizar_actions) && $auth) {
            $userData = $auth->getUserData($_SERVER['REMOTE_USER']);
            if ($userData && isset($userData['grps'])) {
                $isAllowed = in_array('manager', $userData['grps']);
            }
        }
        // Excepción: participantes pueden agregar tareas, comentarios y editar sus propios comentarios
        if (!$isAllowed && in_array($_POST['accion'], ['adminThreadTaskAdd', 'adminThreadCommentAdd', 'adminThreadCommentEdit'])) {
            $adminThreadId = intval($_POST['admin_thread_id'] ?? 0);
            if ($adminThreadId > 0) {
                $dbHelper = plugin_load('helper', 'bez_db');
                if ($dbHelper) {
                    $sqliteP = $dbHelper->getDB();
                    if ($sqliteP) {
                        $resP = $sqliteP->query("SELECT id FROM admin_thread_participant WHERE admin_thread_id = ? AND user_id = ?", $adminThreadId, $_SERVER['REMOTE_USER']);
                        if ($sqliteP->res2row($resP)) {
                            $isAllowed = true;
                        }
                    }
                }
            }
        }
        if (!$isAllowed) {
            header("Content-Type: application/json");
            echo json_encode(["status" => "error", "error" => "No tiene permisos suficientes para esta acción."]);
            http_response_code(403);
            exit;
        }
    }
}

// ===== VERIFICACIÓN CSRF (token de seguridad de DokuWiki) =====
// CSRF es OBLIGATORIO en toda petición POST — sin excepciones.
if (!empty($_POST)) {
    if (!isset($_POST['sectok']) || !checkSecurityToken($_POST['sectok'])) {
        header("Content-Type: application/json");
        echo json_encode(["status" => "error", "error" => "Token de seguridad inválido o ausente. Recargue la página."]);
        http_response_code(403);
        exit;
    }
}

// Conexion base de datos bez.
$action = plugin_load('helper', 'bez_db');

if (!$action) {
    throw new \Exception("No se pudo cargar el plugin 'bez'.");
}

$sqlite = $action->getDB();

// Conexion base de datos do.
$hlp = plugin_load('helper', 'do');

if (!$hlp) {
    throw new \Exception("No se pudo cargar el plugin 'do'.");
}


// Configurar la respuesta como JSON
header("Content-Type: application/json");

// Helper: sanitizar HTML del editor rico (solo tags seguros de Pell)
function _sanitizeRichHtml($html) {
    if (empty($html)) return '';
    $allowed = '<b><i><u><s><strong><em><h1><h2><h3><h4><h5><h6><p><br><ul><ol><li><a><font><span><div><sub><sup><blockquote><hr>';
    $clean = strip_tags($html, $allowed);
    $clean = preg_replace('/\s+on\w+\s*=\s*["\'][^"\']*["\']/i', '', $clean);
    $clean = preg_replace('/\s+on\w+\s*=\s*[^\s>]*/i', '', $clean);
    $clean = preg_replace('/href\s*=\s*["\']?\s*javascript:[^"\'>\s]*/i', 'href="#"', $clean);
    $clean = preg_replace('/src\s*=\s*["\']?\s*(?:javascript|data):[^"\'>\s]*/i', 'src=""', $clean);
    return $clean;
}

// Helper: verificar si el usuario puede operar en un admin_thread específico
// GOD (manager/admin DW) = siempre. Otros = solo si es creador/trabajador O participante.
function _canOperateAdminThread($sqlite, $adminThreadId, $currentUser, $auth) {
    $_ud = ($auth) ? $auth->getUserData($currentUser) : false;
    $_grps = (is_array($_ud) && isset($_ud['grps'])) ? $_ud['grps'] : [];
    if (in_array('manager', $_grps) || in_array('admin', $_grps)) return true;
    $resOwner = $sqlite->query("SELECT created_by, trabajador_id FROM admin_thread WHERE id = ? AND deleted_at IS NULL", $adminThreadId);
    $owner = $sqlite->res2row($resOwner);
    if ($owner && ($owner['created_by'] === $currentUser || $owner['trabajador_id'] === $currentUser)) return true;
    $resPart = $sqlite->query("SELECT id FROM admin_thread_participant WHERE admin_thread_id = ? AND user_id = ?", $adminThreadId, $currentUser);
    if ($sqlite->res2row($resPart)) return true;
    return false;
}

// Verificar si la petición es POST y tiene la acción requerida
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["accion"])) {
    global $ID, $TEXT, $INPUT, $INFO;

    try {
        
        if ($_POST["accion"] === "updateNombreActividadProyecto") {
            // Actualizamos el registro en Base de datos.
            if (!empty($_POST['nombre_actividad'])){
                $sql = "UPDATE progreso_proyecto
                        SET nombre_submodulo = ?
                        WHERE id_progreso = ?";
                $params = [$_POST['nombre_actividad'], intval($_POST['id_proyecto'])];
                $res = $sqlite->query($sql, $params);
                
                echo json_encode(["status" => "success", "mensaje" => 'Actualizacion correcto.']);
                exit;
            } else{
                echo json_encode(["status" => "error", "mensaje" => 'Ingrese un texto valido.']);
                exit;
            }

        } else if ( $_POST['accion'] === 'agregarActividadProyecto' ){
            // Creamos el nuevo modulo.
            if (!empty($_POST['id_proyecto']) && !empty($_POST['nombre_modulo'])){

                $infoModulo = extraerSubmodulos($_POST['nombre_modulo'], intval($_POST['id_proyecto']));

                $sql = "INSERT OR IGNORE INTO progreso_proyecto 
                            (proyecto_id, nombre_submodulo, ruta_submodulo, mostrar_submodulo)
                        VALUES (?, ?, ?, '1')";
        
                $params = [intval($_POST['id_proyecto']), $infoModulo[0]['nombre'], $infoModulo[0]['ruta']];
                $res = $sqlite->query($sql, $params);

                echo json_encode(["status" => "success", "mensaje" => 'Modulo creado Correctamente.']);
                exit;

            } else{
                echo json_encode(["status" => "error", "mensaje" => 'Ingrese un texto valido']);
                exit;
            }
            
        } else if ( $_POST['accion'] === 'eliminarActividadProyecto'){
            // Eliminamos el modulo
            $sql = "DELETE FROM
                    progreso_proyecto
                    WHERE id_progreso = ?";
            $params = [intval($_POST['id_proyecto'])];
            $res = $sqlite->query($sql, $params);
            echo json_encode(["status" => "success", "mensaje" => '¡Modulo Eliminado Correctamente!']);
            exit;

        } else if ( $_POST['accion'] === 'visualizarActividadProyecto'){
            // Eliminamos el modulo
            $sql = "UPDATE 
                    progreso_proyecto 
                    SET mostrar_submodulo = ?
                    WHERE id_progreso = ?";
            $params = [intval($_POST['estado']), intval($_POST['id_proyecto'])];
            $res = $sqlite->query($sql, $params);
            echo json_encode(["status" => "success", "mensaje" => '¡Modulo Actualizado Correctamente!']);
            exit;

        } else if ( $_POST['accion'] === 'activarEjecucionProyecto'){
            // Activamos proceso Ejecucion el modulo
            $sql = "UPDATE progreso_proyecto  
                    SET procesos_ejecucion = ?
                    WHERE id_progreso = ?";
            $params = [intval($_POST['estado']), intval($_POST['id_proyecto'])];
            $res = $sqlite->query($sql, $params);
            echo json_encode(["status" => "success", "mensaje" => '¡Proceso Actualizado Correctamente!']);
            exit;

        } else if ( $_POST['accion'] === 'finalizarActividadProyecto'){
            // OPCIÓN B: Finalizar actividad marcando checks pendientes en BD "do".
            // 1. Obtenemos la ruta del submódulo desde progreso_proyecto.
            $idProgreso = intval($_POST['id_proyecto']);
            $sqlRuta = "SELECT ruta_submodulo, total_requerimientos FROM progreso_proyecto WHERE id_progreso = ?";
            $resRuta = $sqlite->query($sqlRuta, [$idProgreso]);
            $submodulo = $sqlite->res2row($resRuta);

            if (!$submodulo) {
                echo json_encode(["status" => "error", "mensaje" => "No se encontró el módulo solicitado."]);
                exit;
            }

            $rutaNorm = cleanID($submodulo['ruta_submodulo']);

            // 2. Conexión independiente a DB "do".
            $db_do = plugin_load('helper', 'sqlite');
            if (!$db_do || !$db_do->init('do', bez_plugin_base() . 'do/db/')) {
                echo json_encode(["status" => "error", "mensaje" => "Error de conexión con base de datos do."]);
                exit;
            }

            // 3. Consultamos el total de tareas en el módulo (BD do).
            $resTotalDo = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaNorm);
            $totalTareas = intval($db_do->res2single($resTotalDo));

            // 4. Si no hay tareas registradas, el módulo no ha sido visitado → avisar al usuario.
            if ($totalTareas === 0) {
                echo json_encode([
                    "status"  => "sin_visitar",
                    "mensaje" => "Este módulo aún no ha sido visitado. Debe ingresar al módulo al menos una vez para que el sistema registre las tareas. Luego podrá finalizarlo."
                ]);
                exit;
            }

            // 5. Obtenemos las tareas que AÚN NO tienen status (pendientes).
            $sqlPendientes = 'SELECT A.page, A.md5
                              FROM tasks A
                              LEFT JOIN task_status B ON A.page = B.page AND A.md5 = B.md5
                              WHERE A.page = ? AND B.status IS NULL';
            $resPendientes = $db_do->query($sqlPendientes, $rutaNorm);
            $tareasPendientes = $db_do->res2arr($resPendientes);

            // 6. Marcamos cada tarea pendiente como "Completado" en task_status.
            $fechaCierre = date('Y-m-d');
            $usuario = $_SERVER['REMOTE_USER'] ?? 'system';
            $checksMarcados = 0;

            foreach ($tareasPendientes as $tarea) {
                $db_do->query(
                    'INSERT OR IGNORE INTO task_status (page, md5, status, closedby, msg) VALUES (?, ?, ?, ?, ?)',
                    $tarea['page'],
                    $tarea['md5'],
                    $fechaCierre,
                    $usuario,
                    'Completado'
                );
                $checksMarcados++;
            }

            // 7. Recalculamos el progreso real desde BD "do" (igual que ajustarAvance).
            $resTotalFinal = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaNorm);
            $totalFinal = intval($db_do->res2single($resTotalFinal));

            $resTermFinal = $db_do->query(
                'SELECT count(1) FROM task_status WHERE page = ? AND msg IS NOT NULL AND msg IN ("Completado", "No Aplica")',
                $rutaNorm
            );
            $terminadosFinal = intval($db_do->res2single($resTermFinal));

            $porcentajeFinal = ($totalFinal > 0) ? round(($terminadosFinal / $totalFinal) * 100, 2) : 0;

            // 8. Actualizamos progreso_proyecto con los valores reales.
            $fechaActual = date('Y-m-d H:i:s');
            $sqlite->query(
                'UPDATE progreso_proyecto
                 SET total_requerimientos = ?,
                     requerimientos_terminados = ?,
                     porcentaje_progreso = ?,
                     fecha_actualizacion = ?
                 WHERE id_progreso = ?',
                $totalFinal, $terminadosFinal, $porcentajeFinal, $fechaActual, $idProgreso
            );

            echo json_encode([
                "status"  => "success",
                "mensaje" => "¡Proceso Finalizado! Se marcaron {$checksMarcados} tareas pendientes como completadas. Progreso: {$porcentajeFinal}%"
            ]);
            exit;

        } else if ( $_POST['accion'] === 'finalizarTodoProyecto'){
            // Finalizar TODOS los módulos del proyecto: marca checks pendientes en BD "do".
            // Solo para usuarios con grupo "editarproyecto" o "manager".

            // 1. Conexión independiente a DB "do".
            $db_do = plugin_load('helper', 'sqlite');
            if (!$db_do || !$db_do->init('do', bez_plugin_base() . 'do/db/')) {
                echo json_encode(["status" => "error", "mensaje" => "Error de conexión con base de datos do."]);
                exit;
            }

            $proyectoId = intval($_POST['id_proyecto']);
            $fechaCierre = date('Y-m-d');
            $fechaActual = date('Y-m-d H:i:s');
            $usuario = $_SERVER['REMOTE_USER'] ?? 'system';

            // 2. Obtenemos TODOS los submódulos del proyecto.
            $sqlSub = "SELECT id_progreso, ruta_submodulo, nombre_submodulo FROM progreso_proyecto WHERE proyecto_id = ?";
            $resSub = $sqlite->query($sqlSub, [$proyectoId]);
            $submodulos = $sqlite->res2arr($resSub);

            if (empty($submodulos)) {
                echo json_encode(["status" => "error", "mensaje" => "No se encontraron módulos para este proyecto."]);
                exit;
            }

            $modulosFinalizados = 0;
            $modulosSaltados = [];
            $totalChecksMarcados = 0;

            // 3. Iteramos cada submódulo.
            foreach ($submodulos as $sub) {
                $rutaNorm = cleanID($sub['ruta_submodulo']);

                // Contamos tareas en BD "do".
                $resTotalDo = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaNorm);
                $totalTareas = intval($db_do->res2single($resTotalDo));

                // Si el módulo no ha sido visitado (0 tareas), lo saltamos.
                if ($totalTareas === 0) {
                    $modulosSaltados[] = $sub['nombre_submodulo'];
                    continue;
                }

                // Obtenemos tareas pendientes (sin status).
                $sqlPend = 'SELECT A.page, A.md5
                            FROM tasks A
                            LEFT JOIN task_status B ON A.page = B.page AND A.md5 = B.md5
                            WHERE A.page = ? AND B.status IS NULL';
                $resPend = $db_do->query($sqlPend, $rutaNorm);
                $pendientes = $db_do->res2arr($resPend);

                // Marcamos cada tarea pendiente como "Completado".
                foreach ($pendientes as $tarea) {
                    $db_do->query(
                        'INSERT OR IGNORE INTO task_status (page, md5, status, closedby, msg) VALUES (?, ?, ?, ?, ?)',
                        $tarea['page'],
                        $tarea['md5'],
                        $fechaCierre,
                        $usuario,
                        'Completado'
                    );
                    $totalChecksMarcados++;
                }

                // Recalculamos progreso real.
                $resTotalFinal = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaNorm);
                $totalFinal = intval($db_do->res2single($resTotalFinal));

                $resTermFinal = $db_do->query(
                    'SELECT count(1) FROM task_status WHERE page = ? AND msg IS NOT NULL AND msg IN ("Completado", "No Aplica")',
                    $rutaNorm
                );
                $terminadosFinal = intval($db_do->res2single($resTermFinal));

                $porcentajeFinal = ($totalFinal > 0) ? round(($terminadosFinal / $totalFinal) * 100, 2) : 0;

                // Actualizamos progreso_proyecto.
                $sqlite->query(
                    'UPDATE progreso_proyecto
                     SET total_requerimientos = ?,
                         requerimientos_terminados = ?,
                         porcentaje_progreso = ?,
                         fecha_actualizacion = ?
                     WHERE id_progreso = ?',
                    $totalFinal, $terminadosFinal, $porcentajeFinal, $fechaActual,
                    intval($sub['id_progreso'])
                );
                $modulosFinalizados++;
            }

            // 4. Construimos respuesta informativa.
            $mensaje = "¡Proyecto Finalizado! {$modulosFinalizados} módulos completados, {$totalChecksMarcados} tareas marcadas.";

            if (!empty($modulosSaltados)) {
                $listaSaltados = implode(', ', $modulosSaltados);
                echo json_encode([
                    "status"   => "parcial",
                    "mensaje"  => $mensaje,
                    "saltados" => $listaSaltados,
                    "cantidad_saltados" => count($modulosSaltados)
                ]);
            } else {
                echo json_encode([
                    "status"  => "success",
                    "mensaje" => $mensaje
                ]);
            }
            exit;

        } else if ( $_POST['accion'] === 'recargarModulosProyecto'){

            $res = 1;
            // IMPORTANTE: Excluir proyectos eliminados (soft delete)
            $sql_thread = "SELECT content, title
            FROM thread
            WHERE id = ? AND deleted_at IS NULL";
            $params_thread = [intval($_POST['id_proyecto'])];
            $res_thread = $sqlite->query($sql_thread, $params_thread);
            $rows = $sqlite->res2arr($res_thread);

            // Verificar si el proyecto existe y no está eliminado
            if (empty($rows)) {
                echo json_encode(["status" => "error", "mensaje" => "Proyecto no encontrado o ha sido eliminado."]);
                exit;
            }

            $dataModulos = extraerSubmodulos($rows[0]['content'], intval($_POST['id_proyecto']), $rows[0]['title']);

            try {
                $sql = "INSERT OR IGNORE INTO progreso_proyecto (proyecto_id, nombre_submodulo, ruta_submodulo, procesos_ejecucion) VALUES (?, ?, ?, ?)";
                foreach ($dataModulos as $submodulo) {
                    $res_insert = $sqlite->query($sql, array(intval($_POST['id_proyecto']), $submodulo['nombre'], $submodulo['ruta'], $submodulo['procesos_ejecucion']));
                }
            } catch (\Throwable $th) {
                $res = 0;
                dbglog('bez ajax: recargarModulosProyecto error: ' . $th->getMessage());
            }

            if ($res == 1) {
                echo json_encode(["status" => "success", "mensaje" => 'Proceso Actualizado Correctamente!']);
            } else {
                echo json_encode(["status" => "error", "mensaje" => 'Error al crear los modulos!']);
            }
            exit;

        } else if ( $_POST['accion'] === 'recalcularCodigoProyecto'){
            // Recálculo COMPLETO del contenido del proyecto - regenera desde cero
            $idProyecto = intval($_POST['id_proyecto']);
            $codigo = $idProyecto;

            try {
                // Obtenemos datos del thread
                $sql_thread = "SELECT content, title, type FROM thread WHERE id = ? AND deleted_at IS NULL";
                $res_thread = $sqlite->query($sql_thread, [$idProyecto]);
                $rows = $sqlite->res2arr($res_thread);

                if (empty($rows)) {
                    echo json_encode(["status" => "error", "mensaje" => 'Proyecto no encontrado o ha sido eliminado.']);
                    exit;
                }

                $contenidoOriginal = $rows[0]['content'];
                $titulo = $rows[0]['title'];
                $rest = substr($titulo, 0, 20);

                // Obtener nombre del cliente desde thread_label + label
                $auditado = '';
                $sql_label = "SELECT l.name FROM label l
                              INNER JOIN thread_label tl ON l.id = tl.label_id
                              WHERE tl.thread_id = ?";
                $res_label = $sqlite->query($sql_label, [$idProyecto]);
                $label_rows = $sqlite->res2arr($res_label);
                if (!empty($label_rows)) {
                    $auditado = $label_rows[0]['name'];
                } else {
                    // Intentar extraer del contenido actual (entre ~)
                    $partes = explode('~', $contenidoOriginal);
                    if (isset($partes[1])) {
                        $auditado = trim($partes[1]);
                    }
                    // Intentar extraer del título (después de " -")
                    if (empty($auditado)) {
                        $variable = explode(' -', $titulo);
                        if (isset($variable[1])) {
                            $auditado = trim($variable[1]);
                        }
                    }
                }

                // Determinar tipo de proyecto basado en el título
                $proyecto = "X";
                if ($rest == "Revisoría fiscal (A") { $proyecto = "A"; }
                elseif ($rest == "Revisoría fiscal (B") { $proyecto = "B"; }
                elseif ($rest == "Revisoría fiscal (C") { $proyecto = "C"; }
                elseif ($rest == "Revisoría fiscal (D") { $proyecto = "D"; }
                elseif ($rest == "Revisoría fiscal (E") { $proyecto = "E"; }
                elseif ($rest == "Revisoría fiscal (F") { $proyecto = "F"; }
                elseif ($rest == "Revisoría fiscal (G") { $proyecto = "G"; }
                elseif ($rest == "Revisoría fiscal (H") { $proyecto = "H"; }
                elseif ($rest == "Outsourcing contable") { $proyecto = "I"; }
                elseif (trim($rest) == "Asesoría contable") { $proyecto = "J"; }
                elseif (trim($rest) == "Otros proyectos") { $proyecto = "K"; }
                elseif (trim($rest) == "Reporte de gestión") { $proyecto = "L"; }
                elseif (trim($rest) == "Outsourcing" || trim($rest) == "OUTSOURCING") { $proyecto = "M"; }
                elseif (trim($rest) == "Asesoría") { $proyecto = "N"; }

                if ($proyecto == "X") {
                    echo json_encode(["status" => "error", "mensaje" => 'No se pudo identificar el tipo de proyecto a partir del título: ' . $titulo]);
                    exit;
                }

                // ============================================================
                // REGENERAR CONTENIDO COMPLETO DESDE CERO
                // Replica EXACTAMENTE la lógica de thread_report.php
                // usando las mismas variables y concatenaciones
                // ============================================================

                $texto1 = "'200px'";
                $texto2 = "'true'";
                $texto3 = "'true'";
                $texto4 = "'false'";
                $texto5 = "'lfrtip'";
                $micarpeta = "proyectos:auditorias:programas_auditoria:";

                // --- Variables compartidas (idénticas a thread_report.php) ---

                $programas0 = "

<wrap lo>
A continuación determine programas de auditoría adicionales aplicables al proyecto. Una vez determinados, activelos haciendo un clic sobre el nombre del programa respectivo y seleccionando el icono {{:boton2:270.png?20|}} ubicado en el costado derecho. Finalmente, guarde el documento:
</wrap>


";

                $programasOu0 = "

<wrap lo>

A continuación determine los programas adicionales de revisión aplicables al proyecto. Una vez determinados, activelos haciendo un clic sobre el nombre del programa respectivo y seleccionando el icono {{:boton2:270.png?20|}} ubicado en el costado derecho. Finalmente, guarde el documento:

</wrap>



";

                // --- Tabla de programas de auditoría (31 programas) ---
                $programas1 = "<datatable scroll-y=" . $texto1 . " searching=" . $texto2 . " state-save=" . $texto3 . " paging=" . $texto4 . " auto-width=" . $texto3 . " info=" . $texto4 . " dom=" . $texto5 . ">


^ No. ^ Nombre del programa ^
|  1.  |[[" . $micarpeta . "28_efectivo:" . $codigo . ":registro|Efectivo y equivalentes al efectivo]]|
|  2.  |[[" . $micarpeta . "29_inversiones:" . $codigo . ":registro|Inversiones permanentes]]|
|  3.  |[[" . $micarpeta . "31_cuentas_cobrar:" . $codigo . ":registro|Cuentas por cobrar]]|
|  4.  |[[" . $micarpeta . "32_activos_intangibles:" . $codigo . ":registro|Activos intangibles]]|
|  5.  |[[" . $micarpeta . "33_gastos_anticipado:" . $codigo . ":registro|Gastos pagados por anticipado]]|
|  6.  |[[" . $micarpeta . "34_pasivos_provisiones:" . $codigo . ":registro|Pasivos, Provisiones y Contingencias]]|
|  7.  |[[" . $micarpeta . "47_ica_bimestral:" . $codigo . ":registro|Revisión ICA bimestral]]|
|  8.  |[[" . $micarpeta . "48_iva_bimestral:" . $codigo . ":registro|Revisión IVA bimestral]]|
|  9.  |[[" . $micarpeta . "49_rete_fte:" . $codigo . ":registro|Revisión Retención en la fuente]]|
|  10.  |[[" . $micarpeta . "50_rte_ica:" . $codigo . ":registro|Revisión RTE ICA bimestral]]|
|  11.  |[[" . $micarpeta . "51_regimen_simple:" . $codigo . ":registro|Revisión impuesto regimen simple]]|
|  12.  |[[" . $micarpeta . "35_impuestos_ganancias:" . $codigo . ":registro|Impuesto a las Ganancias e Impuesto Diferido]]|
|  13.  |[[" . $micarpeta . "36_ingresos_operacionales:" . $codigo . ":registro|Ingresos Operacionales]]|
|  14.  |[[" . $micarpeta . "37_contingencias:" . $codigo . ":registro|Contingencias No Cuantificables y Compromisos]]|
|  15.  |[[" . $micarpeta . "38_patrimonio:" . $codigo . ":registro|Patrimonio]]|
|  16.  |[[" . $micarpeta . "39_gastos_operacionales:" . $codigo . ":registro|Gastos Operacionales]]|
|  17.  |[[" . $micarpeta . "40_costo_ventas:" . $codigo . ":registro|Costos de ventas]]|
|  18.  |[[" . $micarpeta . "41_propiedad:" . $codigo . ":registro|Propiedad planta y equipo]]|
|  19.  |[[" . $micarpeta . "43_estimaciones:" . $codigo . ":registro|Estimaciones Contables]]|
|  20.  |[[" . $micarpeta . "44_gastos_personales:" . $codigo . ":registro|Gastos de personal]]|
|  21.  |[[" . $micarpeta . "45_inventarios:" . $codigo . ":registro|Inventarios]]|
|  22.  |[[" . $micarpeta . "46_otros_asuntos:" . $codigo . ":registro|Otros Asuntos]]|
|  23.  |[[" . $micarpeta . "bonos:" . $codigo . ":registro|Programa de Auditoría bonos de riesgo (Check Lisk)]]|
|  24.  |[[" . $micarpeta . "bancos:" . $codigo . ":registro|Programa de Auditoría bancos (Check Lisk)]]|
|  25.  |[[" . $micarpeta . "inversiones:" . $codigo . ":registro|Programa de inversiones (Check Lisk)]]|
|  26.  |[[" . $micarpeta . "normatividad:" . $codigo . ":registro|Programa revisión normas legales (Check Lisk)]]|
|  27.  |[[" . $micarpeta . "impuesto_al_patrimonio:" . $codigo . ":registro|Programa de impuesto al patrimonio (Check Lisk)]]|
|  28.  |[[" . $micarpeta . "reestructuracion:" . $codigo . ":registro|Programa de Auditoría para empresas en reestructuración (Check Lisk)]]|
|  29.  |[[" . $micarpeta . "patrimonio:" . $codigo . ":registro|Programa de Auditoría patrimonio (Check Lisk)]]|
|  30.  |[[" . $micarpeta . "auditoria_de_cierre:" . $codigo . ":registro|Programa de Auditoría de cierre (Check Lisk)]]|
|  31.  |[[" . $micarpeta . "auditoria_de_cierre2:" . $codigo . ":registro|Programa de Auditoría de cierre - Modelo 2 (Check Lisk)]]|
</datatables>

";

                // --- Programa general (genérico) ---
                $rfNias8 = "[[{}proyectos:auditorias:programas_auditoria:general:" . $codigo . ":registro|8. PROGRAMA GENERAL DE AUDITORÍA Y PAPELES DE TRABAJO]]";

                // ============================================================
                // ENCABEZADOS (variables $xxxN0) - idénticos a thread_report.php
                // ============================================================

                $rfNiasJur0 = "|  Revisoría fiscal (A)  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  PROYECTO CLIENTE NUEVO  |
|  NIAS  |:::   |:::  |


<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, el diagnóstico inicial del cliente, la planeación, el análisis de riesgos, el control de riesgos, control de tareas programadas y las demás actividades de operación de los trabajos de Revisoría Fiscal a realizar durante el año actual:
</wrap>

";

                $rfNiasJurR0 = "|  Revisoría fiscal (B)  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  PROYECTO CLIENTE RECURRENTE  |
|  NIAS   |:::   |:::  |

<wrap lo>
El objetivo es dar continuidad a la gestión de cumplimiento del sistema de gestión de calidad, el análisis de recurrencia del cliente, la planeación, el análisis de riesgos, el control de riesgos, control de tareas programadas y las demás actividades de operación de los trabajos de Revisoría Fiscal a realizar durante el año actual:
</wrap>

";

                $rfNagasJur0 = "|  Revisoría fiscal (C)  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  PROYECTO CLIENTE NUEVO  |
|  NAGAS  |:::   |:::  |


<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, el diagnóstico inicial del cliente, la planeación, el análisis de riesgos, el control de riesgos, control de tareas programadas y las demás actividades de operación de los trabajos de Revisoría Fiscal a realizar durante el año actual:
</wrap>

";

                $rfNagasJurR0 = "|  Revisoría fiscal (D)  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  PROYECTO CLIENTE RECURRENTE  |
|  NAGAS  |:::   |:::  |


<wrap lo>
El objetivo es dar continuidad a la gestión de cumplimiento del sistema de gestión de calidad, el análisis de recurrencia del cliente, la planeación, el análisis de riesgos, el control de riesgos, control de tareas programadas y las demás actividades de operación de los trabajos de Revisoría Fiscal a realizar durante el año actual:
</wrap>

";

                $rfNiasPei0 = "|  Revisoría fiscal (E)  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  PROYECTO CLIENTE NUEVO  |
|  NIAS   |:::   |:::  |

<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, el diagnóstico inicial del cliente, la planeación, el análisis de riesgos, el control de riesgos, control de tareas programadas y las demás actividades de operación de los trabajos de Revisoría Fiscal a realizar durante el año actual:
</wrap>

";

                $rfNiasPeiR0 = "|  Revisoría fiscal (F)  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  PROYECTO CLIENTE RECURRENTE  |
|  NIAS  |:::   |:::  |

<wrap lo>
El objetivo es dar continuidad a la gestión de cumplimiento del sistema de gestión de calidad, el análisis de recurrencia del cliente, la planeación, el análisis de riesgos, el control de riesgos, control de tareas programadas y las demás actividades de operación de los trabajos de Revisoría Fiscal a realizar durante el año actual:
</wrap>

";

                $rfNagasPei0 = "|  Revisoría fiscal (G)  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  PROYECTO CLIENTE NUEVO  |
|  NAGAS  |:::   |:::  |


<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, el diagnóstico inicial del cliente, la planeación, el análisis de riesgos, el control de riesgos, control de tareas programadas y las demás actividades de operación de los trabajos de Revisoría Fiscal a realizar durante el año actual:
</wrap>


";

                $rfNagasPeiR0 = "|  Revisoría fiscal (H)  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  PROYECTO CLIENTE RECURRENTE  |
|  NAGAS  |:::   |:::  |


<wrap lo>
El objetivo es dar continuidad a la gestión de cumplimiento del sistema de gestión de calidad, el análisis de recurrencia del cliente, la planeación, el análisis de riesgos, el control de riesgos, control de tareas programadas y las demás actividades de operación de los trabajos de Revisoría Fiscal a realizar durante el año actual:
</wrap>


";

                // ============================================================
                // MÓDULOS INDIVIDUALES - NIAS JURÍDICA (A)
                // ============================================================
                $rfNiasJur1 = "[[{}proyectos:control:" . $codigo . ":1_diagnostico:registro|1. INVESTIGACIÓN Y DIAGNÓSTICO DEL CLIENTE]]

";
                $rfNiasJur2 = "[[{}proyectos:control:" . $codigo . ":2_cliente:registro|2. SELECCIÓN Y ACEPTACIÓN DEL CLIENTE]]

";
                $rfNiasJur3 = "[[{}proyectos:control:" . $codigo . ":3_personal:registro|3. INDEPENDENCIA - SELECCIONAR EL PERSONAL]]

";
                $rfNiasJur4 = "[[{}proyectos:control:" . $codigo . ":4_riesgos:registro|4. ANALISIS DE RIESGOS - COMPRENSIÓN DE LA ENTIDAD]]

";
                $rfNiasJur5 = "[[{}proyectos:control:" . $codigo . ":5_materialidad:registro|5. CÁLCULO DE MATERIALIDAD]]

";
                $rfNiasJur6 = "[[{}proyectos:control:" . $codigo . ":6_planeacion:registro|6. PLANEACIÓN]]

";
                $rfNiasJur7 = "[[{}proyectos:control:" . $codigo . ":7_control_interno:registro|7. CONTROL INTERNO]]

";
                $rfNiasJur9 = "[[{}proyectos:control:" . $codigo . ":9_informes:registro|9. INFORMES]]

";
                $rfNiasJur10 = "[[{}proyectos:control:" . $codigo . ":10_manifestaciones:registro|10. CARTA DE MANIFESTACIONES - NIA 580]]

";
                $rfNiasJur11 = "[[{}proyectos:control:" . $codigo . ":11_dictamen:registro|11. DICTAMEN FINAL]]

";
                $rfNiasJur12 = "[[{}proyectos:control:" . $codigo . ":12_consolidado:registro|12. INFORME DE CONTROL DE EJECUCIÓN DEL PROYECTO]]

";
                $rfNiasJur13 = "[[{}proyectos:control:" . $codigo . ":13_otros:registro|13. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // MÓDULOS INDIVIDUALES - NIAS JURÍDICA RECURRENTE (B)
                // ============================================================
                $rfNiasJurR1 = "[[{}proyectos:control:" . $codigo . ":recurrente:1_diagnostico:registro|1. INVESTIGACIÓN Y DIAGNÓSTICO DEL CLIENTE ]]

";
                $rfNiasJurR2 = "[[{}proyectos:control:" . $codigo . ":recurrente:2_cliente:registro|2. SELECCIÓN Y ACEPTACIÓN DEL CLIENTE]]

";
                $rfNiasJurR3 = "[[{}proyectos:control:" . $codigo . ":recurrente:3_personal:registro|3. INDEPENDENCIA - SELECCIONAR EL PERSONAL]]

";
                $rfNiasJurR4 = "[[{}proyectos:control:" . $codigo . ":recurrente:4_riesgos:registro|4. ANÁLISIS DE RIESGOS - COMPRENSIÓN DE LA ENTIDAD]]

";
                $rfNiasJurR5 = "[[{}proyectos:control:" . $codigo . ":recurrente:5_materialidad:registro|5. CÁLCULO DE MATERIALIDAD]]

";
                $rfNiasJurR6 = "[[{}proyectos:control:" . $codigo . ":recurrente:6_planeacion:registro|6. PLANEACIÓN]]

";
                $rfNiasJurR7 = "[[{}proyectos:control:" . $codigo . ":recurrente:7_control_interno:registro|7. CONTROL INTERNO]]

";
                $rfNiasJurR9 = "[[{}proyectos:control:" . $codigo . ":recurrente:9_informes:registro|9. INFORMES]]

";
                $rfNiasJurR10 = "[[{}proyectos:control:" . $codigo . ":recurrente:10_manifestaciones:registro|10. CARTA DE MANIFESTACIONES - NIA 580]]

";
                $rfNiasJurR11 = "[[{}proyectos:control:" . $codigo . ":recurrente:11_dictamen:registro|11. DICTAMEN FINAL]]

";
                $rfNiasJurR12 = "[[{}proyectos:control:" . $codigo . ":recurrente:12_consolidado:registro|12. INFORME DE CONTROL DE EJECUCIÓN DEL PROYECTO]]

";
                $rfNiasJurR13 = "[[{}proyectos:control:" . $codigo . ":recurrente:13_otros:registro|13. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // MÓDULOS ESPECÍFICOS - NAGAS JURÍDICA (C) - solo los que difieren
                // ============================================================
                $rfNagasJur5 = "[[{}proyectos:control:" . $codigo . ":5_materialidad:registro|5. CÁLCULO DE MATERIALIDAD]]

";
                $rfNagasJur9 = "[[{}proyectos:control:" . $codigo . ":9_informes:registro|9. INFORMES]]

";
                $rfNagasJur10 = "[[{}proyectos:control:" . $codigo . ":10_dictamen:registro|10. DICTAMEN FINAL]]

";
                $rfNagasJur11 = "[[{}proyectos:control:" . $codigo . ":11_consolidado:registro|11. INFORME DE CONTROL DE EJECUCIÓN DEL PROYECTO]]

";
                $rfNagasJur12 = "[[{}proyectos:control:" . $codigo . ":12_otros:registro|12. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // MÓDULOS ESPECÍFICOS - NAGAS JURÍDICA RECURRENTE (D)
                // ============================================================
                $rfNagasJurR5 = "[[{}proyectos:control:" . $codigo . ":recurrente:5_materialidad:registro|5. CÁLCULO DE MATERIALIDAD]]

";
                $rfNagasJurR9 = "[[{}proyectos:control:" . $codigo . ":recurrente:9_informes:registro|9. INFORMES]]

";
                $rfNagasJurR10 = "[[{}proyectos:control:" . $codigo . ":recurrente:10_dictamen:registro|10. DICTAMEN FINAL]]

";
                $rfNagasJurR11 = "[[{}proyectos:control:" . $codigo . ":recurrente:11_consolidado:registro|11. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $rfNagasJurR12 = "[[{}proyectos:control:" . $codigo . ":recurrente:12_otros:registro|12. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // MÓDULOS INDIVIDUALES - NIAS PEI (E)
                // ============================================================
                $rfNiasPei1 = "[[{}proyectos:control:" . $codigo . ":pei:1_diagnostico:registro|1. INVESTIGACIÓN Y DIAGNÓSTICO DEL CLIENTE]]

";
                $rfNiasPei2 = "[[{}proyectos:control:" . $codigo . ":pei:2_cliente:registro|2. SELECCIÓN Y ACEPTACIÓN DEL CLIENTE]]

";
                $rfNiasPei3 = "[[{}proyectos:control:" . $codigo . ":pei:3_personal:registro|3. INDEPENDENCIA - SELECCIONAR EL PERSONAL]]

";
                $rfNiasPei4 = "[[{}proyectos:control:" . $codigo . ":pei:4_riesgos:registro|4. ANÁLISIS DE RIESGOS - COMPRENSIÓN DE LA ENTIDAD]]

";
                $rfNiasPei5 = "[[{}proyectos:control:" . $codigo . ":pei:5_materialidad:registro|5. CÁLCULO DE MATERIALIDAD]]

";
                $rfNiasPei6 = "[[{}proyectos:control:" . $codigo . ":pei:6_planeacion:registro|6. PLANEACIÓN]]

";
                $rfNiasPei7 = "[[{}proyectos:control:" . $codigo . ":pei:7_control_interno:registro|7. CONTROL INTERNO]]

";
                $rfNiasPei9 = "[[{}proyectos:control:" . $codigo . ":pei:9_informes:registro|9. INFORMES]]

";
                $rfNiasPei10 = "[[{}proyectos:control:" . $codigo . ":pei:10_manifestaciones:registro|10. CARTA DE MANIFESTACIONES - NIA 580]]

";
                $rfNiasPei11 = "[[{}proyectos:control:" . $codigo . ":pei:11_dictamen:registro|11. DICTAMEN FINAL]]

";
                $rfNiasPei12 = "[[{}proyectos:control:" . $codigo . ":pei:12_consolidado:registro|12. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $rfNiasPei13 = "[[{}proyectos:control:" . $codigo . ":pei:13_otros:registro|13. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // MÓDULOS INDIVIDUALES - NIAS PEI RECURRENTE (F)
                // ============================================================
                $rfNiasPeiR1 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:1_diagnostico:registro|1. INVESTIGACIÓN Y DIAGNÓSTICO DEL CLIENTE]]

";
                $rfNiasPeiR2 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:2_cliente:registro|2. SELECCIÓN Y ACEPTACIÓN DEL CLIENTE]]

";
                $rfNiasPeiR3 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:3_personal:registro|3. INDEPENDENCIA - SELECCIONAR EL PERSONAL]]

";
                $rfNiasPeiR4 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:4_riesgos:registro|4. ANÁLISIS DE RIESGOS - COMPRENSIÓN DE LA ENTIDAD]]

";
                $rfNiasPeiR5 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:5_materialidad:registro|5. CÁLCULO DE MATERIALIDAD]]

";
                $rfNiasPeiR6 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:6_planeacion:registro|6. PLANEACIÓN]]

";
                $rfNiasPeiR7 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:7_control_interno:registro|7. CONTROL INTERNO]]

";
                $rfNiasPeiR9 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:9_informes:registro|9. INFORMES]]

";
                $rfNiasPeiR10 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:10_manifestaciones:registro|10. CARTA DE MANIFESTACIONES - NIA 580]]

";
                $rfNiasPeiR11 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:11_dictamen:registro|11. DICTAMEN FINAL]]

";
                $rfNiasPeiR12 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:12_consolidado:registro|12. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $rfNiasPeiR13 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:13_otros:registro|13. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // MÓDULOS ESPECÍFICOS - NAGAS PEI (G) - solo los que difieren
                // ============================================================
                $rfNagasPei5 = "[[{}proyectos:control:" . $codigo . ":pei:5_materialidad:registro|5. CÁLCULO DE MATERIALIDAD]]

";
                $rfNagasPei9 = "[[{}proyectos:control:" . $codigo . ":pei:9_informes:registro|9. INFORMES]]

";
                $rfNagasPei10 = "[[{}proyectos:control:" . $codigo . ":pei:10_dictamen:registro|10. DICTAMEN FINAL]]

";
                $rfNagasPei11 = "[[{}proyectos:control:" . $codigo . ":pei:11_consolidado:registro|11. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $rfNagasPei12 = "[[{}proyectos:control:" . $codigo . ":pei:12_otros:registro|12. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // MÓDULOS ESPECÍFICOS - NAGAS PEI RECURRENTE (H)
                // ============================================================
                $rfNagasPeiR5 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:5_materialidad:registro|5. CÁLCULO DE MATERIALIDAD]]

";
                $rfNagasPeiR9 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:9_informes:registro|9. INFORMES]]

";
                $rfNagasPeiR10 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:10_dictamen:registro|10. DICTAMEN FINAL]]

";
                $rfNagasPeiR11 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:11_consolidado:registro|11. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $rfNagasPeiR12 = "[[{}proyectos:control:" . $codigo . ":pei:recurrente:12_otros:registro|12. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // OUTSOURCING CONTABLE - JURÍDICA (I)
                // ============================================================
                $ou0 = "|  Outsourcing contable  ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  CONTROL OPERATIVO  |
|  CONTABILIDAD           |:::   |:::  |



<wrap lo>

El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, la planeación, el control de tareas y las demás actividades de operación de los trabajos de outsourcing contable a realizar:

</wrap>



";
                $ouJur1 = "[[{}proyectos:control:" . $codigo . ":outsourcing:1_diagnostico:registro|1. INVESTIGACIÓN Y DIAGNÓSTICO DEL CLIENTE]]

";
                $ouJur2 = "[[{}proyectos:control:" . $codigo . ":outsourcing:2_cliente:registro|2. SELECCIÓN Y ACEPTACIÓN DEL CLIENTE]]

";
                $ouJur3 = "[[{}proyectos:control:" . $codigo . ":outsourcing:3_personal:registro|3. INDEPENDENCIA - SELECCIONAR EL PERSONAL]]

";
                $ouJur4 = "[[{}proyectos:control:" . $codigo . ":outsourcing:4_planeacion:registro|4. PLANEACIÓN Y EJECUCIÓN]]

";
                $ouInformes6 = "[[{}proyectos:control:" . $codigo . ":outsourcing:6_informes:registro|5. INFORMES / RESULTADOS DE OPERACIÓN]]

";
                $ouControl7 = "[[{}proyectos:control:" . $codigo . ":outsourcing:7_consolidado:registro|6. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $ouOtros8 = "[[{}proyectos:control:" . $codigo . ":outsourcing:8_otros:registro|7. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // OUTSOURCING PEI (M)
                // ============================================================
                $ouPei0 = "|  Outsourcing           ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  CONTROL OPERATIVO  |
|  CONTABILIDAD           |:::   |:::  |



<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, la planeación, el control de tareas y las demás actividades de operación de los trabajos de outsourcing contable a realizar:
</wrap>


";
                $ouPei1 = "[[{}proyectos:control:" . $codigo . ":outsourcing:pei:1_diagnostico:registro|1. INVESTIGACIÓN Y DIAGNÓSTICO DEL CLIENTE]]

";
                $ouPei2 = "[[{}proyectos:control:" . $codigo . ":outsourcing:pei:2_cliente:registro|2. SELECCIÓN Y ACEPTACIÓN DEL CLIENTE]]

";
                $ouPei3 = "[[{}proyectos:control:" . $codigo . ":outsourcing:pei:3_personal:registro|3. INDEPENDENCIA - SELECCIONAR EL PERSONAL]]

";
                $ouPei4 = "[[{}proyectos:control:" . $codigo . ":outsourcing:pei:4_planeacion:registro|4. PLANEACIÓN Y EJECUCIÓN]]

";
                $ouPeiInformes6 = "[[{}proyectos:control:" . $codigo . ":outsourcing:pei:6_informes:registro|5. INFORMES / RESULTADOS DE OPERACIÓN]]

";
                $ouControlPei7 = "[[{}proyectos:control:" . $codigo . ":outsourcing:pei:7_consolidado:registro|6. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $ouOtrosPei8 = "[[{}proyectos:control:" . $codigo . ":outsourcing:pei:8_otros:registro|7. OTROS FORMATOS / APLICADOS]]


";

                // ============================================================
                // ASESORÍA CONTABLE - JURÍDICA (J)
                // ============================================================
                $ac0 = "|  Asesoría contable    ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  CONTROL OPERATIVO  |
|  CONTABLE             |:::   |:::  |



<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, la planeación, el control de tareas y las demás actividades de operación de los trabajos de asesoría contable a realizar:
</wrap>



";
                $acJur1 = "[[{}proyectos:control:" . $codigo . ":asesoria:1_diagnostico:registro|1. INVESTIGACIÓN Y DIAGNÓSTICO DEL CLIENTE]]

";
                $acJur2 = "[[{}proyectos:control:" . $codigo . ":asesoria:2_cliente:registro|2. SELECCIÓN Y ACEPTACIÓN DEL CLIENTE]]

";
                $acJur3 = "[[{}proyectos:control:" . $codigo . ":asesoria:3_personal:registro|3. INDEPENDENCIA - SELECCIONAR EL PERSONAL]]

";
                $acJur4 = "[[{}proyectos:control:" . $codigo . ":asesoria:4_planeacion:registro|4. PLANEACIÓN Y EJECUCIÓN]]

";
                $acInformes6 = "[[{}proyectos:control:" . $codigo . ":asesoria:6_informes:registro|6. INFORMES / RESULTADOS DE OPERACIÓN]]

";
                $acControl7 = "[[{}proyectos:control:" . $codigo . ":asesoria:7_consolidado:registro|7. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $acOtros8 = "[[{}proyectos:control:" . $codigo . ":asesoria:8_otros:registro|8. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // ASESORÍA PEI (N)
                // ============================================================
                $acPei0 = "|  Asesoría             ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  CONTROL OPERATIVO  |
|  CONTABLE             |:::   |:::  |



<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, la planeación, el control de tareas y las demás actividades de operación de los trabajos de asesoría contable a realizar:
</wrap>";
                $acPei1 = "[[{}proyectos:control:" . $codigo . ":asesoria:pei:1_diagnostico:registro|1. INVESTIGACIÓN Y DIAGNÓSTICO DEL CLIENTE]]

";
                $acPei2 = "[[{}proyectos:control:" . $codigo . ":asesoria:pei:2_cliente:registro|2. SELECCIÓN Y ACEPTACIÓN DEL CLIENTE]]

";
                $acPei3 = "[[{}proyectos:control:" . $codigo . ":asesoria:pei:3_personal:registro|3. INDEPENDENCIA - SELECCIONAR EL PERSONAL]]

";
                $acPei4 = "[[{}proyectos:control:" . $codigo . ":asesoria:pei:4_planeacion:registro|4. PLANEACIÓN Y EJECUCIÓN]]

";
                $acPeiInformes6 = "[[{}proyectos:control:" . $codigo . ":asesoria:pei:6_informes:registro|6. INFORMES / RESULTADOS DE OPERACIÓN]]

";
                $acControlPei7 = "[[{}proyectos:control:" . $codigo . ":asesoria:pei:7_consolidado:registro|7. CONTROL DE EJECUCIÓN DE REQUISITOS DEL PROYECTO]]

";
                $acOtrosPei8 = "[[{}proyectos:control:" . $codigo . ":asesoria:pei:8_otros:registro|8. OTROS FORMATOS / APLICADOS]]

";

                // ============================================================
                // OTROS PROYECTOS (K)
                // ============================================================
                $op0 = "|  Otros proyectos      ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  CONTROL DE GESTIÓN  |
|  CORPORATIVOS          |:::   |:::  |



<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, la planeación, el control de tareas y las demás actividades de operación de los trabajos  a realizar:
</wrap>



";
                $op1 = "[[{}proyectos:control:" . $codigo . ":otros:1_planeacion:registro|1. PLANEACIÓN]]


";
                $op2 = "[[{}proyectos:control:" . $codigo . ":otros:2_operaciones:registro|2. EVALUACIONES DE CONTROL]]


";
                $op3 = "[[{}proyectos:control:" . $codigo . ":otros:3_informes:registro|3. INFORMES]]


";

                // ============================================================
                // REPORTE DE GESTIÓN (L)
                // ============================================================
                $rt0 = "|  Reporte de gestión   ^  DEFINICIÓN DE ACTIVIDADES DE PLANEACIÓN   |  CONTROL OPERATIVO  |
|  CORPORATIVOS          |:::   |:::  |


<wrap lo>
El objetivo es realizar la gestión de cumplimiento del sistema de gestión de calidad, la planeación, el control de tareas y las demás actividades de operación de los trabajos  a realizar:
</wrap>



";
                $rt1 = "[[{}proyectos:control:" . $codigo . ":reporte:1_planeacion:registro|1. PLANEACIÓN]]

";
                $rt2 = "[[{}proyectos:control:" . $codigo . ":reporte:2_operaciones:registro|2. EVALUACIONES DE CONTROL]]

";
                $rt3 = "[[{}proyectos:control:" . $codigo . ":reporte:3_informes:registro|3. OTROS]]

";

                // ============================================================
                // CONCATENACIÓN FINAL - Idéntica a thread_report.php
                // Cada variable final usa EXACTAMENTE la misma fórmula
                // ============================================================

                $contenidoNuevo = '';

                // TIPO A: $rfJur = $rfNiasJur0 . $rfNiasJur1 ... $rfNiasJur13
                if ($proyecto == "A") {
                    $contenidoNuevo = $rfNiasJur0 . $rfNiasJur1 . $rfNiasJur2 . $rfNiasJur3 . $rfNiasJur4 . $rfNiasJur5 . $rfNiasJur6 . $rfNiasJur7 . $rfNias8 . $programas0 . $programas1 . $rfNiasJur9 . $rfNiasJur10 . $rfNiasJur11 . $rfNiasJur12 . $rfNiasJur13;
                }
                // TIPO B: $rfJurR = $rfNiasJurR0 . $rfNiasJurR1 ... $rfNiasJurR13
                elseif ($proyecto == "B") {
                    $contenidoNuevo = $rfNiasJurR0 . $rfNiasJurR1 . $rfNiasJurR2 . $rfNiasJurR3 . $rfNiasJurR4 . $rfNiasJurR5 . $rfNiasJurR6 . $rfNiasJurR7 . $rfNias8 . $programas0 . $programas1 . $rfNiasJurR9 . $rfNiasJurR10 . $rfNiasJurR11 . $rfNiasJurR12 . $rfNiasJurR13;
                }
                // TIPO C: $rfJurNagas = $rfNagasJur0 . $rfNiasJur1 ... (MEZCLA NIAS/NAGAS)
                elseif ($proyecto == "C") {
                    $contenidoNuevo = $rfNagasJur0 . $rfNiasJur1 . $rfNiasJur2 . $rfNiasJur3 . $rfNiasJur4 . $rfNagasJur5 . $rfNiasJur6 . $rfNiasJur7 . $rfNias8 . $programas0 . $programas1 . $rfNagasJur9 . $rfNagasJur10 . $rfNagasJur11. $rfNagasJur12;
                }
                // TIPO D: $rfJurNagasR = $rfNagasJurR0 . $rfNiasJurR1 ... (MEZCLA NIAS/NAGAS)
                elseif ($proyecto == "D") {
                    $contenidoNuevo = $rfNagasJurR0 . $rfNiasJurR1 . $rfNiasJurR2 . $rfNiasJurR3 . $rfNiasJurR4 . $rfNagasJurR5 . $rfNiasJurR6 . $rfNiasJurR7 . $rfNias8 . $programas0 . $programas1 . $rfNagasJurR9 . $rfNagasJurR10 . $rfNagasJurR11. $rfNagasJurR12;
                }
                // TIPO E: $rfNiasPei = $rfNiasPei0 . $rfNiasPei1 ... $rfNiasPei13
                elseif ($proyecto == "E") {
                    $contenidoNuevo = $rfNiasPei0 . $rfNiasPei1 . $rfNiasPei2 . $rfNiasPei3 . $rfNiasPei4 . $rfNiasPei5 . $rfNiasPei6 . $rfNiasPei7 . $rfNias8 . $programas0 . $programas1 . $rfNiasPei9 . $rfNiasPei10 . $rfNiasPei11 . $rfNiasPei12. $rfNiasPei13;
                }
                // TIPO F: $rfNiasPeiR = $rfNiasPeiR0 . $rfNiasPeiR1 ... $rfNiasPeiR13
                elseif ($proyecto == "F") {
                    $contenidoNuevo = $rfNiasPeiR0 . $rfNiasPeiR1 . $rfNiasPeiR2 . $rfNiasPeiR3 . $rfNiasPeiR4 . $rfNiasPeiR5 . $rfNiasPeiR6 . $rfNiasPeiR7 . $rfNias8 . $programas0 . $programas1 . $rfNiasPeiR9 . $rfNiasPeiR10 . $rfNiasPeiR11 . $rfNiasPeiR12. $rfNiasPeiR13;
                }
                // TIPO G: $rfNagasPei = $rfNagasPei0 . $rfNiasPei1 ... (MEZCLA NIAS/NAGAS)
                elseif ($proyecto == "G") {
                    $contenidoNuevo = $rfNagasPei0 . $rfNiasPei1 . $rfNiasPei2 . $rfNiasPei3 . $rfNiasPei4 . $rfNagasPei5 . $rfNiasPei6 . $rfNiasPei7 . $rfNias8 . $programas0 . $programas1 . $rfNagasPei9 . $rfNagasPei10 . $rfNagasPei11. $rfNagasPei12;
                }
                // TIPO H: $rfNagasPeiR = $rfNagasPeiR0 . $rfNiasPeiR1 ... (MEZCLA NIAS/NAGAS)
                elseif ($proyecto == "H") {
                    $contenidoNuevo = $rfNagasPeiR0 . $rfNiasPeiR1 . $rfNiasPeiR2 . $rfNiasPeiR3 . $rfNiasPeiR4 . $rfNagasPeiR5 . $rfNiasPeiR6 . $rfNiasPeiR7 . $rfNias8 . $programas0 . $programas1 . $rfNagasPeiR9 . $rfNagasPeiR10 . $rfNagasPeiR11. $rfNagasPeiR12;
                }
                // TIPO I: $ouJur = $ou0 . $ouJur1 ... $ouOtros8
                elseif ($proyecto == "I") {
                    $contenidoNuevo = $ou0 . $ouJur1 . $ouJur2 . $ouJur3 . $ouJur4 . $programasOu0 . $programas1 . $ouInformes6 . $ouControl7 . $ouOtros8;
                }
                // TIPO J: $acJur = $ac0 . $acJur1 ... $acOtros8
                elseif ($proyecto == "J") {
                    $contenidoNuevo = $ac0 . $acJur1 . $acJur2 . $acJur3 . $acJur4 . $programasOu0 . $programas1 . $acInformes6 . $acControl7 . $acOtros8;
                }
                // TIPO K: $ot = $op0 . $op1 . $op2 . $op3
                elseif ($proyecto == "K") {
                    $contenidoNuevo = $op0 . $op1 . $op2 . $op3;
                }
                // TIPO L: $rt = $rt0 . $rt1 . $rt2 . $rt3
                elseif ($proyecto == "L") {
                    $contenidoNuevo = $rt0 . $rt1 . $rt2 . $rt3;
                }
                // TIPO M: $ouPei = $ouPei0 . $ouPei1 ... $ouOtrosPei8
                elseif ($proyecto == "M") {
                    $contenidoNuevo = $ouPei0 . $ouPei1 . $ouPei2 . $ouPei3 . $ouPei4 . $programasOu0 . $programas1 . $ouPeiInformes6 . $ouControlPei7 . $ouOtrosPei8;
                }
                // TIPO N: $acPei = $acPei0 . $acPei1 ... $acOtrosPei8
                elseif ($proyecto == "N") {
                    $contenidoNuevo = $acPei0 . $acPei1 . $acPei2 . $acPei3 . $acPei4 . $programasOu0 . $programas1 . $acPeiInformes6 . $acControlPei7 . $acOtrosPei8;
                }

                // Agregar la etiqueta OPERACIÓN con nombre del cliente al final
                if (!empty($auditado)) {
                    $contenidoNuevo .= '<font 7px/inherit;;inherit;;inherit>OPERACIÓN:~ ' . $auditado . '~</font>';
                }

                // Actualizar el contenido y el HTML renderizado en la base de datos
                $ignore = '';
                $contenidoHtml = p_render('xhtml', p_get_instructions($contenidoNuevo), $ignore);
                if (empty($contenidoHtml)) {
                    $contenidoHtml = '<p></p>';
                }
                $sql_update = "UPDATE thread SET content = ?, content_html = ? WHERE id = ?";
                $sqlite->query($sql_update, [$contenidoNuevo, $contenidoHtml, $idProyecto]);

                // Eliminar módulos antiguos de progreso_proyecto
                $sql_delete = "DELETE FROM progreso_proyecto WHERE proyecto_id = ?";
                $sqlite->query($sql_delete, [$idProyecto]);

                // Recrear los módulos con el contenido nuevo
                $dataModulos = extraerSubmodulos($contenidoNuevo, $idProyecto, $titulo);
                $sql_insert = "INSERT OR IGNORE INTO progreso_proyecto (proyecto_id, nombre_submodulo, ruta_submodulo, procesos_ejecucion) VALUES (?, ?, ?, ?)";
                foreach ($dataModulos as $submodulo) {
                    $sqlite->query($sql_insert, [$idProyecto, $submodulo['nombre'], $submodulo['ruta'], $submodulo['procesos_ejecucion']]);
                }

                // ============================================================
                // ETAPA 3.5: RECREAR SUB-ÍTEMS DE <todo> PARA MÓDULOS ESPECIALES
                // Los módulos con procesos_ejecucion=1 (ej: "8. PROGRAMA GENERAL")
                // tienen checkboxes <todo> en su página de registro. Los sub-ítems
                // marcados deben reflejarse en progreso_proyecto.
                // ============================================================
                foreach ($dataModulos as $submodulo) {
                    if ($submodulo['procesos_ejecucion'] == 1) {
                        $rutaWiki = $submodulo['ruta'];
                        $wikiFile = bez_resolve_path('data/pages/') . str_replace(':', '/', $rutaWiki) . '.txt';

                        if (file_exists($wikiFile)) {
                            $contenidoPagina = file_get_contents($wikiFile);

                            // Obtener número del módulo (ej: "8" de "8. PROGRAMA GENERAL...")
                            $moduloNum = '';
                            if (preg_match('/^\d+/', $submodulo['nombre'], $matchMod)) {
                                $moduloNum = $matchMod[0];
                            }

                            // Buscar cada <todo> con su link [[{...}ruta|texto]] asociado
                            $patronTodo = '/<todo([^>]*)>(.*?)<\/todo>\s*\n\s*\[\[\{[^}]*\}([^\]|]+?)(?:\|[^\]]+)?\]\]/s';

                            if (preg_match_all($patronTodo, $contenidoPagina, $matchesTodo, PREG_SET_ORDER)) {
                                foreach ($matchesTodo as $todoMatch) {
                                    $attrs = $todoMatch[1];
                                    $todoText = trim($todoMatch[2]);
                                    $ruta = trim($todoMatch[3]);

                                    // Solo insertar si está marcado (tiene # en atributos)
                                    if (strpos($attrs, '#') !== false) {
                                        $nombreSub = $moduloNum . '. PROGRAMA GENERAL - (' . $todoText . ')';
                                        $sqlite->query($sql_insert, [$idProyecto, $nombreSub, $ruta, 0]);
                                    }
                                }
                            }
                        }
                    }
                }

                // ============================================================
                // ETAPA 4: CREACIÓN/ACTUALIZACIÓN DE PÁGINAS FÍSICAS
                // Delega a crear_paginas_proyecto.php (fuente única de contenido)
                // ============================================================
                require_once(__DIR__ . '/crear_paginas_proyecto.php');

                // Determinar el label de operación usando mapa centralizado
                $restLabels = \getRestLabels();
                $restLabel = $restLabels[$proyecto] ?? 'PROYECTO';

                // Recálculo: coordinador vacío (no se muestra en recálculo)
                $paginasCreadas = \crearPaginasProyecto($codigo, $proyecto, $auditado, $restLabel, "", true);
                $paginasActualizadas = 0;
                // ============================================================
                // ETAPA 5: CALCULAR AVANCE DE TODOS LOS MÓDULOS
                // Para que el recálculo deje los totales actualizados.
                // ============================================================
                $db_do_rc = plugin_load('helper', 'sqlite');
                if ($db_do_rc && $db_do_rc->init('do', bez_plugin_base() . 'do/db/')) {
                    $fechaRC = date('Y-m-d H:i:s');

                    // Leer todos los módulos recién creados
                    $resModRC = $sqlite->query(
                        "SELECT id_progreso, ruta_submodulo, procesos_ejecucion, nombre_submodulo FROM progreso_proyecto WHERE proyecto_id = ?",
                        [$idProyecto]
                    );
                    $modulosRC = $sqlite->res2arr($resModRC);

                    foreach ($modulosRC as $modRC) {
                        if ($modRC['procesos_ejecucion'] == 1) {
                            // Módulo especial: contar <todo> items del wiki
                            $wikiFileRC = bez_resolve_path('data/pages/') . str_replace(':', '/', $modRC['ruta_submodulo']) . '.txt';
                            if (file_exists($wikiFileRC)) {
                                $contRC = file_get_contents($wikiFileRC);
                                $totalRC = preg_match_all('/<todo[^>]*>/', $contRC);
                                $termRC = preg_match_all('/<todo[^>]*#[^>]*>/', $contRC);
                            } else {
                                $totalRC = 0;
                                $termRC = 0;
                            }
                        } else {
                            // Módulo regular: contar <do> tasks
                            $rutaNormRC = cleanID($modRC['ruta_submodulo']);
                            $resTotalRC = $db_do_rc->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaNormRC);
                            $totalRC = intval($db_do_rc->res2single($resTotalRC));
                            $resTermRC = $db_do_rc->query('SELECT count(1) FROM task_status WHERE page = ? AND status IS NOT NULL', $rutaNormRC);
                            $termRC = intval($db_do_rc->res2single($resTermRC));
                        }

                        $porcRC = ($totalRC > 0) ? round(($termRC / $totalRC) * 100, 2) : 0;
                        $sqlite->query(
                            'UPDATE progreso_proyecto SET total_requerimientos = ?, requerimientos_terminados = ?, porcentaje_progreso = ?, fecha_actualizacion = ? WHERE id_progreso = ?',
                            $totalRC, $termRC, $porcRC, $fechaRC, intval($modRC['id_progreso'])
                        );
                    }
                }

                echo json_encode([
                    "status" => "success",
                    "mensaje" => '¡Contenido del proyecto regenerado completamente! Tipo: ' . $proyecto . ', ID: ' . $idProyecto . ', Cliente: ' . $auditado . '. Módulos creados: ' . count($dataModulos) . '. Páginas creadas: ' . $paginasCreadas . '. Páginas actualizadas: ' . $paginasActualizadas . '.'
                ]);

            } catch (\Throwable $th) {
                echo json_encode(["status" => "error", "mensaje" => 'Error al recalcular: ' . $th->getMessage()]);
            }
            exit;

        } else if ( $_POST['accion'] === 'ajustarAvanceProyecto'){

            // Conexión independiente a DB "do" ($hlp->db es private).
            $db_do = plugin_load('helper', 'sqlite');
            if (!$db_do || !$db_do->init('do', bez_plugin_base() . 'do/db/')) {
                echo json_encode(["status" => "error", "mensaje" => "Error de conexión con base de datos do."]);
                exit;
            }

            $fechaActual = date('Y-m-d H:i:s');
            $contadorActualizados = 0;
            $idProyectoAA = intval($_POST['id_proyecto']);

            // Obtenemos todos los submódulos del proyecto (incluir procesos_ejecucion y nombre).
            $sql_sub = "SELECT id_progreso, ruta_submodulo, procesos_ejecucion, nombre_submodulo FROM progreso_proyecto WHERE proyecto_id = ?";
            $res_sub = $sqlite->query($sql_sub, [$idProyectoAA]);
            $submodulos = $sqlite->res2arr($res_sub);

            // ============================================================
            // PASO PREVIO: Para módulos especiales (procesos_ejecucion=1),
            // sincronizar sub-ítems desde <todo> checkboxes del wiki.
            // ============================================================
            foreach ($submodulos as $sub) {
                if ($sub['procesos_ejecucion'] != 1) continue;

                $rutaWiki = $sub['ruta_submodulo'];
                $wikiFile = bez_resolve_path('data/pages/') . str_replace(':', '/', $rutaWiki) . '.txt';

                if (!file_exists($wikiFile)) continue;

                $contenidoPagina = file_get_contents($wikiFile);

                // Obtener número del módulo (ej: "8" de "8. PROGRAMA GENERAL...")
                $moduloNum = '';
                if (preg_match('/^\d+/', $sub['nombre_submodulo'], $matchMod)) {
                    $moduloNum = $matchMod[0];
                }

                // Buscar cada <todo> con su link [[{...}ruta|texto]] asociado
                $patronTodo = '/<todo([^>]*)>(.*?)<\/todo>\s*\n\s*\[\[\{[^}]*\}([^\]|]+?)(?:\|[^\]]+)?\]\]/s';
                $rutasChecked = [];

                if (preg_match_all($patronTodo, $contenidoPagina, $matchesTodo, PREG_SET_ORDER)) {
                    foreach ($matchesTodo as $todoMatch) {
                        $attrs = $todoMatch[1];
                        $todoText = trim($todoMatch[2]);
                        $ruta = trim($todoMatch[3]);

                        if (strpos($attrs, '#') !== false) {
                            $rutasChecked[] = $ruta;
                            // Insertar sub-ítem si no existe
                            $nombreSub = $moduloNum . '. PROGRAMA GENERAL - (' . $todoText . ')';
                            $existeRes = $sqlite->query(
                                'SELECT COUNT(1) FROM progreso_proyecto WHERE proyecto_id = ? AND ruta_submodulo = ?',
                                $idProyectoAA, $ruta
                            );
                            $existe = intval($sqlite->res2single($existeRes));
                            if ($existe == 0) {
                                $sqlite->query(
                                    "INSERT INTO progreso_proyecto (proyecto_id, nombre_submodulo, ruta_submodulo, mostrar_submodulo) VALUES (?, ?, ?, '1')",
                                    [$idProyectoAA, $nombreSub, $ruta]
                                );
                            }
                        }
                    }
                }

                // Eliminar sub-ítems que ya no están checkeados
                $resSubs = $sqlite->query(
                    "SELECT id_progreso, ruta_submodulo FROM progreso_proyecto WHERE proyecto_id = ? AND procesos_ejecucion = 0 AND nombre_submodulo LIKE ?",
                    $idProyectoAA, $moduloNum . '. PROGRAMA GENERAL - (%'
                );
                $subsExistentes = $sqlite->res2arr($resSubs);
                foreach ($subsExistentes as $subEx) {
                    if (!in_array($subEx['ruta_submodulo'], $rutasChecked)) {
                        $sqlite->query('DELETE FROM progreso_proyecto WHERE id_progreso = ?', intval($subEx['id_progreso']));
                    }
                }

                // Contar total de <todo> items y marcados para el módulo padre
                $totalTodos = preg_match_all('/<todo[^>]*>/', $contenidoPagina);
                $checkedTodos = preg_match_all('/<todo[^>]*#[^>]*>/', $contenidoPagina);
                $porcentajeTodo = ($totalTodos > 0) ? round(($checkedTodos / $totalTodos) * 100, 2) : 0;

                $sqlite->query(
                    'UPDATE progreso_proyecto
                     SET total_requerimientos = ?,
                         requerimientos_terminados = ?,
                         porcentaje_progreso = ?,
                         fecha_actualizacion = ?
                     WHERE id_progreso = ?',
                    $totalTodos, $checkedTodos, $porcentajeTodo, $fechaActual,
                    intval($sub['id_progreso'])
                );
                $contadorActualizados++;
            }

            // Re-leer submódulos después de la sincronización (pueden haber nuevos sub-ítems)
            $res_sub = $sqlite->query($sql_sub, [$idProyectoAA]);
            $submodulos = $sqlite->res2arr($res_sub);

            foreach ($submodulos as $sub) {
                // Saltar módulos especiales (ya procesados arriba)
                if ($sub['procesos_ejecucion'] == 1) continue;

                // MITIGACIÓN: normalizamos la ruta para match con tabla tasks.
                $rutaNorm = cleanID($sub['ruta_submodulo']);

                // Contamos el total de tareas (DB do).
                $res_total = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaNorm);
                $total = intval($db_do->res2single($res_total));

                // Contamos las tareas completadas (DB do).
                $res_term = $db_do->query(
                    'SELECT count(1) FROM task_status WHERE page = ? AND status IS NOT NULL',
                    $rutaNorm
                );
                $terminados = intval($db_do->res2single($res_term));

                // Calculamos el porcentaje de avance.
                $porcentaje = ($total > 0) ? round(($terminados / $total) * 100, 2) : 0;

                // Actualizamos progreso_proyecto por id_progreso (PK).
                $sqlite->query(
                    'UPDATE progreso_proyecto
                     SET total_requerimientos = ?,
                         requerimientos_terminados = ?,
                         porcentaje_progreso = ?,
                         fecha_actualizacion = ?
                     WHERE id_progreso = ?',
                    $total, $terminados, $porcentaje, $fechaActual,
                    intval($sub['id_progreso'])
                );
                $contadorActualizados++;
            }

            echo json_encode([
                "status" => "success",
                "mensaje" => "Avance ajustado: {$contadorActualizados} módulos actualizados."
            ]);
            exit;

        } else if ( $_POST['accion'] === 'consultaArchivoProyecto'){

            $id_proyecto = cleanID($_POST['id_proyecto']);
            $urls = $hlp->getFilesProject($id_proyecto);

            if (count($urls) > 0){
                echo json_encode(["status" => "success", "mensaje" => $urls]);
            } else {
                echo json_encode(["status" => "error", "mensaje" => 'No se encontraron archivos para procesar con IA.']);
            }
            exit;
        } else if ( $_POST['accion'] === 'guardarResumenIAarchivo'){

            $comment = $_POST['comentario'];
            $ID = $_POST['id'];
            $TEXT = $comment['raw'];
            $file = metaFN($ID, '.comments');
            $INFO = pageinfo();

            // Asignamos los valores correspondientes.
            $comment['user']['id'] = $INPUT->server->str('REMOTE_USER');
            $comment['user']['name'] = $INFO['userinfo']['name']. ' | SIE IA 💡';
            $comment['user']['mail'] = $INFO['userinfo']['mail'];

            try {
                // Inicializar $parent desde el comentario o null si no existe
                $parent = isset($comment['parent']) ? $comment['parent'] : null;

                if (!file_exists($file)) {
                    $data = [
                        'status' => 1,
                        'number' => 0,
                        'title' => null,
                        'comments' => []
                    ];
                    io_saveFile($file, serialize($data));
                } else {
                    $raw_data = io_readFile($file, false);
                    // Validar que el contenido parece ser datos serializados válidos
                    if (empty($raw_data) || $raw_data[0] !== 'a') {
                        // No es un array serializado válido, crear estructura nueva
                        $data = [
                            'status' => 1,
                            'number' => 0,
                            'title' => null,
                            'comments' => []
                        ];
                    } else {
                        // Usar unserialize con opciones de seguridad (allowed_classes = false)
                        $data = unserialize($raw_data, ['allowed_classes' => false]);
                        if ($data === false || !is_array($data)) {
                            $data = [
                                'status' => 1,
                                'number' => 0,
                                'title' => null,
                                'comments' => []
                            ];
                        }
                    }
                    if (!isset($data['status']) || $data['status'] != 1) {
                        echo json_encode(["status" => "error", "mensaje" => 'Comentarios deshabilitados para esta página.']);
                        exit;
                    }
                }

                if ($comment['date']['created']) {
                    $date = strtotime($comment['date']['created']);
                } else {
                    $date = time();
                }

                if ($date == -1) {
                    $date = time();
                }

                $cid = md5($comment['user']['id'] . $date);

                if (!isset($data['comments'][$parent]) || !is_array($data['comments'][$parent])) {
                    $parent = null;
                }

                // Renderizar DokuWiki syntax a XHTML usando el renderer de DokuWiki
                $render_info = [];
                $xhtml = p_render('xhtml', p_get_instructions($TEXT), $render_info);

                $data['comments'][$cid] = [
                    'user' => $comment['user'],
                    'date' => ['created' => $date],
                    'raw' => $comment['raw'],
                    'xhtml' => $xhtml,
                    'parent' => $parent,
                    'replies' => [],
                    'show' => $comment['show']
                ];

                if ($parent) {
                    $data['comments'][$parent]['replies'][] = $cid;
                }

                $data['number']++;

                $data['comments'][$cid]['cid'] = $cid;

                io_saveFile($file, serialize($data));

                echo json_encode(["status" => "success", "mensaje" => '¡Resumen generado y cargado correctamente!.']);

            } catch (\Throwable $th) {
                echo json_encode(["status" => "error", "mensaje" => 'Error al procesar comentario.']);
            }
            exit;
        }

        // =====================================================
        // MÓDULO DE CONTROL ADMINISTRATIVO
        // =====================================================

        // Obtener grupos de control administrativo
        else if ($_POST['accion'] === 'getGruposAdminControl') {
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);
            $grupos = $helper->getGrupos();
            echo json_encode(["status" => "success", "datos" => $grupos]);
            exit;
        }

        // Obtener items de un grupo
        else if ($_POST['accion'] === 'getItemsGrupoAdminControl') {
            if (empty($_POST['grupo_id'])) {
                echo json_encode(["status" => "error", "mensaje" => "ID de grupo requerido."]);
                exit;
            }
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);
            $items = $helper->getItemsDeGrupo($_POST['grupo_id']);
            echo json_encode(["status" => "success", "datos" => $items]);
            exit;
        }

        // Obtener aplicadores
        else if ($_POST['accion'] === 'getAplicadoresAdminControl') {
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);
            $aplicadores = $helper->getAplicadores();
            echo json_encode(["status" => "success", "datos" => $aplicadores]);
            exit;
        }

        // Obtener progreso de un aplicador
        else if ($_POST['accion'] === 'getProgresoAdminControl') {
            if (empty($_POST['aplicador_id']) || empty($_POST['grupo_id'])) {
                echo json_encode(["status" => "error", "mensaje" => "IDs requeridos."]);
                exit;
            }
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);
            $progreso = $helper->getProgresoAplicador($_POST['aplicador_id'], $_POST['grupo_id']);
            $total = $helper->getProgresoTotalAplicador($_POST['aplicador_id'], $_POST['grupo_id']);
            echo json_encode(["status" => "success", "detalle" => $progreso, "resumen" => $total]);
            exit;
        }

        // Crear nuevo aplicador (trabajador)
        else if ($_POST['accion'] === 'crearAplicadorAdminControl') {
            if (empty($_POST['nombre'])) {
                echo json_encode(["status" => "error", "mensaje" => "Nombre requerido."]);
                exit;
            }
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);

            $tipo = isset($_POST['tipo']) ? $_POST['tipo'] : 'trabajador';
            $usuarioId = isset($_POST['usuario_id']) ? $_POST['usuario_id'] : null;
            $paginaDiscusion = isset($_POST['pagina_discusion']) ? $_POST['pagina_discusion'] : null;

            $id = $helper->crearAplicador($tipo, $_POST['nombre'], $usuarioId, false, $paginaDiscusion);
            echo json_encode(["status" => "success", "mensaje" => "Aplicador creado.", "id" => $id]);
            exit;
        }

        // Actualizar progreso manualmente
        else if ($_POST['accion'] === 'actualizarProgresoAdminControl') {
            if (empty($_POST['aplicador_id']) || empty($_POST['item_id'])) {
                echo json_encode(["status" => "error", "mensaje" => "IDs requeridos."]);
                exit;
            }
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);

            $total = isset($_POST['total']) ? intval($_POST['total']) : 0;
            $terminados = isset($_POST['terminados']) ? intval($_POST['terminados']) : 0;

            $helper->actualizarProgreso($_POST['aplicador_id'], $_POST['item_id'], $total, $terminados);
            echo json_encode(["status" => "success", "mensaje" => "Progreso actualizado."]);
            exit;
        }

        // Recalcular progreso desde plugin DO
        else if ($_POST['accion'] === 'recalcularProgresoAdminControl') {
            if (empty($_POST['aplicador_id']) || empty($_POST['grupo_id'])) {
                echo json_encode(["status" => "error", "mensaje" => "IDs requeridos."]);
                exit;
            }
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';

            // Conexión al plugin DO
            $sqliteDo = null;
            if ($hlp) {
                $sqliteDo = $hlp->_getDB();
            }

            $helper = new \AdminControlHelper($sqlite, $sqliteDo);
            $items = $helper->getActividadesDeGrupo($_POST['grupo_id']);

            foreach ($items as $item) {
                $helper->calcularProgresoDesdeDoPlugin($_POST['aplicador_id'], $item['id'], $item['ruta_wiki']);
            }

            $total = $helper->getProgresoTotalAplicador($_POST['aplicador_id'], $_POST['grupo_id']);
            echo json_encode(["status" => "success", "mensaje" => "Progreso recalculado.", "resumen" => $total]);
            exit;
        }

        // Obtener lista de páginas disponibles para exportar
        else if ($_POST['accion'] === 'getPaginasExportarAdminControl') {
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);

            $prefijo = isset($_POST['prefijo']) ? $_POST['prefijo'] : 'encargos:general';
            $cantidad = isset($_POST['cantidad']) ? intval($_POST['cantidad']) : 20;

            $paginas = $helper->generarListaPaginasExportar($prefijo, $cantidad);
            echo json_encode(["status" => "success", "datos" => $paginas]);
            exit;
        }

        // Contar comentarios de una página
        else if ($_POST['accion'] === 'contarComentariosAdminControl') {
            if (empty($_POST['pagina'])) {
                echo json_encode(["status" => "error", "mensaje" => "Página requerida."]);
                exit;
            }
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);

            $cantidad = $helper->contarComentarios($_POST['pagina']);
            echo json_encode(["status" => "success", "cantidad" => $cantidad]);
            exit;
        }

        // Exportar comentarios a nueva página
        else if ($_POST['accion'] === 'exportarComentariosAdminControl') {
            if (empty($_POST['pagina_origen'])) {
                echo json_encode(["status" => "error", "mensaje" => "Página origen requerida."]);
                exit;
            }
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);

            // Generar nombre de página destino
            $timestamp = date('Ymd_His');
            $nombreOrigen = str_replace(':', '_', $_POST['pagina_origen']);
            $paginaDestino = isset($_POST['pagina_destino'])
                ? $_POST['pagina_destino']
                : 'encargos:registros:' . $nombreOrigen . '_' . $timestamp;

            $aplicadorId = isset($_POST['aplicador_id']) ? intval($_POST['aplicador_id']) : null;
            $usuario = isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : 'sistema';

            $resultado = $helper->exportarComentarios($_POST['pagina_origen'], $paginaDestino, $aplicadorId, $usuario);
            echo json_encode($resultado);
            exit;
        }

        // Obtener historial de exportaciones
        else if ($_POST['accion'] === 'getHistorialExportacionesAdminControl') {
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);

            $limite = isset($_POST['limite']) ? intval($_POST['limite']) : 20;
            $historial = $helper->getHistorialExportaciones($limite);
            echo json_encode(["status" => "success", "datos" => $historial]);
            exit;
        }

        // Vista previa de comentarios (sin exportar)
        else if ($_POST['accion'] === 'previsualizarComentariosAdminControl') {
            if (empty($_POST['pagina'])) {
                echo json_encode(["status" => "error", "mensaje" => "Página requerida."]);
                exit;
            }
            require_once __DIR__ . '/../mdl/AdminControlHelper.php';
            $helper = new \AdminControlHelper($sqlite);

            $texto = $helper->extraerComentariosATexto($_POST['pagina']);
            echo json_encode(["status" => "success", "contenido" => $texto]);
            exit;
        }

        // ===== FINALIZAR SECCIÓN ADMINISTRATIVA (admin_thread_progress) R**G =====
        else if ($_POST['accion'] === 'finalizarAdminSeccion') {
            $adminThreadId = intval($_POST['admin_thread_id']);
            $seccionId = intval($_POST['seccion_id']);

            // 1. Obtener la ruta_wiki de la sección desde admin_thread_progress.
            $sqlRuta = "SELECT ruta_wiki, total_requerimientos FROM admin_thread_progress
                        WHERE admin_thread_id = ? AND seccion_id = ?";
            $resRuta = $sqlite->query($sqlRuta, [$adminThreadId, $seccionId]);
            $seccionData = $sqlite->res2row($resRuta);

            if (!$seccionData) {
                echo json_encode(["status" => "error", "mensaje" => "No se encontró la sección solicitada."]);
                exit;
            }

            $rutaWiki = cleanID($seccionData['ruta_wiki']);

            // 2. Conexión independiente a DB "do".
            $db_do = plugin_load('helper', 'sqlite');
            if (!$db_do || !$db_do->init('do', bez_plugin_base() . 'do/db/')) {
                echo json_encode(["status" => "error", "mensaje" => "Error de conexión con base de datos do."]);
                exit;
            }

            // 3. Consultamos el total de tareas en la sección (BD do).
            $resTotalDo = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaWiki);
            $totalTareas = intval($db_do->res2single($resTotalDo));

            // 4. Si no hay tareas, la sección no ha sido visitada.
            if ($totalTareas === 0) {
                echo json_encode([
                    "status"  => "sin_visitar",
                    "mensaje" => "Esta sección aún no ha sido visitada. Debe ingresar a la sección al menos una vez para que el sistema registre las tareas. Luego podrá finalizarla."
                ]);
                exit;
            }

            // 5. Obtenemos tareas pendientes (sin status).
            $sqlPendientes = 'SELECT A.page, A.md5
                              FROM tasks A
                              LEFT JOIN task_status B ON A.page = B.page AND A.md5 = B.md5
                              WHERE A.page = ? AND B.status IS NULL';
            $resPendientes = $db_do->query($sqlPendientes, $rutaWiki);
            $tareasPendientes = $db_do->res2arr($resPendientes);

            // 6. Marcamos cada tarea pendiente como "Completado".
            $fechaCierre = date('Y-m-d');
            $usuario = $_SERVER['REMOTE_USER'] ?? 'system';
            $checksMarcados = 0;

            foreach ($tareasPendientes as $tarea) {
                $db_do->query(
                    'INSERT OR IGNORE INTO task_status (page, md5, status, closedby, msg) VALUES (?, ?, ?, ?, ?)',
                    $tarea['page'],
                    $tarea['md5'],
                    $fechaCierre,
                    $usuario,
                    'Completado'
                );
                $checksMarcados++;
            }

            // 7. Recalculamos el progreso real desde BD "do".
            $resTotalFinal = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaWiki);
            $totalFinal = intval($db_do->res2single($resTotalFinal));

            $resTermFinal = $db_do->query(
                'SELECT count(1) FROM task_status WHERE page = ? AND msg IS NOT NULL AND msg IN ("Completado", "No Aplica")',
                $rutaWiki
            );
            $terminadosFinal = intval($db_do->res2single($resTermFinal));

            $porcentajeFinal = ($totalFinal > 0) ? round(($terminadosFinal / $totalFinal) * 100, 2) : 0;

            // 8. Actualizamos admin_thread_progress.
            $sqlite->query(
                'UPDATE admin_thread_progress
                 SET total_requerimientos = ?,
                     requerimientos_terminados = ?,
                     porcentaje_progreso = ?,
                     updated_at = datetime("now")
                 WHERE admin_thread_id = ? AND seccion_id = ?',
                $totalFinal, $terminadosFinal, $porcentajeFinal, $adminThreadId, $seccionId
            );

            echo json_encode([
                "status"  => "success",
                "mensaje" => "¡Sección Finalizada! Se marcaron {$checksMarcados} tareas pendientes como completadas. Progreso: {$porcentajeFinal}%"
            ]);
            exit;
        }

        // ===== FINALIZAR TODOS LOS REQUISITOS ADMINISTRATIVOS R**G =====
        else if ($_POST['accion'] === 'finalizarTodosRequisitosAdmin') {
            $adminThreadId = intval($_POST['admin_thread_id']);

            // 1. Conexión independiente a DB "do".
            $db_do = plugin_load('helper', 'sqlite');
            if (!$db_do || !$db_do->init('do', bez_plugin_base() . 'do/db/')) {
                echo json_encode(["status" => "error", "mensaje" => "Error de conexión con base de datos do."]);
                exit;
            }

            $fechaCierre = date('Y-m-d');
            $usuario = $_SERVER['REMOTE_USER'] ?? 'system';

            // 2. Obtenemos TODAS las secciones que aportan progreso.
            $sqlSec = "SELECT seccion_id, seccion_nombre, ruta_wiki
                       FROM admin_thread_progress
                       WHERE admin_thread_id = ? AND aporta_progreso = 1";
            $resSec = $sqlite->query($sqlSec, [$adminThreadId]);
            $secciones = $sqlite->res2arr($resSec);

            if (empty($secciones)) {
                echo json_encode(["status" => "error", "mensaje" => "No se encontraron secciones para este proyecto administrativo."]);
                exit;
            }

            $seccionesFinalizadas = 0;
            $seccionesSaltadas = [];
            $totalChecksMarcados = 0;

            // 3. Iteramos cada sección.
            foreach ($secciones as $sec) {
                $rutaWiki = cleanID($sec['ruta_wiki']);

                // Contamos tareas en BD "do".
                $resTotalDo = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaWiki);
                $totalTareas = intval($db_do->res2single($resTotalDo));

                // Si la sección no ha sido visitada (0 tareas), la saltamos.
                if ($totalTareas === 0) {
                    $seccionesSaltadas[] = $sec['seccion_nombre'];
                    continue;
                }

                // Obtenemos tareas pendientes (sin status).
                $sqlPend = 'SELECT A.page, A.md5
                            FROM tasks A
                            LEFT JOIN task_status B ON A.page = B.page AND A.md5 = B.md5
                            WHERE A.page = ? AND B.status IS NULL';
                $resPend = $db_do->query($sqlPend, $rutaWiki);
                $pendientes = $db_do->res2arr($resPend);

                // Marcamos cada tarea pendiente como "Completado".
                foreach ($pendientes as $tarea) {
                    $db_do->query(
                        'INSERT OR IGNORE INTO task_status (page, md5, status, closedby, msg) VALUES (?, ?, ?, ?, ?)',
                        $tarea['page'],
                        $tarea['md5'],
                        $fechaCierre,
                        $usuario,
                        'Completado'
                    );
                    $totalChecksMarcados++;
                }

                // Recalculamos progreso real.
                $resTotalFinal = $db_do->query('SELECT count(1) FROM tasks WHERE page = ?', $rutaWiki);
                $totalFinal = intval($db_do->res2single($resTotalFinal));

                $resTermFinal = $db_do->query(
                    'SELECT count(1) FROM task_status WHERE page = ? AND msg IS NOT NULL AND msg IN ("Completado", "No Aplica")',
                    $rutaWiki
                );
                $terminadosFinal = intval($db_do->res2single($resTermFinal));

                $porcentajeFinal = ($totalFinal > 0) ? round(($terminadosFinal / $totalFinal) * 100, 2) : 0;

                // Actualizamos admin_thread_progress.
                $sqlite->query(
                    'UPDATE admin_thread_progress
                     SET total_requerimientos = ?,
                         requerimientos_terminados = ?,
                         porcentaje_progreso = ?,
                         updated_at = datetime("now")
                     WHERE admin_thread_id = ? AND seccion_id = ?',
                    $totalFinal, $terminadosFinal, $porcentajeFinal,
                    $adminThreadId, intval($sec['seccion_id'])
                );
                $seccionesFinalizadas++;
            }

            // 4. Construimos respuesta informativa.
            $mensaje = "¡Requisitos Finalizados! {$seccionesFinalizadas} secciones completadas, {$totalChecksMarcados} tareas marcadas.";

            if (!empty($seccionesSaltadas)) {
                $listaSaltadas = implode(', ', $seccionesSaltadas);
                echo json_encode([
                    "status"   => "parcial",
                    "mensaje"  => $mensaje,
                    "saltados" => $listaSaltadas,
                    "cantidad_saltados" => count($seccionesSaltadas)
                ]);
            } else {
                echo json_encode([
                    "status"  => "success",
                    "mensaje" => $mensaje
                ]);
            }
            exit;

        // ── TAREAS, SEGUIMIENTO Y PARTICIPANTES DE ADMIN THREAD ──
        } else if ($_POST['accion'] === 'adminThreadTaskAdd') {
            $sqlite->query("CREATE TABLE IF NOT EXISTS admin_thread_task (id INTEGER PRIMARY KEY AUTOINCREMENT, admin_thread_id INTEGER NOT NULL, content TEXT NOT NULL, content_html TEXT, assignee TEXT, plan_date TEXT, state TEXT DEFAULT 'opened', created_by TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, closed_at DATETIME, closed_by TEXT, seccion_id INTEGER DEFAULT NULL, FOREIGN KEY (admin_thread_id) REFERENCES admin_thread(id) ON DELETE CASCADE)");
            $adminThreadId = intval($_POST['admin_thread_id'] ?? 0);
            $content = trim($_POST['content'] ?? '');
            $contentHtml = _sanitizeRichHtml(trim($_POST['content_html'] ?? ''));
            $assignee = trim($_POST['assignee'] ?? '');
            $planDate = trim($_POST['plan_date'] ?? '');
            $seccionId = isset($_POST['seccion_id']) && $_POST['seccion_id'] !== '' ? intval($_POST['seccion_id']) : null;
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($adminThreadId <= 0 || empty($content)) { echo json_encode(["status" => "error", "mensaje" => "Datos requeridos."]); exit; }
            $resCheck = $sqlite->query("SELECT id FROM admin_thread WHERE id = ? AND deleted_at IS NULL", $adminThreadId);
            if (!$sqlite->res2row($resCheck)) { echo json_encode(["status" => "error", "mensaje" => "Proyecto no encontrado."]); exit; }
            if (!_canOperateAdminThread($sqlite, $adminThreadId, $currentUser, $auth)) { echo json_encode(["status" => "error", "mensaje" => "No tiene permisos en este proyecto."]); exit; }
            $sqlite->query("INSERT INTO admin_thread_task (admin_thread_id, content, content_html, assignee, plan_date, created_by, seccion_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
                $adminThreadId, $content, $contentHtml ?: null, $assignee ?: '', $planDate ?: '', $currentUser, $seccionId);
            echo json_encode(["status" => "success", "mensaje" => "Tarea agregada."]);
            exit;

        } else if ($_POST['accion'] === 'adminThreadTaskToggle') {
            $taskId = intval($_POST['task_id'] ?? 0);
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($taskId <= 0) { echo json_encode(["status" => "error", "mensaje" => "ID invalido."]); exit; }
            $res = $sqlite->query("SELECT t.state, t.admin_thread_id FROM admin_thread_task t JOIN admin_thread p ON t.admin_thread_id = p.id WHERE t.id = ? AND p.deleted_at IS NULL", $taskId);
            $row = $sqlite->res2row($res);
            if (!$row) { echo json_encode(["status" => "error", "mensaje" => "Tarea no encontrada."]); exit; }
            if (!_canOperateAdminThread($sqlite, $row['admin_thread_id'], $currentUser, $auth)) { echo json_encode(["status" => "error", "mensaje" => "No tiene permisos en este proyecto."]); exit; }
            if ($row['state'] === 'opened') {
                $sqlite->query("UPDATE admin_thread_task SET state = 'done', closed_at = datetime('now'), closed_by = ? WHERE id = ?", $currentUser, $taskId);
            } else {
                $sqlite->query("UPDATE admin_thread_task SET state = 'opened', closed_at = NULL, closed_by = NULL WHERE id = ?", $taskId);
            }
            echo json_encode(["status" => "success", "nuevo_estado" => ($row['state'] === 'opened' ? 'done' : 'opened')]);
            exit;

        } else if ($_POST['accion'] === 'adminThreadTaskDelete') {
            $taskId = intval($_POST['task_id'] ?? 0);
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($taskId <= 0) { echo json_encode(["status" => "error", "mensaje" => "ID invalido."]); exit; }
            $res = $sqlite->query("SELECT t.created_by, t.admin_thread_id FROM admin_thread_task t JOIN admin_thread p ON t.admin_thread_id = p.id WHERE t.id = ? AND p.deleted_at IS NULL", $taskId);
            $row = $sqlite->res2row($res);
            if (!$row) { echo json_encode(["status" => "error", "mensaje" => "Tarea no encontrada."]); exit; }
            if (!_canOperateAdminThread($sqlite, $row['admin_thread_id'], $currentUser, $auth)) { echo json_encode(["status" => "error", "mensaje" => "No tiene permisos en este proyecto."]); exit; }
            // Solo GOD o creador puede eliminar
            $_udD = ($auth) ? $auth->getUserData($currentUser) : false;
            $_grpsD = (is_array($_udD) && isset($_udD['grps'])) ? $_udD['grps'] : [];
            $_isGodD = in_array('manager', $_grpsD) || in_array('admin', $_grpsD);
            if (!$_isGodD && $row['created_by'] !== $currentUser) {
                echo json_encode(["status" => "error", "mensaje" => "No tiene permisos para eliminar esta tarea."]);
                exit;
            }
            $sqlite->query("DELETE FROM admin_thread_task WHERE id = ?", $taskId);
            echo json_encode(["status" => "success", "mensaje" => "Tarea eliminada."]);
            exit;

        // ── Notificar participantes sobre una tarea ──
        } else if ($_POST['accion'] === 'adminThreadTaskNotify') {
            $taskId = intval($_POST['task_id'] ?? 0);
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($taskId <= 0) { echo json_encode(["status" => "error", "mensaje" => "Datos requeridos."]); exit; }
            // Obtener datos de la tarea Y su admin_thread_id real (no confiar en POST)
            $resT = $sqlite->query("SELECT t.content, t.assignee, t.plan_date, t.created_by, t.admin_thread_id FROM admin_thread_task t JOIN admin_thread p ON t.admin_thread_id = p.id WHERE t.id = ? AND p.deleted_at IS NULL", $taskId);
            $tarea = $sqlite->res2row($resT);
            if (!$tarea) { echo json_encode(["status" => "error", "mensaje" => "Tarea no encontrada."]); exit; }
            $adminThreadId = intval($tarea['admin_thread_id']);
            if (!_canOperateAdminThread($sqlite, $adminThreadId, $currentUser, $auth)) { echo json_encode(["status" => "error", "mensaje" => "No tiene permisos en este proyecto."]); exit; }
            // Obtener nombre del proyecto
            $resP = $sqlite->query("SELECT trabajador_nombre FROM admin_thread WHERE id = ?", $adminThreadId);
            $proj = $sqlite->res2row($resP);
            $projName = $proj ? $proj['trabajador_nombre'] : "#$adminThreadId";
            // Obtener participantes
            $resParticipants = $sqlite->query("SELECT user_id FROM admin_thread_participant WHERE admin_thread_id = ?", $adminThreadId);
            $participants = $sqlite->res2arr($resParticipants);
            $enviados = 0;
            if (!empty($participants)) {
                // Resolver nombres completos para el email
                $_udSender = ($auth) ? $auth->getUserData($currentUser) : false;
                $senderName = (is_array($_udSender) && !empty($_udSender['name'])) ? $_udSender['name'] : $currentUser;
                $assigneeName = $tarea['assignee'];
                if (!empty($assigneeName)) {
                    $_udAssignee = ($auth) ? $auth->getUserData($assigneeName) : false;
                    $assigneeName = (is_array($_udAssignee) && !empty($_udAssignee['name'])) ? $_udAssignee['name'] : $assigneeName;
                }
                $subject = "Proyecto Administrativo: $projName - Notificacion de tarea";
                $body = "Notificacion enviada por $senderName\n\n";
                $body .= "Tarea: " . $tarea['content'] . "\n";
                if (!empty($tarea['assignee'])) $body .= "Responsable: " . $assigneeName . "\n";
                if (!empty($tarea['plan_date'])) $body .= "Plazo limite: " . $tarea['plan_date'] . "\n";
                foreach ($participants as $p) {
                    if ($p['user_id'] === $currentUser) continue;
                    $userData = $auth ? $auth->getUserData($p['user_id']) : null;
                    if (!$userData || empty($userData['mail'])) continue;
                    $mailer = new \Mailer();
                    $mailer->to($userData['mail']);
                    $mailer->subject($subject);
                    $mailer->setBody($body);
                    if (@$mailer->send()) $enviados++;
                }
            }
            echo json_encode(["status" => "success", "mensaje" => "Notificacion enviada a $enviados participante(s)."]);
            exit;

        } else if ($_POST['accion'] === 'adminThreadCommentAdd') {
            $sqlite->query("CREATE TABLE IF NOT EXISTS admin_thread_comment (id INTEGER PRIMARY KEY AUTOINCREMENT, admin_thread_id INTEGER NOT NULL, content TEXT NOT NULL, content_html TEXT, author TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT NULL, updated_by TEXT DEFAULT NULL, parent_id INTEGER DEFAULT NULL, FOREIGN KEY (admin_thread_id) REFERENCES admin_thread(id) ON DELETE CASCADE)");
            $adminThreadId = intval($_POST['admin_thread_id'] ?? 0);
            $content = trim($_POST['content'] ?? '');
            $contentHtml = _sanitizeRichHtml(trim($_POST['content_html'] ?? ''));
            $parentId = isset($_POST['parent_id']) && $_POST['parent_id'] !== '' ? intval($_POST['parent_id']) : null;
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($adminThreadId <= 0 || (empty($content) && empty($contentHtml))) { echo json_encode(["status" => "error", "mensaje" => "Datos requeridos."]); exit; }
            $resCheck = $sqlite->query("SELECT id FROM admin_thread WHERE id = ? AND deleted_at IS NULL", $adminThreadId);
            if (!$sqlite->res2row($resCheck)) { echo json_encode(["status" => "error", "mensaje" => "Proyecto no encontrado."]); exit; }
            if (!_canOperateAdminThread($sqlite, $adminThreadId, $currentUser, $auth)) { echo json_encode(["status" => "error", "mensaje" => "No tiene permisos en este proyecto."]); exit; }
            // Si no viene content_html del editor rico, generar desde texto plano
            if (empty($contentHtml)) { $contentHtml = nl2br(htmlspecialchars($content)); }
            // Si no viene content plano, extraer del HTML
            if (empty($content) && !empty($contentHtml)) { $content = strip_tags($contentHtml); }
            $sqlite->query("INSERT INTO admin_thread_comment (admin_thread_id, content, content_html, author, parent_id) VALUES (?, ?, ?, ?, ?)",
                $adminThreadId, $content, $contentHtml, $currentUser, $parentId);
            echo json_encode(["status" => "success", "mensaje" => "Comentario agregado."]);
            exit;

        // ── Editar comentario (solo autor o GOD) ──
        } else if ($_POST['accion'] === 'adminThreadCommentEdit') {
            $commentId = intval($_POST['comment_id'] ?? 0);
            $content = trim($_POST['content'] ?? '');
            $contentHtml = _sanitizeRichHtml(trim($_POST['content_html'] ?? ''));
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($commentId <= 0 || (empty($content) && empty($contentHtml))) { echo json_encode(["status" => "error", "mensaje" => "Datos requeridos."]); exit; }
            // Verificar que el comentario existe, pertenece a proyecto activo, y obtener autor
            $resC = $sqlite->query("SELECT c.author, c.admin_thread_id FROM admin_thread_comment c JOIN admin_thread t ON c.admin_thread_id = t.id WHERE c.id = ? AND t.deleted_at IS NULL", $commentId);
            $comment = $sqlite->res2row($resC);
            if (!$comment) { echo json_encode(["status" => "error", "mensaje" => "Comentario no encontrado."]); exit; }
            // Solo el autor o GOD puede editar
            $_udE = ($auth) ? $auth->getUserData($currentUser) : false;
            $_grpsE = (is_array($_udE) && isset($_udE['grps'])) ? $_udE['grps'] : [];
            $_isGodE = in_array('manager', $_grpsE) || in_array('admin', $_grpsE);
            if (!$_isGodE && $comment['author'] !== $currentUser) {
                echo json_encode(["status" => "error", "mensaje" => "Solo el autor puede editar este comentario."]);
                exit;
            }
            if (empty($contentHtml)) { $contentHtml = nl2br(htmlspecialchars($content)); }
            if (empty($content) && !empty($contentHtml)) { $content = strip_tags($contentHtml); }
            $sqlite->query("UPDATE admin_thread_comment SET content = ?, content_html = ?, updated_at = datetime('now'), updated_by = ? WHERE id = ?",
                $content, $contentHtml, $currentUser, $commentId);
            echo json_encode(["status" => "success", "mensaje" => "Comentario editado."]);
            exit;

        } else if ($_POST['accion'] === 'adminThreadCommentDelete') {
            $commentId = intval($_POST['comment_id'] ?? 0);
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($commentId <= 0) { echo json_encode(["status" => "error", "mensaje" => "ID invalido."]); exit; }
            $resCD = $sqlite->query("SELECT c.id, c.author FROM admin_thread_comment c JOIN admin_thread t ON c.admin_thread_id = t.id WHERE c.id = ? AND t.deleted_at IS NULL", $commentId);
            $commentRow = $sqlite->res2row($resCD);
            if (!$commentRow) { echo json_encode(["status" => "error", "mensaje" => "Comentario no encontrado."]); exit; }
            // Solo GOD puede eliminar comentarios
            $_udDel = ($auth) ? $auth->getUserData($currentUser) : false;
            $_grpsDel = (is_array($_udDel) && isset($_udDel['grps'])) ? $_udDel['grps'] : [];
            $_isGodDel = in_array('manager', $_grpsDel) || in_array('admin', $_grpsDel);
            if (!$_isGodDel) { echo json_encode(["status" => "error", "mensaje" => "Solo administradores pueden eliminar comentarios."]); exit; }
            // Eliminar también las respuestas hijas
            $sqlite->query("DELETE FROM admin_thread_comment WHERE parent_id = ?", $commentId);
            $sqlite->query("DELETE FROM admin_thread_comment WHERE id = ?", $commentId);
            echo json_encode(["status" => "success", "mensaje" => "Comentario eliminado."]);
            exit;

        } else if ($_POST['accion'] === 'adminThreadParticipantAdd') {
            $sqlite->query("CREATE TABLE IF NOT EXISTS admin_thread_participant (id INTEGER PRIMARY KEY AUTOINCREMENT, admin_thread_id INTEGER NOT NULL, user_id TEXT NOT NULL, role TEXT DEFAULT 'participant', added_by TEXT NOT NULL, added_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(admin_thread_id, user_id), FOREIGN KEY (admin_thread_id) REFERENCES admin_thread(id) ON DELETE CASCADE)");
            $adminThreadId = intval($_POST['admin_thread_id'] ?? 0);
            $userId = trim($_POST['user_id'] ?? '');
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($adminThreadId <= 0 || empty($userId)) { echo json_encode(["status" => "error", "mensaje" => "Datos requeridos."]); exit; }
            $resCheck = $sqlite->query("SELECT id FROM admin_thread WHERE id = ? AND deleted_at IS NULL", $adminThreadId);
            if (!$sqlite->res2row($resCheck)) { echo json_encode(["status" => "error", "mensaje" => "Proyecto no encontrado."]); exit; }
            // Solo GOD puede agregar participantes
            $_udPA = ($auth) ? $auth->getUserData($currentUser) : false;
            $_grpsPA = (is_array($_udPA) && isset($_udPA['grps'])) ? $_udPA['grps'] : [];
            if (!in_array('manager', $_grpsPA) && !in_array('admin', $_grpsPA)) { echo json_encode(["status" => "error", "mensaje" => "Solo administradores pueden agregar participantes."]); exit; }
            // Verificar que el usuario existe
            if ($auth) { $_checkUser = $auth->getUserData($userId); if (!$_checkUser) { echo json_encode(["status" => "error", "mensaje" => "El usuario no existe."]); exit; } }
            $sqlite->query("INSERT OR IGNORE INTO admin_thread_participant (admin_thread_id, user_id, role, added_by) VALUES (?, ?, 'participant', ?)", $adminThreadId, $userId, $currentUser);
            echo json_encode(["status" => "success", "mensaje" => "Participante agregado."]);
            exit;

        } else if ($_POST['accion'] === 'adminThreadParticipantRemove') {
            $participantId = intval($_POST['participant_id'] ?? 0);
            $currentUser = $_SERVER['REMOTE_USER'] ?? '';
            if ($participantId <= 0) { echo json_encode(["status" => "error", "mensaje" => "ID invalido."]); exit; }
            $res = $sqlite->query("SELECT p.role, p.admin_thread_id FROM admin_thread_participant p JOIN admin_thread t ON p.admin_thread_id = t.id WHERE p.id = ? AND t.deleted_at IS NULL", $participantId);
            $row = $sqlite->res2row($res);
            if (!$row) { echo json_encode(["status" => "error", "mensaje" => "Participante no encontrado."]); exit; }
            if ($row['role'] === 'creator') { echo json_encode(["status" => "error", "mensaje" => "No se puede remover al creador."]); exit; }
            // Solo GOD puede remover participantes
            $_udPR = ($auth) ? $auth->getUserData($currentUser) : false;
            $_grpsPR = (is_array($_udPR) && isset($_udPR['grps'])) ? $_udPR['grps'] : [];
            if (!in_array('manager', $_grpsPR) && !in_array('admin', $_grpsPR)) { echo json_encode(["status" => "error", "mensaje" => "Solo administradores pueden remover participantes."]); exit; }
            $sqlite->query("DELETE FROM admin_thread_participant WHERE id = ? AND role != 'creator'", $participantId);
            echo json_encode(["status" => "success", "mensaje" => "Participante removido."]);
            exit;
        }

    } catch (\Exception $e) {
        echo json_encode(["status" => "error", "error" => $e->getMessage()]);
        exit;
    }
}

/**
 * Funcion encargada de extraer los submodulos del proyecto. R**G
 */
function extraerSubmodulos($texto, $codigo, $tipo) {

    $patron = '/\[\[\{\}(proyectos:[^\]]*:' . preg_quote($codigo, '/') . ':[^\]]*)\|([^\]]+)\]\]/';
    $submodulos = array();

    # Validamos si el proyecto es recurrente.
    $tipoProyectoRevisoria = (stripos($tipo, 'Revisoría fiscal') !== false ? true : false);
    
    preg_match_all($patron, $texto, $dataSubmodulo, PREG_SET_ORDER);

    foreach ($dataSubmodulo as $index => $submodulo) {
        $submodulos[$index]['ruta'] = trim($submodulo[1]);
        $submodulos[$index]['nombre'] = trim($submodulo[2]);
        $submodulos[$index]['procesos_ejecucion'] = ((stripos($submodulo[2], '8.') !== false) && $tipoProyectoRevisoria ? 1 : 0);
    }
    
    return $submodulos;
}

// =====================================================
// PREFERENCIA DE PROYECTO ADMINISTRATIVO
// =====================================================

// Marcar un proyecto como preferido (solo uno a la vez)
if ($_POST['accion'] === 'marcarPreferenciaAdmin') {
    $adminThreadId = isset($_POST['admin_thread_id']) ? intval($_POST['admin_thread_id']) : 0;

    if ($adminThreadId <= 0) {
        echo json_encode(["status" => "error", "mensaje" => "ID de proyecto inválido."]);
        exit;
    }

    try {
        // Borrar preferencia anterior (solo puede haber una)
        $sqlite->query("DELETE FROM admin_thread_preferencia");

        // Insertar nueva preferencia
        $sql = "INSERT INTO admin_thread_preferencia (admin_thread_id, marcado_por) VALUES (?, ?)";
        $sqlite->query($sql, [$adminThreadId, $_SERVER['REMOTE_USER']]);

        // Actualizar generalidades con rutas del proyecto preferido
        require_once(bez_plugin_base() . 'bez/mdl/AdminThreadFactory.php');
        $factoryPref = new \dokuwiki\plugin\bez\mdl\AdminThreadFactory($sqlite);
        $factoryPref->ensureDocumentPages($adminThreadId);
        $factoryPref->actualizarGeneralidades($adminThreadId);

        echo json_encode(["status" => "success", "mensaje" => "Preferencia actualizada.", "admin_thread_id" => $adminThreadId]);
        exit;
    } catch (\Exception $e) {
        echo json_encode(["status" => "error", "mensaje" => $e->getMessage()]);
        exit;
    }
}

// Obtener la preferencia actual
if ($_POST['accion'] === 'obtenerPreferenciaAdmin') {
    try {
        $res = $sqlite->query("SELECT admin_thread_id FROM admin_thread_preferencia LIMIT 1");
        $arr = $sqlite->res2arr($res);
        $prefId = !empty($arr) ? intval($arr[0]['admin_thread_id']) : null;

        echo json_encode(["status" => "success", "admin_thread_id" => $prefId]);
        exit;
    } catch (\Exception $e) {
        echo json_encode(["status" => "error", "mensaje" => $e->getMessage()]);
        exit;
    }
}

// =====================================================
// EDITAR PROYECTO ADMINISTRATIVO (inline desde tabla)
// =====================================================
if ($_POST['accion'] === 'editarAdminThread') {
    $adminThreadId = isset($_POST['admin_thread_id']) ? intval($_POST['admin_thread_id']) : 0;

    if ($adminThreadId <= 0) {
        echo json_encode(["status" => "error", "mensaje" => "ID de proyecto invalido."]);
        exit;
    }

    $nombre = isset($_POST['trabajador_nombre']) ? trim($_POST['trabajador_nombre']) : '';
    $state  = isset($_POST['state']) ? trim($_POST['state']) : '';
    $fecha  = isset($_POST['fecha_inicio']) ? trim($_POST['fecha_inicio']) : '';

    if (empty($nombre)) {
        echo json_encode(["status" => "error", "mensaje" => "El nombre no puede estar vacio."]);
        exit;
    }

    if (!in_array($state, ['opened', 'closed'])) {
        echo json_encode(["status" => "error", "mensaje" => "Estado no valido."]);
        exit;
    }

    try {
        // UPDATE directo con SQL (evita dependencia de clase AdminThreadFactory)
        $sql = "UPDATE admin_thread SET trabajador_nombre = ?, state = ?, fecha_inicio = ?, updated_at = datetime('now') WHERE id = ?";
        $sqlite->query($sql, [$nombre, $state, $fecha, $adminThreadId]);

        // Si se cierra, registrar quien y cuando
        if ($state === 'closed') {
            $sql2 = "UPDATE admin_thread SET closed_by = ?, closed_at = datetime('now') WHERE id = ? AND (closed_by IS NULL OR closed_by = '')";
            $sqlite->query($sql2, [$_SERVER['REMOTE_USER'], $adminThreadId]);
        }

        echo json_encode([
            "status" => "success",
            "mensaje" => "Proyecto actualizado correctamente.",
            "id" => $adminThreadId
        ]);
        exit;
    } catch (\Exception $e) {
        echo json_encode(["status" => "error", "mensaje" => $e->getMessage()]);
        exit;
    }
}

// =====================================================
// ELIMINAR PROYECTO ADMINISTRATIVO
// =====================================================
if ($_POST['accion'] === 'eliminarAdminThread') {
    $adminThreadId = isset($_POST['admin_thread_id']) ? intval($_POST['admin_thread_id']) : 0;

    if ($adminThreadId <= 1) {
        echo json_encode(["status" => "error", "mensaje" => "No se puede eliminar el proyecto base (#1)."]);
        exit;
    }

    try {
        require_once(bez_plugin_base() . 'bez/mdl/AdminThreadFactory.php');
        $factoryDel = new \dokuwiki\plugin\bez\mdl\AdminThreadFactory($sqlite);
        $factoryDel->delete($adminThreadId);

        echo json_encode(["status" => "success", "mensaje" => "Proyecto #$adminThreadId eliminado."]);
    } catch (\Exception $e) {
        echo json_encode(["status" => "error", "mensaje" => $e->getMessage()]);
    }
    exit;
}

// =====================================================
// ESCANEO DE NAMESPACE COMPARTIDOS
// =====================================================
if ($_POST['accion'] === 'scanCompartidos') {
    try {
        require_once(bez_plugin_base() . 'bez/mdl/AdminThreadFactory.php');
        $factoryScan = new \dokuwiki\plugin\bez\mdl\AdminThreadFactory($sqlite);
        $pages = $factoryScan->scanCompartidosNamespace();

        echo json_encode(["status" => "success", "pages" => $pages]);
    } catch (\Exception $e) {
        echo json_encode(["status" => "error", "mensaje" => "Error al escanear compartidos: " . $e->getMessage()]);
    }
    exit;
}

// =====================================================
// TAREAS, SEGUIMIENTO Y PARTICIPANTES DE ADMIN THREAD
// =====================================================

// Asegurar que las tablas de tareas/comentarios/participantes existan
// (necesario si la migración 30 no corrió por fallo en migración 28)
$_adminThreadTablesEnsured = false;
function _ensureAdminThreadTables($sqlite) {
    global $_adminThreadTablesEnsured;
    if ($_adminThreadTablesEnsured) return;
    $_adminThreadTablesEnsured = true;

    $sqlite->query("CREATE TABLE IF NOT EXISTS admin_thread_task (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        admin_thread_id INTEGER NOT NULL,
        content TEXT NOT NULL,
        content_html TEXT,
        assignee TEXT,
        plan_date TEXT,
        state TEXT DEFAULT 'opened',
        created_by TEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        closed_at DATETIME,
        closed_by TEXT,
        seccion_id INTEGER DEFAULT NULL,
        FOREIGN KEY (admin_thread_id) REFERENCES admin_thread(id) ON DELETE CASCADE
    )");
    $sqlite->query("CREATE TABLE IF NOT EXISTS admin_thread_comment (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        admin_thread_id INTEGER NOT NULL,
        content TEXT NOT NULL,
        content_html TEXT,
        author TEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT NULL,
        updated_by TEXT DEFAULT NULL,
        parent_id INTEGER DEFAULT NULL,
        FOREIGN KEY (admin_thread_id) REFERENCES admin_thread(id) ON DELETE CASCADE
    )");
    $sqlite->query("CREATE TABLE IF NOT EXISTS admin_thread_participant (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        admin_thread_id INTEGER NOT NULL,
        user_id TEXT NOT NULL,
        role TEXT DEFAULT 'participant',
        added_by TEXT NOT NULL,
        added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        UNIQUE(admin_thread_id, user_id),
        FOREIGN KEY (admin_thread_id) REFERENCES admin_thread(id) ON DELETE CASCADE
    )");
}

// Helper: notificar participantes por email
function _notifyAdminParticipants($sqlite, $adminThreadId, $excludeUser, $subject, $body) {
    global $auth;
    try {
        $res = $sqlite->query("SELECT user_id FROM admin_thread_participant WHERE admin_thread_id = ?", $adminThreadId);
        $participants = $sqlite->res2arr($res);
        if (empty($participants)) return;

        foreach ($participants as $p) {
            if ($p['user_id'] === $excludeUser) continue;
            $userData = $auth ? $auth->getUserData($p['user_id']) : null;
            if (!$userData || empty($userData['mail'])) continue;

            $mailer = new \Mailer();
            $mailer->to($userData['mail']);
            $mailer->subject($subject);
            $mailer->setBody($body);
            @$mailer->send();
        }
    } catch (\Exception $e) {
        // Fallo de email no bloquea la operación
    }
}

// Respuesta en caso de error
echo json_encode(["status" => "error", "error" => "Acción no válida"]);
exit;
