feat: comprehensive dashboard refactor and performance stabilization
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m33s
All checks were successful
Build and Deploy / build-and-push (push) Successful in 3m33s
This commit is contained in:
184
backend/seed_data.js
Normal file
184
backend/seed_data.js
Normal file
@@ -0,0 +1,184 @@
|
||||
const pool = require('./db.js');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const PRODUCTS = [
|
||||
"Pneu Aro 13 175/70R13",
|
||||
"Pneu Aro 14 175/70R14",
|
||||
"Pneu Aro 15 195/60R15",
|
||||
"Pneu Aro 16 205/55R16",
|
||||
"Pneu Aro 17 225/45R17",
|
||||
"Alinhamento e Balanceamento",
|
||||
"Revisão do Sistema de Arrefecimento",
|
||||
"Manutenção de Freios",
|
||||
"Troca de Óleo e Filtros",
|
||||
"Limpeza de Bico"
|
||||
];
|
||||
|
||||
const DEFAULT_ORIGINS = ['WhatsApp', 'Instagram', 'Website', 'LinkedIn', 'Indicação'];
|
||||
const DEFAULT_FUNNELS = ['Sem atendimento', 'Identificação', 'Negociação', 'Ganhos', 'Perdidos'];
|
||||
|
||||
const FIRST_NAMES = ["Ana", "Bruno", "Carlos", "Daniela", "Eduardo", "Fernanda", "Gabriel", "Helena", "Igor", "Julia", "Lucas", "Mariana", "Nicolas", "Olivia", "Pedro", "Quintino", "Rafael", "Sofia", "Thiago", "Ursula", "Victor", "Wagner", "Xuxa", "Yuri", "Zeca", "Amanda", "Beto", "Camila", "Diogo", "Elisa", "Fabio", "Gisele", "Henrique", "Isabela", "Joao", "Karla"];
|
||||
const LAST_NAMES = ["Silva", "Santos", "Oliveira", "Souza", "Rodrigues", "Ferreira", "Alves", "Pereira", "Lima", "Gomes", "Costa", "Ribeiro", "Martins", "Carvalho", "Almeida", "Lopes", "Soares", "Fernandes", "Vieira", "Barbosa"];
|
||||
|
||||
function getRandomItem(arr) {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function generateUniqueNames(count) {
|
||||
const names = new Set();
|
||||
while (names.size < count) {
|
||||
names.add(`${getRandomItem(FIRST_NAMES)} ${getRandomItem(LAST_NAMES)}`);
|
||||
}
|
||||
return Array.from(names);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
console.log('🔄 Iniciando geração de dados...');
|
||||
|
||||
// 1. Encontrar o tenant "teste"
|
||||
const [tenants] = await pool.query(`SELECT id FROM tenants WHERE slug = 'teste' OR name LIKE '%teste%' LIMIT 1`);
|
||||
if (tenants.length === 0) {
|
||||
console.log('❌ Tenant "teste" não encontrado.');
|
||||
process.exit(1);
|
||||
}
|
||||
const tenantId = tenants[0].id;
|
||||
console.log(`✅ Tenant "teste" encontrado: ${tenantId}`);
|
||||
|
||||
// Pegar origens e funis dinâmicos se existirem
|
||||
let origins = [...DEFAULT_ORIGINS];
|
||||
let funnels = [...DEFAULT_FUNNELS];
|
||||
let originGroupId = null;
|
||||
let funnelId = null;
|
||||
|
||||
try {
|
||||
const [originGroups] = await pool.query(`SELECT id FROM origin_groups WHERE tenant_id = ? LIMIT 1`, [tenantId]);
|
||||
if (originGroups.length > 0) {
|
||||
originGroupId = originGroups[0].id;
|
||||
const [originItems] = await pool.query(`SELECT name FROM origin_items WHERE origin_group_id = ?`, [originGroupId]);
|
||||
if (originItems.length > 0) origins = originItems.map(o => o.name);
|
||||
}
|
||||
const [funnelGroups] = await pool.query(`SELECT id FROM funnels WHERE tenant_id = ? LIMIT 1`, [tenantId]);
|
||||
if (funnelGroups.length > 0) {
|
||||
funnelId = funnelGroups[0].id;
|
||||
const [funnelStages] = await pool.query(`SELECT name FROM funnel_stages WHERE funnel_id = ?`, [funnelId]);
|
||||
if (funnelStages.length > 0) funnels = funnelStages.map(f => f.name);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Aviso: Usando origens/funis padrão devido a erro na busca de dinâmicos.');
|
||||
}
|
||||
|
||||
// 2. Limpar dados existentes do tenant (exceto admin e tenant em si)
|
||||
console.log('🧹 Limpando attendances antigas...');
|
||||
await pool.query(`DELETE FROM attendances WHERE tenant_id = ?`, [tenantId]);
|
||||
|
||||
console.log('🧹 Limpando usuários antigos (exceto admin)...');
|
||||
await pool.query(`DELETE FROM users WHERE tenant_id = ? AND role != 'admin'`, [tenantId]);
|
||||
|
||||
console.log('🧹 Limpando times antigos...');
|
||||
await pool.query(`DELETE FROM teams WHERE tenant_id = ?`, [tenantId]);
|
||||
|
||||
// 3. Criar 5 times
|
||||
const teams = [];
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const teamId = crypto.randomUUID();
|
||||
await pool.query(`INSERT INTO teams (id, tenant_id, name, description, origin_group_id, funnel_id) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[teamId, tenantId, `Equipe Vendas ${i}`, `Equipe responsável pela região ${i}`, originGroupId, funnelId]);
|
||||
teams.push(teamId);
|
||||
}
|
||||
console.log('✅ 5 times criados.');
|
||||
|
||||
// 4. Criar 36 usuários (1 manager por time = 5 managers, 31 agents)
|
||||
const users = [];
|
||||
const roles = ['manager', 'manager', 'manager', 'manager', 'manager', ...Array(31).fill('agent')];
|
||||
const uniqueNames = generateUniqueNames(36);
|
||||
|
||||
// Distribuir usuários entre os times
|
||||
for (let i = 0; i < 36; i++) {
|
||||
const userId = crypto.randomUUID();
|
||||
const role = roles[i];
|
||||
const teamId = teams[i % 5];
|
||||
const name = uniqueNames[i];
|
||||
const email = `${name.split(' ')[0].toLowerCase()}.${name.split(' ')[1].toLowerCase()}.${i}@teste.com`;
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO users (id, tenant_id, team_id, name, email, password_hash, role, status) VALUES (?, ?, ?, ?, ?, ?, ?, 'active')`,
|
||||
[userId, tenantId, teamId, name, email, 'dummy_hash_not_for_login', role]
|
||||
);
|
||||
users.push(userId);
|
||||
}
|
||||
console.log('✅ 36 usuários criados (5 managers, 31 agents).');
|
||||
|
||||
// 5. Gerar attendances (01/01/2026 até 22/04/2026)
|
||||
// 112 dias totais
|
||||
const startDate = new Date('2026-01-01T08:00:00Z');
|
||||
const endDate = new Date('2026-04-22T18:00:00Z');
|
||||
|
||||
let currentDay = new Date(startDate);
|
||||
const attendancesToInsert = [];
|
||||
|
||||
console.log('⏳ Gerando dados de attendances em memória...');
|
||||
while (currentDay <= endDate) {
|
||||
// Pular domingos para dar mais realismo, ou deixar todos os dias? Vamos deixar todos os dias.
|
||||
for (const userId of users) {
|
||||
const numAttendances = getRandomInt(3, 5); // 3 a 5 por dia por usuário
|
||||
for (let a = 0; a < numAttendances; a++) {
|
||||
const createdAt = new Date(currentDay);
|
||||
createdAt.setHours(getRandomInt(8, 17), getRandomInt(0, 59), getRandomInt(0, 59));
|
||||
|
||||
const isConverted = Math.random() > 0.7; // 30% conversão
|
||||
const reqProduct = getRandomItem(PRODUCTS);
|
||||
const soldProduct = isConverted ? reqProduct : null;
|
||||
|
||||
let score = getRandomInt(40, 100);
|
||||
if (isConverted) score = getRandomInt(85, 100);
|
||||
|
||||
attendancesToInsert.push([
|
||||
crypto.randomUUID(),
|
||||
tenantId,
|
||||
userId,
|
||||
isConverted ? "Venda efetuada com sucesso" : "Cliente não finalizou a compra",
|
||||
isConverted ? "Cliente comprou com sucesso. Excelente atendimento." : "Cliente achou o valor alto e desistiu.",
|
||||
score,
|
||||
getRandomInt(1, 45), // first_response_time_min
|
||||
getRandomInt(10, 120), // handling_time_min
|
||||
isConverted ? 'Ganhos' : (Math.random() > 0.5 ? 'Perdidos' : getRandomItem(funnels)), // funnel_stage
|
||||
getRandomItem(origins), // origin
|
||||
reqProduct,
|
||||
soldProduct,
|
||||
isConverted ? 1 : 0,
|
||||
JSON.stringify(isConverted ? [] : ["Faltou oferecer desconto", "Demora no primeiro contato"]),
|
||||
JSON.stringify(["Melhorar rapport inicial"]),
|
||||
createdAt
|
||||
]);
|
||||
}
|
||||
}
|
||||
currentDay.setDate(currentDay.getDate() + 1);
|
||||
}
|
||||
|
||||
// 6. Inserir em lotes
|
||||
console.log(`⏳ Inserindo ${attendancesToInsert.length} attendances no banco em lotes...`);
|
||||
const batchSize = 1000;
|
||||
for (let i = 0; i < attendancesToInsert.length; i += batchSize) {
|
||||
const batch = attendancesToInsert.slice(i, i + batchSize);
|
||||
await pool.query(
|
||||
`INSERT INTO attendances
|
||||
(id, tenant_id, user_id, title, full_summary, score, first_response_time_min, handling_time_min, funnel_stage, origin, product_requested, product_sold, converted, attention_points, improvement_points, created_at)
|
||||
VALUES ?`,
|
||||
[batch]
|
||||
);
|
||||
process.stdout.write(`\r✅ Inseridos ${Math.min(i + batchSize, attendancesToInsert.length)} / ${attendancesToInsert.length}`);
|
||||
}
|
||||
console.log('\n🎉 Todos os dados foram gerados com sucesso!');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Erro:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
Reference in New Issue
Block a user