Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
@@ -17,539 +17,545 @@
|
||||
* 5. Quando terminar, rode simulation-cleanup.sql para limpar
|
||||
*/
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { config } from './simulation.config.js'
|
||||
import { logInfo, logWarning, logError, getLog } from './simulationLogger.js'
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { config } from './simulation.config.js';
|
||||
import { logInfo, logWarning, logError, getLog } from './simulationLogger.js';
|
||||
|
||||
// ─── Validação ────────────────────────────────────────────────────────────────
|
||||
|
||||
if (config.OWNER_ID === 'SEU-OWNER-UUID-AQUI' || config.TENANT_ID === 'SEU-TENANT-UUID-AQUI') {
|
||||
console.error('\n❌ ERRO: Preencha OWNER_ID e TENANT_ID em scripts/simulation/simulation.config.js\n')
|
||||
process.exit(1)
|
||||
console.error('\n❌ ERRO: Preencha OWNER_ID e TENANT_ID em scripts/simulation/simulation.config.js\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// ─── Dados brasileiros falsos ─────────────────────────────────────────────────
|
||||
|
||||
const NOMES = [
|
||||
['Ana', 'Beatriz', 'Carla', 'Daniela', 'Fernanda', 'Gabriela', 'Helena', 'Isabela',
|
||||
'Juliana', 'Karen', 'Laura', 'Mariana', 'Natália', 'Olivia', 'Patrícia'],
|
||||
['Carlos', 'Daniel', 'Eduardo', 'Felipe', 'Gustavo', 'Henrique', 'Igor', 'João',
|
||||
'Lucas', 'Marcos', 'Nelson', 'Otávio', 'Paulo', 'Rafael', 'Sérgio'],
|
||||
]
|
||||
['Ana', 'Beatriz', 'Carla', 'Daniela', 'Fernanda', 'Gabriela', 'Helena', 'Isabela', 'Juliana', 'Karen', 'Laura', 'Mariana', 'Natália', 'Olivia', 'Patrícia'],
|
||||
['Carlos', 'Daniel', 'Eduardo', 'Felipe', 'Gustavo', 'Henrique', 'Igor', 'João', 'Lucas', 'Marcos', 'Nelson', 'Otávio', 'Paulo', 'Rafael', 'Sérgio']
|
||||
];
|
||||
|
||||
const SOBRENOMES = [
|
||||
'Silva', 'Santos', 'Oliveira', 'Souza', 'Lima', 'Ferreira', 'Rodrigues',
|
||||
'Almeida', 'Costa', 'Gomes', 'Martins', 'Pereira', 'Carvalho', 'Rocha', 'Nunes',
|
||||
]
|
||||
const SOBRENOMES = ['Silva', 'Santos', 'Oliveira', 'Souza', 'Lima', 'Ferreira', 'Rodrigues', 'Almeida', 'Costa', 'Gomes', 'Martins', 'Pereira', 'Carvalho', 'Rocha', 'Nunes'];
|
||||
|
||||
const MODALIDADES = ['presencial', 'online', 'presencial', 'presencial'] // ponderado
|
||||
const MODALIDADES = ['presencial', 'online', 'presencial', 'presencial']; // ponderado
|
||||
|
||||
// ─── Utilitários ─────────────────────────────────────────────────────────────
|
||||
|
||||
let _rng = 1
|
||||
let _rng = 1;
|
||||
|
||||
function rng () {
|
||||
// LCG determinístico para gerar sequência reproduzível
|
||||
_rng = (_rng * 1664525 + 1013904223) & 0xffffffff
|
||||
return Math.abs(_rng) / 0x80000000
|
||||
function rng() {
|
||||
// LCG determinístico para gerar sequência reproduzível
|
||||
_rng = (_rng * 1664525 + 1013904223) & 0xffffffff;
|
||||
return Math.abs(_rng) / 0x80000000;
|
||||
}
|
||||
|
||||
function pick (arr) { return arr[Math.floor(rng() * arr.length)] }
|
||||
|
||||
function uuid () {
|
||||
// gera UUID v4-like determinístico baseado no nosso rng
|
||||
const h = () => Math.floor(rng() * 0x10000).toString(16).padStart(4, '0')
|
||||
return `${h()}${h()}-${h()}-4${h().slice(1)}-${(8 + Math.floor(rng() * 4)).toString(16)}${h().slice(1)}-${h()}${h()}${h()}`
|
||||
function pick(arr) {
|
||||
return arr[Math.floor(rng() * arr.length)];
|
||||
}
|
||||
|
||||
function addDays (date, n) {
|
||||
const d = new Date(date)
|
||||
d.setDate(d.getDate() + n)
|
||||
return d
|
||||
function uuid() {
|
||||
// gera UUID v4-like determinístico baseado no nosso rng
|
||||
const h = () =>
|
||||
Math.floor(rng() * 0x10000)
|
||||
.toString(16)
|
||||
.padStart(4, '0');
|
||||
return `${h()}${h()}-${h()}-4${h().slice(1)}-${(8 + Math.floor(rng() * 4)).toString(16)}${h().slice(1)}-${h()}${h()}${h()}`;
|
||||
}
|
||||
|
||||
function toISO (date) {
|
||||
return date.toISOString().split('T')[0]
|
||||
function addDays(date, n) {
|
||||
const d = new Date(date);
|
||||
d.setDate(d.getDate() + n);
|
||||
return d;
|
||||
}
|
||||
|
||||
function toISODateTime (date, hh, mm) {
|
||||
return `${toISO(date)}T${String(hh).padStart(2,'0')}:${String(mm).padStart(2,'0')}:00`
|
||||
function toISO(date) {
|
||||
return date.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
function sqlStr (v) {
|
||||
if (v === null || v === undefined) return 'NULL'
|
||||
return `'${String(v).replace(/'/g, "''")}'`
|
||||
function toISODateTime(date, hh, mm) {
|
||||
return `${toISO(date)}T${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}:00`;
|
||||
}
|
||||
|
||||
function weekdayName (n) {
|
||||
return ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'][n]
|
||||
function sqlStr(v) {
|
||||
if (v === null || v === undefined) return 'NULL';
|
||||
return `'${String(v).replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
function weekdayName(n) {
|
||||
return ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'][n];
|
||||
}
|
||||
|
||||
// ─── Gerador de nomes únicos ──────────────────────────────────────────────────
|
||||
|
||||
const usedNames = new Set()
|
||||
const usedNames = new Set();
|
||||
|
||||
function fakeName () {
|
||||
let attempts = 0
|
||||
while (attempts < 50) {
|
||||
const gender = rng() > 0.5 ? 0 : 1
|
||||
const nome = pick(NOMES[gender])
|
||||
const sobrenome = pick(SOBRENOMES)
|
||||
const full = `${nome} ${sobrenome}`
|
||||
if (!usedNames.has(full)) {
|
||||
usedNames.add(full)
|
||||
return full
|
||||
function fakeName() {
|
||||
let attempts = 0;
|
||||
while (attempts < 50) {
|
||||
const gender = rng() > 0.5 ? 0 : 1;
|
||||
const nome = pick(NOMES[gender]);
|
||||
const sobrenome = pick(SOBRENOMES);
|
||||
const full = `${nome} ${sobrenome}`;
|
||||
if (!usedNames.has(full)) {
|
||||
usedNames.add(full);
|
||||
return full;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
attempts++
|
||||
}
|
||||
return `Paciente ${usedNames.size + 1}`
|
||||
return `Paciente ${usedNames.size + 1}`;
|
||||
}
|
||||
|
||||
function fakeCpf () {
|
||||
const n = () => Math.floor(rng() * 9) + 1
|
||||
return `${n()}${n()}${n()}${n()}${n()}${n()}${n()}${n()}${n()}${n()}${n()}`
|
||||
function fakeCpf() {
|
||||
const n = () => Math.floor(rng() * 9) + 1;
|
||||
return `${n()}${n()}${n()}${n()}${n()}${n()}${n()}${n()}${n()}${n()}${n()}`;
|
||||
}
|
||||
|
||||
function fakePhone () {
|
||||
const ddd = [11,21,31,41,51,61,71,81,91][Math.floor(rng() * 9)]
|
||||
const num = Math.floor(rng() * 900000000) + 900000000
|
||||
return `(${ddd}) 9${String(num).slice(1,5)}-${String(num).slice(5,9)}`
|
||||
function fakePhone() {
|
||||
const ddd = [11, 21, 31, 41, 51, 61, 71, 81, 91][Math.floor(rng() * 9)];
|
||||
const num = Math.floor(rng() * 900000000) + 900000000;
|
||||
return `(${ddd}) 9${String(num).slice(1, 5)}-${String(num).slice(5, 9)}`;
|
||||
}
|
||||
|
||||
function fakeEmail (nome) {
|
||||
const slug = nome.toLowerCase()
|
||||
.replace(/\s+/g, '.')
|
||||
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
||||
const num = Math.floor(rng() * 99) + 1
|
||||
const domains = ['gmail.com','hotmail.com','outlook.com','yahoo.com.br']
|
||||
return `${slug}${num}@${pick(domains)}`
|
||||
function fakeEmail(nome) {
|
||||
const slug = nome
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '.')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '');
|
||||
const num = Math.floor(rng() * 99) + 1;
|
||||
const domains = ['gmail.com', 'hotmail.com', 'outlook.com', 'yahoo.com.br'];
|
||||
return `${slug}${num}@${pick(domains)}`;
|
||||
}
|
||||
|
||||
// ─── Blocos SQL ───────────────────────────────────────────────────────────────
|
||||
|
||||
const seedLines = []
|
||||
const seedLines = [];
|
||||
const cleanupIds = {
|
||||
patients: [],
|
||||
agendaEventos: [],
|
||||
recurrenceRules: [],
|
||||
recurrenceExceptions: [],
|
||||
agendadorSolicitacoes: [],
|
||||
agendaConfiguracoes: [],
|
||||
agendaRegrasSemanais: [],
|
||||
}
|
||||
patients: [],
|
||||
agendaEventos: [],
|
||||
recurrenceRules: [],
|
||||
recurrenceExceptions: [],
|
||||
agendadorSolicitacoes: [],
|
||||
agendaConfiguracoes: [],
|
||||
agendaRegrasSemanais: []
|
||||
};
|
||||
|
||||
function emit (line) { seedLines.push(line) }
|
||||
function emit(line) {
|
||||
seedLines.push(line);
|
||||
}
|
||||
|
||||
// ─── Construção da simulação ──────────────────────────────────────────────────
|
||||
|
||||
const TODAY = new Date()
|
||||
TODAY.setHours(0, 0, 0, 0)
|
||||
const PAST_START = addDays(TODAY, -config.DAYS_BACK)
|
||||
const FUTURE_END = addDays(TODAY, config.SIMULATION_DAYS)
|
||||
const TODAY = new Date();
|
||||
TODAY.setHours(0, 0, 0, 0);
|
||||
const PAST_START = addDays(TODAY, -config.DAYS_BACK);
|
||||
const FUTURE_END = addDays(TODAY, config.SIMULATION_DAYS);
|
||||
|
||||
const ownerId = config.OWNER_ID
|
||||
const tenantId = config.TENANT_ID
|
||||
const ownerId = config.OWNER_ID;
|
||||
const tenantId = config.TENANT_ID;
|
||||
|
||||
logInfo('Iniciando simulação...')
|
||||
logInfo(`OWNER_ID: ${ownerId}`)
|
||||
logInfo(`TENANT_ID: ${tenantId}`)
|
||||
logInfo(`Período: ${toISO(PAST_START)} → ${toISO(FUTURE_END)}`)
|
||||
logInfo('Iniciando simulação...');
|
||||
logInfo(`OWNER_ID: ${ownerId}`);
|
||||
logInfo(`TENANT_ID: ${tenantId}`);
|
||||
logInfo(`Período: ${toISO(PAST_START)} → ${toISO(FUTURE_END)}`);
|
||||
|
||||
// ─── 1. Pacientes ─────────────────────────────────────────────────────────────
|
||||
|
||||
emit('-- ============================================================')
|
||||
emit('-- SIMULAÇÃO AgenciaPsi — gerado por npm run simulate')
|
||||
emit(`-- Data: ${new Date().toLocaleString('pt-BR')}`)
|
||||
emit('-- ============================================================')
|
||||
emit('')
|
||||
emit('-- Desabilita triggers para permitir inserts de simulação')
|
||||
emit('SET session_replication_role = replica;')
|
||||
emit('')
|
||||
emit('BEGIN;')
|
||||
emit('')
|
||||
emit('-- ─── 1. Pacientes ───────────────────────────────────────────────────────────')
|
||||
emit('')
|
||||
emit('-- ============================================================');
|
||||
emit('-- SIMULAÇÃO AgenciaPsi — gerado por npm run simulate');
|
||||
emit(`-- Data: ${new Date().toLocaleString('pt-BR')}`);
|
||||
emit('-- ============================================================');
|
||||
emit('');
|
||||
emit('-- Desabilita triggers para permitir inserts de simulação');
|
||||
emit('SET session_replication_role = replica;');
|
||||
emit('');
|
||||
emit('BEGIN;');
|
||||
emit('');
|
||||
emit('-- ─── 1. Pacientes ───────────────────────────────────────────────────────────');
|
||||
emit('');
|
||||
|
||||
const patients = []
|
||||
const patients = [];
|
||||
|
||||
for (let i = 0; i < config.PATIENTS_COUNT; i++) {
|
||||
const id = uuid()
|
||||
const nome = fakeName()
|
||||
const parts = nome.split(' ')
|
||||
const email = fakeEmail(nome)
|
||||
const tel = fakePhone()
|
||||
const cpf = fakeCpf()
|
||||
const id = uuid();
|
||||
const nome = fakeName();
|
||||
const parts = nome.split(' ');
|
||||
const email = fakeEmail(nome);
|
||||
const tel = fakePhone();
|
||||
const cpf = fakeCpf();
|
||||
|
||||
patients.push({ id, nome_completo: nome, email, telefone: tel })
|
||||
cleanupIds.patients.push(id)
|
||||
patients.push({ id, nome_completo: nome, email, telefone: tel });
|
||||
cleanupIds.patients.push(id);
|
||||
|
||||
emit(`INSERT INTO patients (id, tenant_id, owner_id, responsible_member_id, nome_completo, email_principal, telefone, cpf, patient_scope, status)`)
|
||||
emit(` VALUES (${sqlStr(id)}, ${sqlStr(tenantId)}, ${sqlStr(ownerId)},`)
|
||||
emit(` (SELECT id FROM tenant_members WHERE user_id = ${sqlStr(ownerId)} AND tenant_id = ${sqlStr(tenantId)} LIMIT 1),`)
|
||||
emit(` ${sqlStr(nome)}, ${sqlStr(email)}, ${sqlStr(tel)}, ${sqlStr(cpf)}, 'clinic', 'Ativo');`)
|
||||
emit(`INSERT INTO patients (id, tenant_id, owner_id, responsible_member_id, nome_completo, email_principal, telefone, cpf, patient_scope, status)`);
|
||||
emit(` VALUES (${sqlStr(id)}, ${sqlStr(tenantId)}, ${sqlStr(ownerId)},`);
|
||||
emit(` (SELECT id FROM tenant_members WHERE user_id = ${sqlStr(ownerId)} AND tenant_id = ${sqlStr(tenantId)} LIMIT 1),`);
|
||||
emit(` ${sqlStr(nome)}, ${sqlStr(email)}, ${sqlStr(tel)}, ${sqlStr(cpf)}, 'clinic', 'Ativo');`);
|
||||
}
|
||||
|
||||
logInfo(`✔ ${patients.length} pacientes criados`)
|
||||
emit('')
|
||||
logInfo(`✔ ${patients.length} pacientes criados`);
|
||||
emit('');
|
||||
|
||||
// ─── 2. Configurações de agenda ───────────────────────────────────────────────
|
||||
|
||||
emit('-- ─── 2. Configurações de agenda ─────────────────────────────────────────────')
|
||||
emit('')
|
||||
emit('-- ─── 2. Configurações de agenda ─────────────────────────────────────────────');
|
||||
emit('');
|
||||
|
||||
const pausas = [
|
||||
{ weekday: 1, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
{ weekday: 2, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
{ weekday: 3, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
{ weekday: 4, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
{ weekday: 5, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
]
|
||||
{ weekday: 1, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
{ weekday: 2, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
{ weekday: 3, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
{ weekday: 4, start: '12:00', end: '13:00', label: 'Almoço' },
|
||||
{ weekday: 5, start: '12:00', end: '13:00', label: 'Almoço' }
|
||||
];
|
||||
|
||||
emit(`INSERT INTO agenda_configuracoes (owner_id, tenant_id, session_duration_min, session_break_min, pausas_semanais, online_ativo, setup_clinica_concluido)`)
|
||||
emit(` VALUES (${sqlStr(ownerId)}, ${sqlStr(tenantId)},`)
|
||||
emit(` ${config.SESSION_DURATION_MIN}, 10, '${JSON.stringify(pausas)}'::jsonb, true, true)`)
|
||||
emit(` ON CONFLICT (owner_id) DO NOTHING;`)
|
||||
emit('')
|
||||
emit(`INSERT INTO agenda_configuracoes (owner_id, tenant_id, session_duration_min, session_break_min, pausas_semanais, online_ativo, setup_clinica_concluido)`);
|
||||
emit(` VALUES (${sqlStr(ownerId)}, ${sqlStr(tenantId)},`);
|
||||
emit(` ${config.SESSION_DURATION_MIN}, 10, '${JSON.stringify(pausas)}'::jsonb, true, true)`);
|
||||
emit(` ON CONFLICT (owner_id) DO NOTHING;`);
|
||||
emit('');
|
||||
|
||||
logInfo('✔ agenda_configuracoes inserida')
|
||||
logInfo('✔ agenda_configuracoes inserida');
|
||||
|
||||
// ─── 3. Regras semanais de disponibilidade ────────────────────────────────────
|
||||
|
||||
emit('-- ─── 3. Regras semanais ─────────────────────────────────────────────────────')
|
||||
emit('')
|
||||
emit('-- ─── 3. Regras semanais ─────────────────────────────────────────────────────');
|
||||
emit('');
|
||||
|
||||
const workDays = [1, 2, 3, 4, 5] // seg–sex
|
||||
const workDays = [1, 2, 3, 4, 5]; // seg–sex
|
||||
for (const dia of workDays) {
|
||||
const rid = uuid()
|
||||
cleanupIds.agendaRegrasSemanais.push(rid)
|
||||
emit(`INSERT INTO agenda_regras_semanais (id, owner_id, tenant_id, dia_semana, hora_inicio, hora_fim, ativo)`)
|
||||
emit(` VALUES (${sqlStr(rid)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${dia}, '08:00', '18:00', true);`)
|
||||
const rid = uuid();
|
||||
cleanupIds.agendaRegrasSemanais.push(rid);
|
||||
emit(`INSERT INTO agenda_regras_semanais (id, owner_id, tenant_id, dia_semana, hora_inicio, hora_fim, ativo)`);
|
||||
emit(` VALUES (${sqlStr(rid)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${dia}, '08:00', '18:00', true);`);
|
||||
}
|
||||
|
||||
logInfo(`✔ ${workDays.length} regras semanais inseridas`)
|
||||
emit('')
|
||||
logInfo(`✔ ${workDays.length} regras semanais inseridas`);
|
||||
emit('');
|
||||
|
||||
// ─── 4. Eventos avulsos (passado) ─────────────────────────────────────────────
|
||||
|
||||
emit('-- ─── 4. Eventos avulsos (passado) ──────────────────────────────────────────')
|
||||
emit('')
|
||||
emit('-- ─── 4. Eventos avulsos (passado) ──────────────────────────────────────────');
|
||||
emit('');
|
||||
|
||||
const avulsoCount = 3
|
||||
const avulsoPatients = patients.slice(0, avulsoCount)
|
||||
let avulsosCreated = 0
|
||||
const avulsoCount = 3;
|
||||
const avulsoPatients = patients.slice(0, avulsoCount);
|
||||
let avulsosCreated = 0;
|
||||
|
||||
// Avulsos usam 13h para não conflitar com séries (9h–12h e 14h–17h)
|
||||
for (const pat of avulsoPatients) {
|
||||
const daysAgo = Math.floor(rng() * config.DAYS_BACK) + 1
|
||||
const date = addDays(TODAY, -daysAgo)
|
||||
if (date.getDay() === 0 || date.getDay() === 6) continue // pular fds
|
||||
const daysAgo = Math.floor(rng() * config.DAYS_BACK) + 1;
|
||||
const date = addDays(TODAY, -daysAgo);
|
||||
if (date.getDay() === 0 || date.getDay() === 6) continue; // pular fds
|
||||
|
||||
const hour = 13
|
||||
const startDT = toISODateTime(date, hour, 0)
|
||||
const endDT = toISODateTime(date, hour, 50)
|
||||
const evId = uuid()
|
||||
const modal = pick(MODALIDADES)
|
||||
const statuses = ['realizado', 'faltou', 'agendado']
|
||||
const status = pick(statuses)
|
||||
const hour = 13;
|
||||
const startDT = toISODateTime(date, hour, 0);
|
||||
const endDT = toISODateTime(date, hour, 50);
|
||||
const evId = uuid();
|
||||
const modal = pick(MODALIDADES);
|
||||
const statuses = ['realizado', 'faltou', 'agendado'];
|
||||
const status = pick(statuses);
|
||||
|
||||
cleanupIds.agendaEventos.push(evId)
|
||||
cleanupIds.agendaEventos.push(evId);
|
||||
|
||||
emit(`INSERT INTO agenda_eventos (id, owner_id, tenant_id, patient_id, tipo, status, inicio_em, fim_em, modalidade, titulo)`)
|
||||
emit(` VALUES (${sqlStr(evId)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${sqlStr(pat.id)},`)
|
||||
emit(` 'sessao', ${sqlStr(status)}, ${sqlStr(startDT)}, ${sqlStr(endDT)}, ${sqlStr(modal)}, 'Sessão avulsa');`)
|
||||
emit(`INSERT INTO agenda_eventos (id, owner_id, tenant_id, patient_id, tipo, status, inicio_em, fim_em, modalidade, titulo)`);
|
||||
emit(` VALUES (${sqlStr(evId)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${sqlStr(pat.id)},`);
|
||||
emit(` 'sessao', ${sqlStr(status)}, ${sqlStr(startDT)}, ${sqlStr(endDT)}, ${sqlStr(modal)}, 'Sessão avulsa');`);
|
||||
|
||||
avulsosCreated++
|
||||
avulsosCreated++;
|
||||
}
|
||||
|
||||
logInfo(`✔ ${avulsosCreated} eventos avulsos criados`)
|
||||
emit('')
|
||||
logInfo(`✔ ${avulsosCreated} eventos avulsos criados`);
|
||||
emit('');
|
||||
|
||||
// ─── 5. Séries de recorrência ─────────────────────────────────────────────────
|
||||
|
||||
emit('-- ─── 5. Séries de recorrência ───────────────────────────────────────────────')
|
||||
emit('')
|
||||
emit('-- ─── 5. Séries de recorrência ───────────────────────────────────────────────');
|
||||
emit('');
|
||||
|
||||
const ruleStartDate = addDays(TODAY, -config.DAYS_BACK)
|
||||
const ruleEndDate = addDays(TODAY, config.SIMULATION_DAYS)
|
||||
const ruleStartDate = addDays(TODAY, -config.DAYS_BACK);
|
||||
const ruleEndDate = addDays(TODAY, config.SIMULATION_DAYS);
|
||||
|
||||
// Distribui pacientes pelas séries (cicla se menos pacientes que séries)
|
||||
const seriesPatients = patients.slice(0, config.RECURRENCE_RULES_COUNT)
|
||||
const types = ['weekly', 'weekly', 'weekly', 'biweekly', 'custom_weekdays', 'weekly']
|
||||
const wdays = [[1], [2], [4], [1], [1,3], [3]]
|
||||
const seriesPatients = patients.slice(0, config.RECURRENCE_RULES_COUNT);
|
||||
const types = ['weekly', 'weekly', 'weekly', 'biweekly', 'custom_weekdays', 'weekly'];
|
||||
const wdays = [[1], [2], [4], [1], [1, 3], [3]];
|
||||
// Cada série tem seu próprio horário para evitar sobreposição
|
||||
const ruleHours = [9, 10, 11, 14, 15, 16]
|
||||
const ruleHours = [9, 10, 11, 14, 15, 16];
|
||||
|
||||
const recurrenceRules = []
|
||||
const recurrenceRules = [];
|
||||
|
||||
for (let i = 0; i < config.RECURRENCE_RULES_COUNT; i++) {
|
||||
const pat = seriesPatients[i % seriesPatients.length]
|
||||
const type = types[i] || 'weekly'
|
||||
const weekdays = wdays[i] || [1]
|
||||
const modal = pick(MODALIDADES)
|
||||
const ruleId = uuid()
|
||||
const hour = ruleHours[i] || (9 + i)
|
||||
const startTime = `${String(hour).padStart(2,'0')}:00`
|
||||
const endTime = `${String(hour).padStart(2,'0')}:50`
|
||||
const weekdaysArr = `ARRAY[${weekdays.join(',')}]::smallint[]`
|
||||
const pat = seriesPatients[i % seriesPatients.length];
|
||||
const type = types[i] || 'weekly';
|
||||
const weekdays = wdays[i] || [1];
|
||||
const modal = pick(MODALIDADES);
|
||||
const ruleId = uuid();
|
||||
const hour = ruleHours[i] || 9 + i;
|
||||
const startTime = `${String(hour).padStart(2, '0')}:00`;
|
||||
const endTime = `${String(hour).padStart(2, '0')}:50`;
|
||||
const weekdaysArr = `ARRAY[${weekdays.join(',')}]::smallint[]`;
|
||||
|
||||
recurrenceRules.push({ id: ruleId, patient: pat, type, weekdays, start_date: ruleStartDate, modal, hour })
|
||||
cleanupIds.recurrenceRules.push(ruleId)
|
||||
recurrenceRules.push({ id: ruleId, patient: pat, type, weekdays, start_date: ruleStartDate, modal, hour });
|
||||
cleanupIds.recurrenceRules.push(ruleId);
|
||||
|
||||
emit(`INSERT INTO recurrence_rules (id, owner_id, tenant_id, patient_id, type, weekdays, interval, start_date, end_date, status, start_time, end_time, modalidade)`)
|
||||
emit(` VALUES (${sqlStr(ruleId)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${sqlStr(pat.id)},`)
|
||||
emit(` ${sqlStr(type)}, ${weekdaysArr}, 1, ${sqlStr(toISO(ruleStartDate))}, ${sqlStr(toISO(ruleEndDate))},`)
|
||||
emit(` 'ativo', ${sqlStr(startTime)}, ${sqlStr(endTime)}, ${sqlStr(modal)});`)
|
||||
emit(`INSERT INTO recurrence_rules (id, owner_id, tenant_id, patient_id, type, weekdays, interval, start_date, end_date, status, start_time, end_time, modalidade)`);
|
||||
emit(` VALUES (${sqlStr(ruleId)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${sqlStr(pat.id)},`);
|
||||
emit(` ${sqlStr(type)}, ${weekdaysArr}, 1, ${sqlStr(toISO(ruleStartDate))}, ${sqlStr(toISO(ruleEndDate))},`);
|
||||
emit(` 'ativo', ${sqlStr(startTime)}, ${sqlStr(endTime)}, ${sqlStr(modal)});`);
|
||||
}
|
||||
|
||||
logInfo(`✔ ${recurrenceRules.length} regras de recorrência criadas`)
|
||||
emit('')
|
||||
logInfo(`✔ ${recurrenceRules.length} regras de recorrência criadas`);
|
||||
emit('');
|
||||
|
||||
// ─── 6. Exceções de recorrência (passado) ────────────────────────────────────
|
||||
|
||||
emit('-- ─── 6. Exceções de recorrência ────────────────────────────────────────────')
|
||||
emit('')
|
||||
emit('-- ─── 6. Exceções de recorrência ────────────────────────────────────────────');
|
||||
emit('');
|
||||
|
||||
let excFaltou = 0, excRemarcado = 0, excCancelado = 0
|
||||
let excFaltou = 0,
|
||||
excRemarcado = 0,
|
||||
excCancelado = 0;
|
||||
|
||||
for (const rr of recurrenceRules) {
|
||||
// Gera datas das ocorrências passadas
|
||||
const occs = []
|
||||
let cur = new Date(rr.start_date)
|
||||
// avançar para o primeiro dia correto
|
||||
while (cur <= TODAY) {
|
||||
const dow = cur.getDay()
|
||||
if (rr.weekdays.includes(dow)) {
|
||||
if (cur < TODAY) occs.push(new Date(cur))
|
||||
// Gera datas das ocorrências passadas
|
||||
const occs = [];
|
||||
let cur = new Date(rr.start_date);
|
||||
// avançar para o primeiro dia correto
|
||||
while (cur <= TODAY) {
|
||||
const dow = cur.getDay();
|
||||
if (rr.weekdays.includes(dow)) {
|
||||
if (cur < TODAY) occs.push(new Date(cur));
|
||||
}
|
||||
cur = addDays(cur, 1);
|
||||
}
|
||||
cur = addDays(cur, 1)
|
||||
}
|
||||
|
||||
for (const occDate of occs) {
|
||||
const r = rng()
|
||||
const dateStr = toISO(occDate)
|
||||
for (const occDate of occs) {
|
||||
const r = rng();
|
||||
const dateStr = toISO(occDate);
|
||||
|
||||
if (r < config.RATE_FALTOU) {
|
||||
const excId = uuid()
|
||||
cleanupIds.recurrenceExceptions.push(excId)
|
||||
emit(`INSERT INTO recurrence_exceptions (id, recurrence_id, tenant_id, original_date, type, new_date)`)
|
||||
emit(` VALUES (${sqlStr(excId)}, ${sqlStr(rr.id)}, ${sqlStr(tenantId)}, ${sqlStr(dateStr)}, 'patient_missed', NULL);`)
|
||||
excFaltou++
|
||||
} else if (r < config.RATE_FALTOU + config.RATE_REMARCADO) {
|
||||
const offset = Math.floor(rng() * 4) + 1
|
||||
const newDate = addDays(occDate, offset)
|
||||
// não remarcar para fds
|
||||
if (newDate.getDay() === 0 || newDate.getDay() === 6) continue
|
||||
const excId = uuid()
|
||||
cleanupIds.recurrenceExceptions.push(excId)
|
||||
emit(`INSERT INTO recurrence_exceptions (id, recurrence_id, tenant_id, original_date, type, new_date)`)
|
||||
emit(` VALUES (${sqlStr(excId)}, ${sqlStr(rr.id)}, ${sqlStr(tenantId)}, ${sqlStr(dateStr)}, 'reschedule_session', ${sqlStr(toISO(newDate))});`)
|
||||
excRemarcado++
|
||||
} else if (r < config.RATE_FALTOU + config.RATE_REMARCADO + config.RATE_CANCELADO) {
|
||||
const excId = uuid()
|
||||
cleanupIds.recurrenceExceptions.push(excId)
|
||||
emit(`INSERT INTO recurrence_exceptions (id, recurrence_id, tenant_id, original_date, type, new_date)`)
|
||||
emit(` VALUES (${sqlStr(excId)}, ${sqlStr(rr.id)}, ${sqlStr(tenantId)}, ${sqlStr(dateStr)}, 'cancel_session', NULL);`)
|
||||
excCancelado++
|
||||
if (r < config.RATE_FALTOU) {
|
||||
const excId = uuid();
|
||||
cleanupIds.recurrenceExceptions.push(excId);
|
||||
emit(`INSERT INTO recurrence_exceptions (id, recurrence_id, tenant_id, original_date, type, new_date)`);
|
||||
emit(` VALUES (${sqlStr(excId)}, ${sqlStr(rr.id)}, ${sqlStr(tenantId)}, ${sqlStr(dateStr)}, 'patient_missed', NULL);`);
|
||||
excFaltou++;
|
||||
} else if (r < config.RATE_FALTOU + config.RATE_REMARCADO) {
|
||||
const offset = Math.floor(rng() * 4) + 1;
|
||||
const newDate = addDays(occDate, offset);
|
||||
// não remarcar para fds
|
||||
if (newDate.getDay() === 0 || newDate.getDay() === 6) continue;
|
||||
const excId = uuid();
|
||||
cleanupIds.recurrenceExceptions.push(excId);
|
||||
emit(`INSERT INTO recurrence_exceptions (id, recurrence_id, tenant_id, original_date, type, new_date)`);
|
||||
emit(` VALUES (${sqlStr(excId)}, ${sqlStr(rr.id)}, ${sqlStr(tenantId)}, ${sqlStr(dateStr)}, 'reschedule_session', ${sqlStr(toISO(newDate))});`);
|
||||
excRemarcado++;
|
||||
} else if (r < config.RATE_FALTOU + config.RATE_REMARCADO + config.RATE_CANCELADO) {
|
||||
const excId = uuid();
|
||||
cleanupIds.recurrenceExceptions.push(excId);
|
||||
emit(`INSERT INTO recurrence_exceptions (id, recurrence_id, tenant_id, original_date, type, new_date)`);
|
||||
emit(` VALUES (${sqlStr(excId)}, ${sqlStr(rr.id)}, ${sqlStr(tenantId)}, ${sqlStr(dateStr)}, 'cancel_session', NULL);`);
|
||||
excCancelado++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logInfo(`✔ Exceções: ${excFaltou} faltou, ${excRemarcado} remarcado, ${excCancelado} cancelado`)
|
||||
emit('')
|
||||
logInfo(`✔ Exceções: ${excFaltou} faltou, ${excRemarcado} remarcado, ${excCancelado} cancelado`);
|
||||
emit('');
|
||||
|
||||
// ─── 7. Sessões reais para ocorrências passadas (realizado/faltou) ────────────
|
||||
|
||||
emit('-- ─── 7. Sessões reais (passado — realizado/faltou) ────────────────────────')
|
||||
emit('')
|
||||
emit('-- ─── 7. Sessões reais (passado — realizado/faltou) ────────────────────────');
|
||||
emit('');
|
||||
|
||||
let realSessionsCreated = 0
|
||||
let realSessionsCreated = 0;
|
||||
|
||||
for (const rr of recurrenceRules) {
|
||||
let cur2 = new Date(rr.start_date)
|
||||
while (cur2 < TODAY) {
|
||||
const dow = cur2.getDay()
|
||||
if (rr.weekdays.includes(dow)) {
|
||||
const dateStr = toISO(cur2)
|
||||
const evId = uuid()
|
||||
const hh = String(rr.hour).padStart(2, '0')
|
||||
const startDT = `${dateStr}T${hh}:00:00`
|
||||
const endDT = `${dateStr}T${hh}:50:00`
|
||||
// status baseado nas exceções: se há exceção faltou → faltou, else → realizado
|
||||
// simplificado: 80% realizado, 20% faltou para sessões passadas
|
||||
const status = rng() < 0.8 ? 'realizado' : 'faltou'
|
||||
let cur2 = new Date(rr.start_date);
|
||||
while (cur2 < TODAY) {
|
||||
const dow = cur2.getDay();
|
||||
if (rr.weekdays.includes(dow)) {
|
||||
const dateStr = toISO(cur2);
|
||||
const evId = uuid();
|
||||
const hh = String(rr.hour).padStart(2, '0');
|
||||
const startDT = `${dateStr}T${hh}:00:00`;
|
||||
const endDT = `${dateStr}T${hh}:50:00`;
|
||||
// status baseado nas exceções: se há exceção faltou → faltou, else → realizado
|
||||
// simplificado: 80% realizado, 20% faltou para sessões passadas
|
||||
const status = rng() < 0.8 ? 'realizado' : 'faltou';
|
||||
|
||||
cleanupIds.agendaEventos.push(evId)
|
||||
cleanupIds.agendaEventos.push(evId);
|
||||
|
||||
emit(`INSERT INTO agenda_eventos (id, owner_id, tenant_id, patient_id, tipo, status, inicio_em, fim_em, modalidade, recurrence_id, recurrence_date)`)
|
||||
emit(` VALUES (${sqlStr(evId)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${sqlStr(rr.patient.id)},`)
|
||||
emit(` 'sessao', ${sqlStr(status)}, ${sqlStr(startDT)}, ${sqlStr(endDT)}, ${sqlStr(rr.modal)}, ${sqlStr(rr.id)}, ${sqlStr(dateStr)});`)
|
||||
emit(`INSERT INTO agenda_eventos (id, owner_id, tenant_id, patient_id, tipo, status, inicio_em, fim_em, modalidade, recurrence_id, recurrence_date)`);
|
||||
emit(` VALUES (${sqlStr(evId)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${sqlStr(rr.patient.id)},`);
|
||||
emit(` 'sessao', ${sqlStr(status)}, ${sqlStr(startDT)}, ${sqlStr(endDT)}, ${sqlStr(rr.modal)}, ${sqlStr(rr.id)}, ${sqlStr(dateStr)});`);
|
||||
|
||||
realSessionsCreated++
|
||||
realSessionsCreated++;
|
||||
}
|
||||
cur2 = addDays(cur2, 1);
|
||||
}
|
||||
cur2 = addDays(cur2, 1)
|
||||
}
|
||||
}
|
||||
|
||||
logInfo(`✔ ${realSessionsCreated} sessões reais (passado) criadas`)
|
||||
emit('')
|
||||
logInfo(`✔ ${realSessionsCreated} sessões reais (passado) criadas`);
|
||||
emit('');
|
||||
|
||||
// ─── 8. Solicitações do Agendador Público ─────────────────────────────────────
|
||||
|
||||
emit('-- ─── 8. Agendador Público — solicitações pendentes ─────────────────────────')
|
||||
emit('')
|
||||
emit('-- ─── 8. Agendador Público — solicitações pendentes ─────────────────────────');
|
||||
emit('');
|
||||
|
||||
const agendadorStatuses = ['pendente', 'pendente', 'pendente', 'autorizado', 'recusado']
|
||||
const agendadorStatuses = ['pendente', 'pendente', 'pendente', 'autorizado', 'recusado'];
|
||||
|
||||
for (let i = 0; i < config.AGENDADOR_REQUESTS_COUNT; i++) {
|
||||
const nome = fakeName()
|
||||
const parts2 = nome.split(' ')
|
||||
const primeiro = parts2[0]
|
||||
const sobrenome = parts2.slice(1).join(' ')
|
||||
const email = fakeEmail(nome)
|
||||
const cel = fakePhone()
|
||||
const daysAhead = Math.floor(rng() * 14) + 1
|
||||
const reqDate = addDays(TODAY, daysAhead)
|
||||
if (reqDate.getDay() === 0 || reqDate.getDay() === 6) continue
|
||||
const hour = config.WORK_HOUR_START + Math.floor(rng() * 6)
|
||||
const hora = `${String(hour).padStart(2,'0')}:00`
|
||||
const modal = pick(MODALIDADES)
|
||||
const status = pick(agendadorStatuses)
|
||||
const solId = uuid()
|
||||
const nome = fakeName();
|
||||
const parts2 = nome.split(' ');
|
||||
const primeiro = parts2[0];
|
||||
const sobrenome = parts2.slice(1).join(' ');
|
||||
const email = fakeEmail(nome);
|
||||
const cel = fakePhone();
|
||||
const daysAhead = Math.floor(rng() * 14) + 1;
|
||||
const reqDate = addDays(TODAY, daysAhead);
|
||||
if (reqDate.getDay() === 0 || reqDate.getDay() === 6) continue;
|
||||
const hour = config.WORK_HOUR_START + Math.floor(rng() * 6);
|
||||
const hora = `${String(hour).padStart(2, '0')}:00`;
|
||||
const modal = pick(MODALIDADES);
|
||||
const status = pick(agendadorStatuses);
|
||||
const solId = uuid();
|
||||
|
||||
cleanupIds.agendadorSolicitacoes.push(solId)
|
||||
cleanupIds.agendadorSolicitacoes.push(solId);
|
||||
|
||||
emit(`INSERT INTO agendador_solicitacoes (id, owner_id, tenant_id, paciente_nome, paciente_sobrenome, paciente_email, paciente_celular, tipo, modalidade, data_solicitada, hora_solicitada, status)`)
|
||||
emit(` VALUES (${sqlStr(solId)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${sqlStr(primeiro)}, ${sqlStr(sobrenome)}, ${sqlStr(email)}, ${sqlStr(cel)},`)
|
||||
emit(` ${sqlStr(pick(['primeira', 'retorno', 'reagendar']))}, ${sqlStr(modal)}, ${sqlStr(toISO(reqDate))}, ${sqlStr(hora)}, ${sqlStr(status)});`)
|
||||
emit(`INSERT INTO agendador_solicitacoes (id, owner_id, tenant_id, paciente_nome, paciente_sobrenome, paciente_email, paciente_celular, tipo, modalidade, data_solicitada, hora_solicitada, status)`);
|
||||
emit(` VALUES (${sqlStr(solId)}, ${sqlStr(ownerId)}, ${sqlStr(tenantId)}, ${sqlStr(primeiro)}, ${sqlStr(sobrenome)}, ${sqlStr(email)}, ${sqlStr(cel)},`);
|
||||
emit(` ${sqlStr(pick(['primeira', 'retorno', 'reagendar']))}, ${sqlStr(modal)}, ${sqlStr(toISO(reqDate))}, ${sqlStr(hora)}, ${sqlStr(status)});`);
|
||||
}
|
||||
|
||||
logInfo(`✔ ${config.AGENDADOR_REQUESTS_COUNT} solicitações do agendador criadas`)
|
||||
emit('')
|
||||
logInfo(`✔ ${config.AGENDADOR_REQUESTS_COUNT} solicitações do agendador criadas`);
|
||||
emit('');
|
||||
|
||||
// ─── 9. Fechar transação ──────────────────────────────────────────────────────
|
||||
|
||||
emit('COMMIT;')
|
||||
emit('')
|
||||
emit('-- Restaura comportamento normal dos triggers')
|
||||
emit('SET session_replication_role = DEFAULT;')
|
||||
emit('')
|
||||
emit('-- ─── Fim do seed ────────────────────────────────────────────────────────────')
|
||||
emit('COMMIT;');
|
||||
emit('');
|
||||
emit('-- Restaura comportamento normal dos triggers');
|
||||
emit('SET session_replication_role = DEFAULT;');
|
||||
emit('');
|
||||
emit('-- ─── Fim do seed ────────────────────────────────────────────────────────────');
|
||||
|
||||
// ─── Gerar SQL de cleanup ────────────────────────────────────────────────────
|
||||
|
||||
function buildCleanupSQL () {
|
||||
const lines = []
|
||||
lines.push('-- ============================================================')
|
||||
lines.push('-- CLEANUP — remove dados da simulação AgenciaPsi')
|
||||
lines.push(`-- Data: ${new Date().toLocaleString('pt-BR')}`)
|
||||
lines.push('-- ============================================================')
|
||||
lines.push('')
|
||||
lines.push('SET session_replication_role = replica;')
|
||||
lines.push('')
|
||||
lines.push('BEGIN;')
|
||||
lines.push('')
|
||||
function buildCleanupSQL() {
|
||||
const lines = [];
|
||||
lines.push('-- ============================================================');
|
||||
lines.push('-- CLEANUP — remove dados da simulação AgenciaPsi');
|
||||
lines.push(`-- Data: ${new Date().toLocaleString('pt-BR')}`);
|
||||
lines.push('-- ============================================================');
|
||||
lines.push('');
|
||||
lines.push('SET session_replication_role = replica;');
|
||||
lines.push('');
|
||||
lines.push('BEGIN;');
|
||||
lines.push('');
|
||||
|
||||
function delBlock (table, ids) {
|
||||
if (ids.length === 0) return
|
||||
const quoted = ids.map(id => `'${id}'`).join(', ')
|
||||
lines.push(`DELETE FROM ${table} WHERE id IN (${quoted});`)
|
||||
}
|
||||
function delBlock(table, ids) {
|
||||
if (ids.length === 0) return;
|
||||
const quoted = ids.map((id) => `'${id}'`).join(', ');
|
||||
lines.push(`DELETE FROM ${table} WHERE id IN (${quoted});`);
|
||||
}
|
||||
|
||||
delBlock('recurrence_exceptions', cleanupIds.recurrenceExceptions)
|
||||
lines.push('')
|
||||
delBlock('agenda_eventos', cleanupIds.agendaEventos)
|
||||
lines.push('')
|
||||
delBlock('recurrence_rules', cleanupIds.recurrenceRules)
|
||||
lines.push('')
|
||||
delBlock('agendador_solicitacoes', cleanupIds.agendadorSolicitacoes)
|
||||
lines.push('')
|
||||
delBlock('agenda_regras_semanais', cleanupIds.agendaRegrasSemanais)
|
||||
lines.push('')
|
||||
// agenda_configuracoes: PK é owner_id, não id
|
||||
lines.push(`-- Descomente se quiser remover também as configurações de agenda:`)
|
||||
lines.push(`-- DELETE FROM agenda_configuracoes WHERE owner_id = ${sqlStr(ownerId)};`)
|
||||
lines.push('')
|
||||
delBlock('patients', cleanupIds.patients)
|
||||
lines.push('')
|
||||
lines.push('COMMIT;')
|
||||
lines.push('')
|
||||
lines.push('SET session_replication_role = DEFAULT;')
|
||||
lines.push('')
|
||||
lines.push('-- ─── Fim do cleanup ─────────────────────────────────────────────────────────')
|
||||
delBlock('recurrence_exceptions', cleanupIds.recurrenceExceptions);
|
||||
lines.push('');
|
||||
delBlock('agenda_eventos', cleanupIds.agendaEventos);
|
||||
lines.push('');
|
||||
delBlock('recurrence_rules', cleanupIds.recurrenceRules);
|
||||
lines.push('');
|
||||
delBlock('agendador_solicitacoes', cleanupIds.agendadorSolicitacoes);
|
||||
lines.push('');
|
||||
delBlock('agenda_regras_semanais', cleanupIds.agendaRegrasSemanais);
|
||||
lines.push('');
|
||||
// agenda_configuracoes: PK é owner_id, não id
|
||||
lines.push(`-- Descomente se quiser remover também as configurações de agenda:`);
|
||||
lines.push(`-- DELETE FROM agenda_configuracoes WHERE owner_id = ${sqlStr(ownerId)};`);
|
||||
lines.push('');
|
||||
delBlock('patients', cleanupIds.patients);
|
||||
lines.push('');
|
||||
lines.push('COMMIT;');
|
||||
lines.push('');
|
||||
lines.push('SET session_replication_role = DEFAULT;');
|
||||
lines.push('');
|
||||
lines.push('-- ─── Fim do cleanup ─────────────────────────────────────────────────────────');
|
||||
|
||||
return lines.join('\n')
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// ─── Relatório ────────────────────────────────────────────────────────────────
|
||||
|
||||
function buildReport () {
|
||||
const lines = []
|
||||
lines.push('============================================================')
|
||||
lines.push(' RELATÓRIO DE SIMULAÇÃO — AgenciaPsi')
|
||||
lines.push(` Gerado em: ${new Date().toLocaleString('pt-BR')}`)
|
||||
lines.push('============================================================')
|
||||
lines.push('')
|
||||
lines.push(`OWNER_ID: ${ownerId}`)
|
||||
lines.push(`TENANT_ID: ${tenantId}`)
|
||||
lines.push('')
|
||||
lines.push('─── Dados gerados ─────────────────────────────────────────')
|
||||
lines.push(` Pacientes: ${patients.length}`)
|
||||
lines.push(` Séries de recorrência: ${recurrenceRules.length}`)
|
||||
lines.push(` Sessões reais (passado): ${realSessionsCreated}`)
|
||||
lines.push(` Eventos avulsos: ${avulsosCreated}`)
|
||||
lines.push(` Exceções — faltou: ${excFaltou}`)
|
||||
lines.push(` Exceções — remarcado: ${excRemarcado}`)
|
||||
lines.push(` Exceções — cancelado: ${excCancelado}`)
|
||||
lines.push(` Solicitações agendador: ${config.AGENDADOR_REQUESTS_COUNT}`)
|
||||
lines.push('')
|
||||
lines.push('─── Pacientes criados ─────────────────────────────────────')
|
||||
for (const p of patients) {
|
||||
lines.push(` [${p.id}] ${p.nome_completo} — ${p.email}`)
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('─── Séries de recorrência ─────────────────────────────────')
|
||||
for (const r of recurrenceRules) {
|
||||
const dias = r.weekdays.map(d => weekdayName(d)).join(', ')
|
||||
lines.push(` [${r.id}]`)
|
||||
lines.push(` Paciente: ${r.patient.nome_completo}`)
|
||||
lines.push(` Tipo: ${r.type} | Dias: ${dias}`)
|
||||
lines.push(` Período: ${toISO(r.start_date)} → ${toISO(FUTURE_END)}`)
|
||||
}
|
||||
lines.push('')
|
||||
lines.push('─── Como testar ───────────────────────────────────────────')
|
||||
lines.push(' 1. Abra o Supabase SQL Editor')
|
||||
lines.push(' 2. Cole e rode: logs/simulation-seed.sql')
|
||||
lines.push(' 3. Acesse a agenda — os eventos devem aparecer')
|
||||
lines.push(' 4. Acesse Pacientes — pacientes simulados aparecem na lista')
|
||||
lines.push(' 5. Acesse Agendamentos Recebidos — solicitações pendentes')
|
||||
lines.push(' 6. Quando terminar, rode: logs/simulation-cleanup.sql')
|
||||
lines.push('')
|
||||
lines.push('============================================================')
|
||||
function buildReport() {
|
||||
const lines = [];
|
||||
lines.push('============================================================');
|
||||
lines.push(' RELATÓRIO DE SIMULAÇÃO — AgenciaPsi');
|
||||
lines.push(` Gerado em: ${new Date().toLocaleString('pt-BR')}`);
|
||||
lines.push('============================================================');
|
||||
lines.push('');
|
||||
lines.push(`OWNER_ID: ${ownerId}`);
|
||||
lines.push(`TENANT_ID: ${tenantId}`);
|
||||
lines.push('');
|
||||
lines.push('─── Dados gerados ─────────────────────────────────────────');
|
||||
lines.push(` Pacientes: ${patients.length}`);
|
||||
lines.push(` Séries de recorrência: ${recurrenceRules.length}`);
|
||||
lines.push(` Sessões reais (passado): ${realSessionsCreated}`);
|
||||
lines.push(` Eventos avulsos: ${avulsosCreated}`);
|
||||
lines.push(` Exceções — faltou: ${excFaltou}`);
|
||||
lines.push(` Exceções — remarcado: ${excRemarcado}`);
|
||||
lines.push(` Exceções — cancelado: ${excCancelado}`);
|
||||
lines.push(` Solicitações agendador: ${config.AGENDADOR_REQUESTS_COUNT}`);
|
||||
lines.push('');
|
||||
lines.push('─── Pacientes criados ─────────────────────────────────────');
|
||||
for (const p of patients) {
|
||||
lines.push(` [${p.id}] ${p.nome_completo} — ${p.email}`);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('─── Séries de recorrência ─────────────────────────────────');
|
||||
for (const r of recurrenceRules) {
|
||||
const dias = r.weekdays.map((d) => weekdayName(d)).join(', ');
|
||||
lines.push(` [${r.id}]`);
|
||||
lines.push(` Paciente: ${r.patient.nome_completo}`);
|
||||
lines.push(` Tipo: ${r.type} | Dias: ${dias}`);
|
||||
lines.push(` Período: ${toISO(r.start_date)} → ${toISO(FUTURE_END)}`);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push('─── Como testar ───────────────────────────────────────────');
|
||||
lines.push(' 1. Abra o Supabase SQL Editor');
|
||||
lines.push(' 2. Cole e rode: logs/simulation-seed.sql');
|
||||
lines.push(' 3. Acesse a agenda — os eventos devem aparecer');
|
||||
lines.push(' 4. Acesse Pacientes — pacientes simulados aparecem na lista');
|
||||
lines.push(' 5. Acesse Agendamentos Recebidos — solicitações pendentes');
|
||||
lines.push(' 6. Quando terminar, rode: logs/simulation-cleanup.sql');
|
||||
lines.push('');
|
||||
lines.push('============================================================');
|
||||
|
||||
return lines.join('\n')
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// ─── Salvar arquivos ─────────────────────────────────────────────────────────
|
||||
|
||||
const outDir = path.resolve(config.OUTPUT_DIR)
|
||||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true })
|
||||
const outDir = path.resolve(config.OUTPUT_DIR);
|
||||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||
|
||||
const seedPath = path.join(outDir, 'simulation-seed.sql')
|
||||
const cleanupPath = path.join(outDir, 'simulation-cleanup.sql')
|
||||
const reportPath = path.join(outDir, 'simulation-report.txt')
|
||||
const logPath = path.join(outDir, 'simulation-log.txt')
|
||||
const seedPath = path.join(outDir, 'simulation-seed.sql');
|
||||
const cleanupPath = path.join(outDir, 'simulation-cleanup.sql');
|
||||
const reportPath = path.join(outDir, 'simulation-report.txt');
|
||||
const logPath = path.join(outDir, 'simulation-log.txt');
|
||||
|
||||
fs.writeFileSync(seedPath, seedLines.join('\n'), 'utf-8')
|
||||
fs.writeFileSync(cleanupPath, buildCleanupSQL(), 'utf-8')
|
||||
fs.writeFileSync(reportPath, buildReport(), 'utf-8')
|
||||
fs.writeFileSync(seedPath, seedLines.join('\n'), 'utf-8');
|
||||
fs.writeFileSync(cleanupPath, buildCleanupSQL(), 'utf-8');
|
||||
fs.writeFileSync(reportPath, buildReport(), 'utf-8');
|
||||
|
||||
logInfo(`✔ Seed SQL: ${seedPath}`)
|
||||
logInfo(`✔ Cleanup SQL: ${cleanupPath}`)
|
||||
logInfo(`✔ Relatório: ${reportPath}`)
|
||||
logInfo(`✔ Seed SQL: ${seedPath}`);
|
||||
logInfo(`✔ Cleanup SQL: ${cleanupPath}`);
|
||||
logInfo(`✔ Relatório: ${reportPath}`);
|
||||
|
||||
fs.writeFileSync(logPath, getLog(), 'utf-8')
|
||||
fs.writeFileSync(logPath, getLog(), 'utf-8');
|
||||
|
||||
console.log('\n✅ Simulação concluída. Arquivos em logs/')
|
||||
console.log(' → Rode simulation-seed.sql no Supabase SQL Editor para inserir os dados')
|
||||
console.log(' → Rode simulation-cleanup.sql quando quiser remover\n')
|
||||
console.log('\n✅ Simulação concluída. Arquivos em logs/');
|
||||
console.log(' → Rode simulation-seed.sql no Supabase SQL Editor para inserir os dados');
|
||||
console.log(' → Rode simulation-cleanup.sql quando quiser remover\n');
|
||||
|
||||
@@ -11,33 +11,33 @@
|
||||
*/
|
||||
|
||||
export const config = {
|
||||
// ─── OBRIGATÓRIO — preencha com seus valores reais ──────────────────────────
|
||||
OWNER_ID: 'aaaaaaaa-0002-0002-0002-000000000002',
|
||||
TENANT_ID: 'bbbbbbbb-0002-0002-0002-000000000002',
|
||||
// ─── OBRIGATÓRIO — preencha com seus valores reais ──────────────────────────
|
||||
OWNER_ID: 'aaaaaaaa-0002-0002-0002-000000000002',
|
||||
TENANT_ID: 'bbbbbbbb-0002-0002-0002-000000000002',
|
||||
|
||||
// ─── Configurações de simulação ──────────────────────────────────────────────
|
||||
SIMULATION_DAYS: 90, // quantos dias para o futuro gerar sessões
|
||||
DAYS_BACK: 30, // quantos dias no passado (sessões já realizadas)
|
||||
// ─── Configurações de simulação ──────────────────────────────────────────────
|
||||
SIMULATION_DAYS: 90, // quantos dias para o futuro gerar sessões
|
||||
DAYS_BACK: 30, // quantos dias no passado (sessões já realizadas)
|
||||
|
||||
// ─── Parâmetros dos pacientes ────────────────────────────────────────────────
|
||||
PATIENTS_COUNT: 8, // quantos pacientes criar (patient_scope = 'clinic')
|
||||
// ─── Parâmetros dos pacientes ────────────────────────────────────────────────
|
||||
PATIENTS_COUNT: 8, // quantos pacientes criar (patient_scope = 'clinic')
|
||||
|
||||
// ─── Parâmetros das séries de recorrência ────────────────────────────────────
|
||||
RECURRENCE_RULES_COUNT: 6, // quantas séries semanais criar
|
||||
// ─── Parâmetros das séries de recorrência ────────────────────────────────────
|
||||
RECURRENCE_RULES_COUNT: 6, // quantas séries semanais criar
|
||||
|
||||
// ─── Taxas de exceção (0.0 a 1.0) ────────────────────────────────────────────
|
||||
RATE_FALTOU: 0.10, // 10% das sessões passadas → faltou
|
||||
RATE_REMARCADO: 0.08, // 8% → remarcado para outro dia
|
||||
RATE_CANCELADO: 0.05, // 5% → cancelado
|
||||
// ─── Taxas de exceção (0.0 a 1.0) ────────────────────────────────────────────
|
||||
RATE_FALTOU: 0.1, // 10% das sessões passadas → faltou
|
||||
RATE_REMARCADO: 0.08, // 8% → remarcado para outro dia
|
||||
RATE_CANCELADO: 0.05, // 5% → cancelado
|
||||
|
||||
// ─── Agendador público ────────────────────────────────────────────────────────
|
||||
AGENDADOR_REQUESTS_COUNT: 5, // solicitações pendentes no agendador público
|
||||
// ─── Agendador público ────────────────────────────────────────────────────────
|
||||
AGENDADOR_REQUESTS_COUNT: 5, // solicitações pendentes no agendador público
|
||||
|
||||
// ─── Horário de trabalho ─────────────────────────────────────────────────────
|
||||
WORK_HOUR_START: 8, // 08:00
|
||||
WORK_HOUR_END: 18, // 18:00
|
||||
SESSION_DURATION_MIN: 50, // duração padrão em minutos
|
||||
// ─── Horário de trabalho ─────────────────────────────────────────────────────
|
||||
WORK_HOUR_START: 8, // 08:00
|
||||
WORK_HOUR_END: 18, // 18:00
|
||||
SESSION_DURATION_MIN: 50, // duração padrão em minutos
|
||||
|
||||
// ─── Saída ────────────────────────────────────────────────────────────────────
|
||||
OUTPUT_DIR: 'logs',
|
||||
}
|
||||
// ─── Saída ────────────────────────────────────────────────────────────────────
|
||||
OUTPUT_DIR: 'logs'
|
||||
};
|
||||
|
||||
@@ -4,30 +4,30 @@
|
||||
* Logger simples que imprime no console e acumula linhas para salvar em arquivo.
|
||||
*/
|
||||
|
||||
const lines = []
|
||||
const lines = [];
|
||||
|
||||
function timestamp () {
|
||||
return new Date().toISOString().replace('T', ' ').substring(0, 19)
|
||||
function timestamp() {
|
||||
return new Date().toISOString().replace('T', ' ').substring(0, 19);
|
||||
}
|
||||
|
||||
export function logInfo (msg) {
|
||||
const line = `[INFO] ${timestamp()} ${msg}`
|
||||
console.log(line)
|
||||
lines.push(line)
|
||||
export function logInfo(msg) {
|
||||
const line = `[INFO] ${timestamp()} ${msg}`;
|
||||
console.log(line);
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
export function logWarning (msg) {
|
||||
const line = `[WARN] ${timestamp()} ${msg}`
|
||||
console.warn(line)
|
||||
lines.push(line)
|
||||
export function logWarning(msg) {
|
||||
const line = `[WARN] ${timestamp()} ${msg}`;
|
||||
console.warn(line);
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
export function logError (msg) {
|
||||
const line = `[ERROR] ${timestamp()} ${msg}`
|
||||
console.error(line)
|
||||
lines.push(line)
|
||||
export function logError(msg) {
|
||||
const line = `[ERROR] ${timestamp()} ${msg}`;
|
||||
console.error(line);
|
||||
lines.push(line);
|
||||
}
|
||||
|
||||
export function getLog () {
|
||||
return lines.join('\n')
|
||||
export function getLog() {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user