$action) { if ($normalized === self::normalize($keyword)) { $matchedCommand = $action; break; } } if ($matchedCommand !== null) { ConversationContext::updateNode($ctxId, $matchedCommand); if (isset($flows[$matchedCommand])) { return self::handleFlow($flows[$matchedCommand], $context, $company, $ctxId); } if (isset($menus[$matchedCommand])) { return self::buildMenuResponse($menus[$matchedCommand], $context['from'], $company); } return null; } $greeting = $config['greeting'] ?? null; if ($greeting !== null && $currentNode === null) { ConversationContext::updateNode($ctxId, 'greeting'); if (isset($flows['greeting'])) { return self::handleFlow($flows['greeting'], $context, $company, $ctxId); } return self::sendText($greeting, $context['from'], $company); } $fallback = $config['fallback'] ?? null; if ($fallback !== null) { return self::sendText($fallback, $context['from'], $company); } return null; } private static function handleFlow(array $flow, array $context, array $company, int $ctxId): ?array { $type = $flow['type'] ?? 'text'; return match ($type) { 'text' => self::sendText($flow['message'] ?? '', $context['from'], $company), 'image' => self::sendImage($flow['media_id'] ?? '', $flow['caption'] ?? null, $context['from'], $company), 'menu' => self::buildMenuResponse($flow['menu'] ?? [], $context['from'], $company), 'function' => self::executeFunction($flow['function'] ?? '', $flow['params'] ?? [], $context, $company, $ctxId), default => null, }; } private static function executeFunction(string $function, array $params, array $context, array $company, int $ctxId): ?array { return match ($function) { 'reset' => (function () use ($ctxId, $context, $company) { ConversationContext::reset($ctxId); return self::sendText('¿En qué más puedo ayudarte?', $context['from'], $company); })(), 'forward_to_ai' => null, default => null, }; } private static function buildMenuResponse(array $menu, string $to, array $company): array { $menuType = $menu['type'] ?? 'list'; if ($menuType === 'list') { $interactive = [ 'type' => 'list', 'header' => [ 'type' => 'text', 'text' => mb_substr($menu['header'] ?? 'Menú', 0, 60), ], 'body' => [ 'text' => mb_substr($menu['body'] ?? 'Selecciona una opción:', 0, 1024), ], 'footer' => [ 'text' => mb_substr($menu['footer'] ?? $company['display_name'] ?? '', 0, 60), ], 'action' => [ 'button' => mb_substr($menu['button'] ?? 'Ver opciones', 0, 20), 'sections' => [], ], ]; foreach ($menu['sections'] ?? [] as $section) { $rows = []; foreach ($section['rows'] ?? [] as $row) { $rows[] = [ 'id' => mb_substr($row['id'] ?? '', 0, 200), 'title' => mb_substr($row['title'] ?? '', 0, 24), 'description' => isset($row['description']) ? mb_substr($row['description'], 0, 72) : null, ]; } $interactive['action']['sections'][] = [ 'title' => mb_substr($section['title'] ?? '', 0, 24), 'rows' => $rows, ]; } return self::enqueueInteractive($to, $interactive, $company); } if ($menuType === 'buttons') { $buttons = []; foreach ($menu['buttons'] ?? [] as $btn) { $buttons[] = [ 'type' => 'reply', 'reply' => [ 'id' => mb_substr($btn['id'] ?? '', 0, 256), 'title' => mb_substr($btn['title'] ?? '', 0, 20), ], ]; } $interactive = [ 'type' => 'button', 'body' => [ 'text' => mb_substr($menu['body'] ?? 'Selecciona:', 0, 1024), ], 'action' => ['buttons' => $buttons], ]; return self::enqueueInteractive($to, $interactive, $company); } return null; } private static function sendText(string $text, string $to, array $company): array { return [ 'action' => 'send', 'type' => 'text', 'to' => $to, 'payload' => json_encode(['text' => $text]), ]; } private static function sendImage(string $mediaId, ?string $caption, string $to, array $company): array { return [ 'action' => 'send', 'type' => 'image', 'to' => $to, 'payload' => json_encode(['media_id' => $mediaId, 'caption' => $caption]), ]; } private static function enqueueInteractive(string $to, array $interactive, array $company): array { return [ 'action' => 'send', 'type' => 'interactive', 'to' => $to, 'payload' => json_encode(['interactive' => $interactive]), ]; } private static function getConfig(array $company): array { $json = $company['config_json'] ?? ''; if ($json === '') { return []; } $config = json_decode($json, true); return is_array($config) ? $config : []; } private static function normalize(string $input): string { $input = mb_strtolower(trim($input)); $input = str_replace(['á', 'é', 'í', 'ó', 'ú', 'ü', 'ñ'], ['a', 'e', 'i', 'o', 'u', 'u', 'n'], $input); return preg_replace('/[^a-z0-9\s]/', '', $input); } public static function processInteractive(array $company, array $context, string $input): ?array { $config = self::getConfig($company); $flows = $config['flows'] ?? []; $botCtx = ConversationContext::getOrCreate((int)$company['id'], $context['from'], 'normal'); $ctxId = (int)$botCtx['id']; foreach ($flows as $flowId => $flow) { if (($flow['type'] ?? '') === 'menu') { $menu = $flow['menu'] ?? []; foreach ($menu['sections'] ?? [] as $section) { foreach ($section['rows'] ?? [] as $row) { if (($row['id'] ?? '') === $input) { ConversationContext::updateNode($ctxId, $row['id']); if (isset($flows[$row['id']])) { return self::handleFlow($flows[$row['id']], $context, $company, $ctxId); } return null; } } } foreach ($menu['buttons'] ?? [] as $btn) { if (($btn['id'] ?? '') === $input) { ConversationContext::updateNode($ctxId, $btn['id']); if (isset($flows[$btn['id']])) { return self::handleFlow($flows[$btn['id']], $context, $company, $ctxId); } return null; } } } } return null; } }