$(document).ready(function() {
    // --- ESTADO GLOBAL Y UTILS ---
    // Acceder al ID del paciente desde la constante global definida en el PHP
    const PATIENT_ID = typeof GLOBAL_PATIENT_ID !== 'undefined' ? GLOBAL_PATIENT_ID : null;

    // --- ★ INICIO CORRECCIÓN DE RUTA ★ ---
    // Acceder a la URL base definida en el PHP
    const BASE_URL = typeof APP_BASE_URL !== 'undefined' ? APP_BASE_URL : '';
    if (!PATIENT_ID) {
    // --- ★ FIN CORRECCIÓN DE RUTA ★ ---
        console.error("Error crítico: No se pudo obtener el ID del paciente.");
        $('#alerts-container').prepend('<div class="alert alert-danger">Error crític: No s\'ha pogut identificar l\'usuari. Recarrega la pàgina.</div>');
        return; // Detener la ejecución si no hay ID
    }

    const DAY_NAMES = ['DG', 'DL', 'DM', 'DX', 'DJ', 'DV', 'DS'];
    const MONTH_NAMES = ["Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"];
    const feedbackModal = new bootstrap.Modal(document.getElementById('feedbackModal'));
    // --- INICIO CORRECCIÓN 3: Eliminar 'profileModal' de esta línea ---
    const historyModalElement = document.getElementById('historyModal'); // Para listener show.bs.modal
    // --- FIN CORRECCIÓN 3 ---

    const historyLink = $('#history-menu-link');
    const reportLink = $('#report-menu-link');

    let activeTreatment = null;
    let allExercises = [];
    let fullEvolutionData = {};
    let evolutionDataForDay = {}; // Necesario para los anillos de progreso
    let currentDisplayDate = new Date();
    let selectedDateStr = new Date().toISOString().split('T')[0];
    let currentExerciseButton = null;
    let progressChartInstance = null;
    let exerciseToRateAfterViewing = null;

    // --- FUNCIONES AUXILIARES ---
    function escapeHtml(text) {
        if (typeof text !== 'string') return '';
        const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
        return text.replace(/[&<>"']/g, m => map[m]);
    }
    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');
    }

    // --- LÓGICA PARA ANUNCIOS (MODIFICADA) ---
    let blockingAnnouncementModal_Patient = null; // Variable local para el modal
    function renderAnnouncements(announcements, container) {
        container.empty(); // Limpiar contenedor

        // 1. Buscar si hay un anuncio de bloqueo
        // (Asegura que 'a.is_blocking' viene del AJAX 'get_my_announcements')
        const blockingAnnouncement = announcements.find(a => a.is_blocking == 1);

        if (blockingAnnouncement) {
            // --- Lógica de Modal de Bloqueo (copiada de dashboard.php) ---
            const modalEl = document.getElementById('blockingAnnouncementModal');
            if (!modalEl) {
                console.error("Modal de bloqueo no encontrado en el DOM (footer.php)");
                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';

            // Usar el nombre del remitente (que ya calculas en patient_functions.php)
            const remitenteNombre = blockingAnnouncement.remitente_nombre ? escapeHtml(blockingAnnouncement.remitente_nombre) : 'Administració';

            modalTitle.html(`<i class="bi ${iconClass} me-2"></i> ${escapeHtml(blockingAnnouncement.title)}`);
            modalBody.html(nl2br(escapeHtml(blockingAnnouncement.content)));
            modalButton.data('anuncio-id', blockingAnnouncement.id);
            modalButton.data('remitente-nombre', remitenteNombre); // Guardar nombre para el log

            if (!blockingAnnouncementModal_Patient) {
                blockingAnnouncementModal_Patient = new bootstrap.Modal(modalEl, { backdrop: 'static', keyboard: false });
            }
            blockingAnnouncementModal_Patient.show();

            // Dejar un placeholder en el dashboard
            container.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) {
            // --- Lógica Antigua (si no hay bloqueo) ---
            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';

                // Usar el nombre del remitente (que ya calculas en patient_functions.php)
                const remitenteNombre = a.remitente_nombre ? escapeHtml(a.remitente_nombre) : 'Administració';

                const announcementHtml = `
                    <div class="alert ${alertClass} alert-dismissible fade show mb-2" role="alert" data-anuncio-id="${a.id}">
                        <div class="d-flex justify-content-between align-items-start">
                            <h6 class="alert-heading mb-0" style="font-size: 1.05rem;">
                                <i class="bi ${iconClass} me-2"></i> ${escapeHtml(a.title)}
                            </h6>
                            <small class="text-muted fst-italic ms-2" style="white-space: nowrap; font-size: 0.8rem;">
                                Enviat per: <strong>${remitenteNombre}</strong>
                            </small>
                        </div>
                        <p class="mb-0 mt-2 small">${nl2br(escapeHtml(a.content))}</p>
                        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                    </div>`;
                container.append(announcementHtml);
            });
        } else {
             container.html(''); // Dejar vacío si no hay anuncios
        }
    }
    // --- FIN LÓGICA ANUNCIOS (MODIFICADA) ---

    function loadAndDisplayAnnouncements() {
        const announcementsContainer = $('#dashboard-announcements');
        if (!announcementsContainer.length) return;
        // Usar patient_ajax.php según la refactorización
        // --- ★ CORRECCIÓN DE RUTA ★ ---
        $.getJSON(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'get_my_announcements' })
            .done(response => {
                if (response.status === 'success') {
                    renderAnnouncements(response.announcements, announcementsContainer);
                } else { console.warn("No s'han pogut carregar els anuncis."); announcementsContainer.empty(); }
            })
            .fail(() => { console.error("Error de xarxa carregant anuncis."); announcementsContainer.empty(); });
    }
    // Listener para marcar como leído (usa patient_ajax.php)
    $(document).on('close.bs.alert', '#dashboard-announcements .alert', function (event) {
        const anuncioId = $(this).data('anuncio-id');
        if (anuncioId) {
            // --- ★ CORRECCIÓN DE RUTA ★ ---
            $.post(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'mark_announcement_read', anuncio_id: anuncioId })
                .fail(function(xhr) { console.error("Error al marcar anunci com a vist:", xhr.responseText); });
        }
    });

    // --- LÓGICA PARA BANNER AZUL DINÁMICO (Ejercicios Hoy) ---
    function updateDailyExercisesBanner(dateStrToUpdate) {
        const isToday = dateStrToUpdate === new Date().toISOString().split('T')[0];
        const banner = $('#daily-exercises-banner'); // Seleccionar por ID

        if (!banner.length) return; // Salir si el banner no existe en el DOM

        // Ocultar siempre si la fecha seleccionada no es hoy
        if (!isToday) {
            banner.addClass('d-none').removeClass('show');
            return;
        }

        // Obtener ejercicios programados para hoy (usa la función existente)
        const scheduledToday = getScheduledExercisesForDay(dateStrToUpdate);
        // Contar los completados buscando el texto "Registrat" en la lista actual
        const completedTodayCount = $('#daily-exercises-container .text-success.fw-bold:contains("Registrat")').length;
        const remainingExercises = scheduledToday.length - completedTodayCount;

        console.log(`Updating banner: Scheduled=${scheduledToday.length}, Completed=${completedTodayCount}, Remaining=${remainingExercises}`); // Para depuración

        if (scheduledToday.length > 0) {
            if (remainingExercises > 0) {
                // Actualizar texto y mostrar (o mantener visible) el banner azul/info
                const text = `Tens <strong id="daily-exercises-count">${remainingExercises}</strong> exercici${remainingExercises > 1 ? 's' : ''} programat${remainingExercises > 1 ? 's' : ''} per a hui.`;
                // Reconstruir HTML manteniendo el botón de cierre original
                const closeButtonHtml = banner.find('.btn-close').prop('outerHTML') || '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>';
                banner.html(text + closeButtonHtml);
                banner.removeClass('d-none alert-warning alert-success').addClass('alert-info show');
            } else {
                // Todos completados: mostrar mensaje de éxito verde y cerrar automáticamente
                const closeButtonHtml = banner.find('.btn-close').prop('outerHTML') || '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>';
                banner.html(`👏 Tots els exercicis de hui completats!` + closeButtonHtml);
                banner.removeClass('d-none alert-warning alert-info').addClass('alert-success show');
                 // Programar cierre automático después de 3 segundos
                 setTimeout(() => {
                    // Comprobar si el banner todavía existe y es visible antes de intentar cerrarlo
                    if ($('#daily-exercises-banner').length > 0 && $('#daily-exercises-banner').hasClass('show')) {
                         // Usar la instancia de Bootstrap para cerrar correctamente (evita problemas si ya se cerró manually)
                         bootstrap.Alert.getOrCreateInstance(banner[0]).close();
                    }
                 }, 3000); // 3 segundos
            }
        } else {
            // Hoy sin ejercicios: Ocultar el banner azul (podríamos mostrar otro si quisiéramos)
             banner.addClass('d-none').removeClass('show');
             // Opcional: Mostrar un banner de descanso si no hay ejercicios hoy
             // $('#alerts-container').append('<div class="alert alert-secondary...">Hui tens descans!</div>');
        }
    }
    // --- FIN LÓGICA BANNER AZUL ---


    // --- CONTROL DE MENÚS ---
    function disableAllPatientMenus() { historyLink.addClass('menu-disabled'); reportLink.addClass('menu-disabled'); }
    function enableActiveMenus() { historyLink.removeClass('menu-disabled'); reportLink.removeClass('menu-disabled'); }
    // --- ★ CORRECCIÓN DE RUTA ★ ---
    function checkHistoryStatus() { disableAllPatientMenus(); $.getJSON(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'check_history_exists' }) .done(response => { if (response.status === 'success' && response.exists) { historyLink.removeClass('menu-disabled'); reportLink.removeClass('menu-disabled'); } }); }

    // --- LÓGICA DE DATOS (AJAX - usa patient_ajax.php) ---
    // --- ★ CORRECCIÓN DE RUTA ★ ---
    function fetchActiveTreatmentData() { disableAllPatientMenus(); $('#widgets-container').html('<div class="col-12 text-center p-4"><div class="spinner-border spinner-border-sm text-secondary"></div></div>'); $.getJSON(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'get_active_treatment_data' }) .done(response => { if (response.status === 'success' && response.active_treatments && response.active_treatments.length > 0) { const treatments = response.active_treatments; const selector = $('#treatmentSelector'); selector.empty(); treatments.forEach(t => { selector.append(new Option(t.title, t.id)); }); enableActiveMenus(); $('#treatment-selector-container').toggle(treatments.length > 1); fetchTreatmentDetails(treatments[0].id); } else { showNoTreatmentMessage(); $('#treatment-selector-container').hide(); checkHistoryStatus(); } }).fail(() => { showNoTreatmentMessage('Error en carregar les teues dades.'); $('#treatment-selector-container').hide(); checkHistoryStatus(); }); }
    function updateEvolutionDataForDay() { evolutionDataForDay = {}; if (!fullEvolutionData) return; Object.keys(fullEvolutionData).forEach(te_id => { if(fullEvolutionData[te_id] && Array.isArray(fullEvolutionData[te_id])) { evolutionDataForDay[te_id] = fullEvolutionData[te_id] .filter(rec => rec && rec.fecha_realizacion) .map(rec => ({ fecha_realizacion: rec.fecha_realizacion })); } else { evolutionDataForDay[te_id] = []; } }); }
    function fetchTreatmentDetails(treatmentId) { $('#daily-exercises-container').html('<div class="text-center p-5"><div class="spinner-border text-primary"></div></div>'); $('#calendar-container').addClass('opacity-50'); $('#progressCollapse').collapse('hide'); $('#progress-bars-container').empty(); $.when(
        // --- ★ CORRECCIÓN DE RUTA ★ ---
        $.getJSON(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'get_treatment_details', treatment_id: treatmentId }),
        $.getJSON(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'get_full_evolution', treatment_id: treatmentId, exercise_id: 0 })
    ).done(function(detailsResponse, evolutionResponse) { if (detailsResponse[0].status === 'success' && evolutionResponse[0].status === 'success') { activeTreatment = detailsResponse[0].treatment; allExercises = detailsResponse[0].exercises; fullEvolutionData = evolutionResponse[0].evolution || {}; const widgetsContainer = $('#widgets-container'); widgetsContainer.empty(); if (detailsResponse[0].widgets_html && detailsResponse[0].widgets_html.trim() !== '') { widgetsContainer.html(detailsResponse[0].widgets_html); } else { widgetsContainer.html('<div class="col-12"><p class="text-warning text-center small">No s\'ha pogut carregar el resum del progrés.</p></div>'); console.warn("widgets_html recibido vacío."); } updateEvolutionDataForDay(); if (Object.keys(fullEvolutionData).length === 0) { const welcomeAlert = `<div class="alert alert-info alert-dismissible fade show" role="alert" id="welcome-alert"><h5 class="alert-heading">👋 Benvingut/da al teu tractament!</h5><p>Aquest és el teu portal personal. Per a començar:</p><ol class="mb-0 small"><li>Fes clic en <strong>"Realitzar exercici"</strong> per veure el vídeo o imatge.</li><li>Quan acabis, torna i <strong>valora la teva sessió</strong> (dolor i esforç).</li><li>Això és tot! El teu fisio rebrà les teves dades.</li></ol><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>`; $('#alerts-container').prepend(welcomeAlert); }
            // --- INICIO DE LA CORRECCIÓN ---
            refreshDashboardComponents(); // <-- AQUÍ ESTABA EL ERROR. Renombrar la función.
            // --- FIN DE LA CORRECCIÓN ---
            $('#main-title').text(activeTreatment.title || 'El Meu Portal'); document.title = activeTreatment.title ? `${activeTreatment.title} - AVANT ONLINE` : 'AVANT ONLINE'; const exerciseFilter = $('#chart-exercise-filter'); exerciseFilter.html('<option value="0">Evolució General</option>'); allExercises.forEach(ex => { exerciseFilter.append(new Option(ex.title, ex.ejercicio_id)); }); $('#main-content-card, #exercises-title, #daily-exercises-container, #calendar-container').removeClass('d-none'); $('#calendar-container').addClass('d-flex'); enableActiveMenus(); renderTreatmentInfo(); updateActiveView(); } else { let errorMsg = detailsResponse[0].message || evolutionResponse[0].message || 'Error en carregar les dades.'; showNoTreatmentMessage(errorMsg); } }).fail(function(jqXHR, textStatus, errorThrown) { console.error("Error en AJAX (detalles/evolución):", textStatus, errorThrown); showNoTreatmentMessage('Error de connexió.'); }) .always(function() { $('#calendar-container').removeClass('opacity-50'); }); }
    // --- ★ CORRECCIÓN DE RUTA ★ ---
    function fetchAndRenderChart() { const selectedExerciseId = $('#chart-exercise-filter').val(); if (!activeTreatment) return; $.getJSON(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'get_full_evolution', treatment_id: activeTreatment.id, exercise_id: selectedExerciseId }) .done(response => { if(response.status === 'success') { fullEvolutionData = response.evolution || {}; renderProgressCharts(); } else { showToast('Error al carregar gràfic.', 'danger'); } }).fail(() => { showToast('Error de connexió al carregar gràfic.', 'danger'); }); }

    // --- RENDERIZADO DE LA UI ---
    function renderTreatmentInfo() { if (!activeTreatment) { $('#treatment-info').empty(); return; } const startDate = new Date(activeTreatment.start_date + 'T12:00:00').toLocaleDateString('ca-ES'); const endDate = new Date(activeTreatment.end_date + 'T12:00:00').toLocaleDateString('ca-ES'); $('#treatment-info').html( `<p class="text-muted mb-0 small">Pauta activa del <strong>${startDate}</strong> al <strong>${endDate}</strong>.</p>` ); }
    function getScheduledExercisesForDay(dateStr) { if (!activeTreatment || !allExercises) { return []; } const dateStrSimple = dateStr.split('T')[0]; const startStrSimple = activeTreatment.start_date.split('T')[0]; const endStrSimple = activeTreatment.end_date.split('T')[0]; if (dateStrSimple < startStrSimple || dateStrSimple > endStrSimple) { return []; } const selectedDate = new Date(dateStr + 'T12:00:00'); const dayOfWeek = selectedDate.getDay(); const startDate = new Date(activeTreatment.start_date + 'T12:00:00'); const scheduledExercises = allExercises.filter(ex => { const freq = ex.frecuencia; if (!freq || freq === 'Diari') return true; if (freq === '3xSetmana') return [1, 3, 5].includes(dayOfWeek); if (freq === '2xSetmana') return [2, 4].includes(dayOfWeek); if (freq === 'Altern') { const timeDiff = selectedDate.getTime() - startDate.getTime(); const dayDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); return dayDiff >= 0 && dayDiff % 2 === 0; } return true; }); return scheduledExercises; }
    function createProgressRingSVG(percentage, radius, strokeWidth) { const normalizedRadius = radius - strokeWidth / 2; const circumference = normalizedRadius * 2 * Math.PI; const offset = circumference - (percentage / 100) * circumference; const colorClass = percentage === 100 ? 'progress-ring__circle--complete' : ''; const viewBoxSize = radius * 2; return `<svg class="progress-ring" viewBox="0 0 ${viewBoxSize} ${viewBoxSize}"><circle class="progress-ring__background" stroke-width="${strokeWidth}" r="${normalizedRadius}" cx="${radius}" cy="${radius}"/><circle class="progress-ring__circle ${colorClass}" stroke-width="${strokeWidth}" stroke-dasharray="${circumference} ${circumference}" stroke-dashoffset="${offset}" r="${normalizedRadius}" cx="${radius}" cy="${radius}"/></svg>`; }

