Compare commits
2
Commits
fix/pecl-https
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9749d726fa | ||
|
|
339ca8a40d |
@@ -64,12 +64,62 @@ try {
|
|||||||
|
|
||||||
$estadoActual = $turno['estado'];
|
$estadoActual = $turno['estado'];
|
||||||
|
|
||||||
// Estados finales no modificables
|
// Estados finales — solo admin (sin role_id) puede reabrir
|
||||||
if (in_array($estadoActual, ['finalizado', 'ausente', 'cancelado'], true)) {
|
if (in_array($estadoActual, ['finalizado', 'ausente', 'cancelado'], true)) {
|
||||||
|
$roleId = $_SESSION['admin_user']['role_id'] ?? null;
|
||||||
|
if (!empty($roleId)) {
|
||||||
$pdo->rollBack();
|
$pdo->rollBack();
|
||||||
jsonError("El turno ya está en estado '{$estadoActual}' y no puede modificarse.", 422);
|
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
|
// Verificar que la transición sea válida
|
||||||
$permitidos = TRANSICIONES[$estadoActual] ?? [];
|
$permitidos = TRANSICIONES[$estadoActual] ?? [];
|
||||||
if (!in_array($nuevoEstado, $permitidos, true)) {
|
if (!in_array($nuevoEstado, $permitidos, true)) {
|
||||||
|
|||||||
@@ -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.enviado { background:#fef9c3; color:#78350f; border:1px solid #fde68a; }
|
||||||
.doc-pill.pendiente{ background:#f1f5f9; color:#64748b; border:1px solid #e2e8f0; }
|
.doc-pill.pendiente{ background:#f1f5f9; color:#64748b; border:1px solid #e2e8f0; }
|
||||||
.docs-empty { font-size:.78rem; color:#94a3b8; font-style:italic; }
|
.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; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- ── Encabezado ────────────────────────────────────────────── -->
|
<!-- ── Encabezado ────────────────────────────────────────────── -->
|
||||||
@@ -445,12 +460,17 @@ 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>
|
<td class="text-nowrap">
|
||||||
<button class="btn btn-sm btn-outline-secondary py-0 px-2"
|
<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})"
|
||||||
title="Ver detalles">
|
title="Ver detalles">
|
||||||
<i class="fas fa-chevron-down" style="font-size:.65rem"></i>
|
<i class="fas fa-chevron-down" style="font-size:.65rem"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-warning py-0 px-2"
|
||||||
|
onclick="abrirModalEstado(${t.id}, '${t.estado}', '${nombre.replace(/'/g,\'\\\'')}')"
|
||||||
|
title="Cambiar estado">
|
||||||
|
<i class="fas fa-exchange-alt" style="font-size:.65rem"></i>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="${rowId}" class="detail-row" style="display:none">
|
<tr id="${rowId}" class="detail-row" style="display:none">
|
||||||
@@ -641,6 +661,82 @@ function exportarCSV() {
|
|||||||
window.open(`${API}export_historial.php?${params}`, '_blank');
|
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 => `<option value="${o.v}">${o.l}</option>`).join('');
|
||||||
|
const esReopetura = ['finalizado','ausente','cancelado'].includes(estadoActual);
|
||||||
|
|
||||||
|
const backdrop = document.createElement('div');
|
||||||
|
backdrop.className = 'estado-modal-backdrop';
|
||||||
|
backdrop.id = 'estadoModalBackdrop';
|
||||||
|
backdrop.innerHTML = `
|
||||||
|
<div class="estado-modal" onclick="event.stopPropagation()">
|
||||||
|
<h5><i class="fas fa-exchange-alt me-2 text-warning"></i>Cambiar estado del turno</h5>
|
||||||
|
<div class="sub">${nombre}${esReopetura ? ' <span class="badge bg-warning text-dark ms-1">Reapertura admin</span>' : ''}</div>
|
||||||
|
<select id="selectNuevoEstado">${sel}</select>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="cerrarModalEstado()">Cancelar</button>
|
||||||
|
<button class="btn btn-sm btn-warning" onclick="confirmarCambioEstado()">
|
||||||
|
<i class="fas fa-check me-1"></i>Confirmar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
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 = '<i class="fas fa-spinner fa-spin me-1"></i>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 = '<i class="fas fa-check me-1"></i>Confirmar';
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
alert('Error de conexión');
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = '<i class="fas fa-check me-1"></i>Confirmar';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Helpers visuales ──────────────────────────────────────────
|
// ── Helpers visuales ──────────────────────────────────────────
|
||||||
function mostrarSpinner() {
|
function mostrarSpinner() {
|
||||||
document.getElementById('tbody').innerHTML =
|
document.getElementById('tbody').innerHTML =
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
ini_set('display_errors', 1); error_reporting(E_ALL); // ponytail: quitar después de depurar
|
||||||
/**
|
/**
|
||||||
* ver_formulario_enviado.php — Ver respuesta de un formulario (imprimible / PDF)
|
* ver_formulario_enviado.php — Ver respuesta de un formulario (imprimible / PDF)
|
||||||
* Acceso admin/enfermero: ver_formulario_enviado.php?id=ENVIO_ID (requiere sesión)
|
* Acceso admin/enfermero: ver_formulario_enviado.php?id=ENVIO_ID (requiere sesión)
|
||||||
|
|||||||
Reference in New Issue
Block a user