Files
antigravity-skills-reference/skills/whatsapp-cloud-api/references/automation-patterns.md
ProgramadorBrasil 61ec71c5c7 feat: add 52 specialized AI agent skills (#217)
New skills covering 10 categories:

**Security & Audit**: 007 (STRIDE/PASTA/OWASP), cred-omega (secrets management)
**AI Personas**: Karpathy, Hinton, Sutskever, LeCun (4 sub-skills), Altman, Musk, Gates, Jobs, Buffett
**Multi-agent Orchestration**: agent-orchestrator, task-intelligence, multi-advisor
**Code Analysis**: matematico-tao (Terence Tao-inspired mathematical code analysis)
**Social & Messaging**: Instagram Graph API, Telegram Bot, WhatsApp Cloud API, social-orchestrator
**Image Generation**: AI Studio (Gemini), Stability AI, ComfyUI Gateway, image-studio router
**Brazilian Domain**: 6 auction specialist modules, 2 legal advisors, auctioneers data scraper
**Product & Growth**: design, invention, monetization, analytics, growth engine
**DevOps & LLM Ops**: Docker/CI-CD/AWS, RAG/embeddings/fine-tuning
**Skill Governance**: installer, sentinel auditor, context management

Each skill includes:
- Standardized YAML frontmatter (name, description, risk, source, tags, tools)
- Structured sections (Overview, When to Use, How it Works, Best Practices)
- Python scripts and reference documentation where applicable
- Cross-platform compatibility (Claude Code, Antigravity, Cursor, Gemini CLI, Codex CLI)

Co-authored-by: ProgramadorBrasil <214873561+ProgramadorBrasil@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:04:07 +01:00

19 KiB

Padroes de Automacao de Atendimento - WhatsApp Cloud API

Guia completo para implementar automacao de atendimento profissional via WhatsApp, incluindo chatbots, filas de atendimento, state machines e integracao com IA.


Indice

  1. Arquitetura de Automacao
  2. Menu Principal Interativo
  3. State Machine para Fluxos
  4. Gerenciamento de Sessao
  5. Fila de Atendimento
  6. Escalacao para Humano
  7. Respostas Fora do Horario
  8. Integracao com IA (Claude API)
  9. WhatsApp Flows para Formularios
  10. Fluxo End-to-End Completo

Arquitetura de Automacao

Cliente WhatsApp
       │
       ▼
   Webhook POST
       │
       ▼
  HMAC Validation
       │
       ▼
  Session Manager ──► Busca/cria sessao do cliente
       │
       ▼
  State Router ──► Determina handler baseado no estado atual
       │
       ├── INICIO → Menu Principal
       ├── MENU → Router de opcoes
       ├── SUPORTE → Fluxo de suporte
       ├── VENDAS → Catalogo/checkout
       ├── HUMANO → Fila de atendimento
       └── IA → Claude API handler

Menu Principal Interativo

Com Botoes (ate 3 opcoes)

Node.js:

async function sendMainMenuButtons(to: string): Promise<void> {
  await sendMessage({
    messaging_product: 'whatsapp',
    to,
    type: 'interactive',
    interactive: {
      type: 'button',
      header: { type: 'text', text: 'Bem-vindo!' },
      body: { text: 'Olá! Como posso ajudar você hoje?' },
      footer: { text: 'Selecione uma opção abaixo' },
      action: {
        buttons: [
          { type: 'reply', reply: { id: 'btn_suporte', title: 'Suporte' } },
          { type: 'reply', reply: { id: 'btn_vendas', title: 'Vendas' } },
          { type: 'reply', reply: { id: 'btn_info', title: 'Informações' } }
        ]
      }
    }
  });
}

Com Lista (ate 10 opcoes em secoes)

Python:

async def send_main_menu_list(to: str) -> None:
    await send_message({
        "messaging_product": "whatsapp",
        "to": to,
        "type": "interactive",
        "interactive": {
            "type": "list",
            "header": {"type": "text", "text": "Menu Principal"},
            "body": {"text": "Selecione o departamento:"},
            "footer": {"text": "Horário: Seg-Sex 8h-18h"},
            "action": {
                "button": "Ver opções",
                "sections": [
                    {
                        "title": "Atendimento",
                        "rows": [
                            {"id": "suporte_tecnico", "title": "Suporte Técnico", "description": "Problemas com produto ou serviço"},
                            {"id": "suporte_financeiro", "title": "Financeiro", "description": "Boletos, pagamentos, reembolsos"},
                            {"id": "suporte_comercial", "title": "Comercial", "description": "Novos pedidos e orçamentos"}
                        ]
                    },
                    {
                        "title": "Informações",
                        "rows": [
                            {"id": "info_horario", "title": "Horário de Funcionamento"},
                            {"id": "info_endereco", "title": "Endereço e Contato"},
                            {"id": "info_faq", "title": "Perguntas Frequentes"}
                        ]
                    }
                ]
            }
        }
    })

State Machine para Fluxos

Modelo de Estados

enum ConversationState {
  INICIO = 'INICIO',
  MENU_PRINCIPAL = 'MENU_PRINCIPAL',
  SUPORTE_TECNICO = 'SUPORTE_TECNICO',
  SUPORTE_AGUARDANDO_DETALHES = 'SUPORTE_AGUARDANDO_DETALHES',
  SUPORTE_AGUARDANDO_ANEXO = 'SUPORTE_AGUARDANDO_ANEXO',
  VENDAS_CATALOGO = 'VENDAS_CATALOGO',
  VENDAS_CHECKOUT = 'VENDAS_CHECKOUT',
  FINANCEIRO = 'FINANCEIRO',
  FINANCEIRO_SEGUNDA_VIA = 'FINANCEIRO_SEGUNDA_VIA',
  ATENDIMENTO_HUMANO = 'ATENDIMENTO_HUMANO',
  ATENDIMENTO_IA = 'ATENDIMENTO_IA',
  PESQUISA_NPS = 'PESQUISA_NPS',
  FINALIZADO = 'FINALIZADO'
}

Router de Estados

interface Session {
  phone: string;
  state: ConversationState;
  data: Record<string, any>;
  lastActivity: Date;
  agentId?: string;
}

async function routeMessage(session: Session, message: IncomingMessage): Promise<void> {
  const handlers: Record<ConversationState, MessageHandler> = {
    [ConversationState.INICIO]: handleInicio,
    [ConversationState.MENU_PRINCIPAL]: handleMenuPrincipal,
    [ConversationState.SUPORTE_TECNICO]: handleSuporteTecnico,
    [ConversationState.SUPORTE_AGUARDANDO_DETALHES]: handleAguardandoDetalhes,
    [ConversationState.VENDAS_CATALOGO]: handleVendasCatalogo,
    [ConversationState.FINANCEIRO]: handleFinanceiro,
    [ConversationState.ATENDIMENTO_HUMANO]: handleAtendimentoHumano,
    [ConversationState.ATENDIMENTO_IA]: handleAtendimentoIA,
    [ConversationState.PESQUISA_NPS]: handlePesquisaNPS,
    // ... demais estados
  };

  const handler = handlers[session.state] || handleInicio;
  await handler(session, message);
}

Exemplo de Handler

async function handleMenuPrincipal(session: Session, message: IncomingMessage): Promise<void> {
  const selectedId = message.interactive?.button_reply?.id
    || message.interactive?.list_reply?.id
    || message.text?.body?.toLowerCase();

  switch (selectedId) {
    case 'btn_suporte':
    case 'suporte_tecnico':
      session.state = ConversationState.SUPORTE_TECNICO;
      await sendText(session.phone, 'Entendido! Descreva seu problema e nossa equipe vai ajudar.');
      session.state = ConversationState.SUPORTE_AGUARDANDO_DETALHES;
      break;

    case 'btn_vendas':
    case 'suporte_comercial':
      session.state = ConversationState.VENDAS_CATALOGO;
      await sendProductCatalog(session.phone);
      break;

    case 'btn_info':
    case 'info_faq':
      await sendFAQ(session.phone);
      // Mantem no menu apos FAQ
      break;

    default:
      await sendText(session.phone, 'Desculpe, não entendi. Vou mostrar o menu novamente.');
      await sendMainMenuButtons(session.phone);
      break;
  }

  session.lastActivity = new Date();
  await saveSession(session);
}

Gerenciamento de Sessao

Com Redis (Producao)

import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);
const SESSION_TTL = 86400; // 24 horas (janela do WhatsApp)

