fix(flows): CSS display conflict, addFlowCard, endpoint CRUD, menu re-send, URL vars
- Fix display:none vs display:flex conflict in PHP flow card rendering (sections were always visible)
- Add addFlowCard() JS function that properly inserts new flow cards into the PHP form
- Fix flowTabChange() to use block vs flex display correctly per section type
- Rewrite endpoints tab as full CRUD table (add/edit/delete) with name, direction, active toggle
- Add companyEndpointDelete() handler and route
- Update companyEndpointSave() to handle ep_id, name, params, is_active fields
- Add company_endpoints columns: name, params
- Add saveEpRow/testEpRow/deleteEpRow/addNewEp JS functions
- NormalBot: set __greeted sentinel after showing greeting menu to prevent re-send on every message
- NormalBot: add substituteUrlVars() to replace {param} tokens in endpoint URLs with collected metadata
- DB: submenu_ciclos_sanidad (4 items) → list; prod_kilos_finca endpoint linked; all >3-button menus → list
- DB: fincas_list endpoint activated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
eeda883bd9
commit
2c0ac04993
+25
-10
@@ -41,14 +41,15 @@ class NormalBot
|
||||
}
|
||||
|
||||
// 3. Active node — resume conversation
|
||||
if ($currentNode !== null && $currentNode !== 'collecting' && isset($flows[$currentNode])) {
|
||||
$sentinels = ['collecting', '__greeted'];
|
||||
if ($currentNode !== null && !in_array($currentNode, $sentinels, true) && isset($flows[$currentNode])) {
|
||||
return self::handleFlow($flows[$currentNode], $context, $company, $ctxId, $menus);
|
||||
}
|
||||
|
||||
// 4. Per-type greeting menu (new user / no active context)
|
||||
// 4. Per-type greeting menu — only for brand-new sessions (currentNode === null)
|
||||
$greetingMenuKey = $perType['greeting_menu'] ?? null;
|
||||
if ($greetingMenuKey !== null && $currentNode === null && isset($menus[$greetingMenuKey])) {
|
||||
ConversationContext::updateNode($ctxId, null);
|
||||
ConversationContext::updateNode($ctxId, '__greeted'); // sentinel: evita re-envío del menú
|
||||
return self::buildMenuResponse($menus[$greetingMenuKey], $context['from'], $company);
|
||||
}
|
||||
|
||||
@@ -72,9 +73,9 @@ class NormalBot
|
||||
return self::handleFlow($flows[$fallbackFlowId], $context, $company, $ctxId, $menus);
|
||||
}
|
||||
|
||||
// 7. Greeting menu as fallback
|
||||
// 7. Greeting menu as fallback (texto no reconocido después de estar en __greeted)
|
||||
if ($greetingMenuKey !== null && isset($menus[$greetingMenuKey])) {
|
||||
ConversationContext::updateNode($ctxId, null);
|
||||
ConversationContext::updateNode($ctxId, '__greeted');
|
||||
return self::buildMenuResponse($menus[$greetingMenuKey], $context['from'], $company);
|
||||
}
|
||||
|
||||
@@ -133,7 +134,8 @@ class NormalBot
|
||||
|
||||
private static function handleDynamicList(array $flow, array $context, array $company, int $ctxId): ?array
|
||||
{
|
||||
$items = self::fetchDynamicList($flow['source_endpoint_key'] ?? '', $company);
|
||||
$meta = ConversationContext::getMetadata($ctxId);
|
||||
$items = self::fetchDynamicList($flow['source_endpoint_key'] ?? '', $company, $meta);
|
||||
|
||||
if ($items === null || count($items) === 0) {
|
||||
ConversationContext::reset($ctxId);
|
||||
@@ -143,7 +145,6 @@ class NormalBot
|
||||
);
|
||||
}
|
||||
|
||||
$meta = ConversationContext::getMetadata($ctxId);
|
||||
$meta['collecting'] = [
|
||||
'meta_group' => $flow['meta_group'] ?? '',
|
||||
'meta_key' => $flow['meta_key'] ?? '',
|
||||
@@ -155,7 +156,19 @@ class NormalBot
|
||||
return self::buildDynamicListResponse($items, $flow, $context['from'], $company);
|
||||
}
|
||||
|
||||
private static function fetchDynamicList(string $endpointKey, array $company): ?array
|
||||
// Reemplaza {variable} en la URL con valores del metadata acumulado
|
||||
private static function substituteUrlVars(string $url, array $meta): string
|
||||
{
|
||||
foreach ($meta as $group => $fields) {
|
||||
if (!is_array($fields)) continue;
|
||||
foreach ($fields as $key => $value) {
|
||||
$url = str_replace('{' . $key . '}', urlencode((string)$value), $url);
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
private static function fetchDynamicList(string $endpointKey, array $company, array $meta = []): ?array
|
||||
{
|
||||
if ($endpointKey === '') return null;
|
||||
|
||||
@@ -167,7 +180,8 @@ class NormalBot
|
||||
if (!$ep || empty($ep['url'])) return null;
|
||||
|
||||
$apiKey = $company['api_key'] ?? '';
|
||||
$ch = curl_init($ep['url']);
|
||||
$url = self::substituteUrlVars($ep['url'], $meta);
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
@@ -307,7 +321,8 @@ class NormalBot
|
||||
return self::sendText('El informe solicitado no está configurado. Contacta al administrador.' . $nav, $context['from'], $company);
|
||||
}
|
||||
|
||||
$url = $ep['url'];
|
||||
$meta = ConversationContext::getMetadata($ctxId);
|
||||
$url = self::substituteUrlVars($ep['url'], $meta); // reemplaza {var} con valores del formulario
|
||||
$method = strtoupper($ep['method'] ?? 'GET');
|
||||
$apiKey = $company['api_key'] ?? '';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user