feat(tiempos): agregar tiempo total puerta a puerta en historial y dashboard
- APIs: total_min = creado_at → fin_lugar_at (solo finalizados) - Dashboard: KPI card 'Total prom. puerta a puerta' + columna Total en tabla - Historial: columna Total en tabla Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c357f18b61
commit
44b737c253
@@ -73,7 +73,15 @@ $stmtRes = $pdo->prepare(
|
|||||||
THEN TIMESTAMPDIFF(SECOND, inicio_lugar_at, fin_lugar_at) / 60.0
|
THEN TIMESTAMPDIFF(SECOND, inicio_lugar_at, fin_lugar_at) / 60.0
|
||||||
END
|
END
|
||||||
), 1
|
), 1
|
||||||
) AS tiempo_servicio_promedio_min
|
) AS tiempo_servicio_promedio_min,
|
||||||
|
ROUND(
|
||||||
|
AVG(
|
||||||
|
CASE
|
||||||
|
WHEN fin_lugar_at IS NOT NULL AND creado_at IS NOT NULL
|
||||||
|
THEN TIMESTAMPDIFF(SECOND, creado_at, fin_lugar_at) / 60.0
|
||||||
|
END
|
||||||
|
), 1
|
||||||
|
) AS tiempo_total_promedio_min
|
||||||
FROM turnero_turnos
|
FROM turnero_turnos
|
||||||
WHERE sesion_id = ?"
|
WHERE sesion_id = ?"
|
||||||
);
|
);
|
||||||
@@ -133,7 +141,8 @@ $stmtTurnos = $pdo->prepare(
|
|||||||
ul.full_name AS atendido_lugar_nombre,
|
ul.full_name AS atendido_lugar_nombre,
|
||||||
ts.observaciones AS notas,
|
ts.observaciones AS notas,
|
||||||
ROUND(TIMESTAMPDIFF(SECOND, t.creado_at, COALESCE(t.inicio_recepcion_at, NOW())) / 60.0, 1) AS espera_min,
|
ROUND(TIMESTAMPDIFF(SECOND, t.creado_at, COALESCE(t.inicio_recepcion_at, NOW())) / 60.0, 1) AS espera_min,
|
||||||
ROUND(TIMESTAMPDIFF(SECOND, t.inicio_lugar_at, COALESCE(t.fin_lugar_at, NOW())) / 60.0, 1) AS servicio_min
|
ROUND(TIMESTAMPDIFF(SECOND, t.inicio_lugar_at, COALESCE(t.fin_lugar_at, NOW())) / 60.0, 1) AS servicio_min,
|
||||||
|
ROUND(TIMESTAMPDIFF(SECOND, t.creado_at, t.fin_lugar_at) / 60.0, 1) AS total_min
|
||||||
FROM turnero_turnos t
|
FROM turnero_turnos t
|
||||||
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
||||||
LEFT JOIN turnero_lugares l ON l.id = t.lugar_destino_id
|
LEFT JOIN turnero_lugares l ON l.id = t.lugar_destino_id
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ $sqlTurnos = "
|
|||||||
AS espera_min,
|
AS espera_min,
|
||||||
ROUND(TIMESTAMPDIFF(SECOND, t.inicio_lugar_at,
|
ROUND(TIMESTAMPDIFF(SECOND, t.inicio_lugar_at,
|
||||||
COALESCE(t.fin_lugar_at, NOW())) / 60.0, 1)
|
COALESCE(t.fin_lugar_at, NOW())) / 60.0, 1)
|
||||||
AS servicio_min
|
AS servicio_min,
|
||||||
|
ROUND(TIMESTAMPDIFF(SECOND, t.creado_at, t.fin_lugar_at) / 60.0, 1)
|
||||||
|
AS total_min
|
||||||
FROM turnero_turnos t
|
FROM turnero_turnos t
|
||||||
JOIN turnero_sesiones s ON s.id = t.sesion_id
|
JOIN turnero_sesiones s ON s.id = t.sesion_id
|
||||||
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ try {
|
|||||||
<th>Estado</th>
|
<th>Estado</th>
|
||||||
<th>Espera</th>
|
<th>Espera</th>
|
||||||
<th>Serv.</th>
|
<th>Serv.</th>
|
||||||
|
<th>Total</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -344,6 +345,7 @@ function renderDashboard(json) {
|
|||||||
const cancelados= parseInt(resumen.cancelados || 0);
|
const cancelados= parseInt(resumen.cancelados || 0);
|
||||||
const tEspera = resumen.tiempo_espera_promedio_min;
|
const tEspera = resumen.tiempo_espera_promedio_min;
|
||||||
const tServicio = resumen.tiempo_servicio_promedio_min;
|
const tServicio = resumen.tiempo_servicio_promedio_min;
|
||||||
|
const tTotal = resumen.tiempo_total_promedio_min;
|
||||||
const pct = total > 0 ? Math.round(atendidos / total * 100) : 0;
|
const pct = total > 0 ? Math.round(atendidos / total * 100) : 0;
|
||||||
|
|
||||||
document.getElementById('kpiGrid').innerHTML = `
|
document.getElementById('kpiGrid').innerHTML = `
|
||||||
@@ -352,8 +354,9 @@ function renderDashboard(json) {
|
|||||||
${kpiCard(enEspera, '⏳ En espera', '')}
|
${kpiCard(enEspera, '⏳ En espera', '')}
|
||||||
${kpiCard(ausentes, '🚫 Ausentes', '')}
|
${kpiCard(ausentes, '🚫 Ausentes', '')}
|
||||||
${kpiCard(cancelados,'❌ Cancelados', '')}
|
${kpiCard(cancelados,'❌ Cancelados', '')}
|
||||||
${kpiCard(tEspera !== null ? tEspera + ' min' : '—', '⏱ Espera prom.', 'hasta recepción')}
|
${kpiCard(tEspera !== null ? tEspera + ' min' : '—', '⏱ Espera prom.', 'hasta recepción')}
|
||||||
${kpiCard(tServicio !== null ? tServicio + ' min' : '—', '⏱ Servicio prom.', 'en lugar')}
|
${kpiCard(tServicio !== null ? tServicio + ' min' : '—', '⏱ Servicio prom.', 'en lugar')}
|
||||||
|
${kpiCard(tTotal !== null ? tTotal + ' min' : '—', '⏱ Total prom.', 'puerta a puerta')}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// ── Consentimientos ────────────────────────────────────
|
// ── Consentimientos ────────────────────────────────────
|
||||||
@@ -451,6 +454,7 @@ function renderTabla(turnos) {
|
|||||||
const estadoLbl = labelEstado[t.estado] || t.estado;
|
const estadoLbl = labelEstado[t.estado] || t.estado;
|
||||||
const espMin = t.espera_min !== null ? t.espera_min + ' m' : '—';
|
const espMin = t.espera_min !== null ? t.espera_min + ' m' : '—';
|
||||||
const srvMin = t.servicio_min !== null ? t.servicio_min + ' m' : '—';
|
const srvMin = t.servicio_min !== null ? t.servicio_min + ' m' : '—';
|
||||||
|
const totMin = t.total_min !== null ? t.total_min + ' m' : '—';
|
||||||
const rowId = `dash-det-${t.id}`;
|
const rowId = `dash-det-${t.id}`;
|
||||||
|
|
||||||
// Recepción
|
// Recepción
|
||||||
@@ -478,6 +482,7 @@ function renderTabla(turnos) {
|
|||||||
<td><span class="estado-badge ${estadoCls}">${estadoLbl}</span></td>
|
<td><span class="estado-badge ${estadoCls}">${estadoLbl}</span></td>
|
||||||
<td>${espMin}</td>
|
<td>${espMin}</td>
|
||||||
<td>${srvMin}</td>
|
<td>${srvMin}</td>
|
||||||
|
<td>${totMin}</td>
|
||||||
<td><i class="fas fa-chevron-right" style="font-size:.65rem;color:#94a3b8;transition:transform .2s" id="ico-${t.id}"></i></td>
|
<td><i class="fas fa-chevron-right" style="font-size:.65rem;color:#94a3b8;transition:transform .2s" id="ico-${t.id}"></i></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="${rowId}" class="det-row" style="display:none">
|
<tr id="${rowId}" class="det-row" style="display:none">
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ Layout::open('Historial de Turnos', 'fas fa-history');
|
|||||||
<th>Estado</th>
|
<th>Estado</th>
|
||||||
<th>Espera</th>
|
<th>Espera</th>
|
||||||
<th>Servicio</th>
|
<th>Servicio</th>
|
||||||
|
<th>Total</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -407,7 +408,7 @@ const ESTADO_LBL = {
|
|||||||
function renderTabla(turnos) {
|
function renderTabla(turnos) {
|
||||||
const tbody = document.getElementById('tbody');
|
const tbody = document.getElementById('tbody');
|
||||||
if (!turnos.length) {
|
if (!turnos.length) {
|
||||||
tbody.innerHTML = `<tr><td colspan="11">
|
tbody.innerHTML = `<tr><td colspan="12">
|
||||||
<div class="empty-state"><i class="fas fa-search"></i>Sin resultados para los filtros aplicados</div>
|
<div class="empty-state"><i class="fas fa-search"></i>Sin resultados para los filtros aplicados</div>
|
||||||
</td></tr>`;
|
</td></tr>`;
|
||||||
return;
|
return;
|
||||||
@@ -425,6 +426,7 @@ function renderTabla(turnos) {
|
|||||||
: '—';
|
: '—';
|
||||||
const espMin = t.espera_min != null ? t.espera_min + ' m' : '—';
|
const espMin = t.espera_min != null ? t.espera_min + ' m' : '—';
|
||||||
const srvMin = t.servicio_min != null ? t.servicio_min + ' m' : '—';
|
const srvMin = t.servicio_min != null ? t.servicio_min + ' m' : '—';
|
||||||
|
const totMin = t.total_min != null ? t.total_min + ' m' : '—';
|
||||||
const tel = esc(t.paciente_cel || '—');
|
const tel = esc(t.paciente_cel || '—');
|
||||||
const priCls = esc(t.prioridad_color || '#888');
|
const priCls = esc(t.prioridad_color || '#888');
|
||||||
const cls = ESTADO_CLS[t.estado] || 'eb-finalizado';
|
const cls = ESTADO_CLS[t.estado] || 'eb-finalizado';
|
||||||
@@ -467,6 +469,7 @@ function renderTabla(turnos) {
|
|||||||
<td><span class="eb ${cls}">${lbl}</span></td>
|
<td><span class="eb ${cls}">${lbl}</span></td>
|
||||||
<td class="text-nowrap">${espMin}</td>
|
<td class="text-nowrap">${espMin}</td>
|
||||||
<td class="text-nowrap">${srvMin}</td>
|
<td class="text-nowrap">${srvMin}</td>
|
||||||
|
<td class="text-nowrap">${totMin}</td>
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
<button class="btn btn-sm btn-outline-secondary py-0 px-2 me-1"
|
<button class="btn btn-sm btn-outline-secondary py-0 px-2 me-1"
|
||||||
onclick="toggleDetalle('${rowId}', this, ${t.id})"
|
onclick="toggleDetalle('${rowId}', this, ${t.id})"
|
||||||
|
|||||||
Reference in New Issue
Block a user