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();
|
$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();
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ try {
|
|||||||
$lugarMuestras = $lugaresDestino[0] ?? null;
|
$lugarMuestras = $lugaresDestino[0] ?? null;
|
||||||
|
|
||||||
$examenes = $pdo->query(
|
$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
|
IF(COUNT(etc.formulario_id) > 0, 1, 0) AS tiene_consentimiento
|
||||||
FROM exam_tipos et
|
FROM exam_tipos et
|
||||||
LEFT JOIN exam_tipo_consentimientos etc ON etc.exam_tipo_id = et.id
|
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'] ?>"
|
<option value="<?= (int)$ex['id'] ?>"
|
||||||
data-consent="<?= (int)$ex['tiene_consentimiento'] ?>"
|
data-consent="<?= (int)$ex['tiene_consentimiento'] ?>"
|
||||||
data-cups="<?= htmlspecialchars($ex['cups'] ?? '') ?>"
|
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']) : '' ?>
|
<?= htmlspecialchars($ex['codigo']) ?> — <?= htmlspecialchars($ex['nombre']) ?><?= $ex['cups'] ? ' ' . htmlspecialchars($ex['cups']) : '' ?>
|
||||||
</option>
|
</option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -1155,10 +1156,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
render: {
|
render: {
|
||||||
option(data, escape) {
|
option(data, escape) {
|
||||||
const consent = data['data-consent'] == '1' || data.consent == '1';
|
const consent = data['data-consent'] == '1' || data.consent == '1';
|
||||||
const cups = data['data-cups'] || data.cups || '';
|
const cups = data['data-cups'] || data.cups || '';
|
||||||
return `<div class="d-flex align-items-center justify-content-between">
|
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>
|
<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>`;
|
</div>`;
|
||||||
},
|
},
|
||||||
item(data, escape) {
|
item(data, escape) {
|
||||||
@@ -1618,6 +1623,22 @@ function seleccionarPaciente(pac) {
|
|||||||
// Consultar exámenes recientes en RIPS (últimos 5 min)
|
// Consultar exámenes recientes en RIPS (últimos 5 min)
|
||||||
const cedula = (pac.numero_documento || pac.documento || '').toString().trim();
|
const cedula = (pac.numero_documento || pac.documento || '').toString().trim();
|
||||||
if (cedula) consultarExamenesRips(cedula);
|
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 ───────────────────────────────────────
|
// ── Exámenes desde RIPS ───────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user