Files
whatsapp/modules/turnero/api/ocupar_lugar.php
2026-06-16 19:09:59 -05:00

210 lines
8.0 KiB
PHP

<?php
/**
* POST /modules/turnero/api/ocupar_lugar.php
*
* Gestiona la ocupación exclusiva de un puesto (recepción o muestras).
* Un puesto solo puede tener UN usuario activo.
*
* Actions:
* claim → reclamar el puesto (o renovar si ya es mío)
* ping → renovar heartbeat (cada 60 s)
* release → liberar el puesto
* solicitar → usuario B pide transferencia al ocupante A
* check_solicitud → A consulta si hay una solicitud pendiente
* denegar → A rechaza la solicitud
* aceptar → A acepta y libera el puesto
* check_transfer → B consulta si el puesto quedó libre o fue denegado
* disponibles → lista puestos del mismo tipo sin ocupante activo
*/
require_once __DIR__ . '/_helpers.php';
requireMethod('POST');
$datos = inputJson();
$lugarId = (int)($datos['lugar_id'] ?? 0);
$action = $datos['action'] ?? 'claim';
if ($lugarId <= 0) jsonError('lugar_id inválido.');
$validActions = ['claim','ping','release','solicitar','check_solicitud','denegar','aceptar','check_transfer','disponibles'];
if (!in_array($action, $validActions, true)) jsonError('action inválida.');
$pdo = db();
$userId = (int)adminId();
$nombre = $_SESSION['admin_user']['full_name']
?? $_SESSION['admin_user']['username']
?? 'Usuario';
$timeout = 'NOW() - INTERVAL 2 MINUTE';
/* ── release ─────────────────────────────────────────────────── */
if ($action === 'release') {
$pdo->prepare(
"DELETE FROM turnero_lugar_ocupacion WHERE lugar_id = ? AND user_id = ?"
)->execute([$lugarId, $userId]);
jsonOk(['liberado' => true]);
}
/* ── ping ────────────────────────────────────────────────────── */
if ($action === 'ping') {
$pdo->prepare(
"UPDATE turnero_lugar_ocupacion SET ultimo_ping = NOW()
WHERE lugar_id = ? AND user_id = ?"
)->execute([$lugarId, $userId]);
jsonOk(['ok' => true]);
}
/* ── aceptar (ocupante A acepta la solicitud y libera) ───────── */
if ($action === 'aceptar') {
$pdo->prepare(
"DELETE FROM turnero_lugar_ocupacion WHERE lugar_id = ? AND user_id = ?"
)->execute([$lugarId, $userId]);
jsonOk(['liberado' => true]);
}
/* ── denegar (ocupante A rechaza) ────────────────────────────── */
if ($action === 'denegar') {
$pdo->prepare(
"UPDATE turnero_lugar_ocupacion
SET solicitud_resultado = 'denegada'
WHERE lugar_id = ? AND user_id = ?"
)->execute([$lugarId, $userId]);
jsonOk(['ok' => true]);
}
/* ── check_solicitud (A consulta si hay solicitud pendiente) ─── */
if ($action === 'check_solicitud') {
$st = $pdo->prepare(
"SELECT solicitud_user_id, solicitud_nombre, solicitud_at, solicitud_resultado
FROM turnero_lugar_ocupacion
WHERE lugar_id = ? AND user_id = ?"
);
$st->execute([$lugarId, $userId]);
$row = $st->fetch(PDO::FETCH_ASSOC);
if (!$row || $row['solicitud_resultado'] !== 'pendiente') {
jsonOk(['hay_solicitud' => false]);
}
jsonOk([
'hay_solicitud' => true,
'solicitante' => $row['solicitud_nombre'],
'solicitud_at' => $row['solicitud_at'],
]);
}
/* ── check_transfer (B consulta si el puesto quedó libre) ───── */
if ($action === 'check_transfer') {
$st = $pdo->prepare(
"SELECT user_id, solicitud_user_id, solicitud_resultado
FROM turnero_lugar_ocupacion WHERE lugar_id = ?"
);
$st->execute([$lugarId]);
$row = $st->fetch(PDO::FETCH_ASSOC);
if (!$row) {
jsonOk(['disponible' => true]);
}
if ($row['solicitud_resultado'] === 'denegada') {
jsonOk(['disponible' => false, 'denegado' => true]);
}
jsonOk(['disponible' => false, 'denegado' => false]);
}
/* ── solicitar (B pide transferencia) ───────────────────────── */
if ($action === 'solicitar') {
// Limpiar expirados primero
$pdo->prepare(
"DELETE FROM turnero_lugar_ocupacion WHERE ultimo_ping < $timeout"
)->execute();
$st = $pdo->prepare(
"SELECT user_id, user_nombre, solicitud_at, solicitud_user_id
FROM turnero_lugar_ocupacion WHERE lugar_id = ?"
);
$st->execute([$lugarId]);
$ocupado = $st->fetch(PDO::FETCH_ASSOC);
if (!$ocupado || (int)$ocupado['user_id'] === $userId) {
// Ya libre o soy yo — reclamar directamente
$pdo->prepare(
"INSERT INTO turnero_lugar_ocupacion (lugar_id, user_id, user_nombre, ultimo_ping)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE user_id=?, user_nombre=?, ultimo_ping=NOW(),
solicitud_user_id=NULL, solicitud_nombre=NULL, solicitud_at=NULL, solicitud_resultado=NULL"
)->execute([$lugarId, $userId, $nombre, $userId, $nombre]);
jsonOk(['transferido' => true]);
}
// Guardar solicitud (o renovarla si ya existía de este mismo B)
$pdo->prepare(
"UPDATE turnero_lugar_ocupacion
SET solicitud_user_id = ?,
solicitud_nombre = ?,
solicitud_at = NOW(),
solicitud_resultado = 'pendiente'
WHERE lugar_id = ?"
)->execute([$userId, $nombre, $lugarId]);
jsonOk(['solicitado' => true, 'ocupado_por' => $ocupado['user_nombre']]);
}
/* ── disponibles (puestos del mismo tipo sin ocupante) ───────── */
if ($action === 'disponibles') {
$stLugar = $pdo->prepare("SELECT tipo FROM turnero_lugares WHERE id = ?");
$stLugar->execute([$lugarId]);
$lugar = $stLugar->fetch(PDO::FETCH_ASSOC);
if (!$lugar) jsonError('Lugar no encontrado.', 404);
$stDisp = $pdo->prepare(
"SELECT l.id, l.nombre
FROM turnero_lugares l
WHERE l.tipo = ? AND l.activo = 1 AND l.id != ?
AND NOT EXISTS (
SELECT 1 FROM turnero_lugar_ocupacion o
WHERE o.lugar_id = l.id
AND o.ultimo_ping >= $timeout
)
ORDER BY l.sort_order ASC"
);
$stDisp->execute([$lugar['tipo'], $lugarId]);
jsonOk(['disponibles' => $stDisp->fetchAll(PDO::FETCH_ASSOC)]);
}
/* ── claim ───────────────────────────────────────────────────── */
// Limpiar ocupaciones expiradas
$pdo->prepare(
"DELETE FROM turnero_lugar_ocupacion WHERE ultimo_ping < $timeout"
)->execute();
// Verificar si hay un ocupante
$st = $pdo->prepare(
"SELECT user_id, user_nombre, solicitud_user_id, solicitud_at, solicitud_resultado
FROM turnero_lugar_ocupacion WHERE lugar_id = ?"
);
$st->execute([$lugarId]);
$ocupado = $st->fetch(PDO::FETCH_ASSOC);
if ($ocupado && (int)$ocupado['user_id'] !== $userId) {
// Si hay una solicitud de este usuario pendiente hace más de 6s → auto-liberar
$esMiSolicitud = (int)($ocupado['solicitud_user_id'] ?? 0) === $userId;
$solicitudVieja = $ocupado['solicitud_at'] && (strtotime($ocupado['solicitud_at']) < time() - 6);
if ($esMiSolicitud && $solicitudVieja) {
$pdo->prepare("DELETE FROM turnero_lugar_ocupacion WHERE lugar_id = ?")
->execute([$lugarId]);
$ocupado = null; // caer al INSERT/UPDATE de abajo
} else {
jsonOk([
'disponible' => false,
'ocupado_por' => $ocupado['user_nombre'],
]);
}
}
// Reclamar o renovar
$pdo->prepare(
"INSERT INTO turnero_lugar_ocupacion (lugar_id, user_id, user_nombre, ultimo_ping)
VALUES (?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE user_id=?, user_nombre=?, ultimo_ping=NOW(),
solicitud_user_id=NULL, solicitud_nombre=NULL, solicitud_at=NULL, solicitud_resultado=NULL"
)->execute([$lugarId, $userId, $nombre, $userId, $nombre]);
jsonOk(['disponible' => true]);