feat: condición multi-valor en visibilidad condicional de formularios

Builder:
- Picker de valor ahora es <select multiple> — Ctrl+clic para varios
- Al re-editar un separador, los valores guardados quedan preseleccionados
- aplicarCampo() guarda siempre como condicion.valores (array)
- Badge en canvas muestra todos los valores separados por coma

form_cliente.php / iniciarCondiciones():
- Soporta tanto .valor (string legacy) como .valores (array nuevo)
- Evaluación: activo si CUALQUIER valor de la lista está marcado

BD (ya aplicado):
- Formulario id=15 actualizado con condiciones en los separadores
- Taukit → valor único; Heliprobe → valor único
- Glicemia/Prolactina/etc. → valores[] con todos los prolongados
- Cortisol AM/PM → valores[] para sección de tomas hora fija

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-23 23:35:03 -05:00
co-authored by Claude Sonnet 4.6
parent 21e7c6a836
commit 8bf3614fa0
2 changed files with 33 additions and 20 deletions
+5 -6
View File
@@ -1126,21 +1126,20 @@ function iniciarCondiciones(esquema) {
function evaluar() {
condicionales.forEach(sec => {
const { campo_id, valor } = sec.condicion;
// Leer valor del campo controlador
const { campo_id, valor, valores } = sec.condicion;
const valoresCond = valores ? valores : (valor ? [valor] : []);
const ctrl = document.querySelector(`[data-campo-id="${campo_id}"]`);
if (!ctrl) return;
let activo = false;
const checks = ctrl.querySelectorAll('input[type="checkbox"]');
if (checks.length) {
// checkbox múltiple: activo si alguno con ese valor está checked
checks.forEach(cb => { if (cb.value === valor && cb.checked) activo = true; });
checks.forEach(cb => { if (valoresCond.includes(cb.value) && cb.checked) activo = true; });
} else {
const radio = ctrl.querySelector(`input[type="radio"]:checked`);
const sel = ctrl.querySelector('select');
if (radio) activo = radio.value === valor;
else if (sel) activo = sel.value === valor;
const v = radio ? radio.value : (sel ? sel.value : '');
activo = valoresCond.includes(v);
}
// Mostrar u ocultar toda la sección
+28 -14
View File
@@ -708,7 +708,7 @@ function renderPreview() {
function renderCampoPreview(c) {
if (c.tipo === 'separador') {
const condBadge = c.condicion
? `<span title="Visible solo si: ${esc(c.condicion.valor)}" style="font-size:9px;background:#fff3cd;color:#856404;border-radius:4px;padding:1px 5px;margin-left:6px;vertical-align:middle"><i class="fas fa-eye-slash"></i> si: ${esc(c.condicion.valor)}</span>`
? (()=>{ const vs=(c.condicion.valores||[c.condicion.valor]).join(', '); return `<span title="Visible si: ${esc(vs)}" style="font-size:9px;background:#fff3cd;color:#856404;border-radius:4px;padding:1px 5px;margin-left:6px;vertical-align:middle"><i class="fas fa-eye-slash"></i> si: ${esc(vs)}</span>`; })()
: '';
return `<hr class="preview-sep"><div class="preview-sep-label">${esc(c.label)}${condBadge}</div>`;
}
@@ -786,30 +786,33 @@ function abrirEditorCampo(idx) {
const camposOpciones = _campos.filter(f =>
['checkbox','radio','select'].includes(f.tipo) && f.id !== c.id && (f.options||[]).length
);
const condCampoId = c.condicion?.campo_id || '';
const condValor = c.condicion?.valor || '';
const campoCtrl = camposOpciones.find(f => f.id === condCampoId);
const optsCtrl = campoCtrl ? (campoCtrl.options||[]) : [];
const condCampoId = c.condicion?.campo_id || '';
// soporta tanto valor (string) como valores (array)
const condValores = c.condicion?.valores || (c.condicion?.valor ? [c.condicion.valor] : []);
const campoCtrl = camposOpciones.find(f => f.id === condCampoId);
const optsCtrl = campoCtrl ? (campoCtrl.options||[]) : [];
const optsCampos = camposOpciones.map(f =>
`<option value="${esc(f.id)}" ${f.id===condCampoId?'selected':''}>${esc(f.label)}</option>`
).join('');
const optsValores = optsCtrl.map(o =>
`<option value="${esc(o)}" ${o===condValor?'selected':''}>${esc(o)}</option>`
`<option value="${esc(o)}" ${condValores.includes(o)?'selected':''}>${esc(o)}</option>`
).join('');
html += `<hr class="my-2">
<div class="mb-2">
<label class="form-label small fw-semibold"><i class="fas fa-eye-slash me-1 text-warning"></i>Visibilidad condicional</label>
<div class="form-text mb-2">Esta sección (y los campos hasta el siguiente separador) se muestra solo si se cumple la condición.</div>
<div class="form-text mb-2">Se muestra si alguno de los valores seleccionados está marcado en el campo elegido. Ctrl+clic para selección múltiple.</div>
<select class="form-select form-select-sm mb-2" id="ce-cond-campo" onchange="actualizarOpcionesCondicion(this.value)">
<option value="">— Siempre visible —</option>
${optsCampos}
</select>
<select class="form-select form-select-sm" id="ce-cond-valor" ${condCampoId?'':'disabled'}>
<option value="">— Selecciona campo primero —</option>
<select class="form-select form-select-sm" id="ce-cond-valor" multiple size="5"
style="${condCampoId?'':'opacity:.4;pointer-events:none'}"
${condCampoId?'':'disabled'}>
${optsValores}
</select>
<div class="form-text">Ctrl+clic para marcar varios valores.</div>
</div>`;
} else if (c.tipo === 'linked') {
html += `<div class="alert alert-info small py-2 mb-0">
@@ -913,12 +916,19 @@ function abrirEditorCampo(idx) {
function actualizarOpcionesCondicion(campoId) {
const selValor = document.getElementById('ce-cond-valor');
if (!selValor) return;
if (!campoId) { selValor.innerHTML = '<option value="">— Selecciona campo primero —</option>'; selValor.disabled = true; return; }
if (!campoId) {
selValor.innerHTML = '';
selValor.disabled = true;
selValor.style.opacity = '.4';
selValor.style.pointerEvents = 'none';
return;
}
const campo = _campos.find(f => f.id === campoId);
const opts = campo?.options || [];
selValor.innerHTML = '<option value="">— Selecciona valor —</option>' +
opts.map(o => `<option value="${esc(o)}">${esc(o)}</option>`).join('');
selValor.innerHTML = opts.map(o => `<option value="${esc(o)}">${esc(o)}</option>`).join('');
selValor.disabled = false;
selValor.style.opacity = '';
selValor.style.pointerEvents = '';
}
function _insertarKey(key) {
@@ -967,8 +977,12 @@ function aplicarCampo() {
const condCampo = document.getElementById('ce-cond-campo');
const condValor = document.getElementById('ce-cond-valor');
if (condCampo !== null) {
if (condCampo.value && condValor.value) {
c.condicion = { campo_id: condCampo.value, valor: condValor.value };
const selVals = condValor
? [...condValor.selectedOptions].map(o => o.value).filter(Boolean)
: [];
if (condCampo.value && selVals.length) {
// ponytail: valores[] siempre, forma unificada
c.condicion = { campo_id: condCampo.value, valores: selVals };
} else {
delete c.condicion;
}