feat: restricción de exámenes por género del paciente
- 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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
b280dfcb7a
commit
3eaade80ff
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
<option value="<?= (int)$ex['id'] ?>"
|
||||
data-consent="<?= (int)$ex['tiene_consentimiento'] ?>"
|
||||
data-cups="<?= htmlspecialchars($ex['cups'] ?? '') ?>"
|
||||
data-codigo="<?= htmlspecialchars($ex['codigo'] ?? '') ?>">
|
||||
data-codigo="<?= htmlspecialchars($ex['codigo'] ?? '') ?>"
|
||||
data-genero="<?= htmlspecialchars($ex['genero_permitido'] ?? '') ?>">
|
||||
<?= htmlspecialchars($ex['codigo']) ?> — <?= htmlspecialchars($ex['nombre']) ?><?= $ex['cups'] ? ' ' . htmlspecialchars($ex['cups']) : '' ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
@@ -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 `<div class="d-flex align-items-center justify-content-between">
|
||||
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 `<div class="d-flex align-items-center justify-content-between${blocked ? ' opacity-50' : ''}">
|
||||
<span>${escape(data.text)}${cups ? `<span class="ts-exam-cups-drop">${escape(cups)}</span>` : ''}</span>
|
||||
${consent ? '<span class="ts-consent-badge"><i class="fas fa-file-signature me-1"></i>Consentimiento</span>' : ''}
|
||||
${blocked ? `<span class="ts-consent-badge bg-danger">${gpLabel}</span>` : consent ? '<span class="ts-consent-badge"><i class="fas fa-file-signature me-1"></i>Consentimiento</span>' : ''}
|
||||
</div>`;
|
||||
},
|
||||
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 ───────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user