Skip to content

Assistente IA via WhatsApp

O PopinaFlow integra o assistente de IA ao canal WhatsApp, permitindo que admins e staff gerenciem o restaurante pelo WhatsApp do restaurante. Clientes também recebem um bot de pedidos conversacional por IA, substituindo o antigo bot de regras.


Arquitetura

Mensagem recebida → POST /webhooks/whatsapp/:tenantId

  Validação HMAC-SHA256 (por-tenant secret)

  findVerifiedStaffByPhone(tenantId, phone)
    ├── Encontrado (admin/staff verificado) → WhatsAppAdminAiService
    └── Não encontrado (cliente)             → WhatsAppCustomerAiService
         ↓                                            ↓
  AssistantService.chatSync()              AssistantService.chatSync()
  (prompt de gestão, todas as tools)       (prompt de pedidos, tools limitadas)
         ↓                                            ↓
  WhatsAppService.sendTextMessageFromInstance()

O método AssistantService.chatSync() é a versão não-streaming do chat() existente — mesma lógica de contexto, ferramentas e histórico, mas retorna string completa em vez de SSE chunks.


Verificação de Telefone (OTP)

Antes de usar o assistente, admin/staff precisam vincular o número pessoal de WhatsApp à conta.

Endpoints

POST /t/:slug/auth/profile/phone/send-otp
  Guard: JwtAuthGuard + TenantGuard
  Body: { phone: string }
  → Gera OTP 6 dígitos, armazena no Redis com TTL 600s
  → Envia mensagem WhatsApp via instância do tenant
  → Rate-limit: 1 envio/min por usuário (Redis TTL 60s)

POST /t/:slug/auth/profile/phone/confirm-otp
  Guard: JwtAuthGuard + TenantGuard
  Body: { code: string }
  → Valida OTP (máx 3 tentativas)
  → Marca User.phoneVerified = true
  → Remove chave Redis

Redis keys

ChaveTTLConteúdo
phone-otp:{userId}600s{ code, phone, attempts }
phone-otp-rl:{userId}60s"1" (rate-limit flag)

Schema

typescript
// user.schema.ts
@Prop({ default: false })
phoneVerified: boolean;

Serviço Admin/Staff — WhatsAppAdminAiService

Arquivo: backend/src/assistant/whatsapp-admin-assistant.service.ts

Session ID

whatsapp-admin-{tenantId}-{userId}

Sessão persistente — sem expiração. Comandos especiais:

ComandoAção
/limpar /cleardeleteSession(tenantId, sessionId) → nova conversa
/ajuda /helpResposta fixa com lista de capacidades
/statusChama buildContext(tenantId), extrai e formata os dados do dia

Contexto carregado

  • Últimas 30 mensagens da sessão (getSession(tenantId, sessionId))
  • System prompt: buildContext(tenantId) (mesmo do browser — pedidos, estoque, reservas, mesas)
  • Role: isAdmin = user.role in ['admin', 'superadmin']
  • Ferramentas: todas as 10 ferramentas para admin; subset para staff (mesmo comportamento do browser)

Serviço Cliente — WhatsAppCustomerAiService

Arquivo: backend/src/assistant/whatsapp-customer-assistant.service.ts

Session ID

whatsapp-customer-{tenantId}-{phone}

Sessão expira naturalmente (sem TTL ativo — historicamente o bot usava 2h via MongoDB TTL no WhatsAppConversation).

Contexto carregado

  • Últimas 20 mensagens da sessão
  • System prompt customizado (cliente): cardápio completo, nome do restaurante, regras de uso
  • user = null (anônimo)
  • isAdmin = false

Ferramentas disponíveis (cliente)

Somente create_order e confirm_reservation. O AssistantService.chatSync() chama getToolsForRole(false) — qualquer ferramenta destrutiva ou de gestão é bloqueada automaticamente.


AssistantService.chatSync()

Arquivo: backend/src/assistant/assistant.service.ts

typescript
async chatSync(
  tenantId: string,
  userId: string | null,
  isAdmin: boolean,
  messages: ChatMessage[],
  sessionId: string,
  systemPromptOverride?: string,
): Promise<{ text: string; toolEvents: Record<string, any>[] }>

Diferenças em relação a chat():

chat() (SSE)chatSync() (WhatsApp)
Phase 2: stream: true → SSE chunksPhase 2: stream: false → string completa
Retorna AsyncGenerator<string>Retorna Promise<{ text, toolEvents }>
userId obrigatóriouserId pode ser null (cliente anônimo)
systemPromptOverride não existesystemPromptOverride permite prompt de cliente

O histórico é salvo via AssistantHistoryService.saveMessage() — mesmo schema ConversationMessage.


Utilitários — whatsapp-ai.utils.ts

Arquivo: backend/src/assistant/whatsapp-ai.utils.ts

typescript
markdownToWhatsApp(text: string): string
// **bold** → *bold*, *italic* → _italic_, # Header → *Header*, - → •

splitForWhatsApp(text: string, maxChars = 4000): string[]
// Divide em chunks ≤ 4000 chars nos \n\n mais próximos

sleep(ms: number): Promise<void>
// Delay entre chunks sequenciais (600ms padrão)

Roteamento do Webhook

Arquivo: backend/src/whatsapp-ordering/whatsapp-ordering.controller.ts

O antigo WhatsAppOrderingService (bot de regras) foi removido do fluxo principal. O controller agora:

  1. Valida HMAC-SHA256 usando tenant.whatsappWebhookSecret
  2. Normaliza o telefone (E.164: adiciona 55 se ausente)
  3. Busca UserTenant onde tenant = tenantId e role in [admin, staff], filtra por user.phone = phone AND user.phoneVerified = true
  4. Se encontrado → WhatsAppAdminAiService.handle() (fire-and-forget)
  5. Se não encontrado → WhatsAppCustomerAiService.handle() (fire-and-forget)
  6. Retorna 200 imediatamente

Módulos modificados

MóduloMudança
AssistantModuleExporta AssistantService e AssistantHistoryService
WhatsAppOrderingModuleImporta AssistantModule + UsersModule; registra MenuItem schema; providers: WhatsAppAdminAiService, WhatsAppCustomerAiService
UsersModuleImporta RedisModule (para OTP)
AuthModuleInjeta WhatsAppService e TenantsService no TenantAuthController

Referência de arquivos

backend/src/
  assistant/
    assistant.service.ts                    → chatSync() adicionado
    assistant.module.ts                     → exports expandidos
    whatsapp-admin-assistant.service.ts     → handler admin/staff (NOVO)
    whatsapp-customer-assistant.service.ts  → handler cliente (NOVO)
    whatsapp-ai.utils.ts                    → formatação e split (NOVO)
  auth/
    tenant-auth.controller.ts               → endpoints OTP
  users/
    schemas/user.schema.ts                  → campo phoneVerified
    users.service.ts                        → generatePhoneOtp, confirmPhoneOtp, findVerifiedStaffByPhone
    users.module.ts                         → importa RedisModule
    dto/verify-phone.dto.ts                 → SendOtpDto, ConfirmOtpDto (NOVO)
  notifications/
    whatsapp.service.ts                     → sendTextMessageFromInstance()
  whatsapp-ordering/
    whatsapp-ordering.controller.ts         → roteamento admin vs cliente
    whatsapp-ordering.module.ts             → módulos e providers atualizados
frontend-react/src/
  types/index.ts                            → User.phoneVerified
  views/admin/AdminProfileView.tsx          → WhatsAppVerificationCard

Lançado sob a licença MIT.