From 6589503c575e5c24cd5e04672db3f785898da3c4 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:35:20 -0500 Subject: [PATCH] =?UTF-8?q?Tablet=20de=20firma=20para=20el=20paciente=20en?= =?UTF-8?q?=20Recepci=C3=B3n=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hasta ahora, para que el paciente firmara el consentimiento de bienvenida había que girarle el monitor a la recepcionista o pasarle el mouse: el botón Firmar abre el formulario en la pantalla de ella. Se agrega una pantalla aparte (/erp.php?m=turnero&v=firma) para poner frente al paciente. Sola, sin que la recepcionista haga nada: cuando el turno entra a ese puesto y le falta el F-LAB-01, aparece su nombre y un botón grande de firmar; al terminar dice gracias y vuelve a reposo. Reutiliza lo que ya existía: el formulario de ver_formulario_enviado.php, el aviso 'turneroFirmado' que ya emite al firmar, y el sondeo de recepción, que seguirá poniendo el renglón en verde sin cambios. Sobre el acceso: va en PUBLIC_ROUTES porque nadie va a iniciar sesión cada mañana en una tablet que manipula el público. No queda abierta: se identifica por la cookie del dispositivo, y sin ella no muestra ningún dato. El endpoint devuelve solo el turno que está en ese puesto en ese instante —no permite buscar, ni ver otros, ni consultar historial— y descarta turnos de días anteriores, igual que el televisor. La tablet debe quedar en modo kiosco. Falta registrar el dispositivo de Recepción 4 desde Configuración; hoy no tiene ninguno. Co-Authored-By: Claude Opus 5 --- core/Router.php | 4 + modules/turnero/api/get_firma_pendiente.php | 134 +++++++++++++ modules/turnero/views/firma.php | 200 ++++++++++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 modules/turnero/api/get_firma_pendiente.php create mode 100644 modules/turnero/views/firma.php diff --git a/core/Router.php b/core/Router.php index ecdbb4e..62e018a 100644 --- a/core/Router.php +++ b/core/Router.php @@ -23,6 +23,10 @@ class Router private const PUBLIC_ROUTES = [ 'turnero/display', 'turnero/kiosko', + // Tablet de firma del paciente: la manipula el público y nadie va a + // iniciar sesión en ella cada mañana. No queda abierta: se identifica + // por la cookie del dispositivo y sin ella no muestra dato alguno. + 'turnero/firma', ]; /** Patrón permitido para módulo y vista: solo letras, números y guión bajo */ diff --git a/modules/turnero/api/get_firma_pendiente.php b/modules/turnero/api/get_firma_pendiente.php new file mode 100644 index 0000000..5ed4de6 --- /dev/null +++ b/modules/turnero/api/get_firma_pendiente.php @@ -0,0 +1,134 @@ +getConnection(); + +// ── 1. ¿Qué puesto es esta tablet? ──────────────────────────────────────── +// Solo por token de navegador. La IP no sirve aquí: varias tablets salen por +// la misma y acabaríamos mostrándole a un paciente los datos de otro puesto. +$token = trim($_COOKIE['turnero_token'] ?? ''); +if ($token === '') { + responder(['ok' => false, 'motivo' => 'sin_dispositivo']); +} + +$stmt = $pdo->prepare( + "SELECT td.lugar_id, td.nombre AS dispositivo, tl.nombre AS lugar, tl.tipo + FROM turnero_dispositivos td + JOIN turnero_lugares tl ON tl.id = td.lugar_id + WHERE td.token = ? AND td.activo = 1 + LIMIT 1" +); +$stmt->execute([$token]); +$disp = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$disp || $disp['tipo'] !== 'recepcion') { + responder(['ok' => false, 'motivo' => 'sin_dispositivo']); +} + +// ── 2. ¿Hay alguien siendo atendido ahí ahora? ──────────────────────────── +// Mismo criterio que la pantalla del televisor: el turno vale hasta la +// medianoche de su día, para no mostrar a un paciente que ya se fue. +// La sesión está abierta mientras fin_at siga en nulo; no hay columna de estado. +$stmt = $pdo->prepare( + "SELECT t.id, t.codigo, t.paciente_id, t.paciente_nombre + FROM turnero_turnos t + JOIN turnero_sesiones s ON s.id = t.sesion_id + WHERE t.estado = 'en_recepcion' + AND t.recepcion_desk_id = ? + AND DATE(t.llamado_recepcion_at) = CURDATE() + AND s.fin_at IS NULL + ORDER BY t.llamado_recepcion_at DESC + LIMIT 1" +); +$stmt->execute([(int)$disp['lugar_id']]); +$turno = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$turno) { + responder(['ok' => true, 'estado' => 'reposo', 'lugar' => $disp['lugar']]); +} + +// Sin paciente vinculado no hay a quién atribuirle la firma +if (empty($turno['paciente_id'])) { + responder(['ok' => true, 'estado' => 'reposo', 'lugar' => $disp['lugar']]); +} + +// ── 3. ¿Le falta firmar el consentimiento de bienvenida? ────────────────── +$stmt = $pdo->prepare( + "SELECT token, estado FROM turnero_consentimientos + WHERE turno_id = ? AND formulario_id = ? + LIMIT 1" +); +$stmt->execute([(int)$turno['id'], FORMULARIO_BIENVENIDA]); +$consent = $stmt->fetch(PDO::FETCH_ASSOC); + +if ($consent && in_array($consent['estado'], ['firmado', 'rechazado'], true)) { + responder([ + 'ok' => true, + 'estado' => 'firmado', + 'turno_id' => (int)$turno['id'], + 'codigo' => $turno['codigo'], + ]); +} + +// Si el consentimiento todavía no existe se crea aquí. Es lo que permite que +// la tablet aparezca sola, sin que la recepcionista tenga que mandarlo. +if (!$consent) { + $tokenFirma = sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0x0fff) | 0x4000, + mt_rand(0, 0x3fff) | 0x8000, + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + $ins = $pdo->prepare( + "INSERT IGNORE INTO turnero_consentimientos + (turno_id, formulario_id, token, estado, creado_por) + VALUES (?, ?, ?, 'pendiente', NULL)" + ); + $ins->execute([(int)$turno['id'], FORMULARIO_BIENVENIDA, $tokenFirma]); + + // INSERT IGNORE puede no haber insertado si otra petición se adelantó: + // se relee para quedarse con el token que realmente quedó guardado. + $stmt->execute([(int)$turno['id'], FORMULARIO_BIENVENIDA]); + $consent = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$consent) { + responder(['ok' => false, 'motivo' => 'no_se_pudo_crear']); + } +} + +responder([ + 'ok' => true, + 'estado' => 'por_firmar', + 'turno_id' => (int)$turno['id'], + 'codigo' => $turno['codigo'], + 'paciente' => $turno['paciente_nombre'], + 'url' => BASE_URL . 'ver_formulario_enviado.php?token=' . urlencode($consent['token']), +]); diff --git a/modules/turnero/views/firma.php b/modules/turnero/views/firma.php new file mode 100644 index 0000000..a7e3d6a --- /dev/null +++ b/modules/turnero/views/firma.php @@ -0,0 +1,200 @@ +getConnection(); + $_fCfg = $__pdo->query( + "SELECT clave, valor FROM lab_config WHERE clave IN ('empresa_nombre','doc_logo_base64','doc_color')" + )->fetchAll(PDO::FETCH_KEY_PAIR) ?: []; +} catch (\Throwable $_) {} + +$_fNombre = htmlspecialchars($_fCfg['empresa_nombre'] ?? 'Laboratorio'); +$_fLogo = $_fCfg['doc_logo_base64'] ?? ''; +$_fColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_fCfg['doc_color'] ?? '') ? $_fCfg['doc_color'] : '#1565c0'; +?> + + + + + +Firma · <?= $_fNombre ?> + + + + + +
+ +
+
Bienvenido
+
+ + +
+
Turno
+
+
Por favor lea y firme el consentimiento para continuar con su atención.
+ +
+ + +
+
+
¡Gracias!
+
Su consentimiento quedó registrado. Puede continuar en el mostrador.
+
+ + +
+
+
Esta tablet todavía no está asignada a un puesto.
+ Regístrela desde Configuración del turnero.
+
+ +
+ + + + +