feat: ciclos configurables + gestión de exámenes en Tomas Prolongadas
Feature 1 — Ciclos configurables por paciente: - ver_formulario_enviado.php: pantalla de configuración aparece la primera vez que el profesional abre el formulario (antes de firmar); permite seleccionar qué tomas realizar para cada protocolo. $_mpSkippedSepIds oculta las secciones no seleccionadas en render. $_mpMap se reconstruye desde los grupos filtrados para que next_firma_id salte tomas omitidas. - configurar_tomas.php (nuevo): guarda _tomas_config en datos_respuestas del consentimiento; valida que cada grupo tenga al menos una toma. - guardar_toma.php: aplica el filtro _tomas_config al array firmasCampos para que el conteo de tomas firmadas/pendientes sea correcto. Feature 2 — Gestión de tipos de examen (admin): - lab_tomas_config.php (nuevo): página admin que muestra los exámenes actuales con sus tomas y permite añadir nuevos tipos con su esquema completo (separador + hora + resultado + firma_profesional por toma). - api/lab/save_tomas_config.php (nuevo): modifica el esquema JSON del formulario id=15; add_exam agrega opción al selector y los campos de cada toma; delete_exam elimina la opción y los separadores exclusivos. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3e110324a8
commit
2eefe3f5c8
+161
-2
@@ -248,14 +248,19 @@ $_soloFirmaPro = ($modoTurnero && !empty($tcRow['es_toma_progresiva']))
|
||||
|
||||
// Muestras Prolongadas: detectar secciones hora+firma_profesional con intervalos de minutos.
|
||||
// Agrupa por condición del separador para no mezclar Glicemia y Cortisol (ambas usan prefijo "Toma").
|
||||
$_mpMap = [];
|
||||
$_mpMap = [];
|
||||
$_mpGroupsFull = []; // grupos sin filtrar (para la pantalla de configuración)
|
||||
$_mpSkippedSepIds = []; // separadores de tomas no seleccionadas (se ocultarán en render)
|
||||
$_tomasConfig = null; // configuración guardada por paciente ({grupo:[firmaId,...]})
|
||||
|
||||
if ($modoTurnero && $embebido && $_soloFirmaPro) {
|
||||
$_mpSecs = []; $_mpLabel = null; $_mpMin = null; $_mpHoraFija = null; $_mpHora = null;
|
||||
$_mpCondKey = null; $_mpCondDisplay = null;
|
||||
$_mpCondKey = null; $_mpCondDisplay = null; $_mpSepId = null;
|
||||
foreach ($esquema as $_c) {
|
||||
$_t = $_c['tipo'] ?? '';
|
||||
if ($_t === 'separador') {
|
||||
$_mpLabel = $_c['label'] ?? ''; $_mpMin = null; $_mpHoraFija = null; $_mpHora = null;
|
||||
$_mpSepId = $_c['id'] ?? null;
|
||||
$_mpCond = $_c['condicion'] ?? null;
|
||||
// Clave de agrupación: usa valores de condición para distinguir Glicemia vs Cortisol
|
||||
if ($_mpCond) {
|
||||
@@ -279,6 +284,7 @@ if ($modoTurnero && $embebido && $_soloFirmaPro) {
|
||||
} elseif ($_t === 'firma_profesional' && $_mpHora !== null && ($_mpMin !== null || $_mpHoraFija !== null)) {
|
||||
$_mpSecs[] = [
|
||||
'label' => $_mpLabel,
|
||||
'sep_id' => $_mpSepId,
|
||||
'min' => $_mpMin ?? PHP_INT_MAX,
|
||||
'hora_fija' => $_mpHoraFija,
|
||||
'hora' => $_mpHora,
|
||||
@@ -291,6 +297,26 @@ if ($modoTurnero && $embebido && $_soloFirmaPro) {
|
||||
}
|
||||
$_mpGroups = [];
|
||||
foreach ($_mpSecs as $_s) { $_mpGroups[$_s['tipo']][] = $_s; }
|
||||
$_mpGroupsFull = $_mpGroups;
|
||||
|
||||
// Aplicar filtro de ciclos si el profesional ya configuró las tomas para este paciente
|
||||
$__rawCfg = $datosCliente['_tomas_config'] ?? null;
|
||||
if (is_array($__rawCfg)) {
|
||||
$_tomasConfig = $__rawCfg;
|
||||
foreach ($_mpGroups as $__gk => &$__gsecs) {
|
||||
if (!isset($_tomasConfig[$__gk])) continue;
|
||||
$__allowed = array_flip($_tomasConfig[$__gk]);
|
||||
foreach ($__gsecs as $__s) {
|
||||
if (!isset($__allowed[$__s['firma']])) {
|
||||
$_mpSkippedSepIds[$__s['sep_id']] = true;
|
||||
}
|
||||
}
|
||||
$__gsecs = array_values(array_filter($__gsecs, fn($s) => isset($__allowed[$s['firma']])));
|
||||
}
|
||||
unset($__gsecs);
|
||||
}
|
||||
|
||||
// Construir $_mpMap desde grupos (posiblemente filtrados)
|
||||
foreach ($_mpGroups as $_tipo => $_secs) {
|
||||
for ($i = 0, $n = count($_secs); $i < $n; $i++) {
|
||||
$_s = $_secs[$i]; $_nx = $_secs[$i + 1] ?? null;
|
||||
@@ -513,6 +539,24 @@ function esc2(mixed $v): string {
|
||||
display:inline-flex; align-items:center; gap:5px; transition:all .15s; }
|
||||
.btn-topaz:hover { background:#e0f2fe; }
|
||||
@keyframes mp-pulse { 0%,100%{opacity:1} 50%{opacity:.65} }
|
||||
|
||||
/* ── Panel configuración de ciclos ─────────────────── */
|
||||
.mp-cfg-overlay { position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.45);
|
||||
display:flex;align-items:flex-start;justify-content:center;
|
||||
padding:1rem;overflow-y:auto; }
|
||||
.mp-cfg-card { background:#fff;border-radius:14px;max-width:500px;width:100%;
|
||||
box-shadow:0 12px 48px rgba(0,0,0,.3);margin-top:.5rem; }
|
||||
.mp-cfg-head { background:<?= htmlspecialchars($docColor) ?>;color:#fff;
|
||||
padding:1rem 1.25rem;border-radius:14px 14px 0 0;
|
||||
font-size:1rem;font-weight:700; }
|
||||
.mp-cfg-body { padding:1.25rem; }
|
||||
.mp-cfg-foot { padding:0 1.25rem 1.25rem; }
|
||||
.mp-cfg-gtitle { font-weight:700;font-size:.88rem;color:#1e3a5f;margin-bottom:.5rem;
|
||||
border-left:3px solid <?= htmlspecialchars($docColor) ?>;padding-left:.5rem; }
|
||||
.mp-cfg-item { border:1.5px solid #e5e7eb;border-radius:8px;padding:.35rem .6rem;
|
||||
transition:all .15s;cursor:pointer;user-select:none; }
|
||||
.mp-cfg-item:has(.form-check-input:checked) { background:#f0fdf4;border-color:#86efac; }
|
||||
.mp-cfg-item label { cursor:pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -601,6 +645,70 @@ function esc2(mixed $v): string {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// ── Panel de configuración de ciclos (se muestra antes de iniciar el protocolo) ──
|
||||
$_examOptions = [];
|
||||
foreach ($esquema as $_oc) {
|
||||
if (($_oc['id'] ?? '') === '_c8j2g16') { $_examOptions = $_oc['options'] ?? []; break; }
|
||||
}
|
||||
$_examSel = (array)($datosCliente['_c8j2g16'] ?? []);
|
||||
$_gruposConfig = [];
|
||||
foreach ($_mpGroupsFull as $_gk => $_gsecs) {
|
||||
if (empty($_gsecs)) continue;
|
||||
$__parts = array_values(array_filter(explode('|', $_gk)));
|
||||
$__isCond = !empty(array_intersect($__parts, $_examOptions));
|
||||
$__matchSel = !empty(array_intersect($__parts, $_examSel));
|
||||
if (!$__isCond || $__matchSel) {
|
||||
$__title = implode(' / ', array_intersect($__parts, $_examSel));
|
||||
if (!$__title) $__title = $_gsecs[0]['display'] ?? $_gk;
|
||||
$_gruposConfig[$_gk] = ['title' => $__title, 'secs' => $_gsecs];
|
||||
}
|
||||
}
|
||||
$_showCfgPanel = $modoTurnero && $embebido && !empty($_gruposConfig) && $_tomasConfig === null;
|
||||
?>
|
||||
<?php if ($_showCfgPanel): ?>
|
||||
<div class="mp-cfg-overlay" id="mp-cfg-overlay">
|
||||
<div class="mp-cfg-card">
|
||||
<div class="mp-cfg-head">
|
||||
<i class="fas fa-sliders-h me-2"></i>Configurar protocolo de tomas
|
||||
</div>
|
||||
<div class="mp-cfg-body">
|
||||
<p class="text-muted small mb-3">
|
||||
Seleccione las tomas a realizar para este paciente.
|
||||
Puede desmarcar las tomas que no apliquen según la indicación médica.
|
||||
</p>
|
||||
<?php foreach ($_gruposConfig as $_cgk => $_cg): ?>
|
||||
<div class="mb-3">
|
||||
<div class="mp-cfg-gtitle"><?= esc2($_cg['title']) ?></div>
|
||||
<div class="row g-2">
|
||||
<?php foreach ($_cg['secs'] as $_cs): ?>
|
||||
<div class="col-6 col-sm-4">
|
||||
<div class="mp-cfg-item form-check">
|
||||
<input class="form-check-input mp-cfg-chk" type="checkbox"
|
||||
id="cfg-<?= esc2($_cs['firma']) ?>"
|
||||
value="<?= esc2($_cs['firma']) ?>"
|
||||
data-group="<?= esc2($_cgk) ?>"
|
||||
checked>
|
||||
<label class="form-check-label small" for="cfg-<?= esc2($_cs['firma']) ?>">
|
||||
<?= esc2($_cs['label']) ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="mp-cfg-foot">
|
||||
<button class="btn btn-success btn-lg w-100" id="mp-cfg-iniciar">
|
||||
<i class="fas fa-play-circle me-2"></i>Iniciar protocolo
|
||||
</button>
|
||||
<div id="mp-cfg-msg" class="text-center mt-2 small"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="doc-body">
|
||||
|
||||
<!-- Info del paciente: omitir si el esquema ya tiene campos linked que la muestran -->
|
||||
@@ -674,6 +782,10 @@ function esc2(mixed $v): string {
|
||||
} else {
|
||||
$_saltarSeccion = false;
|
||||
}
|
||||
// Ocultar secciones de tomas excluidas por la configuración de ciclos del paciente
|
||||
if (!$_saltarSeccion && isset($_mpSkippedSepIds[$campo['id'] ?? ''])) {
|
||||
$_saltarSeccion = true;
|
||||
}
|
||||
if ($_saltarSeccion) continue; ?>
|
||||
<div class="esquema-sep<?= $compact ? ' campo-full' : '' ?>" data-campo-id="<?= esc2($campo['id'] ?? '') ?>"><?= esc2($campo['label'] ?? '') ?></div>
|
||||
<?php continue; endif;
|
||||
@@ -1095,6 +1207,53 @@ function esc2(mixed $v): string {
|
||||
|
||||
<script>
|
||||
<?php if (!empty($_mpMap)): ?>
|
||||
<?php if ($_showCfgPanel ?? false): ?>
|
||||
/* ── Panel de configuración de ciclos ── */
|
||||
(function() {
|
||||
var btn = document.getElementById('mp-cfg-iniciar');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', function() {
|
||||
var byGroup = {};
|
||||
document.querySelectorAll('.mp-cfg-chk').forEach(function(chk) {
|
||||
var g = chk.dataset.group;
|
||||
if (!byGroup[g]) byGroup[g] = { total: 0, sel: [] };
|
||||
byGroup[g].total++;
|
||||
if (chk.checked) byGroup[g].sel.push(chk.value);
|
||||
});
|
||||
var config = {};
|
||||
for (var g in byGroup) {
|
||||
if (byGroup[g].sel.length === 0) {
|
||||
document.getElementById('mp-cfg-msg').innerHTML =
|
||||
'<span class="text-danger"><i class="fas fa-exclamation-circle me-1"></i>Seleccione al menos una toma por protocolo.</span>';
|
||||
return;
|
||||
}
|
||||
config[g] = byGroup[g].sel;
|
||||
}
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Guardando...';
|
||||
var base = window.location.href.split('/ver_formulario_enviado.php')[0];
|
||||
fetch(base + '/modules/turnero/api/configurar_tomas.php', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token: <?= json_encode($tokenTurnero) ?>, tomas_config: config })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok) { location.reload(); }
|
||||
else {
|
||||
document.getElementById('mp-cfg-msg').innerHTML =
|
||||
'<span class="text-danger">Error: ' + (d.error || 'Error desconocido') + '</span>';
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-play-circle me-2"></i>Iniciar protocolo';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
document.getElementById('mp-cfg-msg').innerHTML = '<span class="text-danger">Error de conexión.</span>';
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-play-circle me-2"></i>Iniciar protocolo';
|
||||
});
|
||||
});
|
||||
})();
|
||||
<?php endif; ?>
|
||||
window._muestrasMap = <?= json_encode($_mpMap, JSON_UNESCAPED_UNICODE) ?>;
|
||||
window._mpSavedFirmas = {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user