feat: convert all WhatsApp menus from list to button type

- NormalBot: fix type check 'buttons' -> 'button' in buildMenuResponse
- DashboardController: botConfig editor now shows buttons editor for button-type menus,
  list fields (header/footer/button trigger) hidden for button type; menuTypeChange/addBtnRow
  JS functions toggle the UI; addMenu() defaults to button type
- DashboardController: renderMenuNode and renderFlowNode now read buttons[] array for
  button-type menus instead of only sections[].rows
- DashboardController: editMenu modal shows buttons editor or sections editor based on type;
  editMenuTypeToggle/addEditBtnRow; saveMenu reads buttons for button type
- public/index.php: botConfigSave handler parses menu_btn_id/menu_btn_title for button menus;
  saves compact {type,body,buttons} structure; list menus still save {type,header,...,sections}
- DB: migrated all list menus to button type for both companies (show_main_menu,
  submenu_informes, submenu_ciclos, show_menu_cat1, show_menu_cat2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-27 19:27:37 -05:00
co-authored by Claude Sonnet 4.6
parent e4841e2741
commit 639c3e7938
3 changed files with 286 additions and 145 deletions
+208 -84
View File
@@ -2353,24 +2353,51 @@ HTML;
HTML; HTML;
$menuIdx = 0; $menuIdx = 0;
foreach ($menus as $mk => $menu) { foreach ($menus as $mk => $menu) {
$mt = $menu['type'] ?? 'list'; $mt = $menu['type'] ?? 'button';
$mh = self::h($menu['header'] ?? '');
$mb = self::h($menu['body'] ?? ''); $mb = self::h($menu['body'] ?? '');
$mh = self::h($menu['header'] ?? '');
$mf = self::h($menu['footer'] ?? ''); $mf = self::h($menu['footer'] ?? '');
$mbtn = self::h($menu['button'] ?? ''); $mbtn = self::h($menu['button'] ?? '');
$isBtn = $mt === 'button';
$listStyle = $isBtn ? 'display:none' : '';
$btnStyle = $isBtn ? '' : 'display:none';
echo "<div class=\"item-card\" id=\"menu-card-{$menuIdx}\"> echo "<div class=\"item-card\" id=\"menu-card-{$menuIdx}\">
<div class=\"item-head\"> <div class=\"item-head\">
<span>Menú: <input type=\"text\" name=\"menu_key[]\" value=\"" . self::h($mk) . "\" style=\"border:none;background:transparent;font-weight:700;color:#0b3d91;width:200px;font-size:13px\" placeholder=\"ID del menú (ej: show_main)\"></span> <span>Menú: <input type=\"text\" name=\"menu_key[]\" value=\"" . self::h($mk) . "\" style=\"border:none;background:transparent;font-weight:700;color:#0b3d91;width:200px;font-size:13px\" placeholder=\"ID del menú\"></span>
<button type=\"button\" class=\"del\" onclick=\"this.closest('.item-card').remove()\">✕ Eliminar menú</button> <button type=\"button\" class=\"del\" onclick=\"this.closest('.item-card').remove()\">✕ Eliminar</button>
</div> </div>
<div class=\"form-row\"> <div class=\"form-row\">
<div class=\"form-group\"><label>Tipo</label><select name=\"menu_type[]\"><option value=\"list\"" . ($mt==='list'?' selected':'') . ">Lista</option><option value=\"button\"" . ($mt==='button'?' selected':'') . ">Botones</option></select></div> <div class=\"form-group\" style=\"min-width:110px\"><label>Tipo</label>
<div class=\"form-group\"><label>Header</label><input type=\"text\" name=\"menu_header[]\" value=\"{$mh}\" placeholder=\"Título del menú\"></div> <select name=\"menu_type[]\" onchange=\"menuTypeChange(this,{$menuIdx})\">
<div class=\"form-group\"><label>Body</label><input type=\"text\" name=\"menu_body[]\" value=\"{$mb}\" placeholder=\"Cuerpo del mensaje\"></div> <option value=\"button\"" . ($isBtn ? ' selected' : '') . ">Botones directos</option>
<div class=\"form-group\"><label>Footer</label><input type=\"text\" name=\"menu_footer[]\" value=\"{$mf}\" placeholder=\"Footer (opcional)\"></div> <option value=\"list\"" . (!$isBtn ? ' selected' : '') . ">Lista (con modal)</option>
<div class=\"form-group\"><label>Botón</label><input type=\"text\" name=\"menu_button[]\" value=\"{$mbtn}\" placeholder=\"Texto del botón\"></div> </select>
</div> </div>
<div style=\"margin-top:8px\"> <div class=\"form-group\" style=\"flex:2\"><label>Texto del mensaje</label>
<input type=\"text\" name=\"menu_body[]\" value=\"{$mb}\" placeholder=\"Selecciona una opción:\">
</div>
<div class=\"form-group list-fields-{$menuIdx}\" style=\"{$listStyle}\"><label>Header</label><input type=\"text\" name=\"menu_header[]\" value=\"{$mh}\" placeholder=\"Título\"></div>
<div class=\"form-group list-fields-{$menuIdx}\" style=\"{$listStyle}\"><label>Footer</label><input type=\"text\" name=\"menu_footer[]\" value=\"{$mf}\" placeholder=\"Footer\"></div>
<div class=\"form-group list-fields-{$menuIdx}\" style=\"{$listStyle}\"><label>Botón abrir</label><input type=\"text\" name=\"menu_button[]\" value=\"{$mbtn}\" placeholder=\"Ver opciones\"></div>
</div>
<div id=\"btn-area-{$menuIdx}\" style=\"{$btnStyle}margin-top:8px\">
<div style=\"font-size:12px;font-weight:600;color:#3d4552;margin-bottom:6px\">Botones (máx 3):</div>
<div id=\"btn-rows-{$menuIdx}\">";
foreach ($menu['buttons'] ?? [] as $bi => $btn) {
$bid = self::h($btn['id'] ?? '');
$btt = self::h($btn['title'] ?? '');
echo "<div class=\"inline-group\" style=\"margin-bottom:4px\">
<input type=\"text\" name=\"menu_btn_id[{$menuIdx}][]\" value=\"{$bid}\" placeholder=\"ID (ej: ver_info)\" style=\"flex:1\">
<input type=\"text\" name=\"menu_btn_title[{$menuIdx}][]\" value=\"{$btt}\" placeholder=\"Título (máx 20 chars)\" style=\"flex:2\">
<button type=\"button\" class=\"btn-danger-sm\" onclick=\"this.closest('.inline-group').remove()\">✕</button>
</div>";
}
echo "</div>
<button type=\"button\" class=\"btn-secondary\" style=\"font-size:11px;padding:4px 10px;margin-top:4px\" onclick=\"addBtnRow({$menuIdx})\">+ Botón</button>
</div>
<div id=\"sections-area-{$menuIdx}\" style=\"{$listStyle}margin-top:8px\">
<div style=\"font-size:12px;font-weight:600;color:#3d4552;margin-bottom:6px\">Secciones:</div> <div style=\"font-size:12px;font-weight:600;color:#3d4552;margin-bottom:6px\">Secciones:</div>
<div class=\"sections-container\" id=\"menu-sections-{$menuIdx}\">"; <div class=\"sections-container\" id=\"menu-sections-{$menuIdx}\">";
$si = 0; $si = 0;
@@ -2381,22 +2408,19 @@ HTML;
<input type=\"text\" name=\"menu_sections[{$menuIdx}][{$si}][title]\" value=\"{$st}\" placeholder=\"Título de sección\" style=\"flex:1;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:13px\"> <input type=\"text\" name=\"menu_sections[{$menuIdx}][{$si}][title]\" value=\"{$st}\" placeholder=\"Título de sección\" style=\"flex:1;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:13px\">
<button type=\"button\" class=\"btn-danger-sm\" onclick=\"this.closest('.section-card').remove()\">✕</button> <button type=\"button\" class=\"btn-danger-sm\" onclick=\"this.closest('.section-card').remove()\">✕</button>
</div> </div>
<div style=\"font-size:11px;font-weight:600;color:#7a8291;margin-bottom:4px\">Filas (cada fila es una opción):</div>"; <div style=\"font-size:11px;font-weight:600;color:#7a8291;margin-bottom:4px\">Filas:</div>";
$ri = 0; $ri = 0;
foreach ($section['rows'] ?? [] as $row) { foreach ($section['rows'] ?? [] as $row) {
$rid = self::h($row['id'] ?? ''); $rid = self::h($row['id'] ?? ''); $rt = self::h($row['title'] ?? ''); $rd = self::h($row['description'] ?? '');
$rt = self::h($row['title'] ?? '');
$rd = self::h($row['description'] ?? '');
echo "<div class=\"inline-group\" style=\"margin-bottom:4px\"> echo "<div class=\"inline-group\" style=\"margin-bottom:4px\">
<input type=\"text\" name=\"menu_sections[{$menuIdx}][{$si}][rows][{$ri}][id]\" value=\"{$rid}\" placeholder=\"ID (ej: servicios)\" style=\"flex:1\"> <input type=\"text\" name=\"menu_sections[{$menuIdx}][{$si}][rows][{$ri}][id]\" value=\"{$rid}\" placeholder=\"ID\" style=\"flex:1\">
<input type=\"text\" name=\"menu_sections[{$menuIdx}][{$si}][rows][{$ri}][title]\" value=\"{$rt}\" placeholder=\"Título\" style=\"flex:1\"> <input type=\"text\" name=\"menu_sections[{$menuIdx}][{$si}][rows][{$ri}][title]\" value=\"{$rt}\" placeholder=\"Título\" style=\"flex:1\">
<input type=\"text\" name=\"menu_sections[{$menuIdx}][{$si}][rows][{$ri}][description]\" value=\"{$rd}\" placeholder=\"Descripción\" style=\"flex:1.5\"> <input type=\"text\" name=\"menu_sections[{$menuIdx}][{$si}][rows][{$ri}][description]\" value=\"{$rd}\" placeholder=\"Descripción\" style=\"flex:1.5\">
<button type=\"button\" class=\"btn-danger-sm\" onclick=\"this.closest('.inline-group').remove()\">✕</button> <button type=\"button\" class=\"btn-danger-sm\" onclick=\"this.closest('.inline-group').remove()\">✕</button>
</div>"; </div>";
$ri++; $ri++;
} }
echo "<button type=\"button\" class=\"btn-secondary\" style=\"font-size:11px;padding:4px 10px;margin-top:4px\" onclick=\"addRow(this,{$menuIdx},{$si})\">+ Fila</button> echo "<button type=\"button\" class=\"btn-secondary\" style=\"font-size:11px;padding:4px 10px;margin-top:4px\" onclick=\"addRow(this,{$menuIdx},{$si})\">+ Fila</button></div>";
</div>";
$si++; $si++;
} }
echo "</div> echo "</div>
@@ -2572,21 +2596,56 @@ function addCmd() {
} }
// Menus // Menus
function menuTypeChange(sel, mi) {
const isBtn = sel.value === 'button';
document.getElementById('btn-area-' + mi).style.display = isBtn ? '' : 'none';
document.getElementById('sections-area-' + mi).style.display = isBtn ? 'none' : '';
document.querySelectorAll('.list-fields-' + mi).forEach(el => el.style.display = isBtn ? 'none' : '');
}
function addBtnRow(mi) {
const container = document.getElementById('btn-rows-' + mi);
const div = document.createElement('div');
div.className = 'inline-group';
div.style.cssText = 'display:flex;gap:8px;align-items:center;margin-bottom:4px';
div.innerHTML =
'<input type="text" name="menu_btn_id[' + mi + '][]" placeholder="ID (ej: ver_info)" style="flex:1;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:12px">' +
'<input type="text" name="menu_btn_title[' + mi + '][]" placeholder="Título (máx 20 chars)" style="flex:2;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:12px">' +
'<button type="button" class="btn-danger-sm" onclick="this.closest(\'.inline-group\').remove()">✕</button>';
container.appendChild(div);
}
function addMenu() { function addMenu() {
const container = document.getElementById('menuContainer'); const container = document.getElementById('menuContainer');
const idx = container.children.length; const idx = container.children.length;
const div = document.createElement('div'); const div = document.createElement('div');
div.className = 'item-card'; div.className = 'item-card';
div.id = 'menu-card-' + idx; div.id = 'menu-card-' + idx;
div.innerHTML = '<div class="item-head"><span>Menú: <input type="text" name="menu_key[]" style="border:none;background:transparent;font-weight:700;color:#0b3d91;width:200px;font-size:13px" placeholder="ID del menú (ej: show_main)"></span><button type="button" class="del" onclick="this.closest(\'.item-card\').remove()">✕ Eliminar menú</button></div>' + div.innerHTML =
'<div class="item-head"><span>Menú: <input type="text" name="menu_key[]" style="border:none;background:transparent;font-weight:700;color:#0b3d91;width:200px;font-size:13px" placeholder="ID del menú"></span>' +
'<button type="button" class="del" onclick="this.closest(\'.item-card\').remove()">✕ Eliminar</button></div>' +
'<div class="form-row">' + '<div class="form-row">' +
'<div class="form-group"><label>Tipo</label><select name="menu_type[]"><option value="list">Lista</option><option value="button">Botones</option></select></div>' + '<div class="form-group" style="min-width:110px"><label>Tipo</label>' +
'<div class="form-group"><label>Header</label><input type="text" name="menu_header[]" placeholder="Título del menú"></div>' + '<select name="menu_type[]" onchange="menuTypeChange(this,' + idx + ')">' +
'<div class="form-group"><label>Body</label><input type="text" name="menu_body[]" placeholder="Cuerpo del mensaje"></div>' + '<option value="button" selected>Botones directos</option>' +
'<div class="form-group"><label>Footer</label><input type="text" name="menu_footer[]" placeholder="Footer (opcional)"></div>' + '<option value="list">Lista (con modal)</option>' +
'<div class="form-group"><label>Botón</label><input type="text" name="menu_button[]" placeholder="Texto del botón"></div>' + '</select></div>' +
'<div class="form-group" style="flex:2"><label>Texto del mensaje</label>' +
'<input type="text" name="menu_body[]" placeholder="Selecciona una opción:"></div>' +
'<div class="form-group list-fields-' + idx + '" style="display:none"><label>Header</label><input type="text" name="menu_header[]" placeholder="Título"></div>' +
'<div class="form-group list-fields-' + idx + '" style="display:none"><label>Footer</label><input type="text" name="menu_footer[]" placeholder="Footer"></div>' +
'<div class="form-group list-fields-' + idx + '" style="display:none"><label>Botón abrir</label><input type="text" name="menu_button[]" placeholder="Ver opciones"></div>' +
'</div>' + '</div>' +
'<div style="margin-top:8px"><div style="font-size:12px;font-weight:600;color:#3d4552;margin-bottom:6px">Secciones:</div><div class="sections-container" id="menu-sections-' + idx + '"></div><button type="button" class="btn-secondary" style="font-size:11px;padding:4px 10px;margin-top:6px" onclick="addSection(' + idx + ')">+ Sección</button></div>'; '<div id="btn-area-' + idx + '" style="margin-top:8px">' +
'<div style="font-size:12px;font-weight:600;color:#3d4552;margin-bottom:6px">Botones (máx 3):</div>' +
'<div id="btn-rows-' + idx + '"></div>' +
'<button type="button" class="btn-secondary" style="font-size:11px;padding:4px 10px;margin-top:4px" onclick="addBtnRow(' + idx + ')">+ Botón</button>' +
'</div>' +
'<div id="sections-area-' + idx + '" style="display:none;margin-top:8px">' +
'<div style="font-size:12px;font-weight:600;color:#3d4552;margin-bottom:6px">Secciones:</div>' +
'<div class="sections-container" id="menu-sections-' + idx + '"></div>' +
'<button type="button" class="btn-secondary" style="font-size:11px;padding:4px 10px;margin-top:6px" onclick="addSection(' + idx + ')">+ Sección</button>' +
'</div>';
container.appendChild(div); container.appendChild(div);
} }
@@ -2921,13 +2980,22 @@ function deleteCommand(key) {
function editMenu(key) { function editMenu(key) {
const menu = CONFIG.menus[key]; const menu = CONFIG.menus[key];
if (!menu) return; if (!menu) return;
const isBtn = (menu.type || 'button') === 'button';
const buttonsHtml = (menu.buttons || []).map((b, bi) => \`
<div style="display:flex;gap:6px;margin-bottom:4px" class="edit-btn-row">
<input type="text" class="ebtn-id-\${bi}" value="\${b.id}" 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="ebtn-title-\${bi}" value="\${b.title}" placeholder="Título (máx 20)" 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('.edit-btn-row').remove()">✕</button>
</div>\`).join('');
const sectionsHtml = (menu.sections || []).map((s, si) => \` const sectionsHtml = (menu.sections || []).map((s, si) => \`
<div style="background:#f9fafb;border-radius:8px;padding:10px;margin-bottom:8px"> <div style="background:#f9fafb;border-radius:8px;padding:10px;margin-bottom:8px">
<div style="display:flex;gap:6px;margin-bottom:6px"> <div style="display:flex;gap:6px;margin-bottom:6px">
<input type="text" class="sec-title-\${si}" value="\${s.title}" placeholder="Título sección" style="flex:1;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:12px"> <input type="text" class="sec-title-\${si}" value="\${s.title}" placeholder="Título sección" style="flex:1;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:12px">
<button class="btn-cancel" style="padding:4px 10px" onclick="this.parentElement.parentElement.remove()">✕</button> <button class="btn-cancel" style="padding:4px 10px" onclick="this.parentElement.parentElement.remove()">✕</button>
</div> </div>
<div style="font-size:11px;color:#6b7280;margin-bottom:4px">Filas (botones):</div> <div style="font-size:11px;color:#6b7280;margin-bottom:4px">Filas:</div>
\${(s.rows || []).map((r, ri) => \` \${(s.rows || []).map((r, ri) => \`
<div style="display:flex;gap:4px;margin-bottom:3px"> <div style="display:flex;gap:4px;margin-bottom:3px">
<input type="text" class="row-id-\${si}-\${ri}" value="\${r.id}" placeholder="ID" style="flex:1;padding:4px 8px;border:1px solid #e2e5ea;border-radius:4px;font-size:11px;font-family:monospace"> <input type="text" class="row-id-\${si}-\${ri}" value="\${r.id}" placeholder="ID" style="flex:1;padding:4px 8px;border:1px solid #e2e5ea;border-radius:4px;font-size:11px;font-family:monospace">
@@ -2936,16 +3004,37 @@ function editMenu(key) {
<button class="btn-cancel" style="padding:2px 6px;font-size:10px" onclick="this.parentElement.remove()">✕</button> <button class="btn-cancel" style="padding:2px 6px;font-size:10px" onclick="this.parentElement.remove()">✕</button>
</div>\`).join('')} </div>\`).join('')}
<button class="btn-sec" style="font-size:10px;padding:3px 8px;margin-top:4px" onclick="addRowInline(this, \${si})">+ Fila</button> <button class="btn-sec" style="font-size:10px;padding:3px 8px;margin-top:4px" onclick="addRowInline(this, \${si})">+ Fila</button>
</div>\`).join('')} </div>\`).join('');
showModal(\` showModal(\`
<h2>✏️ Editar Menú: \${key}</h2> <h2>✏️ Editar Menú: \${key}</h2>
<div class="form-group"><label>ID del menú</label><input type="text" id="editMenuKey" value="\${key}"></div> <div class="form-row" style="margin-bottom:10px">
<div class="form-group" style="min-width:140px"><label>Tipo</label>
<select id="editMenuType" onchange="editMenuTypeToggle()">
<option value="button"\${isBtn?' selected':''}>Botones directos</option>
<option value="list"\${!isBtn?' selected':''}>Lista (con modal)</option>
</select>
</div>
<div class="form-group" style="flex:2"><label>ID del menú</label><input type="text" id="editMenuKey" value="\${key}"></div>
<div class="form-group" style="flex:3"><label>Texto del mensaje</label><input type="text" id="editMenuBody" value="\${menu.body||''}"></div>
</div>
<div id="editListFields" style="\${isBtn?'display:none':''}">
<div class="form-row" style="margin-bottom:10px">
<div class="form-group"><label>Header</label><input type="text" id="editMenuHeader" value="\${menu.header||''}"></div> <div class="form-group"><label>Header</label><input type="text" id="editMenuHeader" value="\${menu.header||''}"></div>
<div class="form-group"><label>Body</label><input type="text" id="editMenuBody" value="\${menu.body||''}"></div>
<div class="form-group"><label>Footer</label><input type="text" id="editMenuFooter" value="\${menu.footer||''}"></div> <div class="form-group"><label>Footer</label><input type="text" id="editMenuFooter" value="\${menu.footer||''}"></div>
<div class="form-group"><label>Texto del botón</label><input type="text" id="editMenuButton" value="\${menu.button||'Menú'}"></div> <div class="form-group"><label>Botón abrir</label><input type="text" id="editMenuButton" value="\${menu.button||'Ver opciones'}"></div>
</div>
</div>
<div id="editBtnArea" style="\${isBtn?'':'display:none'}">
<div style="font-size:12px;font-weight:600;color:#3d4552;margin-bottom:6px">Botones (máx 3):</div>
<div id="editBtnRows">\${buttonsHtml}</div>
<button class="btn-sec" style="font-size:11px;padding:4px 10px;margin-top:4px" onclick="addEditBtnRow()">+ Botón</button>
</div>
<div id="editSectionsArea" style="\${isBtn?'display:none':''}">
<div style="font-size:12px;font-weight:600;color:#3d4552;margin:8px 0 6px">Secciones:</div>
<div id="sectionsEditContainer">\${sectionsHtml}</div> <div id="sectionsEditContainer">\${sectionsHtml}</div>
<button class="btn-sec" style="font-size:11px;padding:4px 10px;margin-top:6px" onclick="addSectionInline()">+ Sección</button> <button class="btn-sec" style="font-size:11px;padding:4px 10px;margin-top:6px" onclick="addSectionInline()">+ Sección</button>
</div>
<div class="btn-row"> <div class="btn-row">
<button class="btn-cancel" onclick="hideModal()">Cancelar</button> <button class="btn-cancel" onclick="hideModal()">Cancelar</button>
<button class="btn-del-modal" onclick="deleteMenu('\${key}')">Eliminar menú</button> <button class="btn-del-modal" onclick="deleteMenu('\${key}')">Eliminar menú</button>
@@ -2953,6 +3042,26 @@ function editMenu(key) {
</div>\`); </div>\`);
} }
function editMenuTypeToggle() {
const isBtn = document.getElementById('editMenuType').value === 'button';
document.getElementById('editBtnArea').style.display = isBtn ? '' : 'none';
document.getElementById('editSectionsArea').style.display = isBtn ? 'none' : '';
document.getElementById('editListFields').style.display = isBtn ? 'none' : '';
}
function addEditBtnRow() {
const cont = document.getElementById('editBtnRows');
const bi = cont.querySelectorAll('.edit-btn-row').length;
const div = document.createElement('div');
div.className = 'edit-btn-row';
div.style.cssText = 'display:flex;gap:6px;margin-bottom:4px';
div.innerHTML = \`
<input type="text" class="ebtn-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="ebtn-title-\${bi}" placeholder="Título (máx 20)" 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('.edit-btn-row').remove()">✕</button>\`;
cont.appendChild(div);
}
function addSectionInline() { function addSectionInline() {
const cont = document.getElementById('sectionsEditContainer'); const cont = document.getElementById('sectionsEditContainer');
const si = cont.children.length; const si = cont.children.length;
@@ -3000,14 +3109,25 @@ function createRowInput(si, ri) {
function saveMenu(oldKey) { function saveMenu(oldKey) {
const newKey = document.getElementById('editMenuKey').value.trim(); const newKey = document.getElementById('editMenuKey').value.trim();
if (!newKey) { alert('El ID del menú es requerido'); return; } if (!newKey) { alert('El ID del menú es requerido'); return; }
const menu = { const menuType = document.getElementById('editMenuType').value;
type: 'list', const body = document.getElementById('editMenuBody').value;
header: document.getElementById('editMenuHeader').value,
body: document.getElementById('editMenuBody').value, let menu;
footer: document.getElementById('editMenuFooter').value, if (menuType === 'button') {
button: document.getElementById('editMenuButton').value || 'Menú', const buttons = [];
sections: [] document.getElementById('editBtnRows').querySelectorAll('.edit-btn-row').forEach((row, bi) => {
}; const idEl = row.querySelector('.ebtn-id-' + bi) || row.querySelector('input[placeholder*="ID"]');
const titleEl = row.querySelector('.ebtn-title-' + bi) || row.querySelector('input[placeholder*="Título"]');
const id = idEl?.value?.trim();
const title = titleEl?.value?.trim();
if (id && title) buttons.push({ id, title: title.substring(0, 20) });
});
menu = { type: 'button', body, buttons };
} else {
const header = document.getElementById('editMenuHeader')?.value || '';
const footer = document.getElementById('editMenuFooter')?.value || '';
const button = document.getElementById('editMenuButton')?.value || 'Ver opciones';
menu = { type: 'list', header, body, footer, button, sections: [] };
const cont = document.getElementById('sectionsEditContainer'); const cont = document.getElementById('sectionsEditContainer');
const secDivs = cont.children; const secDivs = cont.children;
for (let si = 0; si < secDivs.length; si++) { for (let si = 0; si < secDivs.length; si++) {
@@ -3033,6 +3153,8 @@ function saveMenu(oldKey) {
}); });
if (rows.length > 0) menu.sections.push({ title, rows }); if (rows.length > 0) menu.sections.push({ title, rows });
} }
} // end else list
delete CONFIG.menus[oldKey]; delete CONFIG.menus[oldKey];
CONFIG.menus[newKey] = menu; CONFIG.menus[newKey] = menu;
// Update commands that reference this menu // Update commands that reference this menu
@@ -3466,65 +3588,69 @@ HTML;
private static function renderMenuNode(string $menuKey, array $menu, array $flows, array $allMenus, int $companyId): void private static function renderMenuNode(string $menuKey, array $menu, array $flows, array $allMenus, int $companyId): void
{ {
$typeLabel = ($menu['type'] ?? 'list') === 'list' ? 'Lista' : 'Botones'; $isButton = ($menu['type'] ?? 'list') === 'button';
$typeBadge = ($menu['type'] ?? 'list') === 'list' ? 'badge-list' : 'badge-button'; $typeLabel = $isButton ? 'Botones' : 'Lista';
$hasRows = false; $typeBadge = $isButton ? 'badge-button' : 'badge-list';
// Unifica items: botones directos o filas de secciones
$items = [];
if ($isButton) {
foreach ($menu['buttons'] ?? [] as $btn) {
$items[] = ['id' => $btn['id'], 'title' => $btn['title'], 'description' => ''];
}
} else {
foreach ($menu['sections'] ?? [] as $section) { foreach ($menu['sections'] ?? [] as $section) {
if (!empty($section['rows'])) { $hasRows = true; break; } foreach ($section['rows'] ?? [] as $row) {
$items[] = ['id' => $row['id'], 'title' => $row['title'], 'description' => $row['description'] ?? ''];
}
}
} }
echo '<div style="display:flex;flex-direction:column;align-items:center;width:100%">'; echo '<div style="display:flex;flex-direction:column;align-items:center;width:100%">';
echo '<div class="node-card menu-card" id="menu-node-' . self::h($menuKey) . '">'; echo '<div class="node-card menu-card" id="menu-node-' . self::h($menuKey) . '">';
echo '<div class="tag" style="color:#0ea5e9">📑 Menú <span class="badge ' . $typeBadge . '" style="margin-left:4px">' . $typeLabel . '</span></div>'; echo '<div class="tag" style="color:#0ea5e9">📑 Menú <span class="badge ' . $typeBadge . '" style="margin-left:4px">' . $typeLabel . '</span></div>';
echo '<div class="n-title" style="color:#0ea5e9">' . self::h($menuKey) . '</div>'; echo '<div class="n-title" style="color:#0ea5e9">' . self::h($menuKey) . '</div>';
if ($menu['header']) echo '<div class="n-info"><span>📌 ' . self::h($menu['header']) . '</span></div>'; if (!empty($menu['header'])) echo '<div class="n-info"><span>📌 ' . self::h($menu['header']) . '</span></div>';
if ($menu['body']) echo '<div class="n-sub">' . self::h($menu['body']) . '</div>'; if (!empty($menu['body'])) echo '<div class="n-sub">' . self::h($menu['body']) . '</div>';
echo '<div class="actions">'; echo '<div class="actions"><button class="edit-btn" onclick="editMenu(\'' . self::h($menuKey) . '\')">✎ Editar</button></div>';
echo '<button class="edit-btn" onclick="editMenu(\'' . self::h($menuKey) . '\')">✎ Editar</button>';
echo '</div>';
// Show rows/buttons if (!empty($items)) {
if ($hasRows) {
echo '<div class="row-list">'; echo '<div class="row-list">';
foreach ($menu['sections'] ?? [] as $section) { foreach ($items as $item) {
foreach ($section['rows'] ?? [] as $row) { $flow = $flows[$item['id']] ?? null;
$flow = $flows[$row['id']] ?? null; $flowLabel = $flow
$flowLabel = ''; ? match ($flow['type']) {
if ($flow) { 'text' => '📝 Respuesta',
if ($flow['type'] === 'text') $flowLabel = '📝 Respuesta'; 'function' => '⚡ ' . ($flow['function'] ?? ''),
elseif ($flow['type'] === 'function') $flowLabel = ' ' . ($flow['function'] ?? ''); 'menu' => '📑 → ' . ($flow['menu'] ?? ''),
elseif ($flow['type'] === 'menu') $flowLabel = '📑 → ' . ($flow['menu'] ?? ''); default => '?',
} else {
$flowLabel = '⚠️ Sin flujo';
} }
echo '<div class="row-item" data-flow-target="' . self::h($row['id']) . '" onmouseenter="highlightRow(this)" onmouseleave="unhighlightRow()" onclick="editFlow(\'' . self::h($row['id']) . '\')">'; : '⚠️ Sin flujo';
echo '<span class="r-icon">🔘</span>'; echo '<div class="row-item" data-flow-target="' . self::h($item['id']) . '" onmouseenter="highlightRow(this)" onmouseleave="unhighlightRow()" onclick="editFlow(\'' . self::h($item['id']) . '\')">';
echo '<span class="r-title">' . self::h($row['title']) . '</span>'; echo '<span class="r-icon">' . ($isButton ? '🔲' : '🔘') . '</span>';
if ($row['description']) echo '<span style="font-size:10px;color:#9ca3af;flex-shrink:0">' . self::h($row['description']) . '</span>'; echo '<span class="r-title">' . self::h($item['title']) . '</span>';
echo '<span class="r-id">' . self::h($row['id']) . '</span>'; if ($item['description']) echo '<span style="font-size:10px;color:#9ca3af;flex-shrink:0">' . self::h($item['description']) . '</span>';
echo '<span class="r-id">' . self::h($item['id']) . '</span>';
echo '<span class="r-arrow">→</span>'; echo '<span class="r-arrow">→</span>';
echo '<span class="r-target">' . $flowLabel . '</span>'; echo '<span class="r-target">' . $flowLabel . '</span>';
echo '</div>'; echo '</div>';
} }
}
echo '</div>'; echo '</div>';
} else { } else {
echo '<div style="font-size:11px;color:#9ca3af;margin-top:6px;font-style:italic">Sin botones aún</div>'; echo '<div style="font-size:11px;color:#9ca3af;margin-top:6px;font-style:italic">Sin botones aún</div>';
} }
echo '</div>'; // node-card echo '</div>'; // node-card
// ── Show first sub-menu connection inline ── // Mostrar primer submenú en árbol
foreach ($menu['sections'] ?? [] as $section) { foreach ($items as $item) {
foreach ($section['rows'] ?? [] as $row) { $flow = $flows[$item['id']] ?? null;
$flow = $flows[$row['id']] ?? null;
if ($flow && $flow['type'] === 'menu' && isset($flow['menu'])) { if ($flow && $flow['type'] === 'menu' && isset($flow['menu'])) {
echo '<div class="connector-v"></div>'; echo '<div class="connector-v"></div>';
echo '<div style="display:flex;flex-direction:column;align-items:center">'; echo '<div style="display:flex;flex-direction:column;align-items:center">';
echo '<div style="font-size:10px;color:#6366f1;font-weight:600;margin:2px 0">🔘 ' . self::h($row['title']) . ' →</div>'; echo '<div style="font-size:10px;color:#6366f1;font-weight:600;margin:2px 0">🔲 ' . self::h($item['title']) . ' →</div>';
self::renderFlowNode($row['id'], $flow, $allMenus, $companyId, true); self::renderFlowNode($item['id'], $flow, $allMenus, $companyId, true);
echo '</div>'; echo '</div>';
break 2; break;
}
} }
} }
@@ -3583,22 +3709,20 @@ HTML;
echo '<div class="tag" style="color:#10b981">📑 Sub-menú: ' . self::h($subKey) . '</div>'; echo '<div class="tag" style="color:#10b981">📑 Sub-menú: ' . self::h($subKey) . '</div>';
if ($subMenu['body']) echo '<div class="n-sub">' . self::h($subMenu['body']) . '</div>'; if ($subMenu['body']) echo '<div class="n-sub">' . self::h($subMenu['body']) . '</div>';
// Show sub-menu's rows // Mostrar items del sub-menú (buttons o sections[].rows)
$hasRows = false; $subIsBtn = ($subMenu['type'] ?? 'list') === 'button';
foreach ($subMenu['sections'] ?? [] as $section) { $subItems = $subIsBtn
if (!empty($section['rows'])) { $hasRows = true; break; } ? ($subMenu['buttons'] ?? [])
} : array_merge(...array_map(fn($s) => $s['rows'] ?? [], $subMenu['sections'] ?? []));
if ($hasRows) { if (!empty($subItems)) {
echo '<div class="row-list">'; echo '<div class="row-list">';
foreach ($subMenu['sections'] ?? [] as $section) { foreach ($subItems as $item) {
foreach ($section['rows'] ?? [] as $row) {
echo '<div class="row-item" style="cursor:default">'; echo '<div class="row-item" style="cursor:default">';
echo '<span class="r-icon">🔘</span>'; echo '<span class="r-icon">' . ($subIsBtn ? '🔲' : '🔘') . '</span>';
echo '<span class="r-title">' . self::h($row['title']) . '</span>'; echo '<span class="r-title">' . self::h($item['title']) . '</span>';
echo '<span class="r-id">' . self::h($row['id']) . '</span>'; echo '<span class="r-id">' . self::h($item['id']) . '</span>';
echo '</div>'; echo '</div>';
} }
}
echo '</div>'; echo '</div>';
} else { } else {
echo '<div style="font-size:11px;color:#9ca3af;margin-top:4px;font-style:italic">Sin botones</div>'; echo '<div style="font-size:11px;color:#9ca3af;margin-top:4px;font-style:italic">Sin botones</div>';
+24 -7
View File
@@ -326,18 +326,35 @@ $routes = [
// Menus // Menus
$menuKeys = $_POST['menu_key'] ?? []; $menuKeys = $_POST['menu_key'] ?? [];
$menuTypes = $_POST['menu_type'] ?? []; $menuTypes = $_POST['menu_type'] ?? [];
$menuHeaders = $_POST['menu_header'] ?? [];
$menuBodies = $_POST['menu_body'] ?? []; $menuBodies = $_POST['menu_body'] ?? [];
$menuHeaders = $_POST['menu_header'] ?? [];
$menuFooters = $_POST['menu_footer'] ?? []; $menuFooters = $_POST['menu_footer'] ?? [];
$menuButtons = $_POST['menu_button'] ?? []; $menuBtnTrigger = $_POST['menu_button'] ?? [];
$menuSections = $_POST['menu_sections'] ?? []; $menuSections = $_POST['menu_sections'] ?? [];
$menuBtnIds = $_POST['menu_btn_id'] ?? [];
$menuBtnTitles = $_POST['menu_btn_title'] ?? [];
$menus = []; $menus = [];
foreach ($menuKeys as $i => $mk) { foreach ($menuKeys as $i => $mk) {
$mk = trim($mk); $mk = trim($mk);
$type = $menuTypes[$i] ?? 'button';
if ($mk === '') continue; if ($mk === '') continue;
if ($type === 'button') {
$buttons = [];
foreach ($menuBtnIds[$i] ?? [] as $j => $btnId) {
$btnId = trim($btnId);
$btnTitle = mb_substr(trim($menuBtnTitles[$i][$j] ?? ''), 0, 20);
if ($btnId !== '' && $btnTitle !== '') {
$buttons[] = ['id' => $btnId, 'title' => $btnTitle];
}
}
$menus[$mk] = [
'type' => 'button',
'body' => trim($menuBodies[$i] ?? ''),
'buttons' => $buttons,
];
} else {
$sections = []; $sections = [];
if (!empty($menuSections[$i])) { foreach ($menuSections[$i] ?? [] as $si => $section) {
foreach ($menuSections[$i] as $si => $section) {
$title = trim($section['title'] ?? ''); $title = trim($section['title'] ?? '');
if ($title === '') continue; if ($title === '') continue;
$rows = []; $rows = [];
@@ -349,16 +366,16 @@ $routes = [
} }
$sections[] = ['title' => $title, 'rows' => $rows]; $sections[] = ['title' => $title, 'rows' => $rows];
} }
}
$menus[$mk] = [ $menus[$mk] = [
'type' => $menuTypes[$i] ?? 'list', 'type' => 'list',
'header' => trim($menuHeaders[$i] ?? ''), 'header' => trim($menuHeaders[$i] ?? ''),
'body' => trim($menuBodies[$i] ?? ''), 'body' => trim($menuBodies[$i] ?? ''),
'footer' => trim($menuFooters[$i] ?? ''), 'footer' => trim($menuFooters[$i] ?? ''),
'button' => trim($menuButtons[$i] ?? ''), 'button' => trim($menuBtnTrigger[$i] ?? ''),
'sections' => $sections, 'sections' => $sections,
]; ];
} }
}
if (!empty($menus)) $config['menus'] = $menus; if (!empty($menus)) $config['menus'] = $menus;
// Flows // Flows
+1 -1
View File
@@ -262,7 +262,7 @@ class NormalBot
return self::enqueueInteractive($to, $interactive, $company); return self::enqueueInteractive($to, $interactive, $company);
} }
if ($menuType === 'buttons') { if ($menuType === 'button') {
$buttons = []; $buttons = [];
foreach ($menu['buttons'] ?? [] as $btn) { foreach ($menu['buttons'] ?? [] as $btn) {
$buttons[] = [ $buttons[] = [