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:
@@ -39,6 +39,8 @@ export interface ToolContext {
|
||||
tenantId?: string
|
||||
/** Número/sessão que recebeu a mensagem — escopa o calendário interno por sessão. */
|
||||
instanceId?: string
|
||||
/** Atraso (min) do follow-up proativo da agenda (Parte B). Default 15. */
|
||||
followupDelayMin?: number
|
||||
hooks?: HookBus
|
||||
/** Ponte de agenda do satélite (scoreodonto). Quando presente, as tools de
|
||||
* agenda operam na agenda REAL da clínica em vez do calendário interno. */
|
||||
@@ -429,6 +431,45 @@ export const BUILTIN_TOOLS: ToolDef[] = [
|
||||
} catch (e: any) { return { ok: false, erro: e.message } }
|
||||
},
|
||||
},
|
||||
|
||||
// ── confirmar_depois ────────────────────────────────────────────────────────
|
||||
// Parte B: o cliente pede um horário MAIS TARDE que o oferecido. A IA diz "vou
|
||||
// confirmar, aguarde uns minutinhos" e registra um follow-up; um worker retorna
|
||||
// depois (com atraso) com o horário confirmado.
|
||||
{
|
||||
name: 'confirmar_depois',
|
||||
description:
|
||||
'Use quando o cliente pedir um horário MAIS TARDE no dia (dentro do mesmo período) do que o que você ' +
|
||||
'ofereceu — ou quando quiser "checar a agenda com calma" antes de confirmar. Diga ao cliente para aguardar ' +
|
||||
'uns minutinhos que você vai confirmar; em poucos minutos o sistema retorna sozinho com o horário. NÃO ofereça horário agora.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: { type: 'string', description: 'Data desejada YYYY-MM-DD.' },
|
||||
periodo: { type: 'string', description: '"manha" ou "tarde".', enum: ['manha', 'tarde'] },
|
||||
hora_desejada: { type: 'string', description: 'Horário aproximado que o cliente pediu (HH:MM), se houver.' },
|
||||
nome_cliente: { type: 'string', description: 'Nome do cliente.' },
|
||||
dentista: { type: 'string', description: 'Dentista, se indicado (opcional).' },
|
||||
},
|
||||
required: ['data'],
|
||||
},
|
||||
async execute(args, ctx) {
|
||||
if (!ctx.agenda?.url || !ctx.agenda.clinicaId) return { ok: false, erro: 'Agenda indisponível.' }
|
||||
const jid = (ctx.extChatId ?? '').split(':').pop() ?? ''
|
||||
if (!jid || !ctx.instanceId) return { ok: false, erro: 'Contexto incompleto.' }
|
||||
const delay = Math.max(1, ctx.followupDelayMin ?? 15)
|
||||
try {
|
||||
await ctx.db('sec_agenda_followups').insert({
|
||||
tenant_id: ctx.tenantId ?? null, instance_id: ctx.instanceId, jid,
|
||||
clinica_id: ctx.agenda.clinicaId, agenda_url: ctx.agenda.url, agenda_secret: ctx.agenda.secret ?? '',
|
||||
dentista_nome: args['dentista'] ?? null, data: args['data'], hora_desejada: args['hora_desejada'] ?? null,
|
||||
periodo: args['periodo'] ?? null, paciente_nome: args['nome_cliente'] ?? null,
|
||||
fire_at: new Date(Date.now() + delay * 60_000), status: 'pendente',
|
||||
})
|
||||
return { ok: true, agendado_retorno: true, instrucao: 'Diga ao cliente para aguardar uns minutinhos que você vai confirmar a agenda e JÁ retorna. NÃO ofereça horário agora — o sistema retorna sozinho em instantes.' }
|
||||
} catch (e: any) { return { ok: false, erro: e.message } }
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export function resolveTools(names: string[]): ToolDef[] {
|
||||
|
||||
Reference in New Issue
Block a user