feat(secretaria): follow-up proativo com atraso (Parte B)

Quando o cliente insiste num horário mais tarde, a IA usa confirmar_depois:
diz "vou confirmar, aguarde uns minutinhos" e registra um follow-up
(sec_agenda_followups, fire_at = now + followup_delay_min, default 15). Um worker
no ext-api (a cada 30s) processa os vencidos: consulta /slots perto do horário
desejado e manda ao paciente "Oie, consegui! Posso às X?" (assinado pelo agente).

Testado em dev: worker processa follow-up vencido, consulta a agenda e envia.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-07-06 06:15:26 +02:00
parent 163a5d0db2
commit 02a952c60b
12 changed files with 226 additions and 4 deletions
@@ -195,6 +195,29 @@ async function runMigrations(db) {
});
await db.raw('CREATE INDEX IF NOT EXISTS idx_sec_mem_contact ON sec_contact_memory (agent_id, contact_key)');
}
// ── sec_agenda_followups — follow-up proativo (Parte B): a IA promete "confirmo
// em uns minutos" e um worker envia depois o horário confirmado, com atraso.
if (!(await db.schema.hasTable('sec_agenda_followups'))) {
await db.schema.createTable('sec_agenda_followups', (t) => {
t.uuid('id').primary().defaultTo(db.raw('gen_random_uuid()'));
t.string('tenant_id', 80);
t.string('instance_id', 100);
t.string('chat_id', 300);
t.string('jid', 120); // paciente
t.string('clinica_id', 100);
t.string('agenda_url', 300);
t.string('agenda_secret', 200);
t.string('dentista_nome', 120);
t.date('data');
t.string('hora_desejada', 8);
t.string('periodo', 10); // manha | tarde
t.string('paciente_nome', 120);
t.timestamp('fire_at').notNullable();
t.string('status', 20).defaultTo('pendente'); // pendente | enviado | falhou
t.timestamps(true, true);
});
await db.raw('CREATE INDEX IF NOT EXISTS idx_followups_fire ON sec_agenda_followups (status, fire_at)');
}
// ── 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) {