Edoruin's picture
App administration created, and relation between asistance and classroom data
048e16c
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title or "MAKER SPACE" }}</title>
<!-- Meta Tags para PWA (Progressive Web App) -->
<!-- Define cómo se ve y se comporta la app al instalarse en el móvil -->
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
<meta name="theme-color" content="#38bdf8">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='assets/apple-touch-icon.png') }}">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Maker Space">
<!-- Iconos y Estilos -->
<link rel="icon" href="{{ url_for('static', filename='assets/favicon.png') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Outfit:wght@500;700&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Librería Socket.IO para comunicación en tiempo real -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
</head>
<body class="dark-theme">
<!-- Barra de Navegación Global -->
<nav class="navbar glass">
<div class="nav-container">
<a href="/" class="nav-logo">
<i class="fas fa-cube"></i>
<span>MAKER SPACE</span>
</a>
<!-- Botón Hamburguesa -->
<button class="hamburger" id="hamburger-btn" aria-label="Menu">
<i class="fas fa-bars"></i>
</button>
<!-- Sidebar Overlay -->
<div class="nav-overlay" id="nav-overlay">
<div class="nav-sidebar glass">
<button class="close-btn" id="close-btn">&times;</button>
<div class="sidebar-links">
<a href="/" class="nav-item"><i class="fas fa-home"></i> INICIO</a>
<a href="/tutoria" class="nav-item"><i class="fas fa-book"></i> TUTORÍA</a>
{% if current_user.is_authenticated %}
<a href="/classroom" class="nav-item"><i class="fas fa-chalkboard-teacher"></i> AULAS</a>
<div style="margin-top: auto; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 1rem;">
<a href="/logout" class="nav-item" style="color: #ef4444;"><i
class="fas fa-sign-out-alt"></i> Salir</a>
</div>
{% else %}
<a href="/login" class="nav-item" style="color: #38bdf8;"><i class="fas fa-sign-in-alt"></i>
Acceder</a>
{% endif %}
</div>
</div>
</div>
<div style="display: flex; gap: 1rem; align-items: center;">
<!-- Botón para instalar PWA (solo aparece si es instalable) -->
<button id="install-btn" class="btn-icon" style="display: none;">
<i class="fas fa-download"></i>
</button>
<!-- Botón para solicitar permisos de notificaciones -->
<button id="notif-btn" class="btn-icon">
<i class="fas fa-bell"></i>
</button>
</div>
</div>
</nav>
<!-- Contenido Principal -->
<main class="content-wrapper">
<!-- Mostrar mensajes flash de Flask (Alertas de éxito/error) -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="notification glass {{ category }}" style="margin-bottom: 2rem;">
<i class="fas fa-exclamation-circle"></i> <span>{{ message }}</span>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Bloque donde se inserta el contenido de cada página hija -->
{% block content %}{% endblock %}
</main>
<!-- Contenedor flotante para notificaciones Socket.IO -->
<div id="notification-container"></div>
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
<script>
/*
LÓGICA DE CLIENTE (JAVASCRIPT)
*/
// Inicializar conexión Socket.IO con el servidor
const socket = io();
socket.on('notification', (data) => {
// Recibir notificación del bot de Telegram
showNotification(data.text, data.color || 'blue');
});
// Función para mostrar notificaciones visuales en la web
function showNotification(text, color) {
const container = document.getElementById('notification-container');
const notif = document.createElement('div');
notif.className = `notification glass ${color}`;
notif.innerHTML = `<i class="fas fa-info-circle"></i> <span>${text}</span>`;
container.appendChild(notif);
// Lanzar notificación de sistema del navegador (PC y Móvil)
if ("Notification" in window && Notification.permission === "granted") {
navigator.serviceWorker.ready.then(registration => {
registration.showNotification("MAKER SPACE", {
body: text,
icon: "/static/assets/icon192x192.png",
vibrate: [200, 100, 200],
badge: "/static/assets/favicon.png"
});
});
}
// Desaparecer notificación después de 5 segundos
setTimeout(() => {
notif.style.opacity = '0';
setTimeout(() => notif.remove(), 500);
}, 5000);
}
// Lógica para el botón de activar notificaciones con feedback visual
const notifBtn = document.getElementById('notif-btn');
notifBtn.addEventListener('click', () => {
if ("Notification" in window) {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
notifBtn.style.color = '#10b981'; // Verde si activa
showNotification("Notificaciones activadas", "green");
} else if (permission === 'denied') {
notifBtn.style.color = '#ef4444'; // Rojo si deniega
alert("Has bloqueado las notificaciones. Actívalas en la configuración de tu navegador.");
}
});
}
});
// Registro del Service Worker para soporte PWA (Modo Offline)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/static/sw.js').then((reg) => {
console.log('SW Registered', reg);
}).catch((err) => {
console.log('SW Registration failed', err);
});
});
}
// Lógica del Menú Hamburguesa
const hamburgerBtn = document.getElementById('hamburger-btn');
const closeBtn = document.getElementById('close-btn');
const navOverlay = document.getElementById('nav-overlay');
function toggleMenu() {
navOverlay.classList.toggle('active');
}
if (hamburgerBtn) hamburgerBtn.addEventListener('click', toggleMenu);
if (closeBtn) closeBtn.addEventListener('click', toggleMenu);
// Cerrar al hacer click fuera del sidebar
if (navOverlay) navOverlay.addEventListener('click', (e) => {
if (e.target === navOverlay) toggleMenu();
});
</script>
</body>
</html>