From 909c6be32fff41f7a099cf1203ff7f9c7ca1b466 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:14:49 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20expandir=20tab=20Por=20Categor=C3=ADa?= =?UTF-8?q?=20en=20bot-config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Toggle "Pedir finca al inicio" por categoría → maneja greeting/greeting_flow - Campo editable de greeting_flow (pre-llena ask_finca al activar el toggle) - Selector de menú inicial (greeting_menu, visible cuando NO pide finca) - Selector de flujo de respaldo (fallback_flow) por categoría - Datalist con flows y menús disponibles para autocompletar - save-per-type actualiza greeting, greeting_flow, greeting_menu y fallback_flow preservando menus/flows/commands del seed en cada categoría - Cat 1: sin finca, configura greeting_menu desde admin - Cat 2/3: pide finca activado, greeting_flow = ask_finca Co-Authored-By: Claude Sonnet 4.6 --- admin/DashboardController.php | 121 +++++++++++++++++++++++++++++----- public/index.php | 44 ++++++++++--- 2 files changed, 138 insertions(+), 27 deletions(-) diff --git a/admin/DashboardController.php b/admin/DashboardController.php index 6145a3a..3a3559d 100644 --- a/admin/DashboardController.php +++ b/admin/DashboardController.php @@ -3193,28 +3193,87 @@ HTML; // Per-category config HTML $perTypeConfig = $config['per_type'] ?? []; $menuKeysList = array_keys($menus); - $catLabels = ['1' => 'Categoría 1 — Solo reporta', '2' => 'Categoría 2 — Solo recibe informes', '3' => 'Categoría 3 — Reporta y recibe']; + $flowKeysList = array_keys($config['flows'] ?? []); + // Include per_type menus/flows in the option lists + foreach ($config['per_type'] ?? [] as $pt) { + foreach (array_keys($pt['menus'] ?? []) as $k) if (!in_array($k, $menuKeysList)) $menuKeysList[] = $k; + foreach (array_keys($pt['flows'] ?? []) as $k) if (!in_array($k, $flowKeysList)) $flowKeysList[] = $k; + } + $catMeta = [ + '1' => ['label' => 'Categoría 1 — Solo reporta', 'icon' => '📤', 'bg' => '#f0fdf4', 'bd' => '#86efac', 'clr' => '#166534'], + '2' => ['label' => 'Categoría 2 — Solo recibe informes', 'icon' => '📥', 'bg' => '#eff6ff', 'bd' => '#93c5fd', 'clr' => '#1e40af'], + '3' => ['label' => 'Categoría 3 — Reporta y recibe', 'icon' => '🔄', 'bg' => '#fdf4ff', 'bd' => '#e879f9', 'clr' => '#701a75'], + ]; $categoryTabHtml = ''; + // Build shared option lists + $menuOptsBase = ''; + foreach ($menuKeysList as $mk) $menuOptsBase .= ''; + $flowOptsBase = ''; + foreach ($menuKeysList as $mk) $flowOptsBase .= ''; + foreach ($flowKeysList as $fk) $flowOptsBase .= ''; + foreach ([1, 2, 3] as $cat) { $catCfg = $perTypeConfig[(string)$cat] ?? []; + $m = $catMeta[(string)$cat]; + $greetingVal = array_key_exists('greeting', $catCfg) ? $catCfg['greeting'] : null; + $askFinca = $greetingVal !== null; + $chk = $askFinca ? ' checked' : ''; + $greetFlow = self::h($catCfg['greeting_flow'] ?? ($askFinca ? 'ask_finca' : '')); + $fallback = $catCfg['fallback_flow'] ?? ''; $selMenu = $catCfg['greeting_menu'] ?? ''; - $opts = ''; - foreach ($menuKeysList as $mk) { - $s = $selMenu === $mk ? ' selected' : ''; - $opts .= ''; - } - $label = $catLabels[(string)$cat]; + + // greeting_menu selector (insert selected attr) + $menuOpts = str_replace( + 'value="' . self::h($selMenu) . '"', + 'value="' . self::h($selMenu) . '" selected', + $menuOptsBase + ); + // fallback selector (insert selected attr) + $fbOpts = str_replace( + 'value="' . self::h($fallback) . '"', + 'value="' . self::h($fallback) . '" selected', + $flowOptsBase + ); + + $greetFlowStyle = $askFinca ? '' : 'display:none'; + $greetMenuStyle = $askFinca ? 'display:none' : ''; + + $label = $m['label']; $icon = $m['icon']; $bg = $m['bg']; $bd = $m['bd']; $clr = $m['clr']; $categoryTabHtml .= << -
{$label}
-
- - -
Menú que se muestra cuando el usuario escribe por primera vez o sin contexto activo
-
- +
+
+ {$icon} + {$label} +
+
+ +
Al activarlo, el bot solicita la finca antes de mostrar el menú principal
+
+
+ + +
Flow que se ejecuta en el primer mensaje del usuario
+
+
+ + +
Menú que se muestra cuando el usuario no tiene contexto activo
+
+
+ + +
Se muestra cuando el usuario envía algo que no corresponde a ninguna opción
+
+
CATHTML; } $aiModelMini = $aiModelVal === 'gpt-4o-mini' ? ' selected' : ''; @@ -3643,7 +3702,13 @@ HTML;
👥 Configuración por Categoría
-

