fix(admin): endpoint names in selects, per_type merge, category labels, cmd datalist

- Load name+direction in botConfig() endpoint query (was missing name column)
- Show friendly endpoint names in all flow select dropdowns (was showing raw key)
- Fix per_type save: merge with existing data instead of replacing (was wiping custom flows/commands per category)
- Fix category labels: Categoría 1 = Solo reporta, 2 = Solo recibe, 3 = Reporta y recibe
- Remove incorrect "menú debe ser de tipo botón" restriction text
- Add datalist to command action inputs so admin can pick from existing menu/flow IDs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-28 17:09:47 -05:00
co-authored by Claude Sonnet 4.6
parent 2c0ac04993
commit 1116b8ece9
2 changed files with 31 additions and 20 deletions
+23 -12
View File
@@ -2441,7 +2441,7 @@ HTML;
$parsed = json_decode($cj, true);
if (is_array($parsed)) $config = $parsed;
}
$epStmt = db()->prepare('SELECT endpoint_key, url, method FROM company_endpoints WHERE company_id=? AND is_active=1 ORDER BY endpoint_key');
$epStmt = db()->prepare('SELECT endpoint_key, name, url, method, direction FROM company_endpoints WHERE company_id=? AND is_active=1 ORDER BY endpoint_key');
$epStmt->execute([$companyId]);
$companyEndpoints = $epStmt->fetchAll(PDO::FETCH_ASSOC);
}
@@ -2519,7 +2519,7 @@ HTML;
// Per-category config HTML
$perTypeConfig = $config['per_type'] ?? [];
$menuKeysList = array_keys($menus);
$catLabels = ['1' => 'Categoría 1 — Solo recibe info', '2' => 'Categoría 2 — Solo descarga informes', '3' => 'Categoría 3 — Reporta y recibe'];
$catLabels = ['1' => 'Categoría 1 — Solo reporta', '2' => 'Categoría 2 — Solo recibe informes', '3' => 'Categoría 3 — Reporta y recibe'];
$categoryTabHtml = '';
foreach ([1, 2, 3] as $cat) {
$catCfg = $perTypeConfig[(string)$cat] ?? [];
@@ -2538,7 +2538,7 @@ HTML;
<select name="per_type_menu[{$cat}]" style="width:100%;padding:9px 12px;border:1px solid #e2e5ea;border-radius:8px;font-size:13px">
{$opts}
</select>
<div style="font-size:11px;color:#7a8291;margin-top:4px">Menú en botones que se muestra cuando el usuario escribe por primera vez o sin contexto activo</div>
<div style="font-size:11px;color:#7a8291;margin-top:4px">Menú que se muestra cuando el usuario escribe por primera vez o sin contexto activo</div>
</div>
</div>
CATHTML;
@@ -2563,16 +2563,22 @@ CATHTML;
<!-- ─── COMMANDS ─────────────────────────────────────────────────────── -->
<div class="tab-content active" id="tab-commands">
<div class="card-h">📋 Comandos</div>
<p style="font-size:12px;color:#7a8291;margin-bottom:14px">Palabras clave que el usuario escribe para ejecutar acciones. Ej: "menu", "info", "contacto"</p>
<p style="font-size:12px;color:#7a8291;margin-bottom:14px">Palabras clave que el usuario escribe para ejecutar acciones directamente. El valor de acción debe coincidir con un ID de menú o flujo.</p>
<datalist id="actionsDatalist">
HTML;
foreach (array_keys($menus) as $mk) echo "<option value=\"" . self::h($mk) . "\">📑 Menú</option>\n";
foreach (array_keys($config['flows'] ?? []) as $fk) echo "<option value=\"" . self::h($fk) . "\">🔀 Flujo</option>\n";
echo <<<HTML
</datalist>
<table class="simple" id="cmdTable">
<thead><tr><th>Palabra clave</th><th>Acción (menú o flujo)</th><th></th></tr></thead>
<thead><tr><th>Palabra clave</th><th>Acción (ID de menú o flujo)</th><th></th></tr></thead>
<tbody id="cmdBody">
HTML;
$cmdIdx = 0;
foreach ($commands as $keyword => $action) {
echo "<tr id=\"cmd-row-{$cmdIdx}\">
<td><input type=\"text\" name=\"cmd_keyword[]\" value=\"" . self::h($keyword) . "\" style=\"width:100%;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:13px\"></td>
<td><input type=\"text\" name=\"cmd_action[]\" value=\"" . self::h($action) . "\" style=\"width:100%;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:13px\" placeholder=\"ej: show_main, servicios...\"></td>
<td><input type=\"text\" name=\"cmd_action[]\" value=\"" . self::h($action) . "\" list=\"actionsDatalist\" style=\"width:100%;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:13px\" placeholder=\"ej: show_main, salir...\"></td>
<td><button type=\"button\" class=\"btn-danger-sm\" onclick=\"this.closest('tr').remove()\">✕</button></td>
</tr>";
$cmdIdx++;
@@ -2683,9 +2689,14 @@ HTML;
foreach (array_keys($menus) as $mk) {
$menuOptHtml .= '<option value="' . self::h($mk) . '">📑 ' . self::h($mk) . '</option>';
}
// Helper para etiqueta de endpoint: muestra nombre si existe, si no la key
$epLabel = fn(array $ep): string => self::h(
($ep['name'] !== '' && $ep['name'] !== null) ? $ep['name'] : $ep['endpoint_key']
);
$epOptHtml = '<option value="">— Selecciona endpoint —</option>';
foreach ($companyEndpoints as $ep) {
$epOptHtml .= '<option value="' . self::h($ep['endpoint_key']) . '">' . self::h($ep['endpoint_key']) . '</option>';
$epOptHtml .= '<option value="' . self::h($ep['endpoint_key']) . '">' . $epLabel($ep) . '</option>';
}
$flowKeys = array_keys($flows);
@@ -2721,19 +2732,19 @@ HTML;
$epSelHtml = '<option value="">— Selecciona endpoint —</option>';
foreach ($companyEndpoints as $ep) {
$sel = $epKey === self::h($ep['endpoint_key']) ? ' selected' : '';
$epSelHtml .= '<option value="' . self::h($ep['endpoint_key']) . '"' . $sel . '>' . self::h($ep['endpoint_key']) . '</option>';
$epSelHtml .= '<option value="' . self::h($ep['endpoint_key']) . '"' . $sel . '>' . $epLabel($ep) . '</option>';
}
// For dynamic_list source endpoint
$dlEpSelHtml = '<option value="">— Selecciona endpoint —</option>';
foreach ($companyEndpoints as $ep) {
$sel = $srcEp === self::h($ep['endpoint_key']) ? ' selected' : '';
$dlEpSelHtml .= '<option value="' . self::h($ep['endpoint_key']) . '"' . $sel . '>' . self::h($ep['endpoint_key']) . '</option>';
$dlEpSelHtml .= '<option value="' . self::h($ep['endpoint_key']) . '"' . $sel . '>' . $epLabel($ep) . '</option>';
}
// For submit_form endpoint
$sfEpSelHtml = '<option value="">— Selecciona endpoint —</option>';
foreach ($companyEndpoints as $ep) {
$sel = $epKey === self::h($ep['endpoint_key']) ? ' selected' : '';
$sfEpSelHtml .= '<option value="' . self::h($ep['endpoint_key']) . '"' . $sel . '>' . self::h($ep['endpoint_key']) . '</option>';
$sfEpSelHtml .= '<option value="' . self::h($ep['endpoint_key']) . '"' . $sel . '>' . $epLabel($ep) . '</option>';
}
// Next-node select
$nnSelHtml = '<option value="">— Ninguno —</option>';
@@ -2873,7 +2884,7 @@ HTML;
<!-- ─── POR CATEGORÍA ────────────────────────────────────────────────── -->
<div class="tab-content" id="tab-categories">
<div class="card-h">👥 Configuración por Categoría</div>
<p style="font-size:12px;color:#7a8291;margin-bottom:16px">Define qué menú de bienvenida ve cada categoría de usuario cuando escribe por primera vez o sin contexto activo. El menú debe ser de tipo <strong>botón</strong>.</p>
<p style="font-size:12px;color:#7a8291;margin-bottom:16px">Define qué menú de bienvenida ve cada categoría de usuario cuando escribe por primera vez o sin contexto activo.</p>
{$categoryTabHtml}
</div>
@@ -2992,7 +3003,7 @@ function addCmd() {
const tr = document.createElement('tr');
tr.id = 'cmd-row-' + idx;
tr.innerHTML = '<td><input type="text" name="cmd_keyword[]" style="width:100%;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:13px" placeholder="ej: menu"></td>' +
'<td><input type="text" name="cmd_action[]" style="width:100%;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:13px" placeholder="ej: show_main"></td>' +
'<td><input type="text" name="cmd_action[]" list="actionsDatalist" style="width:100%;padding:6px 10px;border:1px solid #e2e5ea;border-radius:6px;font-size:13px" placeholder="ej: show_main"></td>' +
'<td><button type="button" class="btn-danger-sm" onclick="this.closest(\'tr\').remove()">✕</button></td>';
tbody.appendChild(tr);
}