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
|
||||
* en qué finca/grupo está parado sin tener que volver a consultar la API.
|
||||
@@ -997,6 +1058,18 @@ class NormalBot
|
||||
|
||||
$collected = [];
|
||||
$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)) {
|
||||
foreach ($fields as $i => $field) {
|
||||
$fKey = $field['key'] ?? '';
|
||||
@@ -1015,6 +1088,10 @@ class NormalBot
|
||||
}
|
||||
}
|
||||
|
||||
while ($startIdx < count($fields) && isset($collected[$fields[$startIdx]['key'] ?? ''])) {
|
||||
$startIdx++;
|
||||
}
|
||||
|
||||
$meta['__cap'] = [
|
||||
'fields' => $fields,
|
||||
'index' => $startIdx,
|
||||
@@ -2097,6 +2174,7 @@ class NormalBot
|
||||
|
||||
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);
|
||||
$menuType = $menu['type'] ?? 'list';
|
||||
|
||||
|
||||
+11
-2
@@ -71,11 +71,13 @@ class PhoneSync
|
||||
|
||||
$db = db();
|
||||
$upsert = $db->prepare("
|
||||
INSERT INTO company_phones (company_id, wa_number, label, permission_type, is_active)
|
||||
VALUES (?, ?, ?, ?, 1)
|
||||
INSERT INTO company_phones (company_id, wa_number, label, permission_type, tercero_id, modulos_json, is_active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 1)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
label = VALUES(label),
|
||||
permission_type = VALUES(permission_type),
|
||||
tercero_id = VALUES(tercero_id),
|
||||
modulos_json = VALUES(modulos_json),
|
||||
is_active = 1
|
||||
");
|
||||
|
||||
@@ -84,11 +86,18 @@ class PhoneSync
|
||||
$waNumber = trim($n['wa_number'] ?? '');
|
||||
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([
|
||||
$companyId,
|
||||
$waNumber,
|
||||
trim($n['nombre'] ?? ''),
|
||||
(int)($n['permiso'] ?? 1),
|
||||
$tercero,
|
||||
$modulos ? json_encode($modulos) : null,
|
||||
]);
|
||||
$activeNumbers[] = $waNumber;
|
||||
}
|
||||
|
||||
+14
-12
@@ -97,9 +97,9 @@ $configJson = [
|
||||
[
|
||||
'title' => 'Informes',
|
||||
'rows' => [
|
||||
['id' => 'submenu_produccion', 'title' => '🌿 Producción', 'description' => 'Informes de producción'],
|
||||
['id' => 'submenu_ciclos', 'title' => '🔄 Ciclos', 'description' => 'Ciclos de campo'],
|
||||
['id' => 'submenu_ausentismos', 'title' => '👥 Ausentismos', 'description' => 'Faltas y novedades'],
|
||||
['id' => 'submenu_produccion', 'modulo' => 'produccion', 'title' => '🌿 Producción', 'description' => 'Informes de producción'],
|
||||
['id' => 'submenu_ciclos', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Ciclos de campo'],
|
||||
['id' => 'submenu_ausentismos', 'modulo' => 'ausentismos', 'title' => '👥 Ausentismos', 'description' => 'Faltas y novedades'],
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -230,9 +230,9 @@ $configJson = [
|
||||
[
|
||||
'title' => 'Opciones',
|
||||
'rows' => [
|
||||
['id' => 'registrar_ausentismo', 'title' => '👥 Ausentismo', 'description' => 'Registrar falta, incapacidad o permiso'],
|
||||
['id' => 'registrar_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_ausentismo', 'modulo' => 'ausentismos', 'title' => '👥 Ausentismo', 'description' => 'Registrar falta, incapacidad o permiso'],
|
||||
['id' => 'registrar_pluviometria','modulo' => 'pluviometria', 'title' => '🌧️ Pluviometría', 'description' => 'Milímetros por finca · pronto formulario'],
|
||||
['id' => 'registrar_ciclo', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Abrir o cerrar lotes · pronto formulario'],
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -416,6 +416,8 @@ $configJson = [
|
||||
'key' => 'empleado_id',
|
||||
'label' => 'Trabajador',
|
||||
'type' => 'lookup',
|
||||
// Numero vinculado a un trabajador: no se le pregunta
|
||||
'from_phone' => 'tercero_id',
|
||||
'prompt' => '👤 Escribe el nombre o documento del trabajador:',
|
||||
'lookup_endpoint_key' => 'empleados_buscar_dn',
|
||||
'lookup_param' => 'filtro',
|
||||
@@ -560,9 +562,9 @@ $configJson = [
|
||||
[
|
||||
'title' => 'Opciones',
|
||||
'rows' => [
|
||||
['id' => 'registrar_ausentismo', 'title' => '👥 Ausentismo', 'description' => 'Registrar falta, incapacidad o permiso'],
|
||||
['id' => 'registrar_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_ausentismo', 'modulo' => 'ausentismos', 'title' => '👥 Ausentismo', 'description' => 'Registrar falta, incapacidad o permiso'],
|
||||
['id' => 'registrar_pluviometria','modulo' => 'pluviometria', 'title' => '🌧️ Pluviometría', 'description' => 'Milímetros por finca · pronto formulario'],
|
||||
['id' => 'registrar_ciclo', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Abrir o cerrar lotes · pronto formulario'],
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -592,9 +594,9 @@ $configJson = [
|
||||
[
|
||||
'title' => 'Informes',
|
||||
'rows' => [
|
||||
['id' => 'submenu_produccion', 'title' => '🌿 Producción', 'description' => 'Informes de producción'],
|
||||
['id' => 'submenu_ciclos', 'title' => '🔄 Ciclos', 'description' => 'Ciclos de campo'],
|
||||
['id' => 'submenu_ausentismos', 'title' => '👥 Ausentismos', 'description' => 'Faltas y novedades'],
|
||||
['id' => 'submenu_produccion', 'modulo' => 'produccion', 'title' => '🌿 Producción', 'description' => 'Informes de producción'],
|
||||
['id' => 'submenu_ciclos', 'modulo' => 'ciclos', 'title' => '🔄 Ciclos', 'description' => 'Ciclos de campo'],
|
||||
['id' => 'submenu_ausentismos', 'modulo' => 'ausentismos', 'title' => '👥 Ausentismos', 'description' => 'Faltas y novedades'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user