feat: URL var config per endpoint + smart date handling in api_report
- Admin: each endpoint with {vars} shows a vars config section; per-var
mode is "ask user" (bot prompts in WhatsApp) or "fixed" (preset: today,
month_start, month_end, last_7, last_30, year_start, or literal)
- Bot: resolves fixed vars before calling endpoint; queues "ask" vars and
collects them sequentially from the user before executing the report
- date_mode now uses fecha_desde/fecha_hasta (ERP expected names); added
current_month mode
- JSON responses from ERP (sin_datos etc.) shown as text, not uploaded as file
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
fb28140e23
commit
ff9ff67445
@@ -1142,6 +1142,59 @@ HTML;
|
|||||||
</div>";
|
</div>";
|
||||||
}
|
}
|
||||||
$bfSection = $epDir === 'upload' ? '' : 'display:none';
|
$bfSection = $epDir === 'upload' ? '' : 'display:none';
|
||||||
|
|
||||||
|
// ── URL vars config ──────────────────────────────────────────
|
||||||
|
$varConfigs = json_decode($ep['params'] ?? '[]', true) ?: [];
|
||||||
|
// Detect {var} placeholders in URL
|
||||||
|
preg_match_all('/\{([^}]+)\}/', $ep['url'] ?? '', $urlVarMatches);
|
||||||
|
$urlVars = array_unique($urlVarMatches[1] ?? []);
|
||||||
|
// Merge: URL vars not yet in config get added with default mode=ask
|
||||||
|
$configuredKeys = array_column($varConfigs, 'key');
|
||||||
|
foreach ($urlVars as $uv) {
|
||||||
|
if (!in_array($uv, $configuredKeys, true)) {
|
||||||
|
$varConfigs[] = ['key' => $uv, 'mode' => 'ask', 'prompt' => "¿Ingresa {$uv}?", 'value' => ''];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$varRowsHtml = '';
|
||||||
|
$fixedPresets = ['current_date'=>'Fecha actual','month_start'=>'Inicio mes actual','month_end'=>'Fin mes actual','last_7_start'=>'Hace 7 días','last_30_start'=>'Hace 30 días','year_start'=>'Inicio año'];
|
||||||
|
foreach ($varConfigs as $vc) {
|
||||||
|
$vk = self::h($vc['key'] ?? '');
|
||||||
|
$vm = $vc['mode'] ?? 'ask';
|
||||||
|
$vp = self::h($vc['prompt'] ?? '');
|
||||||
|
$vv = self::h($vc['value'] ?? '');
|
||||||
|
$mAsk = $vm === 'ask' ? 'selected' : '';
|
||||||
|
$mFix = $vm === 'fixed' ? 'selected' : '';
|
||||||
|
$askStyle = $vm === 'ask' ? '' : 'display:none';
|
||||||
|
$fixStyle = $vm === 'fixed' ? '' : 'display:none';
|
||||||
|
$fixOptions = '';
|
||||||
|
foreach ($fixedPresets as $pk => $pl) {
|
||||||
|
$sel = $vv === $pk ? 'selected' : '';
|
||||||
|
$fixOptions .= "<option value=\"{$pk}\" {$sel}>{$pl}</option>";
|
||||||
|
}
|
||||||
|
// Custom literal option
|
||||||
|
$isCustom = $vv !== '' && !array_key_exists($vv, $fixedPresets);
|
||||||
|
$fixOptions .= '<option value="__custom"' . ($isCustom ? ' selected' : '') . '>Valor literal...</option>';
|
||||||
|
$customVal = $isCustom ? $vv : '';
|
||||||
|
$varRowsHtml .= <<<VR
|
||||||
|
<div class="var-row" data-key="{$vk}" style="display:grid;grid-template-columns:80px 100px 1fr auto;gap:6px;align-items:center;margin-bottom:5px;font-size:12px">
|
||||||
|
<span style="font-family:monospace;background:#f1f5f9;padding:2px 5px;border-radius:3px;font-size:11px">{{{$vk}}}</span>
|
||||||
|
<select class="vr-mode" onchange="toggleVarMode(this)" style="font-size:11px;padding:3px;border:1px solid #e5e7eb;border-radius:3px">
|
||||||
|
<option value="ask" {$mAsk}>❓ Preguntar</option>
|
||||||
|
<option value="fixed" {$mFix}>📌 Fijo</option>
|
||||||
|
</select>
|
||||||
|
<span class="vr-ask-wrap" style="{$askStyle}">
|
||||||
|
<input type="text" class="vr-prompt" value="{$vp}" placeholder="Pregunta al usuario (ej: ¿Desde qué fecha? AAAA-MM-DD)" style="width:100%;font-size:11px;padding:3px 5px;border:1px solid #e5e7eb;border-radius:3px">
|
||||||
|
</span>
|
||||||
|
<span class="vr-fix-wrap" style="{$fixStyle};display:flex;gap:4px">
|
||||||
|
<select class="vr-preset" onchange="toggleCustom(this)" style="font-size:11px;padding:3px;border:1px solid #e5e7eb;border-radius:3px">{$fixOptions}</select>
|
||||||
|
<input type="text" class="vr-custom" value="{$customVal}" placeholder="AAAA-MM-DD" style="width:100px;font-size:11px;padding:3px 5px;border:1px solid #e5e7eb;border-radius:3px;display:' . ($isCustom ? 'block' : 'none') . '">
|
||||||
|
</span>
|
||||||
|
<button type="button" onclick="this.closest('.var-row').remove()" style="background:#fee2e2;border:none;border-radius:3px;padding:2px 6px;cursor:pointer;font-size:11px">✕</button>
|
||||||
|
</div>
|
||||||
|
VR;
|
||||||
|
}
|
||||||
|
$varsSection = !empty($urlVars) ? '' : 'display:none';
|
||||||
|
|
||||||
$epRows .= <<<ROW
|
$epRows .= <<<ROW
|
||||||
<tr id="ep-row-{$epId}">
|
<tr id="ep-row-{$epId}">
|
||||||
<td style="min-width:120px">
|
<td style="min-width:120px">
|
||||||
@@ -1163,7 +1216,7 @@ HTML;
|
|||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td style="min-width:200px">
|
<td style="min-width:200px">
|
||||||
<input type="text" value="{$epUrl}" id="epu_{$epId}" placeholder="https://..." style="width:100%;font-size:12px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px">
|
<input type="text" value="{$epUrl}" id="epu_{$epId}" placeholder="https://..." oninput="onUrlChange({$epId})" style="width:100%;font-size:12px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:4px">
|
||||||
<div style="font-size:10px;color:#9ca3af;margin-top:2px">Usa {'{var}'} para variables. {$lastAt}</div>
|
<div style="font-size:10px;color:#9ca3af;margin-top:2px">Usa {'{var}'} para variables. {$lastAt}</div>
|
||||||
</td>
|
</td>
|
||||||
<td><input type="checkbox" {$epAct} id="epa_{$epId}" title="Activo"></td>
|
<td><input type="checkbox" {$epAct} id="epa_{$epId}" title="Activo"></td>
|
||||||
@@ -1173,6 +1226,12 @@ HTML;
|
|||||||
<button class="btn-danger-sm" onclick="deleteEpRow({$id},{$epId})">✕</button>
|
<button class="btn-danger-sm" onclick="deleteEpRow({$id},{$epId})">✕</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr id="ep-vars-{$epId}" style="{$varsSection}">
|
||||||
|
<td colspan="7" style="padding:8px 12px;background:#fefce8;border-bottom:1px solid #fde68a">
|
||||||
|
<div style="font-size:11px;font-weight:600;color:#92400e;margin-bottom:6px">⚙️ Variables de URL — configura cómo se obtiene cada <code>{'{var}'}</code></div>
|
||||||
|
<div id="vr-rows-{$epId}">{$varRowsHtml}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr id="ep-bf-{$epId}" style="{$bfSection}">
|
<tr id="ep-bf-{$epId}" style="{$bfSection}">
|
||||||
<td colspan="7" style="padding:8px 12px;background:#f0f7ff;border-bottom:1px solid #e5e7eb">
|
<td colspan="7" style="padding:8px 12px;background:#f0f7ff;border-bottom:1px solid #e5e7eb">
|
||||||
<div style="font-size:11px;font-weight:600;color:#3b5bdb;margin-bottom:6px">📋 Campos del body (para "Pedir campos y enviar")</div>
|
<div style="font-size:11px;font-weight:600;color:#3b5bdb;margin-bottom:6px">📋 Campos del body (para "Pedir campos y enviar")</div>
|
||||||
@@ -1504,6 +1563,72 @@ function collectBfFields(epId) {
|
|||||||
return JSON.stringify(fields);
|
return JSON.stringify(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleVarMode(sel) {
|
||||||
|
const row = sel.closest('.var-row');
|
||||||
|
const isAsk = sel.value === 'ask';
|
||||||
|
row.querySelector('.vr-ask-wrap').style.display = isAsk ? '' : 'none';
|
||||||
|
row.querySelector('.vr-fix-wrap').style.display = isAsk ? 'none' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCustom(sel) {
|
||||||
|
const wrap = sel.closest('.vr-fix-wrap');
|
||||||
|
wrap.querySelector('.vr-custom').style.display = sel.value === '__custom' ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectVarConfigs(epId) {
|
||||||
|
const rows = document.querySelectorAll('#vr-rows-'+epId+' .var-row');
|
||||||
|
const configs = [];
|
||||||
|
rows.forEach(r => {
|
||||||
|
const key = r.dataset.key;
|
||||||
|
const mode = r.querySelector('.vr-mode').value;
|
||||||
|
if (mode === 'ask') {
|
||||||
|
configs.push({ key, mode, prompt: r.querySelector('.vr-prompt')?.value.trim() || '' });
|
||||||
|
} else {
|
||||||
|
const preset = r.querySelector('.vr-preset').value;
|
||||||
|
const value = preset === '__custom' ? (r.querySelector('.vr-custom')?.value.trim() || '') : preset;
|
||||||
|
configs.push({ key, mode, value });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return JSON.stringify(configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When URL changes, refresh the vars section
|
||||||
|
function onUrlChange(epId) {
|
||||||
|
const url = document.getElementById('epu_'+epId).value;
|
||||||
|
const matches = [...url.matchAll(/\{([^}]+)\}/g)].map(m => m[1]);
|
||||||
|
const vrRows = document.getElementById('vr-rows-'+epId);
|
||||||
|
const vrTr = document.getElementById('ep-vars-'+epId);
|
||||||
|
if (!vrRows) return;
|
||||||
|
// Add rows for new vars not yet present
|
||||||
|
const existing = [...vrRows.querySelectorAll('.var-row')].map(r => r.dataset.key);
|
||||||
|
matches.forEach(key => {
|
||||||
|
if (existing.includes(key)) return;
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'var-row';
|
||||||
|
div.dataset.key = key;
|
||||||
|
div.style.cssText = 'display:grid;grid-template-columns:80px 100px 1fr auto;gap:6px;align-items:center;margin-bottom:5px;font-size:12px';
|
||||||
|
div.innerHTML = '<span style="font-family:monospace;background:#f1f5f9;padding:2px 5px;border-radius:3px;font-size:11px">{'+key+'}</span>' + `
|
||||||
|
<select class="vr-mode" onchange="toggleVarMode(this)" style="font-size:11px;padding:3px;border:1px solid #e5e7eb;border-radius:3px">
|
||||||
|
<option value="ask" selected>❓ Preguntar</option><option value="fixed">📌 Fijo</option>
|
||||||
|
</select>
|
||||||
|
<span class="vr-ask-wrap">
|
||||||
|
<input type="text" class="vr-prompt" value="" placeholder="Pregunta al usuario" style="width:100%;font-size:11px;padding:3px 5px;border:1px solid #e5e7eb;border-radius:3px">
|
||||||
|
</span>
|
||||||
|
<span class="vr-fix-wrap" style="display:none;gap:4px">
|
||||||
|
<select class="vr-preset" onchange="toggleCustom(this)" style="font-size:11px;padding:3px;border:1px solid #e5e7eb;border-radius:3px">
|
||||||
|
<option value="current_date">Fecha actual</option><option value="month_start">Inicio mes</option>
|
||||||
|
<option value="month_end">Fin mes</option><option value="last_7_start">Hace 7 días</option>
|
||||||
|
<option value="last_30_start">Hace 30 días</option><option value="year_start">Inicio año</option>
|
||||||
|
<option value="__custom">Valor literal...</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" class="vr-custom" placeholder="AAAA-MM-DD" style="display:none;width:100px;font-size:11px;padding:3px 5px;border:1px solid #e5e7eb;border-radius:3px">
|
||||||
|
</span>
|
||||||
|
<button type="button" onclick="this.closest('.var-row').remove()" style="background:#fee2e2;border:none;border-radius:3px;padding:2px 6px;cursor:pointer;font-size:11px">✕</button>`;
|
||||||
|
vrRows.appendChild(div);
|
||||||
|
});
|
||||||
|
if (vrTr) vrTr.style.display = matches.length ? '' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
async function saveEpRow(cid, epId) {
|
async function saveEpRow(cid, epId) {
|
||||||
const fd=new FormData();
|
const fd=new FormData();
|
||||||
fd.append('company_id', cid);
|
fd.append('company_id', cid);
|
||||||
@@ -1515,6 +1640,7 @@ async function saveEpRow(cid, epId) {
|
|||||||
fd.append('url', document.getElementById('epu_'+epId).value.trim());
|
fd.append('url', document.getElementById('epu_'+epId).value.trim());
|
||||||
fd.append('is_active', document.getElementById('epa_'+epId).checked ? '1' : '0');
|
fd.append('is_active', document.getElementById('epa_'+epId).checked ? '1' : '0');
|
||||||
fd.append('body_fields', collectBfFields(epId));
|
fd.append('body_fields', collectBfFields(epId));
|
||||||
|
fd.append('params', collectVarConfigs(epId));
|
||||||
const msg=document.getElementById('epMsg');
|
const msg=document.getElementById('epMsg');
|
||||||
const r=await fetch('/admin/company/endpoint/save',{method:'POST',body:fd});
|
const r=await fetch('/admin/company/endpoint/save',{method:'POST',body:fd});
|
||||||
const j=await r.json();
|
const j=await r.json();
|
||||||
|
|||||||
+131
-7
@@ -59,6 +59,11 @@ class NormalBot
|
|||||||
return self::handleCapInput($meta, $input, $context, $company, $ctxId);
|
return self::handleCapInput($meta, $input, $context, $company, $ctxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2d. api_vars: collecting URL variables before executing an api_report endpoint
|
||||||
|
if (!empty($meta['__api_vars'])) {
|
||||||
|
return self::handleApiVarsInput($meta, $input, $context, $company, $ctxId, $flows, $menus);
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Active node — resume conversation
|
// 3. Active node — resume conversation
|
||||||
$sentinels = ['collecting', '__greeted'];
|
$sentinels = ['collecting', '__greeted'];
|
||||||
if ($currentNode !== null && !in_array($currentNode, $sentinels, true) && isset($flows[$currentNode])) {
|
if ($currentNode !== null && !in_array($currentNode, $sentinels, true) && isset($flows[$currentNode])) {
|
||||||
@@ -178,8 +183,14 @@ class NormalBot
|
|||||||
// Reemplaza {variable} en la URL con valores del metadata acumulado
|
// Reemplaza {variable} en la URL con valores del metadata acumulado
|
||||||
private static function substituteUrlVars(string $url, array $meta): string
|
private static function substituteUrlVars(string $url, array $meta): string
|
||||||
{
|
{
|
||||||
|
// Priority: __ep_vars_combined (vars collected/resolved for this endpoint call)
|
||||||
|
$epVars = $meta['__ep_vars_combined'] ?? [];
|
||||||
|
foreach ($epVars as $key => $value) {
|
||||||
|
$url = str_replace('{' . $key . '}', urlencode((string)$value), $url);
|
||||||
|
}
|
||||||
|
// Then the rest of meta groups
|
||||||
foreach ($meta as $group => $fields) {
|
foreach ($meta as $group => $fields) {
|
||||||
if (!is_array($fields)) continue;
|
if (!is_array($fields) || str_starts_with($group, '__')) continue;
|
||||||
foreach ($fields as $key => $value) {
|
foreach ($fields as $key => $value) {
|
||||||
$url = str_replace('{' . $key . '}', urlencode((string)$value), $url);
|
$url = str_replace('{' . $key . '}', urlencode((string)$value), $url);
|
||||||
}
|
}
|
||||||
@@ -620,6 +631,57 @@ class NormalBot
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Resolves fixed-mode var values ───────────────────────────────────────
|
||||||
|
private static function resolveFixedVar(string $value): string
|
||||||
|
{
|
||||||
|
return match ($value) {
|
||||||
|
'current_date' => date('Y-m-d'),
|
||||||
|
'month_start' => date('Y-m-01'),
|
||||||
|
'month_end' => date('Y-m-t'),
|
||||||
|
'last_7_start' => date('Y-m-d', strtotime('-7 days')),
|
||||||
|
'last_30_start' => date('Y-m-d', strtotime('-30 days')),
|
||||||
|
'year_start' => date('Y-01-01'),
|
||||||
|
default => $value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Handles sequential collection of URL vars before calling endpoint ───
|
||||||
|
private static function handleApiVarsInput(array $meta, string $input, array $context, array $company, int $ctxId, array $flows, array $menus): ?array
|
||||||
|
{
|
||||||
|
$nav = "\n\nEscribe *menu* para ver opciones o *salir* para terminar.";
|
||||||
|
$state = $meta['__api_vars'];
|
||||||
|
|
||||||
|
$pending = $state['pending'] ?? [];
|
||||||
|
$collected = $state['collected'] ?? [];
|
||||||
|
$epKey = $state['endpoint_key'] ?? '';
|
||||||
|
$epParams = $state['ep_params'] ?? [];
|
||||||
|
|
||||||
|
// Save the user's answer for the current question
|
||||||
|
if (!empty($pending)) {
|
||||||
|
$current = array_shift($pending);
|
||||||
|
$collected[$current['key']] = trim($input);
|
||||||
|
$state['pending'] = $pending;
|
||||||
|
$state['collected'] = $collected;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If more questions remain, ask the next one
|
||||||
|
if (!empty($state['pending'])) {
|
||||||
|
$next = $state['pending'][0];
|
||||||
|
$meta['__api_vars'] = $state;
|
||||||
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
|
return self::sendText($next['prompt'] . $nav, $context['from'], $company);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All vars collected — inject into meta and execute
|
||||||
|
unset($meta['__api_vars']);
|
||||||
|
foreach ($collected as $k => $v) {
|
||||||
|
$meta['__ep_vars'][$k] = $v;
|
||||||
|
}
|
||||||
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
|
|
||||||
|
return self::executeApiReport(array_merge($epParams, ['endpoint_key' => $epKey]), $context, $company, $ctxId);
|
||||||
|
}
|
||||||
|
|
||||||
private static function executeApiReport(array $params, array $context, array $company, int $ctxId): ?array
|
private static function executeApiReport(array $params, array $context, array $company, int $ctxId): ?array
|
||||||
{
|
{
|
||||||
$endpointKey = $params['endpoint_key'] ?? '';
|
$endpointKey = $params['endpoint_key'] ?? '';
|
||||||
@@ -630,7 +692,7 @@ class NormalBot
|
|||||||
return self::sendText('Error: endpoint no configurado.' . $nav, $context['from'], $company);
|
return self::sendText('Error: endpoint no configurado.' . $nav, $context['from'], $company);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = db()->prepare("SELECT url, method FROM company_endpoints WHERE company_id = ? AND endpoint_key = ? AND is_active = 1 LIMIT 1");
|
$stmt = db()->prepare("SELECT url, method, params FROM company_endpoints WHERE company_id = ? AND endpoint_key = ? AND is_active = 1 LIMIT 1");
|
||||||
$stmt->execute([(int)$company['id'], $endpointKey]);
|
$stmt->execute([(int)$company['id'], $endpointKey]);
|
||||||
$ep = $stmt->fetch();
|
$ep = $stmt->fetch();
|
||||||
|
|
||||||
@@ -640,18 +702,72 @@ class NormalBot
|
|||||||
return self::sendText('El informe solicitado no está configurado. Contacta al administrador.' . $nav, $context['from'], $company);
|
return self::sendText('El informe solicitado no está configurado. Contacta al administrador.' . $nav, $context['from'], $company);
|
||||||
}
|
}
|
||||||
|
|
||||||
$meta = ConversationContext::getMetadata($ctxId);
|
// ── Resolve URL vars from params config ──────────────────────────────
|
||||||
$url = self::substituteUrlVars($ep['url'], $meta); // reemplaza {var} con valores del formulario
|
$varConfigs = json_decode($ep['params'] ?? '[]', true) ?: [];
|
||||||
|
$meta = ConversationContext::getMetadata($ctxId);
|
||||||
|
|
||||||
|
// Inject any previously collected vars into meta for substituteUrlVars
|
||||||
|
$epVars = $meta['__ep_vars'] ?? [];
|
||||||
|
if (!empty($epVars)) {
|
||||||
|
$meta['__ep_vars_flat'] = $epVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
$askVars = [];
|
||||||
|
foreach ($varConfigs as $vc) {
|
||||||
|
$key = $vc['key'] ?? '';
|
||||||
|
$mode = $vc['mode'] ?? 'ask';
|
||||||
|
if ($key === '') continue;
|
||||||
|
|
||||||
|
if ($mode === 'fixed') {
|
||||||
|
// Inject fixed value directly into meta for substituteUrlVars
|
||||||
|
$meta['__ep_vars_flat'][$key] = self::resolveFixedVar($vc['value'] ?? '');
|
||||||
|
} elseif ($mode === 'ask') {
|
||||||
|
// Only ask if not already collected
|
||||||
|
if (!isset($epVars[$key])) {
|
||||||
|
$askVars[] = ['key' => $key, 'prompt' => $vc['prompt'] ?? "Ingresa el valor para {$key}:"];
|
||||||
|
} else {
|
||||||
|
$meta['__ep_vars_flat'][$key] = $epVars[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are vars to ask, start collection flow
|
||||||
|
if (!empty($askVars)) {
|
||||||
|
$first = array_shift($askVars);
|
||||||
|
$meta['__api_vars'] = [
|
||||||
|
'endpoint_key' => $endpointKey,
|
||||||
|
'ep_params' => $params,
|
||||||
|
'pending' => $askVars,
|
||||||
|
'collected' => [],
|
||||||
|
];
|
||||||
|
// Clean collected vars so they don't bleed into the next call
|
||||||
|
unset($meta['__ep_vars']);
|
||||||
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
|
return self::sendText($first['prompt'] . $nav, $context['from'], $company);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten collected vars into meta for substituteUrlVars
|
||||||
|
if (!empty($meta['__ep_vars_flat'])) {
|
||||||
|
$meta['__ep_vars_combined'] = $meta['__ep_vars_flat'];
|
||||||
|
}
|
||||||
|
unset($meta['__ep_vars'], $meta['__ep_vars_flat']);
|
||||||
|
ConversationContext::updateMetadata($ctxId, $meta);
|
||||||
|
|
||||||
|
$url = self::substituteUrlVars($ep['url'], array_merge($meta, ['__ep_vars_combined' => $meta['__ep_vars_combined'] ?? []]));
|
||||||
$method = strtoupper($ep['method'] ?? 'GET');
|
$method = strtoupper($ep['method'] ?? 'GET');
|
||||||
$apiKey = $company['api_key'] ?? '';
|
$apiKey = $company['api_key'] ?? '';
|
||||||
|
|
||||||
$extraQuery = $params['query'] ?? [];
|
$extraQuery = $params['query'] ?? [];
|
||||||
$dateMode = $params['date_mode'] ?? '';
|
$dateMode = $params['date_mode'] ?? '';
|
||||||
if ($dateMode === 'today') {
|
if ($dateMode === 'today') {
|
||||||
$extraQuery['fecha'] = date('Y-m-d');
|
$extraQuery['fecha_desde'] = date('Y-m-d');
|
||||||
|
$extraQuery['fecha_hasta'] = date('Y-m-d');
|
||||||
} elseif ($dateMode === 'last_30') {
|
} elseif ($dateMode === 'last_30') {
|
||||||
$extraQuery['fecha_inicio'] = date('Y-m-d', strtotime('-30 days'));
|
$extraQuery['fecha_desde'] = date('Y-m-d', strtotime('-30 days'));
|
||||||
$extraQuery['fecha_fin'] = date('Y-m-d');
|
$extraQuery['fecha_hasta'] = date('Y-m-d');
|
||||||
|
} elseif ($dateMode === 'current_month') {
|
||||||
|
$extraQuery['fecha_desde'] = date('Y-m-01');
|
||||||
|
$extraQuery['fecha_hasta'] = date('Y-m-d');
|
||||||
}
|
}
|
||||||
$extraQuery['telefono'] = $context['from'];
|
$extraQuery['telefono'] = $context['from'];
|
||||||
$extraQuery['nombre'] = $context['name'] ?? '';
|
$extraQuery['nombre'] = $context['name'] ?? '';
|
||||||
@@ -693,6 +809,14 @@ class NormalBot
|
|||||||
return self::sendText('Error al obtener el reporte. Intenta de nuevo.' . $nav, $context['from'], $company);
|
return self::sendText('Error al obtener el reporte. Intenta de nuevo.' . $nav, $context['from'], $company);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Si el ERP devuelve JSON (ej: sin_datos), informar al usuario en texto
|
||||||
|
if (str_starts_with($contentType ?? '', 'application/json')) {
|
||||||
|
$json = json_decode($content, true);
|
||||||
|
$msg = $json['mensaje'] ?? ($json['message'] ?? 'No hay datos disponibles para el período solicitado.');
|
||||||
|
ConversationContext::reset($ctxId);
|
||||||
|
return self::sendText('ℹ️ ' . $msg . $nav, $context['from'], $company);
|
||||||
|
}
|
||||||
|
|
||||||
$mimeMap = [
|
$mimeMap = [
|
||||||
'application/pdf' => ['pdf', 'application/pdf'],
|
'application/pdf' => ['pdf', 'application/pdf'],
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
||||||
|
|||||||
Reference in New Issue
Block a user