<?php
// /dashboard.php - NUEVO DASHBOARD INTEGRAL (CON CSS SCROLL/CARDS Y CORRECCIÓN SQL para MySQL)

// --- 1. GESTIÓN DE SESIÓN Y PERMISOS ---
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: index.php');
    exit;
}
// Redirigir pacientes a su propio dashboard
if ($_SESSION['user_rol'] === 'paciente') {
    header('Location: patient_dashboard.php');
    exit;
}

require_once 'db.php';
require_once 'backup_utils.php'; // Para acciones de backup (ahora MySQL)
require_once 'avatar_helpers.php'; // Para la gestión del avatar
require_once 'logger.php'; // ★ IMPORTANTE: Aseguramos que las funciones de registro estén disponibles

$user_rol = $_SESSION['user_rol'];
$user_id = $_SESSION['user_id'];


// ===================================================================
// ★★★ INICIO DE LA MODIFICACIÓN (CRON SIMULADO) ★★★
// ===================================================================

// Solo se ejecuta si es Superadmin, para no sobrecargar
if ($user_rol === 'superadmin') {

    // 1. Incluimos la nueva librería de utilidades de partición
    require_once 'partition_utils.php';

    // 2. Ejecutamos la función de chequeo.
    // Esta función es "inteligente": comprueba la BD
    // y solo se ejecutará si no se ha ejecutado ya este mes.
    check_and_run_partition_maintenance();
}

// ===================================================================
// ★★★ FIN DE LA MODIFICACIÓN ★★★
// ===================================================================

// --- ★ INICIO LÓGICA DASHBOARD CONMUTABLE ★ ---
$current_view = 'personal'; // Default para 'fisio'
if ($user_rol === 'superadmin') {
    // Si se pasa un parámetro 'view' por URL, se actualiza la sesión
    if (isset($_GET['view']) && in_array($_GET['view'], ['personal', 'global'])) {
        $_SESSION['dashboard_view'] = $_GET['view'];
        // Redireccionar para limpiar la URL (evita recargas con el parámetro)
        header('Location: dashboard.php');
        exit;
    }
    // Obtener la vista actual de la sesión, o 'personal' por defecto
    $current_view = $_SESSION['dashboard_view'] ?? 'personal';
}
// --- ★ FIN LÓGICA DASHBOARD CONMUTABLE ★ ---


// --- OBTENER PREFERENCIA DE FILTRO Y VISTA DEL USUARIO ---
// (Se mantiene por si algún modal lo usa)
$filtro_defecto = 0;
$user_view_prefs_json = '{}';
if (in_array($user_rol, ['superadmin', 'fisio'])) {
    $stmt_prefs = $db->prepare("SELECT filtro_personal_defecto, vista_defecto FROM cuentas WHERE id = ?");
    $stmt_prefs->execute([$user_id]);
    if ($user_prefs = $stmt_prefs->fetch(PDO::FETCH_ASSOC)) {
        $filtro_defecto = $user_prefs['filtro_personal_defecto'];
        $user_view_prefs_json = $user_prefs['vista_defecto'] ?? '{}';
    }
}

// ===== INICIO: OBTENER NOMBRE DEL ACTOR PARA LOGS =====
// Obtenemos el nombre del actor (el admin/fisio logueado) para los registros
$actor_username = $_SESSION['user_email'] ?? 'Usuario Desconocido'; // Usamos email como fallback
try {
    $stmt_actor = $db->prepare("SELECT CONCAT(nombre, ' ', apellido) AS full_name FROM cuentas WHERE id = ?");
    $stmt_actor->execute([$user_id]);
    if ($actor_data = $stmt_actor->fetch()) {
        $actor_username = $actor_data['full_name'];
    }
} catch (Exception $e) { /* Ignorar si falla, ya tenemos el email */ }
// ===== FIN: OBTENER NOMBRE DEL ACTOR PARA LOGS =====

// --- INICIO: FUNCIÓN DE CÁLCULO DE ADHERENCIA (No requiere cambio de DB, usa PHP) ---
/**
 * Calcula el percentatge d'adherència per a un tractament donat.
 * @param int $treatment_id L'ID del tractament.
 * @param PDO $db La connexió a la base de dades.
 * @return int El percentatge d'adherència (0-100), o 0 si hi ha un error o no hi ha exercicis.
 */
