diff --git a/modules/turnero/api/cambiar_estado.php b/modules/turnero/api/cambiar_estado.php index 2f78efb..9e891e9 100644 --- a/modules/turnero/api/cambiar_estado.php +++ b/modules/turnero/api/cambiar_estado.php @@ -64,10 +64,60 @@ try { $estadoActual = $turno['estado']; - // Estados finales no modificables + // Estados finales — solo admin (sin role_id) puede reabrir if (in_array($estadoActual, ['finalizado', 'ausente', 'cancelado'], true)) { - $pdo->rollBack(); - jsonError("El turno ya está en estado '{$estadoActual}' y no puede modificarse.", 422); + $roleId = $_SESSION['admin_user']['role_id'] ?? null; + if (!empty($roleId)) { + $pdo->rollBack(); + jsonError("El turno ya está en estado '{$estadoActual}' y no puede modificarse.", 422); + } + + $permitidosAdmin = ['en_servicio', 'en_espera_lugar', 'en_recepcion', 'espera']; + if (!in_array($nuevoEstado, $permitidosAdmin, true)) { + $pdo->rollBack(); + jsonError("Estado destino inválido para reapertura. Opciones: " . implode(', ', $permitidosAdmin), 422); + } + + $sets = ['estado = ?']; + $binds = [$nuevoEstado]; + + if ($nuevoEstado === 'en_servicio') { + $sets[] = 'fin_lugar_at = NULL'; + $sets[] = 'inicio_lugar_at = COALESCE(inicio_lugar_at, NOW())'; + $sets[] = 'atendido_lugar_por = COALESCE(atendido_lugar_por, ?)'; + $binds[] = adminId(); + } elseif ($nuevoEstado === 'en_espera_lugar') { + $sets[] = 'fin_lugar_at = NULL'; + $sets[] = 'inicio_lugar_at = NULL'; + $sets[] = 'llamado_lugar_at = NULL'; + } elseif ($nuevoEstado === 'espera') { + $sets[] = 'llamado_recepcion_at = NULL'; + $sets[] = 'inicio_recepcion_at = NULL'; + $sets[] = 'fin_recepcion_at = NULL'; + $sets[] = 'llamado_lugar_at = NULL'; + $sets[] = 'inicio_lugar_at = NULL'; + $sets[] = 'fin_lugar_at = NULL'; + $sets[] = 'atendido_recepcion_por = NULL'; + $sets[] = 'atendido_lugar_por = NULL'; + $sets[] = 'recepcion_desk_id = NULL'; + } + + $sql = 'UPDATE turnero_turnos SET ' . implode(', ', $sets) . ' WHERE id = ?'; + $binds[] = $turnoId; + $pdo->prepare($sql)->execute($binds); + $pdo->commit(); + + $stmt = $pdo->prepare( + 'SELECT t.*, p.codigo AS prioridad_codigo, p.nombre AS prioridad_nombre, p.color AS prioridad_color + FROM turnero_turnos t + JOIN turnero_prioridades p ON p.id = t.prioridad_id + WHERE t.id = ?' + ); + $stmt->execute([$turnoId]); + $turnoActualizado = $stmt->fetch(PDO::FETCH_ASSOC); + + notificarSSE((int) $turnoActualizado['sesion_id']); + jsonOk(['turno' => $turnoActualizado], "Turno reabierto: estado actualizado a '{$nuevoEstado}'"); } // Verificar que la transición sea válida diff --git a/modules/turnero/views/historial.php b/modules/turnero/views/historial.php index c95696b..d54c45b 100644 --- a/modules/turnero/views/historial.php +++ b/modules/turnero/views/historial.php @@ -111,6 +111,21 @@ Layout::open('Historial de Turnos', 'fas fa-history'); .doc-pill.enviado { background:#fef9c3; color:#78350f; border:1px solid #fde68a; } .doc-pill.pendiente{ background:#f1f5f9; color:#64748b; border:1px solid #e2e8f0; } .docs-empty { font-size:.78rem; color:#94a3b8; font-style:italic; } + + /* ── Modal cambio de estado ── */ + .estado-modal-backdrop { + position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:1050; + display:flex; align-items:center; justify-content:center; + } + .estado-modal { + background:#fff; border-radius:14px; padding:24px 28px; width:340px; + box-shadow:0 20px 60px rgba(0,0,0,.2); + } + .estado-modal h5 { font-size:.95rem; font-weight:700; margin-bottom:4px; } + .estado-modal .sub { font-size:.78rem; color:#64748b; margin-bottom:16px; } + .estado-modal select { width:100%; padding:8px 10px; border:1px solid #e2e8f0; + border-radius:8px; font-size:.85rem; margin-bottom:16px; } + .estado-modal .actions { display:flex; gap:8px; justify-content:flex-end; } @@ -445,12 +460,17 @@ function renderTabla(turnos) { ${lbl} ${espMin} ${srvMin} - - + @@ -641,6 +661,82 @@ function exportarCSV() { window.open(`${API}export_historial.php?${params}`, '_blank'); } +// ── Cambio de estado ───────────────────────────────────────── +const ESTADOS_OPCIONES = { + espera : [{v:'en_recepcion', l:'En recepción'}, {v:'ausente', l:'Ausente'}, {v:'cancelado', l:'Cancelado'}], + en_recepcion : [{v:'en_espera_lugar', l:'Esp. lugar'}, {v:'espera', l:'Espera'}, {v:'ausente', l:'Ausente'}, {v:'cancelado', l:'Cancelado'}], + en_espera_lugar : [{v:'en_servicio', l:'En servicio'}, {v:'ausente', l:'Ausente'}, {v:'cancelado', l:'Cancelado'}], + en_servicio : [{v:'finalizado', l:'Finalizado'}, {v:'en_espera_lugar', l:'Esp. lugar'}, {v:'ausente', l:'Ausente'}, {v:'cancelado', l:'Cancelado'}], + // estados finales: reapertura (admin) + finalizado : [{v:'en_servicio', l:'Reabrir → En servicio'}, {v:'en_espera_lugar', l:'Reabrir → Esp. lugar'}, {v:'espera', l:'Reabrir → Espera'}], + ausente : [{v:'espera', l:'Reabrir → Espera'}, {v:'en_servicio', l:'Reabrir → En servicio'}], + cancelado : [{v:'espera', l:'Reabrir → Espera'}, {v:'en_servicio', l:'Reabrir → En servicio'}], +}; + +let _modalTurnoId = null; + +function abrirModalEstado(turnoId, estadoActual, nombre) { + _modalTurnoId = turnoId; + const opciones = ESTADOS_OPCIONES[estadoActual] || []; + if (!opciones.length) { alert('No hay transiciones disponibles para este estado.'); return; } + + const sel = opciones.map(o => ``).join(''); + const esReopetura = ['finalizado','ausente','cancelado'].includes(estadoActual); + + const backdrop = document.createElement('div'); + backdrop.className = 'estado-modal-backdrop'; + backdrop.id = 'estadoModalBackdrop'; + backdrop.innerHTML = ` +
+
Cambiar estado del turno
+
${nombre}${esReopetura ? ' Reapertura admin' : ''}
+ +
+ + +
+
`; + backdrop.addEventListener('click', cerrarModalEstado); + document.body.appendChild(backdrop); +} + +function cerrarModalEstado() { + document.getElementById('estadoModalBackdrop')?.remove(); + _modalTurnoId = null; +} + +async function confirmarCambioEstado() { + const nuevoEstado = document.getElementById('selectNuevoEstado').value; + if (!_modalTurnoId || !nuevoEstado) return; + + const btn = document.querySelector('#estadoModalBackdrop .btn-warning'); + btn.disabled = true; + btn.innerHTML = 'Guardando…'; + + try { + const res = await fetch(`${API}cambiar_estado.php`, { + method : 'POST', + headers: { 'Content-Type': 'application/json' }, + body : JSON.stringify({ turno_id: _modalTurnoId, nuevo_estado: nuevoEstado }), + }); + const json = await res.json(); + if (json.ok) { + cerrarModalEstado(); + buscar(_state.page); + } else { + alert('Error: ' + (json.error || 'No se pudo cambiar el estado.')); + btn.disabled = false; + btn.innerHTML = 'Confirmar'; + } + } catch(e) { + alert('Error de conexión'); + btn.disabled = false; + btn.innerHTML = 'Confirmar'; + } +} + // ── Helpers visuales ────────────────────────────────────────── function mostrarSpinner() { document.getElementById('tbody').innerHTML =