up
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* Helpers compartidos para los endpoints del módulo Turnero.
|
||||
* Incluido por cada endpoint de modules/turnero/api/.
|
||||
*/
|
||||
|
||||
ob_start();
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
ini_set('display_errors', '0');
|
||||
ini_set('html_errors', '0');
|
||||
|
||||
register_shutdown_function(function () {
|
||||
$err = error_get_last();
|
||||
if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
|
||||
ob_clean();
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['ok' => false, 'error' => 'Error interno: ' . $err['message']]);
|
||||
}
|
||||
});
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(204);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Funciones de respuesta ───────────────────────────────────
|
||||
|
||||
function jsonOk(array $payload = [], string $mensaje = ''): void
|
||||
{
|
||||
ob_clean();
|
||||
$resp = ['ok' => true, 'success' => true];
|
||||
if ($mensaje) {
|
||||
$resp['message'] = $mensaje;
|
||||
}
|
||||
echo json_encode(array_merge($resp, $payload), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function jsonError(string $mensaje, int $code = 400): void
|
||||
{
|
||||
ob_clean();
|
||||
http_response_code($code);
|
||||
echo json_encode(['ok' => false, 'success' => false, 'error' => $mensaje], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
function requireMethod(string $method): void
|
||||
{
|
||||
if ($_SERVER['REQUEST_METHOD'] !== strtoupper($method)) {
|
||||
jsonError('Método no permitido', 405);
|
||||
}
|
||||
}
|
||||
|
||||
function inputJson(): array
|
||||
{
|
||||
static $parsed = null;
|
||||
if ($parsed === null) {
|
||||
$raw = file_get_contents('php://input');
|
||||
$parsed = json_decode($raw, true) ?? [];
|
||||
}
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
// ── Autenticación (kiosko y display son públicos) ────────────
|
||||
|
||||
/**
|
||||
* Exige sesión activa. Para endpoints de login requerido.
|
||||
*/
|
||||
function requireAuth(): void
|
||||
{
|
||||
if (empty($_SESSION['admin_user']['id'])) {
|
||||
jsonError('No autenticado', 401);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exige sesión + módulo turnero.
|
||||
*/
|
||||
function requireTurnero(): void
|
||||
{
|
||||
requireAuth();
|
||||
$modules = $_SESSION['admin_user']['modules'] ?? [];
|
||||
$roleId = $_SESSION['admin_user']['role_id'] ?? null;
|
||||
|
||||
// Superadmin / admin sin role_id = acceso total
|
||||
if (empty($roleId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('turnero', (array) $modules, true)) {
|
||||
jsonError('Sin acceso al módulo Turnero', 403);
|
||||
}
|
||||
}
|
||||
|
||||
function adminId(): ?int
|
||||
{
|
||||
return isset($_SESSION['admin_user']['id'])
|
||||
? (int) $_SESSION['admin_user']['id']
|
||||
: null;
|
||||
}
|
||||
|
||||
// ── Helper BD ────────────────────────────────────────────────
|
||||
|
||||
function db(): PDO
|
||||
{
|
||||
return Database::getInstance()->getConnection();
|
||||
}
|
||||
|
||||
// ── Motor de cola ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Obtiene o crea la sesión del día actual.
|
||||
* Devuelve el id de la sesión.
|
||||
*/
|
||||
function obtenerOCrearSesionHoy(): int
|
||||
{
|
||||
$pdo = db();
|
||||
$hoy = date('Y-m-d');
|
||||
|
||||
$stmt = $pdo->prepare('SELECT id FROM turnero_sesiones WHERE fecha = ? LIMIT 1');
|
||||
$stmt->execute([$hoy]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
return (int) $row['id'];
|
||||
}
|
||||
|
||||
// Crear sesión del día
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO turnero_sesiones (fecha, abierto_por, inicio_at) VALUES (?, ?, NOW())'
|
||||
);
|
||||
$stmt->execute([$hoy, adminId()]);
|
||||
return (int) $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera el siguiente número correlativo de turno para una sesión.
|
||||
* Usa bloqueo a nivel de fila para evitar duplicados en concurrencia.
|
||||
*/
|
||||
function siguienteNumero(int $sesionId): int
|
||||
{
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT COALESCE(MAX(numero), 0) + 1 AS siguiente
|
||||
FROM turnero_turnos
|
||||
WHERE sesion_id = ?
|
||||
FOR UPDATE'
|
||||
);
|
||||
$stmt->execute([$sesionId]);
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selecciona el siguiente turno a llamar según el motor de prioridades.
|
||||
*
|
||||
* @param string $estado Estado que deben tener los turnos en cola
|
||||
* @param int|null $lugarId Si no es null, filtra por lugar_destino_id
|
||||
* @return array|null Fila de turnero_turnos o null si cola vacía
|
||||
*/
|
||||
function siguienteTurnoEnCola(string $estado, ?int $lugarId = null): ?array
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
if ($lugarId !== null) {
|
||||
$sql = '
|
||||
SELECT t.*, p.orden_peso, p.codigo AS prioridad_codigo, p.color AS prioridad_color
|
||||
FROM turnero_turnos t
|
||||
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
||||
WHERE t.estado = ?
|
||||
AND t.lugar_destino_id = ?
|
||||
ORDER BY p.orden_peso ASC, t.creado_at ASC
|
||||
LIMIT 1
|
||||
';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$estado, $lugarId]);
|
||||
} else {
|
||||
$sql = '
|
||||
SELECT t.*, p.orden_peso, p.codigo AS prioridad_codigo, p.color AS prioridad_color
|
||||
FROM turnero_turnos t
|
||||
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
||||
WHERE t.estado = ?
|
||||
ORDER BY p.orden_peso ASC, t.creado_at ASC
|
||||
LIMIT 1
|
||||
';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$estado]);
|
||||
}
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emite un evento SSE a todos los clientes suscritos escribiendo
|
||||
* un flag en la tabla turnero_sesiones (campo sse_ping_at).
|
||||
* Los clientes hacen polling de ese campo o escuchan el SSE directamente.
|
||||
*
|
||||
* Implementación ligera: actualiza timestamp en sesión para que
|
||||
* sse_turno.php lo detecte y reenvíe el estado.
|
||||
*/
|
||||
function notificarSSE(int $sesionId): void
|
||||
{
|
||||
try {
|
||||
db()->prepare(
|
||||
'UPDATE turnero_sesiones SET sse_ping_at = NOW() WHERE id = ?'
|
||||
)->execute([$sesionId]);
|
||||
} catch (\Throwable $e) {
|
||||
// No es crítico si falla; el SSE continuará con polling normal
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user