feat(turnero): múltiples consentimientos por tipo de examen

- save_ex_tipo.php: GET devuelve form_ids[], POST acepta form_ids[] y
  hace DELETE+INSERT por cada uno (la tabla ya soportaba muchos a muchos)
- configuracion.php: reemplaza select único por checkboxes en formulario
  Agregar y modal Editar; JS lee los checks y envía form_ids[]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-08 08:10:57 -05:00
co-authored by Claude Sonnet 4.6
parent b6179574c3
commit b2bc0f950a
2 changed files with 62 additions and 45 deletions
+18 -21
View File
@@ -1,10 +1,10 @@
<?php <?php
/** /**
* modules/turnero/api/save_ex_tipo.php * modules/turnero/api/save_ex_tipo.php
* GET ?id=X → devuelve datos de un tipo de examen (incluyendo formulario_id vinculado) * GET ?id=X → devuelve datos de un tipo de examen (incluyendo form_ids[])
* POST {codigo, nombre, categoria, formulario_id, activo} → crear * POST {codigo, nombre, categoria, form_ids:[], activo} → crear
* POST {id, codigo, nombre, categoria, formulario_id, activo} → actualizar * POST {id, codigo, nombre, categoria, form_ids:[], activo} → actualizar
* POST {id, _delete: true} → eliminar * POST {id, _delete: true} → eliminar
*/ */
require_once __DIR__ . '/_helpers.php'; require_once __DIR__ . '/_helpers.php';
@@ -15,16 +15,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$id = (int)($_GET['id'] ?? 0); $id = (int)($_GET['id'] ?? 0);
if (!$id) jsonError('ID requerido'); if (!$id) jsonError('ID requerido');
$stmt = db()->prepare( $stmt = db()->prepare('SELECT * FROM exam_tipos WHERE id = ?');
'SELECT et.*,
(SELECT formulario_id FROM exam_tipo_consentimientos
WHERE exam_tipo_id = et.id LIMIT 1) AS formulario_id
FROM exam_tipos et
WHERE et.id = ?'
);
$stmt->execute([$id]); $stmt->execute([$id]);
$data = $stmt->fetch(PDO::FETCH_ASSOC); $data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$data) jsonError('Examen no encontrado', 404); if (!$data) jsonError('Examen no encontrado', 404);
$fStmt = db()->prepare('SELECT formulario_id FROM exam_tipo_consentimientos WHERE exam_tipo_id = ?');
$fStmt->execute([$id]);
$data['form_ids'] = $fStmt->fetchAll(PDO::FETCH_COLUMN);
jsonOk(['data' => $data]); jsonOk(['data' => $data]);
} }
@@ -52,11 +50,11 @@ if ($delete) {
} }
// ── CREAR / ACTUALIZAR ─────────────────────────────────────── // ── CREAR / ACTUALIZAR ───────────────────────────────────────
$codigo = strtoupper(trim($input['codigo'] ?? '')); $codigo = strtoupper(trim($input['codigo'] ?? ''));
$nombre = trim($input['nombre'] ?? ''); $nombre = trim($input['nombre'] ?? '');
$categoria = trim($input['categoria'] ?? ''); $categoria = trim($input['categoria'] ?? '');
$formId = (int)($input['formulario_id'] ?? 0) ?: null; $formIds = array_values(array_unique(array_filter(array_map('intval', (array)($input['form_ids'] ?? [])))));
$activo = (int)($input['activo'] ?? 1); $activo = (int)($input['activo'] ?? 1);
if ($codigo === '') jsonError('El código es requerido'); if ($codigo === '') jsonError('El código es requerido');
if ($nombre === '') jsonError('El nombre es requerido'); if ($nombre === '') jsonError('El nombre es requerido');
@@ -80,12 +78,11 @@ try {
$examId = (int)$pdo->lastInsertId(); $examId = (int)$pdo->lastInsertId();
} }
// Actualizar relación formulario ↔ examen // Actualizar relaciones formulario ↔ examen (muchos a muchos)
$pdo->prepare('DELETE FROM exam_tipo_consentimientos WHERE exam_tipo_id = ?')->execute([$examId]); $pdo->prepare('DELETE FROM exam_tipo_consentimientos WHERE exam_tipo_id = ?')->execute([$examId]);
if ($formId) { $ins = $pdo->prepare('INSERT INTO exam_tipo_consentimientos (exam_tipo_id, formulario_id) VALUES (?, ?)');
$pdo->prepare( foreach ($formIds as $fid) {
'INSERT INTO exam_tipo_consentimientos (exam_tipo_id, formulario_id) VALUES (?, ?)' $ins->execute([$examId, $fid]);
)->execute([$examId, $formId]);
} }
$pdo->commit(); $pdo->commit();
+44 -24
View File
@@ -675,13 +675,21 @@ $tab = $_GET['tab'] ?? 'lugares';
<input type="text" id="ex-categoria" class="form-control form-control-sm" placeholder="Hematología"> <input type="text" id="ex-categoria" class="form-control form-control-sm" placeholder="Hematología">
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label small fw-semibold">Consentimiento</label> <label class="form-label small fw-semibold">Consentimientos</label>
<select id="ex-form" class="form-select form-select-sm"> <div id="ex-forms-wrap" class="border rounded p-2" style="max-height:120px;overflow-y:auto;background:#fffbeb">
<option value="">— Sin consentimiento —</option>
<?php foreach ($formulariosCons as $fc): ?> <?php foreach ($formulariosCons as $fc): ?>
<option value="<?= $fc['id'] ?>"><?= htmlspecialchars($fc['nombre']) ?></option> <div class="form-check mb-1">
<input class="form-check-input ex-form-chk" type="checkbox"
value="<?= $fc['id'] ?>" id="ex-fc-<?= $fc['id'] ?>">
<label class="form-check-label small" for="ex-fc-<?= $fc['id'] ?>">
<?= htmlspecialchars($fc['nombre']) ?>
</label>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</select> <?php if (empty($formulariosCons)): ?>
<span class="text-muted small">Sin formularios activos</span>
<?php endif; ?>
</div>
</div> </div>
<div class="col-12 text-end"> <div class="col-12 text-end">
<button class="btn btn-primary btn-sm" onclick="guardarExamen()"> <button class="btn btn-primary btn-sm" onclick="guardarExamen()">
@@ -738,13 +746,21 @@ $tab = $_GET['tab'] ?? 'lugares';
<input type="text" id="edit-ex-categoria" class="form-control form-control-sm"> <input type="text" id="edit-ex-categoria" class="form-control form-control-sm">
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label small fw-semibold">Consentimiento</label> <label class="form-label small fw-semibold">Consentimientos</label>
<select id="edit-ex-form" class="form-select form-select-sm"> <div id="edit-ex-forms-wrap" class="border rounded p-2" style="max-height:120px;overflow-y:auto;background:#fffbeb">
<option value="">— Sin consentimiento —</option>
<?php foreach ($formulariosCons as $fc): ?> <?php foreach ($formulariosCons as $fc): ?>
<option value="<?= $fc['id'] ?>"><?= htmlspecialchars($fc['nombre']) ?></option> <div class="form-check mb-1">
<input class="form-check-input edit-ex-form-chk" type="checkbox"
value="<?= $fc['id'] ?>" id="edit-ex-fc-<?= $fc['id'] ?>">
<label class="form-check-label small" for="edit-ex-fc-<?= $fc['id'] ?>">
<?= htmlspecialchars($fc['nombre']) ?>
</label>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</select> <?php if (empty($formulariosCons)): ?>
<span class="text-muted small">Sin formularios activos</span>
<?php endif; ?>
</div>
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="form-check"> <div class="form-check">
@@ -1340,15 +1356,17 @@ async function eliminarDispositivo(id, nombre) {
// TAB 2: EXÁMENES // TAB 2: EXÁMENES
// ───────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────
async function guardarExamen(id = null) { async function guardarExamen(id = null) {
const codigo = (id ? document.getElementById('edit-ex-codigo') : document.getElementById('ex-codigo'))?.value.trim().toUpperCase(); const prefix = id ? 'edit-ex' : 'ex';
const nombre = (id ? document.getElementById('edit-ex-nombre') : document.getElementById('ex-nombre'))?.value.trim(); const codigo = document.getElementById(prefix + '-codigo')?.value.trim().toUpperCase();
const categoria = (id ? document.getElementById('edit-ex-categoria'): document.getElementById('ex-categoria'))?.value.trim(); const nombre = document.getElementById(prefix + '-nombre')?.value.trim();
const formId = parseInt((id ? document.getElementById('edit-ex-form') : document.getElementById('ex-form'))?.value) || null; const categoria = document.getElementById(prefix + '-categoria')?.value.trim();
const activo = id ? (document.getElementById('edit-ex-activo')?.checked ? 1 : 0) : 1; const activo = id ? (document.getElementById('edit-ex-activo')?.checked ? 1 : 0) : 1;
const chkClass = id ? 'edit-ex-form-chk' : 'ex-form-chk';
const form_ids = [...document.querySelectorAll(`.${chkClass}:checked`)].map(c => parseInt(c.value));
if (!codigo || !nombre) { toast('Código y nombre son requeridos', 'error'); return; } if (!codigo || !nombre) { toast('Código y nombre son requeridos', 'error'); return; }
const body = { codigo, nombre, categoria, formulario_id: formId, activo }; const body = { codigo, nombre, categoria, form_ids, activo };
if (id) body.id = id; if (id) body.id = id;
try { try {
@@ -1359,25 +1377,27 @@ async function guardarExamen(id = null) {
if (!json.ok) { toast(json.error || 'Error', 'error'); return; } if (!json.ok) { toast(json.error || 'Error', 'error'); return; }
toast(id ? 'Examen actualizado' : 'Examen creado'); toast(id ? 'Examen actualizado' : 'Examen creado');
if (id) bootstrap.Modal.getInstance(document.getElementById('modalEditarExamen'))?.hide(); if (id) bootstrap.Modal.getInstance(document.getElementById('modalEditarExamen'))?.hide();
// Limpiar checkboxes del formulario de agregar
if (!id) document.querySelectorAll('.ex-form-chk').forEach(c => c.checked = false);
setTimeout(() => location.reload(), 600); setTimeout(() => location.reload(), 600);
} catch (e) { toast('Error de conexión', 'error'); } } catch (e) { toast('Error de conexión', 'error'); }
} }
function editarExamen(id) { function editarExamen(id) {
const row = document.querySelector(`[data-exam-id="${id}"]`);
const tags = row ? row.querySelectorAll('.consent-tag') : [];
// Leer datos de los atributos del servidor (via fetch de datos ya cargados)
fetch(API + 'save_ex_tipo.php?id=' + id) fetch(API + 'save_ex_tipo.php?id=' + id)
.then(r => r.json()) .then(r => r.json())
.then(json => { .then(json => {
if (!json.ok) return; if (!json.ok) return;
const d = json.data; const d = json.data;
document.getElementById('edit-ex-id').value = d.id; document.getElementById('edit-ex-id').value = d.id;
document.getElementById('edit-ex-codigo').value = d.codigo; document.getElementById('edit-ex-codigo').value = d.codigo;
document.getElementById('edit-ex-nombre').value = d.nombre; document.getElementById('edit-ex-nombre').value = d.nombre;
document.getElementById('edit-ex-categoria').value= d.categoria || ''; document.getElementById('edit-ex-categoria').value = d.categoria || '';
document.getElementById('edit-ex-form').value = d.formulario_id || ''; document.getElementById('edit-ex-activo').checked = !!d.activo;
document.getElementById('edit-ex-activo').checked = !!d.activo; // Marcar consentimientos vinculados
document.querySelectorAll('.edit-ex-form-chk').forEach(chk => {
chk.checked = (d.form_ids || []).includes(parseInt(chk.value));
});
new bootstrap.Modal(document.getElementById('modalEditarExamen')).show(); new bootstrap.Modal(document.getElementById('modalEditarExamen')).show();
}) })
.catch(() => toast('Error cargando datos', 'error')); .catch(() => toast('Error cargando datos', 'error'));