Define qué menú de bienvenida ve cada categoría de usuario cuando escribe por primera vez o sin contexto activo.

+

Define el comportamiento inicial de cada categoría de usuario: si pide finca, qué menú muestra y qué hacer ante texto no reconocido.

+ +HTML; + foreach ($menuKeysList as $mk) echo '' . "\n"; + foreach ($flowKeysList as $fk) echo '' . "\n"; + echo << {$categoryTabHtml}
@@ -3846,13 +3911,33 @@ HTML; const CONFIG = {$botConfigJson}; const ENDPOINTS = {$botEndpointsJson}; +function toggleAskFinca(chk, cat) { + var flowDiv = document.getElementById('cat-greet-flow-' + cat); + var menuDiv = document.getElementById('cat-greet-menu-' + cat); + if (chk.checked) { + if (flowDiv) flowDiv.style.display = ''; + if (menuDiv) menuDiv.style.display = 'none'; + var gf = document.querySelector('[name="per_type_greeting_flow[' + cat + ']"]'); + if (gf && gf.value === '') gf.value = 'ask_finca'; + } else { + if (flowDiv) flowDiv.style.display = 'none'; + if (menuDiv) menuDiv.style.display = ''; + } +} + function savePerType() { const cid = document.querySelector('input[name="company_id"]').value; const fd = new FormData(); fd.set('company_id', cid); [1, 2, 3].forEach(function(cat) { + var chk = document.querySelector('[name="per_type_ask_finca[' + cat + ']"]'); + fd.append('per_type_ask_finca[' + cat + ']', chk && chk.checked ? '1' : '0'); + var gf = document.querySelector('[name="per_type_greeting_flow[' + cat + ']"]'); + if (gf) fd.append('per_type_greeting_flow[' + cat + ']', gf.value); var sel = document.querySelector('[name="per_type_menu[' + cat + ']"]'); if (sel) fd.append('per_type_menu[' + cat + ']', sel.value); + var fb = document.querySelector('[name="per_type_fallback[' + cat + ']"]'); + if (fb) fd.append('per_type_fallback[' + cat + ']', fb.value); }); var btn = document.getElementById('savePerTypeBtn'); if (btn) { btn.textContent = 'Guardando...'; btn.disabled = true; } diff --git a/public/index.php b/public/index.php index eb7de0d..a1d630c 100644 --- a/public/index.php +++ b/public/index.php @@ -660,22 +660,48 @@ $routes = [ $config = json_decode($company['config_json'] ?? '{}', true) ?: []; - $rawPt = $config['per_type'] ?? []; + $rawPt = $config['per_type'] ?? []; $perType = (is_array($rawPt) && count(array_filter(array_keys($rawPt), 'is_string')) > 0) ? $rawPt : []; - $perTypeMenus = $_POST['per_type_menu'] ?? []; + $ptAskFinca = $_POST['per_type_ask_finca'] ?? []; + $ptGreetFlow = $_POST['per_type_greeting_flow'] ?? []; + $ptMenus = $_POST['per_type_menu'] ?? []; + $ptFallback = $_POST['per_type_fallback'] ?? []; + foreach ([1, 2, 3] as $cat) { - $mk = trim($perTypeMenus[$cat] ?? ''); - $key = (string)$cat; - if ($mk !== '') { - $perType[$key] = array_merge($perType[$key] ?? [], ['greeting_menu' => $mk]); + $key = (string)$cat; + $current = $perType[$key] ?? []; // preserve menus/flows/commands from seed + + $askFinca = ($ptAskFinca[$cat] ?? '0') === '1'; + + if ($askFinca) { + $gf = trim($ptGreetFlow[$cat] ?? 'ask_finca'); + $current['greeting'] = ''; + $current['greeting_flow'] = $gf !== '' ? $gf : 'ask_finca'; + unset($current['greeting_menu']); } else { - unset($perType[$key]['greeting_menu']); - if (empty($perType[$key])) unset($perType[$key]); + $current['greeting'] = null; + unset($current['greeting_flow']); + $mk = trim($ptMenus[$cat] ?? ''); + if ($mk !== '') { + $current['greeting_menu'] = $mk; + } else { + unset($current['greeting_menu']); + } } + + $fb = trim($ptFallback[$cat] ?? ''); + if ($fb !== '') { + $current['fallback_flow'] = $fb; + } else { + unset($current['fallback_flow']); + } + + $perType[$key] = $current; } - $config['per_type'] = empty($perType) ? new stdClass() : $perType; + + $config['per_type'] = $perType; CompanyRepository::save(['id' => $companyId, 'config_json' => json_encode($config, JSON_UNESCAPED_UNICODE)]); echo json_encode(['ok' => true]);