diff --git a/login.php b/login.php index 2f0bd76..bf2aefd 100644 --- a/login.php +++ b/login.php @@ -41,6 +41,25 @@ if ($_POST && !$loginBlocked) { $_SESSION['login_ip'] = $clientIp; $_SESSION['login_time'] = time(); + // Tablet fija: redirigir al lugar asignado por IP y bloquear el resto + try { + $_dispStmt = Database::getInstance()->getConnection()->prepare( + "SELECT td.lugar_id, td.nombre, tl.tipo + FROM turnero_dispositivos td + JOIN turnero_lugares tl ON tl.id = td.lugar_id + WHERE td.ip = ? AND td.activo = 1 LIMIT 1" + ); + $_dispStmt->execute([$clientIp]); + $_disp = $_dispStmt->fetch(PDO::FETCH_ASSOC); + if ($_disp) { + $_SESSION['turnero_dispositivo'] = $_disp; + $url = $_disp['tipo'] === 'recepcion' + ? BASE_URL . 'erp.php?m=turnero&v=recepcion&desk_id=' . $_disp['lugar_id'] + : BASE_URL . 'erp.php?m=turnero&v=lugar&lugar_id=' . $_disp['lugar_id']; + header('Location: ' . $url); exit; + } + } catch (\Throwable $_) {} + $roleSlug = $adminUser['role'] ?? 'admin'; $modules = $adminUser['modules'] ?? []; diff --git a/migrations/20260707_admin_users_firma.sql b/migrations/20260707_admin_users_firma.sql new file mode 100644 index 0000000..78ef102 --- /dev/null +++ b/migrations/20260707_admin_users_firma.sql @@ -0,0 +1,7 @@ +-- ============================================================ +-- Migration: 20260707_admin_users_firma +-- Firma pre-guardada del profesional para firma en 1 clic +-- ============================================================ +ALTER TABLE admin_users + ADD COLUMN IF NOT EXISTS firma_svg MEDIUMTEXT DEFAULT NULL + COMMENT 'Firma pre-guardada del profesional (data URL PNG)'; diff --git a/migrations/20260707_formulario_vih_turnero.sql b/migrations/20260707_formulario_vih_turnero.sql new file mode 100644 index 0000000..cb56252 --- /dev/null +++ b/migrations/20260707_formulario_vih_turnero.sql @@ -0,0 +1,107 @@ +-- ============================================================ +-- Migration: 20260707_formulario_vih_turnero +-- Clon de F-LAB-05 VIH para uso en turnero. +-- +-- Problema del original (id=1): +-- · Esquema sin campos tipo "firma" ni "firma_profesional" +-- · Paciente firmaba por fallback global (canvas genérico al pie) +-- · Profesional NUNCA podía firmar (requiere $_soloFirmaPro=true +-- o campo firma_profesional en esquema — ninguno se cumplía) +-- · doc_color vacío, requiere_firma=0 +-- +-- Esta versión corrige el esquema agregando: +-- · Separador + campo texto para responsable (menores/incapaces) +-- · campo tipo="firma" → firma inline del paciente +-- · campo tipo="firma_profesional" → 1-clic para el profesional +-- ============================================================ + +SET @esquema_vih = '[ + { + "id": "_8h4v4jg", + "tipo": "parrafo", + "contenido": "Autorización voluntaria para realizar la prueba presuntiva para VIH (Decreto 1543/97 del Ministerio de Protección Social por el cual se reglamentan los mecanismos de prevención, diagnóstico, manejo y reporte epidemiológico de la infección por VIH)", + "flujoLibre": false + }, + { + "id": "_z7pv612", + "tipo": "fecha_hoy", + "label": "Fecha", + "required": false + }, + { + "id": "_afsycwa", + "tipo": "parrafo", + "contenido": "Qué es el síndrome de inmunodeficiencia adquirida (SIDA)? \nEs una enfermedad producida por un virus conocido como el virus de inmunodeficiencia Humana (VIH), el cual infecta y destruye las células del sistema inmune, originando una falla progresiva y grave en los sistemas de defensa del organismo el cual queda expuesto a la infección y ciertos tipos de tumores. \n\n¿Cómo se adquiere la enfermedad? \nLa enfermedad se adquiere principalmente por contacto sexual con personas infectadas con el VIH. Por exposición a la sangre y a ciertos productos derivados de la misma contaminados con el virus. Además, durante el embarazo, las madres infectadas con el virus de inmunodeficiencia humana pueden transmitir la infección al feto a través de la placenta. \n\n¿Cómo se hace el diagnóstico de la infección? El diagnóstico se hace mediante una prueba de sangre que busca anticuerpos producidos por el organismo contra el virus. Existen dos clases de pruebas de laboratorio. Presuntivas, que pueden indicar una posible infección, y las pruebas confirmatorias, las cuales se hacen únicamente en caso de que la prueba presuntiva de positiva. \n\n¿Cuál es el procedimiento que el laboratorio debe realizar en el análisis de la prueba? El laboratorio procesa la muestra y en caso de un resultado presuntamente positivo o inconcluyente, realiza un segundo examen con otra muestra. En caso de que la segunda muestra también arroje un resultado presuntamente positivo, el resultado de la prueba se reporta como reactivo. En caso de que la segunda muestra no confirme los resultados de la primera muestra, las dos muestras se envían a un tercer laboratorio, con el fin de que sea procesada en él. Los resultados de este tercer laboratorio se toman como definitivos para decidir el reporte como Reactivo o Negativo. En todo caso, los costos por estas pruebas son asumidos por cuenta del laboratorio. \n\n¿Cómo se debe interpretar el resultado de la prueba? La prueba inicial, como ya se anotó, es apenas una prueba presuntiva, y por lo tanto, el hecho de salir reactiva no implica que usted tenga SIDA, o esté infectado por el virus. Este resultado debe ser confirmado mediante una prueba llamada Western Blot, con el fin de eliminar posibles falsos positivos de la prueba presuntiva. Para este segundo examen es importante tomar una nueva muestra y procesarla asumiendo usted los costos. Aún si esta segunda prueba confirma la presencia de anticuerpos para el VIH, esto no significa que usted tiene SIDA, pues existe un período de la enfermedad, controlable, en el cual los pacientes tienen anticuerpos contra el VIH, pero no tienen síntomas de la enfermedad, y pueden, inclusive, no desarrollar jamás la enfermedad. Lo que es muy urgente, es consultar con un médico para que se determine el estado de su enfermedad, y se inicie el tratamiento apropiado. \n\nEl resultado será entregado personalmente, previa identificación. ", + "flujoLibre": false + }, + { + "id": "_wsqqoiu", + "tipo": "parrafo_inline", + "contenido": "Yo, {nombre_completo}, con N.º de identificación {numero_documento}, declaro que fui informado (a) sobre el examen de anticuerpos contra el VIH que me será practicado el día de hoy, he recibido asesoría, me han explicado en que consiste; el procedimiento y sus implicaciones en mi vida, y la confidencialidad con la que se manejara las información que he dado y que se obtendrá. YO COMPRENDO Y AUTORIZO LA REALIZACIÓN DE LA PRUEBA DE FORMA LIBRE Y ESPONTÁNEA, en el Laboratorio Clínico Ximena Caicedo Empresa Unipersonal." + }, + { + "id": "_vihsep1", + "tipo": "separador", + "label": "AUTORIZACIÓN Y FIRMA" + }, + { + "id": "_vihresp", + "tipo": "texto", + "label": "En caso de menor o incapacitado: nombre y parentesco del responsable", + "required": false + }, + { + "id": "_vihfpac", + "tipo": "firma", + "label": "Firma del paciente / responsable", + "required": true + }, + { + "id": "_vihsep2", + "tipo": "separador", + "label": "USO EXCLUSIVO DEL PROFESIONAL" + }, + { + "id": "_vihfpro", + "tipo": "firma_profesional", + "label": "Firma del profesional de salud" + } +]'; + +INSERT INTO lab_formularios ( + nombre, + descripcion, + categoria, + esquema, + permite_firma, + requiere_firma, + doc_encabezado, + doc_subtitulo, + doc_color, + tipo, + is_principal, + is_active, + version, + creado_por +) VALUES ( + 'CONSENTIMIENTO INFORMADO VIH F-LAB-05 V.5', + 'Autorización voluntaria para prueba presuntiva de VIH — uso turnero', + 'consentimiento', + @esquema_vih, + 1, + 1, + 'XIMENA CAICEDO G. E.U', + 'Laboratorio Hematológico', + '#a0a59c', + 'consentimiento', + 0, + 1, + 5, + 1 +); + +-- Asignar a todos los puestos de toma de muestras activos +INSERT INTO turnero_lugar_consentimientos (lugar_id, formulario_id) +SELECT id, LAST_INSERT_ID() +FROM turnero_lugares +WHERE tipo = 'muestras' AND activo = 1; diff --git a/migrations/20260707_turnero_dispositivos.sql b/migrations/20260707_turnero_dispositivos.sql new file mode 100644 index 0000000..a22416f --- /dev/null +++ b/migrations/20260707_turnero_dispositivos.sql @@ -0,0 +1,28 @@ +-- ============================================================ +-- Migration: 20260707_turnero_dispositivos +-- Mapeo IP fija → lugar para tablets del turnero. +-- Cada tablet solo puede acceder a su lugar asignado. +-- ============================================================ +CREATE TABLE IF NOT EXISTS turnero_dispositivos ( + id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + ip VARCHAR(45) NOT NULL UNIQUE, + lugar_id INT NOT NULL, + nombre VARCHAR(100) NOT NULL, + activo TINYINT(1) NOT NULL DEFAULT 1, + INDEX idx_ip (ip), + INDEX idx_lugar (lugar_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +INSERT INTO turnero_dispositivos (ip, lugar_id, nombre) VALUES +('192.168.21.61', 1, 'Toma de Muestras 1'), +('192.168.21.62', 2, 'Toma de Muestras 2'), +('192.168.21.63', 13, 'Toma de Muestras 3'), +('192.168.21.64', 14, 'Toma de Muestras 4'), +('192.168.21.65', 15, 'Toma de Muestras 5'), +('192.168.21.66', 16, 'Toma de Muestras 6'), +('192.168.21.67', 17, 'Toma de Muestras 7'), +('192.168.21.68', 18, 'Toma de Muestras 8'), +('192.168.0.221', 10, 'Recepción 1 - Vianny Ortega'), +('192.168.0.29', 11, 'Recepción 2 - Angel Wilches'), +('192.168.0.225', 19, 'Recepción 3 - Gina Gomez') +ON DUPLICATE KEY UPDATE lugar_id = VALUES(lugar_id), nombre = VALUES(nombre); diff --git a/modules/turnero/api/get_consentimientos.php b/modules/turnero/api/get_consentimientos.php index ef86664..f8eb1d8 100644 --- a/modules/turnero/api/get_consentimientos.php +++ b/modules/turnero/api/get_consentimientos.php @@ -55,10 +55,10 @@ foreach ($consentimientos as &$c) { $decoded = json_decode($esquema, true); $campos = is_array($decoded) ? $decoded : ($decoded['campos'] ?? []); } - $c['requiere_firma_profesional'] = !empty(array_filter( - $campos, - fn($f) => ($f['tipo'] ?? '') === 'firma_profesional' - )); + $tieneFirmaPro = !empty(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma_profesional')); + $tieneFirmaPac = !empty(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma')); + $c['requiere_firma_profesional'] = $tieneFirmaPro; + $c['requiere_firma_paciente'] = !($tieneFirmaPro && !$tieneFirmaPac); // false solo si es solo-profesional unset($c['formulario_esquema']); } unset($c); @@ -205,10 +205,10 @@ if ($incluirSolicitud) { $decoded = json_decode($esquema, true); $campos = is_array($decoded) ? $decoded : ($decoded['campos'] ?? []); } - $c['requiere_firma_profesional'] = !empty(array_filter( - $campos, - fn($f) => ($f['tipo'] ?? '') === 'firma_profesional' - )); + $tieneFirmaPro = !empty(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma_profesional')); + $tieneFirmaPac = !empty(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma')); + $c['requiere_firma_profesional'] = $tieneFirmaPro; + $c['requiere_firma_paciente'] = !($tieneFirmaPro && !$tieneFirmaPac); unset($c['formulario_esquema']); } unset($c); diff --git a/modules/turnero/api/save_firma_profesional.php b/modules/turnero/api/save_firma_profesional.php new file mode 100644 index 0000000..5fb72ba --- /dev/null +++ b/modules/turnero/api/save_firma_profesional.php @@ -0,0 +1,22 @@ +prepare("UPDATE admin_users SET firma_svg = ? WHERE id = ?")->execute([$svg, $uid]); + +jsonOk(['mensaje' => 'Firma guardada correctamente.']); diff --git a/modules/turnero/views/dashboard.php b/modules/turnero/views/dashboard.php index d8072b3..f64d819 100644 --- a/modules/turnero/views/dashboard.php +++ b/modules/turnero/views/dashboard.php @@ -7,6 +7,15 @@ require_once APP_ROOT . '/config/config.php'; if (!isUserLoggedIn()) { header('Location: ' . BASE_URL . 'login.php'); exit; } +// Tablet asignada → no tiene acceso al dashboard, ir a su lugar +if (!empty($_SESSION['turnero_dispositivo'])) { + $_d = $_SESSION['turnero_dispositivo']; + header('Location: ' . BASE_URL . ($_d['tipo'] === 'recepcion' + ? 'erp.php?m=turnero&v=recepcion&desk_id=' . $_d['lugar_id'] + : 'erp.php?m=turnero&v=lugar&lugar_id=' . $_d['lugar_id'])); + exit; +} + $_iaActiva = false; try { $_cfgIA = Database::getInstance()->fetch("SELECT valor FROM lab_config WHERE clave = 'ia_activa'"); diff --git a/modules/turnero/views/lugar.php b/modules/turnero/views/lugar.php index 0c0758f..d474ae1 100644 --- a/modules/turnero/views/lugar.php +++ b/modules/turnero/views/lugar.php @@ -9,6 +9,18 @@ if (!isUserLoggedIn()) { exit; } +// Tablet asignada → forzar su lugar, bloquear cualquier otro +if (!empty($_SESSION['turnero_dispositivo'])) { + $_d = $_SESSION['turnero_dispositivo']; + $_forzado = (int)$_d['lugar_id']; + if ($_d['tipo'] === 'recepcion') { + header('Location: ' . BASE_URL . 'erp.php?m=turnero&v=recepcion&desk_id=' . $_forzado); exit; + } + if ((int)($_GET['lugar_id'] ?? 0) !== $_forzado) { + header('Location: ' . BASE_URL . 'erp.php?m=turnero&v=lugar&lugar_id=' . $_forzado); exit; + } +} + try { $pdo = Database::getInstance()->getConnection(); $lugares = $pdo->query( @@ -33,6 +45,13 @@ foreach ($lugares as $l) { } $adminNombre = $_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['username'] ?? 'Operador'; +$adminFirmaSvg = null; +try { + $stmt = Database::getInstance()->getConnection() + ->prepare("SELECT firma_svg FROM admin_users WHERE id = ? LIMIT 1"); + $stmt->execute([(int)($_SESSION['admin_user']['id'] ?? 0)]); + $adminFirmaSvg = $stmt->fetchColumn() ?: null; +} catch (\Throwable $_) {} ?> @@ -465,6 +484,10 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';