From a188c3bd8fde2447f0bdba4edd046ba70d52049e Mon Sep 17 00:00:00 2001 From: Carlos Tello Date: Wed, 23 Sep 2026 20:18:43 -0300 Subject: [PATCH] feat(settings): agregar opcion global para habilitar o deshabilitar saludo y selector de rol en dashboard --- CHANGELOG.md | 4 + backend/app/routes/api/admin.py | 48 ++++++++++- backend/app/routes/api/dashboard.py | 8 +- frontend/src/routes/admin.js | 4 + frontend/src/routes/dashboard.js | 6 ++ .../views/admin/settings/global_config.html | 85 +++++++++++++++++++ frontend/views/dashboard.html | 2 + 7 files changed, 154 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 541e993..6176b4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ El formato está basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1 - Sustitución de listas fijas/mockeadas en `frontend/src/routes/schedule.js` por consulta dinámica a `/commissions` en vivo, permitiendo reservar aulas para cualquiera de las comisiones reales de la plataforma. - **Soporte Multisede Consistente en Aulas:** - Resolución robusta de `building_id` y flag `is_virtual` en `frontend/src/routes/classrooms.js`, eliminando hardcodes y asegurando que las aulas físicas pertenezcan a la sede seleccionada. +- **Personalización de Interfaz y Saludo en Dashboard (Configuración Global):** + - **Nueva Pestaña de Interfaz & Dashboard (`/admin/settings`):** Módulo en el panel de configuración global para personalizar elementos visuales del sistema. + - **Desactivación Opcional del Saludo y Selector de Rol:** Switch interactivo para habilitar o deshabilitar el banner superior `¡Hola, {Usuario}!` y el selector de perspectiva de roles en `dashboard.html`. Al deshabilitarlo, el usuario accede directamente a los indicadores y accesos operativos de su rol sin distracciones ni dudas de contexto. + - **Persistencia y Endpoint REST:** Parámetro `dashboard_role_banner_enabled` administrado vía `POST /api/v1/admin/settings/ui`, respaldado por `SystemSetting` y registrado en `AuditLog`. ### Corregido - **Motor de Purga y Reinicio de Datos Académicos:** diff --git a/backend/app/routes/api/admin.py b/backend/app/routes/api/admin.py index ee3b376..0dfd528 100644 --- a/backend/app/routes/api/admin.py +++ b/backend/app/routes/api/admin.py @@ -1832,13 +1832,18 @@ def get_all_settings(): 'sync_interval_minutes': int(SystemSetting.get_value('moodle_sync_interval_minutes', 15)) } + ui_config = { + 'show_role_banner': SystemSetting.get_value('dashboard_role_banner_enabled', 'true') in ['true', 'True', '1', True] + } + return jsonify({ 'status': 'success', 'settings': { 'smtp': smtp_config, 'auth_providers': auth_providers, 'google_oauth': google_oauth, - 'moodle': moodle_config + 'moodle': moodle_config, + 'ui': ui_config } }), 200 @@ -2021,6 +2026,47 @@ def update_auth_providers(): }), 200 +@api_admin_bp.route('/settings/ui', methods=['POST']) +@jwt_required +def update_ui_settings(): + """Habilita o deshabilita elementos de interfaz de usuario, como el banner de saludo institucional y selector de roles.""" + from app.models.setting import SystemSetting + from app.models.audit_log import AuditLog + + acting_user = getattr(g, 'jwt_user', None) + if not (acting_user and (acting_user.is_admin() or getattr(acting_user, 'role', '').upper() == 'ADMIN')): + return jsonify({'error': 'Forbidden', 'message': 'Solo administradores pueden modificar las preferencias de interfaz.'}), 403 + + data = request.get_json(silent=True) or request.form.to_dict() or {} + show_banner_input = data.get('show_role_banner') + if show_banner_input is None: + show_banner_input = data.get('dashboard_role_banner_enabled') + + show_val = 'true' if show_banner_input in [True, 'true', '1', 'on', 1] else 'false' + SystemSetting.set_value('dashboard_role_banner_enabled', show_val, 'Mostrar banner de saludo y selector de rol en Dashboard', category='ui') + + try: + audit = AuditLog( + user_id=acting_user.id, + user_email=acting_user.email, + action='UPDATE_UI_SETTINGS', + module='settings', + details=f"Preferencia de interfaz actualizada: banner_saludo_rol={show_val}" + ) + db.session.add(audit) + db.session.commit() + except Exception: + pass + + return jsonify({ + 'status': 'success', + 'message': 'Preferencia de interfaz guardada correctamente.', + 'ui': { + 'show_role_banner': show_val == 'true' + } + }), 200 + + @api_admin_bp.route('/settings/demo-mode', methods=['GET', 'POST']) @jwt_required def toggle_demo_mode_api(): diff --git a/backend/app/routes/api/dashboard.py b/backend/app/routes/api/dashboard.py index ef391ea..e7763ae 100644 --- a/backend/app/routes/api/dashboard.py +++ b/backend/app/routes/api/dashboard.py @@ -11,6 +11,7 @@ from app import db from app.models.milestone import AcademicMilestone from app.models.subject import Commission +from app.models.setting import SystemSetting api_dashboard_bp = Blueprint('api_dashboard', __name__, url_prefix='/api/v1/dashboard') @@ -137,9 +138,9 @@ def get_stats(): recent_audits = AuditLog.query.order_by(AuditLog.created_at.desc()).limit(6).all() audit_data = [{ 'created_at': a.created_at.strftime('%Y-%m-%d %H:%M') if a.created_at else None, - 'action_type': a.action_type, + 'action_type': getattr(a, 'action', getattr(a, 'action_type', 'AUDIT')), 'ip_address': a.ip_address, - 'user_email': getattr(a.user, 'email', 'System') if hasattr(a, 'user') else 'System' + 'user_email': getattr(a, 'user_email', None) or (getattr(a.user, 'email', 'System') if hasattr(a, 'user') else 'System') } for a in recent_audits] occupancy_rate = min(100, int((today_reservations / max(1, physical_classrooms * 3)) * 100)) if physical_classrooms > 0 else 0 @@ -307,6 +308,9 @@ def get_stats(): 'today_schedule_list': today_schedule_list, 'upcoming_reservations': upcoming_reservations, 'milestones': milestones, + 'ui': { + 'show_role_banner': SystemSetting.get_value('dashboard_role_banner_enabled', 'true') in ['true', 'True', '1', True] + }, 'bedelia': { 'unassigned_commissions_week': unassigned_commissions_week, 'unassigned_commissions_month': unassigned_commissions_month, diff --git a/frontend/src/routes/admin.js b/frontend/src/routes/admin.js index 2bd1193..e06a1e3 100644 --- a/frontend/src/routes/admin.js +++ b/frontend/src/routes/admin.js @@ -1406,6 +1406,9 @@ const handleGlobalSettings = async (req, res) => { if (settings && settings.moodle) { settings.moodle.ws_token_masked = settings.moodle.ws_token_masked || settings.moodle.ws_token || ''; } + if (settings) { + settings.ui = settings.ui || { show_role_banner: true }; + } try { const demoResp = await req.apiClient.get('/settings/demo-mode'); @@ -1503,5 +1506,6 @@ router.post('/settings/moodle', forwardSettingsAction); router.post('/settings/moodle/test', forwardSettingsAction); router.post('/settings/moodle/sync', forwardSettingsAction); router.get('/settings/moodle/stats', forwardSettingsAction); +router.post('/settings/ui', forwardSettingsAction); module.exports = router; diff --git a/frontend/src/routes/dashboard.js b/frontend/src/routes/dashboard.js index a721a79..2c0550b 100644 --- a/frontend/src/routes/dashboard.js +++ b/frontend/src/routes/dashboard.js @@ -266,6 +266,11 @@ router.get('/', async (req, res) => { { start_time: '18:30', end_time: '22:30', date_display: '24/09', subject_name: 'Ingeniería de Software', subject_code: 'IS-301', shift: 'Noche', commission: { code: 'COM-3' }, status: 'PENDING', classroom: { code: 'Aula Magna' }, user: { name: 'Ing. Marcos Ruiz' } } ]; + let show_role_banner = true; + if (apiData && apiData.ui && apiData.ui.show_role_banner !== undefined) { + show_role_banner = Boolean(apiData.ui.show_role_banner); + } + res.render('dashboard', { title: role === 'bedelia' ? 'Panel Operativo de Bedelía - Edu-Space Admin' : role === 'docente' ? 'Panel Docente - Edu-Space Admin' : @@ -274,6 +279,7 @@ router.get('/', async (req, res) => { user: req.user, role, actual_role, + show_role_banner, stats, role_data, monthly_data, diff --git a/frontend/views/admin/settings/global_config.html b/frontend/views/admin/settings/global_config.html index 8bccba9..a9ca8fa 100644 --- a/frontend/views/admin/settings/global_config.html +++ b/frontend/views/admin/settings/global_config.html @@ -83,6 +83,11 @@ Moodle 4.1 & Cola de Sync + @@ -495,6 +500,55 @@ + + + +
+
+
+
+
+
+ Personalización Visual del Dashboard +
+ +
+
+ +
+ Habilita el banner superior ¡Hola, {Usuario}! junto a la insignia de rol y la barra de cambio de perspectiva de roles (Admin, Bedelía, Docente, Alumno) en el Dashboard. +
+ Si se desactiva, el dashboard se mostrará directamente con los indicadores operativos del rol del usuario, evitando confusiones. +
+
+
+ +
+
+ +
+ +
+
+
+ + +
+
+
Experiencia de Usuario (UX)
+

