Motivo al marcar ausente, y notas visibles con su origen en dashboard e historial

Marcar ausente solo pedía confirmación: el turno quedaba registrado sin ninguna
explicación, y hoy son 6 en un día. Ahora se pide el motivo y se guarda como
comentario de recepción, no como columna nueva: así aparece en todas las
pantallas que ya muestran comentarios, sin duplicar el dato ni tocar el esquema.

Dashboard e historial mostraban las notas sin indicar de dónde venían, así que no
se distinguía una observación de toma de muestras de una de recepción. Se agrega
la insignia de origen con los mismos colores que ya usa la bandeja, y el título
pasa a "Notas y observaciones" con su cantidad.

El historial además traía solo las 3 últimas por turno y en orden inverso. Con
recepción empezando a dejar notas eso habría escondido justamente el motivo de
una ausencia sin que nada indicara que faltaban; ahora trae hasta 20, en orden
cronológico.

De las 114 notas existentes, todas son de toma de muestras: recepción nunca dejó
ninguna porque no tenía dónde hacerlo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-11 10:37:33 -05:00
co-authored by Claude Opus 5
parent 4bc22bd00c
commit 813b712df0
4 changed files with 44 additions and 6 deletions
+4 -2
View File
@@ -183,13 +183,15 @@ if ($turnoIds) {
"SELECT tc.turno_id, tc.usuario_nombre, tc.comentario, tc.tipo, tc.creado_at
FROM turnero_comentarios tc
WHERE tc.turno_id IN ($ph)
ORDER BY tc.turno_id, tc.creado_at DESC"
ORDER BY tc.turno_id, tc.creado_at ASC"
);
$stmtCom->execute($turnoIds);
foreach ($stmtCom->fetchAll(PDO::FETCH_ASSOC) as $row) {
$tid = (int)$row['turno_id'];
if (!isset($comentarios[$tid])) $comentarios[$tid] = [];
if (count($comentarios[$tid]) < 3) {
// Se traen todos: cortar en 3 escondía notas —entre ellas el motivo de
// una ausencia— sin que nada indicara que faltaban.
if (count($comentarios[$tid]) < 20) {
unset($row['turno_id']);
$comentarios[$tid][] = $row;
}
+9 -1
View File
@@ -83,6 +83,13 @@ Layout::open('Dashboard Turnero', 'fas fa-chart-bar');
.det-row td { padding:0 !important; border-top:none !important; }
.det-inner { padding:12px 16px 14px; border-top:2px solid color-mix(in srgb, var(--brand,#1565c0) 20%, #e2e8f0); background:#f8fafc; }
/* ── Origen de cada nota ── */
.com-tipo { font-size:.62rem; font-weight:700; letter-spacing:.04em; padding:1px 7px;
border-radius:10px; text-transform:uppercase; margin-right:6px; }
.com-tipo.recepcion { background:#dbeafe; color:#1d4ed8; }
.com-tipo.muestras { background:#dcfce7; color:#15803d; }
.com-tipo.general { background:#fef3c7; color:#92400e; }
/* ── Sesión badge ── */
.sesion-open { background:#dcfce7; color:#15803d; }
.sesion-closed { background:#f1f5f9; color:#64748b; }
@@ -657,8 +664,9 @@ function renderDashDetalle(t) {
// 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>
<div style="font-size:.67rem;text-transform:uppercase;letter-spacing:.07em;color:#94a3b8;margin-bottom:5px"><i class="fas fa-comments me-1"></i>Notas y observaciones (${t.comentarios.length})</div>
${t.comentarios.map(c=>`<div style="font-size:.8rem;padding:4px 0;border-bottom:1px solid #f1f5f9">
<span class="com-tipo ${e(c.tipo||'general')}">${({recepcion:'Recepción',muestras:'Toma de muestras',general:'General'})[c.tipo]||c.tipo}</span>
<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>
+9 -1
View File
@@ -100,6 +100,13 @@ Layout::open('Historial de Turnos', 'fas fa-history');
.detail-extra .d-lbl { font-size:.67rem; text-transform:uppercase; letter-spacing:.07em; color:#94a3b8; margin-bottom:5px; }
.detail-comment { padding:5px 0; border-bottom:1px solid #f1f5f9; font-size:.82rem; }
.detail-comment:last-child { border-bottom:none; }
/* ── Origen de cada nota ── */
.com-tipo { font-size:.62rem; font-weight:700; letter-spacing:.04em; padding:1px 7px;
border-radius:10px; text-transform:uppercase; margin-right:6px; }
.com-tipo.recepcion { background:#dbeafe; color:#1d4ed8; }
.com-tipo.muestras { background:#dcfce7; color:#15803d; }
.com-tipo.general { background:#fef3c7; color:#92400e; }
/* ── Documentos firmados ── */
.docs-section { margin-top:10px; padding-top:10px; border-top:1px solid #e2e8f0; }
@@ -567,9 +574,10 @@ function renderDetalle(t) {
let comHtml = '';
if (t.comentarios && t.comentarios.length) {
comHtml = `<div class="detail-extra">
<div class="d-lbl"><i class="fas fa-comments me-1 text-primary"></i>Comentarios</div>
<div class="d-lbl"><i class="fas fa-comments me-1 text-primary"></i>Notas y observaciones (${t.comentarios.length})</div>
<div>${t.comentarios.map(c => `
<div class="detail-comment">
<span class="com-tipo ${esc(c.tipo||'general')}">${({recepcion:'Recepción',muestras:'Toma de muestras',general:'General'})[c.tipo]||c.tipo}</span>
<span class="fw-semibold">${esc(c.usuario_nombre)}</span>
<span class="text-muted small ms-1">${c.creado_at ? new Date(c.creado_at.replace(' ','T')).toLocaleString('es-CO',{dateStyle:'short',timeStyle:'short'}) : ''}</span>
<div class="text-secondary">${esc(c.comentario)}</div>
+22 -2
View File
@@ -2307,12 +2307,32 @@ async function pasarALugar() {
// ── Ausente ───────────────────────────────────────────────────
async function marcarAusente() {
if (!turnoActivo) return;
if (!confirm(`¿Marcar turno ${turnoActivo.codigo} como AUSENTE?`)) return;
// El motivo se guarda como comentario de recepción: así aparece en la
// bandeja, el historial y el dashboard sin duplicar el dato.
const motivo = prompt(
`Marcar el turno ${turnoActivo.codigo} como AUSENTE.\n\n`
+ '¿Por qué? (no respondió al llamado, se retiró, reprogramó…)');
if (motivo === null) return; // canceló
const motivoLimpio = motivo.trim();
if (!motivoLimpio) { mostrarError('Indique el motivo para marcar como ausente.'); return; }
const turnoId = turnoActivo.id;
await fetch(API + 'comentarios.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
turno_id: turnoId,
comentario: 'Marcado ausente: ' + motivoLimpio,
tipo: 'recepcion',
}),
}).catch(() => {});
await fetch(API + 'cambiar_estado.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ turno_id: turnoActivo.id, nuevo_estado: 'ausente' }),
body: JSON.stringify({ turno_id: turnoId, nuevo_estado: 'ausente' }),
});
turnoActivo = null;