fix: multiple consistency bugs found in full audit
conversationFlow saveConfig():
- Was not serializing buttons[] array for button-type menus (would erase
all buttons on save). Now sends menu_btn_id/menu_btn_title like botConfig.
- Defaulted menu type to 'list' instead of 'button'.
conversationFlow addNew('menu') modal:
- Defaulted to 'list' with header/footer/button fields.
- Now defaults to 'button' with inline buttons editor; toggle via type select.
- saveNewMenu() creates correct {type,body,buttons} or {type,...,sections}.
conversationFlow orphan-flow detection:
- Only checked sections[].rows, so flows linked via button menus appeared
as orphaned. Now checks buttons[] array for button-type menus.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
0be3d467ff
commit
0555b35766
@@ -3337,11 +3337,28 @@ function addNew(type) {
|
||||
} else if (type === 'menu') {
|
||||
showModal(\`
|
||||
<h2>➕ Nuevo Menú</h2>
|
||||
<div class="form-group"><label>ID del menú</label><input type="text" id="newMenuKey" placeholder="ej: show_main, sub_servicios"></div>
|
||||
<div class="form-group"><label>Header</label><input type="text" id="newMenuHeader" placeholder="Título del menú"></div>
|
||||
<div class="form-group"><label>Body</label><input type="text" id="newMenuBody" placeholder="Cuerpo del mensaje"></div>
|
||||
<div class="form-group"><label>Footer</label><input type="text" id="newMenuFooter" placeholder="Footer opcional"></div>
|
||||
<div class="form-group"><label>Texto del botón</label><input type="text" id="newMenuButton" value="Menú" placeholder="Texto del botón"></div>
|
||||
<div class="form-row" style="margin-bottom:10px">
|
||||
<div class="form-group" style="min-width:140px"><label>Tipo</label>
|
||||
<select id="newMenuType" onchange="newMenuTypeToggle()">
|
||||
<option value="button" selected>Botones directos</option>
|
||||
<option value="list">Lista (con modal)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="flex:2"><label>ID del menú</label><input type="text" id="newMenuKey" placeholder="ej: show_main, sub_servicios"></div>
|
||||
<div class="form-group" style="flex:3"><label>Texto del mensaje</label><input type="text" id="newMenuBody" placeholder="Selecciona una opción:"></div>
|
||||
</div>
|
||||
<div id="newMenuBtnArea">
|
||||
<div style="font-size:12px;font-weight:600;color:#3d4552;margin-bottom:6px">Botones (máx 3):</div>
|
||||
<div id="newMenuBtnRows"></div>
|
||||
<button class="btn-sec" style="font-size:11px;padding:4px 10px;margin-top:4px" onclick="addNewMenuBtnRow()">+ Botón</button>
|
||||
</div>
|
||||
<div id="newMenuListArea" style="display:none">
|
||||
<div class="form-row" style="margin-bottom:8px">
|
||||
<div class="form-group"><label>Header</label><input type="text" id="newMenuHeader" placeholder="Título"></div>
|
||||
<div class="form-group"><label>Footer</label><input type="text" id="newMenuFooter" placeholder="Footer opcional"></div>
|
||||
<div class="form-group"><label>Botón abrir</label><input type="text" id="newMenuButton" value="Ver opciones"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn-cancel" onclick="hideModal()">Cancelar</button>
|
||||
<button class="btn-save" onclick="saveNewMenu()">Crear</button>
|
||||
@@ -3387,18 +3404,48 @@ function saveNewCommand() {
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
function newMenuTypeToggle() {
|
||||
const isBtn = document.getElementById('newMenuType').value === 'button';
|
||||
document.getElementById('newMenuBtnArea').style.display = isBtn ? '' : 'none';
|
||||
document.getElementById('newMenuListArea').style.display = isBtn ? 'none' : '';
|
||||
}
|
||||
|
||||
function addNewMenuBtnRow() {
|
||||
const cont = document.getElementById('newMenuBtnRows');
|
||||
const bi = cont.querySelectorAll('.new-btn-row').length;
|
||||
const div = document.createElement('div');
|
||||
div.className = 'new-btn-row';
|
||||
div.style.cssText = 'display:flex;gap:6px;margin-bottom:4px';
|
||||
div.innerHTML = \`<input type="text" class="nbtn-id-\${bi}" placeholder="ID (ej: ver_info)" style="flex:1;padding:5px 8px;border:1px solid #e2e5ea;border-radius:6px;font-size:12px;font-family:monospace">
|
||||
<input type="text" class="nbtn-title-\${bi}" placeholder="Título (máx 20 chars)" style="flex:2;padding:5px 8px;border:1px solid #e2e5ea;border-radius:6px;font-size:12px">
|
||||
<button class="btn-cancel" style="padding:4px 8px" onclick="this.closest('.new-btn-row').remove()">✕</button>\`;
|
||||
cont.appendChild(div);
|
||||
}
|
||||
|
||||
function saveNewMenu() {
|
||||
const key = document.getElementById('newMenuKey').value.trim();
|
||||
if (!key) { alert('El ID del menú es requerido'); return; }
|
||||
if (CONFIG.menus[key]) { alert('El menú "' + key + '" ya existe'); return; }
|
||||
CONFIG.menus[key] = {
|
||||
type: 'list',
|
||||
header: document.getElementById('newMenuHeader').value.trim(),
|
||||
body: document.getElementById('newMenuBody').value.trim(),
|
||||
footer: document.getElementById('newMenuFooter').value.trim(),
|
||||
button: document.getElementById('newMenuButton').value.trim() || 'Menú',
|
||||
sections: []
|
||||
};
|
||||
const menuType = document.getElementById('newMenuType').value;
|
||||
const body = document.getElementById('newMenuBody').value.trim();
|
||||
if (menuType === 'button') {
|
||||
const buttons = [];
|
||||
document.getElementById('newMenuBtnRows').querySelectorAll('.new-btn-row').forEach((row, bi) => {
|
||||
const id = (row.querySelector('.nbtn-id-' + bi) || row.querySelector('input:first-child'))?.value?.trim();
|
||||
const title = (row.querySelector('.nbtn-title-' + bi) || row.querySelector('input:nth-child(2)'))?.value?.trim();
|
||||
if (id && title) buttons.push({ id, title: title.substring(0, 20) });
|
||||
});
|
||||
CONFIG.menus[key] = { type: 'button', body, buttons };
|
||||
} else {
|
||||
CONFIG.menus[key] = {
|
||||
type: 'list',
|
||||
header: document.getElementById('newMenuHeader')?.value?.trim() || '',
|
||||
body,
|
||||
footer: document.getElementById('newMenuFooter')?.value?.trim() || '',
|
||||
button: document.getElementById('newMenuButton')?.value?.trim() || 'Ver opciones',
|
||||
sections: []
|
||||
};
|
||||
}
|
||||
hideModal();
|
||||
saveConfig();
|
||||
}
|
||||
@@ -3442,20 +3489,28 @@ function saveConfig() {
|
||||
// Menus
|
||||
let mi = 0;
|
||||
Object.entries(fullConfig.menus || {}).forEach(([mk, menu]) => {
|
||||
const mtype = menu.type || 'button';
|
||||
fd.append('menu_key[]', mk);
|
||||
fd.append('menu_type[]', menu.type || 'list');
|
||||
fd.append('menu_header[]', menu.header || '');
|
||||
fd.append('menu_type[]', mtype);
|
||||
fd.append('menu_body[]', menu.body || '');
|
||||
fd.append('menu_footer[]', menu.footer || '');
|
||||
fd.append('menu_button[]', menu.button || 'Menú');
|
||||
(menu.sections || []).forEach((section, si) => {
|
||||
fd.append('menu_sections[' + mi + '][' + si + '][title]', section.title);
|
||||
(section.rows || []).forEach((row, ri) => {
|
||||
fd.append('menu_sections[' + mi + '][' + si + '][rows][' + ri + '][id]', row.id);
|
||||
fd.append('menu_sections[' + mi + '][' + si + '][rows][' + ri + '][title]', row.title);
|
||||
fd.append('menu_sections[' + mi + '][' + si + '][rows][' + ri + '][description]', row.description || '');
|
||||
if (mtype === 'button') {
|
||||
(menu.buttons || []).forEach((btn, bi) => {
|
||||
fd.append('menu_btn_id[' + mi + '][]', btn.id || '');
|
||||
fd.append('menu_btn_title[' + mi + '][]', btn.title || '');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
fd.append('menu_header[]', menu.header || '');
|
||||
fd.append('menu_footer[]', menu.footer || '');
|
||||
fd.append('menu_button[]', menu.button || 'Ver opciones');
|
||||
(menu.sections || []).forEach((section, si) => {
|
||||
fd.append('menu_sections[' + mi + '][' + si + '][title]', section.title);
|
||||
(section.rows || []).forEach((row, ri) => {
|
||||
fd.append('menu_sections[' + mi + '][' + si + '][rows][' + ri + '][id]', row.id);
|
||||
fd.append('menu_sections[' + mi + '][' + si + '][rows][' + ri + '][title]', row.title);
|
||||
fd.append('menu_sections[' + mi + '][' + si + '][rows][' + ri + '][description]', row.description || '');
|
||||
});
|
||||
});
|
||||
}
|
||||
mi++;
|
||||
});
|
||||
|
||||
@@ -3600,13 +3655,17 @@ HTML;
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
// ── Render orphan flows (not linked by any menu row) ──
|
||||
// ── Render orphan flows (not linked by any menu item) ──
|
||||
$linkedFlows = [];
|
||||
foreach ($menus as $mk => $menu) {
|
||||
foreach ($menu['sections'] ?? [] as $section) {
|
||||
foreach ($section['rows'] ?? [] as $row) {
|
||||
if (isset($flows[$row['id']])) {
|
||||
$linkedFlows[$row['id']] = true;
|
||||
if (($menu['type'] ?? 'button') === 'button') {
|
||||
foreach ($menu['buttons'] ?? [] as $btn) {
|
||||
if (isset($flows[$btn['id']])) $linkedFlows[$btn['id']] = true;
|
||||
}
|
||||
} else {
|
||||
foreach ($menu['sections'] ?? [] as $section) {
|
||||
foreach ($section['rows'] ?? [] as $row) {
|
||||
if (isset($flows[$row['id']])) $linkedFlows[$row['id']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user