Skip to content

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: providerWHATSAPP_PROVIDER, borrowerWHATSAPP_BORROWER.

Caminho feliz (exemplo)

  1. 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º"
  2. Agente (1 bolha): "Achei a Maria Silva pelo nome. Confirma que é ela? (CPF cadastrado termina em ***-09)"
  3. Usuário: "sim"
  4. 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?"
  5. Usuário: "confirmo"
  6. 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)

PassoToolFunção
1search_contract_partiesAcha contraparte pelo nome/CPF/CNPJ informado
2lookup_company_by_cnpj (PJ)Valida CNPJ se a parte for empresa nova
3create_natural_person ou register_company_partyCadastra contraparte nova quando não existe
4search_templates_by_intentResolve template do tenant para "prestação de serviço"
5create_workflowCria workflow padrão (todo contrato precisa de um)
6create_contractCria o contrato com workflowId, datas e contraparte
7request_signatureDispara 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/turn no contrasync-ai-api (porta 4001).
  • Auth bot→AI: header x-bot-api-key (env WHATSAPP_BOT_API_KEY).
  • JWT interno: o AI API minta um HS256 token (issuer contrasync-ai-api/whatsapp) com o mesmo JWT_SECRET da nest-api para que as tools chamem o Product API impersonando o usuário do WhatsApp.
  • Dedupe: (whatsappNumber, messageId). Z-API faz retry; se o mesmo messageId chega duas vezes, devolvemos kind: 'REPLAY' com messages: [] e auditamos whatsapp.duplicate_webhook. Bot não re-envia.
  • Sessão: uma WhatsappSession por número (TTL 24h, configurável via WHATSAPP_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

SintomaCausa provávelOnde olhar
Usuário recebe mesma resposta 2×dedupe falhoutabela whatsapp_message external_id único deveria ter bloqueado
Bot manda 1 bolha giganteAI não usou \n\n na respostalogs do turn (bubbles=1); prompt da persona pode precisar de reforço
AI cria contraparte duplicadapulou search_contract_partieslogs de tool-calls da sessão; abrir ai-tool-registry para revisar persona
OUT_OF_HOURS quando deveria estar no horárioWHATSAPP_TIMEZONE erradoappConfig.whatsapp.operatingTimezone
403 invalid_bot_api_keyenv desalinhadoconferir 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.