El turno original no se modifica (queda finalizado igual que antes) — solo se agrega trazabilidad: turnero_muestras.recibida_en_turno_id registra en qué turno se completó una muestra que había quedado pendiente en otra visita. Historial, bandeja y lugar.php ahora muestran un chip/botón "Turno vinculado" en ambos sentidos (origen ⇄ seguimiento) con acceso directo. Además, al abrir un formulario "Datos Toma de Muestras" pendiente para una visita de seguimiento, aparece un botón para cargar las respuestas del último formulario firmado de ese mismo paciente (sin firma ni identidad del profesional anterior) — evita reescribir la historia clínica; la firma y fecha siguen siendo las de hoy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
749 lines
30 KiB
PHP
749 lines
30 KiB
PHP
<?php
|
|
Layout::open('Bandeja del día', 'fas fa-layer-group');
|
|
?>
|
|
<style>
|
|
/* ── Layout ──────────────────────────────────────────────────── */
|
|
.bnd-wrap {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: calc(100vh - 60px);
|
|
overflow: hidden;
|
|
background: #f8fafc;
|
|
}
|
|
.bnd-topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 16px;
|
|
background: #fff;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
flex-shrink: 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
.bnd-title {
|
|
font-size: 1rem;
|
|
font-weight: 700;
|
|
color: #1e293b;
|
|
margin-right: 4px;
|
|
white-space: nowrap;
|
|
}
|
|
.bnd-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
padding: 3px 10px;
|
|
border-radius: 20px;
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
line-height: 1.6;
|
|
white-space: nowrap;
|
|
}
|
|
.bnd-chip-total { background:#ede9fe; color:#5b21b6; }
|
|
.bnd-chip-pend { background:#fef3c7; color:#92400e; }
|
|
.bnd-chip-vis { background:#f1f5f9; color:#475569; }
|
|
.bnd-chip-mp { background:#fff7ed; color:#c2410c; cursor:pointer; transition:box-shadow .12s; }
|
|
.bnd-chip-prog { background:#ecfdf5; color:#065f46; cursor:pointer; transition:box-shadow .12s; }
|
|
.bnd-chip-mp.activo { box-shadow:0 0 0 2px #c2410c; }
|
|
.bnd-chip-prog.activo { box-shadow:0 0 0 2px #065f46; }
|
|
.bnd-refresh-btn {
|
|
margin-left: auto;
|
|
background: none;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 6px;
|
|
padding: 3px 10px;
|
|
font-size: 0.78rem;
|
|
color: #475569;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
white-space: nowrap;
|
|
}
|
|
.bnd-refresh-btn:hover { background:#f1f5f9; }
|
|
|
|
/* ── Cuerpo ──────────────────────────────────────────────────── */
|
|
.bnd-body { display: flex; flex: 1; overflow: hidden; }
|
|
|
|
/* ── Lista izquierda ─────────────────────────────────────────── */
|
|
.bnd-list {
|
|
width: 290px;
|
|
flex-shrink: 0;
|
|
border-right: 1px solid #e2e8f0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #fff;
|
|
}
|
|
.bnd-search-box {
|
|
padding: 8px 10px;
|
|
border-bottom: 1px solid #f1f5f9;
|
|
flex-shrink: 0;
|
|
}
|
|
.bnd-search-box input {
|
|
width: 100%;
|
|
border: 1.5px solid #e2e8f0;
|
|
border-radius: 7px;
|
|
padding: 5px 10px;
|
|
font-size: 0.8rem;
|
|
outline: none;
|
|
font-family: inherit;
|
|
box-sizing: border-box;
|
|
}
|
|
.bnd-search-box input:focus { border-color: #7c3aed; }
|
|
.bnd-list-scroll { flex: 1; overflow-y: auto; }
|
|
.bnd-section-hdr {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
padding: 5px 12px;
|
|
font-size: 0.68rem;
|
|
font-weight: 700;
|
|
letter-spacing: .07em;
|
|
text-transform: uppercase;
|
|
background: #f8fafc;
|
|
color: #64748b;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
border-top: 1px solid #e2e8f0;
|
|
}
|
|
.bnd-card {
|
|
display: flex;
|
|
align-items: stretch;
|
|
cursor: pointer;
|
|
border-bottom: 1px solid #f1f5f9;
|
|
transition: background .1s;
|
|
}
|
|
.bnd-card:hover { background: #f8fafc; }
|
|
.bnd-card.selected { background: #eef2ff; }
|
|
.bnd-card-bar { width: 3px; flex-shrink: 0; }
|
|
.bnd-card-bar.pendiente { background: #f59e0b; }
|
|
.bnd-card-bar.visto { background: #cbd5e1; }
|
|
.bnd-card-inner { padding: 8px 10px; min-width: 0; flex: 1; }
|
|
.bnd-card-top {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 5px;
|
|
margin-bottom: 1px;
|
|
}
|
|
.bnd-orden { font-size: 0.68rem; font-weight: 700; color: #7c3aed; white-space: nowrap; }
|
|
.bnd-nombre {
|
|
font-size: 0.81rem;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
min-width: 0;
|
|
}
|
|
.bnd-card.visto .bnd-nombre { color: #94a3b8; }
|
|
.bnd-card-bot { display: flex; align-items: center; justify-content: space-between; }
|
|
.bnd-lugar-badge { font-size: 0.67rem; color: #64748b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.bnd-hora { font-size: 0.67rem; color: #94a3b8; white-space: nowrap; margin-left: 4px; }
|
|
.bnd-com-badges { display: flex; gap: 3px; margin-left: 2px; }
|
|
.bnd-com-dot {
|
|
font-size: 0.62rem;
|
|
font-weight: 700;
|
|
padding: 1px 5px;
|
|
border-radius: 8px;
|
|
white-space: nowrap;
|
|
line-height: 1.5;
|
|
}
|
|
.bnd-com-dot.recepcion { background: #dbeafe; color: #1d4ed8; }
|
|
.bnd-com-dot.muestras { background: #dcfce7; color: #15803d; }
|
|
.bnd-com-dot.general { background: #fef3c7; color: #92400e; }
|
|
|
|
/* ── Detalle derecha ─────────────────────────────────────────── */
|
|
.bnd-detail { flex: 1; overflow-y: auto; }
|
|
.bnd-empty {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
color: #cbd5e1;
|
|
gap: 10px;
|
|
}
|
|
.bnd-empty i { font-size: 3rem; }
|
|
.bnd-empty p { font-size: 0.9rem; }
|
|
|
|
/* Detail header */
|
|
.bnd-dh {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 2;
|
|
background: #fff;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
padding: 12px 20px 10px;
|
|
}
|
|
.bnd-dh-nombre { font-size: 1.05rem; font-weight: 700; color: #1e293b; margin-bottom: 3px; }
|
|
.bnd-dh-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
.bnd-dh-meta span { font-size: 0.77rem; color: #64748b; }
|
|
.bnd-dh-meta .sep { color: #cbd5e1; }
|
|
.bnd-dh-actions { display: flex; gap: 8px; margin-top: 9px; flex-wrap: wrap; }
|
|
.bnd-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 5px 13px;
|
|
border-radius: 6px;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
border: 1.5px solid;
|
|
transition: all .12s;
|
|
}
|
|
.bnd-btn-pdf { border-color:#3b82f6; color:#2563eb; background:#fff; }
|
|
.bnd-btn-pdf:hover { background:#eff6ff; }
|
|
.bnd-btn-visto { border-color:#7c3aed; color:#5b21b6; background:#fff; }
|
|
.bnd-btn-visto:hover { background:#f5f3ff; }
|
|
.bnd-btn-ya-visto{ border-color:#cbd5e1; color:#94a3b8; background:#f8fafc; cursor:default; }
|
|
|
|
/* Secciones */
|
|
.bnd-sec { padding: 14px 20px; border-bottom: 1px solid #f1f5f9; }
|
|
.bnd-sec-title {
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
letter-spacing: .07em;
|
|
text-transform: uppercase;
|
|
color: #94a3b8;
|
|
margin-bottom: 10px;
|
|
}
|
|
/* Exámenes */
|
|
.bnd-cat { margin-bottom: 8px; }
|
|
.bnd-cat-name { font-size: 0.71rem; font-weight: 600; color: #475569; margin-bottom: 4px; }
|
|
.bnd-chips { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
.bnd-exam-chip {
|
|
background: #f1f5f9;
|
|
color: #334155;
|
|
font-size: 0.71rem;
|
|
font-weight: 600;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
}
|
|
/* Muestras */
|
|
.bnd-muestra { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 0.82rem; }
|
|
.bnd-m-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
.bnd-m-dot.pendiente { background: #f59e0b; }
|
|
.bnd-m-dot.recibida { background: #22c55e; }
|
|
.bnd-m-dot.rechazada { background: #f97316; }
|
|
.bnd-m-label { font-weight: 600; color: #1e293b; }
|
|
.bnd-m-estado { color: #64748b; font-size: 0.74rem; }
|
|
.bnd-m-rechazado { color: #ef4444; font-size: 0.72rem; }
|
|
/* Consentimientos */
|
|
.bnd-consent { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 0.82rem; }
|
|
.bnd-consent-ico.firmado { color: #22c55e; }
|
|
.bnd-consent-ico.pendiente{ color: #f59e0b; }
|
|
.bnd-consent a { font-size: 0.74rem; color: #3b82f6; margin-left: 4px; text-decoration: none; }
|
|
.bnd-consent a:hover { text-decoration: underline; }
|
|
/* Comentarios */
|
|
.bnd-com-list { display: flex; flex-direction: column; gap: 7px; }
|
|
.bnd-com { background: #f8fafc; border-radius: 8px; padding: 8px 12px; font-size: 0.82rem; }
|
|
.bnd-com-header { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
|
|
.bnd-com-tipo {
|
|
font-size: 0.66rem;
|
|
font-weight: 700;
|
|
letter-spacing: .04em;
|
|
padding: 1px 6px;
|
|
border-radius: 10px;
|
|
text-transform: uppercase;
|
|
}
|
|
.bnd-com-tipo.recepcion { background:#dbeafe; color:#1d4ed8; }
|
|
.bnd-com-tipo.muestras { background:#dcfce7; color:#15803d; }
|
|
.bnd-com-tipo.general { background:#fef3c7; color:#92400e; }
|
|
.bnd-com-user { font-weight: 600; color: #1e293b; }
|
|
.bnd-com-date { font-size: 0.7rem; color: #94a3b8; margin-left: auto; }
|
|
.bnd-com-text { color: #334155; }
|
|
/* Agregar comentario */
|
|
.bnd-add-com { display: flex; gap: 8px; align-items: flex-end; }
|
|
.bnd-add-com textarea {
|
|
flex: 1;
|
|
border: 1.5px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 8px 10px;
|
|
font-size: 0.82rem;
|
|
resize: none;
|
|
font-family: inherit;
|
|
height: 52px;
|
|
outline: none;
|
|
transition: border-color .15s;
|
|
}
|
|
.bnd-add-com textarea:focus { border-color: #7c3aed; }
|
|
.bnd-add-com button {
|
|
background: #7c3aed;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 10px 14px;
|
|
font-size: 0.82rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
height: 52px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
.bnd-add-com button:hover { background: #6d28d9; }
|
|
.bnd-add-com button:disabled { background: #c4b5fd; cursor:default; }
|
|
/* Toast */
|
|
.bnd-toast {
|
|
position: fixed;
|
|
bottom: 24px;
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(20px);
|
|
background: #1e293b;
|
|
color: #fff;
|
|
padding: 8px 18px;
|
|
border-radius: 20px;
|
|
font-size: 0.82rem;
|
|
z-index: 9999;
|
|
opacity: 0;
|
|
transition: all .25s;
|
|
pointer-events: none;
|
|
}
|
|
.bnd-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
/* ── Modal consentimiento ── */
|
|
.bnd-modal-consent {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 9200;
|
|
background: rgba(0,0,0,.55);
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.bnd-modal-consent.open { display: flex; }
|
|
.bnd-modal-inner {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
width: min(820px, 96vw);
|
|
height: min(88vh, 700px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,.25);
|
|
}
|
|
.bnd-modal-hdr {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 18px;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
flex-shrink: 0;
|
|
}
|
|
.bnd-modal-hdr span { font-size:.88rem; font-weight:600; color:#1e293b; }
|
|
.bnd-modal-hdr button {
|
|
background: none; border: none; cursor: pointer;
|
|
font-size: 1.1rem; color: #64748b; line-height:1;
|
|
}
|
|
.bnd-modal-hdr button:hover { color:#1e293b; }
|
|
.bnd-modal-body { flex:1; overflow:hidden; position:relative; }
|
|
.bnd-modal-body iframe { width:100%; height:100%; border:none; }
|
|
.bnd-modal-loading {
|
|
position: absolute; inset: 0;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: .9rem; color: #94a3b8;
|
|
gap: 8px;
|
|
}
|
|
/* ── Print ── */
|
|
@media print {
|
|
.bnd-list, .bnd-topbar, .bnd-dh-actions,
|
|
.bnd-add-com, nav, .sidebar, .erp-sidebar,
|
|
#sidebar, .navbar, #navbar { display: none !important; }
|
|
.bnd-wrap, .bnd-body { height: auto !important; overflow: visible !important; }
|
|
.bnd-detail { overflow: visible !important; }
|
|
.bnd-dh { position: static !important; }
|
|
}
|
|
</style>
|
|
|
|
<div class="bnd-wrap">
|
|
<div class="bnd-topbar">
|
|
<span class="bnd-title"><i class="fas fa-layer-group"></i> Bandeja</span>
|
|
<span class="bnd-chip bnd-chip-total" id="st-total"><i class="fas fa-users"></i> —</span>
|
|
<span class="bnd-chip bnd-chip-pend" id="st-pend"><i class="fas fa-clock"></i> Por revisar: —</span>
|
|
<span class="bnd-chip bnd-chip-vis" id="st-vis"><i class="fas fa-eye"></i> Vistos: —</span>
|
|
<span class="bnd-chip bnd-chip-mp" id="st-mp" onclick="_bndToggleChip('mp')" title="Filtrar: con muestras pendientes"><i class="fas fa-flask"></i> Muestras pend.: —</span>
|
|
<span class="bnd-chip bnd-chip-prog" id="st-prog" onclick="_bndToggleChip('prog')" title="Filtrar: toma progresiva en espera"><i class="fas fa-clock"></i> Prog. en espera: —</span>
|
|
<input type="date" id="bnd-desde" onchange="_bndRefrescarLista()" style="border:1px solid #cbd5e1;border-radius:6px;padding:3px 7px;font-size:.78rem;color:#475569;background:#fff;outline:none">
|
|
<input type="date" id="bnd-hasta" onchange="_bndRefrescarLista()" style="border:1px solid #cbd5e1;border-radius:6px;padding:3px 7px;font-size:.78rem;color:#475569;background:#fff;outline:none">
|
|
<select id="bnd-filtro-estado" onchange="_bndFiltrar()" style="border:1px solid #cbd5e1;border-radius:6px;padding:3px 8px;font-size:.78rem;color:#475569;background:#fff;cursor:pointer;outline:none">
|
|
<option value="">Todos los estados</option>
|
|
<option value="en_espera_lugar">En espera</option>
|
|
<option value="en_servicio">En servicio</option>
|
|
<option value="finalizado">Finalizado</option>
|
|
<option value="ausente">Ausente</option>
|
|
<option value="cancelado">Cancelado</option>
|
|
</select>
|
|
<button class="bnd-refresh-btn" onclick="_bndRefrescarLista()">
|
|
<i class="fas fa-sync-alt" id="bnd-spin"></i> Actualizar
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bnd-body">
|
|
<div class="bnd-list">
|
|
<div class="bnd-search-box">
|
|
<input type="text" id="bnd-filtro" placeholder="Nombre, # orden o examen…" oninput="_bndFiltrar()" autocomplete="off">
|
|
</div>
|
|
<div class="bnd-list-scroll" id="bnd-list-scroll">
|
|
<div class="bnd-section-hdr">Cargando...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bnd-detail" id="bnd-detail">
|
|
<div class="bnd-empty">
|
|
<i class="fas fa-hand-pointer"></i>
|
|
<p>Selecciona un paciente para ver el detalle</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal consentimiento -->
|
|
<div class="bnd-modal-consent" id="bnd-modal-consent" onclick="_bndCerrarConsent(event)">
|
|
<div class="bnd-modal-inner">
|
|
<div class="bnd-modal-hdr">
|
|
<span id="bnd-modal-title"><i class="fas fa-file-signature"></i> Consentimiento</span>
|
|
<button onclick="_bndCerrarConsent()"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
<div class="bnd-modal-body">
|
|
<div class="bnd-modal-loading" id="bnd-modal-loading">
|
|
<i class="fas fa-spinner fa-spin"></i> Cargando...
|
|
</div>
|
|
<iframe id="bnd-modal-iframe" src="" style="display:none"
|
|
onload="document.getElementById('bnd-modal-loading').style.display='none';this.style.display='block'">
|
|
</iframe>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bnd-toast" id="bnd-toast"></div>
|
|
|
|
<script>
|
|
const BND_API = 'modules/turnero/api/get_bandeja.php';
|
|
const COM_API = 'modules/turnero/api/comentarios.php';
|
|
const VISTO_API= 'modules/turnero/api/marcar_visto.php';
|
|
|
|
let _bndTurnoId = null;
|
|
let _bndAllData = { pendientes: [], vistos: [] };
|
|
|
|
const _hoy = new Date().toISOString().slice(0,10);
|
|
const _ayer = new Date(Date.now()-86400000).toISOString().slice(0,10);
|
|
|
|
document.getElementById('bnd-desde').value = _hoy;
|
|
document.getElementById('bnd-hasta').value = _hoy;
|
|
|
|
function _hora(dt) {
|
|
if (!dt) return '';
|
|
return new Date(dt.replace(' ','T')).toLocaleTimeString('es-CO',{hour:'2-digit',minute:'2-digit'});
|
|
}
|
|
function _fechaLabel(dt) {
|
|
if (!dt) return '';
|
|
const d = dt.slice(0,10);
|
|
if (d === _hoy) return 'Hoy';
|
|
if (d === _ayer) return 'Ayer';
|
|
return new Date(dt.replace(' ','T')).toLocaleDateString('es-CO',{day:'2-digit',month:'2-digit'});
|
|
}
|
|
function _toast(msg, ms = 2200) {
|
|
const t = document.getElementById('bnd-toast');
|
|
t.textContent = msg;
|
|
t.classList.add('show');
|
|
setTimeout(() => t.classList.remove('show'), ms);
|
|
}
|
|
|
|
// ── Lista ─────────────────────────────────────────────────────
|
|
async function _bndRefrescarLista() {
|
|
const spin = document.getElementById('bnd-spin');
|
|
spin.style.transition = 'transform .4s';
|
|
spin.style.transform = 'rotate(360deg)';
|
|
setTimeout(()=>{ spin.style.transition=''; spin.style.transform=''; }, 400);
|
|
|
|
const desde = document.getElementById('bnd-desde')?.value || '';
|
|
const hasta = document.getElementById('bnd-hasta')?.value || '';
|
|
const qs = (desde && hasta) ? `?desde=${desde}&hasta=${hasta}` : '';
|
|
const res = await fetch(BND_API + qs).then(r=>r.json()).catch(()=>null);
|
|
if (!res?.ok) return;
|
|
|
|
_bndAllData = res;
|
|
const s = res.stats;
|
|
document.getElementById('st-total').innerHTML = `<i class="fas fa-users"></i> Total: ${s.total}`;
|
|
document.getElementById('st-pend').innerHTML = `<i class="fas fa-clock"></i> Por revisar: ${s.pendientes}`;
|
|
document.getElementById('st-vis').innerHTML = `<i class="fas fa-eye"></i> Vistos: ${s.vistos}`;
|
|
document.getElementById('st-mp').innerHTML = `<i class="fas fa-flask"></i> Muestras pend.: ${s.muestras_pendientes}`;
|
|
document.getElementById('st-prog').innerHTML = `<i class="fas fa-clock"></i> Prog. en espera: ${s.prolongadas_pendientes}`;
|
|
|
|
_bndFiltrar();
|
|
}
|
|
|
|
const _bndChipActivo = { mp: false, prog: false };
|
|
|
|
function _bndToggleChip(tipo) {
|
|
_bndChipActivo[tipo] = !_bndChipActivo[tipo];
|
|
const id = tipo === 'mp' ? 'st-mp' : 'st-prog';
|
|
document.getElementById(id).classList.toggle('activo', _bndChipActivo[tipo]);
|
|
_bndFiltrar();
|
|
}
|
|
|
|
function _bndFiltrar() {
|
|
const q = (document.getElementById('bnd-filtro')?.value || '').toLowerCase().trim();
|
|
const st = document.getElementById('bnd-filtro-estado')?.value || '';
|
|
const match = t =>
|
|
(!q || (t.paciente_nombre || '').toLowerCase().includes(q) || (t.examenes_txt || '').toLowerCase().includes(q) || String(t.numero_orden || '').includes(q) || (t.codigo || '').toLowerCase().includes(q)) &&
|
|
(!st || t.estado === st) &&
|
|
(!_bndChipActivo.mp || +t.muestras_pendientes > 0) &&
|
|
(!_bndChipActivo.prog || (t.muestra_espera_at && !t.fin_lugar_at));
|
|
|
|
_renderLista(
|
|
_bndAllData.pendientes?.filter(match) || [],
|
|
_bndAllData.vistos?.filter(match) || []
|
|
);
|
|
}
|
|
|
|
function _renderLista(pendientes, vistos) {
|
|
const el = document.getElementById('bnd-list-scroll');
|
|
const _estadoLabel = {
|
|
en_servicio: ['En servicio', '#15803d', '#dcfce7'],
|
|
en_espera_lugar: ['En espera', '#92400e', '#fef3c7'],
|
|
finalizado: ['Finalizado', '#475569', '#f1f5f9'],
|
|
ausente: ['Ausente', '#b91c1c', '#fee2e2'],
|
|
cancelado: ['Cancelado', '#b91c1c', '#fee2e2'],
|
|
};
|
|
|
|
const card = (t, tipo) => {
|
|
const sel = t.id === _bndTurnoId ? ' selected' : '';
|
|
const orden = t.numero_orden || t.codigo;
|
|
const nombre = (t.paciente_nombre || '—').split(' ').slice(0,3).join(' ');
|
|
|
|
// Badges de comentarios
|
|
let comBadges = '';
|
|
if (+t.muestras_pendientes > 0) comBadges += `<span class="bnd-com-dot" style="background:#fee2e2;color:#b91c1c"><i class="fas fa-flask"></i> ${t.muestras_pendientes} pend.</span>`;
|
|
if (+t.com_recepcion > 0) comBadges += `<span class="bnd-com-dot recepcion"><i class="fas fa-comment-dots"></i> Recep.</span>`;
|
|
if (+t.com_muestras > 0) comBadges += `<span class="bnd-com-dot muestras"><i class="fas fa-comment-dots"></i> Toma</span>`;
|
|
if (+t.com_general > 0) comBadges += `<span class="bnd-com-dot general"><i class="fas fa-comment-dots"></i> Gral.</span>`;
|
|
|
|
// Label de estado
|
|
const [etiq, color, bg] = _estadoLabel[t.estado] || ['—', '#94a3b8', '#f8fafc'];
|
|
const estadoBadge = `<span style="font-size:.62rem;font-weight:700;padding:1px 6px;border-radius:8px;background:${bg};color:${color};white-space:nowrap">${etiq}</span>`;
|
|
|
|
return `<div class="bnd-card ${tipo}${sel}" onclick="_bndVerDetalle(${t.id})">
|
|
<div class="bnd-card-bar ${tipo}"></div>
|
|
<div class="bnd-card-inner">
|
|
<div class="bnd-card-top">
|
|
<span class="bnd-orden">${orden}</span>
|
|
<span class="bnd-nombre">${nombre}</span>
|
|
</div>
|
|
<div class="bnd-card-bot">
|
|
<span class="bnd-lugar-badge">${t.lugar_nombre||''}</span>
|
|
<span class="bnd-hora">${_hora(t.inicio_lugar_at)} <span style="opacity:.6">${_fechaLabel(t.inicio_lugar_at)}</span></span>
|
|
</div>
|
|
<div class="bnd-card-bot" style="margin-top:3px">
|
|
${estadoBadge}
|
|
<div class="bnd-com-badges">${comBadges}</div>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
};
|
|
|
|
let html = `<div class="bnd-section-hdr">Por revisar (${pendientes.length})</div>`;
|
|
if (!pendientes.length) html += `<div style="padding:10px 12px;font-size:.77rem;color:#94a3b8">Sin pacientes pendientes</div>`;
|
|
pendientes.forEach(t => html += card(t, 'pendiente'));
|
|
|
|
html += `<div class="bnd-section-hdr">Vistos (${vistos.length})</div>`;
|
|
if (!vistos.length) html += `<div style="padding:10px 12px;font-size:.77rem;color:#94a3b8">Ninguno visto aún</div>`;
|
|
vistos.forEach(t => html += card(t, 'visto'));
|
|
|
|
el.innerHTML = html;
|
|
}
|
|
|
|
// ── Modal consentimiento ──────────────────────────────────────
|
|
function _bndAbrirConsent(token, nombre, ev) {
|
|
if (ev) ev.stopPropagation();
|
|
const modal = document.getElementById('bnd-modal-consent');
|
|
const iframe = document.getElementById('bnd-modal-iframe');
|
|
const loading = document.getElementById('bnd-modal-loading');
|
|
document.getElementById('bnd-modal-title').innerHTML = `<i class="fas fa-file-signature"></i> ${nombre||'Consentimiento'}`;
|
|
iframe.style.display = 'none';
|
|
iframe.src = '';
|
|
loading.style.display = 'flex';
|
|
modal.classList.add('open');
|
|
setTimeout(() => {
|
|
iframe.src = `ver_formulario_enviado.php?token=${encodeURIComponent(token)}&embed=1&compact=1&zoom=1.1`;
|
|
}, 60);
|
|
}
|
|
function _bndCerrarConsent(ev) {
|
|
if (ev && ev.target !== document.getElementById('bnd-modal-consent')) return;
|
|
document.getElementById('bnd-modal-consent').classList.remove('open');
|
|
document.getElementById('bnd-modal-iframe').src = '';
|
|
}
|
|
|
|
// ── Detalle ───────────────────────────────────────────────────
|
|
async function _bndVerDetalle(turnoId) {
|
|
_bndTurnoId = turnoId;
|
|
document.querySelectorAll('.bnd-card').forEach(c => {
|
|
c.classList.toggle('selected', c.onclick?.toString().includes(`${turnoId}`));
|
|
});
|
|
|
|
const det = document.getElementById('bnd-detail');
|
|
det.innerHTML = `<div class="bnd-empty"><i class="fas fa-spinner fa-spin"></i><p>Cargando...</p></div>`;
|
|
|
|
const res = await fetch(`${BND_API}?turno_id=${turnoId}`).then(r=>r.json()).catch(()=>null);
|
|
if (!res?.ok) { det.innerHTML = `<div class="bnd-empty"><i class="fas fa-times-circle"></i><p>Error al cargar</p></div>`; return; }
|
|
|
|
_renderDetalle(res);
|
|
}
|
|
|
|
function _renderDetalle(d) {
|
|
const t = d.turno;
|
|
const yaVisto= !!t.bandeja_visto_at;
|
|
|
|
let hdr = `<div class="bnd-dh">
|
|
<div class="bnd-dh-nombre">${t.paciente_nombre||'—'}</div>
|
|
<div class="bnd-dh-meta">
|
|
<span><b>${t.numero_orden||t.codigo}</b></span>
|
|
<span class="sep">·</span>
|
|
<span>${t.lugar_nombre||''}</span>
|
|
<span class="sep">·</span>
|
|
<span>${_hora(t.inicio_lugar_at)}</span>
|
|
</div>
|
|
<div class="bnd-dh-actions">
|
|
<button class="bnd-btn bnd-btn-pdf" onclick="window.print()"><i class="fas fa-print"></i> PDF</button>`;
|
|
|
|
if (yaVisto) {
|
|
hdr += `<button class="bnd-btn bnd-btn-ya-visto" disabled><i class="fas fa-eye"></i> Visto ${_hora(t.bandeja_visto_at)}</button>`;
|
|
} else {
|
|
hdr += `<button class="bnd-btn bnd-btn-visto" onclick="_bndMarcarVisto(${t.id})"><i class="fas fa-eye"></i> Marcar como visto</button>`;
|
|
}
|
|
hdr += `</div></div>`;
|
|
|
|
// Exámenes
|
|
let secEx = `<div class="bnd-sec"><div class="bnd-sec-title"><i class="fas fa-vial"></i> Exámenes</div>`;
|
|
const cats = Object.keys(d.examenes_por_cat||{});
|
|
if (!cats.length) {
|
|
secEx += `<span style="color:#94a3b8;font-size:.82rem">Sin exámenes registrados</span>`;
|
|
} else {
|
|
cats.forEach(cat => {
|
|
const chips = d.examenes_por_cat[cat].map(c=>`<span class="bnd-exam-chip">${c}</span>`).join('');
|
|
secEx += `<div class="bnd-cat"><div class="bnd-cat-name">${cat}</div><div class="bnd-chips">${chips}</div></div>`;
|
|
});
|
|
}
|
|
secEx += `</div>`;
|
|
|
|
// Muestras
|
|
let secM = `<div class="bnd-sec"><div class="bnd-sec-title"><i class="fas fa-tint"></i> Muestras</div>`;
|
|
if (!d.muestras?.length) {
|
|
secM += `<span style="color:#94a3b8;font-size:.82rem">Sin muestras registradas</span>`;
|
|
} else {
|
|
d.muestras.forEach(m => {
|
|
const rechazo = m.estado==='rechazada'&&m.motivo_rechazo
|
|
? `<span class="bnd-m-rechazado"> · ${m.motivo_rechazo}</span>` : '';
|
|
secM += `<div class="bnd-muestra">
|
|
<div class="bnd-m-dot ${m.estado}"></div>
|
|
<span class="bnd-m-label">${m.tipo_muestra}</span>
|
|
<span class="bnd-m-estado">${m.estado==='rechazada'?'Pendiente':m.estado}${m.recibida_at?' · '+_hora(m.recibida_at):''}</span>
|
|
${rechazo}
|
|
</div>`;
|
|
});
|
|
}
|
|
secM += `</div>`;
|
|
|
|
// Turno vinculado (muestra pendiente completada en otra visita, o viceversa)
|
|
let secRel = '';
|
|
if (d.relacionados?.length) {
|
|
secRel = `<div class="bnd-sec"><div class="bnd-sec-title"><i class="fas fa-link"></i> Turno vinculado</div>`;
|
|
d.relacionados.forEach(r => {
|
|
const txt = r.direccion === 'seguimiento'
|
|
? `Muestra completada en ${r.codigo}`
|
|
: `Completa muestra pendiente de ${r.codigo}`;
|
|
secRel += `<button class="bnd-btn" style="margin-top:4px" onclick="_bndVerDetalle(${r.turno_id})">
|
|
<i class="fas fa-arrow-right"></i> ${txt}
|
|
</button>`;
|
|
});
|
|
secRel += `</div>`;
|
|
}
|
|
|
|
// Consentimientos
|
|
let secC = `<div class="bnd-sec"><div class="bnd-sec-title"><i class="fas fa-file-signature"></i> Consentimientos</div>`;
|
|
if (!d.consentimientos?.length) {
|
|
secC += `<span style="color:#94a3b8;font-size:.82rem">Sin consentimientos</span>`;
|
|
} else {
|
|
d.consentimientos.forEach(c => {
|
|
const esFirmado = c.estado==='firmado';
|
|
const ico = esFirmado ? 'firmado fas fa-check-circle' : 'pendiente fas fa-clock';
|
|
const link = c.token
|
|
? `<button onclick="_bndAbrirConsent('${c.token}','${c.formulario_nombre.replace(/'/g,"\\'")}',event)" style="background:none;border:none;cursor:pointer;font-size:.74rem;color:#3b82f6;padding:0;margin-left:4px"><i class="fas fa-eye"></i> Ver</button>` : '';
|
|
secC += `<div class="bnd-consent">
|
|
<i class="bnd-consent-ico ${ico}"></i>
|
|
<span style="font-size:.82rem">${c.formulario_nombre}</span>
|
|
${c.firmado_at?`<span style="font-size:.71rem;color:#94a3b8">${_hora(c.firmado_at)}</span>`:''}
|
|
${link}
|
|
</div>`;
|
|
});
|
|
}
|
|
secC += `</div>`;
|
|
|
|
// Comentarios
|
|
let secCom = `<div class="bnd-sec"><div class="bnd-sec-title"><i class="fas fa-comments"></i> Comentarios</div>
|
|
<div class="bnd-com-list" id="bnd-com-list">`;
|
|
if (!d.comentarios?.length) {
|
|
secCom += `<span style="color:#94a3b8;font-size:.82rem">Sin comentarios</span>`;
|
|
} else {
|
|
d.comentarios.forEach(c => {
|
|
const tStr = new Date((c.creado_at||'').replace(' ','T'))
|
|
.toLocaleTimeString('es-CO',{hour:'2-digit',minute:'2-digit'});
|
|
secCom += `<div class="bnd-com">
|
|
<div class="bnd-com-header">
|
|
<span class="bnd-com-tipo ${c.tipo}">${c.tipo}</span>
|
|
<span class="bnd-com-user">${c.usuario_nombre||''}</span>
|
|
<span class="bnd-com-date">${tStr}</span>
|
|
</div>
|
|
<div class="bnd-com-text">${c.comentario}</div>
|
|
</div>`;
|
|
});
|
|
}
|
|
secCom += `</div>
|
|
<div class="bnd-add-com" style="margin-top:10px">
|
|
<textarea id="bnd-new-com" placeholder="Agregar comentario de toma de muestras..."></textarea>
|
|
<button onclick="_bndEnviarCom(${t.id})"><i class="fas fa-paper-plane"></i> Enviar</button>
|
|
</div></div>`;
|
|
|
|
document.getElementById('bnd-detail').innerHTML = hdr + secEx + secM + secRel + secC + secCom;
|
|
}
|
|
|
|
// ── Marcar visto ──────────────────────────────────────────────
|
|
async function _bndMarcarVisto(turnoId) {
|
|
const res = await fetch(VISTO_API, {
|
|
method: 'POST',
|
|
headers: {'Content-Type':'application/json'},
|
|
body: JSON.stringify({turno_id: turnoId})
|
|
}).then(r=>r.json()).catch(()=>null);
|
|
|
|
if (!res?.ok) { _toast('Error al marcar como visto'); return; }
|
|
_toast('Marcado como visto');
|
|
await _bndRefrescarLista();
|
|
_bndVerDetalle(turnoId);
|
|
}
|
|
|
|
// ── Comentario ────────────────────────────────────────────────
|
|
async function _bndEnviarCom(turnoId) {
|
|
const ta = document.getElementById('bnd-new-com');
|
|
const txt = ta.value.trim();
|
|
if (!txt) return;
|
|
const btn = ta.nextElementSibling;
|
|
btn.disabled = true;
|
|
|
|
const res = await fetch(COM_API, {
|
|
method: 'POST',
|
|
headers: {'Content-Type':'application/json'},
|
|
body: JSON.stringify({turno_id: turnoId, comentario: txt, tipo: 'muestras'})
|
|
}).then(r=>r.json()).catch(()=>null);
|
|
|
|
btn.disabled = false;
|
|
if (!res?.ok) { _toast('Error al guardar comentario'); return; }
|
|
ta.value = '';
|
|
_toast('Comentario guardado');
|
|
_bndVerDetalle(turnoId);
|
|
}
|
|
|
|
// ── Init ──────────────────────────────────────────────────────
|
|
_bndRefrescarLista();
|
|
setInterval(_bndRefrescarLista, 30000);
|
|
</script>
|
|
|
|
<?php Layout::close(); ?>
|