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:
@@ -470,6 +470,54 @@ export const BUILTIN_TOOLS: ToolDef[] = [
|
||||
} catch (e: any) { return { ok: false, erro: e.message } }
|
||||
},
|
||||
},
|
||||
|
||||
// ── perguntar_dentista ──────────────────────────────────────────────────────
|
||||
// Parte A: encaixe SUB-HORA (ex.: 09:45, que quebra o espaçamento de 1h). A IA
|
||||
// pergunta ao DENTISTA por WhatsApp; ele responde SIM/NÃO e o sistema agenda.
|
||||
{
|
||||
name: 'perguntar_dentista',
|
||||
description:
|
||||
'Use quando o cliente pedir um encaixe que QUEBRA o espaçamento de 1 hora (ex.: 09:45, 10:20) — um horário ' +
|
||||
'curto/fora da grade normal. NÃO agende: pergunte ao DENTISTA responsável se dá tempo. Diga ao cliente que ' +
|
||||
'você vai verificar com o dentista e confirma em seguida.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
dentista: { type: 'string', description: 'Nome do dentista responsável.' },
|
||||
data: { type: 'string', description: 'Data YYYY-MM-DD.' },
|
||||
hora: { type: 'string', description: 'Horário do encaixe HH:MM (ex.: 09:45).' },
|
||||
procedimento: { type: 'string', description: 'Tipo (ex.: avaliação, avaliação de prótese).' },
|
||||
nome_cliente: { type: 'string', description: 'Nome do cliente.' },
|
||||
telefone_cliente: { type: 'string', description: 'Telefone do cliente (opcional).' },
|
||||
},
|
||||
required: ['dentista', 'data', 'hora', 'nome_cliente'],
|
||||
},
|
||||
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.' }
|
||||
let telefone = String(args['telefone_cliente'] ?? '').replace(/\D/g, '')
|
||||
if (!telefone) telefone = (jid.split('@')[0] ?? '').replace(/\D/g, '')
|
||||
try {
|
||||
const u = new URL(`${ctx.agenda.url.replace(/\/$/, '')}/dentista-contato`)
|
||||
u.searchParams.set('clinica_id', ctx.agenda.clinicaId); u.searchParams.set('nome', String(args['dentista']))
|
||||
const r = await fetch(u, { headers: { 'x-nw-agenda-secret': ctx.agenda.secret ?? '' }, signal: AbortSignal.timeout(10_000) })
|
||||
const dj: any = await r.json().catch(() => ({}))
|
||||
if (!dj.encontrado || !dj.telefone) return { ok: false, mensagem: 'Sem contato do dentista.', instrucao: 'Diga que não conseguiu falar com o dentista agora e ofereça um horário normal da agenda.' }
|
||||
const dentistaJid = `${dj.telefone}@s.whatsapp.net`
|
||||
await ctx.db('sec_agenda_approvals').insert({
|
||||
tenant_id: ctx.tenantId ?? null, instance_id: ctx.instanceId, clinica_id: ctx.agenda.clinicaId,
|
||||
agenda_url: ctx.agenda.url, agenda_secret: ctx.agenda.secret ?? '',
|
||||
paciente_jid: jid, paciente_nome: args['nome_cliente'] ?? null, paciente_telefone: telefone || null,
|
||||
dentista_id: dj.dentista_id, dentista_nome: dj.nome, dentista_jid: dentistaJid,
|
||||
data: args['data'], hora: args['hora'], procedimento: args['procedimento'] ?? 'avaliação', status: 'aguardando',
|
||||
})
|
||||
const texto = `Oi, Dr(a) ${String(dj.nome).split(' ')[0]}! 👋 Tenho um paciente (${args['nome_cliente']}) querendo ${args['procedimento'] ?? 'uma avaliação'} às ${args['hora']} do dia ${args['data']}. Consegue encaixar? Responda *SIM* ou *NÃO*.`
|
||||
ctx.hooks?.emit?.('ext:agenda.notify-dentist', { instanceId: ctx.instanceId, tenantId: ctx.tenantId, jid: dentistaJid, text: texto })
|
||||
return { ok: true, perguntou: true, instrucao: 'Diga ao cliente que você já perguntou ao dentista se dá pra encaixar e que, assim que ele responder, você confirma. NÃO garanta o horário.' }
|
||||
} catch (e: any) { return { ok: false, erro: e.message } }
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export function resolveTools(names: string[]): ToolDef[] {
|
||||
|
||||
Reference in New Issue
Block a user