up
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* GET /modules/turnero/api/sse_turno.php
|
||||
* Server-Sent Events — actualiza pantallas TV y kiosko en tiempo real.
|
||||
* No requiere autenticación (acceso público).
|
||||
*
|
||||
* Query params:
|
||||
* area string opcional "recepcion" | "lugar" (default: "recepcion")
|
||||
* lugar_id int requerido si area = "lugar"
|
||||
*
|
||||
* Eventos emitidos:
|
||||
* cola_update — estado completo de la cola cada vez que haya cambio o cada 30 s
|
||||
* ping — comentario de keep-alive cada 15 s
|
||||
*/
|
||||
|
||||
// ── Asegurar que la columna sse_ping_at exista (idempotente) ──
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
|
||||
$pdo = db();
|
||||
$pdo->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();
|
||||
$lastPing = null; // sse_ping_at de la última iteración
|
||||
$lastKeepalive = time();
|
||||
$lastEmit = 0;
|
||||
|
||||
// Emitir estado inicial inmediatamente
|
||||
$snapshot = buildSnapshot($pdo, $sesionId, $area, $lugarId);
|
||||
sseEvent('cola_update', $snapshot);
|
||||
|
||||
while (!connection_aborted()) {
|
||||
sleep(2);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user