feat(turnero): panel de estado de muestras en recepción
Recepción ve en una barra inferior qué turno atiende cada estación de toma de muestras (verde = ocupada, gris = libre), cuántos están en espera y cuántos en espera entre muestras progresivas. Se refresca cada 7 s. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
7b599b28ef
commit
7d444699d3
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* GET get_resumen_muestras.php
|
||||||
|
* Resumen de estaciones de toma de muestras para recepción.
|
||||||
|
*/
|
||||||
|
require_once __DIR__ . '/_helpers.php';
|
||||||
|
requireTurnero();
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
$sesionId = (int)(obtenerOCrearSesionHoy()['id'] ?? 0);
|
||||||
|
|
||||||
|
// Estaciones con el turno en servicio actual
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"SELECT l.id, l.nombre,
|
||||||
|
t.id AS turno_id, t.codigo, t.paciente_nombre
|
||||||
|
FROM turnero_lugares l
|
||||||
|
LEFT JOIN turnero_turnos t
|
||||||
|
ON t.lugar_destino_id = l.id
|
||||||
|
AND t.sesion_id = ?
|
||||||
|
AND t.estado = 'en_servicio'
|
||||||
|
AND t.muestra_espera_at IS NULL
|
||||||
|
AND t.fin_lugar_at IS NULL
|
||||||
|
WHERE l.tipo = 'muestras' AND l.activo = 1
|
||||||
|
ORDER BY l.sort_order"
|
||||||
|
);
|
||||||
|
$stmt->execute([$sesionId]);
|
||||||
|
$estaciones = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Cola global de muestras (en_espera_lugar)
|
||||||
|
$s = $pdo->prepare(
|
||||||
|
"SELECT COUNT(*) FROM turnero_turnos
|
||||||
|
WHERE sesion_id = ? AND estado = 'en_espera_lugar' AND muestra_espera_at IS NULL
|
||||||
|
AND lugar_destino_id IN (SELECT id FROM turnero_lugares WHERE tipo='muestras' AND activo=1)"
|
||||||
|
);
|
||||||
|
$s->execute([$sesionId]);
|
||||||
|
$enEspera = (int)$s->fetchColumn();
|
||||||
|
|
||||||
|
// Pacientes entre tomas (espera progresiva)
|
||||||
|
$s2 = $pdo->prepare(
|
||||||
|
"SELECT COUNT(*) FROM turnero_turnos
|
||||||
|
WHERE sesion_id = ? AND muestra_espera_at IS NOT NULL AND fin_lugar_at IS NULL
|
||||||
|
AND lugar_destino_id IN (SELECT id FROM turnero_lugares WHERE tipo='muestras' AND activo=1)"
|
||||||
|
);
|
||||||
|
$s2->execute([$sesionId]);
|
||||||
|
$enEsperaMuestra = (int)$s2->fetchColumn();
|
||||||
|
|
||||||
|
jsonOk([
|
||||||
|
'estaciones' => $estaciones,
|
||||||
|
'en_espera' => $enEspera,
|
||||||
|
'en_espera_muestra' => $enEsperaMuestra,
|
||||||
|
]);
|
||||||
@@ -934,6 +934,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
</div><!-- /rec-ficha -->
|
</div><!-- /rec-ficha -->
|
||||||
|
|
||||||
</div><!-- /rec-layout -->
|
</div><!-- /rec-layout -->
|
||||||
|
|
||||||
|
<!-- ══ Panel resumen toma de muestras ════════════════════════ -->
|
||||||
|
<div id="panel-muestras-resumen" style="padding:6px 12px;border-top:1px solid #e2e8f0;background:#f8fafc;display:flex;gap:8px;align-items:center;flex-wrap:wrap;min-height:38px">
|
||||||
|
<span class="text-muted small fw-semibold me-1" style="white-space:nowrap"><i class="fas fa-vial me-1"></i>Muestras:</span>
|
||||||
|
<div id="muestras-estaciones" class="d-flex gap-2 flex-wrap align-items-center"></div>
|
||||||
|
<span id="muestras-cola-badge" class="badge bg-warning text-dark ms-auto d-none" title="En espera de ser llamados"></span>
|
||||||
|
<span id="muestras-prog-badge" class="badge bg-info text-dark d-none" title="Esperando siguiente muestra (toma progresiva)"></span>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- ═══ Modal firma enfermero ═══════════════════════════════════════ -->
|
<!-- ═══ Modal firma enfermero ═══════════════════════════════════════ -->
|
||||||
@@ -1200,6 +1208,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
cargarCola();
|
cargarCola();
|
||||||
pollingColaId = setInterval(cargarCola, 3000);
|
pollingColaId = setInterval(cargarCola, 3000);
|
||||||
|
_cargarResumenMuestras();
|
||||||
|
setInterval(_cargarResumenMuestras, 7000);
|
||||||
let _pacBusqTimer;
|
let _pacBusqTimer;
|
||||||
const _inpPac = document.getElementById('inp-buscar-pac');
|
const _inpPac = document.getElementById('inp-buscar-pac');
|
||||||
_inpPac.addEventListener('input', () => {
|
_inpPac.addEventListener('input', () => {
|
||||||
@@ -3079,6 +3089,28 @@ async function mpacGuardar() {
|
|||||||
|
|
||||||
// ── Toast ────────────────────────────────────────────────────
|
// ── Toast ────────────────────────────────────────────────────
|
||||||
let toastTimer = null;
|
let toastTimer = null;
|
||||||
|
async function _cargarResumenMuestras() {
|
||||||
|
try {
|
||||||
|
const r = await fetch(API + 'get_resumen_muestras.php');
|
||||||
|
const j = await r.json();
|
||||||
|
if (!j.ok) return;
|
||||||
|
const cont = document.getElementById('muestras-estaciones');
|
||||||
|
if (!cont) return;
|
||||||
|
cont.innerHTML = j.estaciones.map(e => {
|
||||||
|
const libre = !e.turno_id;
|
||||||
|
return `<span class="badge ${libre ? 'bg-secondary' : 'bg-success'}" style="font-size:.75rem;font-weight:500" title="${libre ? 'Libre' : escHtml(e.paciente_nombre || '')}">
|
||||||
|
${escHtml(e.nombre)}: ${libre ? '—' : escHtml(e.codigo)}
|
||||||
|
</span>`;
|
||||||
|
}).join('');
|
||||||
|
const cb = document.getElementById('muestras-cola-badge');
|
||||||
|
cb.textContent = `${j.en_espera} en espera`;
|
||||||
|
cb.classList.toggle('d-none', j.en_espera === 0);
|
||||||
|
const pb = document.getElementById('muestras-prog-badge');
|
||||||
|
pb.textContent = `${j.en_espera_muestra} prog.`;
|
||||||
|
pb.classList.toggle('d-none', j.en_espera_muestra === 0);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
function mostrarToast(msg, type = 'info', duration = 2500) {
|
function mostrarToast(msg, type = 'info', duration = 2500) {
|
||||||
const el = document.getElementById('rec-toast');
|
const el = document.getElementById('rec-toast');
|
||||||
const ico = document.getElementById('toast-ico');
|
const ico = document.getElementById('toast-ico');
|
||||||
|
|||||||
Reference in New Issue
Block a user