Tres correcciones sobre lo reportado en E042. 1. La bitácora daba por hecho que firmado_at era del paciente, y lo anunciaba así en documentos que el paciente nunca firma: el F-LAB-08 solo lo firma el profesional y el F-LAB-28 lleva una firma por toma. Ahora mira qué firma hay realmente guardada; si no hay ninguna dice «completado» o «protocolo cerrado», según el caso. 2. Los protocolos prolongados desglosan cada toma con su hora y su firmante, que es el dato que interesa: en E042 se ve que el minuto 0 lo firmó Yesica y el minuto 150 Omaira. La firma general no distinguía eso. 3. firmar_profesional_consentimiento.php aceptaba el nombre del firmante que mandaba el navegador, y ese valor se carga al abrir la página: si el personal cambiaba de turno sin recargar, la firma final quedaba a nombre de quien abrió el formulario. Ahora se resuelve desde la sesión en el servidor, igual que ya hacía guardar_toma.php para cada toma. Además, llamar y rellamar no dejaban rastro —el turno solo guarda la hora del último llamado, que el rellamado sobrescribe—, así que la bitácora mostraba «Llamado a X» sin autor. Ahora se auditan ambos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
170 lines
6.7 KiB
PHP
170 lines
6.7 KiB
PHP
<?php
|
|
/**
|
|
* POST /modules/turnero/api/llamar_turno.php
|
|
* Llama el siguiente turno de una cola con el motor de prioridades.
|
|
* Requiere login + módulo turnero.
|
|
*
|
|
* Body JSON:
|
|
* area string requerido "recepcion" | "lugar"
|
|
* lugar_id int requerido si area = "lugar"
|
|
*/
|
|
|
|
require_once __DIR__ . '/_helpers.php';
|
|
require_once __DIR__ . '/../../../classes/lab/ActividadAdmin.php';
|
|
requireMethod('POST');
|
|
requireTurnero();
|
|
|
|
$datos = inputJson();
|
|
$area = $datos['area'] ?? '';
|
|
$lugarId = isset($datos['lugar_id']) ? (int) $datos['lugar_id'] : null;
|
|
$deskId = isset($datos['desk_id']) ? (int) $datos['desk_id'] : null;
|
|
$turnoId = isset($datos['turno_id']) ? (int) $datos['turno_id'] : null; // llamar uno específico
|
|
|
|
// ── Validación ────────────────────────────────────────────────
|
|
if (!in_array($area, ['recepcion', 'lugar'], true)) {
|
|
jsonError('area debe ser "recepcion" o "lugar".');
|
|
}
|
|
if ($area === 'lugar' && !$lugarId) {
|
|
jsonError('lugar_id es obligatorio cuando area = "lugar".');
|
|
}
|
|
|
|
$pdo = db();
|
|
// Obtener sesión del día ANTES de abrir la transacción para que un posible
|
|
// INSERT de sesión nueva no quede dentro del rollback.
|
|
$sesionHoy = obtenerOCrearSesionHoy();
|
|
$pdo->beginTransaction();
|
|
|
|
try {
|
|
if ($area === 'recepcion') {
|
|
// Cola recepción: solo turnos de la sesión activa de hoy
|
|
$turno = siguienteTurnoEnCola('espera', null, null, $sesionHoy);
|
|
|
|
if (!$turno) {
|
|
$pdo->rollBack();
|
|
jsonOk(['turno' => null], 'Cola de recepción vacía');
|
|
}
|
|
|
|
// Actualizar estado + timestamp
|
|
// $deskId puede ser null si el recepcionista no seleccionó escritorio;
|
|
// se guarda igual (puede quedar NULL) pero la query del display ya no
|
|
// usa IS NULL para evitar que aparezca en todos los escritorios.
|
|
$stmt = $pdo->prepare(
|
|
'UPDATE turnero_turnos
|
|
SET estado = "en_recepcion",
|
|
llamado_recepcion_at = NOW(),
|
|
inicio_recepcion_at = NOW(),
|
|
atendido_recepcion_por = ?,
|
|
recepcion_desk_id = ?
|
|
WHERE id = ? AND estado = "espera"'
|
|
);
|
|
$stmt->execute([adminId(), $deskId, $turno['id']]);
|
|
|
|
if ($stmt->rowCount() === 0) {
|
|
// Otro proceso lo tomó primero (race condition)
|
|
$pdo->rollBack();
|
|
jsonError('El turno ya fue tomado por otro operador. Intente de nuevo.', 409);
|
|
}
|
|
|
|
} else {
|
|
// Cola de un Lugar específico: turnos 'en_espera_lugar' asignados a ese lugar
|
|
if ($turnoId) {
|
|
// Llamar uno específico por id (puede ser en_espera_lugar o ya en_servicio para re-llamar)
|
|
$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 = ? AND t.estado IN ("en_espera_lugar", "en_servicio")'
|
|
);
|
|
$stmt->execute([$turnoId]);
|
|
$turno = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
if (!$turno) {
|
|
$pdo->rollBack();
|
|
jsonError('El turno no está en espera o no existe.', 404);
|
|
}
|
|
} else {
|
|
$turno = siguienteTurnoEnCola('en_espera_lugar', $lugarId, null, $sesionHoy);
|
|
if (!$turno) {
|
|
$pdo->rollBack();
|
|
jsonOk(['turno' => null], 'Cola del lugar vacía');
|
|
}
|
|
}
|
|
|
|
// Re-llamar: si ya está en_servicio en ESTA estación, solo actualiza llamado_at
|
|
if ($turno['estado'] === 'en_servicio' && $turno['lugar_destino_id'] == $lugarId) {
|
|
$pdo->prepare(
|
|
'UPDATE turnero_turnos SET llamado_lugar_at = NOW() WHERE id = ?'
|
|
)->execute([$turno['id']]);
|
|
$pdo->commit();
|
|
// Refrescar y retornar
|
|
$stmt = $pdo->prepare(
|
|
'SELECT t.*, p.codigo AS prioridad_codigo, p.nombre AS prioridad_nombre, p.color AS prioridad_color,
|
|
COALESCE(ts.solo_muestras, 0) AS solo_muestras
|
|
FROM turnero_turnos t
|
|
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
|
LEFT JOIN turnero_solicitudes ts ON ts.turno_id = t.id
|
|
WHERE t.id = ?'
|
|
);
|
|
$stmt->execute([$turno['id']]);
|
|
$turnoActualizado = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
notificarSSE((int)$turnoActualizado['sesion_id']);
|
|
jsonOk(['turno' => $turnoActualizado], "Turno {$turnoActualizado['codigo']} re-llamado");
|
|
}
|
|
|
|
$stmt = $pdo->prepare(
|
|
'UPDATE turnero_turnos
|
|
SET estado = "en_servicio",
|
|
llamado_lugar_at = NOW(),
|
|
inicio_lugar_at = NOW(),
|
|
atendido_lugar_por= ?,
|
|
lugar_destino_id = ?
|
|
WHERE id = ? AND estado = "en_espera_lugar"'
|
|
);
|
|
$stmt->execute([adminId(), $lugarId, $turno['id']]);
|
|
|
|
if ($stmt->rowCount() === 0) {
|
|
$pdo->rollBack();
|
|
jsonError('El turno ya fue tomado por otro operador. Intente de nuevo.', 409);
|
|
}
|
|
}
|
|
|
|
// Refrescar datos del turno actualizado
|
|
$stmt = $pdo->prepare(
|
|
'SELECT t.*,
|
|
p.codigo AS prioridad_codigo,
|
|
p.nombre AS prioridad_nombre,
|
|
p.color AS prioridad_color,
|
|
COALESCE(ts.solo_muestras, 0) AS solo_muestras
|
|
FROM turnero_turnos t
|
|
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
|
LEFT JOIN turnero_solicitudes ts ON ts.turno_id = t.id
|
|
WHERE t.id = ?'
|
|
);
|
|
$stmt->execute([$turno['id']]);
|
|
$turnoActualizado = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
$pdo->commit();
|
|
notificarSSE((int) $turnoActualizado['sesion_id']);
|
|
|
|
// Quién llamó no se guardaba en ninguna parte: el turno solo conserva la hora
|
|
// del último llamado, así que en la bitácora aparecía «Llamado a X» sin autor.
|
|
try {
|
|
(new ActividadAdmin())->registrar(
|
|
adminId(), 'turnero', 'llamar_turno', $turnoId,
|
|
[
|
|
'turno_id' => $turnoId,
|
|
'turno_codigo' => $turnoActualizado['codigo'] ?? null,
|
|
]
|
|
);
|
|
} catch (\Throwable $e) {
|
|
error_log('[llamar_turno] auditoría: ' . $e->getMessage());
|
|
}
|
|
|
|
jsonOk(['turno' => $turnoActualizado], "Turno {$turnoActualizado['codigo']} llamado");
|
|
|
|
} catch (\Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
jsonError('Error al llamar turno: ' . $e->getMessage(), 500);
|
|
}
|