async function getSession(phone: string): Promise<Session> {
  const data = await redis.get(`wa_session:${phone}`);
  if (data) {
    return JSON.parse(data);
  }
  return createNewSession(phone);
}

async function saveSession(session: Session): Promise<void> {
  session.lastActivity = new Date();
  await redis.set(
    `wa_session:${session.phone}`,
    JSON.stringify(session),
    'EX',
    SESSION_TTL
  );
}

function createNewSession(phone: string): Session {
  return {
    phone,
    state: ConversationState.INICIO,
    data: {},
    lastActivity: new Date()
  };
}

Com In-Memory (Desenvolvimento)

from datetime import datetime, timedelta
from typing import Dict, Optional

sessions: Dict[str, dict] = {}
SESSION_TTL = timedelta(hours=24)

def get_session(phone: str) -> dict:
    session = sessions.get(phone)
    if session and datetime.now() - session["last_activity"] < SESSION_TTL:
        return session
    return create_new_session(phone)

def save_session(session: dict) -> None:
    session["last_activity"] = datetime.now()
    sessions[session["phone"]] = session

def create_new_session(phone: str) -> dict:
    session = {
        "phone": phone,
        "state": "INICIO",
        "data": {},
        "last_activity": datetime.now()
    }
    sessions[phone] = session
    return session

