feat(secretaria): encaixe sub-hora escala ao dentista + auto-confirma (Parte A)

Encaixe que quebra o espaçamento de 1h (ex.: 09:45): a IA usa perguntar_dentista
→ resolve o WhatsApp do dentista (bridge /dentista-contato), registra
sec_agenda_approvals (aguardando) e manda a pergunta ao dentista ("consegue
encaixar às X? SIM/NÃO", via hook ext:agenda.notify-dentist). Quando o dentista
responde, ext:message.new detecta (roda mesmo com auto_reply off), interpreta
sim/não; se SIM agenda (/book) e avisa o paciente automaticamente ("o Dr
confirmou, agendei às X"); se NÃO oferece outro horário.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-07-06 06:31:40 +02:00
parent 02a952c60b
commit f9e93647bb
12 changed files with 263 additions and 4 deletions
@@ -218,6 +218,32 @@ async function runMigrations(db) {
});
await db.raw('CREATE INDEX IF NOT EXISTS idx_followups_fire ON sec_agenda_followups (status, fire_at)');
}
// ── sec_agenda_approvals — Parte A: encaixe sub-hora aguardando o DENTISTA.
// A IA manda WhatsApp ao dentista; quando ele responde SIM/NÃO, o sistema
// agenda (ou não) e avisa o paciente automaticamente.
if (!(await db.schema.hasTable('sec_agenda_approvals'))) {
await db.schema.createTable('sec_agenda_approvals', (t) => {
t.uuid('id').primary().defaultTo(db.raw('gen_random_uuid()'));
t.string('tenant_id', 80);
t.string('instance_id', 100);
t.string('clinica_id', 100);
t.string('agenda_url', 300);
t.string('agenda_secret', 200);
t.string('paciente_jid', 120);
t.string('paciente_chat_id', 300);
t.string('paciente_nome', 120);
t.string('paciente_telefone', 30);
t.string('dentista_id', 120);
t.string('dentista_nome', 120);
t.string('dentista_jid', 120); // WhatsApp do dentista
t.date('data');
t.string('hora', 8);
t.string('procedimento', 160);
t.string('status', 20).defaultTo('aguardando'); // aguardando | confirmado | recusado
t.timestamps(true, true);
});
await db.raw('CREATE INDEX IF NOT EXISTS idx_approvals_dent ON sec_agenda_approvals (dentista_jid, status)');
}
// ── Seeds: agente padrão + nós + calendário ───────────────────────────────
const agentCount = await db('sec_agents').count('id as c').first();
if (Number(agentCount?.c ?? 0) === 0) {