Tomas: añadir tipo de examen inline sin página separada

- Botón "Añadir tipo de examen…" debajo del selector _c8j2g16
- Al escribir y guardar, añade la opción vía API y la selecciona al instante
- api/lab/add_exam_option.php: guarda la nueva opción en el esquema del formulario
- Migración actualizada: solo elimina condiciones de separadores (no toca el selector)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-10 11:05:45 -05:00
co-authored by Claude Sonnet 4.6
parent 4e6da5d7eb
commit be55a087aa
3 changed files with 140 additions and 10 deletions
+2 -10
View File
@@ -22,14 +22,6 @@ 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'] ?? '';
@@ -49,8 +41,8 @@ $pdo->prepare("UPDATE lab_formularios SET esquema = ? WHERE id = 15")
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>
• Condiciones eliminadas de separadores de tomas<br>
• Campo _c8j2g16 conservado como selector (+ botón "Añadir tipo")<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>';
+42
View File
@@ -0,0 +1,42 @@
<?php
/**
* POST /api/lab/add_exam_option.php
* Añade una opción al campo selector de tipo de examen (_c8j2g16) del formulario id=15.
* Body JSON: { exam_name: string }
*/
require_once __DIR__ . '/_helpers.php';
requireMethod('POST');
requireAdmin();
$body = inputJson();
$examName = trim($body['exam_name'] ?? '');
if (!$examName) jsonError('exam_name requerido.');
if (strlen($examName) > 80) jsonError('Nombre demasiado largo (máx 80 chars).');
if (!preg_match('/\S/', $examName)) jsonError('Nombre inválido.');
$db = Database::getInstance();
$row = $db->fetch("SELECT esquema FROM lab_formularios WHERE id = 15 LIMIT 1");
if (!$row) jsonError('Formulario no encontrado.', 404);
$esquema = json_decode($row['esquema'], true);
if (!is_array($esquema)) jsonError('Esquema inválido.', 500);
$updated = false;
foreach ($esquema as &$campo) {
if (($campo['id'] ?? '') !== '_c8j2g16') continue;
$opts = $campo['options'] ?? [];
if (in_array($examName, $opts, true)) jsonError("El tipo \"$examName\" ya existe.");
$opts[] = $examName;
$campo['options'] = $opts;
$updated = true;
break;
}
unset($campo);
if (!$updated) jsonError('Campo selector de examen no encontrado en el formulario.', 500);
$db->getConnection()->prepare("UPDATE lab_formularios SET esquema = ? WHERE id = 15")
->execute([json_encode($esquema, JSON_UNESCAPED_UNICODE)]);
jsonOk(['exam_name' => $examName], "Tipo \"$examName\" añadido.");
+96
View File
@@ -994,6 +994,28 @@ function esc2(mixed $v): string {
<label class="form-check-label"><?= esc2($opt) ?></label>
</div>
<?php endforeach; ?>
<?php if ($cid === '_c8j2g16'): ?>
<div class="mt-2" id="add-exam-wrap-<?= esc2($cid) ?>">
<button type="button" class="btn btn-link btn-sm p-0 text-muted"
onclick="toggleAddExam('<?= esc2($cid) ?>')">
<i class="fas fa-plus-circle me-1"></i>Añadir tipo de examen…
</button>
<div id="add-exam-box-<?= esc2($cid) ?>" class="d-none mt-1 d-flex gap-2">
<input type="text" id="add-exam-input-<?= esc2($cid) ?>"
class="form-control form-control-sm" style="max-width:260px"
placeholder="Nombre del tipo de examen">
<button type="button" class="btn btn-success btn-sm"
onclick="guardarNuevoExamen('<?= esc2($cid) ?>')">
<i class="fas fa-check me-1"></i>Guardar
</button>
<button type="button" class="btn btn-outline-secondary btn-sm"
onclick="toggleAddExam('<?= esc2($cid) ?>')">
<i class="fas fa-times"></i>
</button>
</div>
<div id="add-exam-msg-<?= esc2($cid) ?>" class="small mt-1"></div>
</div>
<?php endif; ?>
</div>
<?php continue; endif;
if ($tipo === 'checkbox' || $tipo === 'lista_marcable'):
@@ -1013,6 +1035,28 @@ function esc2(mixed $v): string {
</div>
<?php endforeach; ?>
</div>
<?php if ($cid === '_c8j2g16'): ?>
<div class="mt-2" id="add-exam-wrap-<?= esc2($cid) ?>">
<button type="button" class="btn btn-link btn-sm p-0 text-muted"
onclick="toggleAddExam('<?= esc2($cid) ?>')">
<i class="fas fa-plus-circle me-1"></i>Añadir tipo de examen…
</button>
<div id="add-exam-box-<?= esc2($cid) ?>" class="d-none mt-1 d-flex gap-2">
<input type="text" id="add-exam-input-<?= esc2($cid) ?>"
class="form-control form-control-sm" style="max-width:260px"
placeholder="Nombre del tipo de examen">
<button type="button" class="btn btn-success btn-sm"
onclick="guardarNuevoExamen('<?= esc2($cid) ?>')">
<i class="fas fa-check me-1"></i>Guardar
</button>
<button type="button" class="btn btn-outline-secondary btn-sm"
onclick="toggleAddExam('<?= esc2($cid) ?>')">
<i class="fas fa-times"></i>
</button>
</div>
<div id="add-exam-msg-<?= esc2($cid) ?>" class="small mt-1"></div>
</div>
<?php endif; ?>
</div>
<?php continue; endif;
if ($tipo === 'select'):
@@ -1283,6 +1327,58 @@ document.addEventListener('DOMContentLoaded', function() {
})();
<?php endif; ?>
<?php endif; ?>
/* ── Añadir tipo de examen inline ── */
function toggleAddExam(cid) {
var box = document.getElementById('add-exam-box-' + cid);
if (!box) return;
box.classList.toggle('d-none');
if (!box.classList.contains('d-none')) {
var inp = document.getElementById('add-exam-input-' + cid);
if (inp) inp.focus();
}
}
function guardarNuevoExamen(cid) {
var inp = document.getElementById('add-exam-input-' + cid);
var msg = document.getElementById('add-exam-msg-' + cid);
if (!inp) return;
var name = inp.value.trim();
if (!name) { if (msg) msg.innerHTML = '<span class="text-danger">Ingrese un nombre.</span>'; return; }
var base = window.location.href.split('/ver_formulario_enviado.php')[0];
fetch(base + '/api/lab/add_exam_option.php', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({exam_name: name})
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.ok) {
// Añadir radio button al grupo y seleccionarlo
var wrap = document.querySelector('[data-campo-id="' + cid + '"]');
if (wrap) {
var div = document.createElement('div');
div.className = 'form-check';
div.innerHTML = '<input class="form-check-input" type="radio" name="' + cid + '" value="' + name.replace(/"/g, '&quot;') + '" checked>'
+ '<label class="form-check-label">' + name.replace(/</g, '&lt;') + '</label>';
// Insertar antes del bloque add-exam-wrap
var addWrap = document.getElementById('add-exam-wrap-' + cid);
wrap.insertBefore(div, addWrap);
// Disparar evento change para condiciones de visibilidad
div.querySelector('input').dispatchEvent(new Event('change', {bubbles: true}));
}
inp.value = '';
if (msg) msg.innerHTML = '<span class="text-success"><i class="fas fa-check me-1"></i>Añadido.</span>';
setTimeout(function() {
toggleAddExam(cid);
if (msg) msg.innerHTML = '';
}, 1500);
} else {
if (msg) msg.innerHTML = '<span class="text-danger">' + (d.error || 'Error') + '</span>';
}
})
.catch(function() {
if (msg) msg.innerHTML = '<span class="text-danger">Error de conexión.</span>';
});
}
window._muestrasMap = <?= json_encode($_mpMap, JSON_UNESCAPED_UNICODE) ?>;
window._mpSavedFirmas = {};