// --- ★★★ INICIO MODIFICACIÓN: RENDER CALENDARIO (REDUCIDO Y RESPONSIVE) ★★★ ---
    function renderCalendar(date) {
        const carousel = document.getElementById('day-carousel');
        carousel.innerHTML = '';

        const currentDayOfWeek = date.getDay();
        // Ajuste para que la semana empiece en Lunes (1), no en Domingo (0)
        const diff = date.getDate() - (currentDayOfWeek === 0 ? 6 : currentDayOfWeek - 1); 
        const startOfWeek = new Date(date);
        startOfWeek.setDate(diff);

        // --- ★ LÓGICA RESPONSIVE (AJUSTADA: Anillo más fino y radio ligeramente mayor) ★ ---
        const isMobile = window.innerWidth < 768;
        
        // Móvil: Radio 20 (ancho total ~40px). Escritorio: Radio 40 (ancho total ~80px).
        const weeklyRadius = isMobile ? 40 : 40; 
        // CAMBIO AQUÍ: Bajamos a 1.5 para un aspecto más fino y aireado en móvil
        const weeklyStrokeWidth = isMobile ? 8 : 15; 

        for (let i = 0; i < 7; i++) {
            const day = new Date(startOfWeek);
            day.setDate(startOfWeek.getDate() + i);
            const dateStr = day.toISOString().split('T')[0];
            const isSelected = dateStr === selectedDateStr;
            const scheduledExercises = getScheduledExercisesForDay(dateStr);

            let progressIndicator = '';
            let cardClasses = `day-card mx-1 p-2 text-center rounded-3 flex-grow-1`;
            let percentage = 0;

            if (scheduledExercises.length > 0) {
                const completedCount = scheduledExercises.filter(ex => {
                    return evolutionDataForDay && Array.isArray(evolutionDataForDay[ex.id]) && evolutionDataForDay[ex.id].some(rec => rec.fecha_realizacion === dateStr);
                }).length;
                percentage = Math.round((completedCount / scheduledExercises.length) * 100);
                progressIndicator = createProgressRingSVG(percentage, weeklyRadius, weeklyStrokeWidth);
            } else {
                const todayStr = new Date().toISOString().split('T')[0];
                if (dateStr !== todayStr) {
                    cardClasses += ' is-empty';
                }
                progressIndicator = createProgressRingSVG(0, weeklyRadius, weeklyStrokeWidth);
            }

            if (isSelected) cardClasses += ' active';
            
            // NOTE: Quitamos 'mx-1 p-2' si es móvil, pero Bootstrap flex-grow lo gestiona.
            // Para ser estrictos y si 'mx-1 p-2' causa problemas en móvil, se haría:
            // if (isMobile) cardClasses = cardClasses.replace('mx-1 p-2', ''); 


            const card = document.createElement('div');
            card.className = cardClasses;
            card.dataset.date = dateStr;

            // --- ★★★ NUEVO CAMBIO DE ESTRUCTURA HTML (Día fuera del círculo) ★★★ ---
            // El nombre del día (DL, DM, etc.) va FUERA del contenedor del anillo
            card.innerHTML = `
                <div class="day-info day-name-top fw-bold">${DAY_NAMES[day.getDay()]}</div> <div class="progress-ring-container">
                    ${progressIndicator}
                    <div class="day-info day-number-inner">
                        <div class="day-number">${day.getDate()}</div>
                    </div>
                </div>`;
            // --- ★★★ FIN CAMBIO DE ESTRUCTURA ★★★ ---

            carousel.appendChild(card);
        }

        document.getElementById('month-display').textContent = MONTH_NAMES[startOfWeek.getMonth()] + ' ' + startOfWeek.getFullYear();
        loadDailyExercises(selectedDateStr);
    }
    // --- ★★★ FIN MODIFICACIÓN: RENDER CALENDARIO (REDUCIDO Y RESPONSIVE) ★★★ ---

    function loadDailyExercises(dateStr) {
        selectedDateStr = dateStr;
        const container = document.getElementById('daily-exercises-container');
        const isToday = dateStr === new Date().toISOString().split('T')[0];
        const dateDisplay = isToday ? 'Hui' : new Date(dateStr + 'T12:00:00').toLocaleDateString('ca-ES', { weekday: 'long', day: 'numeric', month: 'long' });

        document.getElementById('exercises-title').textContent = `Exercicis per a ${dateDisplay}`;

        const filteredExercises = getScheduledExercisesForDay(dateStr);
        const completedToday = new Set();
        if (fullEvolutionData && typeof fullEvolutionData === 'object') {
             Object.keys(fullEvolutionData).forEach(te_id => {
                 const records = fullEvolutionData[te_id];
                 if (records && Array.isArray(records) && records.some(rec => rec.fecha_realizacion === dateStr)) {
                     completedToday.add(parseInt(te_id));
                 }
             });
        }

        // --- Generar HTML de la lista ---
        if (filteredExercises.length > 0) {
            container.innerHTML = `<ul class="list-group list-group-flush">${filteredExercises.map(ex => {
                const isCompleted = completedToday.has(ex.id);
                let buttonHtml = '';
                if (isCompleted) {
                    buttonHtml = `<div class="d-flex gap-2 justify-content-end align-items-center"><span class="text-success fw-bold"><i class="bi bi-check-circle-fill me-2"></i>Registrat</span><button class="btn btn-sm btn-outline-secondary valorar-btn" data-te-id="${ex.id}" data-title="${escapeHtml(ex.title)}"><i class="bi bi-pencil-fill me-1"></i>Modificar</button></div>`;
                } else {
                    buttonHtml = `<div class="d-flex justify-content-end"><button class="btn btn-sm btn-success perform-exercise-btn" data-bs-toggle="modal" data-bs-target="#exerciseModal" data-te-id="${ex.id}" data-title="${escapeHtml(ex.title)}" data-video="${escapeHtml(ex.video_filename || '')}" data-image="${escapeHtml(ex.image_filename || '')}" data-explanation="${escapeHtml(ex.exercise_general_notes || '')}" data-frequency="${escapeHtml(ex.frecuencia)}" data-series="${escapeHtml(ex.series)}" data-repetitions="${escapeHtml(ex.repetitions)}" data-rest="${escapeHtml(ex.rest_time)}" data-pauta-notes="${escapeHtml(ex.notes || '')}"><i class="bi bi-play-circle-fill me-1"></i>Realitzar exercici</button></div>`;
                }
                 return `<li class="list-group-item p-3" id="exercise-item-${ex.id}"><div class="row align-items-center"><div class="col-lg-5 mb-3 mb-lg-0"><strong>${escapeHtml(ex.title)}</strong><small class="d-block text-muted">${escapeHtml(ex.frecuencia)} | ${escapeHtml(ex.series)} series | ${escapeHtml(ex.repetitions)} reps | ${escapeHtml(ex.rest_time)} descans</small></div><div class="col-lg-7">${buttonHtml}</div></div></li>`;
            }).join('')}</ul>`;
        } else {
            let message = "No hi ha exercicis programats per a este dia.";
            if (activeTreatment) {
                const dateStrSimple = dateStr.split('T')[0];
                const startStrSimple = activeTreatment.start_date.split('T')[0];
                const endStrSimple = activeTreatment.end_date.split('T')[0];
                if (dateStrSimple < startStrSimple || dateStrSimple > endStrSimple) {
                    message = "Fora del rang del tractament.";
                } else {
                    message = "Hui tens descans. Bon treball!";
                }
            }
            container.innerHTML = `<p class="text-muted text-center p-4">${message}</p>`;
        }

        // --- LLAMADA FINAL PARA ACTUALIZAR EL BANNER ---
        updateDailyExercisesBanner(dateStr); // <-- Llamada a la función del banner azul
    }
    function renderMonthCalendar(date) { const wrapper = $('#month-calendar-wrapper'); wrapper.html(''); const month = date.getMonth(); const year = date.getFullYear(); const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const lastDayDate = lastDay.getDate(); let startDayOfWeek = (firstDay.getDay() + 6) % 7; const monthlyRadius = 20; const monthlyStrokeWidth = 4; let html = '<table class="table table-bordered text-center month-calendar"><thead><tr class="small text-muted"><th>DL</th><th>DM</th><th>DX</th><th>DJ</th><th>DV</th><th>DS</th><th>DG</th></tr></thead><tbody><tr>'; for (let i = 0; i < startDayOfWeek; i++) { html += '<td class="is-empty"></td>'; } const todayStr = new Date().toISOString().split('T')[0]; for (let day = 1; day <= lastDayDate; day++) { const dateStr = `${year}-${(month + 1).toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`; const isToday = dateStr === todayStr; let classes = 'day-cell'; let progressIndicator = ''; const scheduledExercises = getScheduledExercisesForDay(dateStr); let percentage = 0; if (scheduledExercises.length > 0) { const completedCount = scheduledExercises.filter(ex => { return evolutionDataForDay && Array.isArray(evolutionDataForDay[ex.id]) && evolutionDataForDay[ex.id].some(rec => rec.fecha_realizacion === dateStr); }).length; percentage = Math.round((completedCount / scheduledExercises.length) * 100); progressIndicator = createProgressRingSVG(percentage, monthlyRadius, monthlyStrokeWidth); } else { if (dateStr !== todayStr) { classes += ' is-empty'; } progressIndicator = createProgressRingSVG(0, monthlyRadius, monthlyStrokeWidth); } if (dateStr === selectedDateStr) classes += ' active'; if (isToday) classes += ' today'; html += `<td class="${classes}" data-date="${dateStr}"><div>${progressIndicator}<span class="day-number-month">${day}</span></div></td>`; if ((day + startDayOfWeek) % 7 === 0 && day < lastDayDate) { html += '</tr><tr>'; } } const remainingCells = (7 - ((lastDayDate + startDayOfWeek) % 7)) % 7; for (let i = 0; i < remainingCells; i++) { html += '<td class="is-empty"></td>'; } html += '</tr></tbody></table>'; wrapper.html(html); loadDailyExercises(selectedDateStr); }

    // --- GRÁFICOS ---

    // ==================================================================
    // ========= INICIO DE LA CORRECCIÓN DEL GRÁFICO (BUGS 1 y 2) =======
    // ==================================================================
    function prepareChartData() {
        const labels = [];
        const painData = [];
        const effortData = [];
        const commentsList = [];
        if (!activeTreatment || !fullEvolutionData || Object.keys(fullEvolutionData).length === 0) {
            console.warn("prepareChartData: Faltan datos.");
            return { labels, datasets: [], comments: [] };
        }

        const dataByDate = {};
        const exerciseList = allExercises || [];

        Object.keys(fullEvolutionData).forEach(te_id_str => {
            const te_id = parseInt(te_id_str, 10);
            const exerciseInfo = exerciseList.find(ex => ex && ex.id == te_id); // Get info once

            if(fullEvolutionData[te_id] && Array.isArray(fullEvolutionData[te_id])) {

                // BUCLE forEach(record) REEMPLAZADO (FIX BUG 2)
                fullEvolutionData[te_id].forEach(record => {
                    const date = record.fecha_realizacion;
                    // Si no hay fecha, saltar este registro
                    if (!date) return;

                    // 1. Asegurarse de que el día existe en dataByDate (¡Esto es lo que faltaba!)
                    if (!dataByDate[date]) {
                        dataByDate[date] = { painSum: 0, painCount: 0, effortSum: 0, effortCount: 0, comments: [] };
                    }

                    // 2. Obtener dolor y esfuerzo (convertir a null si no es numérico)
                    const dolor = (record.dolor_percibido !== null && !isNaN(record.dolor_percibido)) ? Number(record.dolor_percibido) : null;
                    const esfuerzo = (record.esfuerzo_percibido !== null && !isNaN(record.esfuerzo_percibido)) ? Number(record.esfuerzo_percibido) : null;

                    // 3. Sumar valores SÓLO SI son numéricos (no nulos)
                    if (dolor !== null) {
                        dataByDate[date].painSum += dolor;
                        dataByDate[date].painCount++;
                    }
                    if (esfuerzo !== null) {
                        dataByDate[date].effortSum += esfuerzo;
                        dataByDate[date].effortCount++;
                    }

                    // 4. Añadir comentarios (ahora funciona aunque dolor/esfuerzo sean nulos)
                    if (record.comentarios && record.comentarios.trim() !== '') {
                        let title = exerciseInfo ? exerciseInfo.title : `Ex. ID ${te_id}`; // Use info from above
                        dataByDate[date].comments.push({ title: title, text: record.comentarios });
                    }
                });
                // FIN DEL BUCLE REEMPLAZADO
            }
        });

        try {
            const startDate = new Date(activeTreatment.start_date + 'T12:00:00');
            const endDateTreatment = new Date(activeTreatment.end_date + 'T12:00:00');

            // --- INICIO CORRECCIÓN BUG 1 (Eje X) ---
            // El gráfico debe mostrar el eje X completo del tratamiento,
            // no detenerse en "today".
            const endDate = endDateTreatment;
            // --- FIN CORRECCIÓN BUG 1 ---

            let currentDate = new Date(startDate);
            while (currentDate <= endDate) {
                const dateStr = currentDate.toISOString().split('T')[0];
                labels.push(dateStr);

                // Esta lógica ahora funciona porque dataByDate PUEDE existir
                // solo con comentarios, pero painCount/effortCount serán 0.
                if (dataByDate[dateStr]) {
                    painData.push(dataByDate[dateStr].painCount > 0 ? (dataByDate[dateStr].painSum / dataByDate[dateStr].painCount) : null);
                    effortData.push(dataByDate[dateStr].effortCount > 0 ? (dataByDate[dateStr].effortSum / dataByDate[dateStr].effortCount) : null);
                    if(dataByDate[dateStr].comments.length > 0) {
                        commentsList.push({ date: dateStr, comments: dataByDate[dateStr].comments });
                    }
                } else {
                    // Este 'else' se ejecutará para los días futuros del tratamiento
                    // que aún no tienen ningún registro (ni siquiera de comentario).
                    painData.push(null);
                    effortData.push(null);
                }
                currentDate.setDate(currentDate.getDate() + 1);
            }
        } catch (dateError) {
            console.error("Error procesando fechas para gráfico:", dateError);
            return { labels: [], datasets: [], comments: [] };
        }

        return {
            labels,
            datasets: [
                { label: 'Dolor Mitjà (0-5)', data: painData, borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.5)', tension: 0.1, spanGaps: true, yAxisID: 'y' },
                { label: 'Esforç Mitjà (0-5)', data: effortData, borderColor: 'rgb(54, 162, 235)', backgroundColor: 'rgba(54, 162, 235, 0.5)', tension: 0.1, spanGaps: true, yAxisID: 'y' }
            ],
            comments: commentsList.sort((a,b) => a.date.localeCompare(b.date))
        };
    }
    // ==================================================================
    // ========= FIN DE LA CORRECCIÓN DEL GRÁFICO =======================
    // ==================================================================

    function renderProgressCharts() { if (!$('#progressCollapse').hasClass('show')) { return; } const { labels, datasets, comments } = prepareChartData(); const ctx = document.getElementById('progressChart'); if (!ctx) { console.error("Canvas no encontrado."); return; } if (progressChartInstance) { progressChartInstance.destroy(); } const hasValidData = labels.length > 0 && datasets.length > 0 && datasets.some(ds => ds.data.some(val => val !== null && !isNaN(val))); if (!hasValidData) { ctx.getContext('2d').clearRect(0, 0, ctx.width, ctx.height); $('#chart-comments ul').html('<li>No hi ha dades d\'evolució per a este filtre.</li>'); progressChartInstance = null; console.warn("renderProgressCharts: No hay datos válidos."); return; } const chartOptions = { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, max: 5, ticks: { stepSize: 1 } }, x: { type: 'time', time: { unit: 'day', tooltipFormat: 'dd MMM yyyy', displayFormats: { day: 'dd MMM' } }, ticks: { maxTicksLimit: Math.min(15, labels.length), autoSkip: true, maxRotation: 0, minRotation: 0 } } }, plugins: { tooltip: { mode: 'index', intersect: false } }, interaction: { intersect: false, mode: 'index' } }; try { progressChartInstance = new Chart(ctx.getContext('2d'), { type: 'line', data: { labels: labels, datasets: datasets }, options: chartOptions }); } catch (e) { console.error("Error Chart.js:", e); showToast("Error al mostrar gráfico.", "danger"); } const commentsContainer = $('#chart-comments ul'); commentsContainer.empty(); if (comments.length > 0) { comments.forEach(entry => { const dateFormatted = new Date(entry.date + 'T12:00:00').toLocaleDateString('ca-ES', { year: 'numeric', month: 'short', day: 'numeric' }); entry.comments.forEach(comment => { commentsContainer.append(`<li><strong>${dateFormatted} (${escapeHtml(comment.title)}):</strong> ${escapeHtml(comment.text)}</li>`); }); }); } else { commentsContainer.html('<li>No hi ha comentaris per a este filtre.</li>'); } }
    function showNoTreatmentMessage(message = "Actualment no tens cap tractament actiu...", hideImminent = true) { $('#daily-exercises-banner').addClass('d-none').removeClass('show'); $('#alerts-container #alert-banner').removeClass('alert-info d-none').addClass('alert-warning show').html(message + '<button type="button" class="btn-close" data-bs-dismiss="alert"></button>'); $('#widgets-container').hide(); $('#main-content-card').addClass('d-none'); $('#exercises-title').removeClass('d-none').text('Exercicis'); $('#daily-exercises-container').removeClass('d-none').html('<p class="text-muted text-center p-5">'+message+'</p>'); $('#treatment-info, #calendar-container').hide(); $('#progress-bars-container').empty(); $('#progressCollapse').collapse('hide'); if (progressChartInstance) { progressChartInstance.destroy(); progressChartInstance = null; } activeTreatment = null; allExercises = []; fullEvolutionData = {}; evolutionDataForDay = {}; if (hideImminent) { $('#imminent-treatment-alert').hide(); } else { $('#imminent-treatment-alert').show(); } checkHistoryStatus(); }

    // --- MANEJADORES DE EVENTOS ---
    function updateActiveView() { if (!activeTreatment) { $('#calendar-container').addClass('d-none'); return; } $('#calendar-container').removeClass('d-none').addClass('d-flex'); const isMensual = $('.view-toggle-btn.active').text() === 'Mensual'; if (isMensual) { $('#week-carousel-container').addClass('d-none'); $('#month-calendar-wrapper').removeClass('d-none'); $('#month-display').text(MONTH_NAMES[currentDisplayDate.getMonth()] + ' ' + currentDisplayDate.getFullYear()); renderMonthCalendar(currentDisplayDate); } else { $('#week-carousel-container').removeClass('d-none').addClass('d-flex'); $('#month-calendar-wrapper').addClass('d-none'); renderCalendar(currentDisplayDate); } }
    $('#prev-week-btn').on('click', () => { currentDisplayDate.setDate(currentDisplayDate.getDate() - 7); if (!$('.view-toggle-btn.active').text().includes('Mensual')) { selectedDateStr = new Date(currentDisplayDate).toISOString().split('T')[0]; } updateActiveView(); });
    $('#next-week-btn').on('click', () => { currentDisplayDate.setDate(currentDisplayDate.getDate() + 7); if (!$('.view-toggle-btn.active').text().includes('Mensual')) { selectedDateStr = new Date(currentDisplayDate).toISOString().split('T')[0]; } updateActiveView(); });
    $('#prev-month-btn').on('click', () => { currentDisplayDate.setMonth(currentDisplayDate.getMonth() - 1); if ($('.view-toggle-btn.active').text().includes('Mensual')) { let firstDayOfMonth = new Date(currentDisplayDate.getFullYear(), currentDisplayDate.getMonth(), 1); selectedDateStr = firstDayOfMonth.toISOString().split('T')[0]; } else { selectedDateStr = new Date(currentDisplayDate).toISOString().split('T')[0]; } updateActiveView(); });
    $('#next-month-btn').on('click', () => { currentDisplayDate.setMonth(currentDisplayDate.getMonth() + 1); if ($('.view-toggle-btn.active').text().includes('Mensual')) { let firstDayOfMonth = new Date(currentDisplayDate.getFullYear(), currentDisplayDate.getMonth(), 1); selectedDateStr = firstDayOfMonth.toISOString().split('T')[0]; } else { selectedDateStr = new Date(currentDisplayDate).toISOString().split('T')[0]; } updateActiveView(); });
    $('#today-btn').on('click', () => { currentDisplayDate = new Date(); selectedDateStr = currentDisplayDate.toISOString().split('T')[0]; updateActiveView(); });
    $('#day-carousel').on('click', '.day-card:not(.is-empty)', (event) => { const card = event.target.closest('.day-card'); if (card && card.dataset.date !== selectedDateStr) { selectedDateStr = card.dataset.date; $('#day-carousel .day-card').removeClass('active'); card.classList.add('active'); loadDailyExercises(selectedDateStr); } });
    $('#month-calendar-wrapper').on('click', '.day-cell:not(.is-empty)', function() { const dateStr = $(this).data('date'); if (!dateStr || dateStr === selectedDateStr) return; selectedDateStr = dateStr; currentDisplayDate = new Date(dateStr + 'T12:00:00'); renderMonthCalendar(currentDisplayDate); });
    $('.view-toggle-btn').on('click', function() { if ($(this).hasClass('active')) return; $('.view-toggle-btn').removeClass('active'); $(this).addClass('active'); updateActiveView(); });

    // --- Feedback Modal ---
    $('#daily-exercises-container').on('click', '.valorar-btn', function() { currentExerciseButton = $(this); const teId = $(this).data('te-id'); const title = $(this).data('title'); const isModifying = $(this).closest('li').find('.text-success.fw-bold:contains("Registrat")').length > 0; $('#deleteFeedbackBtn').toggle(isModifying); $('#feedbackModalLabel').text(`${isModifying ? 'Modificar' : 'Valorar'}: ${title}`); $('#tratamientoEjercicioId').val(teId); if (isModifying && fullEvolutionData[teId]) { const recordForDay = fullEvolutionData[teId].find(rec => rec.fecha_realizacion === selectedDateStr); if (recordForDay) { $('#dolor_percibido').val(recordForDay.dolor_percibido); $('#esfuerzo_percibido').val(recordForDay.esfuerzo_percibido); $('#comentarios').val(recordForDay.comentarios); $('#rating-dolor i').each(function(index) { $(this).toggleClass('bi-star-fill', index < recordForDay.dolor_percibido).toggleClass('bi-star', index >= recordForDay.dolor_percibido); }); $('#rating-esfuerzo i').each(function(index) { $(this).toggleClass('bi-star-fill', index < recordForDay.esfuerzo_percibido).toggleClass('bi-star', index >= recordForDay.esfuerzo_percibido); }); } else { $('#feedbackForm')[0].reset(); $('#feedbackModal .star-rating i').removeClass('bi-star-fill').addClass('bi-star'); $('#dolor_percibido').val(0); $('#esfuerzo_percibido').val(0); } } else { $('#feedbackForm')[0].reset(); $('#feedbackModal .star-rating i').removeClass('bi-star-fill').addClass('bi-star'); $('#dolor_percibido').val(0); $('#esfuerzo_percibido').val(0); } feedbackModal.show(); });
    $('#feedbackModal').on('mouseover', '.star-rating i', function() { const ratingGroup = $(this).parent(); const value = $(this).data('value'); ratingGroup.children('i').each(function(index) { $(this).toggleClass('bi-star-fill', index < value).toggleClass('bi-star', index >= value); }); });
    $('#feedbackModal').on('mouseout', '.star-rating i', function() { const ratingGroup = $(this).parent(); const savedValue = ratingGroup.siblings('input[type="hidden"]').val(); ratingGroup.children('i').each(function(index) { $(this).toggleClass('bi-star-fill', index < savedValue).toggleClass('bi-star', index >= savedValue); }); });
    $('#feedbackModal').on('click', '.star-rating i', function() { const value = $(this).data('value'); $(this).parent().siblings('input[type="hidden"]').val(value); $(this).trigger('mouseout'); });

    // --- INICIO CORRECCIÓN 1: Actualizar Widgets al enviar feedback ---
    $('#feedbackForm').on('submit', function(e) {
        e.preventDefault();
        const teIdToUpdate = $('#tratamientoEjercicioId').val();
        const formDataObj = {
            dolor_percibido: parseInt($('#dolor_percibido').val(), 10) || 0,
            esfuerzo_percibido: parseInt($('#esfuerzo_percibido').val(), 10) || 0,
            comentarios: $('#comentarios').val(),
            fecha_realizacion: selectedDateStr
        };
        const formData = $(this).serialize() + '&ajax=true&action=save_exercise_feedback&fecha_realizacion=' + selectedDateStr;

        // --- ★ CORRECCIÓN DE RUTA ★ ---
        $.post(BASE_URL + 'patient_ajax.php', formData, function(response) {
            if (response.status === 'success') {
                feedbackModal.hide();
                showToast(response.message, 'success');

                // --- INICIO CORRECCIÓN 3: Asegurar el dropdown ---
                // Nos aseguramos de que el selector visual siga mostrando el tratamiento
                // que acabamos de valorar, usando la variable global 'activeTreatment'.
                if (activeTreatment && activeTreatment.id) {
                    $('#treatmentSelector').val(activeTreatment.id);
                }
                // --- FIN CORRECCIÓN 3 ---

                // 1. Actualizar datos locales (como antes)
                if (!fullEvolutionData[teIdToUpdate]) { fullEvolutionData[teIdToUpdate] = []; }
                const existingDayIndex = fullEvolutionData[teIdToUpdate].findIndex(rec => rec.fecha_realizacion === selectedDateStr);
                if (existingDayIndex !== -1) {
                    fullEvolutionData[teIdToUpdate][existingDayIndex] = { ...fullEvolutionData[teIdToUpdate][existingDayIndex], ...formDataObj };
                } else {
                    fullEvolutionData[teIdToUpdate].push(formDataObj);
                }
                updateEvolutionDataForDay();

                // 2. Usar los datos de la respuesta para actualizar los widgets y barras de progreso
                if (response.updated_data) {
                    const updatedData = response.updated_data;

                    // Actualizar KPIs/Widgets
                    const widgetsContainer = $('#widgets-container');
                    if (updatedData.widgets_html && updatedData.widgets_html.trim() !== '') {
                        widgetsContainer.html(updatedData.widgets_html);
                    } else {
                        console.warn("Respuesta de feedback no contenía widgets_html. Refrescando manualmente.");
                        // --- INICIO CORRECCIÓN 2: Renombrar función ---
                        refreshDashboardComponents(); // Llama a la función que refresca AMBOS
                        // --- FIN CORRECCIÓN 2 ---
                    }

                    // Actualizar Barras de Progreso (la función 'refreshProgressBars' es redundante ahora,
                    // pero la usamos para mantener la lógica de renderizado en un solo lugar)
                    if (updatedData.progress_percent_time !== undefined && updatedData.progress_percent_adherence !== undefined) {
                         renderProgressBars(updatedData.progress_percent_time, updatedData.progress_percent_adherence);
                    }

                } else {
                    // Fallback si updated_data no viene (comportamiento antiguo)
                    // --- INICIO CORRECCIÓN 2: Renombrar función ---
                    console.warn("Feedback guardado, pero updated_data vino nulo. Refrescando widgets y progreso manualmente.");
                    refreshDashboardComponents();
                    // --- FIN CORRECCIÓN 2 ---
                }

                // 3. Actualizar calendario y lista de ejercicios (como antes)
                loadDailyExercises(selectedDateStr);
                updateActiveView();

                // 4. Actualizar gráfico si está visible (como antes)
                if ($('#progressCollapse').hasClass('show')) {
                    fetchAndRenderChart();
                }

            } else {
                showToast(response.message || 'Error en guardar.', 'danger');
            }
        }, 'json').fail(() => {
            showToast('Error de connexió.', 'danger');
        });
    });
    // --- FIN CORRECCIÓN 1 ---


    // --- ★ CORRECCIÓN DE RUTA ★ ---
    $('#deleteFeedbackBtn').on('click', function() { const teIdToDelete = $('#tratamientoEjercicioId').val(); const dateToDelete = selectedDateStr; if (!teIdToDelete || !dateToDelete || !confirm('Estàs segur que vols eliminar...?')) return; $.post(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'delete_exercise_feedback', tratamiento_ejercicio_id: teIdToDelete, fecha_realizacion: dateToDelete }, function(response) { if (response.status === 'success') { feedbackModal.hide(); showToast(response.message, 'success'); if (fullEvolutionData[teIdToDelete]) { const indexToRemove = fullEvolutionData[teIdToDelete].findIndex(rec => rec.fecha_realizacion === dateToDelete); if (indexToRemove !== -1) { fullEvolutionData[teIdToDelete].splice(indexToRemove, 1); } } updateEvolutionDataForDay(); loadDailyExercises(selectedDateStr); updateActiveView();
    // --- INICIO CORRECCIÓN 2: Renombrar función de refresco ---
    refreshDashboardComponents();
    // --- FIN CORRECCIÓN 2 ---
    if ($('#progressCollapse').hasClass('show')) { fetchAndRenderChart(); } } else { showToast(response.message || 'Error en eliminar.', 'danger'); } }, 'json').fail(() => { showToast('Error de connexió en eliminar.', 'danger'); }); });

    // --- Actualizar Barras de Progreso ---
    // --- INICIO CORRECCIÓN 1 (Helper): Modificar refreshProgressBars para aceptar datos ---
    function renderProgressBars(progressTime, progressAdherence) {
         const progressBarsContainer = $('#progress-bars-container');
         progressBarsContainer.empty();
         let progressHtml = `
            <div class="flex-grow-1 d-flex flex-row gap-3">
                <div class="flex-grow-1">
                    <label class="small fw-bold mb-1" style="font-size: 0.8rem; color: #555;">Temps transcorregut</label>
                    <div class="progress" style="height: 20px;" title="Temps transcorregut (${progressTime}%)">
                        <div class="progress-bar bg-info progress-bar-striped" role="progressbar" style="width: ${progressTime}%;" aria-valuenow="${progressTime}" aria-valuemin="0" aria-valuemax="100">
                            <span class="fw-bold" style="font-size: 0.9rem;">${progressTime}%</span>
                        </div>
                    </div>
                </div>
                <div class="flex-grow-1">
                    <label class="small fw-bold mb-1" style="font-size: 0.8rem; color: #555;">Adherència (fins hui)</label>
                    <div class="progress" style="height: 20px;" title="Exercicis completats (${progressAdherence}%)">
                        <div class="progress-bar bg-success progress-bar-striped" role="progressbar" style="width: ${progressAdherence}%;" aria-valuenow="${progressAdherence}" aria-valuemin="0" aria-valuemax="100">
                            <span class="fw-bold" style="font-size: 0.9rem;">${progressAdherence}%</span>
                        </div>
                    </div>
                </div>
            </div>`;
         progressBarsContainer.html(progressHtml);
    }

    // --- INICIO CORRECCIÓN 2: Renombrar y ampliar la función de refresco ---
    // Esta función ahora refresca TANTO los widgets como las barras de progreso
    function refreshDashboardComponents() {
    // --- FIN CORRECCIÓN 2 ---
        if (!activeTreatment) return;
        // Esta función ahora solo hace el fetch, el renderizado lo hace renderProgressBars
        // --- ★ CORRECCIÓN DE RUTA ★ ---
        $.getJSON(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'get_treatment_details', treatment_id: activeTreatment.id })
            .done(function(detailsResponse) {
                if (detailsResponse.status === 'success') {

                    // --- INICIO CORRECCIÓN 2: Añadir actualización de widgets ---
                    const widgetsContainer = $('#widgets-container');
                    if (detailsResponse.widgets_html && detailsResponse.widgets_html.trim() !== '') {
                        widgetsContainer.html(detailsResponse.widgets_html);
                    } else {
                        widgetsContainer.html('<div class="col-12"><p class="text-warning text-center small">No s\'ha pogut carregar el resum del progrés.</p></div>');
                        console.warn("refreshDashboardComponents: widgets_html recibido vacío.");
                    }
                    // --- FIN CORRECCIÓN 2 ---

                    renderProgressBars(detailsResponse.progress_percent_time, detailsResponse.progress_percent_adherence);
                }
            }).fail(() => {
                showToast('Error en refrescar progrés.', 'warning');
                $('#progress-bars-container').html('<p class="small text-danger">Error</p>');
                // --- INICIO CORRECCIÓN 2: Añadir fallback de widgets ---
                $('#widgets-container').html('<div class="col-12"><p class="text-danger text-center small">Error en refrescar el resum.</p></div>');
                // --- FIN CORRECCIÓN 2 ---
            });
    }
    // --- FIN CORRECCIÓN 1 (Helper) ---

    // --- Perfil Modal (usa patient_ajax.php) ---
    // (Esta sección no existe en patient_dashboard.js, está en footer.php)

    // --- Selector Tratamiento ---
    $('#treatmentSelector').on('change', function() { const selectedTreatmentId = $(this).val(); if (selectedTreatmentId) { currentDisplayDate = new Date(); selectedDateStr = currentDisplayDate.toISOString().split('T')[0]; fetchTreatmentDetails(selectedTreatmentId); } });

    // --- Gráficos ---
    document.getElementById('progressCollapse').addEventListener('shown.bs.collapse', fetchAndRenderChart);
    $('#chart-exercise-filter').on('change', fetchAndRenderChart);

    // --- Historial Modal (usa patient_ajax.php) ---
    if (historyModalElement) { historyModalElement.addEventListener('show.bs.modal', function () { const historyModalBody = document.getElementById('historyModalBody'); historyModalBody.innerHTML = '<div class="text-center p-5"><div class="spinner-border text-primary"></div></div>';
    // --- ★ CORRECCIÓN DE RUTA ★ ---
    $.getJSON(BASE_URL + 'patient_ajax.php', { ajax: true, action: 'get_treatment_history' }) .done(response => { if (response.status === 'success' && response.history && response.history.length > 0) { let html = '<div class="accordion" id="historyAccordion">'; response.history.forEach((treatment, index) => { let statusBadge = ''; switch (treatment.status) { case 'Completat': statusBadge = '<span class="badge bg-success">Completat</span>'; break; case 'Omés': statusBadge = '<span class="badge bg-secondary">Omés</span>'; break; case 'Finalitzat': statusBadge = '<span class="badge bg-info text-dark">Finalitzat</span>'; break; default: statusBadge = `<span class="badge bg-light text-dark">${treatment.status}</span>`; } html += ` <div class="accordion-item"> <h2 class="accordion-header" id="heading${index}"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse${index}" aria-expanded="false" aria-controls="collapse${index}"> <div class="d-flex w-100 justify-content-between align-items-center pe-3"> <span class="fw-bold">${treatment.title}</span> <small class="text-muted">${treatment.start_date_formatted} - ${treatment.end_date_formatted}</small> ${statusBadge} </div> </button> </h2> <div id="collapse${index}" class="accordion-collapse collapse" aria-labelledby="heading${index}" data-bs-parent="#historyAccordion"> <div class="accordion-body small"> <p><strong>Percentatge de realització estimat:</strong> ${treatment.realizacion_percent_display}</p> <h6>Exercicis inclosos:</h6> ${treatment.exercises && treatment.exercises.length > 0 ? '<ul class="list-unstyled mb-0">' + treatment.exercises.map(ex => `<li>- ${ex.title} (${ex.series}s / ${ex.repetitions}r / ${ex.rest_time} desc)</li>`).join('') + '</ul>' : '<p class="text-muted">No hi ha exercicis.</p>'} </div> </div> </div> `; }); html += '</div>'; historyModalBody.innerHTML = html; } else if (response.status === 'success') { historyModalBody.innerHTML = '<p class="text-center text-muted p-4">No hi ha historial.</p>'; } else { historyModalBody.innerHTML = '<div class="alert alert-danger">Error carregant historial.</div>'; } }).fail(() => { historyModalBody.innerHTML = '<div class="alert alert-danger">Error de connexió (historial).</div>'; }); }); }

    // --- Modal Ejercicio ---
    const exerciseModalElement = document.getElementById('exerciseModal'); if(exerciseModalElement) { exerciseModalElement.addEventListener('show.bs.modal', function (event) { const button = event.relatedTarget; const modal = $(this); const data = button.dataset; modal.find('.modal-title').text(data.title); const mediaContainer = modal.find('#exercise-media-container'); mediaContainer.empty(); if (data.video && data.video.trim() !== '') { if (data.video.startsWith('http')) { mediaContainer.html(`<div class="ratio ratio-16x9" style="width: 100%;"><iframe src="${data.video}" allowfullscreen></iframe></div>`); } else { mediaContainer.html(`<video src="videos/${data.video}" controls controlsList="nodownload" class="modal-video" autoplay loop muted playsinline></video>`); } } else if (data.image && data.image.trim() !== '') { mediaContainer.html(`<img src="images/${data.image}" class="modal-image" alt="${data.title}">`); } else { mediaContainer.html('<div class="no-media-placeholder-modal"><i class="bi bi-camera-video-off"></i><p>Sense medis</p></div>'); } modal.find('#modal-exercise-title-details').text(data.title); modal.find('#modal-exercise-explanation').text(data.explanation || "Sense descripció.");

        // --- **** INICIO DE LA MODIFICACIÓN **** ---
        // Rellenar la nueva estructura de pauta clara
        modal.find('#modal-pauta-frecuencia').text(data.frequency || 'N/A');
        modal.find('#modal-pauta-series').text(data.series || 'N/A');
        modal.find('#modal-pauta-repetitions').text(data.repetitions || 'N/A');
        modal.find('#modal-pauta-rest').text(data.rest || 'N/A');

        // Ocultar el párrafo antiguo (ya no existe en el HTML nuevo, pero no hace daño)
        modal.find('#modal-exercise-pauta').hide();
        // --- **** FIN DE LA MODIFICACIÓN **** ---

        const notesContainer = modal.find('#modal-exercise-pauta-notes-container'); if (data.pautaNotes && data.pautaNotes.trim() !== '') { modal.find('#modal-exercise-pauta-notes').text(data.pautaNotes); notesContainer.removeClass('d-none'); } else { notesContainer.addClass('d-none'); } if (data.teId && data.title) { exerciseToRateAfterViewing = { teId: data.teId, title: data.title, buttonElement: $(button) }; } else { exerciseToRateAfterViewing = null; } }); exerciseModalElement.addEventListener('hidden.bs.modal', function () { const video = $(this).find('video')[0]; if (video) video.pause(); const iframe = $(this).find('iframe')[0]; if (iframe) { const src = iframe.src; iframe.src = src; } if (exerciseToRateAfterViewing) { const dataForFeedback = exerciseToRateAfterViewing; setTimeout(() => { const { teId, title, buttonElement } = dataForFeedback; if(buttonElement && buttonElement.closest('body').length && !buttonElement.prop('disabled')) { $('#feedbackModalLabel').text(`Valorar: ${title}`); $('#tratamientoEjercicioId').val(teId); $('#feedbackForm')[0].reset(); $('#feedbackModal .star-rating i').removeClass('bi-star-fill').addClass('bi-star'); $('#dolor_percibido').val(0); $('#esfuerzo_percibido').val(0); $('#deleteFeedbackBtn').hide(); feedbackModal.show(); currentExerciseButton = buttonElement; } else { console.log("Botón no disponible."); } }, 150); } exerciseToRateAfterViewing = null; }); }


    // --- *** INICIO: CORRECCIÓN DEFINITIVA MODAL DE BLOQUEO *** ---

    // 1. Apagamos CUALQUIER listener previo de clic en ese botón.
    // Esto previene que un script genérico (como de footer.php) se ejecute
    // y envíe el AJAX a 'dashboard.php' (lo cual causa el 404).
    $(document).off('click', '#confirmReadBlockingAnnouncementBtn');

    // 2. Añadimos NUESTRO listener específico, que apunta al lugar correcto.
    $(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...');

            // 3. Apuntar al AJAX correcto del paciente usando la URL base
            // --- ★ CORRECCIÓN DE RUTA ★ ---
            $.post(BASE_URL + 'patient_ajax.php', {
                ajax: true,
                action: 'mark_announcement_read',
                anuncio_id: anuncioId
            })
            .done(function() {
                if (blockingAnnouncementModal_Patient) {
                    blockingAnnouncementModal_Patient.hide();
                }
                loadAndDisplayAnnouncements(); // Recargar los anuncios normales
            })
            .fail(function(xhr) {
                console.error("Error al marcar anuncio como visto:", xhr.responseText);
                // Informar al usuario del error
                $btn.closest('.modal-body').prepend('<div class="alert alert-danger small">Error en marcar com a llegit. Tanca i torna a obrir.</div>');
            })
            .always(function() {
                $btn.prop('disabled', false).html('He llegit i entès');
            });
        }
    });
    // --- *** FIN: CORRECCIÓN DEFINITIVA MODAL DE BLOQUEO *** ---


    // --- INICIALIZACIÓN ---
    loadAndDisplayAnnouncements(); // Cargar anuncios
    fetchActiveTreatmentData(); // Carga todo lo demás (widgets, calendario, ejercicios, banner azul inicial)

}); // Fin document ready
