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.
+
+ +
+ + + + +