Files
whatsapp/modules/turnero/api/cambiar_estado.php
T

174 lines
6.3 KiB
PHP

<?php
/**
* POST /modules/turnero/api/cambiar_estado.php
* Avanza o cancela el estado de un turno.
* Requiere login + módulo turnero.
*
* Body JSON:
* turno_id int requerido
* nuevo_estado string requerido (ver transiciones válidas abajo)
* lugar_id int requerido cuando nuevo_estado = "en_espera_lugar"
*
* Transiciones permettidas:
* espera → en_recepcion
* en_recepcion → en_espera_lugar, ausente, cancelado
* en_espera_lugar → en_servicio, ausente, cancelado
* en_servicio → finalizado, ausente, cancelado
* (cualquier) → ausente, cancelado
*
* Bloqueo especial:
* en_espera_lugar → en_servicio requiere que todos los consentimientos
* del turno estén en estado 'firmado' o 'rechazado'.
*/
require_once __DIR__ . '/_helpers.php';
requireMethod('POST');
requireTurnero();
$datos = inputJson();
$turnoId = isset($datos['turno_id']) ? (int) $datos['turno_id'] : 0;
$nuevoEstado = isset($datos['nuevo_estado']) ? trim((string) $datos['nuevo_estado']) : '';
$lugarId = isset($datos['lugar_id']) ? (int) $datos['lugar_id'] : null;
// ── Validación básica ─────────────────────────────────────────
if ($turnoId <= 0) jsonError('turno_id inválido.');
if ($nuevoEstado === '') jsonError('nuevo_estado es requerido.');
// Mapa de transiciones válidas [estado_actual => [estados_destino_permitidos]]
const TRANSICIONES = [
'espera' => ['en_recepcion', 'ausente', 'cancelado'],
'en_recepcion' => ['en_espera_lugar', 'ausente', 'cancelado'],
'en_espera_lugar' => ['en_servicio', 'ausente', 'cancelado'],
'en_servicio' => ['finalizado', 'ausente', 'cancelado', 'en_espera_lugar'],
];
$pdo = db();
$pdo->beginTransaction();
try {
// Leer turno actual con lock
$stmt = $pdo->prepare(
'SELECT t.*, p.codigo AS prioridad_codigo
FROM turnero_turnos t
JOIN turnero_prioridades p ON p.id = t.prioridad_id
WHERE t.id = ?
FOR UPDATE'
);
$stmt->execute([$turnoId]);
$turno = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$turno) {
$pdo->rollBack();
jsonError('Turno no encontrado.', 404);
}
$estadoActual = $turno['estado'];
// Estados finales no modificables
if (in_array($estadoActual, ['finalizado', 'ausente', 'cancelado'], true)) {
$pdo->rollBack();
jsonError("El turno ya está en estado '{$estadoActual}' y no puede modificarse.", 422);
}
// Verificar que la transición sea válida
$permitidos = TRANSICIONES[$estadoActual] ?? [];
if (!in_array($nuevoEstado, $permitidos, true)) {
$pdo->rollBack();
jsonError(
"Transición '{$estadoActual}' → '{$nuevoEstado}' no está permitida. "
. "Transiciones válidas: " . implode(', ', $permitidos),
422
);
}
// ── Regla especial: en_espera_lugar → en_servicio ─────────
if ($nuevoEstado === 'en_servicio') {
$cStmt = $pdo->prepare(
"SELECT COUNT(*) FROM turnero_consentimientos
WHERE turno_id = ?
AND estado NOT IN ('firmado', 'rechazado')"
);
$cStmt->execute([$turnoId]);
$pendientes = (int) $cStmt->fetchColumn();
if ($pendientes > 0) {
$pdo->rollBack();
jsonError(
"Hay {$pendientes} consentimiento(s) pendiente(s). "
. "El turno no puede pasar a 'en_servicio' hasta que todos estén firmados o rechazados.",
422
);
}
}
// ── Construir UPDATE dinámico ─────────────────────────────
$sets = ['estado = ?'];
$binds = [$nuevoEstado];
switch ($nuevoEstado) {
case 'en_recepcion':
$sets[] = 'llamado_recepcion_at = COALESCE(llamado_recepcion_at, NOW())';
$sets[] = 'inicio_recepcion_at = COALESCE(inicio_recepcion_at, NOW())';
$sets[] = 'atendido_recepcion_por = COALESCE(atendido_recepcion_por, ?)';
$binds[] = adminId();
break;
case 'en_espera_lugar':
// Requiere lugar_id (al volver desde en_servicio se conserva lugar_destino_id actual)
if ($lugarId) {
$sets[] = 'lugar_destino_id = ?';
$binds[] = $lugarId;
}
$sets[] = 'llamado_lugar_at = NULL';
$sets[] = 'inicio_lugar_at = NULL';
$sets[] = 'fin_recepcion_at = COALESCE(fin_recepcion_at, NOW())';
$sets[] = 'atendido_recepcion_por = COALESCE(atendido_recepcion_por, ?)';
$binds[] = adminId();
break;
case 'en_servicio':
$sets[] = 'llamado_lugar_at = COALESCE(llamado_lugar_at, NOW())';
$sets[] = 'inicio_lugar_at = COALESCE(inicio_lugar_at, NOW())';
$sets[] = 'atendido_lugar_por = COALESCE(atendido_lugar_por, ?)';
$binds[] = adminId();
break;
case 'finalizado':
$sets[] = 'fin_lugar_at = COALESCE(fin_lugar_at, NOW())';
$sets[] = 'atendido_lugar_por = COALESCE(atendido_lugar_por, ?)';
$binds[] = adminId();
break;
case 'ausente':
case 'cancelado':
// Sin timestamps adicionales; simplemente cambio de estado
break;
}
$sql = 'UPDATE turnero_turnos SET ' . implode(', ', $sets) . ' WHERE id = ?';
$binds[] = $turnoId;
$pdo->prepare($sql)->execute($binds);
$pdo->commit();
// Refrescar y devolver turno actualizado
$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], "Estado actualizado a '{$nuevoEstado}'");
} catch (\Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
jsonError('Error al cambiar estado: ' . $e->getMessage(), 500);
}