Importante: A janela de 24h do WhatsApp permite respostas gratuitas por 24h apos a ultima mensagem do cliente. Use o TTL da sessao alinhado com esta janela.


Fila de Atendimento

Modelo de Fila com Prioridade

interface QueueItem {
  phone: string;
  department: string;
  priority: 'alta' | 'media' | 'baixa';
  enteredAt: Date;
  estimatedWait: number; // minutos
}

class AttendanceQueue {
  private queues: Map<string, QueueItem[]> = new Map();

  async addToQueue(item: QueueItem): Promise<number> {
    const dept = item.department;
    if (!this.queues.has(dept)) this.queues.set(dept, []);

    const queue = this.queues.get(dept)!;
    queue.push(item);

    // Ordenar por prioridade e depois por tempo de entrada
    queue.sort((a, b) => {
      const priorityOrder = { alta: 0, media: 1, baixa: 2 };
      if (priorityOrder[a.priority] !== priorityOrder[b.priority]) {
        return priorityOrder[a.priority] - priorityOrder[b.priority];
      }
      return a.enteredAt.getTime() - b.enteredAt.getTime();
    });

    const position = queue.indexOf(item) + 1;

    // Notificar cliente da posicao
    await sendText(item.phone,
      `Você está na posição ${position} da fila do setor ${dept}. ` +
      `Tempo estimado: ~${position * 5} minutos. Aguarde!`
    );

    return position;
  }

  async getNext(department: string): Promise<QueueItem | undefined> {
    const queue = this.queues.get(department);
    return queue?.shift();
  }
}

SLA e Monitoramento

const SLA_CONFIG = {
  suporte: { maxWaitMinutes: 15, alertAfterMinutes: 10 },
  vendas: { maxWaitMinutes: 5, alertAfterMinutes: 3 },
  financeiro: { maxWaitMinutes: 20, alertAfterMinutes: 15 }
};

async function checkSLABreaches(): Promise<void> {
  for (const [dept, config] of Object.entries(SLA_CONFIG)) {
    const queue = attendanceQueue.getQueue(dept);
    for (const item of queue) {
      const waitMinutes = (Date.now() - item.enteredAt.getTime()) / 60000;
      if (waitMinutes > config.maxWaitMinutes) {
        await alertSupervisor(dept, item, waitMinutes);
      }
    }
  }
}

Escalacao para Humano

Detectar Necessidade de Escalacao

const ESCALATION_TRIGGERS = [
  'falar com humano', 'falar com atendente', 'atendente',
  'pessoa real', 'humano', 'reclamacao', 'reclamar',
  'cancelar', 'cancelamento', 'insatisfeito', 'gerente'
];

function shouldEscalate(message: string): boolean {
  const lower = message.toLowerCase();
  return ESCALATION_TRIGGERS.some(trigger => lower.includes(trigger));
}

