feat(secretaria): roteiro de agendamento (manhã/tarde, 1 horário por vez)

Regras injetadas no prompt: NUNCA listar vários horários — primeiro perguntar
"manhã ou tarde?", depois oferecer UM único horário (o primeiro livre do período,
via listar_horarios com periodo), um de cada vez, mantendo 1h entre atendimentos.
listar_horarios ganhou o parâmetro periodo (manha|tarde).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-07-06 05:59:36 +02:00
parent 46b9efaed0
commit 163a5d0db2
6 changed files with 25 additions and 4 deletions
@@ -210,7 +210,11 @@ class ProtocolEngine {
L.push(`Próximos feriados/fechamentos: ${st.feriados_proximos.map((f) => `${dm(f.data)} ${f.nome}`).join('; ')}.`); L.push(`Próximos feriados/fechamentos: ${st.feriados_proximos.map((f) => `${dm(f.data)} ${f.nome}`).join('; ')}.`);
} }
L.push('IMPORTANTE: só ofereça horários e confirme agendamento quando a clínica estiver ABERTA. Se pedirem em dia/horário fechado ou feriado, informe o horário de funcionamento e ofereça o próximo dia disponível.'); L.push('IMPORTANTE: só ofereça horários e confirme agendamento quando a clínica estiver ABERTA. Se pedirem em dia/horário fechado ou feriado, informe o horário de funcionamento e ofereça o próximo dia disponível.');
L.push('Se o cliente pedir um horário PONTUAL fora dos disponíveis (mais cedo/mais tarde, ex.: 8h30 ou depois das 17h), NÃO recuse nem confirme: use a ferramenta solicitar_horario_especial e responda algo como "No momento esse horário não está disponível, mas ainda não confirmei a agenda — posso te confirmar daqui a pouco?" (sem mencionar "secretária").'); L.push('ROTEIRO DE AGENDAMENTO — siga à risca:');
L.push('1) NUNCA liste vários horários de uma vez. PRIMEIRO pergunte: "Você prefere de manhã ou à tarde?".');
L.push('2) Com a resposta, chame listar_horarios passando periodo=manha ou tarde e ofereça UM ÚNICO horário — sempre o PRIMEIRO livre daquele período. Ex.: "Consigo te encaixar às 10:00, fica bom?".');
L.push('3) Se não servir, ofereça o PRÓXIMO horário livre, um de cada vez (nunca uma lista). Mantenha sempre 1 hora entre os atendimentos.');
L.push('4) Se o cliente pedir um horário PONTUAL fora dos disponíveis (mais cedo/mais tarde, ex.: 8h30 ou depois das 17h), NÃO recuse nem confirme: use a ferramenta solicitar_horario_especial e responda algo como "No momento esse horário não está disponível, mas ainda não confirmei a agenda — posso te confirmar daqui a pouco?" (sem mencionar "secretária").');
return `\n\n${L.join('\n')}\n`; return `\n\n${L.join('\n')}\n`;
} }
// ── System Prompt Builder ───────────────────────────────────────────────── // ── System Prompt Builder ─────────────────────────────────────────────────
File diff suppressed because one or more lines are too long
@@ -238,7 +238,11 @@ export class ProtocolEngine {
L.push(`Próximos feriados/fechamentos: ${st.feriados_proximos.map((f: any) => `${dm(f.data)} ${f.nome}`).join('; ')}.`) L.push(`Próximos feriados/fechamentos: ${st.feriados_proximos.map((f: any) => `${dm(f.data)} ${f.nome}`).join('; ')}.`)
} }
L.push('IMPORTANTE: só ofereça horários e confirme agendamento quando a clínica estiver ABERTA. Se pedirem em dia/horário fechado ou feriado, informe o horário de funcionamento e ofereça o próximo dia disponível.') L.push('IMPORTANTE: só ofereça horários e confirme agendamento quando a clínica estiver ABERTA. Se pedirem em dia/horário fechado ou feriado, informe o horário de funcionamento e ofereça o próximo dia disponível.')
L.push('Se o cliente pedir um horário PONTUAL fora dos disponíveis (mais cedo/mais tarde, ex.: 8h30 ou depois das 17h), NÃO recuse nem confirme: use a ferramenta solicitar_horario_especial e responda algo como "No momento esse horário não está disponível, mas ainda não confirmei a agenda — posso te confirmar daqui a pouco?" (sem mencionar "secretária").') L.push('ROTEIRO DE AGENDAMENTO — siga à risca:')
L.push('1) NUNCA liste vários horários de uma vez. PRIMEIRO pergunte: "Você prefere de manhã ou à tarde?".')
L.push('2) Com a resposta, chame listar_horarios passando periodo=manha ou tarde e ofereça UM ÚNICO horário — sempre o PRIMEIRO livre daquele período. Ex.: "Consigo te encaixar às 10:00, fica bom?".')
L.push('3) Se não servir, ofereça o PRÓXIMO horário livre, um de cada vez (nunca uma lista). Mantenha sempre 1 hora entre os atendimentos.')
L.push('4) Se o cliente pedir um horário PONTUAL fora dos disponíveis (mais cedo/mais tarde, ex.: 8h30 ou depois das 17h), NÃO recuse nem confirme: use a ferramenta solicitar_horario_especial e responda algo como "No momento esse horário não está disponível, mas ainda não confirmei a agenda — posso te confirmar daqui a pouco?" (sem mencionar "secretária").')
return `\n\n${L.join('\n')}\n` return `\n\n${L.join('\n')}\n`
} }
@@ -21,6 +21,11 @@ exports.BUILTIN_TOOLS = [
type: 'string', type: 'string',
description: 'Data no formato YYYY-MM-DD. Opcional — padrão: próximos 7 dias.', description: 'Data no formato YYYY-MM-DD. Opcional — padrão: próximos 7 dias.',
}, },
periodo: {
type: 'string',
description: 'Período do dia: "manha" ou "tarde". SEMPRE informe (pergunte ao cliente antes se não souber).',
enum: ['manha', 'tarde'],
},
}, },
}, },
async execute(args, ctx) { async execute(args, ctx) {
@@ -31,6 +36,8 @@ exports.BUILTIN_TOOLS = [
u.searchParams.set('clinica_id', ctx.agenda.clinicaId); u.searchParams.set('clinica_id', ctx.agenda.clinicaId);
if (args['data']) if (args['data'])
u.searchParams.set('date', String(args['data'])); u.searchParams.set('date', String(args['data']));
if (args['periodo'])
u.searchParams.set('periodo', String(args['periodo']));
const r = await fetch(u, { const r = await fetch(u, {
headers: { 'x-nw-agenda-secret': ctx.agenda.secret ?? '' }, headers: { 'x-nw-agenda-secret': ctx.agenda.secret ?? '' },
signal: AbortSignal.timeout(12000), signal: AbortSignal.timeout(12000),
File diff suppressed because one or more lines are too long
@@ -76,6 +76,11 @@ export const BUILTIN_TOOLS: ToolDef[] = [
type: 'string', type: 'string',
description: 'Data no formato YYYY-MM-DD. Opcional — padrão: próximos 7 dias.', description: 'Data no formato YYYY-MM-DD. Opcional — padrão: próximos 7 dias.',
}, },
periodo: {
type: 'string',
description: 'Período do dia: "manha" ou "tarde". SEMPRE informe (pergunte ao cliente antes se não souber).',
enum: ['manha', 'tarde'],
},
}, },
}, },
async execute(args, ctx) { async execute(args, ctx) {
@@ -85,6 +90,7 @@ export const BUILTIN_TOOLS: ToolDef[] = [
const u = new URL(`${ctx.agenda.url.replace(/\/$/, '')}/slots`) const u = new URL(`${ctx.agenda.url.replace(/\/$/, '')}/slots`)
u.searchParams.set('clinica_id', ctx.agenda.clinicaId) u.searchParams.set('clinica_id', ctx.agenda.clinicaId)
if (args['data']) u.searchParams.set('date', String(args['data'])) if (args['data']) u.searchParams.set('date', String(args['data']))
if (args['periodo']) u.searchParams.set('periodo', String(args['periodo']))
const r = await fetch(u, { const r = await fetch(u, {
headers: { 'x-nw-agenda-secret': ctx.agenda.secret ?? '' }, headers: { 'x-nw-agenda-secret': ctx.agenda.secret ?? '' },
signal: AbortSignal.timeout(12_000), signal: AbortSignal.timeout(12_000),