refactor: el alcance de fincas lo resuelve el ERP, no el bot
Los catalogos ahora mandan ?wa= igual que los informes, y el endpoint fincas devuelve solo las asignadas a ese numero. El "Todas las fincas" se antepone unicamente a quien no tiene restriccion: con dos de cinco asignadas no significa nada claro. Con eso sale sobrando todo el filtrado del lado del bot —scope_from, alcanceDelNumero y fincas_json en PhoneSync— y queda una sola fuente de verdad, del lado donde estan los datos. Efecto util: con una sola finca asignada el catalogo trae un unico item y handleDynamicList auto-selecciona, asi que el usuario nunca ve la pregunta. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
478ba3ea73
commit
94d4dcd26a
+13
-39
@@ -307,7 +307,7 @@ class NormalBot
|
||||
if (!empty($meta['__nlu_entities']) && !empty($flow['source_endpoint_key_all'])) {
|
||||
$srcKey = $flow['source_endpoint_key_all'];
|
||||
}
|
||||
$items = self::fetchDynamicList($srcKey, $company, $meta);
|
||||
$items = self::fetchDynamicList($srcKey, $company, $meta, $context['from'] ?? '');
|
||||
|
||||
if ($items === null || count($items) === 0) {
|
||||
ConversationContext::reset($ctxId);
|
||||
@@ -448,26 +448,6 @@ class NormalBot
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alcance sincronizado desde el ERP para este número. Vacío = sin restricción.
|
||||
* Hoy solo hay fincas; el parámetro deja lugar a otros alcances sin tocar esto.
|
||||
*/
|
||||
private static function alcanceDelNumero(array $company, string $waNumber, string $alcance): array
|
||||
{
|
||||
if ($waNumber === '' || $alcance !== 'fincas') return [];
|
||||
try {
|
||||
$stmt = db()->prepare(
|
||||
"SELECT fincas_json FROM company_phones WHERE company_id = ? AND wa_number = ? LIMIT 1"
|
||||
);
|
||||
$stmt->execute([(int)$company['id'], $waNumber]);
|
||||
$json = $stmt->fetchColumn();
|
||||
} catch (\PDOException $e) {
|
||||
return []; // columna aún sin crear: nadie queda restringido
|
||||
}
|
||||
if (!$json) return [];
|
||||
return array_map('strval', json_decode((string)$json, true) ?: []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarda la opción elegida con su etiqueta, para poder mostrarle al usuario
|
||||
* en qué finca/grupo está parado sin tener que volver a consultar la API.
|
||||
@@ -527,7 +507,7 @@ class NormalBot
|
||||
return $url;
|
||||
}
|
||||
|
||||
private static function fetchDynamicList(string $endpointKey, array $company, array $meta = []): ?array
|
||||
private static function fetchDynamicList(string $endpointKey, array $company, array $meta = [], string $waNumber = ''): ?array
|
||||
{
|
||||
if ($endpointKey === '') return null;
|
||||
|
||||
@@ -540,6 +520,13 @@ class NormalBot
|
||||
|
||||
$apiKey = $company['api_key'] ?? '';
|
||||
$url = self::buildUrl(self::substituteUrlVars($ep['url'], $meta), $company);
|
||||
|
||||
// Quién consulta: el ERP acota el catálogo a lo que ese número tiene
|
||||
// asignado. Mismo criterio que los informes.
|
||||
if ($waNumber !== '') {
|
||||
$url .= (str_contains($url, '?') ? '&' : '?') . 'wa=' . urlencode($waNumber);
|
||||
}
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
@@ -690,21 +677,7 @@ class NormalBot
|
||||
private static function handleCollectForEach(array $flow, array $context, array $company, int $ctxId): ?array
|
||||
{
|
||||
$meta = ConversationContext::getMetadata($ctxId);
|
||||
$items = self::fetchDynamicList($flow['source_endpoint_key'] ?? '', $company, $meta);
|
||||
|
||||
// scope_from acota la lista a lo que este número tiene asignado en el ERP.
|
||||
// Sin restricción se deja pasar todo. El id 0 ("todas") sobrevive siempre:
|
||||
// con alcance activo pasa a significar "todas las mías", y el ERP lo acota.
|
||||
$alcance = $flow['scope_from'] ?? '';
|
||||
if ($alcance !== '' && is_array($items)) {
|
||||
$permitidas = self::alcanceDelNumero($company, $context['from'] ?? '', $alcance);
|
||||
if ($permitidas) {
|
||||
$vf = $flow['value_field'] ?? 'id';
|
||||
$items = array_values(array_filter($items, fn($i) =>
|
||||
(string)($i[$vf] ?? '') === '0' || in_array((string)($i[$vf] ?? ''), $permitidas, true)
|
||||
));
|
||||
}
|
||||
}
|
||||
$items = self::fetchDynamicList($flow['source_endpoint_key'] ?? '', $company, $meta, $context['from'] ?? '');
|
||||
|
||||
// exclude_values descarta opciones que no son ítems reales, como el
|
||||
// "🌐 Todas las fincas" (id 0) que el catálogo antepone para los informes.
|
||||
@@ -1328,7 +1301,8 @@ class NormalBot
|
||||
$items = self::fetchDynamicList(
|
||||
self::resolverPorCampos($field['source_endpoint_key'] ?? '', $cap['collected'] ?? []),
|
||||
$company,
|
||||
['__ep_vars_combined' => $cap['collected'] ?? []]
|
||||
['__ep_vars_combined' => $cap['collected'] ?? []],
|
||||
$to
|
||||
);
|
||||
if (empty($items)) {
|
||||
return self::sendText(
|
||||
@@ -1358,7 +1332,7 @@ class NormalBot
|
||||
if ($type === 'select') {
|
||||
$vf = $field['value_field'] ?? 'id';
|
||||
$lf = $field['label_field'] ?? 'label';
|
||||
$items = self::fetchDynamicList($field['source_endpoint_key'] ?? '', $company, ['__ep_vars_combined' => $cap['collected'] ?? []]);
|
||||
$items = self::fetchDynamicList($field['source_endpoint_key'] ?? '', $company, ['__ep_vars_combined' => $cap['collected'] ?? []], $to);
|
||||
|
||||
|
||||
if (!empty($items)) {
|
||||
|
||||
@@ -71,12 +71,11 @@ class PhoneSync
|
||||
|
||||
$db = db();
|
||||
$upsert = $db->prepare("
|
||||
INSERT INTO company_phones (company_id, wa_number, label, permission_type, fincas_json, is_active)
|
||||
VALUES (?, ?, ?, ?, ?, 1)
|
||||
INSERT INTO company_phones (company_id, wa_number, label, permission_type, is_active)
|
||||
VALUES (?, ?, ?, ?, 1)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
label = VALUES(label),
|
||||
permission_type = VALUES(permission_type),
|
||||
fincas_json = VALUES(fincas_json),
|
||||
is_active = 1
|
||||
");
|
||||
|
||||
@@ -85,15 +84,11 @@ class PhoneSync
|
||||
$waNumber = trim($n['wa_number'] ?? '');
|
||||
if ($waNumber === '') continue;
|
||||
|
||||
// Lista vacía = sin restricción (ve todas las fincas)
|
||||
$fincas = array_values(array_filter(array_map('intval', (array)($n['fincas'] ?? []))));
|
||||
|
||||
$upsert->execute([
|
||||
$companyId,
|
||||
$waNumber,
|
||||
trim($n['nombre'] ?? ''),
|
||||
(int)($n['permiso'] ?? 1),
|
||||
$fincas ? json_encode($fincas) : null,
|
||||
]);
|
||||
$activeNumbers[] = $waNumber;
|
||||
}
|
||||
|
||||
@@ -253,8 +253,6 @@ $configJson = [
|
||||
'meta_group' => 'finca',
|
||||
'meta_key' => 'finca_id',
|
||||
'entity_key' => 'finca',
|
||||
// Solo las fincas que el ERP le asigno a este numero
|
||||
'scope_from' => 'fincas',
|
||||
'next_node' => 'show_main_menu',
|
||||
'skip_if_set' => true,
|
||||
'nlu_description' => 'Seleccionar o cambiar la finca de trabajo',
|
||||
|
||||
+19
-16
@@ -511,26 +511,29 @@ check('el bot ya no reordena por su cuenta',
|
||||
|
||||
echo "\nAlcance de fincas por número\n";
|
||||
|
||||
check('ask_finca respeta el alcance del número',
|
||||
$flows['ask_finca']['scope_from'] ?? null, 'fincas');
|
||||
|
||||
/** Espejo del filtro de handleDynamicList */
|
||||
$acotar = function (array $items, array $permitidas): array {
|
||||
if (!$permitidas) return array_column($items, 'id');
|
||||
return array_column(array_values(array_filter($items,
|
||||
fn($i) => $i['id'] === '0' || in_array($i['id'], $permitidas, true))), 'id');
|
||||
/**
|
||||
* Espejo del endpoint fincas: acota al alcance del numero y antepone "Todas"
|
||||
* solo a quien no tiene restriccion. Con dos de cinco asignadas, "todas" no
|
||||
* significa nada claro, asi que no se ofrece.
|
||||
*/
|
||||
$catalogoFincas = function (array $activas, array $alcance): array {
|
||||
if ($alcance) {
|
||||
$activas = array_values(array_filter($activas, fn($f) => in_array($f['id'], $alcance, true)));
|
||||
}
|
||||
$lista = array_column($activas, 'id');
|
||||
if (!$alcance) array_unshift($lista, '0');
|
||||
return $lista;
|
||||
};
|
||||
|
||||
$catalogo = [['id' => '0'], ['id' => '4'], ['id' => '7'], ['id' => '9']];
|
||||
$activas = [['id' => '4'], ['id' => '7'], ['id' => '9']];
|
||||
|
||||
check('sin restricción ve todas', $acotar($catalogo, []), ['0', '4', '7', '9']);
|
||||
check('con dos fincas ve solo esas', $acotar($catalogo, ['4', '9']), ['0', '4', '9']);
|
||||
check('"todas" sobrevive al filtro', in_array('0', $acotar($catalogo, ['4']), true), true);
|
||||
check('sin restricción ve todas y el "Todas"', $catalogoFincas($activas, []), ['0', '4', '7', '9']);
|
||||
check('con dos asignadas ve solo esas', $catalogoFincas($activas, ['4', '9']), ['4', '9']);
|
||||
check('y sin el "Todas"', in_array('0', $catalogoFincas($activas, ['4', '9']), true), false);
|
||||
|
||||
// Una sola finca: handleDynamicList auto-selecciona con un unico item, pero el
|
||||
// "todas" cuenta como segundo, asi que igual pregunta. Es correcto: elegir entre
|
||||
// "su finca" y "todas las suyas" son cosas distintas si mañana le suman otra.
|
||||
check('una sola finca deja dos opciones', $acotar($catalogo, ['7']), ['0', '7']);
|
||||
// Una sola finca: un unico item, y handleDynamicList auto-selecciona en ese caso
|
||||
check('con una sola no hay nada que preguntar', $catalogoFincas($activas, ['7']), ['7']);
|
||||
check('el bot ya no filtra por su cuenta', isset($flows['ask_finca']['scope_from']), false);
|
||||
|
||||
echo "\nask_finca\n";
|
||||
$af = $flows['ask_finca'];
|
||||
|
||||
Reference in New Issue
Block a user