# 📦 Estructura propuesta ``` /mesacontrol/ ├─ db.php # Conexión PDO + sesión persistente (sin timeout) ├─ login.php # Formulario de acceso de agentes ├─ dashboard.php # Panel de estado (disponible/pausa/baño/comida/entrenamiento) ├─ api_login.php # POST: autentica y marca "disponible" ├─ api_state.php # POST: cambia estado del agente ├─ api_heartbeat.php # POST: latido para evitar timeout y marcar last_seen ├─ api_logout.php # POST/BEACON: marca logout y cierra sesión └─ hard_logout.php # GET: forzar salida manual (link) ``` --- # 🧱 SQL (tablas + evento opcional auto-logout por inactividad) ```sql -- Tabla de estados de agente (simple y eficiente) CREATE TABLE IF NOT EXISTS mesacontrol_agente_estado ( id INT AUTO_INCREMENT PRIMARY KEY, login VARCHAR(64) NOT NULL, estado ENUM('logout','disponible','pausa','baño','comida','entrenamiento') NOT NULL DEFAULT 'logout', session_id VARCHAR(128) DEFAULT NULL, last_change DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, last_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, user_agent VARCHAR(255) DEFAULT NULL, ip VARCHAR(64) DEFAULT NULL, is_active TINYINT(1) NOT NULL DEFAULT 0, UNIQUE KEY uk_login (login), KEY idx_seen (last_seen) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- (Opcional) Evento MySQL para dar logout si no hay heartbeat > 2 min -- Requiere: SET GLOBAL event_scheduler = ON; DROP EVENT IF EXISTS ev_mesacontrol_autologout; CREATE EVENT ev_mesacontrol_autologout ON SCHEDULE EVERY 1 MINUTE DO UPDATE mesacontrol_agente_estado SET estado='logout', is_active=0 WHERE is_active=1 AND TIMESTAMPDIFF(SECOND, last_seen, NOW()) > 120; -- 2 minutos sin latido ``` > Nota: Si prefieres manejar la caducidad desde PHP (sin EVENT), puedes ejecutar el UPDATE anterior en cualquier endpoint sensible (por ejemplo en `api_heartbeat.php` o `dashboard.php` al cargar). --- # 🔧 db.php ```php $lifetime, 'path' => '/', 'secure' => false, // pon true si usas HTTPS 'httponly' => true, 'samesite' => 'Lax', ]); if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); } function pdo(): PDO { static $pdo = null; if ($pdo) return $pdo; $dsn = 'mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4'; $pdo = new PDO($dsn, DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]); return $pdo; } function agent_require_login() { if (empty($_SESSION['agent_login'])) { header('Location: login.php'); exit; } } function sanitize_state(string $s): string { $allowed = ['logout','disponible','pausa','baño','comida','entrenamiento']; return in_array($s, $allowed, true) ? $s : 'logout'; } function agent_upsert_state(string $login, string $estado, bool $active, ?string $sessionId=null) { $pdo = pdo(); $estado = sanitize_state($estado); $sql = "INSERT INTO mesacontrol_agente_estado (login, estado, is_active, session_id, last_change, last_seen, user_agent, ip) VALUES (:login, :estado, :active, :sid, NOW(), NOW(), :ua, :ip) ON DUPLICATE KEY UPDATE estado=VALUES(estado), is_active=VALUES(is_active), session_id=VALUES(session_id), last_change=NOW(), last_seen=NOW(), user_agent=VALUES(user_agent), ip=VALUES(ip)"; $stmt = $pdo->prepare($sql); $stmt->execute([ ':login' => $login, ':estado' => $estado, ':active' => $active ? 1 : 0, ':sid' => $sessionId, ':ua' => $_SERVER['HTTP_USER_AGENT'] ?? null, ':ip' => $_SERVER['REMOTE_ADDR'] ?? null, ]); } function agent_touch_seen(string $login) { $pdo = pdo(); $stmt = $pdo->prepare("UPDATE mesacontrol_agente_estado SET last_seen=NOW() WHERE login=?"); $stmt->execute([$login]); } ``` --- # 🔐 login.php ```php