Turnero: mejoras UI, historial detallado, kiosko scanner, firma por turno

- Kiosko: detector scanner (gap < 80ms), iconos 90px, fa-person-pregnant, ticket Arial
- Recepción: vincular_paciente.php para persistir paciente_id antes de firmar consentimiento
- Historial y dashboard: recepción desk, empleado recepción/muestras, exámenes y comentarios
- display_global: turno más pequeño, lugar-nombre más grande
- lab_pacientes: modal compacta con form-sm
- Configuración: icono embarazada fa-person-pregnant en prioridades
- ver_formulario_enviado: firma por campo (fix doble firma)
- docker-compose.local.yml: override BD producción para dev local

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-22 22:07:09 -05:00
co-authored by Claude Sonnet 4.6
parent ebbf4ac6cf
commit f363631b32
12 changed files with 285 additions and 98 deletions
+70 -1
View File
@@ -137,6 +137,7 @@ Layout::open('Dashboard Turnero', 'fas fa-chart-bar');
<th>Espera</th>
<th>Servicio</th>
<th>Entrada</th>
<th></th>
</tr>
</thead>
<tbody id="tbodyTurnos">
@@ -324,7 +325,8 @@ function renderTabla(turnos) {
const hora = t.creado_at ? new Date(t.creado_at.replace(' ', 'T')).toLocaleTimeString('es-CO', {hour:'2-digit',minute:'2-digit'}) : '—';
const espMin = t.espera_min !== null ? t.espera_min + ' m' : '—';
const srvMin = t.servicio_min !== null ? t.servicio_min + ' m' : '—';
return `<tr>
const rowId = `dash-det-${t.id}`;
return `<tr style="cursor:pointer" onclick="toggleDashDetalle('${rowId}', this)">
<td><span class="fw-bold" style="color:${escHtml(t.prioridad_color || '#333')}">${escHtml(t.codigo)}</span></td>
<td><span class="estado-badge" style="background:${escHtml(t.prioridad_color+'22'||'#eee')};color:${escHtml(t.prioridad_color||'#333')}">${escHtml(t.prioridad_codigo)}</span></td>
<td>${nombrePac}</td>
@@ -333,10 +335,77 @@ function renderTabla(turnos) {
<td>${espMin}</td>
<td>${srvMin}</td>
<td>${hora}</td>
<td><i class="fas fa-chevron-down text-muted" style="font-size:.65rem"></i></td>
</tr>
<tr id="${rowId}" style="display:none;background:#f8fafc">
<td colspan="9" style="padding:0">
<div style="padding:12px 16px;border-top:1px solid #e2e8f0">
${renderDashDetalle(t)}
</div>
</td>
</tr>`;
}).join('');
}
function toggleDashDetalle(rowId, tr) {
const det = document.getElementById(rowId);
const icon = tr.querySelector('.fa-chevron-down, .fa-chevron-up');
if (!det) return;
const open = det.style.display !== 'none';
det.style.display = open ? 'none' : '';
if (icon) icon.className = open ? 'fas fa-chevron-down text-muted' : 'fas fa-chevron-up text-muted';
if (icon) icon.style.fontSize = '.65rem';
}
function renderDashDetalle(t) {
const e = s => escHtml(s || '—');
const fmt = dt => dt ? new Date(dt.replace(' ','T')).toLocaleString('es-CO',{dateStyle:'short',timeStyle:'short'}) : '—';
const chips = [
{ lbl:'Recepción (desk)', val: e(t.recepcion_desk_nombre) },
{ lbl:'Atendió recepción', val: e(t.atendido_recepcion_nombre) },
{ lbl:'Atendió muestras', val: e(t.atendido_lugar_nombre) },
{ lbl:'Inicio recepción', val: fmt(t.inicio_recepcion_at) },
{ lbl:'Fin recepción', val: fmt(t.fin_recepcion_at) },
{ lbl:'Inicio servicio', val: fmt(t.inicio_lugar_at) },
{ lbl:'Fin servicio', val: fmt(t.fin_lugar_at) },
];
let grid = `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:8px;margin-bottom:10px">
${chips.map(c=>`<div style="background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:7px 11px">
<div style="font-size:.63rem;text-transform:uppercase;letter-spacing:.07em;color:#94a3b8">${c.lbl}</div>
<div style="font-size:.82rem;font-weight:600;color:#1e293b">${c.val}</div>
</div>`).join('')}
</div>`;
// Notas
if (t.notas) {
grid += `<div style="margin-bottom:8px;font-size:.82rem"><span style="font-size:.67rem;text-transform:uppercase;letter-spacing:.07em;color:#94a3b8">📝 Notas: </span>${e(t.notas)}</div>`;
}
// Exámenes
if (t.examenes && t.examenes.length) {
grid += `<div style="margin-bottom:8px">
<span style="font-size:.67rem;text-transform:uppercase;letter-spacing:.07em;color:#94a3b8"><i class="fas fa-vials me-1"></i>Exámenes (${t.examenes.length}): </span>
${t.examenes.map(ex=>`<span class="badge" style="background:#dcfce7;color:#166534;margin:1px 2px">${e(ex.nombre)}</span>`).join('')}
</div>`;
}
// Comentarios
if (t.comentarios && t.comentarios.length) {
grid += `<div style="border-top:1px solid #e2e8f0;padding-top:8px">
<div style="font-size:.67rem;text-transform:uppercase;letter-spacing:.07em;color:#94a3b8;margin-bottom:5px"><i class="fas fa-comments me-1"></i>Comentarios</div>
${t.comentarios.map(c=>`<div style="font-size:.8rem;padding:4px 0;border-bottom:1px solid #f1f5f9">
<strong>${e(c.usuario_nombre)}</strong>
<span style="color:#94a3b8;font-size:.72rem;margin-left:6px">${fmt(c.creado_at)}</span>
<div style="color:#475569">${e(c.comentario)}</div>
</div>`).join('')}
</div>`;
}
return grid;
}
function filtrarTabla() {
const q = document.getElementById('filtroBusqueda').value.toLowerCase().trim();
if (!q) { renderTabla(_turnos); return; }