async function escalateToHuman(session: Session): Promise<void> {
  session.state = ConversationState.ATENDIMENTO_HUMANO;

  // Notificar cliente
  await sendText(session.phone,
    'Entendi! Vou transferir você para um de nossos atendentes. ' +
    'Por favor, aguarde um momento.'
  );

  // Adicionar a fila
  await attendanceQueue.addToQueue({
    phone: session.phone,
    department: session.data.department || 'geral',
    priority: session.data.isVIP ? 'alta' : 'media',
    enteredAt: new Date(),
    estimatedWait: 5
  });

  // Notificar painel de atendentes
  await notifyAgentPanel({
    type: 'new_customer',
    phone: session.phone,
    context: session.data,
    conversationHistory: session.data.history || []
  });

  await saveSession(session);
}

Transferencia de Contexto

Quando um humano assume, ele deve ver o historico da conversa automatizada:

async function buildHandoffContext(session: Session): string {
  return `
📋 Contexto da conversa:
- Cliente: ${session.phone}
- Departamento: ${session.data.department}
- Estado anterior: ${session.state}
- Problema relatado: ${session.data.problemDescription || 'Não especificado'}
- Tentativas do bot: ${session.data.botAttempts || 0}
- Tempo na conversa: ${getElapsedTime(session.data.startedAt)}

📝 Histórico resumido:
${session.data.history?.map(h => `[${h.from}] ${h.text}`).join('\n') || 'Sem histórico'}
  `.trim();
}

Respostas Fora do Horario

interface BusinessHours {
  timezone: string;
  schedule: Record<string, { open: string; close: string } | null>;
}

const BUSINESS_HOURS: BusinessHours = {
  timezone: 'America/Sao_Paulo',
  schedule: {
    monday: { open: '08:00', close: '18:00' },
    tuesday: { open: '08:00', close: '18:00' },
    wednesday: { open: '08:00', close: '18:00' },
    thursday: { open: '08:00', close: '18:00' },
    friday: { open: '08:00', close: '17:00' },
    saturday: { open: '09:00', close: '13:00' },
    sunday: null // fechado
  }
};

function isWithinBusinessHours(): boolean {
  const now = new Date().toLocaleString('en-US', { timeZone: BUSINESS_HOURS.timezone });
  const date = new Date(now);
  const day = date.toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase();
  const hours = BUSINESS_HOURS.schedule[day];

  if (!hours) return false;

  const currentTime = date.toTimeString().slice(0, 5);
  return currentTime >= hours.open && currentTime <= hours.close;
}

async function handleOffHours(phone: string): Promise<void> {
  // Enviar template (fora da janela de 24h pode nao ter sessao ativa)
  await sendText(phone,
    '⏰ Nosso horário de atendimento é:\n' +
    'Seg-Qui: 8h às 18h\n' +
    'Sex: 8h às 17h\n' +
    'Sáb: 9h às 13h\n\n' +
    'Deixe sua mensagem que retornaremos assim que possível!'
  );
}

Integracao com IA (Claude API)

Chatbot Inteligente com Claude

import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic();

const SYSTEM_PROMPT = `Você é um assistente virtual da empresa [NOME]. Sua função é:
- Responder dúvidas sobre produtos e serviços
- Ajudar com problemas técnicos simples
- Encaminhar para atendente humano quando necessário
- Ser cordial, profissional e objetivo
- Responder em português brasileiro

Regras:
- Máximo 300 caracteres por resposta (limite do WhatsApp para boa leitura)
- Se não souber a resposta, diga que vai transferir para um especialista
- Nunca invente informações sobre preços ou disponibilidade
- Use emojis com moderação`;

async function getAIResponse(
  session: Session,
  userMessage: string
): Promise<{ text: string; shouldEscalate: boolean }> {
  const messages = (session.data.aiHistory || []).concat([
    { role: 'user', content: userMessage }
  ]);

  const response = await anthropic.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 300,
    system: SYSTEM_PROMPT,
    messages
  });

  const aiText = response.content[0].type === 'text'
    ? response.content[0].text
    : '';

  // Detectar se a IA sugere escalacao
  const shouldEscalate = aiText.toLowerCase().includes('transferir')
    || aiText.toLowerCase().includes('especialista')
    || aiText.toLowerCase().includes('atendente');

  // Salvar historico
  session.data.aiHistory = messages.concat([
    { role: 'assistant', content: aiText }
  ]);

  return { text: aiText, shouldEscalate };
}