+ El banner de saludo institucional y selector de perspectiva es ideal durante capacitaciones. Al deshabilitarlo para el uso diario institucional, los administradores y usuarios acceden de inmediato a sus módulos de gestión y carteleras sin distracciones ni dudas de rol. +

+
+
+
+
+ @@ -1024,6 +1078,37 @@ function escapeHtml(str) { }); } +// ------------------------------------------------------------- +// Guardar Configuración de Interfaz (UI & Dashboard) +// ------------------------------------------------------------- +async function saveUiConfig(e) { + e.preventDefault(); + const btn = document.getElementById('btnSaveUi'); + btn.disabled = true; + btn.innerHTML = 'Guardando...'; + + const showRoleBanner = document.getElementById('show_role_banner_switch').checked; + + try { + const res = await fetch('/admin/settings/ui', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ show_role_banner: showRoleBanner }) + }); + const data = await res.json(); + if (res.ok) { + showAlert(data.message || 'Preferencias de interfaz guardadas correctamente.', 'success'); + } else { + showAlert(data.message || 'Error al guardar preferencias de interfaz.', 'danger'); + } + } catch (err) { + showAlert('Error de comunicación con el servidor: ' + err.message, 'danger'); + } finally { + btn.disabled = false; + btn.innerHTML = 'Guardar Preferencias de Interfaz'; + } +} + // Cargar estadísticas y tareas al iniciar document.addEventListener('DOMContentLoaded', () => { refreshQueueStats(); diff --git a/frontend/views/dashboard.html b/frontend/views/dashboard.html index 16810aa..7d6da5f 100644 --- a/frontend/views/dashboard.html +++ b/frontend/views/dashboard.html @@ -20,6 +20,7 @@ + {% if show_role_banner %}
@@ -78,6 +79,7 @@ {% endif %}
+ {% endif %}