From 3eaade80ff398d6e4e695e67f78e28975c83d10e Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:45:39 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20restricci=C3=B3n=20de=20ex=C3=A1menes?= =?UTF-8?q?=20por=20g=C3=A9nero=20del=20paciente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DB: columna genero_permitido en exam_tipos (M/F/NULL) GX,FV → solo femenino | PSA,FPSA → solo masculino - recepcion.php: TomSelect muestra badge rojo y deshabilita opciones incompatibles al seleccionar paciente; elimina los ya agregados - create_solicitud.php: validación backend 422 si hay conflicto género/examen Co-Authored-By: Claude Sonnet 4.6 --- modules/turnero/api/create_solicitud.php | 23 ++++++++++++++++++ modules/turnero/views/recepcion.php | 31 ++++++++++++++++++++---- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/modules/turnero/api/create_solicitud.php b/modules/turnero/api/create_solicitud.php index 5f96c6c..c5daddd 100644 --- a/modules/turnero/api/create_solicitud.php +++ b/modules/turnero/api/create_solicitud.php @@ -63,6 +63,29 @@ if ($metodoPago && !in_array($metodoPago, $metodosValidos, true)) { } $pdo = db(); + +// Validar restricción de género antes de abrir transacción +if (!empty($examIds)) { + $stPac = $pdo->prepare("SELECT genero FROM lab_pacientes WHERE id = ? LIMIT 1"); + $stPac->execute([$pacienteId]); + $genPac = strtoupper(trim((string)($stPac->fetchColumn() ?: ''))); + + if ($genPac) { + $ph = implode(',', array_fill(0, count($examIds), '?')); + $stGp = $pdo->prepare( + "SELECT codigo, nombre, genero_permitido FROM exam_tipos + WHERE id IN ($ph) AND genero_permitido IS NOT NULL AND genero_permitido <> ?" + ); + $stGp->execute(array_merge($examIds, [$genPac])); + $incompatibles = $stGp->fetchAll(PDO::FETCH_ASSOC); + if ($incompatibles) { + $nombres = implode(', ', array_map(fn($r) => $r['codigo'], $incompatibles)); + $sexo = $genPac === 'M' ? 'masculino' : 'femenino'; + jsonError("Examen(es) no disponibles para paciente $sexo: $nombres", 422); + } + } +} + $pdo->beginTransaction(); try { diff --git a/modules/turnero/views/recepcion.php b/modules/turnero/views/recepcion.php index 71dea93..fce0d35 100644 --- a/modules/turnero/views/recepcion.php +++ b/modules/turnero/views/recepcion.php @@ -82,7 +82,7 @@ try { $lugarMuestras = $lugaresDestino[0] ?? null; $examenes = $pdo->query( - "SELECT et.id, et.codigo, et.nombre, et.categoria, et.cups, + "SELECT et.id, et.codigo, et.nombre, et.categoria, et.cups, et.genero_permitido, IF(COUNT(etc.formulario_id) > 0, 1, 0) AS tiene_consentimiento FROM exam_tipos et LEFT JOIN exam_tipo_consentimientos etc ON etc.exam_tipo_id = et.id @@ -732,7 +732,8 @@ document.addEventListener('DOMContentLoaded', function() { @@ -1155,10 +1156,14 @@ document.addEventListener('DOMContentLoaded', () => { render: { option(data, escape) { const consent = data['data-consent'] == '1' || data.consent == '1'; - const cups = data['data-cups'] || data.cups || ''; - return `
+ const cups = data['data-cups'] || data.cups || ''; + const gp = (data['data-genero'] || '').toUpperCase(); + const pg = (pacienteActivo?.genero || '').toUpperCase(); + const blocked = gp && pg && gp !== pg; + const gpLabel = gp === 'M' ? '♂ Solo hombres' : gp === 'F' ? '♀ Solo mujeres' : ''; + return `
${escape(data.text)}${cups ? `${escape(cups)}` : ''} - ${consent ? 'Consentimiento' : ''} + ${blocked ? `${gpLabel}` : consent ? 'Consentimiento' : ''}
`; }, item(data, escape) { @@ -1618,6 +1623,22 @@ function seleccionarPaciente(pac) { // Consultar exámenes recientes en RIPS (últimos 5 min) const cedula = (pac.numero_documento || pac.documento || '').toString().trim(); if (cedula) consultarExamenesRips(cedula); + filtrarExamenesPorGenero(); +} + +function filtrarExamenesPorGenero() { + if (!examTS) return; + const genero = (pacienteActivo?.genero || '').toUpperCase(); + const bloqueados = []; + Object.values(examTS.options).forEach(opt => { + const gp = (opt['data-genero'] || '').toUpperCase(); + const incompatible = !!(gp && genero && gp !== genero); + opt.disabled = incompatible; + if (incompatible && examTS.items.includes(String(opt.value))) bloqueados.push(String(opt.value)); + }); + bloqueados.forEach(id => examTS.removeItem(id, true)); + if (bloqueados.length) recalcularPrecios(); + examTS.refreshOptions(false); } // ── Exámenes desde RIPS ───────────────────────────────────────