function calculate_adherence_percentage(int $treatment_id, PDO $db): int {
    try {
        $ex_stmt = $db->prepare("
            SELECT te.id, te.ejercicio_id, te.frecuencia, t.start_date, t.end_date
            FROM tratamiento_ejercicios te
            JOIN tratamientos t ON te.tratamiento_id = t.id
            WHERE te.tratamiento_id = ? ORDER BY te.id
        ");
        $ex_stmt->execute([$treatment_id]);
        $exercises_with_dates = $ex_stmt->fetchAll(PDO::FETCH_ASSOC);

        if (empty($exercises_with_dates)) {
            return 0; // Si no hi ha exercicis, l'adherència és 0.
        }

        $treatmentDetails = [
            'start_date' => $exercises_with_dates[0]['start_date'],
            'end_date' => $exercises_with_dates[0]['end_date']
        ];
        $exercises = $exercises_with_dates;

        $stmt_evo = $db->prepare("
            SELECT DISTINCT ev.fecha_realizacion, te.id as tratamiento_ejercicio_id
            FROM tratamiento_evolucion ev
            JOIN tratamiento_ejercicios te ON ev.tratamiento_ejercicio_id = te.id
            WHERE te.tratamiento_id = ?
        ");
        $stmt_evo->execute([$treatment_id]);
        $all_evolution_records = $stmt_evo->fetchAll(PDO::FETCH_ASSOC);

        $evolution_map = [];
        foreach ($all_evolution_records as $rec) {
            $evolution_map[$rec['fecha_realizacion']][$rec['tratamiento_ejercicio_id']] = true;
        }

        $total_scheduled = 0;
        $total_completed = 0;

        // Corregido: Usar DateTimeImmutable para evitar modificar la fecha original en el bucle
        $loop_start_date = new DateTimeImmutable($treatmentDetails['start_date']);
        $loop_end_date = new DateTimeImmutable($treatmentDetails['end_date']);
        $current_loop_date = $loop_start_date;

        while ($current_loop_date <= $loop_end_date) {
            $dateStr = $current_loop_date->format('Y-m-d');
            $dayOfWeek = (int)$current_loop_date->format('w'); // 0=Diumenge, 1=Dilluns...

            foreach ($exercises as $ex) {
                $is_scheduled = false;
                $freq = $ex['frecuencia'];

                if (!$freq || $freq === 'Diari') { $is_scheduled = true; }
                else if ($freq === '3xSetmana' && in_array($dayOfWeek, [1, 3, 5])) { $is_scheduled = true; }
                else if ($freq === '2xSetmana' && in_array($dayOfWeek, [2, 4])) { $is_scheduled = true; }
                else if ($freq === 'Altern') {
                    // diff() devuelve un DateInterval, usamos ->days
                    $dayDiff = $loop_start_date->diff($current_loop_date)->days;
                    if ($dayDiff >= 0 && $dayDiff % 2 === 0) { $is_scheduled = true; }
                } else {
                    // Asumir 'Diari' si la frecuencia no es reconocida
                    $is_scheduled = true;
                }

                if ($is_scheduled) {
                    $total_scheduled++;
                    if (isset($evolution_map[$dateStr][$ex['id']])) {
                        $total_completed++;
                    }
                }
            }
            $current_loop_date = $current_loop_date->modify('+1 day');
        }

        return ($total_scheduled > 0) ? (int)round(($total_completed / $total_scheduled) * 100) : 0;

    } catch (Exception $e) {
        error_log("Error calculant adherència per a treatment ID $treatment_id: " . $e->getMessage());
        return 0; // Retorna 0 en cas d'error.
    }
}
// --- FIN: FUNCIÓN DE CÁLCULO DE ADHERENCIA ---


// --- 2. GESTOR DE PETICIONES AJAX ---
if (isset($_REQUEST['ajax'])) {
    header('Content-Type: application/json');
    // ** ACCIÓN POR DEFECTO CAMBIADA **
    $action = $_REQUEST['action'] ?? 'get_dashboard_data';

    try {switch ($action) {

            // ***** NUEVA ACCIÓN CENTRALIZADA PARA EL DASHBOARD *****
            case 'get_dashboard_data':

                // --- Lógica original de get_dashboard_data ---
                $dashboard_data = [ /* ... (array sin cambios) ... */
                    'announcements' => [],
                    'kpis' => ['active_patients' => 0, 'ending_soon' => 0, 'recent_alerts' => 0, 'total_en_curs' => 0],
                    'urgent_actions' => ['alerts' => [], 'ending_treatments' => []],
                    'activity_feed' => ['recent_activity' => [], 'unread_messages' => [], 'recent_notifications' => []],
                    'personal_summary' => ['counts' => [], 'treatment_stats' => []]
                ];

                // ======================================================= -->
                // ============ INICIO: BLOQUE DE LÓGICA CONMUTABLE CORREGIDO
                // ======================================================= -->

                // Determinar la vista (el rol ya se ha comprobado al inicio de la página)
                $view_mode = ($user_rol === 'superadmin') ? ($_SESSION['dashboard_view'] ?? 'personal') : 'personal';

                // --- 1. Preparar variables para Estadísticas (KPIs S1-S5, Gráfico, Ending Soon)
                $stats_join_condition = "";
                $stats_params = [];
                if ($view_mode === 'personal') {
                    $stats_join_condition = "LEFT JOIN tratamiento_fisios_asignados tfa ON t.id = tfa.tratamiento_id WHERE (t.creator_fisio_id = :user_id OR tfa.fisio_id = :user_id) AND t.is_protocol = 0";
                    $stats_params = [':user_id' => $user_id];
                } else {
                    $stats_join_condition = "WHERE t.is_protocol = 0";
                    $stats_params = [];
                }

                // --- 2. Preparar variables para Alertas (KPI Alertas, Lista Alertas)
                // (Siempre necesita :user_id para el filtro de "descartados")
                $alert_join_condition = "";
                $alert_params = [':user_id' => $user_id];
                if ($view_mode === 'personal') {
                    $alert_join_condition = "LEFT JOIN tratamiento_fisios_asignados tfa ON t.id = tfa.tratamiento_id WHERE (t.creator_fisio_id = :fisio_id OR tfa.fisio_id = :fisio_id) AND t.is_protocol = 0";
                    $alert_params[':fisio_id'] = $user_id; // Añadir el segundo marcador
                } else {
                    $alert_join_condition = "WHERE t.is_protocol = 0";
                    // $alert_params ya tiene solo :user_id, que es correcto
                }

                // --- 3. Preparar variables para Feed de Actividad
                $activity_join_condition = "";
                $activity_params = [];
                if ($view_mode === 'personal') {
                    $activity_join_condition = "LEFT JOIN tratamiento_fisios_asignados tfa ON t.id = tfa.tratamiento_id WHERE (t.creator_fisio_id = :user_id OR tfa.fisio_id = :user_id) AND t.is_protocol = 0";
                    $activity_params = [':user_id' => $user_id];
                } else {
                    $activity_join_condition = "WHERE t.is_protocol = 0";
                    $activity_params = [];
                }

                // --- 4. Preparar variables para Resumen de Contenido
                $counts_sql = "";
                $counts_params = [];
                if ($view_mode === 'personal') {
                    $counts_sql = "
                        SELECT
                            (SELECT COUNT(id) FROM videos WHERE id_uploader = :user_id) as videos_uploaded,
                            (SELECT COUNT(id) FROM images WHERE id_uploader = :user_id) as images_uploaded,
                            (SELECT COUNT(id) FROM ejercicios WHERE id_creator = :user_id) as exercises_created,
                            (SELECT COUNT(id) FROM tratamientos WHERE creator_fisio_id = :user_id AND is_protocol = 1) as protocols_created,
                            (SELECT COUNT(DISTINCT c.id) FROM cuentas c WHERE c.id_fisio_registrador = :user_id AND c.rol = 'paciente') as patients_registered,
                            (SELECT COUNT(id) FROM tratamientos WHERE creator_fisio_id = :user_id AND is_protocol = 0) as treatments_created
                    ";
                    $counts_params = [':user_id' => $user_id];
                } else {
                    $counts_sql = "
                        SELECT
                            (SELECT COUNT(id) FROM videos) as videos_uploaded,
                            (SELECT COUNT(id) FROM images) as images_uploaded,
                            (SELECT COUNT(id) FROM ejercicios) as exercises_created,
                            (SELECT COUNT(id) FROM tratamientos WHERE is_protocol = 1) as protocols_created,
                            (SELECT COUNT(id) FROM cuentas WHERE rol = 'paciente') as patients_registered,
                            (SELECT COUNT(id) FROM tratamientos WHERE is_protocol = 0) as treatments_created
                    ";
                    $counts_params = []; // Sin parámetros
                }
                // ======================================================= -->
                // ============ FIN: BLOQUE DE LÓGICA CONMUTABLE CORREGIDO
                // ======================================================= -->


                // 1. Obtener Anuncios (SIN CAMBIOS, siempre es personal)
                $current_date = date('Y-m-d');
                $sql_ann = "SELECT a.id, a.title, a.content, a.message_type, a.is_blocking FROM anuncios a WHERE a.is_active = 1 AND (a.start_date IS NULL OR a.start_date <= :current_date) AND (a.end_date IS NULL OR a.end_date >= :current_date) AND NOT EXISTS (SELECT 1 FROM anuncio_vistos av WHERE av.anuncio_id = a.id AND av.user_id = :user_id) AND (a.target_audience = 'all_users' OR (a.target_audience = 'all_fisios' AND :user_role IN ('fisio', 'superadmin')) OR (a.target_audience = 'all_pacientes' AND :user_role = 'paciente') OR (a.target_audience IN ('specific_fisios', 'specific_pacientes') AND EXISTS (SELECT 1 FROM anuncio_recipients ar WHERE ar.anuncio_id = a.id AND ar.user_id = :user_id))) ORDER BY a.is_blocking DESC, a.created_at DESC";
                $stmt_ann = $db->prepare($sql_ann);
                $stmt_ann->execute([':current_date' => $current_date, ':user_id' => $user_id, ':user_role' => $user_rol]);
                $dashboard_data['announcements'] = $stmt_ann->fetchAll(PDO::FETCH_ASSOC);

                // 2. Obtener Datos de Widgets (CON VARIABLES CORREGIDAS)

                // KPIs de Estadísticas (S1-S5) y Gráfico
                $stmt_s1 = $db->prepare("SELECT COUNT(DISTINCT t.paciente_id) FROM tratamientos t $stats_join_condition AND t.status = 'En curs'");
                $stmt_s1->execute($stats_params);
                $dashboard_data['kpis']['active_patients'] = $stmt_s1->fetchColumn();

                $stmt_s2 = $db->prepare("SELECT COUNT(DISTINCT t.id) FROM tratamientos t $stats_join_condition AND t.status = 'En curs'");
                $stmt_s2->execute($stats_params);
                $total_en_curs = $stmt_s2->fetchColumn();
                $dashboard_data['personal_summary']['treatment_stats']['en_curs'] = $total_en_curs;
                $dashboard_data['kpis']['total_en_curs'] = $total_en_curs;

                $stmt_s3 = $db->prepare("SELECT COUNT(DISTINCT t.id) FROM tratamientos t $stats_join_condition AND t.status = 'Programat'");
                $stmt_s3->execute($stats_params);
                $dashboard_data['personal_summary']['treatment_stats']['programat'] = $stmt_s3->fetchColumn();

                $stmt_s4 = $db->prepare("SELECT COUNT(DISTINCT t.id) FROM tratamientos t $stats_join_condition AND t.status = 'Completat'");
                $stmt_s4->execute($stats_params);
                $dashboard_data['personal_summary']['treatment_stats']['completat'] = $stmt_s4->fetchColumn();

                $stmt_s5 = $db->prepare("SELECT COUNT(DISTINCT t.id) FROM tratamientos t $stats_join_condition AND t.status = 'Omés'");
                $stmt_s5->execute($stats_params);
                $dashboard_data['personal_summary']['treatment_stats']['omes'] = $stmt_s5->fetchColumn();

                // KPI 'ENDING SOON' y Lista
                // [MYSQL CORREGIDO] t.end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 3 DAY)
                $stmt_kpi_ending = $db->prepare("SELECT COUNT(DISTINCT t.id) FROM tratamientos t $stats_join_condition AND t.status = 'En curs' AND t.end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 3 DAY)");
                $stmt_kpi_ending->execute($stats_params);
                $dashboard_data['kpis']['ending_soon'] = $stmt_kpi_ending->fetchColumn() ?: 0;

                // [MYSQL CORREGIDO] t.end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 3 DAY)
                $stmt_ending = $db->prepare("SELECT t.id, t.title, t.end_date, p.id as patient_id, p.nombre as patient_nombre, p.apellido as patient_apellido FROM tratamientos t JOIN cuentas p ON t.paciente_id = p.id $stats_join_condition AND t.status = 'En curs' AND t.end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 3 DAY) GROUP BY t.id ORDER BY t.end_date ASC LIMIT 5");
                $stmt_ending->execute($stats_params);
                $dashboard_data['urgent_actions']['ending_treatments'] = $stmt_ending->fetchAll(PDO::FETCH_ASSOC);

                // KPI 'ALERTS' y Lista
                // [MYSQL CORREGIDO] ev.fecha_realizacion >= DATE_SUB(CURDATE(), INTERVAL 5 DAY)
                $stmt_kpi_alerts = $db->prepare("SELECT COUNT(DISTINCT ev.id) FROM tratamiento_evolucion ev JOIN tratamiento_ejercicios te ON ev.tratamiento_ejercicio_id = te.id JOIN tratamientos t ON te.tratamiento_id = t.id $alert_join_condition AND t.status = 'En curs' AND (ev.dolor_percibido >= 4 OR ev.esfuerzo_percibido >= 4) AND ev.fecha_realizacion >= DATE_SUB(CURDATE(), INTERVAL 5 DAY) AND NOT EXISTS (SELECT 1 FROM dashboard_alert_dismissals dad WHERE dad.evolucion_id = ev.id AND dad.user_id = :user_id)");
                $stmt_kpi_alerts->execute($alert_params);
                $dashboard_data['kpis']['recent_alerts'] = $stmt_kpi_alerts->fetchColumn() ?: 0;

                // [MYSQL CORREGIDO] ev.fecha_realizacion >= DATE_SUB(CURDATE(), INTERVAL 5 DAY)
                $stmt_alerts = $db->prepare("SELECT ev.id, ev.dolor_percibido, ev.esfuerzo_percibido, ev.fecha_realizacion, p.id as patient_id, p.nombre as patient_nombre, p.apellido as patient_apellido, e.title as exercise_title, t.id as treatment_id FROM tratamiento_evolucion ev JOIN tratamiento_ejercicios te ON ev.tratamiento_ejercicio_id = te.id JOIN ejercicios e ON te.ejercicio_id = e.id JOIN tratamientos t ON te.tratamiento_id = t.id JOIN cuentas p ON t.paciente_id = p.id $alert_join_condition AND t.status = 'En curs' AND (ev.dolor_percibido >= 4 OR ev.esfuerzo_percibido >= 4) AND ev.fecha_realizacion >= DATE_SUB(CURDATE(), INTERVAL 5 DAY) AND NOT EXISTS (SELECT 1 FROM dashboard_alert_dismissals dad WHERE dad.evolucion_id = ev.id AND dad.user_id = :user_id) GROUP BY ev.id ORDER BY ev.fecha_realizacion DESC, COALESCE(ev.hora_realizacion, '00:00:00') DESC LIMIT 5");
                $stmt_alerts->execute($alert_params);
                $dashboard_data['urgent_actions']['alerts'] = $stmt_alerts->fetchAll(PDO::FETCH_ASSOC);

                // FEED DE ACTIVIDAD
                $stmt_activity = $db->prepare("SELECT ev.id, ev.fecha_realizacion, ev.hora_realizacion, ev.dolor_percibido, ev.esfuerzo_percibido, p.id as patient_id, p.nombre as patient_nombre, p.apellido as patient_apellido, e.title as exercise_title, t.id as treatment_id FROM tratamiento_evolucion ev JOIN tratamiento_ejercicios te ON ev.tratamiento_ejercicio_id = te.id JOIN ejercicios e ON te.ejercicio_id = e.id JOIN tratamientos t ON te.tratamiento_id = t.id JOIN cuentas p ON t.paciente_id = p.id $activity_join_condition AND t.status IN ('En curs', 'Programat') GROUP BY ev.id ORDER BY ev.fecha_realizacion DESC, COALESCE(ev.hora_realizacion, '00:00:00') DESC LIMIT 5");
                $stmt_activity->execute($activity_params);
                $dashboard_data['activity_feed']['recent_activity'] = $stmt_activity->fetchAll(PDO::FETCH_ASSOC);

                // Mensajes y Notificaciones (Estos SIEMPRE son personales, no cambian)
                // ===== INICIO MODIFICACIÓN (Ocultar Chat durante suplantación Y VISTA GLOBAL) =====
                // Determinar si la vista es de solo lectura
                $is_read_only_view = isset($_SESSION['admin_origen_id']) || ($view_mode === 'global');

                if (!$is_read_only_view) {
                    $stmt_msg_count = $db->prepare("SELECT COUNT(DISTINCT from_id) FROM chat_messages WHERE to_id = :user_id AND status != 'read'"); $stmt_msg_count->execute([':user_id' => $user_id]); $dashboard_data['activity_feed']['unread_message_count'] = $stmt_msg_count->fetchColumn() ?: 0;

                    $stmt_notif = $db->prepare("SELECT id, mensaje, url_destino, fecha_creacion, leido_en, tipo_evento FROM notificaciones WHERE fisio_id = :user_id ORDER BY fecha_creacion DESC LIMIT 3");
                    $stmt_notif->execute([':user_id' => $user_id]);
                    $dashboard_data['activity_feed']['recent_notifications'] = $stmt_notif->fetchAll(PDO::FETCH_ASSOC);
                } else {
                    $dashboard_data['activity_feed']['unread_message_count'] = 0;
                    $dashboard_data['activity_feed']['recent_notifications'] = []; // Vaciar notificaciones
                }
                // ===== FIN MODIFICACIÓN =====


                // Resumen de Contenido (AHORA USA $counts_sql y $counts_params)
                $stmt_counts = $db->prepare($counts_sql);
                $stmt_counts->execute($counts_params);
                $dashboard_data['personal_summary']['counts'] = $stmt_counts->fetch(PDO::FETCH_ASSOC);

                // Devolver el modo de vista al JS
                echo json_encode(['status' => 'success', 'data' => $dashboard_data, 'view_mode' => $view_mode]);
                break;
            // ***** FIN NUEVA ACCIÓN CENTRALIZADA *****


            // --- ACCIONES EXISTENTES (Mantenidas por compatibilidad o uso en modales) ---

            case 'get_profile_data':
                                        // --- ★ INICIO MODIFICACIÓN (Bloqueo de Perfil en Suplantación) ★ ---
                                        if (isset($_SESSION['admin_origen_id'])) {
                                           throw new Exception('Accés denegat durant la suplantació.');
                                        }
                                        // --- ★ FIN MODIFICACIÓN ★ ---
                                        $stmt = $db->prepare("SELECT nombre, apellido, email, telefono, filtro_personal_defecto, avatar, records_per_page FROM cuentas WHERE id = ?");
                                        $stmt->execute([$user_id]);
                                        echo json_encode(['status' => 'success', 'data' => $stmt->fetch(PDO::FETCH_ASSOC)]);
                                        break;

                            case 'update_preferences':
                                            // Lógica de seguridad: Solo Superadmin puede acceder a la configuración de preferencias
                                            if ($user_rol !== 'superadmin') {
                                                echo json_encode(['status' => 'error', 'message' => 'Accés denegat.']);
                                                break;
                                            }

                                            // --- LÓGICA EXISTENTE ---
                                            $filtro_raw = $_POST['filtro_personal_defecto'] ?? '0';
                                            $filtro = in_array($filtro_raw, ['true', '1']) ? 1 : 0;

                                            // --- ======================================================= ---
                                            // --- ★★★ INICIO DE LA MODIFICACIÓN ★★★ ---
                                            // --- ======================================================= ---

                                            $records_per_page = (int)($_POST['records_per_page'] ?? 8);
                                            // Validar que esté en un rango seguro (los valores del <select>)
                                            if (!in_array($records_per_page, [4, 8, 12, 16, 20, 50, 100])) {
                                                $records_per_page = 8; // Volver al default si el valor es inválido
                                            }

                                            try {
                                                // Actualizar la BBDD con AMBOS valores
                                                $stmt = $db->prepare("UPDATE cuentas SET filtro_personal_defecto = ?, records_per_page = ? WHERE id = ?");
                                                $stmt->execute([$filtro, $records_per_page, $user_id]);

                                                // ¡Actualizar la sesión inmediatamente!
                                                $_SESSION['records_per_page'] = $records_per_page;

                                                // --- ======================================================= ---
                                                // --- ★★★ FIN DE LA MODIFICACIÓN ★★★ ---
                                                // --- ======================================================= ---

                                                $filtro_texto = $filtro === 1 ? 'Activat' : 'Desactivat';
                                                $records_texto = $records_per_page . ' registres';

                                                // B8: Se actualizan las preferencias de la interfaz por defecto
                                                // ★★★ CÓDIGO LIMPIO: Uso de función logger.php ★★★
                                                registrar_preferencias_actualizadas($db, $user_id, $actor_username, 'Filtre personal: ' . $filtro_texto . ', Registres: ' . $records_texto);
                                                // ★★★ FIN CÓDIGO LIMPIO ★★★

                                                echo json_encode(['status' => 'success', 'message' => 'Preferències actualitzades.']);

                                            } catch (Exception $e) {
                                                echo json_encode(['status' => 'error', 'message' => 'Error al guardar preferències: ' . $e->getMessage()]);
                                            }
                                            break;

            case 'save_view_preference': if (!in_array($user_rol, ['superadmin', 'fisio'])) { throw new Exception('Accés denegat.'); } $page_name = $_POST['page_name'] ?? ''; $view_mode = $_POST['view_mode'] ?? ''; if (empty($page_name) || !in_array($view_mode, ['grid', 'list'])) { throw new Exception('Dades de preferència invàlides.'); } $stmt_get = $db->prepare("SELECT vista_defecto FROM cuentas WHERE id = ?"); $stmt_get->execute([$user_id]); $current_prefs_json = $stmt_get->fetchColumn(); $prefs = json_decode($current_prefs_json, true); if (!is_array($prefs)) { $prefs = []; } $prefs[$page_name] = $view_mode; $new_prefs_json = json_encode($prefs); $stmt_set = $db->prepare("UPDATE cuentas SET vista_defecto = ? WHERE id = ?"); $stmt_set->execute([$new_prefs_json, $user_id]); echo json_encode(['status' => 'success', 'message' => 'Preferència de vista guardada.']); break;

            case 'update_profile':
                // Lógica de seguridad: los pacientes deberían tener su propio endpoint
                if ($user_rol === 'paciente') {
                    throw new Exception('Accés denegat per a pacients a aquest endpoint.');
                }

                // La función auxiliar maneja la validación de datos (nombre, teléfono) y la lógica de avatar
                $response = handleAvatarUpload($db, $user_id, $_POST, $_FILES, $user_rol);

                // ★★★ CÓDIGO LIMPIO: Uso de función logger.php ★★★
                // Solo registramos el log si la función auxiliar ha reportado un éxito (B7)
                if ($response['status'] === 'success') {
                    registrar_perfil_actualizado($db, $user_id, $actor_username, 'L\'usuari ha actualitzat les seves dades de perfil (Nom, Telèfon, o Avatar).');
                }
                // ★★★ FIN CÓDIGO LIMPIO ★★★

                echo json_encode($response);
                break;

                            case 'change_password':
                                            // --- ★ INICIO MODIFICACIÓN (Bloqueo de Perfil en Suplantación) ★ ---
                                            if (isset($_SESSION['admin_origen_id'])) {
                                               throw new Exception('Accés denegat durant la suplantació.');
                                            }
                                            // --- ★ FIN MODIFICACIÓN ★ ---

                                            $current_password = $_POST['current_password'];
                                            $new_password = $_POST['new_password'];
                                            $confirm_new_password = $_POST['confirm_new_password'];
                                            if (empty($current_password) || empty($new_password) || empty($confirm_new_password)) { throw new Exception("Tots els camps de contrasenya són obligatoris."); }
                                            if ($new_password !== $confirm_new_password) { throw new Exception("Les noves contrasenyes no coincideixen."); }
                                            if (!preg_match('/^(?=.*\d)(?=.*[A-Z]).{6,12}$/', $new_password)) { throw new Exception('La nova contrasenya ha de tindre entre 6 i 12 caràcters, amb una majúscula i un número.'); }
                                            $stmt = $db->prepare("SELECT password FROM cuentas WHERE id = ?");
                                            $stmt->execute([$user_id]);
                                            $user = $stmt->fetch();
                                            if (!$user || !password_verify($current_password, $user['password'])) { throw new Exception("La contrasenya actual és incorrecta."); }
                                            $new_hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
                                            $stmt = $db->prepare("UPDATE cuentas SET password = ? WHERE id = ?");
                                            $stmt->execute([$new_hashed_password, $user_id]);

                                            // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (B1) ★★★
                                            registrar_password_cambiada($db, $user_id, $actor_username);
                                            // ★★★ FIN CÓDIGO LIMPIO ★★★

                                            echo json_encode(['status' => 'success', 'message' => 'Contrasenya canviada correctament.']);
                                            break;
                                            // [MYSQL CORREGIDO] Lógica de backup para MySQL

                                            case 'manual_backup':
                                                if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }

                                                // Llamamos directamente a la nueva función de MySQL
                                                $backupDir = __DIR__ . '/_backups';
                                                $result = backupDatabase(null, $backupDir); // $result contiene 'status' y 'filename'

                                                if ($result['status'] === 'success') {

                                                    // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A8) ★★★
                                                    $backupFilename = $result['filename'] ?? 'desconocido.sql'; // Usamos el nombre que devuelve la función
                                                    registrar_backup_creado($db, $user_id, $actor_username, $backupFilename);
                                                    // ★★★ FIN CÓDIGO LIMPIO ★★★

                                                    echo json_encode($result);
                                                } else {
                                                    throw new Exception($result['message']);
                                                }
                                                break;

                // [MYSQL CORREGIDO] listBackups buscará .sql
            case 'list_backups':
                if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                $backupDir = __DIR__ . '/_backups';
                $backups = listBackups($backupDir); // listBackups ahora busca archivos .sql
                echo json_encode(['status' => 'success', 'backups' => $backups]);
                break;

                // [MYSQL CORREGIDO] Lógica de restauración para MySQL
                            case 'restore_backup':
                                            if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                            $backupFilename = basename($_POST['filename'] ?? '');
                                            // [MYSQL] Buscar la extensión .sql y el prefijo db-backup-
                                            if (empty($backupFilename) || strpos($backupFilename, 'db-backup-') !== 0 || !str_ends_with($backupFilename, '.sql')) { throw new Exception('Nombre de archivo de backup MySQL inválido (debe ser db-backup-*.sql).');}

                                            $backupDir = __DIR__ . '/_backups';
                                            $backupFilePath = $backupDir . '/' . $backupFilename;
                                            if (!file_exists($backupFilePath)) { throw new Exception('El archivo de backup seleccionado no existe.'); }

                                            // --- INICIO: LÓGICA DE RESTAURACIÓN MySQL (Usando la conexión PDO existente) ---
                                            try {
                                                // 1. Leer el archivo SQL
                                                $sql_content = file_get_contents($backupFilePath);
                                                if ($sql_content === false) {
                                                    throw new Exception("No se pudo leer el archivo de backup.");
                                                }

                                                // 2. Ejecutar
                                                // $db->beginTransaction(); // <-- ELIMINADO: El .sql maneja su propia transacción
                                                $db->exec("SET FOREIGN_KEY_CHECKS = 0"); // Deshabilitar FK (Como medida de seguridad)

                                                // Ejecutar el script SQL
                                                // El script .sql contiene sus propios START TRANSACTION y COMMIT
                                                $db->exec($sql_content);

                                                // Habilitar Foreign Keys
                                                $db->exec("SET FOREIGN_KEY_CHECKS = 1"); // Habilitar FK (Como medida de seguridad)
                                                // $db->commit(); // <-- ELIMINADO: Conflicto de transacción

                                                // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A5) ★★★
                                                registrar_backup_restaurado($db, $user_id, $actor_username, $backupFilename);
                                                // ★★★ FIN CÓDIGO LIMPIO ★★★

                                                echo json_encode(['status' => 'success', 'message' => 'Base de datos restaurada correctamente desde ' . htmlspecialchars($backupFilename) . '. Es recomendable recargar la página.']);

                                            // --- ★★★ INICIO: BLOQUE CATCH (SIN MODIFICAR, YA ES ROBUSTO) ★★★ ---
                                            } catch (Exception $e) {
                                                // Guardar el mensaje del error original, que es el más importante.
                                                $original_error_message = $e->getMessage();

                                                // Intentar hacer rollback SOLO SI hay una transacción activa.
                                                // (El .sql podría haber fallado a mitad de su propia transacción)
                                                try {
                                                    if ($db->inTransaction()) {
                                                        $db->rollBack();
                                                    }
                                                } catch (Exception $rollBackException) {
                                                    // El rollback falló, registrar esto pero no sobrescribir el error original.
                                                    error_log("Error durante el rollback de la restauración (backup): " . $rollBackException->getMessage());
                                                }

                                                // Intentar restaurar el estado de las FK en CUALQUIER CASO de error.
                                                try {
                                                    $db->exec("SET FOREIGN_KEY_CHECKS = 1");
                                                } catch (Exception $fkException) {
                                                    // Error al intentar restaurar el estado de FK
                                                    error_log("Error al restaurar FOREIGN_KEY_CHECKS=1 (backup): " . $fkException->getMessage());
                                                }

                                                // Registrar el error original y completo
                                                error_log("Error crítico al restaurar backup MySQL. Error original: " . $original_error_message . "\nTrace: " . $e->getTraceAsString());

                                                // Devolver el error ORIGINAL al cliente, que es mucho más útil.
                                                throw new Exception("Error en la restauración: " . $original_error_message);
                                            }
                                            // --- ★★★ FIN: BLOQUE CATCH (SIN MODIFICAR) ★★★ ---
                                            // --- FIN: LÓGICA DE RESTAURACIÓN MySQL ---
                                            break;

                                            // [MYSQL CORREGIDO] Lógica de restauración desde subida para MySQL
                                            case 'restore_from_upload':
                                                                                                                                    if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                                                                                                    if (!isset($_FILES['backupFile']) || $_FILES['backupFile']['error'] !== UPLOAD_ERR_OK) { throw new Exception('Error al subir el archivo.'); }
                                                                                                                                    $uploadedFile = $_FILES['backupFile'];

                                                                                                                                    $fileExtension = strtolower(pathinfo($uploadedFile['name'], PATHINFO_EXTENSION));
                                                                                                                                    // [MYSQL] Solo permitimos .sql
                                                                                                                                    if ($fileExtension !== 'sql') {
                                                                                                                                        throw new Exception('Extensión de archivo inválida. Solo se acepta .sql para restaurar bases de datos MySQL.');
                                                                                                                                    }

                                                                                                                                    // --- ★ INICIO CORRECCIÓN DE RUTA TEMPORAL ★ ---
                                                                                                                                    // Usar un directorio local escribible en lugar de sys_get_temp_dir()
                                                                                                                                    $backupDir = __DIR__ . '/_backups';
                                                                                                                                    if (!is_writable($backupDir)) {
                                                                                                                                        throw new Exception('Error de permisos: El directorio /_backups no es escribible.');
                                                                                                                                    }
                                                                                                                                    // Crear un nombre de archivo temporal único dentro de nuestra carpeta de backups
                                                                                                                                    $tempBackupFile = $backupDir . '/temp_restore_upload_' . uniqid() . '.sql';
                                                                                                                                    // --- ★ FIN CORRECCIÓN DE RUTA TEMPORAL ★ ---

                                                                                                                                    // Mover el archivo subido a nuestra ubicación temporal segura
                                                                                                                                    if (!move_uploaded_file($uploadedFile['tmp_name'], $tempBackupFile)) {
                                                                                                                                        throw new Exception('Error al mover el archivo subido a la carpeta _backups.');
                                                                                                                                    }

                                                                                                                                    // --- INICIO: LÓGICA DE RESTAURACIÓN (Lectura línea por línea) ---
                                                                                                                                    try {
                                                                                                                                        $fileHandle = fopen($tempBackupFile, 'r');
                                                                                                                                        if ($fileHandle === false) {
                                                                                                                                            throw new Exception("No se pudo abrir el archivo de backup temporal para lectura.");
                                                                                                                                        }

                                                                                                                                        $db->exec("SET FOREIGN_KEY_CHECKS = 0");

                                                                                                                                        $current_query = '';
                                                                                                                                        while (($line = fgets($fileHandle)) !== false) {
                                                                                                                                            $line_trimmed = trim($line);

                                                                                                                                            // Ignorar comentarios y líneas vacías
                                                                                                                                            if (empty($line_trimmed) || strpos($line_trimmed, '--') === 0 || strpos($line_trimmed, '/*') === 0 || strpos($line_trimmed, 'LOCK TABLES') === 0 || strpos($line_trimmed, 'UNLOCK TABLES') === 0) {
                                                                                                                                                continue;
                                                                                                                                            }

                                                                                                                                            $current_query .= $line;

                                                                                                                                            // Si la línea (recortada) termina con punto y coma
                                                                                                                                            if (substr($line_trimmed, -1) === ';') {
                                                                                                                                                try {
                                                                                                                                                    // Ejecutar la sentencia acumulada
                                                                                                                                                    $db->exec($current_query);
                                                                                                                                                } catch (PDOException $pe) {
                                                                                                                                                    // Registrar la consulta específica que falló (para depuración)
                                                                                                                                                    error_log("Error al ejecutar sentencia SQL durante restauración (upload): " . $pe->getMessage() . "\nQuery: " . substr($current_query, 0, 250) . "...");
                                                                                                                                                    throw $pe; // Volver a lanzar para que el catch exterior lo capture
                                                                                                                                                }
                                                                                                                                                // Resetear para la siguiente sentencia
                                                                                                                                                $current_query = '';
                                                                                                                                            }
                                                                                                                                        }
                                                                                                                                        fclose($fileHandle); // Cerrar el handle del archivo

                                                                                                                                        $db->exec("SET FOREIGN_KEY_CHECKS = 1");

                                                                                                                                        // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A5) ★★★
                                                                                                                                        registrar_backup_restaurado($db, $user_id, $actor_username, 'Subida local: ' . $uploadedFile['name']);
                                                                                                                                        // ★★★ FIN CÓDIGO LIMPIO ★★★

                                                                                                                                        echo json_encode(['status' => 'success', 'message' => 'Base de datos restaurada desde archivo local. Recarga recomendada.']);

                                                                                                                                    // --- INICIO: BLOQUE CATCH (SIN MODIFICAR) ---
                                                                                                                                    } catch (Exception $e) {
                                                                                                                                        $original_error_message = $e->getMessage();
                                                                                                                                        try {
                                                                                                                                            if ($db->inTransaction()) {
                                                                                                                                                $db->rollBack();
                                                                                                                                            }
                                                                                                                                        } catch (Exception $rollBackException) {
                                                                                                                                            error_log("Error durante el rollback de la restauración (upload): " . $rollBackException->getMessage());
                                                                                                                                        }
                                                                                                                                        try {
                                                                                                                                            $db->exec("SET FOREIGN_KEY_CHECKS = 1");
                                                                                                                                        } catch (Exception $fkException) {
                                                                                                                                            error_log("Error al restaurar FOREIGN_KEY_CHECKS=1 (upload): " . $fkException->getMessage());
                                                                                                                                        }
                                                                                                                                        error_log("Error crítico al restaurar backup MySQL (upload). Error original: " . $original_error_message . "\nTrace: " . $e->getTraceAsString());
                                                                                                                                        throw new Exception("Error en la restauración (upload): " . $original_error_message);

                                                                                                                                    } finally {
                                                                                                                                        // ★ CORRECCIÓN ★: Limpiar nuestro archivo temporal específico
                                                                                                                                        @unlink($tempBackupFile);
                                                                                                                                    }
                                                                                                                                    // --- FIN: BLOQUE CATCH ---
                                                                                                                                    // --- FIN: LÓGICA DE RESTAURACIÓN ---
                                                                                                                                    break;

            // [MYSQL CORREGIDO] Lógica para borrar backup .sql
            case 'delete_backup':
                if ($user_rol !== 'superadmin') {
                    throw new Exception('Accés denegat.');
                }

                // 1. Sanitizar y validar el nombre del archivo (ahora .sql)
                $backupFilename = basename($_POST['filename'] ?? '');
                if (empty($backupFilename) ||
                    strpos($backupFilename, 'db-backup-') !== 0 || // Corregido: busca el prefijo de MySQL
                    !str_ends_with($backupFilename, '.sql')) { // Corregido: busca la extensión .sql
                    throw new Exception('Nombre de archivo de backup inválido.');
                }

                // 2. Definir la ruta del archivo
                $backupDir = __DIR__ . '/_backups';
                $backupFilePath = $backupDir . '/' . $backupFilename;

                // 3. Comprobar que existe
                if (!file_exists($backupFilePath)) {
                    throw new Exception('El archivo de backup seleccionado no existe.');
                }

                // 4. Comprobar permisos de escritura (necesarios para borrar)
                if (!is_writable($backupFilePath)) {
                    throw new Exception('Error de permisos: El archivo no se puede eliminar. Verifique los permisos del servidor para el archivo.');
                }
                if (!is_writable($backupDir)) {
                     throw new Exception('Error de permisos: El directorio de backups no tiene permisos de escritura.');
                }

                // 5. Intentar eliminar
                if (@unlink($backupFilePath)) {

                    // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A6) ★★★
                    registrar_backup_eliminado($db, $user_id, $actor_username, $backupFilename);
                    // ★★★ FIN CÓDIGO LIMPIO ★★★

                    echo json_encode([
                        'status' => 'success',
                        'message' => 'Copia de seguridad eliminada: ' . htmlspecialchars($backupFilename)
                    ]);
                } else {
                    $last_error = error_get_last();
                    $error_detail = $last_error ? $last_error['message'] : 'Error desconocido.';
                    throw new Exception('No se pudo eliminar el archivo. Detalle: ' . $error_detail);
                }
                break;

                case 'check_data_integrity':
                                                if ($user_rol !== 'superadmin') {
                                                    throw new Exception('Accés denegat.');
                                                }

                                                // --- ★ INICIO CORRECCIÓN DEFINITIVA (Uso de LEFT JOIN / IS NULL y COUNT(*)) ---
                                                $report = [];
                                                // Esta sintaxis SÍ detecta correctamente los IDs nulos.
                                                $report['orphan_evolutions'] = $db->query("SELECT COUNT(ev.id) FROM tratamiento_evolucion ev LEFT JOIN tratamiento_ejercicios te ON ev.tratamiento_ejercicio_id = te.id WHERE te.id IS NULL")->fetchColumn();
                                                $report['orphan_exercises'] = $db->query("SELECT COUNT(te.id) FROM tratamiento_ejercicios te LEFT JOIN tratamientos t ON te.tratamiento_id = t.id WHERE t.id IS NULL")->fetchColumn();
                                                $report['orphan_treatment_to_exercise_links'] = $db->query("SELECT COUNT(te.id) FROM tratamiento_ejercicios te LEFT JOIN ejercicios e ON te.ejercicio_id = e.id WHERE e.id IS NULL")->fetchColumn();
                                                $report['orphan_chat_sender'] = $db->query("SELECT COUNT(cm.id) FROM chat_messages cm LEFT JOIN cuentas c ON cm.from_id = c.id WHERE cm.from_id IS NOT NULL AND c.id IS NULL")->fetchColumn();
                                                $report['orphan_chat_receiver'] = $db->query("SELECT COUNT(cm.id) FROM chat_messages cm LEFT JOIN cuentas c ON cm.to_id = c.id WHERE cm.to_id IS NOT NULL AND c.id IS NULL")->fetchColumn();

                                                // --- ★ CORRECCIÓN: COUNT(tfa.id) -> COUNT(*) ---
                                                $report['orphan_treatment_fisios'] = $db->query("SELECT COUNT(*) FROM tratamiento_fisios_asignados tfa LEFT JOIN tratamientos t ON tfa.tratamiento_id = t.id WHERE t.id IS NULL")->fetchColumn();
                                                $report['orphan_fisio_accounts'] = $db->query("SELECT COUNT(*) FROM tratamiento_fisios_asignados tfa LEFT JOIN cuentas c ON tfa.fisio_id = c.id WHERE tfa.fisio_id IS NOT NULL AND c.id IS NULL")->fetchColumn();
                                                // --- ★ FIN CORRECCIÓN ---

                                                $report['orphan_notifications'] = $db->query("SELECT COUNT(n.id) FROM notificaciones n LEFT JOIN cuentas c ON n.fisio_id = c.id WHERE n.fisio_id IS NOT NULL AND c.id IS NULL")->fetchColumn();

                                                // --- MODIFICACIÓN: CORREGIR LÓGICA DE DETECCIÓN DE PACIENTE HUÉRFANO (INCLUIR PROTOCOLOS) ---
                                                $report['orphan_treatments_patient'] = $db->query(
                                                    "SELECT COUNT(t.id) FROM tratamientos t LEFT JOIN cuentas c ON t.paciente_id = c.id " .
                                                    "WHERE (t.is_protocol = 0 AND t.paciente_id IS NULL) OR (t.paciente_id IS NOT NULL AND c.id IS NULL)"
                                                )->fetchColumn();
                                                // --- FIN MODIFICACIÓN ---

                                                $report['orphan_treatments_creator'] = $db->query("SELECT COUNT(t.id) FROM tratamientos t LEFT JOIN cuentas c ON t.creator_fisio_id = c.id WHERE t.creator_fisio_id IS NULL OR (t.creator_fisio_id IS NOT NULL AND c.id IS NULL)")->fetchColumn();

                                                echo json_encode(['status' => 'success', 'report' => $report]);
                                                break;

                                // ======================================================= -->
                                            // ============ INICIO: NUEVAS ACCIONES DE PURGA ========= -->
                                            // ======================================================= -->

                                            case 'purge_orphan_evolutions':
                                                if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                // --- ★ CORRECCIÓN (Sintaxis NOT EXISTS) ---
                                                $stmt = $db->prepare("DELETE FROM tratamiento_evolucion WHERE NOT EXISTS (SELECT 1 FROM tratamiento_ejercicios te WHERE te.id = tratamiento_evolucion.tratamiento_ejercicio_id)");
                                                $stmt->execute();
                                                $rowCount = $stmt->rowCount();
                                                // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A7) ★★★
                                                registrar_purga_ejecutada($db, $user_id, $actor_username, 'Evoluciones huérfanas: ' . $rowCount . ' eliminadas.');
                                                // ★★★ FIN CÓDIGO LIMPIO ★★★
                                                echo json_encode(['status' => 'success', 'message' => $rowCount . ' evoluciones huérfanas purgadas.']);
                                                break;

                                                case 'purge_orphan_exercises':
                                                    if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                    // --- ★ CORRECCIÓN (Sintaxis NOT EXISTS) ---
                                                    $stmt = $db->prepare("DELETE FROM tratamiento_ejercicios WHERE NOT EXISTS (SELECT 1 FROM tratamientos t WHERE t.id = tratamiento_ejercicios.tratamiento_id)");
                                                    $stmt->execute();
                                                    $rowCount = $stmt->rowCount();
                                                    // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A7) ★★★
                                                    registrar_purga_ejecutada($db, $user_id, $actor_username, 'Ejercicios de tratamiento huérfanos: ' . $rowCount . ' eliminados.');
                                                    // ★★★ FIN CÓDIGO LIMPIO ★★★
                                                    echo json_encode(['status' => 'success', 'message' => $rowCount . ' ejercicios huérfanos purgados.']);
                                                    break;

                                                    case 'purge_orphan_treatment_to_exercise_links':
                                                    if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                    // (Esta es la consulta que soluciona el problema)
                                                    $stmt = $db->prepare("DELETE te FROM tratamiento_ejercicios te LEFT JOIN ejercicios e ON te.ejercicio_id = e.id WHERE e.id IS NULL");
                                                    $stmt->execute();
                                                    $rowCount = $stmt->rowCount();
                                                    registrar_purga_ejecutada($db, $user_id, $actor_username, 'Ejercicios de tratamiento (sin ejercicio en biblioteca): ' . $rowCount . ' eliminados.');
                                                    echo json_encode(['status' => 'success', 'message' => $rowCount . ' enllaços trencats (tractament -> exercici) purgats.']);
                                                    break;

                                                    case 'purge_orphan_chat_sender':
                                                        if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                        // --- ★ CORRECCIÓN (Sintaxis NOT EXISTS) ---
                                                        $stmt = $db->prepare("DELETE FROM chat_messages WHERE from_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM cuentas c WHERE c.id = chat_messages.from_id)");
                                                        $stmt->execute();
                                                        $rowCount = $stmt->rowCount();
                                                        // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A7) ★★★
                                                        registrar_purga_ejecutada($db, $user_id, $actor_username, 'Mensajes de chat (emisor huérfano): ' . $rowCount . ' eliminados.');
                                                        // ★★★ FIN CÓDIGO LIMPIO ★★★
                                                        echo json_encode(['status' => 'success', 'message' => $rowCount . ' mensajes (emisor) huérfanos purgados.']);
                                                        break;

                                                        case 'purge_orphan_chat_receiver':
                                                            if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                            // --- ★ CORRECCIÓN (Sintaxis NOT EXISTS) ---
                                                            $stmt = $db->prepare("DELETE FROM chat_messages WHERE to_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM cuentas c WHERE c.id = chat_messages.to_id)");
                                                            $stmt->execute();
                                                            $rowCount = $stmt->rowCount();
                                                            // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A7) ★★★
                                                            registrar_purga_ejecutada($db, $user_id, $actor_username, 'Mensajes de chat (receptor huérfano): ' . $rowCount . ' eliminados.');
                                                            // ★★★ FIN CÓDIGO LIMPIO ★★★
                                                            echo json_encode(['status' => 'success', 'message' => $rowCount . ' mensajes (receptor) huérfanos purgados.']);
                                                            break;

                                                            case 'purge_orphan_treatment_fisios':
                                                                if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                                // --- ★ CORRECCIÓN (Sintaxis NOT EXISTS) ---
                                                                $stmt = $db->prepare("DELETE FROM tratamiento_fisios_asignados WHERE NOT EXISTS (SELECT 1 FROM tratamientos t WHERE t.id = tratamiento_fisios_asignados.tratamiento_id)");
                                                                $stmt->execute();
                                                                $rowCount = $stmt->rowCount();
                                                                // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A7) ★★★
                                                                registrar_purga_ejecutada($db, $user_id, $actor_username, 'Asignaciones de fisios (sin tratamiento): ' . $rowCount . ' eliminadas.');
                                                                // ★★★ FIN CÓDIGO LIMPIO ★★★
                                                                echo json_encode(['status' => 'success', 'message' => $rowCount . ' asignaciones (tratamiento) huérfanas purgadas.']);
                                                                break;

                                                                case 'purge_orphan_fisio_accounts':
                                                                    if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                                    // --- ★ CORRECCIÓN (Sintaxis NOT EXISTS) ---
                                                                    $stmt = $db->prepare("DELETE FROM tratamiento_fisios_asignados WHERE fisio_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM cuentas c WHERE c.id = tratamiento_fisios_asignados.fisio_id)");
                                                                    $stmt->execute();
                                                                    $rowCount = $stmt->rowCount();
                                                                    // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A7) ★★★
                                                                    registrar_purga_ejecutada($db, $user_id, $actor_username, 'Asignaciones de fisios (sin cuenta): ' . $rowCount . ' eliminadas.');
                                                                    // ★★★ FIN CÓDIGO LIMPIO ★★★
                                                                    echo json_encode(['status' => 'success', 'message' => $rowCount . ' asignaciones (fisio) huérfanas purgadas.']);
                                                                    break;

                                                                    case 'purge_orphan_notifications':
                                                                        if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }
                                                                        // --- ★ CORRECCIÓN (Sintaxis NOT EXISTS) ---
                                                                        $stmt = $db->prepare("DELETE FROM notificaciones WHERE fisio_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM cuentas c WHERE c.id = notificaciones.fisio_id)");
                                                                        $stmt->execute();
                                                                        $rowCount = $stmt->rowCount();
                                                                        // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A7) ★★★
                                                                        registrar_purga_ejecutada($db, $user_id, $actor_username, 'Notificaciones huérfanas: ' . $rowCount . ' eliminadas.');
                                                                        // ★★★ FIN CÓDIGO LIMPIO ★★★
                                                                        echo json_encode(['status' => 'success', 'message' => $rowCount . ' notificaciones huérfanas purgadas.']);
                                                                        break;

                                                                    case 'purge_orphan_treatments_patient':
                                                                            if ($user_rol !== 'superadmin') { throw new Exception('Accés denegat.'); }

                                                                            // --- MODIFICACIÓN: CORREGIR LÓGICA DE PURGA DE PACIENTE HUÉRFANO (INCLUIR PROTOCOLOS) ---
                                                                            $stmt = $db->prepare(
                                                                                "DELETE t FROM tratamientos t " .
                                                                                "LEFT JOIN cuentas c ON t.paciente_id = c.id " .
                                                                                "WHERE (t.is_protocol = 0 AND t.paciente_id IS NULL) OR (t.paciente_id IS NOT NULL AND c.id IS NULL)"
                                                                            );
                                                                            // --- FIN MODIFICACIÓN ---

                                                                            $stmt->execute();
                                                                            $rowCount = $stmt->rowCount();
                                                                            // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (A7) ★★★
                                                                            registrar_purga_ejecutada($db, $user_id, $actor_username, 'Tratamientos huérfanos (sin paciente): ' . $rowCount . ' eliminados.');
                                                                            // ★★★ FIN CÓDIGO LIMPIO ★★★
                                                                            echo json_encode(['status' => 'success', 'message' => $rowCount . ' tratamientos (paciente) huérfanos purgados.']);
                                                                            break;

                                            // ======================================================= -->
                                            // ============ FIN: NUEVAS ACCIONES DE PURGA ============ -->
                                            // ======================================================= -->

            // [MYSQL CORREGIDO] Usamos INSERT IGNORE y NOW()
            case 'dismiss_dashboard_alert':

                if (!isset($_POST['evolucion_id']) || !is_numeric($_POST['evolucion_id'])) {
                    http_response_code(400); // Bad Request
                    throw new Exception("ID de alerta no válido.");
                }
                $evolucion_id_to_dismiss = (int)$_POST['evolucion_id'];

                // [MYSQL] Usamos INSERT IGNORE y NOW()
                $stmt_dismiss = $db->prepare(
                    "INSERT IGNORE INTO dashboard_alert_dismissals (user_id, evolucion_id, dismissed_at)
                     VALUES (:user_id, :evolucion_id, NOW())"
                );

                $stmt_dismiss->execute([
                    ':user_id' => $user_id,
                    ':evolucion_id' => $evolucion_id_to_dismiss
                ]);

                echo json_encode(['status' => 'success', 'message' => 'Alerta descartada del dashboard.']);

                break;

            case 'get_my_announcements':
                if (!isset($user_id)) { throw new Exception('Accés denegat.'); }
                $current_date = date('Y-m-d');
                $sql = "SELECT a.id, a.title, a.content, a.message_type FROM anuncios a WHERE a.is_active = 1 AND (a.start_date IS NULL OR a.start_date <= :current_date) AND (a.end_date IS NULL OR a.end_date >= :current_date) AND NOT EXISTS (SELECT 1 FROM anuncio_vistos av WHERE av.anuncio_id = a.id AND av.user_id = :user_id) AND (a.target_audience = 'all_users' OR (a.target_audience = 'all_fisios' AND :user_role IN ('fisio', 'superadmin')) OR (a.target_audience = 'all_pacientes' AND :user_role = 'paciente') OR (a.target_audience IN ('specific_fisios', 'specific_pacientes') AND EXISTS (SELECT 1 FROM anuncio_recipients ar WHERE ar.anuncio_id = a.id AND ar.user_id = :user_id))) ORDER BY a.created_at DESC";
                $stmt = $db->prepare($sql);
                $stmt->execute([':current_date' => $current_date, ':user_id' => $user_id, ':user_role' => $user_rol]);
                echo json_encode(['status' => 'success', 'announcements' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
                break;

            // [MYSQL CORREGIDO] Usamos INSERT IGNORE y NOW()
            case 'mark_announcement_read':
                if (!isset($user_id)) { throw new Exception('Accés denegat.'); }
                $anuncio_id = (int)($_POST['anuncio_id'] ?? 0);
                if ($anuncio_id <= 0) { throw new Exception('ID d\'anunci no vàlid.'); }

                // [MYSQL] Usamos INSERT IGNORE y NOW()
                $stmt = $db->prepare("INSERT IGNORE INTO anuncio_vistos (anuncio_id, user_id, dismissed_at) VALUES (?, ?, NOW())");
                $stmt->execute([$anuncio_id, $user_id]);
                echo json_encode(['status' => 'success']);
                break;
                // --- ****** INICIO: SOLUCIÓN NOTIFICACIÓN "PERENNE" ****** ---
             case 'get_unread_message_count':
                if (!in_array($user_rol, ['superadmin', 'fisio'])) {
                    echo json_encode(['status' => 'success', 'count' => 0]); exit;
                }

                // ===== INICIO MODIFICACIÓN (Ocultar Chat durante suplantación Y VISTA GLOBAL) =====
                $is_read_only_view = isset($_SESSION['admin_origen_id']) || ($_SESSION['dashboard_view'] ?? 'personal') === 'global';
                if ($is_read_only_view) {
                    echo json_encode(['status' => 'success', 'count' => 0]);
                    exit;
                }
                // ===== FIN MODIFICACIÓN =====

                $stmt = $db->prepare("
                    SELECT COUNT(DISTINCT cm.from_id)
                    FROM chat_messages cm
                    JOIN cuentas p ON cm.from_id = p.id
                    JOIN tratamientos t ON t.paciente_id = p.id
                    LEFT JOIN tratamiento_fisios_asignados tfa ON t.id = tfa.tratamiento_id
                    WHERE
                        cm.to_id = :user_id
                        AND cm.status != 'read'
                        AND p.rol = 'paciente'
                        AND p.status = 'activo'
                        AND t.status = 'En curs'
                        AND (t.creator_fisio_id = :user_id OR tfa.fisio_id = :user_id OR p.id_fisio_registrador = :user_id)
                ");
                $stmt->execute([':user_id' => $user_id]);
                $count = (int)$stmt->fetchColumn();
                echo json_encode(['status' => 'success', 'count' => ($count ?: 0)]);
                break;
            // --- ****** FIN: SOLUCIÓN NOTIFICACIÓN "PERENNE" ****** ---


            // ======================================================= -->
            // ============ INICIO: NUEVAS ACCIONES DE CONFIGURACIÓN DE EMAIL (Solució 1)
            // ======================================================= -->
            case 'get_email_settings':
                if ($user_rol !== 'superadmin') {
                    throw new Exception('Accés denegat.');
                }
                // La función get_all_email_settings() está ahora en db.php
                $settings = get_all_email_settings();
                echo json_encode(['status' => 'success', 'data' => $settings]);
                break;

                case 'save_email_settings':
                                                    if ($user_rol !== 'superadmin') {
                                                        echo json_encode(['status' => 'error', 'message' => 'Accés denegat.']);
                                                        break;
                                                    }

                                                    // --- ★ ESTE ES EL ARRAY QUE YA CORREGIMOS ANTES ★ ---
                                                    $controlable_settings = [
                                                      'new_treatment_assigned',
                                                      'treatment_finished',
                                                      'treatment_transferred', // (Asegúrate que esté este)
                                                      'new_user_admin',
                                                      'new_user_register',
                                                      'password_reset',
                                                      'weekly_summary'
                                                    ];

                                                    $settings_from_js = $_POST['settings'] ?? [];
                                                    if (!is_array($settings_from_js)) {
                                                        throw new Exception("Formato de datos incorrecto.");
                                                    }

                                                    $received_settings = [];
                                                    foreach ($settings_from_js as $setting) {
                                                        if (isset($setting['name']) && isset($setting['value'])) {
                                                            $received_settings[$setting['name']] = $setting['value'];
                                                        }
                                                    }

                                                    $db->beginTransaction();
                                                    try {
                                                        // --- ★ INICIO DE LA CORRECCIÓN: ELIMINAR 'AND is_critical = 0' ★ ---
                                                        $stmt_update = $db->prepare("UPDATE email_settings SET is_enabled = ? WHERE setting_name = ?");
                                                        // --- ★ FIN DE LA CORRECCIÓN ★ ---

                                                        $updates = [];
                                                        foreach ($controlable_settings as $setting_name) {
                                                            $is_enabled = (isset($received_settings[$setting_name]) && $received_settings[$setting_name] == '1') ? 1 : 0;

                                                            // Ejecutamos la actualización (ahora funcionará para todos)
                                                            $stmt_update->execute([$is_enabled, $setting_name]);
                                                            $updates[$setting_name] = $is_enabled;
                                                        }

                                                        $db->commit();

                                                        $details = json_encode($updates);
                                                        registrar_config_emails_guardada($db, $user_id, $actor_username, $details);

                                                        echo json_encode(['status' => 'success', 'message' => 'Configuració d\'emails guardada correctament.']);

                                                    } catch (Exception $e) {
                                                        $db->rollBack();
                                                        error_log("Error al guardar config emails: " . $e->getMessage());
                                                        echo json_encode(['status' => 'error', 'message' => 'Error al guardar la configuració: ' . $e->getMessage()]);
                                                    }
                                                    break;

            // ======================================================= -->
            // ============ FIN: ACCIONES DE CONFIGURACIÓN DE EMAIL
            // ======================================================= -->


            // ======================================================= -->
            // ============ INICIO: NUEVAS ACCIONES DE BANEO ========= -->
            // ======================================================= -->

            // --- Gestión de Emails Baneados ---
            case 'get_banned_list':
                if ($user_rol !== 'superadmin') { throw new Exception('Acceso denegado.'); }
                // Usamos NOW() en lugar de CURRENT_TIMESTAMP en el SQL si fuera un DEFAULT, pero aquí es lectura.
                $stmt = $db->query("SELECT be.id, be.email, be.reason, be.banned_at, c.nombre, c.apellido
                                   FROM banned_emails be
                                   LEFT JOIN cuentas c ON be.banned_by_user_id = c.id
                                   ORDER BY be.banned_at DESC");
                echo json_encode(['status' => 'success', 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
                break;

                case 'add_banned_email':
                    if ($user_rol !== 'superadmin') {
                        echo json_encode(['status' => 'error', 'message' => 'Accés denegat.']);
                        break;
                    }

                    $email = trim($_POST['email']);
                    $reason = trim($_POST['reason'] ?? 'Sense motiu'); // Usamos 'reason' del formulario

                    if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                        echo json_encode(['status' => 'error', 'message' => 'Email no vàlid.']);
                        break;
                    }

                    try {
                        // ★★★ CORRECCIÓN C10: Usamos la tabla 'banned_emails' y las columnas 'emails', 'reason', 'banned_by_user_id' ★★★
                        $stmt = $db->prepare("INSERT INTO banned_emails (email, reason, banned_by_user_id, banned_at) VALUES (?, ?, ?, NOW())");
                        $stmt->execute([$email, $reason, $user_id]);

                        // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (C10) ★★★
                        registrar_email_baneado_anadido($db, $user_id, $actor_username, $email, $reason);
                        // ★★★ FIN CÓDIGO LIMPIO ★★★

                        echo json_encode(['status' => 'success', 'message' => 'Email afegit correctament.']);
                    } catch (PDOException $e) {
                        if ($e->getCode() == '23000') { // Error de entrada duplicada
                            echo json_encode(['status' => 'error', 'message' => 'Aquest email ja estava a la llista de baneig.']);
                        } else {
                            error_log("Error al afegir email prohibit: " . $e->getMessage());
                            echo json_encode(['status' => 'error', 'message' => 'Error de BBDD: ' . $e->getMessage()]);
                        }
                    }
                    break;

                    case 'remove_banned_email':
                        if ($user_rol !== 'superadmin') {
                            echo json_encode(['status' => 'error', 'message' => 'Accés denegat.']);
                            break;
                        }

                        $email_id = (int)$_POST['id'];

                        if ($email_id <= 0) {
                            echo json_encode(['status' => 'error', 'message' => 'ID no vàlid.']);
                            break;
                        }

                        try {
                            // ★★★ CORRECCIÓN C11: Usamos la tabla 'banned_emails' y la columna 'emails' ★★★
                            // Primero, obtener el email para el log antes de eliminar
                            $stmt_select = $db->prepare("SELECT email FROM banned_emails WHERE id = ?");
                            $stmt_select->execute([$email_id]);
                            $email_row = $stmt_select->fetch(PDO::FETCH_ASSOC);
                            // Corregida la clave de fetch (era 'emails', ahora 'email')
                            $email_for_log = $email_row['email'] ?? 'ID ' . $email_id;

                            // Luego, eliminar
                            $stmt_delete = $db->prepare("DELETE FROM banned_emails WHERE id = ?");
                            $stmt_delete->execute([$email_id]);

                            // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (C11) ★★★
                            registrar_email_baneado_eliminado($db, $user_id, $actor_username, $email_for_log);
                            // ★★★ FIN CÓDIGO LIMPIO ★★★

                            echo json_encode(['status' => 'success', 'message' => 'Email eliminat correctament.']);
                        } catch (Exception $e) {
                            error_log("Error al eliminar email prohibit: " . $e->getMessage());
                            echo json_encode(['status' => 'error', 'message' => 'Error al eliminar l\'email: ' . $e->getMessage()]);
                        }
                        break;

            // --- Gestión de Contraseñas Prohibidas ---
            case 'get_prohibited_passwords':
                if ($user_rol !== 'superadmin') { throw new Exception('Acceso denegado.'); }
                $stmt = $db->query("SELECT id, password_text FROM prohibited_passwords ORDER BY password_text ASC");
                echo json_encode(['status' => 'success', 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
                break;

                case 'add_prohibited_password':
                    if ($user_rol !== 'superadmin') {
                        echo json_encode(['status' => 'error', 'message' => 'Accés denegat.']);
                        break;
                    }

                    $password_text = trim($_POST['password_text'] ?? ''); // Aseguramos usar 'password_text'

                    if (empty($password_text)) {
                        echo json_encode(['status' => 'error', 'message' => 'El text de contrasenya no pot estar buit.']);
                        break;
                    }

                    try {
                        // ★★★ CORRECCIÓN C12: Usamos la tabla 'prohibited_passwords' y el campo 'password_text' ★★★
                        // Usamos INSERT IGNORE o el manejo de duplicados si la columna es UNIQUE
                        $stmt = $db->prepare("INSERT INTO prohibited_passwords (password_text) VALUES (?)");
                        $stmt->execute([$password_text]);

                        // Verificamos si la inserción fue exitosa
                        if ($stmt->rowCount() > 0) {
                            // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (C12) ★★★
                            registrar_pass_prohibida_anadida($db, $user_id, $actor_username, $password_text);
                            // ★★★ FIN CÓDIGO LIMPIO ★★★

                            echo json_encode(['status' => 'success', 'message' => 'Contrasenya afegida correctament.']);
                        } else {
                            // Si rowCount es 0, asumimos que ya existía (por clave UNIQUE o INSERT IGNORE)
                            echo json_encode(['status' => 'warning', 'message' => 'Aquesta contrasenya ja estava a la llista.']);
                        }
                    } catch (PDOException $e) {
                        // Error 23000 (Integrity Constraint Violation) cubre claves duplicadas si no se usa IGNORE
                        if ($e->getCode() == '23000') {
                            echo json_encode(['status' => 'warning', 'message' => 'Aquesta contrasenya ja estava a la llista.']);
                        } else {
                            error_log("Error al afegir contrasenya prohibida: " . $e->getMessage());
                            echo json_encode(['status' => 'error', 'message' => 'Error de BBDD: ' . $e->getMessage()]);
                        }
                    }
                    break;


                case 'remove_prohibited_password':
                    if ($user_rol !== 'superadmin') {
                        echo json_encode(['status' => 'error', 'message' => 'Accés denegat.']);
                        break;
                    }

                    $pass_id = (int)$_POST['id'];

                    if ($pass_id <= 0) {
                        echo json_encode(['status' => 'error', 'message' => 'ID no vàlid.']);
                        break;
                    }

                    try {
                        // ★★★ CORRECCIÓN C13: Usamos la tabla 'prohibited_passwords' ★★★

                        // 1. Obtener el texto para el log antes de eliminar
                        $stmt_select = $db->prepare("SELECT password_text FROM prohibited_passwords WHERE id = ?");
                        $stmt_select->execute([$pass_id]);
                        $pass_row = $stmt_select->fetch(PDO::FETCH_ASSOC);
                        $pass_for_log = $pass_row['password_text'] ?? 'ID ' . $pass_id;

                        // 2. Eliminar
                        $stmt_delete = $db->prepare("DELETE FROM prohibited_passwords WHERE id = ?");
                        $stmt_delete->execute([$pass_id]);

                        // ★★★ CÓDIGO LIMPIO: Uso de función logger.php (C13) ★★★
                        registrar_pass_prohibida_eliminada($db, $user_id, $actor_username, $pass_for_log);
                        // ★★★ FIN CÓDIGO LIMPIO ★★★

                        echo json_encode(['status' => 'success', 'message' => 'Prohibició de contrasenya eliminada.']);
                    } catch (Exception $e) {
                        error_log("Error al eliminar prohibició de contrasenya: " . $e->getMessage());
                        echo json_encode(['status' => 'error', 'message' => 'Error al eliminar la contrasenya: ' . $e->getMessage()]);
                    }
                    break;

            // ======================================================= -->
            // ============ FIN: NUEVAS ACCIONES DE BANEO   ========== -->
            // ======================================================= -->

            default:
                throw new Exception('Acció AJAX no reconeguda en dashboard.');
        } // Fin switch
      } catch (Exception $e) {
      if (isset($db) && $db->inTransaction()) $db->rollBack();
      http_response_code(400);
      error_log("Error en dashboard AJAX action=$action: " . $e->getMessage() . "\nTrace: " . $e->getTraceAsString());
      echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
  }
  exit;
} // Fin if AJAX


// --- [MYSQL CORREGIDO] INICIO: LÓGICA DE URL BASE DINÁMICA ---
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$domain_root = $protocol . $_SERVER['HTTP_HOST'];
// Usamos SCRIPT_NAME para obtener la ruta del script actual (dashboard.php)
// y dirname() para obtener la carpeta que lo contiene.
$app_base_url = rtrim($domain_root . dirname($_SERVER['SCRIPT_NAME']), '/') . '/';
// --- FIN: LÓGICA DE URL BASE DINÁMICA ---


// --- RENDERIZADO HTML PRINCIPAL ---
$page_title = "Panell Principal - AVANT ONLINE";
include 'partials/header.php';

// --- ★★★ INICIO MODIFICACIÓN 3 (ELIMINADO) ★★★ ---
// Esta lógica ahora está centralizada en header.php
/*
if ($user_rol === 'superadmin' && $current_view === 'global') {
  echo "
  <style>
      #notificationBellLink {
          display: none !important;
      }
  </style>
  ";
}
*/
// --- ★★★ FIN MODIFICACIÓN 3 ★★★ ---
?>
<style>
  /* Estilos CSS sin cambios... */
  .kpi-card { text-align: center; } .kpi-card .display-4 { font-weight: bold; margin-bottom: 0; } .kpi-card .card-subtitle { font-size: 0.8rem; text-transform: uppercase; }
  #dashboard-activity-feed .list-group-item, #dashboard-urgent-alerts .list-group-item, #dashboard-urgent-ending .list-group-item, #dashboard-recent-notifications .list-group-item:not(:last-child) { padding: 0.6rem 0.85rem; font-size: 0.9rem; border-bottom: 1px solid #eee; }
  #dashboard-recent-notifications .list-group-item:last-child { border-top: 1px solid #eee; }
  .dashboard-section { min-height: 350px; display: flex; flex-direction: column; } .dashboard-section .card { flex-grow: 1; } .dashboard-section .card-body { display: flex; flex-direction: column; } .dashboard-section .card-title { flex-shrink: 0; }
  /* --- ★ MEJORA: Gráfico de Anillo (Ajuste de altura) --- */
  #dashboard-announcements, #dashboard-urgent-alerts .list-group, #dashboard-urgent-ending .list-group, #dashboard-activity-feed .list-group, #dashboard-recent-notifications .list-group, #dashboard-my-content .list-group { max-height: 250px; overflow-y: auto; flex-grow: 1; margin-bottom: 0; }

  /* --- ★ MODIFICACIÓN: Separado #dashboard-my-treatments para arreglar el corte del enlace --- */
  #dashboard-recent-notifications, #dashboard-my-content {
      overflow: hidden;
      display: flex;
      flex-direction: column;
      height: 100%;
  }
  #dashboard-my-treatments {
      /* overflow: hidden; <-- ELIMINADO */
      display: flex;
      flex-direction: column;
      height: 100%;
  }
  /* --- ★ FIN MODIFICACIÓN --- */

  #dashboard-recent-notifications .list-group-item-action, #dashboard-my-content .list-group-item-action, #dashboard-my-treatments .list-group-item-action { flex-shrink: 0; border-top: 1px solid #dee2e6; }
  #fisioSummaryModal .modal-body { max-height: 65vh; overflow-y: auto; }
  .text-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* --- ★ MEJORA 2 (Corregida): Hover gris en filas de widgets --- */
  /* Para tarjetas KPI (oscurecer levemente) - Esto estaba bien */
  .kpi-card {
      transition: filter 0.2s ease-in-out;
  }
  .kpi-card:hover {
      filter: brightness(95%);
  }

  /* Para las "filas" (list-group-items) dentro de los widgets */
  #dashboard-urgent-alerts .list-group-item:hover,
  #dashboard-urgent-ending .list-group-item:hover,
  #dashboard-activity-feed .list-group-item:hover,
  #dashboard-recent-notifications .list-group-item-action:hover,
  #dashboard-my-content .list-group-item:hover,
  #dashboard-my-treatments .list-group-item:hover:not(a) { /* Excluir el enlace "Anar a..." */
       background-color: #f8f9fa; /* Gris claro de Bootstrap */
       transition: background-color 0.1s ease-in-out;
  }
  /* --- ★ FIN MEJORA 2 --- */
</style>
<main class="main-content container mt-4" style="max-width: 1420px;">

    <?php
    // --- ★ INICIO INTERRUPTOR VISTA SUPERADMIN ★ ---
    if ($user_rol === 'superadmin'):
        $personal_active = ($current_view === 'personal') ? 'active btn-primary' : 'btn-outline-primary';
        $global_active = ($current_view === 'global') ? 'active btn-primary' : 'btn-outline-primary';
    ?>
    <div class="d-flex justify-content-between align-items-center mb-3">
        <div class="btn-group" role="group" aria-label="Vista del Dashboard">
            <a href="dashboard.php?view=personal" class="btn btn-sm <?php echo $personal_active; ?>">
                <i class="bi bi-person-fill me-1"></i> Vista Personal
            </a>
            <a href="dashboard.php?view=global" class="btn btn-sm <?php echo $global_active; ?>">
                <i class="bi bi-globe me-1"></i> Vista Global
            </a>
        </div>

        <button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#dashboard-top-row-collapse" aria-expanded="true" aria-controls="dashboard-top-row-collapse" id="toggle-summary-btn">
            <i class="bi bi-chevron-up"></i>
            <span>Ocultar Resumen</span>
        </button>
    </div>
    <?php else: ?>
        <div class="d-flex justify-content-end mb-2">
            <button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#dashboard-top-row-collapse" aria-expanded="true" aria-controls="dashboard-top-row-collapse" id="toggle-summary-btn">
                <i class="bi bi-chevron-up"></i>
                <span>Ocultar Resumen</span>
            </button>
        </div>
    <?php endif;
    // --- ★ FIN INTERRUPTOR VISTA SUPERADMIN ★ ---
    ?>

    <div class="collapse show" id="dashboard-top-row-collapse">
        <div class="row g-4 mb-4" id="dashboard-top-row">
            <div class="col-lg-8" id="announcements-col">
                 <div class="card h-100 shadow-sm">
                     <div class="card-body d-flex flex-column">
                        <h5 class="card-title mb-3"><i class="bi bi-megaphone me-2 text-primary"></i> Anuncis Actius</h5>
                        <div id="dashboard-announcements" class="flex-grow-1" style="overflow-y: auto; max-height: 200px;">
                            <p class="text-center p-3"><span class="spinner-border spinner-border-sm"></span> Carregant anuncis...</p>
                        </div>
                    </div>
                </div>
                 </div>
            <div class="col-lg-4" id="kpis-col">
                 <h5 class="mb-3"><i class="bi bi-graph-up me-2 text-success"></i> Indicadors Clau</h5>

                <div id="dashboard-kpis" class="row g-3">
                    <div class="col-6">
                        <div class="card kpi-card bg-primary-subtle text-primary-emphasis h-100">
                            <div class="card-body">
                                <h6 class="card-subtitle mb-2">Pacients Actius</h6>
                                <p class="display-4 mb-0" id="kpi-active-patients">...</p>
                                <small class="text-muted d-block" style="font-size: 0.7rem; margin-top: -5px;">Pacients únics amb Tractaments "En curs"</small>
                            </div>
                        </div>
                    </div>
                    <div class="col-6">
                        <div class="card kpi-card bg-info-subtle text-info-emphasis h-100">
                            <div class="card-body">
                                <h6 class="card-subtitle mb-2">Finalitzen Prompte</h6>
                                <p class="display-4 mb-0" id="kpi-ending-soon">...</p>
                                <small class="text-muted d-block" style="font-size: 0.7rem; margin-top: -5px;">Tractaments "En curs" que finalitzen en 3 dies</small>
                            </div>
                        </div>
                    </div>
                    <div class="col-6">
                        <div class="card kpi-card bg-warning-subtle text-warning-emphasis h-100">
                            <div class="card-body">
                                <h6 class="card-subtitle mb-2">Tractaments en Curs</h6>
                                <p class="display-4 mb-0" id="kpi-total-en-curs">...</p>
                                <small class="text-muted d-block" style="font-size: 0.7rem; margin-top: -5px;">Total de tractaments actius (totes les dates)</small>
                            </div>
                        </div>
                    </div>
                    <div class="col-6">
                        <div class="card kpi-card bg-danger-subtle text-danger-emphasis h-100">
                            <div class="card-body">
                                <h6 class="card-subtitle mb-2">Alertes Recents</h6>
                                <p class="display-4 mb-0" id="kpi-recent-alerts">...</p>
                                <small class="text-muted d-block" style="font-size: 0.7rem; margin-top: -5px;">Alertes Dolor/Esforç (>=4) en 5 dies</small>
                            </div>
                        </div>
                    </div>
                 </div>
                 </div>
        </div>
    </div>
    <hr class="mb-4">

    <div class="row g-4">
            <div class="col-lg-4 dashboard-section">
                 <div class="card h-100 shadow-sm">
                     <div class="card-body d-flex flex-column">
                        <h5 class="card-title mb-3"><i class="bi bi-exclamation-triangle-fill me-2 text-danger"></i> Accions Urgents</h5>

                        <h6 class="mb-2 flex-shrink-0"><i class="bi bi-heartbreak-fill me-1 text-danger"></i> Alertes Dolor/Esforç (+4)</h6>
                        <div id="dashboard-urgent-alerts" class="mb-3 border rounded">
                            <div class="list-group list-group-flush">
                                <div class="list-group-item text-center p-3 text-muted">Carregant alertes...</div>
                            </div>
                        </div>

                        <h6 class="mb-2 flex-shrink-0"><i class="bi bi-calendar-x-fill me-1 text-warning"></i> Tractaments Finalitzant (3 dies)</h6>
                        <div id="dashboard-urgent-ending" class="mb-3 border rounded">
                            <div class="list-group list-group-flush">
                                <div class="list-group-item text-center p-3 text-muted">Carregant tractaments...</div>
                            </div>
                        </div>

                        <?php
                        // --- ★ INICIO MODIFICACIÓN 1 (Ocultar en Vista Global Y Suplantación) ---
                        // Esta lógica ahora se basa en la API. Si no hay mensajes (count=0), no se mostrará.
                        // Y la API ya devuelve 0 si es vista global o suplantación.
                        if ($current_view === 'personal' && !isset($_SESSION['admin_origen_id'])):
                        ?>
                        <h6 class="mb-2 flex-shrink-0"><i class="bi bi-chat-dots-fill me-1 text-primary"></i> Missatges No Llegits (<span id="feed-unread-msg-count">0</span>)</h6>
                         <div id="dashboard-unread-messages" class="list-group list-group-flush border rounded"> <div class="list-group-item text-center p-3 text-muted">Comprovant missatges...</div>
                             <a href="chat_inbox.php" class="list-group-item list-group-item-action text-center p-2 text-primary small">
                                 Anar a la Bústia d'Entrada <i class="bi bi-arrow-right-short"></i>
                             </a>
                         </div>
                        <?php
                        endif;
                        // --- ★ FIN MODIFICACIÓN 1 ---
                        ?>

                     </div>
                 </div>
            </div>

            <div class="col-lg-4 dashboard-section">
                 <div class="card h-100 shadow-sm">
                     <div class="card-body d-flex flex-column">
                        <h5 class="card-title mb-3"><i class="bi bi-activity me-2 text-info"></i> Feed d'Activitat</h5>

                         <h6 class="mb-2 flex-shrink-0"><i class="bi bi-person-check-fill me-1 text-success"></i> Exercicis Realitzats Recentment</h6>
                         <div id="dashboard-activity-feed" class="mb-3 border rounded">
                             <div class="list-group list-group-flush">
                                <div classs="list-group-item text-center p-3 text-muted">Carregant activitat...</div>
                             </div>
                         </div>

                        <?php
                        // --- ★ INICIO MODIFICACIÓN 2 (Ocultar en Vista Global Y Suplantación) ---
                        // Esta lógica ahora se basa en la API. Si no hay notificaciones, no se mostrará.
                        // Y la API ya devuelve [] si es vista global o suplantación.
                        if ($current_view === 'personal' && !isset($_SESSION['admin_origen_id'])):
                        ?>
                         <h6 class="mb-2 flex-shrink-0"><i class="bi bi-bell-fill me-1 text-warning"></i> Últimes Notificacions</h6>
                         <div id="dashboard-recent-notifications" class="border rounded d-flex flex-column"> <div class="list-group list-group-flush flex-grow-1">
                                 <div class="list-group-item text-center p-3 text-muted">Carregant notificacions...</div>
                              </div>
                              <button class="list-group-item list-group-item-action text-center text-primary small p-2" data-bs-toggle="offcanvas" data-bs-target="#notificationOffcanvas">
                                  Veure Totes les Notificacions <i class="bi bi-box-arrow-up-right ms-1"></i>
                              </button>
                         </div>
                        <?php
                        endif;
                        // --- ★ FIN MODIFICACIÓN 2 ---
                        ?>

                         </div>
                 </div>
            </div>

            <div class="col-lg-4 dashboard-section">
                 <div class="card h-100 shadow-sm">
                     <div class="card-body d-flex flex-column">
                        <h5 class="card-title mb-3"><i class="bi bi-person-workspace me-2 text-success"></i> El Meu Resum</h5>
                         <h6 class="mb-2 flex-shrink-0"><i class="bi bi-folder-fill me-1"></i> El Meu Contingut</h6>
                         <div id="dashboard-my-content" class="border rounded d-flex flex-column mb-3">
                             <div class="list-group list-group-flush flex-grow-1">
                                <div class="list-group-item text-center p-3 text-muted">Carregant contingut...</div>
                             </div>
                              <?php if ($current_view === 'personal'): // ★ INICIO MODIFICACIÓN: Ocultar en vista global ?>
                              <a href="#" class="list-group-item list-group-item-action text-center text-primary small p-2 open-summary-modal-link disabled" aria-disabled="true">
                                  <i class="bi bi-box-arrow-up-right me-1"></i> Veure Resum Detallat
                              </a>
                              <?php endif; // ★ FIN MODIFICACIÓN ?>
                         </div>

                         <h6 class="mb-2 flex-shrink-0"><i class="bi bi-clipboard2-data-fill me-1"></i> Estat dels Meus Tractaments</h6>
                         <div id="dashboard-my-treatments" class="border rounded d-flex flex-column">
                            <div class="flex-grow-1 p-3" style="position: relative; min-height: 250px;">
                                <canvas id="treatmentStatusChart"></canvas>
                                <div class="chart-loading-spinner list-group-item text-center p-3 text-muted" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: #fff; display: flex; align-items: center; justify-content: center;">
                                    Carregant estadístiques...
                                </div>
                            </div>
                            <a href="treatments.php" class="list-group-item list-group-item-action text-center text-primary small p-2 disabled" aria-disabled="true">
                                <i class="bi bi-arrow-right-short me-1"></i> Anar a Gestió de Tractaments
                            </a>
                         </div>
                     </div>
                 </div>
            </div>
        </div>

</main>
<div class="modal fade" id="fisioSummaryModal" tabindex="-1">
    <div class="modal-dialog modal-lg modal-dialog-scrollable">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="fisioSummaryModalLabel">Resum d'Activitat</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body" id="fisioSummaryModalBody">
                <div class="text-center p-5"><div class="spinner-border text-primary"></div></div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tancar</button>
            </div>
        </div>
    </div>
</div>

<div class="modal fade" id="feedbackModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="feedbackModalTitle"><i class="bi bi-chat-dots-fill me-2 text-primary"></i> Feedback Rápido</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <p class="mb-1">Enviando a: <strong id="feedbackPatientName">...</strong></p>
                <p class="mb-2 fst-italic text-muted" id="feedbackExerciseContext" style="font-size: 0.9rem;">Sobre el ejercicio: ...</p>

                <form id="feedbackForm" onsubmit="return false;">
                    <div class="mb-3">
                        <label for="feedbackMessage" class="form-label">Mensaje:</label>
                        <textarea class="form-control" id="feedbackMessage" rows="3" placeholder="Escribe un mensaje corto de ánimo o una corrección..."></textarea>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
                <button type="button" class="btn btn-primary" id="sendFeedbackBtn" data-patient-id="" data-exercise-title="">
                    <i class="bi bi-send-fill me-1"></i> Enviar
                </button>
            </div>
        </div>
    </div>
</div>

<?php include 'partials/footer.php'; // Incluye el footer que contiene los scripts JS y modals comunes ?>

<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.0/dist/chart.min.js"></script>
<script>
// JavaScript sin cambios...
const CURRENT_USER_ROL = '<?= $user_rol ?>';
const CURRENT_USER_ID = <?= $user_id ?>;
const IS_SUPERADMIN = CURRENT_USER_ROL === 'superadmin';
// --- [MYSQL CORREGIDO] Nueva URL base dinámica ---
const APP_BASE_URL = "<?php echo $app_base_url; ?>";

// --- ★ INICIO: LÓGICA DE VISTA GLOBAL (para CSS) ★ ---
// Esta variable global la usará el CSS de header.php para ocultar acciones
const CURRENT_DASHBOARD_VIEW = '<?= $current_view ?>';
if (CURRENT_DASHBOARD_VIEW === 'global') {
    document.body.dataset.globalView = 'true';
}
// --- ★ FIN: LÓGICA DE VISTA GLOBAL ★ ---

function nl2br(str) { if (typeof str === 'undefined' || str === null) return ''; return (str + '').replace(/([^>\r_n]?)(\r\n|\n\r|\r|\n)/g, '$1<br>$2'); }

// --- ★ INICIO MODIFICACIÓN: Nueva función helper "formatRelativeTime" (CON CORRECCIÓN) ---
function formatRelativeTime(fecha, hora) {
    if (!fecha) return '';
    const horaFallback = hora || '00:00:00';
    const activityDate = new Date(`${fecha}T${horaFallback}`);
    if (isNaN(activityDate)) return fecha; // Fallback si la fecha es inválida
    const now = new Date();
    const diffSeconds = Math.round((now - activityDate) / 1000);
    const diffMinutes = Math.round(diffSeconds / 60);
    const diffHours = Math.round(diffMinutes / 60);
    const timeFormatted = activityDate.toLocaleTimeString('ca-ES', { hour: '2-digit', minute: '2-digit' });
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const activityDay = new Date(activityDate);
    activityDay.setHours(0, 0, 0, 0);
    if (activityDay.getTime() === today.getTime()) {
        if (!hora) return "Hui";
        if (diffMinutes < 1) return "Ara mateix";
        if (diffMinutes < 60) return `Fa ${diffMinutes} min`;
        if (diffHours < 2) return "Fa 1 hora";
        return `Fa ${diffHours} hores`; // ej: "Fa 4 hores"
    }
    const yesterday = new Date(today);
    yesterday.setDate(yesterday.getDate() - 1);
    if (activityDay.getTime() === yesterday.getTime()) {
        if (!hora) return "Ahir";
        return `Ahir a les ${timeFormatted}`; // ej: "Ahir a les 15:30"
    }
    return activityDate.toLocaleDateString('ca-ES', { day: '2-digit', month: '2-digit' });
}
// --- ★ FIN MODIFICACIÓN ---

function updateTopRowLayout() {
    const hasActiveAnnouncements = $('#dashboard-announcements .alert').length > 0;
    const kpiCol = $('#kpis-col');
    const kpiItems = $('#dashboard-kpis .col-6'); // Selector de los 4 items KPI
    if (hasActiveAnnouncements) {
        $('#announcements-col').removeClass('d-none');
        kpiCol.removeClass('col-lg-12').addClass('col-lg-4');
        kpiItems.removeClass('col-lg-3');
    } else {
        $('#announcements-col').addClass('d-none'); // Oculta la columna de anuncios
        kpiCol.removeClass('col-lg-4').addClass('col-lg-12'); // Expande la columna de KPIs
        kpiItems.addClass('col-lg-3');
    }
}

// --- [MYSQL CORREGIDO] NUEVA FUNCIÓN: Simulación de Tareas Cron (Corregida con APP_BASE_URL) ---
function runSimulatedCron() {
    // ESTA FUNCIÓN SE ELIMINA PARA LA SOLUCIÓN SÍNCRONA
    /*
    $.ajax({
        type: "POST",
        url: APP_BASE_URL + 'cron_tasks.php', // CORREGIDO: Usar APP_BASE_URL
        data: { ajax: true, action: 'run_centralized_tasks' },
        dataType: 'json'
    }).done(response => {
        // Tarea completada con éxito
    }).fail(xhr => {
        console.warn("Centralized cron task failed to ping:", xhr);
    });
    */
}
// --- FIN NUEVA FUNCIÓN ---

// --- [MYSQL CORREGIDO] NUEVA FUNCIÓN: Actualizar Última Vista de Usuario (Corregida con APP_BASE_URL) ---
function updateUserLastSeen() {
    $.ajax({
        type: "POST",
        url: APP_BASE_URL + 'reports_ajax.php', // CORREGIDO: Usar APP_BASE_URL
        data: { ajax: true, action: 'update_user_last_seen' },
        dataType: 'json'
    }).done(response => {
        // Éxito
    }).fail(xhr => {
        console.error("Error al actualizar 'last_seen'.", xhr);
    });
}
// --- FIN NUEVA FUNCIÓN ---


$(document).ready(function() {
    // ===== INICIO MODIFICACIÓN (Detectar Suplantación Y VISTA GLOBAL) =====
    const isImpersonating = document.body.dataset.impersonating === 'true';
    const isGlobalView = document.body.dataset.globalView === 'true';
    const isReadOnlyView = isImpersonating || isGlobalView; // ★ Variable JS combinada
    // ===== FIN MODIFICACIÓN =====

    // --- [MYSQL CORREGIDO] Llama a las funciones de ping/cron ---
    // runSimulatedCron(); // NO SE LLAMA en modo síncrono
    updateUserLastSeen();

    // --- Resto de Selectores y Listeners (SIN CAMBIOS) ---
    const announcementsContainer = $('#dashboard-announcements'); const kpiActivePatients = $('#kpi-active-patients'); const kpiEndingSoon = $('#kpi-ending-soon'); const kpiRecentAlerts = $('#kpi-recent-alerts'); const kpiTotalEnCurs = $('#kpi-total-en-curs');
    const urgentAlertsContainer = $('#dashboard-urgent-alerts .list-group'); const urgentEndingContainer = $('#dashboard-urgent-ending .list-group'); const activityFeedContainer = $('#dashboard-activity-feed .list-group'); const feedUnreadMsgCount = $('#feed-unread-msg-count'); const recentNotificationsContainer = $('#dashboard-recent-notifications .list-group'); const myContentContainer = $('#dashboard-my-content .list-group');
    const fisioSummaryModalEl = document.getElementById('fisioSummaryModal');
    const treatmentChartSpinner = $('#dashboard-my-treatments .chart-loading-spinner');
    let treatmentChartInstance = null;

    // --- Listeners de Colapso (SIN CAMBIOS) ---
    const topRowCollapse = document.getElementById('dashboard-top-row-collapse');
    const toggleBtn = $('#toggle-summary-btn');
    if (topRowCollapse) {
        topRowCollapse.addEventListener('show.bs.collapse', function () {
            toggleBtn.find('i').removeClass('bi-chevron-down').addClass('bi-chevron-up');
            toggleBtn.find('span').text('Ocultar Resumen');
        });
        topRowCollapse.addEventListener('hide.bs.collapse', function () {
            toggleBtn.find('i').removeClass('bi-chevron-up').addClass('bi-chevron-down');
            toggleBtn.find('span').text('Mostrar Resumen');
        });
    }

    // --- Lógica Modal de Bloqueo (SIN CAMBIOS) ---
    let blockingAnnouncementModal = null;
    function renderAnnouncements(announcements) {
        announcementsContainer.empty();
        const blockingAnnouncement = announcements.find(a => a.is_blocking == 1);
        if (blockingAnnouncement) {
            const modalEl = document.getElementById('blockingAnnouncementModal');
            if (!modalEl) { console.error("Modal de bloqueo no encontrado en el DOM"); return; }
            const modalTitle = $(modalEl).find('.modal-title');
            const modalBody = $(modalEl).find('.modal-body');
            const modalButton = $(modalEl).find('#confirmReadBlockingAnnouncementBtn');
            let iconClass = 'bi-info-circle-fill text-warning';
            if (blockingAnnouncement.message_type === 'warning') iconClass = 'bi-exclamation-triangle-fill text-warning';
            if (blockingAnnouncement.message_type === 'danger') iconClass = 'bi-exclamation-octagon-fill text-danger';
            modalTitle.html(`<i class="bi ${iconClass} me-2"></i> ${escapeHtml(blockingAnnouncement.title)}`);
            modalBody.html(nl2br(escapeHtml(blockingAnnouncement.content)));
            modalButton.data('anuncio-id', blockingAnnouncement.id);
            if (!blockingAnnouncementModal) {
                blockingAnnouncementModal = new bootstrap.Modal(modalEl, { backdrop: 'static', keyboard: false });
            }
            blockingAnnouncementModal.show();
            announcementsContainer.html('<div class="text-center text-muted p-4"><i class="bi bi-info-circle fs-3 d-block mb-2" style="color:lightgray;"></i><i style="color:gray";>Primero revisa el anuncio importante.</i></div>');
        } else {
            if (announcements && announcements.length > 0) {
                announcements.forEach(a => {
                    let alertClass = 'alert-info';
                    if (a.message_type === 'warning') alertClass = 'alert-warning';
                    if (a.message_type === 'danger') alertClass = 'alert-danger';
                    let iconClass = 'bi-info-circle-fill';
                    if (a.message_type === 'warning') iconClass = 'bi-exclamation-triangle-fill';
                    if (a.message_type === 'danger') iconClass = 'bi-exclamation-octagon-fill';
                    const announcementHtml = `<div class="alert ${alertClass} alert-dismissible fade show mb-2" role="alert" data-anuncio-id="${a.id}"><strong><i class="bi ${iconClass} me-2"></i> ${escapeHtml(a.title)}</strong><p class="mb-0 mt-1 small">${nl2br(escapeHtml(a.content))}</p><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>`;
                    announcementsContainer.append(announcementHtml);
                });
            } else {
                announcementsContainer.html('<div class="text-center text-muted p-4"><i class="bi bi-info-circle fs-3 d-block mb-2" style="color:lightgray;"></i><i style="color:gray";>No hi ha anuncis actius en aquest moment.</i></div>');
            }
        }
        updateTopRowLayout();
    }
    $(document).on('click', '#confirmReadBlockingAnnouncementBtn', function() {
        const anuncioId = $(this).data('anuncio-id');
        const $btn = $(this);
        if (anuncioId) {
            $btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-2"></span> Processant...');
            // [MYSQL CORREGIDO] Usar APP_BASE_URL para llamadas AJAX
            $.post(APP_BASE_URL + 'dashboard.php', { ajax: true, action: 'mark_announcement_read', anuncio_id: anuncioId })
                .done(function() {
                    if (blockingAnnouncementModal) { blockingAnnouncementModal.hide(); }
                    loadDashboardData();
                })
                .fail(function(xhr) { console.error("Error al marcar anuncio como visto:", xhr.responseText); })
                .always(function() { $btn.prop('disabled', false).html('He llegit i entès'); });
        }
    });

    // --- Renderizado de KPIs (SIN CAMBIOS) ---
    function renderKPIs(kpis) {
        kpiActivePatients.text(kpis.active_patients || 0);
        kpiEndingSoon.text(kpis.ending_soon || 0);
        kpiRecentAlerts.text(kpis.recent_alerts || 0);
        kpiTotalEnCurs.text(kpis.total_en_curs || 0);
    }

    function renderUrgentActions(urgent_actions) {
                // Alertas
                urgentAlertsContainer.empty();
                if (urgent_actions.alerts && urgent_actions.alerts.length > 0) {
                    urgent_actions.alerts.forEach(alert => {

                        // ★ INICIO DE LA CORRECCIÓN (Colores de Consistencia) ★

                        // Nueva lógica explícita:
                        let dolorVal = alert.dolor_percibido !== null ? parseInt(alert.dolor_percibido) : 0;
                        let esforVal = alert.esfuerzo_percibido !== null ? parseInt(alert.esfuerzo_percibido) : 0;

                        // Lógica de 3 niveles (como en el Feed d'Activitat)

                        // Dolor: 0-1 (Success/Verde), 2-3 (Warning/Amarillo), 4+ (Danger/Rojo)
                        let dolorClass = 'bg-success-subtle text-success-emphasis';
                        if (dolorVal >= 2 && dolorVal <= 3) dolorClass = 'bg-warning-subtle text-warning-emphasis';
                        if (dolorVal >= 4) dolorClass = 'bg-danger-subtle text-danger-emphasis';

                        // Esforç: 0-1 (Info/Azul), 2-3 (Primary/Azul), 4+ (Danger/Rojo)
                        let esforClass = 'bg-info-subtle text-info-emphasis';
                        if (esforVal >= 2 && esforVal <= 3) esforClass = 'bg-primary-subtle text-primary-emphasis';
                        if (esforVal >= 4) esforClass = 'bg-danger-subtle text-danger-emphasis';

                        // HTML explícito (Ahora usa badges)
                        const alertScoresHtml = `
                            <span class="text-nowrap ps-2">
                                <span class="badge ${dolorClass}" title="Dolor: ${dolorVal}/5">D: ${dolorVal}</span>
                                <span class="badge ${esforClass} ms-1" title="Esforç: ${esforVal}/5">E: ${esforVal}</span>
                            </span>
                        `;
                        // ★ FIN DE LA CORRECCIÓN ★


                        // ★ INICIO MODIFICACIÓN 1: Añadir variables para botón de chat
                        const patientFullName = `${escapeHtml(alert.patient_nombre)} ${escapeHtml(alert.patient_apellido)}`;
                        const exerciseTitle = escapeHtml(alert.exercise_title);
                        // ★ FIN MODIFICACIÓN 1

                        // --- CAMBIO 1: Añadido 'border-0' ---
                        // ★ MODIFICACIÓN 2: Añadido botón de chat y ajuste de márgenes
                        // ===== INICIO MODIFICACIÓN (Ocultar botones si se suplanta O ES VISTA GLOBAL) =====
                        // La clase 'accion-suplantacion-oculta' ahora es controlada por CSS en header.php
                        // para ambas condiciones (impersonating Y global-view)

                        // ★★★ INICIO MODIFICACIÓN (Añadir &evo_date=) ★★★
                        const itemHtml = `<div class="list-group-item p-0"><div class="d-flex align-items-center">`+
                                         `<a href="fitxa_pacient.php?id=${alert.patient_id}&action=open_evo&treatment_id=${alert.treatment_id}&evo_date=${alert.fecha_realizacion}" class="text-decoration-none text-dark flex-grow-1 p-2" style="min-width:0;">`+
                                         // ★★★ FIN MODIFICACIÓN ★★★
                                            `<div class="d-flex w-100 justify-content-between">`+
                                                `<h6 class="mb-1 text-truncate" title="${escapeHtml(alert.patient_apellido)}, ${escapeHtml(alert.patient_nombre)}">${escapeHtml(alert.patient_apellido)}, ${escapeHtml(alert.patient_nombre)}</h6>`+

                                                // ★ LÍNEA REEMPLAZADA ★
                                                alertScoresHtml + // <-- Aquí usamos la nueva variable

                                            `</div>`+
                                            `<p class="mb-1 small text-muted text-truncate" title="Exercici: ${exerciseTitle}">Exercici: ${exerciseTitle}</p>`+ // <-- Usar variable
                                            `<small class="text-muted">${new Date(alert.fecha_realizacion+'T12:00:00').toLocaleDateString('ca-ES')}</small>`+
                                         `</a>`+
                                         // --- INICIO BOTÓN CHAT (Con clase de ocultación) ---
                                         `<button class="btn btn-sm btn-outline-primary quick-feedback-btn border-0 rounded-circle accion-suplantacion-oculta" style="width: 2.0rem; height: 2.0rem; line-height: 1; margin: 0 0.2rem 0 0.5rem;" data-patient-id="${alert.patient_id}" data-patient-name="${patientFullName}" data-exercise-title="${exerciseTitle}" title="Enviar feedback ràpid">`+
                                            `<i class="bi bi-chat-dots"></i>`+
                                         `</button>`+
                                         // --- FIN BOTÓN CHAT ---
                                         // --- INICIO BOTÓN DESCARTAR (Con clase de ocultación) ---
                                         `<button type="button" class="btn btn-sm btn-outline-secondary border-0 dismiss-alert-btn rounded-circle accion-suplantacion-oculta" data-evolucion-id="${alert.id}" title="Marcar com atesa" style="width: 2.0rem; height: 2.0rem; margin: 0 0.5rem 0 0.2rem; line-height: 1;">`+ // <-- Ajuste de margen
                                            `<i class="bi bi-check-lg"></i>`+
                                         `</button>`+
                                         // --- FIN BOTÓN DESCARTAR ---
                                        `</div></div>`;
                        // ===== FIN MODIFICACIÓN =====
                        // ★ FIN MODIFICACIÓN 2
                        urgentAlertsContainer.append(itemHtml);
                    });
                } else {
                    urgentAlertsContainer.html('<div class="list-group-item text-muted small p-3 text-center">Cap alerta recent.</div>');
                }
                // Tratamientos finalizando
                urgentEndingContainer.empty();
                if (urgent_actions.ending_treatments && urgent_actions.ending_treatments.length > 0) {
                    urgent_actions.ending_treatments.forEach(treat => {
                        const today = new Date(); today.setHours(0, 0, 0, 0);
                        const parts = treat.end_date.split('-');
                        const endDate = new Date(parts[0], parts[1] - 1, parts[2]); endDate.setHours(0, 0, 0, 0);
                        const diffTime = endDate.getTime() - today.getTime();
                        const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24));
                        let dateBadgeHtml = '';
                        // (Botones ya reducidos en cambio anterior)
                        if (diffDays <= 0) { dateBadgeHtml = '<span class="badge bg-danger" style="font-size: 0.7rem;">Finalitza HUI</span>'; }
                        else if (diffDays === 1) { dateBadgeHtml = '<span class="badge bg-warning text-dark" style="font-size: 0.7rem;">Finalitza DEMÀ</span>'; }
                        else { dateBadgeHtml = `<span class="badge bg-secondary" style="font-size: 0.7rem;">Finalitza en ${diffDays} dies</span>`; }

                        // --- ★ INICIO MODIFICACIÓN (Botón 'Finalitzar' eliminado) ★ ---
                        // ===== INICIO MODIFICACIÓN (Botón +7 DIES AHORA USA LA CLASE DE OCULTACIÓN) =====
                        const itemHtml = `<div class="list-group-item p-2" data-treatment-id="${treat.id}"><div class="d-flex w-100 justify-content-between align-items-center"><a href="fitxa_pacient.php?id=${treat.patient_id}" class="text-decoration-none text-dark flex-grow-1" style="min-width:0;"><h6 class="mb-1 text-truncate" title="${escapeHtml(treat.patient_apellido)}, ${escapeHtml(treat.patient_nombre)}">${escapeHtml(treat.patient_apellido)}, ${escapeHtml(treat.patient_nombre)}</h6></a><div class="text-nowrap ps-2">${dateBadgeHtml} </div></div><p class="mb-1 small text-muted text-truncate" title="Tractament: ${escapeHtml(treat.title)}">Tractament: ${escapeHtml(treat.title)}</p><div class="mt-2 text-end"><button class="btn btn-sm btn-outline-primary extend-treatment-btn accion-suplantacion-oculta" data-treatment-id="${treat.id}" title="Ampliar 7 dies" style="font-size: 0.7rem; padding: 0.1rem 0.3rem;">+7 dies <i class="bi bi-chevron-right"></i></button></div></div>`;
                        // ===== FIN MODIFICACIÓN =====
                        // --- ★ FIN MODIFICACIÓN ---

                        urgentEndingContainer.append(itemHtml);
                    });
                } else {
                    urgentEndingContainer.html('<div class="list-group-item text-muted small p-3 text-center">Cap tractament finalitza pròximament.</div>');
                }
            }

        // --- ★★★ INICIO: RENDERIZADO DE ACTIVIDAD (CORREGIDO DEFINITIVAMENTE) ★★★ ---

        // --- ★★★ INICIO: RENDERIZADO DE ACTIVIDAD (CORREGIDO DEFINITIVAMENTE) ★★★ ---
                    function renderActivityFeed(feed) {
                        activityFeedContainer.empty();
                        if (feed.recent_activity && feed.recent_activity.length > 0) {
                            feed.recent_activity.forEach(act => {
                                const dateFormatted = formatRelativeTime(act.fecha_realizacion, act.hora_realizacion);
                                const isToday = !dateFormatted.includes('/') && !dateFormatted.includes('Ahir');
                                const todayBadge = isToday ? '<span class="badge bg-success-subtle text-success-emphasis" style="font-size: 0.65rem; vertical-align: middle; margin-left: 5px;">Hui</span>' : '';
                                let badgesHtml = '';

                                // 1. OBTENER VALORES (Si son NULL o undefined, serán null)
                                let dolorVal = (act.dolor_percibido !== null && act.dolor_percibido !== undefined) ? parseInt(act.dolor_percibido) : null;
                                let esfuerzoVal = (act.esfuerzo_percibido !== null && act.esfuerzo_percibido !== undefined) ? parseInt(act.esfuerzo_percibido) : null;

                                // 2. GENERAR BADGES DE DOLOR
                                if (dolorVal !== null && dolorVal >= 0) {
                                    let dolorClass = 'bg-success-subtle text-success-emphasis'; // 0-1
                                    if (dolorVal >= 2 && dolorVal <= 3) dolorClass = 'bg-warning-subtle text-warning-emphasis'; // 2-3
                                    if (dolorVal >= 4) dolorClass = 'bg-danger-subtle text-danger-emphasis'; // 4+
                                    badgesHtml += `<span class="badge ${dolorClass} ms-2" title="Dolor: ${dolorVal}/5">D: ${dolorVal}</span>`;
                                }

                                // 3. GENERAR BADGES DE ESFUERZO
                                if (esfuerzoVal !== null && esfuerzoVal >= 0) {
                                    let esforClass = 'bg-info-subtle text-info-emphasis'; // 0-1
                                    if (esfuerzoVal >= 2 && esfuerzoVal <= 3) esforClass = 'bg-primary-subtle text-primary-emphasis'; // 2-3
                                    if (esfuerzoVal >= 4) esforClass = 'bg-danger-subtle text-danger-emphasis'; // 4+
                                    badgesHtml += `<span class="badge ${esforClass} ms-1" title="Esforç: ${esfuerzoVal}/5">E: ${esfuerzoVal}</span>`;
                                }

                                // 4. GENERAR FILA
                                const patientFullName = `${escapeHtml(act.patient_nombre)} ${escapeHtml(act.patient_apellido)}`;
                                const exerciseTitle = escapeHtml(act.exercise_title);
                                // ===== INICIO MODIFICACIÓN (Ocultar botón chat) =====
                                // La clase 'accion-suplantacion-oculta' hace el trabajo

                                // ★★★ INICIO MODIFICACIÓN (Añadir &evo_date=) ★★★
                                const itemHtml = `<div class="list-group-item p-2"><a href="fitxa_pacient.php?id=${act.patient_id}&action=open_evo&treatment_id=${act.treatment_id}&evo_date=${act.fecha_realizacion}" class="text-decoration-none text-dark d-block"><div class="d-flex w-100 justify-content-between"><h6 class="mb-1 text-truncate" title="${patientFullName}">${patientFullName}${todayBadge}</h6><small class="text-muted text-nowrap">${dateFormatted}</small></div></a><div class="d-flex justify-content-between align-items-center mt-1"><p class="mb-0 small text-muted text-truncate" title="Ha completat: ${exerciseTitle}">Ha completat: ${exerciseTitle}</p><div class="text-nowrap d-flex align-items-center">${badgesHtml}<button class="btn btn-sm btn-outline-primary quick-feedback-btn border-0 rounded-circle ms-2 accion-suplantacion-oculta" style="width: 2.0rem; height: 2.0rem;" data-patient-id="${act.patient_id}" data-patient-name="${patientFullName}" data-exercise-title="${exerciseTitle}" title="Enviar feedback ràpid"><i class="bi bi-chat-dots"></i></button></div></div></div>`;
                                // ★★★ FIN MODIFICACIÓN ★★★

                                // ===== FIN MODIFICACIÓN =====
                                activityFeedContainer.append(itemHtml);
                            });
                        } else {
                            activityFeedContainer.html('<div class="list-group-item text-muted small p-3 text-center">Cap activitat recent.</div>');
                        }

                        // Mensajes
                        // ★ MODIFICACIÓN: Si es read-only, la API devuelve 0, ocultando el widget.
                        feedUnreadMsgCount.text(feed.unread_message_count || 0);
                        $('#dashboard-unread-messages .list-group-item:not(a)').remove();
                        if (feed.unread_message_count === 0) {
                             // Si el contenedor de mensajes existe (porque no es vista global), pero el count es 0, mostrar mensaje.
                             if ($('#dashboard-unread-messages').length > 0) {
                                $('#dashboard-unread-messages').find('a').before('<div class="list-group-item text-muted small p-3 text-center">Cap missatge nou.</div>');
                             }
                        }


                        // Notificaciones (EL BLOQUE MODIFICADO)
                        // ★ MODIFICACIÓN: Si es read-only, la API devuelve [], ocultando el widget.
                        recentNotificationsContainer.empty();
                        if (feed.recent_notifications && feed.recent_notifications.length > 0) {
                            feed.recent_notifications.forEach(notif => {
                                const dateFormatted = new Date(notif.fecha_creacion.replace(' ', 'T') + 'Z').toLocaleDateString('ca-ES');
                                const isUnread = notif.leido_en === null;
                                const itemClass = isUnread ? 'fw-bold' : 'text-body-secondary';
                                const newBadge = isUnread ? '<span class="badge bg-primary rounded-pill" style="font-size: 0.6rem; vertical-align: middle; margin-left: 6px;">Nova</span>' : '';

                                // --- CAMBIO 1: Añadido 'border-0' a ambos botones ---
                                // ===== INICIO MODIFICACIÓN (Ocultar botones si se suplanta) =====
                                let buttonsHtml = '';
                                // Usamos la variable JS 'isReadOnlyView' definida al inicio del $(document).ready
                                if (!isReadOnlyView) {
                                    buttonsHtml = `
                                        <div class="notification-actions ms-2 d-flex flex-row align-items-center">
                                            ${isUnread ?
                                            `<button class="btn btn-sm btn-outline-primary border-0 mark-read-btn rounded-circle"
                                                     data-id="${notif.id}"
                                                     title="Marcar com a llegit"
                                                     style="width: 2.0rem; height: 2.0rem; line-height: 1;">
                                                <i class="bi bi-check-lg"></i>
                                            </button>` : ''}

                                            <button class="btn btn-sm btn-outline-danger border-0 delete-permanently-btn rounded-circle ${isUnread ? 'ms-1' : ''}"
                                                     data-id="${notif.id}"
                                                     title="Eliminar permanentment"
                                                     style="width: 2.0rem; height: 2.0rem; line-height: 1;">
                                                <i class="bi bi-trash"></i>
                                            </button>
                                        </div>
                                    `;
                                }
                                // ===== FIN MODIFICACIÓN =====

                                // ★★★ MODIFICACIÓN: (Notificaciones) El enlace notif.url_destino ya viene bien de la BBDD ★★★
                                const itemHtml = `
                                    <div class="list-group-item d-flex justify-content-between align-items-center ${isUnread ? '' : 'notification-read'}"
                                         style="${(notif.tipo_evento === 'ALERTA_DOLOR' || notif.tipo_evento === 'ALERTA_ESFUERZO' || notif.tipo_evento === 'feedback_urgent') ? 'background-color: #fdeeee;' : ''}">

                                        <a href="${notif.url_destino || '#!'}" class="text-decoration-none text-dark flex-grow-1 notification-link" data-id="${notif.id}" style="min-width: 0;">
                                            <div class="d-flex w-100 align-items-center">
                                                <p class="mb-0 small text-truncate notification-message ${itemClass}" title="${escapeHtml(notif.mensaje)}">
                                                    ${escapeHtml(notif.mensaje)}
                                                </p>
                                                ${newBadge}
                                            </div>
                                            <small class="notification-time ${isUnread ? 'text-muted' : ''}">${dateFormatted}</small>
                                        </a>

                                        ${buttonsHtml} </div>`;
                                recentNotificationsContainer.append(itemHtml);
                            });
                        } else {
                             // Si el contenedor de notificaciones existe (porque no es vista global), pero está vacío, mostrar mensaje.
                            if ($('#dashboard-recent-notifications').length > 0) {
                                recentNotificationsContainer.html('<div class="list-group-item text-muted small p-3 text-center">Cap notificació recent.</div>');
                            }
                        }
                    }
                    // --- ★★★ FIN: RENDERIZADO DE ACTIVIDAD (CORREGIDO DEFINITIVAMENTE) ★★★ ---

            // --- ★★★ FIN: RENDERIZADO DE ACTIVIDAD (CORREGIDO DEFINITIVAMENTE) ★★★ ---
    // ======================================================= -->
    // ============ INICIO: MODIFICACIÓN "EL MEU RESUM" (CON VIEW_MODE)
    // ======================================================= -->
    function renderPersonalSummary(summary, view_mode) {
        // Mi Contenido
        myContentContainer.empty();

        // ★ Determinar el parámetro de filtro para los enlaces
        const filter_param = (view_mode === 'personal') ? '?filter=mine' : '';

        if (summary.counts) {

            // 1. Pacients creats
            const patient_text = (view_mode === 'personal') ? 'Pacients creats' : 'Total Pacients';
            myContentContainer.append(`<a href="users.php${filter_param}" class="list-group-item d-flex justify-content-between align-items-center">${patient_text}<span class="badge bg-secondary rounded-pill">${summary.counts.patients_registered || 0}</span></a>`);

            // 2. Medis (Suma)
            const media_text = (view_mode === 'personal') ? 'Medis (videos i imatges)' : 'Total Medis';
            const videos = parseInt(summary.counts.videos_uploaded) || 0;
            const images = parseInt(summary.counts.images_uploaded) || 0;
            const totalMedia = videos + images;
            myContentContainer.append(`<a href="gallery.php${filter_param}" class="list-group-item d-flex justify-content-between align-items-center">${media_text}<span class="badge bg-info rounded-pill">${totalMedia}</span></a>`);

            // 3. Exercicis creats
            const exercise_text = (view_mode === 'personal') ? 'Exercicis creats' : 'Total Exercicis';
            myContentContainer.append(`<a href="exercises.php${filter_param}" class="list-group-item d-flex justify-content-between align-items-center">${exercise_text}<span class="badge bg-primary rounded-pill">${summary.counts.exercises_created || 0}</span></a>`);

            // 4. Protocols creats
            const protocol_text = (view_mode === 'personal') ? 'Protocols creats' : 'Total Protocols';
            myContentContainer.append(`<a href="protocols.php${filter_param}" class="list-group-item d-flex justify-content-between align-items-center">${protocol_text}<span class="badge bg-primary rounded-pill">${summary.counts.protocols_created || 0}</span></a>`);

            // 5. Tractaments propis / Total Tractaments
            const treatment_text = (view_mode === 'personal') ? 'Tractaments propis' : 'Total Tractaments';
            myContentContainer.append(`<a href="treatments.php${filter_param}" class="list-group-item d-flex justify-content-between align-items-center">${treatment_text}<span class="badge bg-primary rounded-pill">${summary.counts.treatments_created || 0}</span></a>`);

        } else {
            myContentContainer.html('<div class="list-group-item text-muted small p-3 text-center">Error carregant dades.</div>');
        }

        // Gráfico (LÓGICA ORIGINAL - SIN CAMBIOS)
        treatmentChartSpinner.hide();
        if (summary.treatment_stats) {
            const stats = summary.treatment_stats;
            const ctx = document.getElementById('treatmentStatusChart').getContext('2d');
            if (treatmentChartInstance) { treatmentChartInstance.destroy(); }
            const colors = { warning: '#ffc107', info: '#0dcaf0', success: '#198754', secondary: '#6c757d' };
            const total = (stats.en_curs || 0) + (stats.programat || 0) + (stats.completat || 0) + (stats.omes || 0);
            if (total === 0) {
                 treatmentChartSpinner.html('No hi ha dades de tractaments.').show().removeClass('text-muted').addClass('text-info');
                 return;
            }
            treatmentChartInstance = new Chart(ctx, {
                type: 'doughnut',
                data: {
                    labels: ['En Curs', 'Programats', 'Completats', 'Omesos'],
                    datasets: [{
                        label: 'Tractaments',
                        data: [ stats.en_curs || 0, stats.programat || 0, stats.completat || 0, stats.omes || 0 ],
                        backgroundColor: [ colors.warning, colors.info, colors.success, colors.secondary ],
                        borderColor: '#fff', borderWidth: 2
                    }]
                },
                options: {
                    responsive: true, maintainAspectRatio: false,
                    cutout: '50%', // <-- CAMBIADO DE '60%' para hacerlo más grueso
                    plugins: {
                        legend: { position: 'bottom', labels: { padding: 15, boxWidth: 12, font: { size: 11 } } },
                        tooltip: { callbacks: { label: function(context) { let label = context.label || ''; if (label) { label += ': '; } if (context.parsed !== null) { label += context.parsed; } return label; } } }
                    }
                }
            });
        } else {
            treatmentChartSpinner.html('Error carregant estadístiques.').show().removeClass('text-muted').addClass('text-danger');
        }
    }
    // ======================================================= -->
    // ============ FIN: MODIFICACIÓN "EL MEU RESUM" =========== -->
    // ======================================================= -->


    // --- Carga de datos principal (MODIFICADA) ---
    function loadDashboardData() {
        announcementsContainer.html('<p class="text-center p-3"><span class="spinner-border spinner-border-sm"></span> Carregant anuncis...</p>');
        kpiActivePatients.text('...'); kpiEndingSoon.text('...'); kpiRecentAlerts.text('...'); kpiTotalEnCurs.text('...');
        urgentAlertsContainer.html('<div class="list-group-item text-center p-3 text-muted">Carregant alertes...</div>');
        urgentEndingContainer.html('<div class="list-group-item text-center p-3 text-muted">Carregant tractaments...</div>');
        activityFeedContainer.html('<div class="list-group-item text-center p-3 text-muted">Carregant activitat...</div>');
        recentNotificationsContainer.html('<div class="list-group-item text-center p-3 text-muted">Carregant notificacions...</div>');
        myContentContainer.html('<div class="list-group-item text-center p-3 text-muted">Carregant contingut...</div>');
        if (treatmentChartInstance) { treatmentChartInstance.destroy(); treatmentChartInstance = null; }
        treatmentChartSpinner.html('Carregant estadístiques...').show().removeClass('text-danger text-info').addClass('text-muted');
        $('#dashboard-my-content a.open-summary-modal-link').addClass('disabled').attr('aria-disabled', 'true');
        $('#dashboard-my-treatments a.list-group-item-action').addClass('disabled').attr('aria-disabled', 'true');

        // [MYSQL CORREGIDO] Usar APP_BASE_URL para llamadas AJAX
        $.getJSON(APP_BASE_URL + 'dashboard.php', { ajax: true, action: 'get_dashboard_data' }).done(response => {
            if (response.status === 'success' && response.data) {
                const data = response.data;
                const view_mode = response.view_mode || 'personal'; // ★ LEER EL VIEW_MODE

                // ★ ACTUALIZAR TÍTULOS
                if (view_mode === 'global') {
                    $('#kpis-col h5').html('<i class="bi bi-graph-up me-2 text-success"></i> Indicadors Globals');
                    $('.dashboard-section:eq(0) h5').html('<i class="bi bi-exclamation-triangle-fill me-2 text-danger"></i> Accions Urgents (Global)');
                    $('.dashboard-section:eq(1) h5').html('<i class="bi bi-activity me-2 text-info"></i> Feed d\'Activitat (Global)');
                    $('.dashboard-section:eq(2) h5').html('<i class="bi bi-person-workspace me-2 text-success"></i> Resum Global');
                    $('.dashboard-section:eq(2) h6:first').html('<i class="bi bi-folder-fill me-1"></i> Contingut de la Plataforma');
                    $('.dashboard-section:eq(2) h6:last').html('<i class="bi bi-clipboard2-data-fill me-1"></i> Estat de Tots els Tractaments');
                } else {
                    // Restaurar títulos personales (por si acaso)
                    $('#kpis-col h5').html('<i class="bi bi-graph-up me-2 text-success"></i> Indicadors Clau');
                    $('.dashboard-section:eq(0) h5').html('<i class="bi bi-exclamation-triangle-fill me-2 text-danger"></i> Accions Urgents');
                    $('.dashboard-section:eq(1) h5').html('<i class="bi bi-activity me-2 text-info"></i> Feed d\'Activitat');
                    $('.dashboard-section:eq(2) h5').html('<i class="bi bi-person-workspace me-2 text-success"></i> El Meu Resum');
                    $('.dashboard-section:eq(2) h6:first').html('<i class="bi bi-folder-fill me-1"></i> El Meu Contingut');
                    $('.dashboard-section:eq(2) h6:last').html('<i class="bi bi-clipboard2-data-fill me-1"></i> Estat dels Meus Tractaments');
                }

                renderAnnouncements(data.announcements);
                renderKPIs(data.kpis);
                renderUrgentActions(data.urgent_actions);
                renderActivityFeed(data.activity_feed);

                // ★ PASAR EL VIEW_MODE A LA FUNCIÓN DE RENDERIZADO
                renderPersonalSummary(data.personal_summary, view_mode);

                $('#dashboard-my-content a.open-summary-modal-link').removeClass('disabled').removeAttr('aria-disabled');
                $('#dashboard-my-treatments a.list-group-item-action').removeClass('disabled').removeAttr('aria-disabled');
                if (typeof bootstrap !== 'undefined' && typeof bootstrap.Tooltip !== 'undefined') {
                    new bootstrap.Tooltip(document.body, { selector: "[data-bs-toggle='tooltip']" });
                }
            } else {
                $('main.main-content').prepend('<div class="alert alert-danger">Error crític al carregar les dades del panell.</div>');
                $('#dashboard-announcements, #dashboard-kpis, #dashboard-urgent-alerts .list-group, #dashboard-urgent-ending .list-group, #dashboard-activity-feed .list-group, #dashboard-recent-notifications .list-group, #dashboard-my-content .list-group, #dashboard-my-treatments .chart-loading-spinner').html('<p class="text-danger small text-center">Error</p>');
            }
        }).fail(xhr => {
            $('main.main-content').prepend('<div class="alert alert-danger">Error de connexió al carregar il panell.</div>');
            $('#dashboard-announcements, #dashboard-kpis, #dashboard-urgent-alerts .list-group, #dashboard-urgent-ending .list-group, #dashboard-activity-feed .list-group, #dashboard-recent-notifications .list-group, #dashboard-my-content .list-group, #dashboard-my-treatments .chart-loading-spinner').html('<p class="text-danger small text-center">Error</p>');
        });
    }

    // --- Listener cierre de anuncio (SIN CAMBIOS) ---
    $(document).on('close.bs.alert', '#dashboard-announcements .alert', function (event) {
        const anuncioId = $(this).data('anuncio-id');
        if (anuncioId) {
            // [MYSQL CORREGIDO] Usar APP_BASE_URL para llamadas AJAX
            $.post(APP_BASE_URL + 'dashboard.php', { ajax: true, action: 'mark_announcement_read', anuncio_id: anuncioId })
                .fail(function(xhr) { console.error("Error al marcar anuncio como visto:", xhr.responseText); });
        }
        setTimeout(updateTopRowLayout, 200);
    });

// --- Resumen Fisio (Función modificada para título simple) ---
    function loadFisioDetailedSummary(targetId = null, targetTabId = null) {
        // CAMBIO: Título fijo y simple
        const modalTitle = 'Càrrega de Treball';

        const fisioSummaryModalEl = document.getElementById('fisioSummaryModal');
        if (!fisioSummaryModalEl) {
            console.error("Modal #fisioSummaryModal not found.");
            return;
        }
        const fisioSummaryModalInstance = bootstrap.Modal.getOrCreateInstance(fisioSummaryModalEl);

        const params = {
            ajax: true,
            action: 'get_fisio_detailed_summary'
        };

        if (IS_SUPERADMIN && targetId && targetId !== 0 && targetId !== CURRENT_USER_ID) {
            params.target_id = targetId;
        }

        // CAMBIO: Asignamos el título directamente sin concatenar nada más
        $('#fisioSummaryModalLabel').text(modalTitle);

        $('#fisioSummaryModalBody').html('<div class="text-center p-5"><div class="spinner-border text-primary"></div><p class="mt-2">Carregant dades...</p></div>');
        fisioSummaryModalInstance.show();

        // [MYSQL CORREGIDO] Usar APP_BASE_URL para llamadas AJAX (reports_ajax.php)
        $.getJSON(APP_BASE_URL + 'reports_ajax.php', params).done(function(response) {
            if (response.status === 'success' && response.html) {
                $('#fisioSummaryModalBody').html(response.html);
                if (targetTabId) {
                    setTimeout(() => {
                        const tabEl = fisioSummaryModalEl.querySelector(`#${targetTabId}`);
                        if (tabEl) bootstrap.Tab.getOrCreateInstance(tabEl)?.show();
                        else {
                            const firstTab = fisioSummaryModalEl.querySelector('.nav-tabs .nav-link, .nav-pills .nav-link');
                            if (firstTab) bootstrap.Tab.getOrCreateInstance(firstTab)?.show();
                        }
                    }, 150);
                } else {
                    const firstTab = fisioSummaryModalEl.querySelector('.nav-tabs .nav-link, .nav-pills .nav-link');
                    if (firstTab && !fisioSummaryModalEl.querySelector('.nav-tabs .nav-link.active, .nav-pills .nav-link.active')) {
                        bootstrap.Tab.getOrCreateInstance(firstTab)?.show();
                    }
                }
            } else {
                $('#fisioSummaryModalBody').html('<div class="alert alert-warning">Error: ' + escapeHtml(response.message || 'Desconegut.') + '</div>');
            }
        }).fail(xhr => {
            $('#fisioSummaryModalBody').html('<div class="alert alert-danger">Error: ' + escapeHtml(xhr.responseJSON?.message || 'Connexió.') + '</div>');
        });
    }



    // --- ★ INICIO MODIFICACIÓN (Handler condicional del modal) --- (REVERTIDO)
    $(document).on('click', '.open-summary-modal-link', function(e) {
        e.preventDefault();
        if ($(this).hasClass('disabled')) return;

        // Comportamiento original: cargar resumen personal/del fisio
        const targetTab = $(this).data('target-tab');
        let targetUserId = $(this).data('fisio-id') || null;
        loadFisioDetailedSummary(targetUserId, targetTab);
    });
    // --- ★ FIN MODIFICACIÓN ★ --- (REVERTIDO)

    if (fisioSummaryModalEl) { fisioSummaryModalEl.addEventListener('hidden.bs.modal', () => { $('.dropdown-toggle[aria-expanded="true"]').dropdown('toggle'); }); }

    // --- ★★★ INICIO: HANDLERS DE ACCIONES RÁPIDAS (CORREGIDAS con APP_BASE_URL) ★★★ ---

    // 1. Descartar Alerta
    $(document).on('click', '.dismiss-alert-btn', function(e) {
        // ===== INICIO MODIFICACIÓN (Bloqueo por vista solo-lectura) =====
        if (isReadOnlyView) {
            e.preventDefault(); e.stopPropagation();
            showToast('Las acciones están deshabilitadas en esta vista.', 'warning');
            return;
        }
        // ===== FIN MODIFICACIÓN =====
        e.preventDefault(); e.stopPropagation();
        const $button = $(this);
        const $listItem = $button.closest('.list-group-item');
        const evolucionId = $button.data('evolucion-id');
        if (!evolucionId) return;

        if (!confirm("¿Heu donat resposta a aquesta acció urgent i voleu eliminar-la de la llista?")) { return; }

        $button.prop('disabled', true);

        $.post(APP_BASE_URL + 'dashboard.php', {
                          ajax: true,
                          action: 'dismiss_dashboard_alert',
                          evolucion_id: evolucionId
                      }, 'json')
                      .done(function(response) {
                          if (response.status === 'success') {
                              $listItem.addClass('bg-danger-subtle');
                              $listItem.fadeOut(500, function() {
                                  $(this).remove();
                                  if (urgentAlertsContainer.children().length === 0) {
                                      urgentAlertsContainer.html('<div class="list-group-item text-muted small p-3 text-center">Cap alerta recent.</div>');
                                  }
                              });
                              const currentKpiCount = parseInt(kpiRecentAlerts.text()) || 0;
                              const newKpiCount = Math.max(0, currentKpiCount - 1);
                              kpiRecentAlerts.text(newKpiCount);
                          } else {
                              showToast(response.message || 'Error en descartar.', 'danger');
                              $button.prop('disabled', false);
                          }
                      })
                      .fail(function(xhr) {
                          showToast(xhr.responseJSON?.message || 'Error de connexió.', 'danger');
                          $button.prop('disabled', false);
                      });
          });

          // 3. Ampliar +7 días
          $(document).on('click', '.extend-treatment-btn', function(e) {
              // ===== INICIO MODIFICACIÓN (Bloqueo por vista solo-lectura) =====
              // Esta acción AHORA se bloquea para coincidir con la clase CSS 'accion-suplantacion-oculta'
              if (isReadOnlyView) {
                  e.preventDefault(); e.stopPropagation();
                  showToast('Las acciones están deshabilitadas en esta vista.', 'warning');
                  return;
              }
              // ===== FIN MODIFICACIÓN =====
              e.preventDefault(); e.stopPropagation();
              const $button = $(this);
              const $listItem = $button.closest('.list-group-item');
              const treatmentId = $button.data('treatment-id');
              if (!treatmentId) return;

              if (!confirm("¿Segur que voleu ampliar la data de finalització d'aquest tractament 7 dies més?")) { return; }

              $button.prop('disabled', true).html('<span class="spinner-border spinner-border-sm"></span>');
              $button.siblings('button').prop('disabled', true);

              // ★ MODIFICADO: URL
              $.post(APP_BASE_URL + 'treatments.php', {
                  ajax: true,
                  action: 'extend_treatment_duration', // <-- La acción está en treatments.php
                  treatment_id: treatmentId,
                  days_to_extend: 7
              }, 'json')
              .done(function(response) {
                  if (response.status === 'success' && response.new_end_date) {
                      $listItem.addClass('bg-primary-subtle');
                      $listItem.fadeOut(500, function() {
                          $(this).remove();
                          if (urgentEndingContainer.children().length === 0) {
                              urgentEndingContainer.html('<div class="list-group-item text-muted small p-3 text-center">Cap tractament finalitza pròximament.</div>');
                          }
                      });
                      const currentKpiCount = parseInt(kpiEndingSoon.text()) || 0;
                      const newKpiCount = Math.max(0, currentKpiCount - 1);
                      kpiEndingSoon.text(newKpiCount);
                      showToast(`Tractament ampliat fins al ${response.new_end_date}`, 'success');
                  } else {
                      showToast(response.message || 'Error en ampliar.', 'danger');
                      $button.prop('disabled', false).html('+7 dies <i class="bi bi-chevron-right"></i>');
                      $button.siblings('button').prop('disabled', false);
                  }
              })
              .fail(function(xhr) {
                  showToast(xhr.responseJSON?.message || 'Error de connexió.', 'danger');
                  $button.prop('disabled', false).html('+7 dies <i class="bi bi-chevron-right"></i>');
                  $button.siblings('button').prop('disabled', false);
              });
          });

          // 4. Feedback Rápido (Chat)
          let feedbackModalInstance = null;
          $(document).on('click', '.quick-feedback-btn', function(e) {
              // ===== INICIO MODIFICACIÓN (Bloqueo por vista solo-lectura) =====
              if (isReadOnlyView) {
                  e.preventDefault(); e.stopPropagation();
                  showToast('Las acciones están deshabilitadas en esta vista.', 'warning');
                  return;
              }
              // ===== FIN MODIFICACIÓN =====
              e.preventDefault(); e.stopPropagation();
              const $button = $(this);
              const patientId = $button.data('patient-id');
              const patientName = $button.data('patient-name');
              const exerciseTitle = $button.data('exercise-title');
              $('#feedbackPatientName').text(patientName);
              $('#feedbackExerciseContext').text(`Sobre el ejercicio: "${exerciseTitle}"`);
              $('#feedbackMessage').val('');
              $('#sendFeedbackBtn').data('patient-id', patientId);
              $('#sendFeedbackBtn').data('exercise-title', exerciseTitle);
              $('#feedbackMessage').removeClass('is-invalid');
              const feedbackModalEl = document.getElementById('feedbackModal');
              if (!feedbackModalInstance) {
                  feedbackModalInstance = new bootstrap.Modal(feedbackModalEl);
              }
              feedbackModalInstance.show();
              setTimeout(() => { $('#feedbackMessage').focus(); }, 500);
          });
          $(document).on('click', '#sendFeedbackBtn', function() {
              // ===== INICIO MODIFICACIÓN (Bloqueo por vista solo-lectura) =====
              if (isReadOnlyView) {
                  showToast('Las acciones están deshabilitadas en esta vista.', 'warning');
                  return;
              }
              // ===== FIN MODIFICACIÓN =====
              const $button = $(this);
              const patientId = $button.data('patient-id');
              const exerciseTitle = $button.data('exercise-title');
              const userMessage = $('#feedbackMessage').val().trim();
              if (userMessage.length < 2) {
                  $('#feedbackMessage').addClass('is-invalid');
                  showToast('El mensaje es demasiado corto.', 'warning');
                  return;
              }
              const finalMessageBody = `Sobre el ejercicio "${exerciseTitle}": \n${userMessage}`;
              $('#feedbackMessage').removeClass('is-invalid');
              $button.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-2"></span>Enviando...');

              // ★ MODIFICADO: URL
              $.post(APP_BASE_URL + 'chat_inbox.php', {
                  ajax: true,
                  action: 'send_quick_feedback', // <-- La acción está en chat_inbox.php
                  to_patient_id: patientId,
                  message_body: finalMessageBody
              }, 'json')
              .done(function(response) {
                  if (response.status === 'success') {
                      showToast('Mensaje enviado correctamente.', 'success');
                      if (feedbackModalInstance) {
                          feedbackModalInstance.hide();
                      }
                  } else {
                      showToast(response.message || 'Error al enviar el mensaje.', 'danger');
                  }
              })
              .fail(function(xhr) {
                  showToast(xhr.responseJSON?.message || 'Error de conexión.', 'danger');
              })
              .always(function() {
                  $button.prop('disabled', false).html('<i class="bi bi-send-fill me-1"></i> Enviar');
              });
          });
          $('#feedbackModal').on('hidden.bs.modal', function () {
              $('#feedbackMessage').val('');
              $('#feedbackPatientName').text('...');
              $('#feedbackExerciseContext').text('Sobre el ejercicio: ...');
              $('#sendFeedbackBtn').data('patient-id', '');
              $('#sendFeedbackBtn').data('exercise-title', '');
              $('#feedbackMessage').removeClass('is-invalid');
          });

          // --- ★★★ INICIO: HANDLERS DE NOTIFICACIÓN CORREGIDOS con APP_BASE_URL ★★★ ---

          // Handler para marcar como leída la notificación (tick)
          $(document).on('click', '#dashboard-recent-notifications .mark-read-btn', function(e) {
              // ===== INICIO MODIFICACIÓN (Bloqueo por vista solo-lectura) =====
              if (isReadOnlyView) {
                  e.preventDefault(); e.stopPropagation();
                  return;
              }
              // ===== FIN MODIFICACIÓN =====
              e.preventDefault();
              e.stopPropagation();
              const $button = $(this);
              const $listItem = $button.closest('.list-group-item');
              const notifId = $button.data('id');
              if (!notifId) return;

              if (!confirm("¿Segur que voleu marcar aquesta notificació com a llegida?")) { return; }

              $button.prop('disabled', true);

              // CORREGIDO: URL y Acción
              $.post(APP_BASE_URL + 'notifications_ajax.php', {
                  action: 'delete_notification',
                  id: notifId
              }, 'json')
              .done(function(response) {
                  if (response.status === 'success') {
                      // Marcar visualmente como leída
                      $listItem.removeClass('notification-unread').addClass('notification-read');
                      $listItem.find('.notification-message').removeClass('fw-bold').addClass('text-body-secondary');
                      $listItem.find('.notification-time').addClass('text-muted');
                      $listItem.find('.badge').remove();
                      $button.remove(); // Quitar el botón de tick
                  } else {
                      showToast(response.message || 'Error al marcar com a llegida.', 'danger');
                      $button.prop('disabled', false);
                  }
              })
              .fail(function(xhr) {
                  showToast(xhr.responseJSON?.message || 'Error de connexió.', 'danger');
                  $button.prop('disabled', false);
              });
          });

          // Handler para eliminar permanentemente la notificación (papelera)
          $(document).on('click', '#dashboard-recent-notifications .delete-permanently-btn', function(e) {
              // ===== INICIO MODIFICACIÓN (Bloqueo por vista solo-lectura) =====
              if (isReadOnlyView) {
                  e.preventDefault(); e.stopPropagation();
                  return;
              }
              // ===== FIN MODIFICACIÓN =====
              e.preventDefault();
              e.stopPropagation();
              const $button = $(this);
              const $listItem = $button.closest('.list-group-item');
              const notifId = $button.data('id');
              if (!notifId) return;

              if (confirm('¿Segur que voleu eliminar aquesta notificació permanentment?')) {
                  $button.prop('disabled', true);
                  $button.siblings('button').prop('disabled', true);

                  // CORREGIDO: URL y Acción
                  $.post(APP_BASE_URL + 'notifications_ajax.php', {
                      action: 'permanently_delete_notification',
                      id: notifId
                  }, 'json')
                  .done(function(response) {
                      if (response.status === 'success') {
                          $listItem.fadeOut(400, function() {
                              $(this).remove();
                              if (recentNotificationsContainer.children().length === 0) {
                                  recentNotificationsContainer.html('<div class="list-group-item text-muted small p-3 text-center">Cap notificació recent.</div>');
                              }
                          });
                      } else {
                          showToast(response.message || 'Error al eliminar.', 'danger');
                          $button.prop('disabled', false);
                          $button.siblings('button').prop('disabled', false);
                      }
                  })
                  .fail(function(xhr) {
                      showToast(xhr.responseJSON?.message || 'Error de connexió.', 'danger');
                      $button.prop('disabled', false);
                      $button.siblings('button').prop('disabled', false);
                  });
              }
          });

          // Handler para el clic en el enlace de la notificación (marcar como leída)
          $(document).on('click', '#dashboard-recent-notifications .notification-link', function(e) {
              // ===== INICIO MODIFICACIÓN (Bloqueo por vista solo-lectura) =====
              if (isReadOnlyView) {
                  return; // Permite el clic pero no ejecuta la acción de marcar como leído
              }
              // ===== FIN MODIFICACIÓN =====
              const $link = $(this);
              const notifId = $link.data('id');
              if (!notifId) return;

              // CORREGIDO: URL y Acción
              $.post(APP_BASE_URL + 'notifications_ajax.php', {
                  action: 'delete_notification',
                  id: notifId
              }, 'json');

              // Permitir que el enlace continúe con su acción (navegar)
          });
          // --- ★★★ FIN: HANDLERS DE NOTIFICACIÓN CORREGIDOS ★★★ ---


          // Carga inicial
          loadDashboardData();
      });
      </script>

      </body>
      </html>
