feat(superadmin): página "Secretária IA" com motivos de liga/desliga por sessão

Nova aba no super admin (/superadmin/secretaria): tabela com quem ligou/desligou a
Secretária de cada número, quando e o MOTIVO do desligamento. Endpoint
/api/superadmin/secretaria-desligamentos (superadminGuard) busca o log global no
motor (resolve um e-mail de dono pareado; log é global). Item no Sidebar + rotas.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-07-06 20:02:39 +02:00
parent cfeac891fb
commit b67fa15c55
4 changed files with 118 additions and 6 deletions
+21
View File
@@ -9438,6 +9438,27 @@ app.get('/api/superadmin/login-log', superadminGuard, async (req, res) => {
} catch (err) { res.status(500).json({ error: err.message }); }
});
// Super admin: motivos de LIGAR/DESLIGAR a Secretária por sessão (histórico global,
// vem do motor — sec_session_power_log). Requer integração newwhats configurada.
app.get('/api/superadmin/secretaria-desligamentos', superadminGuard, async (req, res) => {
try {
const { getConfig } = await import('./newwhats/config.js');
const cfg = getConfig();
if (!cfg.motorUrl || !cfg.integrationKey) return res.json({ configurado: false, log: [] });
// O log é GLOBAL no motor; qualquer conta de dono pareada serve para autenticar.
const { rows } = await pool.query(`SELECT u.email FROM usuarios u JOIN vinculos v ON v.usuario_id = u.id WHERE v.role IN ('donoclinica','donoconsultorio') ORDER BY u.email LIMIT 1`);
const email = rows[0]?.email;
if (!email) return res.json({ configurado: true, log: [] });
const limit = Math.min(500, Math.max(1, parseInt(String(req.query.limit || '150'), 10)));
const r = await fetch(`${cfg.motorUrl}/api/ext/v1/secretaria/session-power/log?limit=${limit}`, {
headers: { 'x-nw-key': cfg.integrationKey, 'x-nw-email': email },
});
if (!r.ok) return res.status(502).json({ error: 'Motor indisponível', status: r.status });
const log = await r.json();
res.json({ configurado: true, log: Array.isArray(log) ? log : [] });
} catch (err) { res.status(500).json({ error: err.message }); }
});
// Defaults mesclados sob o JSON salvo: chaves novas (biomedico, add-ons de plugin)
// aparecem na tabela mesmo p/ configs gravadas antes delas existirem.
const PRICING_DEFAULTS = {