exec( "ALTER TABLE turnero_sesiones ADD COLUMN IF NOT EXISTS sse_ping_at DATETIME NULL DEFAULT NULL" ); // ── Headers SSE ─────────────────────────────────────────────── @set_time_limit(0); @ini_set('output_buffering', 'off'); @ini_set('zlib.output_compression', false); header('Content-Type: text/event-stream; charset=UTF-8'); header('Cache-Control: no-store, no-cache'); header('X-Accel-Buffering: no'); // Nginx: deshabilitar buffering header('Connection: keep-alive'); // ── Parámetros ──────────────────────────────────────────────── $area = isset($_GET['area']) ? trim($_GET['area']) : 'recepcion'; $lugarId = isset($_GET['lugar_id']) ? (int) $_GET['lugar_id'] : null; if (!in_array($area, ['recepcion', 'lugar'], true)) { $area = 'recepcion'; } // ── Helpers de emisión ──────────────────────────────────────── function sseEvent(string $event, mixed $payload): void { echo "event: {$event}\n"; echo 'data: ' . json_encode($payload, JSON_UNESCAPED_UNICODE) . "\n\n"; flush(); } function ssePing(): void { echo ": ping\n\n"; flush(); } // ── Función que arma el snapshot de la cola ─────────────────── function buildSnapshot(PDO $pdo, int $sesionId, string $area, ?int $lugarId): array { if ($area === 'recepcion') { $estadosCola = ["'espera'", "'en_recepcion'"]; $filtroLugar = ''; $bindsCola = [$sesionId]; $estadoActivo = "'en_recepcion'"; $campoLlamado = 'llamado_recepcion_at'; $bindActivo = [$sesionId]; $filtroActivo = ''; } else { $estadosCola = ["'en_espera_lugar'", "'en_servicio'"]; $filtroLugar = 'AND t.lugar_destino_id = ?'; $bindsCola = [$sesionId, $lugarId]; $estadoActivo = "'en_servicio'"; $campoLlamado = 'llamado_lugar_at'; $bindActivo = [$sesionId, $lugarId]; $filtroActivo = 'AND t.lugar_destino_id = ?'; } $inEstados = implode(', ', $estadosCola); $stmt = $pdo->prepare( "SELECT t.id, t.codigo, t.numero, t.estado, t.paciente_nombre, t.creado_at, p.codigo AS prioridad_codigo, p.nombre AS prioridad_nombre, p.color AS prioridad_color, p.orden_peso FROM turnero_turnos t JOIN turnero_prioridades p ON p.id = t.prioridad_id WHERE t.sesion_id = ? AND t.estado IN ({$inEstados}) {$filtroLugar} ORDER BY p.orden_peso ASC, t.creado_at ASC" ); $stmt->execute($bindsCola); $cola = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt = $pdo->prepare( "SELECT t.id, t.codigo, t.numero, t.estado, t.paciente_nombre, t.{$campoLlamado} AS llamado_at, p.codigo AS prioridad_codigo, p.nombre AS prioridad_nombre, p.color AS prioridad_color FROM turnero_turnos t JOIN turnero_prioridades p ON p.id = t.prioridad_id WHERE t.sesion_id = ? AND t.estado = {$estadoActivo} {$filtroActivo} ORDER BY t.{$campoLlamado} DESC LIMIT 1" ); $stmt->execute($bindActivo); $activo = $stmt->fetch(PDO::FETCH_ASSOC) ?: null; return [ 'activo' => $activo, 'cola' => $cola, 'timestamp' => date('c'), ]; } // ── Loop principal ──────────────────────────────────────────── $sesionId = obtenerOCrearSesionHoy(); $lastKeepalive = time(); $lastEmit = 0; // Emitir estado inicial inmediatamente $snapshot = buildSnapshot($pdo, $sesionId, $area, $lugarId); sseEvent('cola_update', $snapshot); // Inicializar $lastPing con el valor ACTUAL (no null) para que la // primera iteración del loop no emita un snapshot extra sin cambios $stmt = $pdo->prepare('SELECT sse_ping_at FROM turnero_sesiones WHERE id = ?'); $stmt->execute([$sesionId]); $lastPing = $stmt->fetchColumn() ?: null; while (!connection_aborted()) { sleep(1); // Leer el último sse_ping_at de la sesión $stmt = $pdo->prepare('SELECT sse_ping_at FROM turnero_sesiones WHERE id = ?'); $stmt->execute([$sesionId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); $pingActual = $row['sse_ping_at'] ?? null; // Emitir si hubo cambio (notificarSSE actualiza sse_ping_at) o cada 30 s $ahora = time(); $forzar = ($ahora - $lastEmit) >= 30; if ($pingActual !== $lastPing || $forzar) { $snapshot = buildSnapshot($pdo, $sesionId, $area, $lugarId); sseEvent('cola_update', $snapshot); $lastPing = $pingActual; $lastEmit = $ahora; } // Keep-alive cada 15 s if (($ahora - $lastKeepalive) >= 15) { ssePing(); $lastKeepalive = $ahora; } }