async function handleAtendimentoIA(session: Session, message: IncomingMessage): Promise<void> {
  const userText = message.text?.body || '[mídia recebida]';
  const { text, shouldEscalate } = await getAIResponse(session, userText);

  await sendText(session.phone, text);

  if (shouldEscalate) {
    await escalateToHuman(session);
  }
}

Limite de Tentativas do Bot

const MAX_BOT_ATTEMPTS = 3;

async function handleWithFallback(session: Session, message: IncomingMessage): Promise<void> {
  session.data.botAttempts = (session.data.botAttempts || 0) + 1;

  if (session.data.botAttempts >= MAX_BOT_ATTEMPTS) {
    await sendText(session.phone,
      'Parece que não estou conseguindo ajudar. Vou transferir para um atendente.'
    );
    await escalateToHuman(session);
    return;
  }

  await handleAtendimentoIA(session, message);
}

WhatsApp Flows para Formularios

WhatsApp Flows permite criar formularios interativos multi-tela. Exemplo de Flow de agendamento:

Enviar Flow

async function sendAppointmentFlow(to: string, flowId: string): Promise<void> {
  await sendMessage({
    messaging_product: 'whatsapp',
    to,
    type: 'interactive',
    interactive: {
      type: 'flow',
      header: { type: 'text', text: 'Agendar Consulta' },
      body: { text: 'Preencha o formulário para agendar sua consulta.' },
      footer: { text: 'Seus dados estão protegidos' },
      action: {
        name: 'flow',
        parameters: {
          flow_message_version: '3',
          flow_id: flowId,
          flow_cta: 'Agendar agora',
          flow_action: 'navigate',
          flow_action_payload: {
            screen: 'APPOINTMENT_SCREEN',
            data: {
              available_dates: ['2026-03-01', '2026-03-02', '2026-03-03']
            }
          }
        }
      }
    }
  });
}

Receber Resposta do Flow

As respostas do Flow chegam via webhook como mensagem interativa:

function handleFlowResponse(message: IncomingMessage): void {
  if (message.type === 'interactive' && message.interactive?.type === 'nfm_reply') {
    const flowResponse = JSON.parse(message.interactive.nfm_reply.response_json);
    // flowResponse contem os dados preenchidos pelo usuario
    console.log('Dados do flow:', flowResponse);
    // Ex: { date: '2026-03-01', time: '14:00', name: 'João Silva' }
  }
}

Para mais detalhes sobre WhatsApp Flows, leia references/advanced-features.md.


Fluxo End-to-End Completo

Webhook Handler Principal

app.post('/webhook', validateHMAC, async (req, res) => {
  // Responder 200 imediatamente (requisito: < 5 segundos)
  res.sendStatus(200);

  try {
    const entry = req.body.entry?.[0];
    const changes = entry?.changes?.[0];
    const value = changes?.value;

    // Processar mensagens
    if (value?.messages) {
      for (const message of value.messages) {
        await processIncomingMessage(message);
      }
    }

    // Processar status updates
    if (value?.statuses) {
      for (const status of value.statuses) {
        await processStatusUpdate(status);
      }
    }
  } catch (error) {
    console.error('Erro ao processar webhook:', error);
    // Nao retornar erro - ja respondeu 200
  }
});

async function processIncomingMessage(message: IncomingMessage): Promise<void> {
  const phone = message.from;
  const session = await getSession(phone);

  // Marcar como lida
  await markAsRead(message.id);

  // Verificar horario de funcionamento
  if (!isWithinBusinessHours() && session.state === ConversationState.INICIO) {
    await handleOffHours(phone);
    return;
  }

  // Verificar triggers de escalacao
  if (message.text?.body && shouldEscalate(message.text.body)) {
    await escalateToHuman(session);
    await saveSession(session);
    return;
  }

  // Se e uma nova conversa, enviar menu
  if (session.state === ConversationState.INICIO) {
    session.state = ConversationState.MENU_PRINCIPAL;
    await sendMainMenuButtons(phone);
    await saveSession(session);
    return;
  }

  // Rotear para o handler correto
  await routeMessage(session, message);
}

Este fluxo garante:

  1. Resposta HTTP 200 imediata (requisito WhatsApp)
  2. Validacao HMAC de seguranca
  3. Gerenciamento de sessao com estado
  4. Verificacao de horario de funcionamento
  5. Deteccao de escalacao
  6. Roteamento por estado da conversa
  7. Marcacao automatica como lida