diff --git a/admin/DashboardController.php b/admin/DashboardController.php index 493affb..9b08981 100644 --- a/admin/DashboardController.php +++ b/admin/DashboardController.php @@ -1604,6 +1604,7 @@ HTML; ⏳ Pendientes 💬 Chat 🏢 Empresas + 🤖 Bot Config ⚙️ Configuración 🔄 Sincronizar 📨 Procesar Cola @@ -1914,4 +1915,463 @@ HTML; jsonResponse(200, ['status' => 'sent']); } + + // ─── GET /admin/bot-config ────────────────────────────────────────────── + + public static function botConfig(): void + { + SessionAuth::require(); + $user = SessionAuth::user(); + $userName = self::h($user['name'] ?? 'Admin'); + $companyId = (int)($_GET['id'] ?? 0); + $companies = CompanyRepository::findAll(); + $company = null; + $config = ['commands' => [], 'menus' => [], 'flows' => [], 'ai_prompt' => '', 'fallback' => '', 'greeting' => '']; + $msg = $_GET['msg'] ?? ''; + $toastHtml = $msg === 'saved' ? '
Configuración guardada exitosamente.
' : ''; + + if ($companyId > 0) { + $company = CompanyRepository::findById($companyId); + if ($company) { + $cj = $company['config_json'] ?? ''; + if ($cj) { + $parsed = json_decode($cj, true); + if (is_array($parsed)) $config = $parsed; + } + } + } + + $configJson = self::h(json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + + http_response_code(200); + header('Content-Type: text/html; charset=utf-8'); + echo << + + + + +Configurar Bot — Palmas360 + + + +
+

Palmas360 Configurar Bot

+
👤 {$userName}Salir
+
+ +
+ {$toastHtml} +
+ +
'; + + if (!$company) { + echo '
Selecciona una empresa para configurar su bot.
'; + echo '
'; + exit; + } + + $c = $company; + $cId = (int)$c['id']; + $cName = self::h($c['name'] ?? ''); + + // Pre-fill form values from config + $greetingVal = self::h($config['greeting'] ?? ''); + $fallbackVal = self::h($config['fallback'] ?? ''); + $aiPromptVal = self::h($config['ai_prompt'] ?? ''); + $aiModelVal = self::h($config['ai_model'] ?? 'gpt-4o-mini'); + $aiTempVal = self::h((string)($config['ai_temperature'] ?? 0.7)); + $aiProviderVal = self::h($config['ai_provider'] ?? ''); + $approvalWebhookVal = self::h($config['approval_webhook'] ?? ''); + $ignoreVal = self::h(implode("\n", $config['ignore_prefixes'] ?? [])); + $commands = $config['commands'] ?? []; + $menus = $config['menus'] ?? []; + $flows = $config['flows'] ?? []; + $aiProviderOpenai = $aiProviderVal === 'openai' ? ' selected' : ''; + $aiProviderMock = $aiProviderVal === 'mock' ? ' selected' : ''; + $aiModelMini = $aiModelVal === 'gpt-4o-mini' ? ' selected' : ''; + $aiModel4o = $aiModelVal === 'gpt-4o' ? ' selected' : ''; + $aiModel35 = $aiModelVal === 'gpt-3.5-turbo' ? ' selected' : ''; + + echo << + + +
+ + + + + +
+ + +
+
📋 Comandos
+

Palabras clave que el usuario escribe para ejecutar acciones. Ej: "menu", "info", "contacto"

+ + + +HTML; + $cmdIdx = 0; + foreach ($commands as $keyword => $action) { + echo " + + + + "; + $cmdIdx++; + } + echo << +
Palabra claveAcción (menú o flujo)
+ +
+ + +
+
📑 Menús Interactivos
+

Menús tipo lista o botones que se envían al usuario. Cada menú tiene un ID (ej: show_main) y puede tener secciones con filas.

+ + + +
+
🔀 Flujos
+

Acciones que se ejecutan cuando un comando o fila de menú es seleccionada.

