Appearance
Criar contrato pelo WhatsApp
Fluxo end-to-end da Rodada 3 da Fase 3. Usuário descreve o contrato em linguagem natural no chat; o Agente coleta os dados, busca contraparte, cria o contrato e dispara a assinatura via link.
Pré-condições
- Usuário autenticado no bot (passou pelo onboarding OTP, tem
defaultCompanyId). - Janela operacional 8h às 22h BRT (fora disso o gateway responde automático).
- Persona resolvida pelo papel:
provider→WHATSAPP_PROVIDER,borrower→WHATSAPP_BORROWER.
Caminho feliz (exemplo)
- Usuário: "Cria um contrato de prestação de serviço com a Maria Silva por R$ 5k/mês, 12 meses, começando dia 1º"
- Agente (1 bolha): "Achei a Maria Silva pelo nome. Confirma que é ela? (CPF cadastrado termina em ***-09)"
- Usuário: "sim"
- Agente (2 bolhas):
- "Antes de criar, preciso confirmar a vigência: 01/06/2026 a 31/05/2027, certo?"
- "Vou usar o template padrão de prestação de serviço PJ. Pode confirmar?"
- Usuário: "confirmo"
- Agente (3 bolhas):
- "Criei o contrato #2026-0184."
- "Enviei o link de assinatura para a Maria no e-mail dela."
- "Te aviso assim que ela assinar. Algo mais?"
Tools envolvidas (ordem típica de chamada)
| Passo | Tool | Função |
|---|---|---|
| 1 | search_contract_parties | Acha contraparte pelo nome/CPF/CNPJ informado |
| 2 | lookup_company_by_cnpj (PJ) | Valida CNPJ se a parte for empresa nova |
| 3 | create_natural_person ou register_company_party | Cadastra contraparte nova quando não existe |
| 4 | search_templates_by_intent | Resolve template do tenant para "prestação de serviço" |
| 5 | create_workflow | Cria workflow padrão (todo contrato precisa de um) |
| 6 | create_contract | Cria o contrato com workflowId, datas e contraparte |
| 7 | request_signature | Dispara link público de assinatura |
Regra crítica: o agente sempre busca a contraparte (
search_contract_parties) antes de tentar cadastrar. Nunca assume que não existe.
Limites e regras (vêm dos prompts)
- Mensagens curtas, até 2-3 frases por bolha. Splitting é responsabilidade do AI API (
splitIntoBubbles): ele quebra por\n\n. - Confirmação explícita antes de qualquer mutação irreversível (criar contrato, disparar assinatura). Exige "sim", "confirma", "pode", não infere.
- Sem markdown rico, sem tabelas, sem listas longas. Máximo uma lista de 3 itens.
- Sem links a menos que o usuário peça explicitamente, o link de assinatura é exceção (faz parte da entrega).
- Vigência obrigatória antes de
create_contract. Se faltar, pergunta.
Infra & idempotência
- Endpoint:
POST /ai/v1/whatsapp/turnnocontrasync-ai-api(porta 4001). - Auth bot→AI: header
x-bot-api-key(envWHATSAPP_BOT_API_KEY). - JWT interno: o AI API minta um HS256 token (issuer
contrasync-ai-api/whatsapp) com o mesmoJWT_SECRETdanest-apipara que as tools chamem o Product API impersonando o usuário do WhatsApp. - Dedupe:
(whatsappNumber, messageId). Z-API faz retry; se o mesmomessageIdchega duas vezes, devolvemoskind: 'REPLAY'commessages: []e auditamoswhatsapp.duplicate_webhook. Bot não re-envia. - Sessão: uma
WhatsappSessionpor número (TTL 24h, configurável viaWHATSAPP_AI_SESSION_TTL_HOURS). Reuso se(userId, companyId, persona)continuam iguais. - Persistência: todo inbound + outbound vai pra
whatsapp_message(uma linha por bolha). Base para auditoria, replay de bug e construção do eval-set.
Resposta multi-bolha
A IA pode separar a resposta em até 5 bolhas (separador: linha em branco \n\n). Cada bolha vira uma mensagem Z-API separada, UX de chat real, não bloco de texto. Bolha longa (>1000 chars) é re-particionada por frase.
Esquema:
json
{
"kind": "AI",
"text": "Achei a Maria.\n\nQuer confirmar?",
"messages": ["Achei a Maria.", "Quer confirmar?"],
"sessionId": "...",
"persona": "WHATSAPP_PROVIDER",
"reused": false
}O bot itera messages[] em ordem, mandando uma Z-API call por bolha.
Erros & como diagnosticar
| Sintoma | Causa provável | Onde olhar |
|---|---|---|
| Usuário recebe mesma resposta 2× | dedupe falhou | tabela whatsapp_message external_id único deveria ter bloqueado |
| Bot manda 1 bolha gigante | AI não usou \n\n na resposta | logs do turn (bubbles=1); prompt da persona pode precisar de reforço |
| AI cria contraparte duplicada | pulou search_contract_parties | logs de tool-calls da sessão; abrir ai-tool-registry para revisar persona |
OUT_OF_HOURS quando deveria estar no horário | WHATSAPP_TIMEZONE errado | appConfig.whatsapp.operatingTimezone |
403 invalid_bot_api_key | env desalinhado | conferir WHATSAPP_BOT_API_KEY no bot (AI_API_BOT_API_KEY) e no ai-api |
O que NÃO está coberto pelo runbook (out of scope F3.3)
- PDF preview no chat: depende de geração no nest-api. Tracking: F3.5 (multimídia).
- Assinatura inline no chat (2FA OTP): F3.4 (semana 5).
- Buttons/list interativos Z-API: deferido.
- Multi-número por tenant: F3.7.
Eval (pendente)
Eval-set dedicado para esse fluxo deve ter pelo menos 10 transcrições reais (anonimizadas) cobrindo: contraparte existente, contraparte nova PJ, contraparte nova PF, falta de vigência, ambiguidade no template, usuário desistindo no meio. Tracking: F3.9.