feat: perfil por numero — trabajador vinculado y modulos permitidos
Dos perfiles, sin agregar ningun campo para distinguirlos:
- Con tercero vinculado en el ERP, el numero es de un trabajador: from_phone
pre-llena empleado_id y el flujo se saltea el buscador. Sale gratis porque
empleados y terceros son la misma tabla, asi que el id sirve tal cual.
- Sin vinculo es supervisor y elige de quien es el registro.
Los modulos habilitados (ciclos, ausentismos, produccion, pluviometria) llegan
del ERP y filtran las filas de menu. Las filas sin modulo declarado no se tocan,
asi la navegacion queda intacta.
Se mantiene la convencion de las fincas: nada marcado significa todo. Los
numeros que ya existen siguen igual sin migrar nada.
Requiere en la base del bot:
ALTER TABLE company_phones
ADD COLUMN tercero_id INT NULL, ADD COLUMN modulos_json TEXT NULL;
El codigo tolera que no existan todavia.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
94d4dcd26a
commit
724179a3c6
@@ -448,6 +448,67 @@ class NormalBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perfil sincronizado del numero: con quien esta vinculado y que modulos
|
||||||
|
* tiene habilitados. Tolera que las columnas aun no existan.
|
||||||
|
*/
|
||||||
|
private static function perfilDelNumero(array $company, string $waNumber): array
|
||||||
|
{
|
||||||
|
static $cache = [];
|
||||||
|
$clave = $company['id'] . '|' . $waNumber;
|
||||||
|
if (isset($cache[$clave])) return $cache[$clave];
|
||||||
|
|
||||||
|
$vacio = ['tercero_id' => null, 'modulos' => []];
|
||||||
|
if ($waNumber === '') return $vacio;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = db()->prepare(
|
||||||
|
"SELECT tercero_id, modulos_json FROM company_phones WHERE company_id = ? AND wa_number = ? LIMIT 1"
|
||||||
|
);
|
||||||
|
$stmt->execute([(int)$company['id'], $waNumber]);
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
return $vacio; // columnas aun sin crear
|
||||||
|
}
|
||||||
|
if (!$row) return $vacio;
|
||||||
|
|
||||||
|
return $cache[$clave] = [
|
||||||
|
'tercero_id' => $row['tercero_id'] ?: null,
|
||||||
|
'modulos' => json_decode((string)($row['modulos_json'] ?? ''), true) ?: [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quita del menu las filas de modulos que este numero no tiene habilitados.
|
||||||
|
* Sin modulos declarados no se filtra nada: la ausencia significa "todos",
|
||||||
|
* misma convencion que las fincas.
|
||||||
|
*/
|
||||||
|
private static function filtrarMenuPorModulos(array $menu, array $company, string $waNumber): array
|
||||||
|
{
|
||||||
|
$perfil = self::perfilDelNumero($company, $waNumber);
|
||||||
|
if (!$perfil['modulos']) return $menu;
|
||||||
|
|
||||||
|
$permitidos = array_merge(
|
||||||
|
(array)($perfil['modulos']['descarga'] ?? []),
|
||||||
|
(array)($perfil['modulos']['carga'] ?? [])
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($menu['sections'] ?? [] as $i => $sec) {
|
||||||
|
$filas = array_values(array_filter($sec['rows'] ?? [], fn($r) =>
|
||||||
|
!isset($r['modulo']) || in_array($r['modulo'], $permitidos, true)
|
||||||
|
));
|
||||||
|
$menu['sections'][$i]['rows'] = $filas;
|
||||||
|
}
|
||||||
|
$menu['sections'] = array_values(array_filter($menu['sections'] ?? [], fn($s) => !empty($s['rows'])));
|
||||||
|
|
||||||
|
foreach ($menu['buttons'] ?? [] as $i => $btn) {
|
||||||
|
if (isset($btn['modulo']) && !in_array($btn['modulo'], $permitidos, true)) unset($menu['buttons'][$i]);
|
||||||
|
}
|
||||||
|
if (isset($menu['buttons'])) $menu['buttons'] = array_values($menu['buttons']);
|
||||||
|
|
||||||
|
return $menu;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guarda la opción elegida con su etiqueta, para poder mostrarle al usuario
|
* 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.
|
* en qué finca/grupo está parado sin tener que volver a consultar la API.
|
||||||
@@ -997,6 +1058,18 @@ class NormalBot
|
|||||||
|
|
||||||
$collected = [];
|
$collected = [];
|
||||||
$startIdx = 0;
|
$startIdx = 0;
|
||||||
|
|
||||||
|
// from_phone: el dato sale del perfil del numero, no se pregunta. Con un
|
||||||
|
// tercero vinculado el trabajador reporta lo suyo y el bot no le pide
|
||||||
|
// que se busque a si mismo; sin vinculo es supervisor y elige.
|
||||||
|
$perfil = self::perfilDelNumero($company, $context['from'] ?? '');
|
||||||
|
foreach ($fields as $f) {
|
||||||
|
$origen = $f['from_phone'] ?? '';
|
||||||
|
if ($origen !== '' && !empty($perfil[$origen])) {
|
||||||
|
$collected[$f['key']] = (string)$perfil[$origen];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($entities)) {
|
if (!empty($entities)) {
|
||||||
foreach ($fields as $i => $field) {
|
foreach ($fields as $i => $field) {
|
||||||
$fKey = $field['key'] ?? '';
|
$fKey = $field['key'] ?? '';
|
||||||
@@ -1015,6 +1088,10 @@ class NormalBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while ($startIdx < count($fields) && isset($collected[$fields[$startIdx]['key'] ?? ''])) {
|
||||||
|
$startIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
$meta['__cap'] = [
|
$meta['__cap'] = [
|
||||||
'fields' => $fields,
|
'fields' => $fields,
|
||||||
'index' => $startIdx,
|
'index' => $startIdx,
|
||||||
@@ -2097,6 +2174,7 @@ class NormalBot
|
|||||||
|
|
||||||
private static function buildMenuResponse(array $menu, string $to, array $company, ?int $ctxId = null): ?array
|
private static function buildMenuResponse(array $menu, string $to, array $company, ?int $ctxId = null): ?array
|
||||||
{
|
{
|
||||||
|
$menu = self::filtrarMenuPorModulos($menu, $company, $to);
|
||||||
$menu = self::aplicarVarsMenu($menu, $ctxId, $company);
|
$menu = self::aplicarVarsMenu($menu, $ctxId, $company);
|
||||||
$menuType = $menu['type'] ?? 'list';
|
$menuType = $menu['type'] ?? 'list';
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -71,11 +71,13 @@ class PhoneSync
|
|||||||
|
|
||||||
$db = db();
|
$db = db();
|
||||||
$upsert = $db->prepare("
|
$upsert = $db->prepare("
|
||||||
INSERT INTO company_phones (company_id, wa_number, label, permission_type, is_active)
|
INSERT INTO company_phones (company_id, wa_number, label, permission_type, tercero_id, modulos_json, is_active)
|
||||||
VALUES (?, ?, ?, ?, 1)
|
VALUES (?, ?, ?, ?, ?, ?, 1)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
label = VALUES(label),
|
label = VALUES(label),
|
||||||
permission_type = VALUES(permission_type),
|
permission_type = VALUES(permission_type),
|
||||||
|
tercero_id = VALUES(tercero_id),
|
||||||
|
modulos_json = VALUES(modulos_json),
|
||||||
is_active = 1
|
is_active = 1
|
||||||
");
|
");
|
||||||
|
|
||||||
@@ -84,11 +86,18 @@ class PhoneSync
|
|||||||
$waNumber = trim($n['wa_number'] ?? '');
|
$waNumber = trim($n['wa_number'] ?? '');
|
||||||
if ($waNumber === '') continue;
|
if ($waNumber === '') continue;
|
||||||
|
|
||||||
|
// tercero_id vinculado = perfil trabajador: el bot no le pregunta
|
||||||
|
// de quién es el registro. Sin él, es supervisor y elige.
|
||||||
|
$tercero = !empty($n['tercero_id']) ? (int)$n['tercero_id'] : null;
|
||||||
|
$modulos = (array)($n['modulos'] ?? []);
|
||||||
|
|
||||||
$upsert->execute([
|
$upsert->execute([
|
||||||
$companyId,
|
$companyId,
|
||||||
$waNumber,
|
$waNumber,
|
||||||
trim($n['nombre'] ?? ''),
|
trim($n['nombre'] ?? ''),
|
||||||
(int)($n['permiso'] ?? 1),
|
(int)($n['permiso'] ?? 1),
|
||||||
|
$tercero,
|
||||||
|
$modulos ? json_encode($modulos) : null,
|
||||||
]);
|
]);
|
||||||
$activeNumbers[] = $waNumber;
|
$activeNumbers[] = $waNumber;
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-12
@@ -97,9 +97,9 @@ $configJson = [
|
|||||||
[
|
[
|
||||||
'title' => 'Informes',
|
'title' => 'Informes',
|
||||||
'rows' => [
|
'rows' => [
|
||||||
['id' => 'submenu_produccion', 'title' => '🌿 Producción', 'description' => 'Informes de producción'],
|
['id' => 'submenu_produccion', 'modulo' => 'produccion', 'title' => '🌿 Producción', 'description' => 'Informes de producción'],
|
||||||
['id' => 'submenu_ciclos', 'title' => '🔄 Ciclos', 'description' => 'Ciclos de campo'],
|
['id' => 'submenu_ciclos', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Ciclos de campo'],
|
||||||
['id' => 'submenu_ausentismos', 'title' => '👥 Ausentismos', 'description' => 'Faltas y novedades'],
|
['id' => 'submenu_ausentismos', 'modulo' => 'ausentismos', 'title' => '👥 Ausentismos', 'description' => 'Faltas y novedades'],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -230,9 +230,9 @@ $configJson = [
|
|||||||
[
|
[
|
||||||
'title' => 'Opciones',
|
'title' => 'Opciones',
|
||||||
'rows' => [
|
'rows' => [
|
||||||
['id' => 'registrar_ausentismo', 'title' => '👥 Ausentismo', 'description' => 'Registrar falta, incapacidad o permiso'],
|
['id' => 'registrar_ausentismo', 'modulo' => 'ausentismos', 'title' => '👥 Ausentismo', 'description' => 'Registrar falta, incapacidad o permiso'],
|
||||||
['id' => 'registrar_pluviometria','title' => '🌧️ Pluviometría', 'description' => 'Milímetros por finca · pronto formulario'],
|
['id' => 'registrar_pluviometria','modulo' => 'pluviometria', 'title' => '🌧️ Pluviometría', 'description' => 'Milímetros por finca · pronto formulario'],
|
||||||
['id' => 'registrar_ciclo', 'title' => '🔄 Ciclos', 'description' => 'Abrir o cerrar lotes · pronto formulario'],
|
['id' => 'registrar_ciclo', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Abrir o cerrar lotes · pronto formulario'],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -416,6 +416,8 @@ $configJson = [
|
|||||||
'key' => 'empleado_id',
|
'key' => 'empleado_id',
|
||||||
'label' => 'Trabajador',
|
'label' => 'Trabajador',
|
||||||
'type' => 'lookup',
|
'type' => 'lookup',
|
||||||
|
// Numero vinculado a un trabajador: no se le pregunta
|
||||||
|
'from_phone' => 'tercero_id',
|
||||||
'prompt' => '👤 Escribe el nombre o documento del trabajador:',
|
'prompt' => '👤 Escribe el nombre o documento del trabajador:',
|
||||||
'lookup_endpoint_key' => 'empleados_buscar_dn',
|
'lookup_endpoint_key' => 'empleados_buscar_dn',
|
||||||
'lookup_param' => 'filtro',
|
'lookup_param' => 'filtro',
|
||||||
@@ -560,9 +562,9 @@ $configJson = [
|
|||||||
[
|
[
|
||||||
'title' => 'Opciones',
|
'title' => 'Opciones',
|
||||||
'rows' => [
|
'rows' => [
|
||||||
['id' => 'registrar_ausentismo', 'title' => '👥 Ausentismo', 'description' => 'Registrar falta, incapacidad o permiso'],
|
['id' => 'registrar_ausentismo', 'modulo' => 'ausentismos', 'title' => '👥 Ausentismo', 'description' => 'Registrar falta, incapacidad o permiso'],
|
||||||
['id' => 'registrar_pluviometria','title' => '🌧️ Pluviometría', 'description' => 'Milímetros por finca · pronto formulario'],
|
['id' => 'registrar_pluviometria','modulo' => 'pluviometria', 'title' => '🌧️ Pluviometría', 'description' => 'Milímetros por finca · pronto formulario'],
|
||||||
['id' => 'registrar_ciclo', 'title' => '🔄 Ciclos', 'description' => 'Abrir o cerrar lotes · pronto formulario'],
|
['id' => 'registrar_ciclo', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Abrir o cerrar lotes · pronto formulario'],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -592,9 +594,9 @@ $configJson = [
|
|||||||
[
|
[
|
||||||
'title' => 'Informes',
|
'title' => 'Informes',
|
||||||
'rows' => [
|
'rows' => [
|
||||||
['id' => 'submenu_produccion', 'title' => '🌿 Producción', 'description' => 'Informes de producción'],
|
['id' => 'submenu_produccion', 'modulo' => 'produccion', 'title' => '🌿 Producción', 'description' => 'Informes de producción'],
|
||||||
['id' => 'submenu_ciclos', 'title' => '🔄 Ciclos', 'description' => 'Ciclos de campo'],
|
['id' => 'submenu_ciclos', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Ciclos de campo'],
|
||||||
['id' => 'submenu_ausentismos', 'title' => '👥 Ausentismos', 'description' => 'Faltas y novedades'],
|
['id' => 'submenu_ausentismos', 'modulo' => 'ausentismos', 'title' => '👥 Ausentismos', 'description' => 'Faltas y novedades'],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user