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
@@ -208,6 +208,30 @@ export async function runMigrations(db: Knex): Promise<void> {
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) {