Agenda, Agendador, Configurações

This commit is contained in:
Leonardo
2026-03-12 08:58:36 -03:00
parent f733db8436
commit f4b185ae17
197 changed files with 33405 additions and 6507 deletions

View File

@@ -0,0 +1,555 @@
/**
* simulateUsage.js
*
* Gera dois arquivos SQL:
* logs/simulation-seed.sql → insere dados de simulação
* logs/simulation-cleanup.sql → remove os dados inseridos
*
* e um relatório legível:
* logs/simulation-report.txt
* logs/simulation-log.txt
*
* COMO USAR:
* 1. Preencha scripts/simulation/simulation.config.js com seu OWNER_ID e TENANT_ID
* 2. npm run simulate
* 3. Abra o Supabase SQL Editor e rode simulation-seed.sql
* 4. Teste o sistema
* 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'
// ─── 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)
}
// ─── 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'],
]
const SOBRENOMES = [
'Silva', 'Santos', 'Oliveira', 'Souza', 'Lima', 'Ferreira', 'Rodrigues',
'Almeida', 'Costa', 'Gomes', 'Martins', 'Pereira', 'Carvalho', 'Rocha', 'Nunes',
]
const MODALIDADES = ['presencial', 'online', 'presencial', 'presencial'] // ponderado
// ─── Utilitários ─────────────────────────────────────────────────────────────
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 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 addDays (date, n) {
const d = new Date(date)
d.setDate(d.getDate() + n)
return d
}
function toISO (date) {
return date.toISOString().split('T')[0]
}
function toISODateTime (date, hh, mm) {
return `${toISO(date)}T${String(hh).padStart(2,'0')}:${String(mm).padStart(2,'0')}:00`
}
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()
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++
}
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 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)}`
}
// ─── Blocos SQL ───────────────────────────────────────────────────────────────
const seedLines = []
const cleanupIds = {
patients: [],
agendaEventos: [],
recurrenceRules: [],
recurrenceExceptions: [],
agendadorSolicitacoes: [],
agendaConfiguracoes: [],
agendaRegrasSemanais: [],
}
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 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)}`)
// ─── 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('')
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()
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');`)
}
logInfo(`${patients.length} pacientes criados`)
emit('')
// ─── 2. Configurações de agenda ───────────────────────────────────────────────
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' },
]
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')
// ─── 3. Regras semanais de disponibilidade ────────────────────────────────────
emit('-- ─── 3. Regras semanais ─────────────────────────────────────────────────────')
emit('')
const workDays = [1, 2, 3, 4, 5] // segsex
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);`)
}
logInfo(`${workDays.length} regras semanais inseridas`)
emit('')
// ─── 4. Eventos avulsos (passado) ─────────────────────────────────────────────
emit('-- ─── 4. Eventos avulsos (passado) ──────────────────────────────────────────')
emit('')
const avulsoCount = 3
const avulsoPatients = patients.slice(0, avulsoCount)
let avulsosCreated = 0
// Avulsos usam 13h para não conflitar com séries (9h12h e 14h17h)
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 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)
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++
}
logInfo(`${avulsosCreated} eventos avulsos criados`)
emit('')
// ─── 5. Séries de recorrência ─────────────────────────────────────────────────
emit('-- ─── 5. Séries de recorrência ───────────────────────────────────────────────')
emit('')
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]]
// Cada série tem seu próprio horário para evitar sobreposição
const ruleHours = [9, 10, 11, 14, 15, 16]
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[]`
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)});`)
}
logInfo(`${recurrenceRules.length} regras de recorrência criadas`)
emit('')
// ─── 6. Exceções de recorrência (passado) ────────────────────────────────────
emit('-- ─── 6. Exceções de recorrência ────────────────────────────────────────────')
emit('')
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))
}
cur = addDays(cur, 1)
}
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++
}
}
}
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('')
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'
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)});`)
realSessionsCreated++
}
cur2 = addDays(cur2, 1)
}
}
logInfo(`${realSessionsCreated} sessões reais (passado) criadas`)
emit('')
// ─── 8. Solicitações do Agendador Público ─────────────────────────────────────
emit('-- ─── 8. Agendador Público — solicitações pendentes ─────────────────────────')
emit('')
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()
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)});`)
}
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 ────────────────────────────────────────────────────────────')
// ─── 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 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 ─────────────────────────────────────────────────────────')
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('============================================================')
return lines.join('\n')
}
// ─── Salvar arquivos ─────────────────────────────────────────────────────────
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')
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}`)
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')

View File

@@ -0,0 +1,43 @@
/**
* simulation.config.js
*
* Preencha OWNER_ID e TENANT_ID com os valores reais do seu usuário.
* Para encontrar esses valores:
* - OWNER_ID: SELECT id FROM auth.users WHERE email = 'seu@email.com';
* - TENANT_ID: SELECT id FROM tenants WHERE name ILIKE '%nome da clínica%';
*
* Depois rode: npm run simulate
* Os arquivos serão gerados em logs/
*/
export const config = {
// ─── 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)
// ─── 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
// ─── 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
// ─── 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
// ─── Saída ────────────────────────────────────────────────────────────────────
OUTPUT_DIR: 'logs',
}

View File

@@ -0,0 +1,33 @@
/**
* simulationLogger.js
*
* Logger simples que imprime no console e acumula linhas para salvar em arquivo.
*/
const lines = []
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 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 getLog () {
return lines.join('\n')
}