feat(turnero): anexar formulario adicional a turno desde lugar.php

Permite al operador agregar un consentimiento informado extra (fuera de
los asignados por defecto) mientras el turno está en servicio.

- PHP: consulta lab_formularios activos al cargar lugar.php
- JS: modal de selección con lista de formularios
- Botón visible solo cuando turno está en_servicio, oculto en resetFicha
- API: modules/turnero/api/anexar_formulario.php crea el consentimiento
  y notifica SSE para que el dispositivo lo vea inmediatamente
- Fix: _olvidarConsentimiento usaba cargarConsentimientos() inexistente;
  corregido a actualizarConsentimientos(turnoActivo.id)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-28 07:49:06 -05:00
co-authored by Claude Sonnet 4.6
parent ff52fa5ba7
commit afa7cc6812
2 changed files with 115 additions and 1 deletions
+43
View File
@@ -0,0 +1,43 @@
<?php
/**
* POST /modules/turnero/api/anexar_formulario.php
* Crea un consentimiento adicional para un turno activo.
* Body JSON: { turno_id: int, formulario_id: int }
*/
require_once __DIR__ . '/_helpers.php';
requireMethod('POST');
requireTurnero();
$body = inputJson();
$turnoId = (int)($body['turno_id'] ?? 0);
$formularioId = (int)($body['formulario_id'] ?? 0);
if (!$turnoId) jsonError('turno_id requerido.');
if (!$formularioId) jsonError('formulario_id requerido.');
$pdo = db();
// Verificar que el turno existe y está activo
$turno = $pdo->prepare("SELECT id, sesion_id, estado FROM turnero_turnos WHERE id = ? LIMIT 1");
$turno->execute([$turnoId]);
$t = $turno->fetch(PDO::FETCH_ASSOC);
if (!$t) jsonError('Turno no encontrado.', 404);
if (!in_array($t['estado'], ['en_espera_lugar', 'en_servicio'])) jsonError('El turno no está activo.');
// Verificar que el formulario existe
$form = $pdo->prepare("SELECT id, nombre FROM lab_formularios WHERE id = ? AND is_active = 1 LIMIT 1");
$form->execute([$formularioId]);
if (!$form->fetch()) jsonError('Formulario no encontrado o inactivo.', 404);
// Crear el consentimiento con token único
$token = bin2hex(random_bytes(16));
$ins = $pdo->prepare(
"INSERT INTO turnero_consentimientos (turno_id, formulario_id, token, estado, datos_respuestas)
VALUES (?, ?, ?, 'enviado', '{}')"
);
$ins->execute([$turnoId, $formularioId, $token]);
$newId = (int)$pdo->lastInsertId();
notificarSSE((int)$t['sesion_id']);
jsonOk(['id' => $newId, 'token' => $token], 'Formulario agregado.');
+72 -1
View File
@@ -92,6 +92,13 @@ foreach ($lugares as $_el) {
$especialidades[] = $_el;
}
}
$formulariosList = [];
try {
$formulariosList = Database::getInstance()->getConnection()
->query("SELECT id, nombre FROM lab_formularios WHERE is_active = 1 ORDER BY nombre ASC")
->fetchAll(PDO::FETCH_ASSOC);
} catch (\Throwable $_) {}
?>
<!DOCTYPE html>
<html lang="es">
@@ -836,6 +843,11 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
<div id="sin-consent" class="text-muted small">
<i class="fas fa-check-circle text-success me-1"></i>No se requieren consentimientos
</div>
<div id="btn-anexar-wrap" class="d-none mt-2">
<button type="button" class="btn btn-outline-secondary btn-sm w-100" onclick="_abrirAnexarFormulario()">
<i class="fas fa-plus me-1"></i>Agregar formulario adicional
</button>
</div>
</div>
<!-- ── Muestras (solo estaciones tipo muestras) ── -->
@@ -1027,6 +1039,7 @@ let _tomaProgresivaActiva = false;
let hayMuestrasPendientes = false;
let _solicitudActiva = null;
let _muestrasActivas = [];
const _formulariosList = <?= json_encode($formulariosList, JSON_UNESCAPED_UNICODE) ?>;
const API = '<?= BASE_URL ?>modules/turnero/api/';
const BASE_WA = '<?= BASE_URL ?>';
@@ -1667,7 +1680,7 @@ async function _olvidarConsentimiento(token, nombre) {
});
const j = await r.json();
if (!j.ok) { mostrarError(j.error || 'Error al resetear'); return; }
cargarConsentimientos();
if (turnoActivo?.id) actualizarConsentimientos(turnoActivo.id);
} catch(e) { mostrarError(e.message); }
}
@@ -2061,10 +2074,45 @@ function resetFicha() {
document.getElementById('btn-regresar').classList.add('d-none');
document.getElementById('btn-rellamar').classList.add('d-none');
document.getElementById('btn-devolver').classList.add('d-none');
document.getElementById('btn-anexar-wrap')?.classList.add('d-none');
volverACola();
}
// ── Anexar formulario ─────────────────────────────────────────
function _abrirAnexarFormulario() {
const sel = document.getElementById('sel-anexar-form');
sel.innerHTML = '<option value="">— Seleccionar formulario —</option>';
_formulariosList.forEach(f => {
const o = document.createElement('option');
o.value = f.id; o.textContent = f.nombre;
sel.appendChild(o);
});
document.getElementById('anexar-form-msg').style.display = 'none';
document.getElementById('btn-guardar-anexar').disabled = false;
document.getElementById('modal-anexar-form').style.display = 'flex';
}
function _cerrarAnexarFormulario() {
document.getElementById('modal-anexar-form').style.display = 'none';
}
async function _guardarAnexarFormulario() {
const fId = parseInt(document.getElementById('sel-anexar-form').value);
const msg = document.getElementById('anexar-form-msg');
if (!fId) { msg.textContent = 'Selecciona un formulario.'; msg.style.display=''; return; }
const btn = document.getElementById('btn-guardar-anexar');
btn.disabled = true;
try {
const r = await fetch('modules/turnero/api/anexar_formulario.php', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ turno_id: turnoActivo.id, formulario_id: fId })
});
const j = await r.json();
if (!j.ok) { msg.textContent = j.error || 'Error al agregar.'; msg.style.display=''; btn.disabled=false; return; }
_cerrarAnexarFormulario();
actualizarConsentimientos(turnoActivo.id);
} catch(e) { msg.textContent = e.message; msg.style.display=''; btn.disabled=false; }
}
// ── Mobile ────────────────────────────────────────────────────
function esMobile() { return window.innerWidth <= 680; }
@@ -2099,6 +2147,7 @@ function actualizarEstadoBadge(estado) {
const btnDev = document.getElementById('btn-devolver');
const btnEsp = document.getElementById('btn-espera');
const btnAnexar = document.getElementById('btn-anexar-wrap');
if (estado === 'en_espera_lugar') {
btnIni.classList.remove('d-none'); btnIni.disabled = false;
btnFin.classList.add('d-none');
@@ -2106,6 +2155,7 @@ function actualizarEstadoBadge(estado) {
btnRellamar.classList.add('d-none');
btnDev.classList.remove('d-none');
if (btnEsp) btnEsp.classList.add('d-none');
if (btnAnexar) btnAnexar.classList.add('d-none');
} else if (estado === 'en_servicio') {
btnIni.classList.add('d-none');
btnFin.classList.remove('d-none');
@@ -2114,6 +2164,7 @@ function actualizarEstadoBadge(estado) {
btnDev.classList.remove('d-none');
// btn-espera: solo si hay toma progresiva activa (sincronizado desde renderConsentimientos)
if (btnEsp) btnEsp.classList.add('d-none');
if (btnAnexar) btnAnexar.classList.remove('d-none');
}
}
@@ -2864,6 +2915,26 @@ function _renderEspecialidades(grupos) {
})();
</script>
<!-- ── Modal: Anexar formulario ──────────────────────────────── -->
<div id="modal-anexar-form" style="display:none;position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.5);align-items:center;justify-content:center;">
<div style="background:var(--bs-body-bg,#fff);border-radius:10px;padding:1.25rem;width:min(380px,92vw);box-shadow:0 8px 32px rgba(0,0,0,.25)">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;">
<strong><i class="fas fa-file-plus me-2"></i>Agregar formulario adicional</strong>
<button type="button" onclick="_cerrarAnexarFormulario()" style="background:none;border:none;font-size:1.2rem;cursor:pointer;opacity:.6">&times;</button>
</div>
<select id="sel-anexar-form" class="form-select mb-3">
<option value="">— Seleccionar formulario —</option>
</select>
<div id="anexar-form-msg" class="small text-danger mb-2" style="display:none"></div>
<div style="display:flex;gap:.5rem;justify-content:flex-end">
<button type="button" class="btn btn-secondary btn-sm" onclick="_cerrarAnexarFormulario()">Cancelar</button>
<button type="button" class="btn btn-primary btn-sm" id="btn-guardar-anexar" onclick="_guardarAnexarFormulario()">
<i class="fas fa-plus me-1"></i>Agregar
</button>
</div>
</div>
</div>
<script src="<?= defined('APP_URL') ? rtrim(APP_URL,'/') : '' ?>/assets/js/lab-sidebar.js"></script>
</body>
</html>