40bed96515
Satélite da Secretária IA: - /contexto (dentistas+especialidades+situações+cautelas) e /duvida (encaminhar ao humano sem agendar); agenda_pedidos ganha tipo=duvida + resumo. - Situações da clínica: tabela agenda_situacoes + config (dono) + aba na UI. - Checklist do dono (checklist-defs.js + agenda_checklist): perguntas Sim/Não conferidas contra o banco (dentistas/pacientes) → pendências de config + cautelas p/ a IA não chutar. - Área de PENDÊNCIAS DO DONO (PendenciasDono.tsx): engloba config + fila operacional (horários/dúvidas); botão+badge no /wa-secretaria (só o dono). - Fila humana (PedidosPendentes): cards de dúvida + resolver; /resolver. - Botão DESLIGAR global da Secretária no ThinNav (secPowerApi, visível a todos). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
119 lines
6.0 KiB
JavaScript
119 lines
6.0 KiB
JavaScript
// Schema (idempotente) das tabelas de horário/feriado usadas pela Secretária.
|
|
// Chamado uma vez no registerNewwhats. Espelha os tipos de `dentistas_horarios`.
|
|
//
|
|
// clinicas_horarios — horário de FUNCIONAMENTO da clínica (config do DONO).
|
|
// feriados — EXCEÇÕES da clínica (fechamento/ponte/horário especial);
|
|
// os feriados NACIONAIS são computados (ver feriados.js).
|
|
|
|
export async function ensureAgendaSchema(pool) {
|
|
try {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS clinicas_horarios (
|
|
id varchar PRIMARY KEY,
|
|
clinica_id varchar NOT NULL,
|
|
dia_semana integer NOT NULL, -- 0=domingo … 6=sábado
|
|
hora_inicio time without time zone NOT NULL,
|
|
hora_fim time without time zone NOT NULL,
|
|
ativo smallint DEFAULT 1
|
|
)`);
|
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_clinicas_horarios_clinica ON clinicas_horarios (clinica_id)`);
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS feriados (
|
|
id varchar PRIMARY KEY,
|
|
clinica_id varchar NOT NULL,
|
|
data date NOT NULL,
|
|
nome varchar NOT NULL,
|
|
fecha smallint DEFAULT 1, -- 1=fechado o dia; 0=abre em horário especial
|
|
hora_inicio time without time zone, -- horário especial (quando fecha=0)
|
|
hora_fim time without time zone,
|
|
recorrente smallint DEFAULT 0, -- 1=repete todo ano (mesmo dia/mês)
|
|
created_at timestamptz DEFAULT now()
|
|
)`);
|
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_feriados_clinica_data ON feriados (clinica_id, data)`);
|
|
|
|
// ── dentistas_folgas — folga/férias/indisponibilidade por DATA do dentista.
|
|
// O dentista (ou o dono) marca dias em que NÃO atende; o /slots pula.
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS dentistas_folgas (
|
|
id varchar PRIMARY KEY,
|
|
dentista_id varchar NOT NULL,
|
|
clinica_id varchar,
|
|
data_inicio date NOT NULL,
|
|
data_fim date NOT NULL, -- inclusive; = data_inicio p/ 1 dia
|
|
motivo varchar,
|
|
created_at timestamptz DEFAULT now()
|
|
)`);
|
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_dentistas_folgas_dent ON dentistas_folgas (dentista_id, data_inicio, data_fim)`);
|
|
|
|
// ── B (salas): agendamentos.sala_id — permite agendar num quarto alugado.
|
|
// Aditivo/nullable: NÃO afeta o fluxo existente (clínica) quando nulo.
|
|
await pool.query(`ALTER TABLE agendamentos ADD COLUMN IF NOT EXISTS sala_id varchar`);
|
|
|
|
// ── Janela ESTENDIDA do dentista (zona cinza): a IA agenda livre em
|
|
// [hora_inicio, hora_fim]; entre a estendida e a livre exige confirmação
|
|
// humana. Nulo = sem zona cinza (só a janela livre).
|
|
await pool.query(`ALTER TABLE dentistas_horarios ADD COLUMN IF NOT EXISTS hora_inicio_flex time without time zone`);
|
|
await pool.query(`ALTER TABLE dentistas_horarios ADD COLUMN IF NOT EXISTS hora_fim_flex time without time zone`);
|
|
|
|
// ── agenda_pedidos — pedidos da ZONA CINZA aguardando a secretária humana.
|
|
// A IA cria (status 'pendente') e diz "vou confirmar"; a humana confirma
|
|
// (vira agendamento) ou recusa.
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS agenda_pedidos (
|
|
id varchar PRIMARY KEY,
|
|
clinica_id varchar NOT NULL,
|
|
dentista_id varchar,
|
|
sala_id varchar,
|
|
data date NOT NULL,
|
|
hora_inicio time without time zone NOT NULL,
|
|
hora_fim time without time zone,
|
|
paciente_nome varchar,
|
|
paciente_telefone varchar,
|
|
procedimento varchar,
|
|
origem varchar DEFAULT 'secretaria_ia',
|
|
status varchar DEFAULT 'pendente', -- pendente | confirmado | recusado
|
|
agendamento_id varchar, -- preenchido ao confirmar
|
|
created_at timestamptz DEFAULT now(),
|
|
resolved_at timestamptz,
|
|
resolved_by varchar
|
|
)`);
|
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_agenda_pedidos_clinica_status ON agenda_pedidos (clinica_id, status, created_at)`);
|
|
// Pedido também serve para DÚVIDAS (sem horário): tipo + resumo; data/hora nullable.
|
|
await pool.query(`ALTER TABLE agenda_pedidos ADD COLUMN IF NOT EXISTS tipo varchar DEFAULT 'horario'`); // horario | duvida
|
|
await pool.query(`ALTER TABLE agenda_pedidos ADD COLUMN IF NOT EXISTS resumo text`);
|
|
await pool.query(`ALTER TABLE agenda_pedidos ALTER COLUMN data DROP NOT NULL`).catch(() => {});
|
|
await pool.query(`ALTER TABLE agenda_pedidos ALTER COLUMN hora_inicio DROP NOT NULL`).catch(() => {});
|
|
|
|
// ── agenda_situacoes — "área de situações": regras da clínica que a Secretária
|
|
// IA deve seguir (ex.: "plano X não cobre canal posterior — ofereça avaliação").
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS agenda_situacoes (
|
|
id varchar PRIMARY KEY,
|
|
clinica_id varchar NOT NULL,
|
|
texto text NOT NULL,
|
|
ativo smallint DEFAULT 1,
|
|
ordem integer DEFAULT 0,
|
|
created_at timestamptz DEFAULT now()
|
|
)`);
|
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_agenda_situacoes_clinica ON agenda_situacoes (clinica_id)`);
|
|
|
|
// ── agenda_checklist — respostas Sim/Não do DONO sobre a clínica (ver
|
|
// checklist-defs.js). Conferidas contra o banco → geram pendências de config
|
|
// e deixam a Secretária cautelosa. resposta: 1=sim, 0=não, NULL=não respondido.
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS agenda_checklist (
|
|
clinica_id varchar NOT NULL,
|
|
chave varchar NOT NULL,
|
|
resposta smallint,
|
|
updated_at timestamptz DEFAULT now(),
|
|
PRIMARY KEY (clinica_id, chave)
|
|
)`);
|
|
return true;
|
|
} catch (e) {
|
|
// Não derruba o boot do satélite se o banco estiver indisponível no momento.
|
|
console.error('[newwhats] ensureAgendaSchema falhou:', e.message);
|
|
return false;
|
|
}
|
|
}
|