Simplificar tomas prolongadas: campo texto libre + sin condiciones
- _c8j2g16 pasa a texto libre ("Tipo de examen"), sin opciones predefinidas
- Separadores de tomas sin condición → siempre visibles
- Eliminar lab_tomas_config.php (ya no necesario)
- $_gruposConfig incluye todos los grupos directamente
- Script _migrate_tomas_simplify.php para actualizar esquema en BD
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
645c9b2c46
commit
4e6da5d7eb
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Migración: simplificar formulario de Tomas Prolongadas (id=15)
|
||||
* - Campo _c8j2g16 pasa de radio/checkbox a texto libre ("Tipo de examen")
|
||||
* - Se eliminan las condiciones de todos los separadores (siempre visibles)
|
||||
* Auto-elimina al ejecutar. Acceder como admin.
|
||||
*/
|
||||
require_once __DIR__ . '/config/config.php';
|
||||
if (!isUserLoggedIn() || !in_array($_SESSION['admin_user']['role'] ?? '', ['admin','superadmin'], true)) {
|
||||
http_response_code(403); die('Acceso denegado.');
|
||||
}
|
||||
|
||||
$pdo = Database::getInstance()->getConnection();
|
||||
$row = $pdo->query("SELECT esquema FROM lab_formularios WHERE id = 15 LIMIT 1")->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$row) { die('Formulario id=15 no encontrado.'); }
|
||||
|
||||
$esquema = json_decode($row['esquema'], true);
|
||||
if (!is_array($esquema)) { die('Esquema inválido.'); }
|
||||
|
||||
$cambios = 0;
|
||||
foreach ($esquema as &$campo) {
|
||||
$id = $campo['id'] ?? '';
|
||||
$tipo = $campo['tipo'] ?? '';
|
||||
|
||||
// Campo selector → texto libre
|
||||
if ($id === '_c8j2g16') {
|
||||
$campo['tipo'] = 'texto';
|
||||
$campo['label'] = 'Tipo de examen';
|
||||
unset($campo['options'], $campo['multiple'], $campo['required']);
|
||||
$cambios++;
|
||||
}
|
||||
|
||||
// Separadores con condición sobre _c8j2g16 → quitar condición
|
||||
if ($tipo === 'separador' && isset($campo['condicion'])) {
|
||||
$condCampo = $campo['condicion']['campo_id'] ?? '';
|
||||
if ($condCampo === '_c8j2g16') {
|
||||
unset($campo['condicion']);
|
||||
$cambios++;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($campo);
|
||||
|
||||
$pdo->prepare("UPDATE lab_formularios SET esquema = ? WHERE id = 15")
|
||||
->execute([json_encode($esquema, JSON_UNESCAPED_UNICODE)]);
|
||||
|
||||
@unlink(__FILE__);
|
||||
|
||||
echo '<p style="font-family:sans-serif;padding:2rem">
|
||||
<span style="color:green;font-size:1.2rem">✅ Migración completada</span><br><br>
|
||||
<strong>' . $cambios . ' campos modificados</strong>:<br>
|
||||
• Campo _c8j2g16 → texto libre "Tipo de examen"<br>
|
||||
• Condiciones eliminadas de separadores de tomas<br><br>
|
||||
<small style="color:#666">Este archivo fue eliminado automáticamente.</small><br><br>
|
||||
<a href="lab_formularios.php" style="color:#0d6efd">← Volver a Formularios</a>
|
||||
</p>';
|
||||
@@ -444,11 +444,6 @@ function renderFormularios() {
|
||||
onclick="envio.abrir(${f.id})">
|
||||
<i class="fas fa-paper-plane me-1"></i>Enviar
|
||||
</button>
|
||||
${f.es_toma_progresiva && PUEDE_ESCRIBIR ? `
|
||||
<a class="btn btn-sm btn-outline-info" href="lab_tomas_config.php"
|
||||
title="Configurar tipos de examen de tomas prolongadas">
|
||||
<i class="fas fa-vials"></i>
|
||||
</a>` : ''}
|
||||
${PUEDE_ESCRIBIR ? `
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="clonarFormulario(${f.id},'${esc(f.nombre)}')"
|
||||
title="Clonar formulario"><i class="fas fa-copy"></i></button>
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* lab_tomas_config.php — Configuración de tipos de examen en Tomas Prolongadas (F-LAB-28)
|
||||
* Admin: añadir / eliminar tipos de examen y sus esquemas de tomas.
|
||||
*/
|
||||
require_once 'config/config.php';
|
||||
if (!defined('APP_ROOT')) { define('APP_ROOT', __DIR__); }
|
||||
if (!isUserLoggedIn()) { header('Location: login.php'); exit; }
|
||||
if (isEnfermero()) { header('Location: index.php'); exit; }
|
||||
|
||||
$db = Database::getInstance();
|
||||
$row = $db->fetch("SELECT esquema FROM lab_formularios WHERE id = 15 LIMIT 1");
|
||||
if (!$row) { die('Formulario de tomas no encontrado (id=15).'); }
|
||||
|
||||
$esquema = json_decode($row['esquema'], true) ?? [];
|
||||
|
||||
// Leer campo selector de examen
|
||||
$selectorCampo = null;
|
||||
foreach ($esquema as $c) {
|
||||
if (($c['id'] ?? '') === '_c8j2g16') { $selectorCampo = $c; break; }
|
||||
}
|
||||
$examOptions = $selectorCampo['options'] ?? [];
|
||||
|
||||
// Agrupar tomas por tipo de examen (leer la misma lógica del $_mpMap)
|
||||
$gruposActuales = [];
|
||||
$curLabel = null; $curCond = null; $curSepId = null;
|
||||
$curMin = null; $curHoraFija = null; $curHora = null;
|
||||
foreach ($esquema as $c) {
|
||||
$tipo = $c['tipo'] ?? '';
|
||||
if ($tipo === 'separador') {
|
||||
$curLabel = $c['label'] ?? '';
|
||||
$curSepId = $c['id'] ?? null;
|
||||
$curCond = $c['condicion'] ?? null;
|
||||
$curMin = $curHoraFija = $curHora = null;
|
||||
if (preg_match('/[Mm]inuto\s+(\d+)/u', $curLabel, $mx)) {
|
||||
$curMin = (int)$mx[1];
|
||||
} elseif (preg_match('/(\d{1,2}):(\d{2})\s*(a\.?m\.?|p\.?m\.?)/i', $curLabel, $mx)) {
|
||||
$h = (int)$mx[1]; $m2 = (int)$mx[2];
|
||||
$pm = strtolower(preg_replace('/[^apm]/i', '', $mx[3])) === 'pm';
|
||||
if ($pm && $h < 12) $h += 12;
|
||||
$curHoraFija = sprintf('%02d:%02d', $h, $m2);
|
||||
$curMin = PHP_INT_MAX;
|
||||
}
|
||||
} elseif ($tipo === 'hora' && ($curMin !== null || $curHoraFija !== null)) {
|
||||
$curHora = $c['id'] ?? null;
|
||||
} elseif ($tipo === 'firma_profesional' && $curHora !== null && ($curMin !== null || $curHoraFija !== null)) {
|
||||
// Determinar a qué examen pertenece
|
||||
$examKey = 'Sin condición';
|
||||
if ($curCond) {
|
||||
$vals = $curCond['valores'] ?? [];
|
||||
$examKey = count($vals) === 1 ? $vals[0] : implode(' / ', $vals);
|
||||
} else {
|
||||
// Label-based: prefijo antes de "·"
|
||||
$examKey = trim(explode('·', $curLabel)[0]);
|
||||
}
|
||||
if (!isset($gruposActuales[$examKey])) $gruposActuales[$examKey] = [];
|
||||
$gruposActuales[$examKey][] = [
|
||||
'label' => $curLabel,
|
||||
'sep_id' => $curSepId,
|
||||
'hora_fija' => $curHoraFija,
|
||||
'min' => $curMin === PHP_INT_MAX ? null : $curMin,
|
||||
'firma_id' => $c['id'],
|
||||
];
|
||||
$curHora = null;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tipos de Examen — Tomas Prolongadas</title>
|
||||
<script>(function(){try{var m=window.matchMedia('(prefers-color-scheme: dark)');function a(d){document.documentElement.setAttribute('data-bs-theme',d?'dark':'light');}a(m.matches);m.addEventListener('change',function(e){a(e.matches);});}catch(e){}})();</script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="./assets/css/styles.css?v=15" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php
|
||||
$SIDEBAR_TITLE = 'Panel Lab';
|
||||
$SIDEBAR_ICON = 'fas fa-flask';
|
||||
require_once __DIR__ . '/shared/components/sidebar.php';
|
||||
?>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex align-items-center mb-4 gap-2">
|
||||
<i class="fas fa-vials fa-lg text-primary"></i>
|
||||
<h4 class="mb-0 fw-bold">Configurar Tomas Prolongadas</h4>
|
||||
<span class="badge bg-secondary ms-1">F-LAB-28</span>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<!-- ── Exámenes actuales ── -->
|
||||
<div class="col-lg-7">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<span><i class="fas fa-list me-2 text-primary"></i>Tipos de examen configurados</span>
|
||||
<span class="badge bg-secondary"><?= count($examOptions) ?> tipos</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<?php if (empty($gruposActuales)): ?>
|
||||
<p class="text-muted text-center py-4">No hay grupos de tomas configurados.</p>
|
||||
<?php else: ?>
|
||||
<div class="accordion accordion-flush" id="acc-examenes">
|
||||
<?php foreach ($gruposActuales as $examNombre => $tomas): ?>
|
||||
<?php
|
||||
$accId = 'acc-' . md5($examNombre);
|
||||
$isCond = in_array($examNombre, $examOptions, true);
|
||||
?>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2 px-3" type="button"
|
||||
data-bs-toggle="collapse" data-bs-target="#<?= $accId ?>">
|
||||
<i class="fas fa-syringe me-2 text-primary small"></i>
|
||||
<strong><?= htmlspecialchars($examNombre) ?></strong>
|
||||
<span class="badge bg-light text-dark border ms-2"><?= count($tomas) ?> tomas</span>
|
||||
<?php if (!$isCond): ?>
|
||||
<span class="badge bg-info text-white ms-1 small">Sin condición</span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="<?= $accId ?>" class="accordion-collapse collapse">
|
||||
<div class="accordion-body p-0">
|
||||
<table class="table table-sm mb-0 small">
|
||||
<thead class="table-light">
|
||||
<tr><th>#</th><th>Label</th><th>Tiempo</th><th>Firma ID</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($tomas as $i => $t): ?>
|
||||
<tr>
|
||||
<td class="text-muted"><?= $i + 1 ?></td>
|
||||
<td><?= htmlspecialchars($t['label']) ?></td>
|
||||
<td>
|
||||
<?php if ($t['hora_fija']): ?>
|
||||
<span class="badge bg-warning text-dark">Fija <?= htmlspecialchars($t['hora_fija']) ?></span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-light text-dark border">Min <?= (int)$t['min'] ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="font-monospace text-muted small"><?= htmlspecialchars($t['firma_id']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php if ($isCond): ?>
|
||||
<div class="p-2 text-end">
|
||||
<button class="btn btn-sm btn-outline-danger btn-delete-exam"
|
||||
data-exam="<?= htmlspecialchars($examNombre, ENT_QUOTES) ?>">
|
||||
<i class="fas fa-trash me-1"></i>Eliminar examen
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Agregar nuevo examen ── -->
|
||||
<div class="col-lg-5">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-plus-circle me-2 text-success"></i>Agregar nuevo tipo de examen
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">Nombre del examen</label>
|
||||
<input type="text" id="new-exam-name" class="form-control"
|
||||
placeholder="Ej: Insulina Basal y Post-Carga" maxlength="80">
|
||||
<div class="form-text">Debe ser único. Aparecerá en el selector de examen del formulario.</div>
|
||||
</div>
|
||||
|
||||
<label class="form-label fw-semibold">Tomas</label>
|
||||
<div id="tomas-container">
|
||||
<!-- Tomas se agregan aquí dinámicamente -->
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary btn-sm mt-1" id="btn-add-toma">
|
||||
<i class="fas fa-plus me-1"></i>Agregar toma
|
||||
</button>
|
||||
|
||||
<hr>
|
||||
<button class="btn btn-success w-100" id="btn-save-exam">
|
||||
<i class="fas fa-save me-2"></i>Guardar examen
|
||||
</button>
|
||||
<div id="save-msg" class="mt-2 small text-center"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-info-circle me-2 text-info"></i>Instrucciones
|
||||
</div>
|
||||
<div class="card-body small text-muted">
|
||||
<ul class="mb-0 ps-3">
|
||||
<li>Defina el nombre exacto del examen (aparecerá en el checkbox del formulario).</li>
|
||||
<li><strong>Minutos</strong>: tiempo en minutos desde la primera toma (ej: 0, 30, 60).</li>
|
||||
<li><strong>Hora fija</strong>: hora exacta de toma (ej: 15:00 para 3pm).</li>
|
||||
<li>No mezcle minutos y hora fija en el mismo examen.</li>
|
||||
<li>Para <strong>eliminar</strong> un examen: expanda el grupo y use el botón Eliminar.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="tpl-toma">
|
||||
<div class="toma-row border rounded p-2 mb-2 bg-light position-relative">
|
||||
<button type="button" class="btn-close position-absolute top-0 end-0 m-1 btn-remove-toma" style="font-size:.65rem"></button>
|
||||
<div class="row g-2 align-items-end">
|
||||
<div class="col-5">
|
||||
<label class="form-label small mb-1">Label</label>
|
||||
<input type="text" class="form-control form-control-sm toma-label"
|
||||
placeholder="Ej: Minuto 0">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-label small mb-1">Tipo</label>
|
||||
<select class="form-select form-select-sm toma-tipo">
|
||||
<option value="minutos">Minutos</option>
|
||||
<option value="hora_fija">Hora fija</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label small mb-1 toma-valor-label">Minutos</label>
|
||||
<input type="text" class="form-control form-control-sm toma-valor"
|
||||
placeholder="0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var BASE_URL = '<?= defined('APP_URL') ? rtrim(APP_URL, '/') : '' ?>';
|
||||
|
||||
function addToma(label, tipo, valor) {
|
||||
var tpl = document.getElementById('tpl-toma').content.cloneNode(true);
|
||||
var row = tpl.querySelector('.toma-row');
|
||||
if (label) row.querySelector('.toma-label').value = label;
|
||||
if (tipo) row.querySelector('.toma-tipo').value = tipo;
|
||||
if (valor !== undefined) row.querySelector('.toma-valor').value = valor;
|
||||
row.querySelector('.toma-tipo').addEventListener('change', function() {
|
||||
var lbl = this.closest('.toma-row').querySelector('.toma-valor-label');
|
||||
var inp = this.closest('.toma-row').querySelector('.toma-valor');
|
||||
if (this.value === 'hora_fija') {
|
||||
lbl.textContent = 'HH:MM'; inp.placeholder = '15:00';
|
||||
} else {
|
||||
lbl.textContent = 'Minutos'; inp.placeholder = '0';
|
||||
}
|
||||
});
|
||||
row.querySelector('.btn-remove-toma').addEventListener('click', function() {
|
||||
this.closest('.toma-row').remove();
|
||||
});
|
||||
document.getElementById('tomas-container').appendChild(tpl);
|
||||
}
|
||||
|
||||
document.getElementById('btn-add-toma').addEventListener('click', function() { addToma(); });
|
||||
// Iniciar con una toma vacía
|
||||
addToma('Minuto 0', 'minutos', '0');
|
||||
|
||||
document.getElementById('btn-save-exam').addEventListener('click', function() {
|
||||
var examName = document.getElementById('new-exam-name').value.trim();
|
||||
if (!examName) { showMsg('Ingrese el nombre del examen.', 'danger'); return; }
|
||||
|
||||
var tomas = [];
|
||||
document.querySelectorAll('#tomas-container .toma-row').forEach(function(row) {
|
||||
tomas.push({
|
||||
label: row.querySelector('.toma-label').value.trim(),
|
||||
tipo: row.querySelector('.toma-tipo').value,
|
||||
valor: row.querySelector('.toma-valor').value.trim(),
|
||||
});
|
||||
});
|
||||
if (!tomas.length) { showMsg('Agregue al menos una toma.', 'danger'); return; }
|
||||
|
||||
var btn = this;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Guardando...';
|
||||
|
||||
fetch(BASE_URL + '/api/lab/save_tomas_config.php', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ action: 'add_exam', exam_name: examName, tomas: tomas })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (d.ok) {
|
||||
showMsg('Examen guardado. Recargando...', 'success');
|
||||
setTimeout(() => location.reload(), 1200);
|
||||
} else {
|
||||
showMsg('Error: ' + (d.error || 'Error desconocido'), 'danger');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-save me-2"></i>Guardar examen';
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
showMsg('Error de conexión.', 'danger');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fas fa-save me-2"></i>Guardar examen';
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.btn-delete-exam').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var name = this.dataset.exam;
|
||||
if (!confirm('¿Eliminar el examen "' + name + '" y todas sus tomas del formulario?\n\nEsta acción no se puede deshacer.')) return;
|
||||
fetch(BASE_URL + '/api/lab/save_tomas_config.php', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ action: 'delete_exam', exam_name: name })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (d.ok) location.reload();
|
||||
else alert('Error: ' + (d.error || 'Error desconocido'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function showMsg(msg, type) {
|
||||
document.getElementById('save-msg').innerHTML =
|
||||
'<span class="text-' + type + '">' + msg + '</span>';
|
||||
}
|
||||
</script>
|
||||
|
||||
</div><!-- /container-fluid -->
|
||||
</div><!-- /main-content -->
|
||||
|
||||
<script src="assets/js/lab-sidebar.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -647,22 +647,11 @@ function esc2(mixed $v): string {
|
||||
|
||||
<?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'] ?? []);
|
||||
// Con campo tipo texto libre, todos los grupos se muestran siempre
|
||||
$_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];
|
||||
}
|
||||
$_gruposConfig[$_gk] = ['title' => $_gsecs[0]['display'] ?? $_gk, 'secs' => $_gsecs];
|
||||
}
|
||||
// Mostrar panel solo si al menos un grupo tiene >= 2 tomas (hay algo que configurar).
|
||||
// Si todos los grupos tienen 1 sola toma, el JS auto-guarda la config sin preguntar.
|
||||
|
||||
Reference in New Issue
Block a user