+
+HTML; + $flowIdx = 0; + foreach ($flows as $fk => $flow) { + $ft = $flow['type'] ?? 'text'; + $fMsg = self::h($flow['message'] ?? ''); + $fFn = self::h($flow['function'] ?? 'forward_to_ai'); + $fMenu = self::h($flow['menu'] ?? ''); + echo "
+
+ ID: + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
"; + $flowIdx++; + } + echo << + +
+ + +
+
🤖 Inteligencia Artificial
+
+ + +
Si no se selecciona, usa el proveedor de Configuración General
+
+
+
+ + +
+
+ + +
+
+
+ + +
Instrucciones que define el comportamiento de la IA
+
+
+ + +
+
⚙️ General
+
+
+ + +
Primer mensaje que recibe un usuario nuevo
+
+
+ + +
Cuando el bot no entiende el mensaje
+
+
+
+ + +
URL a la que se notificará cuando se requiera aprobación
+
+
+ + +
Números que comiencen con estos prefijos no recibirán respuestas del bot
+
+
+ +
+ + ↩ Volver a empresas +
+ +
+ + + + +HTML; + exit; + } } diff --git a/admin/v1/WpWebhook.php b/admin/v1/WpWebhook.php index a7dd028..73ed46b 100644 --- a/admin/v1/WpWebhook.php +++ b/admin/v1/WpWebhook.php @@ -109,6 +109,13 @@ class WpWebhook // Persistir evento crudo para auditoría self::saveRawEvent($raw); + + // Procesar cola outbound inmediatamente para respuestas automáticas + try { + OutboundWorker::processQueue(); + } catch (\Throwable $e) { + self::log('WARN', 'OutboundWorker falló: ' . $e->getMessage()); + } } private static function resolveCompany(array $payload): void diff --git a/public/index.php b/public/index.php index 8d56e16..c2ce9e0 100644 --- a/public/index.php +++ b/public/index.php @@ -282,6 +282,110 @@ $routes = [ jsonResponse(200, ['status' => 'rejected', 'message' => 'Mensaje rechazado']); })()], + // ─── Admin: configurador visual del bot ──────────────────────────────── + ['GET', '/admin/bot-config', fn() => DashboardController::botConfig()], + + ['POST', '/admin/bot-config/save', fn() => (function () { + SessionAuth::require(); + $companyId = (int)($_POST['company_id'] ?? 0); + if ($companyId === 0) { header('Location: /admin/bot-config?msg=error'); exit; } + + $config = []; + // Commands + $keywords = $_POST['cmd_keyword'] ?? []; + $actions = $_POST['cmd_action'] ?? []; + $commands = []; + foreach ($keywords as $i => $kw) { + $kw = trim($kw); + $act = trim($actions[$i] ?? ''); + if ($kw !== '' && $act !== '') $commands[$kw] = $act; + } + if (!empty($commands)) $config['commands'] = $commands; + + // Menus + $menuKeys = $_POST['menu_key'] ?? []; + $menuTypes = $_POST['menu_type'] ?? []; + $menuHeaders = $_POST['menu_header'] ?? []; + $menuBodies = $_POST['menu_body'] ?? []; + $menuFooters = $_POST['menu_footer'] ?? []; + $menuButtons = $_POST['menu_button'] ?? []; + $menuSections = $_POST['menu_sections'] ?? []; + $menus = []; + foreach ($menuKeys as $i => $mk) { + $mk = trim($mk); + if ($mk === '') continue; + $sections = []; + if (!empty($menuSections[$i])) { + foreach ($menuSections[$i] as $si => $section) { + $title = trim($section['title'] ?? ''); + if ($title === '') continue; + $rows = []; + foreach ($section['rows'] ?? [] as $row) { + $rid = trim($row['id'] ?? ''); + $rtitle = trim($row['title'] ?? ''); + if ($rid === '' || $rtitle === '') continue; + $rows[] = ['id' => $rid, 'title' => $rtitle, 'description' => trim($row['description'] ?? '')]; + } + $sections[] = ['title' => $title, 'rows' => $rows]; + } + } + $menus[$mk] = [ + 'type' => $menuTypes[$i] ?? 'list', + 'header' => trim($menuHeaders[$i] ?? ''), + 'body' => trim($menuBodies[$i] ?? ''), + 'footer' => trim($menuFooters[$i] ?? ''), + 'button' => trim($menuButtons[$i] ?? ''), + 'sections' => $sections, + ]; + } + if (!empty($menus)) $config['menus'] = $menus; + + // Flows + $flowKeys = $_POST['flow_key'] ?? []; + $flowTypes = $_POST['flow_type'] ?? []; + $flowMessages = $_POST['flow_message'] ?? []; + $flowFunctions = $_POST['flow_function'] ?? []; + $flowMenus = $_POST['flow_menu'] ?? []; + $flows = []; + foreach ($flowKeys as $i => $fk) { + $fk = trim($fk); + if ($fk === '') continue; + $ftype = $flowTypes[$i] ?? 'text'; + $f = ['type' => $ftype]; + if ($ftype === 'text') $f['message'] = trim($flowMessages[$i] ?? ''); + elseif ($ftype === 'function') $f['function'] = trim($flowFunctions[$i] ?? 'forward_to_ai'); + elseif ($ftype === 'menu') $f['menu'] = trim($flowMenus[$i] ?? ''); + $flows[$fk] = $f; + } + if (!empty($flows)) $config['flows'] = $flows; + + // AI + $aiPrompt = trim($_POST['ai_prompt'] ?? ''); + if ($aiPrompt !== '') $config['ai_prompt'] = $aiPrompt; + $aiModel = trim($_POST['ai_model'] ?? ''); + if ($aiModel !== '') $config['ai_model'] = $aiModel; + $aiTemp = trim($_POST['ai_temperature'] ?? ''); + if ($aiTemp !== '') $config['ai_temperature'] = (float)$aiTemp; + $aiProvider = trim($_POST['ai_provider'] ?? ''); + if ($aiProvider !== '') $config['ai_provider'] = $aiProvider; + + // General + $greeting = trim($_POST['greeting'] ?? ''); + if ($greeting !== '') $config['greeting'] = $greeting; + $fallback = trim($_POST['fallback'] ?? ''); + if ($fallback !== '') $config['fallback'] = $fallback; + $ignorePrefixes = trim($_POST['ignore_prefixes'] ?? ''); + if ($ignorePrefixes !== '') { + $config['ignore_prefixes'] = array_map('trim', explode("\n", $ignorePrefixes)); + } + $approvalWebhook = trim($_POST['approval_webhook'] ?? ''); + if ($approvalWebhook !== '') $config['approval_webhook'] = $approvalWebhook; + + CompanyRepository::save(['id' => $companyId, 'config_json' => json_encode($config, JSON_UNESCAPED_UNICODE)]); + header('Location: /admin/bot-config?id=' . $companyId . '&msg=saved'); + exit; + })()], + // ─── Admin: configuración general ────────────────────────────────────── ['GET', '/admin/settings', fn() => DashboardController::settings()],