Pular para conteúdo

Tutorial: Sistema Automatizado de Feedback de Clientes com Análise de Sentimento

Aprenda a criar um sistema completo que coleta, analisa e age sobre feedback de clientes automaticamente, usando IA para análise de sentimento e priorização inteligente de ações.

O Que Você Vai Construir

Um sistema de feedback 360° que: 1. Coleta feedback de múltiplos canais (email, WhatsApp, Forms, NPS) 2. Analisa sentimento com IA (AWS Comprehend ou OpenAI) 3. Classifica e prioriza automaticamente 4. Roteia para os departamentos corretos 5. Cria tickets em ferramentas de suporte 6. Envia alertas para feedback negativo crítico 7. Gera relatórios e insights automaticamente 8. Fecha o loop respondendo ao cliente

Tempo estimado: 50 minutos Nível: Intermediário Impacto esperado: +35% na satisfação do cliente, -70% no tempo de resposta

O Que Você Vai Aprender

  • Integração com ferramentas de pesquisa (Typeform, Google Forms, SurveyMonkey)
  • Análise de sentimento com AWS Comprehend e OpenAI
  • Extração de tópicos e categorização automática
  • Roteamento inteligente baseado em contexto
  • Integração com Zendesk, Jira, Slack
  • Geração de relatórios com charts
  • Automação de follow-up

Pré-requisitos

  • ✅ Conta AWS (para Comprehend) OU OpenAI API key
  • ✅ Ferramenta de pesquisa (Typeform, Google Forms, ou similar)
  • ✅ Sistema de tickets (Zendesk, Jira, ou similar)
  • ✅ Slack workspace
  • ✅ PostgreSQL configurado

Parte 1: Setup e Estrutura de Dados

1.1. Criar Tabelas no Banco de Dados

-- Tabela principal de feedback
CREATE TABLE customer_feedback (
  id SERIAL PRIMARY KEY,
  feedback_id VARCHAR(255) UNIQUE,

  -- Cliente
  customer_email VARCHAR(255),
  customer_name VARCHAR(255),
  customer_id VARCHAR(255),
  customer_segment VARCHAR(100),

  -- Feedback
  source VARCHAR(100), -- typeform, whatsapp, email, nps, etc
  type VARCHAR(50), -- nps, csat, review, complaint, suggestion
  rating INT, -- 0-10 para NPS, 1-5 para CSAT
  comment TEXT,
  language VARCHAR(10) DEFAULT 'pt',

  -- Análise
  sentiment VARCHAR(20), -- positive, neutral, negative, mixed
  sentiment_score DECIMAL(5,4), -- 0-1
  emotions JSONB, -- {happy: 0.8, angry: 0.2, ...}
  topics TEXT[], -- ["suporte", "preço", "usabilidade"]
  keywords TEXT[],
  category VARCHAR(100), -- product, support, billing, etc
  priority VARCHAR(20), -- critical, high, medium, low
  urgency_score INT, -- 0-100

  -- Contexto
  product_mentioned VARCHAR(255),
  feature_mentioned VARCHAR(255),
  metadata JSONB,

  -- Ações
  assigned_to VARCHAR(255),
  assigned_at TIMESTAMP,
  ticket_id VARCHAR(255),
  ticket_url VARCHAR(500),
  status VARCHAR(50) DEFAULT 'new', -- new, assigned, in_progress, resolved, closed
  resolution_notes TEXT,
  resolved_at TIMESTAMP,
  response_sent_at TIMESTAMP,

  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Tabela de respostas automáticas
CREATE TABLE feedback_responses (
  id SERIAL PRIMARY KEY,
  feedback_id INT REFERENCES customer_feedback(id),
  response_type VARCHAR(50), -- auto_ack, follow_up, resolution
  channel VARCHAR(50), -- email, whatsapp, sms
  message TEXT,
  sent_at TIMESTAMP DEFAULT NOW()
);

-- Tabela de métricas agregadas
CREATE TABLE feedback_metrics (
  id SERIAL PRIMARY KEY,
  period_start DATE NOT NULL,
  period_end DATE NOT NULL,
  metric_type VARCHAR(100),
  metric_value DECIMAL(10,2),
  metadata JSONB,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Índices
CREATE INDEX idx_feedback_email ON customer_feedback(customer_email);
CREATE INDEX idx_feedback_sentiment ON customer_feedback(sentiment);
CREATE INDEX idx_feedback_priority ON customer_feedback(priority);
CREATE INDEX idx_feedback_status ON customer_feedback(status);
CREATE INDEX idx_feedback_created ON customer_feedback(created_at DESC);
CREATE INDEX idx_feedback_topics ON customer_feedback USING GIN(topics);

1.2. Configurar Variáveis do Flow

No Lumina Flow Builder, defina variáveis globais:

{
  "criticalKeywords": ["bug", "erro", "falha", "problema grave", "não funciona", "perdeu dados"],
  "urgentDepartments": {
    "suporte": "suporte@empresa.com",
    "produto": "produto@empresa.com",
    "financeiro": "financeiro@empresa.com",
    "vendas": "vendas@empresa.com"
  },
  "slackChannels": {
    "critical": "C024BE7LR",
    "feedbackGeneral": "C024BE7LS"
  },
  "responseTemplates": {
    "positive": "Obrigado pelo feedback positivo!",
    "negative": "Lamentamos sua experiência. Vamos resolver isso!"
  }
}

Parte 2: Captura de Feedback

2.1. Webhook para Typeform

Crie um novo flow com Webhook Trigger.

Configurar no Typeform: 1. Vá para ConnectWebhooks 2. Cole a URL do webhook do Lumina 3. Teste a conexão

2.2. Normalizar Dados de Entrada

// Transform Node: "Normalizar Feedback"
{{
  (() => {
    const body = $trigger.body;

    // Detectar fonte
    let source = 'unknown';
    let data = {};

    // Typeform
    if (body.form_response) {
      source = 'typeform';
      const answers = body.form_response.answers;
      data = {
        email: answers.find(a => a.type === 'email')?.email || '',
        name: answers.find(a => a.field.ref === 'name')?.text || '',
        rating: answers.find(a => a.type === 'number' || a.type === 'rating')?.number || null,
        comment: answers.find(a => a.type === 'text' || a.type === 'textarea')?.text || '',
        responseId: body.form_response.token
      };
    }
    // Google Forms (via Zapier/Make)
    else if (body.formResponse) {
      source = 'google_forms';
      data = {
        email: body.formResponse.email || '',
        name: body.formResponse.name || '',
        rating: parseInt(body.formResponse.rating) || null,
        comment: body.formResponse.comment || '',
        responseId: body.formResponse.responseId
      };
    }
    // WhatsApp
    else if (body.type === 'text' && body.from) {
      source = 'whatsapp';
      data = {
        phone: body.from,
        comment: body.text?.body || '',
        responseId: body.id
      };
    }
    // Email direto
    else if (body.subject && body.text) {
      source = 'email';
      data = {
        email: body.from || '',
        name: body.fromName || '',
        comment: body.text || body.html || '',
        subject: body.subject || '',
        responseId: body.messageId
      };
    }
    // NPS específico
    else if (body.nps_score !== undefined) {
      source = 'nps';
      data = {
        email: body.email || '',
        name: body.name || '',
        rating: body.nps_score,
        comment: body.feedback || '',
        responseId: body.response_id
      };
    }
    // Fallback genérico
    else {
      data = {
        email: body.email || '',
        name: body.name || '',
        rating: body.rating || body.score || null,
        comment: body.comment || body.feedback || body.message || '',
        responseId: body.id || body.response_id || Date.now().toString()
      };
    }

    // Determinar tipo de feedback baseado em rating
    let type = 'review';
    if (data.rating !== null) {
      if (data.rating >= 0 && data.rating <= 10) {
        type = 'nps';
      } else if (data.rating >= 1 && data.rating <= 5) {
        type = 'csat';
      }
    } else if (data.comment.length > 200) {
      type = 'review';
    } else if (data.comment.toLowerCase().includes('problema') || data.comment.toLowerCase().includes('erro')) {
      type = 'complaint';
    } else if (data.comment.toLowerCase().includes('sugestão') || data.comment.toLowerCase().includes('ideia')) {
      type = 'suggestion';
    }

    return {
      source,
      type,
      feedbackId: `${source}_${data.responseId}`,
      customer: {
        email: data.email,
        name: data.name,
        phone: data.phone || null
      },
      rating: data.rating,
      comment: data.comment,
      subject: data.subject || '',
      metadata: {
        originalPayload: body,
        receivedAt: new Date().toISOString()
      }
    };
  })()
}}

2.3. Validação

// If/Else: "Tem Comentário ou Rating?"
{{
  ($nodes['normalizar'].output.comment && $nodes['normalizar'].output.comment.trim().length > 0) ||
  ($nodes['normalizar'].output.rating !== null && $nodes['normalizar'].output.rating !== undefined)
}}

Branch FALSE: Enviar resposta pedindo mais detalhes e encerrar.

Parte 3: Análise de Sentimento com IA

3.1. Detectar Idioma

// HTTP Request: "Detectar Idioma (AWS Comprehend)"
{
  "method": "POST",
  "url": "https://comprehend.us-east-1.amazonaws.com/",
  "headers": {
    "X-Amz-Target": "Comprehend_20171127.DetectDominantLanguage",
    "Content-Type": "application/x-amz-json-1.1"
  },
  "authentication": "aws",
  "body": {
    "Text": "{{ $nodes['normalizar'].output.comment }}"
  }
}
// Transform: "Processar Idioma"
{{
  {
    "language": $nodes['detectar-idioma'].output.Languages?.[0]?.LanguageCode || 'pt',
    "confidence": $nodes['detectar-idioma'].output.Languages?.[0]?.Score || 0
  }
}}

3.2. Análise de Sentimento (AWS Comprehend)

// HTTP Request: "Analisar Sentimento"
{
  "method": "POST",
  "url": "https://comprehend.us-east-1.amazonaws.com/",
  "headers": {
    "X-Amz-Target": "Comprehend_20171127.DetectSentiment",
    "Content-Type": "application/x-amz-json-1.1"
  },
  "authentication": "aws",
  "body": {
    "Text": "{{ $nodes['normalizar'].output.comment }}",
    "LanguageCode": "{{ $nodes['processar-idioma'].output.language }}"
  }
}

Alternativa: OpenAI GPT-4

// HTTP Request: "Analisar com OpenAI"
{
  "method": "POST",
  "url": "https://api.openai.com/v1/chat/completions",
  "headers": {
    "Authorization": "Bearer {{ $credentials.openai.apiKey }}",
    "Content-Type": "application/json"
  },
  "body": {
    "model": "gpt-4",
    "messages": [
      {
        "role": "system",
        "content": "Você é um analisador de feedback de clientes. Analise o sentimento, emoções, tópicos e urgência do feedback. Responda APENAS com JSON válido."
      },
      {
        "role": "user",
        "content": `Analise este feedback:

Texto: "{{ $nodes['normalizar'].output.comment }}"
Rating: {{ $nodes['normalizar'].output.rating || 'N/A' }}

Retorne JSON com:
{
  "sentiment": "positive|neutral|negative|mixed",
  "sentimentScore": 0-1,
  "emotions": {"emotion": score},
  "topics": ["topic1", "topic2"],
  "category": "product|support|billing|other",
  "urgency": 0-100,
  "keywords": ["keyword1"],
  "summary": "resumo em 1 linha"
}`
      }
    ],
    "temperature": 0.3,
    "response_format": { "type": "json_object" }
  }
}
// Transform: "Processar Análise OpenAI"
{{
  (() => {
    const response = JSON.parse($nodes['analisar-openai'].output.choices[0].message.content);

    return {
      sentiment: response.sentiment,
      sentimentScore: response.sentimentScore,
      emotions: response.emotions,
      topics: response.topics,
      category: response.category,
      urgency: response.urgency,
      keywords: response.keywords,
      summary: response.summary
    };
  })()
}}

3.3. Extrair Entidades e Tópicos

Se usar AWS Comprehend:

// HTTP Request: "Extrair Entidades"
{
  "method": "POST",
  "url": "https://comprehend.us-east-1.amazonaws.com/",
  "headers": {
    "X-Amz-Target": "Comprehend_20171127.DetectEntities",
    "Content-Type": "application/x-amz-json-1.1"
  },
  "authentication": "aws",
  "body": {
    "Text": "{{ $nodes['normalizar'].output.comment }}",
    "LanguageCode": "{{ $nodes['processar-idioma'].output.language }}"
  }
}
// HTTP Request: "Extrair Frases-Chave"
{
  "method": "POST",
  "url": "https://comprehend.us-east-1.amazonaws.com/",
  "headers": {
    "X-Amz-Target": "Comprehend_20171127.DetectKeyPhrases",
    "Content-Type": "application/x-amz-json-1.1"
  },
  "authentication": "aws",
  "body": {
    "Text": "{{ $nodes['normalizar'].output.comment }}",
    "LanguageCode": "{{ $nodes['processar-idioma'].output.language }}"
  }
}

3.4. Consolidar Análise

// Function Node: "Consolidar Análise"
{{
  (() => {
    // Dados do OpenAI ou Comprehend
    const analysis = $nodes['processar-analise-openai']?.output || {};
    const sentiment = $nodes['analisar-sentimento']?.output;

    // Se usou Comprehend
    if (sentiment && !analysis.sentiment) {
      analysis.sentiment = sentiment.Sentiment?.toLowerCase() || 'neutral';
      analysis.sentimentScore = sentiment.SentimentScore?.[sentiment.Sentiment] || 0.5;
    }

    // Extrair produtos/features mencionados
    const comment = $nodes['normalizar'].output.comment.toLowerCase();
    let productMentioned = null;
    let featureMentioned = null;

    const products = ['flow builder', 'api', 'integracoes', 'dashboard'];
    const features = ['webhook', 'whatsapp', 'email', 'automation'];

    products.forEach(p => {
      if (comment.includes(p)) productMentioned = p;
    });

    features.forEach(f => {
      if (comment.includes(f)) featureMentioned = f;
    });

    // Calcular prioridade
    let priority = 'low';
    const urgency = analysis.urgency || 0;
    const rating = $nodes['normalizar'].output.rating;

    if (analysis.sentiment === 'negative' && urgency > 70) {
      priority = 'critical';
    } else if (analysis.sentiment === 'negative' && urgency > 50) {
      priority = 'high';
    } else if (rating !== null && rating <= 6) {
      priority = 'high';
    } else if (urgency > 60) {
      priority = 'medium';
    }

    // Verificar palavras críticas
    const criticalKeywords = $vars.criticalKeywords || [];
    const hasCriticalKeyword = criticalKeywords.some(keyword =>
      comment.includes(keyword.toLowerCase())
    );

    if (hasCriticalKeyword) {
      priority = 'critical';
    }

    return {
      ...analysis,
      productMentioned,
      featureMentioned,
      priority,
      isCritical: priority === 'critical'
    };
  })()
}}

Parte 4: Categorização e Roteamento

4.1. Determinar Departamento Responsável

// Function Node: "Determinar Departamento"
{{
  (() => {
    const category = $nodes['consolidar-analise'].output.category;
    const topics = $nodes['consolidar-analise'].output.topics || [];
    const sentiment = $nodes['consolidar-analise'].output.sentiment;

    let department = 'suporte'; // default
    let assignedTo = $vars.urgentDepartments.suporte;

    // Regras de categorização
    if (category === 'billing' || topics.includes('pagamento') || topics.includes('fatura')) {
      department = 'financeiro';
      assignedTo = $vars.urgentDepartments.financeiro;
    } else if (category === 'product' || topics.includes('feature') || topics.includes('funcionalidade')) {
      department = 'produto';
      assignedTo = $vars.urgentDepartments.produto;
    } else if (topics.includes('vendas') || topics.includes('plano') || topics.includes('upgrade')) {
      department = 'vendas';
      assignedTo = $vars.urgentDepartments.vendas;
    } else if (category === 'support' || sentiment === 'negative') {
      department = 'suporte';
      assignedTo = $vars.urgentDepartments.suporte;
    }

    return {
      department,
      assignedTo,
      routingReason: `Category: ${category}, Topics: ${topics.join(', ')}, Sentiment: ${sentiment}`
    };
  })()
}}

4.2. Salvar no Banco de Dados

// Database Insert: "Salvar Feedback"
{
  "table": "customer_feedback",
  "data": {
    "feedback_id": "{{ $nodes['normalizar'].output.feedbackId }}",
    "customer_email": "{{ $nodes['normalizar'].output.customer.email }}",
    "customer_name": "{{ $nodes['normalizar'].output.customer.name }}",
    "source": "{{ $nodes['normalizar'].output.source }}",
    "type": "{{ $nodes['normalizar'].output.type }}",
    "rating": "{{ $nodes['normalizar'].output.rating }}",
    "comment": "{{ $nodes['normalizar'].output.comment }}",
    "language": "{{ $nodes['processar-idioma']?.output.language || 'pt' }}",

    "sentiment": "{{ $nodes['consolidar-analise'].output.sentiment }}",
    "sentiment_score": "{{ $nodes['consolidar-analise'].output.sentimentScore }}",
    "emotions": "{{ JSON.stringify($nodes['consolidar-analise'].output.emotions || {}) }}",
    "topics": "{{ JSON.stringify($nodes['consolidar-analise'].output.topics || []) }}",
    "keywords": "{{ JSON.stringify($nodes['consolidar-analise'].output.keywords || []) }}",
    "category": "{{ $nodes['consolidar-analise'].output.category }}",
    "priority": "{{ $nodes['consolidar-analise'].output.priority }}",
    "urgency_score": "{{ $nodes['consolidar-analise'].output.urgency || 0 }}",

    "product_mentioned": "{{ $nodes['consolidar-analise'].output.productMentioned }}",
    "feature_mentioned": "{{ $nodes['consolidar-analise'].output.featureMentioned }}",
    "metadata": "{{ JSON.stringify($nodes['normalizar'].output.metadata) }}",

    "assigned_to": "{{ $nodes['determinar-depto'].output.assignedTo }}",
    "assigned_at": "{{ new Date().toISOString() }}",
    "status": "{{ $nodes['consolidar-analise'].output.isCritical ? 'assigned' : 'new' }}",
    "created_at": "{{ new Date().toISOString() }}"
  },
  "returning": ["id"]
}

Parte 5: Criar Ticket em Sistema de Suporte

5.1. Criar Ticket no Zendesk

// HTTP Request: "Criar Ticket Zendesk"
{
  "method": "POST",
  "url": "https://{{ $credentials.zendesk.subdomain }}.zendesk.com/api/v2/tickets",
  "authentication": "basicAuth",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {
    "ticket": {
      "subject": "{{ $nodes['consolidar-analise'].output.sentiment === 'negative' ? '🚨' : '📝' }} Feedback {{ $nodes['normalizar'].output.type }} - {{ $nodes['normalizar'].output.customer.name || 'Cliente' }}",
      "comment": {
        "body": `FEEDBACK DO CLIENTE

📧 Email: {{ $nodes['normalizar'].output.customer.email }}
👤 Nome: {{ $nodes['normalizar'].output.customer.name }}
⭐ Rating: {{ $nodes['normalizar'].output.rating || 'N/A' }}/{{ $nodes['normalizar'].output.type === 'nps' ? '10' : '5' }}

📊 ANÁLISE:
• Sentimento: {{ $nodes['consolidar-analise'].output.sentiment.toUpperCase() }} ({{ ($nodes['consolidar-analise'].output.sentimentScore * 100).toFixed(0) }}%)
• Categoria: {{ $nodes['consolidar-analise'].output.category }}
• Prioridade: {{ $nodes['consolidar-analise'].output.priority.toUpperCase() }}
• Urgência: {{ $nodes['consolidar-analise'].output.urgency }}/100
• Tópicos: {{ ($nodes['consolidar-analise'].output.topics || []).join(', ') }}

💬 COMENTÁRIO:
"{{ $nodes['normalizar'].output.comment }}"

📌 CONTEXTO:
• Fonte: {{ $nodes['normalizar'].output.source }}
• Produto mencionado: {{ $nodes['consolidar-analise'].output.productMentioned || 'N/A' }}
• Feature mencionada: {{ $nodes['consolidar-analise'].output.featureMentioned || 'N/A' }}
• Resumo: {{ $nodes['consolidar-analise'].output.summary || 'N/A' }}

---
ID: {{ $nodes['normalizar'].output.feedbackId }}
Recebido em: {{ new Date().toLocaleString('pt-BR') }}`
      },
      "priority": "{{ $nodes['consolidar-analise'].output.priority }}",
      "status": "{{ $nodes['consolidar-analise'].output.isCritical ? 'urgent' : 'open' }}",
      "type": "{{ $nodes['normalizar'].output.type === 'complaint' ? 'problem' : 'task' }}",
      "tags": [
        "feedback",
        "{{ $nodes['normalizar'].output.source }}",
        "{{ $nodes['consolidar-analise'].output.sentiment }}",
        "{{ $nodes['consolidar-analise'].output.category }}"
      ],
      "custom_fields": [
        {
          "id": 360000000001,
          "value": "{{ $nodes['consolidar-analise'].output.sentiment }}"
        },
        {
          "id": 360000000002,
          "value": "{{ $nodes['normalizar'].output.rating }}"
        }
      ]
    }
  }
}

Alternativa: Jira

// HTTP Request: "Criar Issue no Jira"
{
  "method": "POST",
  "url": "https://{{ $credentials.jira.domain }}.atlassian.net/rest/api/3/issue",
  "authentication": "basicAuth",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {
    "fields": {
      "project": {
        "key": "CS" // Customer Support
      },
      "summary": "Feedback {{ $nodes['normalizar'].output.type }} - {{ $nodes['normalizar'].output.customer.name }}",
      "description": {
        "type": "doc",
        "version": 1,
        "content": [
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "{{ $nodes['normalizar'].output.comment }}"
              }
            ]
          }
        ]
      },
      "issuetype": {
        "name": "Task"
      },
      "priority": {
        "name": "{{ $nodes['consolidar-analise'].output.priority === 'critical' ? 'Highest' : ($nodes['consolidar-analise'].output.priority === 'high' ? 'High' : 'Medium') }}"
      },
      "labels": [
        "feedback",
        "{{ $nodes['consolidar-analise'].output.sentiment }}",
        "{{ $nodes['normalizar'].output.source }}"
      ]
    }
  }
}

5.2. Atualizar com ID do Ticket

// Database Update: "Salvar Ticket ID"
{
  "table": "customer_feedback",
  "data": {
    "ticket_id": "{{ $nodes['criar-zendesk'].output.ticket.id }}",
    "ticket_url": "https://{{ $credentials.zendesk.subdomain }}.zendesk.com/agent/tickets/{{ $nodes['criar-zendesk'].output.ticket.id }}",
    "updated_at": "{{ new Date().toISOString() }}"
  },
  "where": {
    "id": "{{ $nodes['salvar-feedback'].output.id }}"
  }
}

Parte 6: Notificações e Alertas

6.1. Notificar no Slack

// If/Else: "É Crítico?"
{{
  $nodes['consolidar-analise'].output.isCritical ||
  $nodes['consolidar-analise'].output.priority === 'critical'
}}

Branch TRUE (Crítico):

// Slack - Send Message (Canal de Alertas)
{
  "channel": "{{ $vars.slackChannels.critical }}",
  "text": "🚨 FEEDBACK CRÍTICO RECEBIDO!",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "🚨 ALERTA: Feedback Crítico Negativo",
        "emoji": true
      }
    },
    {
      "type": "section",
      "fields": [
        {
          "type": "mrkdwn",
          "text": `*Cliente:*\n{{ $nodes['normalizar'].output.customer.name || 'N/A' }}`
        },
        {
          "type": "mrkdwn",
          "text": `*Email:*\n{{ $nodes['normalizar'].output.customer.email }}`
        },
        {
          "type": "mrkdwn",
          "text": `*Rating:*\n{{ $nodes['normalizar'].output.rating || 'N/A' }}`
        },
        {
          "type": "mrkdwn",
          "text": `*Sentimento:*\n{{ $nodes['consolidar-analise'].output.sentiment.toUpperCase() }}`
        }
      ]
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": `*📝 Comentário:*\n> {{ $nodes['normalizar'].output.comment.substring(0, 200) }}{{ $nodes['normalizar'].output.comment.length > 200 ? '...' : '' }}`
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": `*🏷️ Tópicos:* {{ ($nodes['consolidar-analise'].output.topics || []).join(', ') }}\n*📊 Urgência:* {{ $nodes['consolidar-analise'].output.urgency }}/100\n*🎯 Departamento:* {{ $nodes['determinar-depto'].output.department }}`
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "Ver Ticket",
            "emoji": true
          },
          "url": "{{ $nodes['salvar-ticket-id'].output.ticket_url }}",
          "style": "danger"
        },
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "Responder Cliente",
            "emoji": true
          },
          "url": "mailto:{{ $nodes['normalizar'].output.customer.email }}"
        }
      ]
    },
    {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",
          "text": `⚡ *AÇÃO IMEDIATA NECESSÁRIA* | Fonte: {{ $nodes['normalizar'].output.source }} | {{ new Date().toLocaleString('pt-BR') }}`
        }
      ]
    }
  ]
}

Branch FALSE (Normal):

// Slack - Send Message (Canal Geral)
{
  "channel": "{{ $vars.slackChannels.feedbackGeneral }}",
  "text": "{{ $nodes['consolidar-analise'].output.sentiment === 'positive' ? '😊' : '📝' }} Novo feedback recebido",
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": `*{{ $nodes['consolidar-analise'].output.sentiment === 'positive' ? '😊 Feedback Positivo!' : '📝 Novo Feedback' }}*\n{{ $nodes['normalizar'].output.customer.name || 'Cliente' }} ({{ $nodes['normalizar'].output.rating || 'N/A' }}/{{ $nodes['normalizar'].output.type === 'nps' ? '10' : '5' }})\n\n_"{{ $nodes['normalizar'].output.comment.substring(0, 150) }}..."_`
      },
      "accessory": {
        "type": "button",
        "text": {
          "type": "plain_text",
          "text": "Ver Detalhes"
        },
        "url": "{{ $nodes['salvar-ticket-id'].output.ticket_url }}"
      }
    }
  ]
}

6.2. Enviar Email para Departamento

// Send Email (Para departamento responsável)
{
  "to": "{{ $nodes['determinar-depto'].output.assignedTo }}",
  "from": "feedback@empresa.com",
  "subject": "{{ $nodes['consolidar-analise'].output.isCritical ? '🚨 URGENTE: ' : '' }}Novo feedback {{ $nodes['normalizar'].output.type }} - {{ $nodes['consolidar-analise'].output.category }}",
  "html": `
    <!DOCTYPE html>
    <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header {
          background: {{ $nodes['consolidar-analise'].output.sentiment === 'positive' ? '#4CAF50' : ($nodes['consolidar-analise'].output.sentiment === 'negative' ? '#f44336' : '#2196F3') }};
          color: white;
          padding: 20px;
          text-align: center;
          border-radius: 5px;
        }
        .rating { font-size: 32px; font-weight: bold; }
        .section { background: #f9f9f9; padding: 15px; margin: 15px 0; border-radius: 5px; }
        .label { font-weight: bold; color: #666; display: inline-block; width: 150px; }
        .badge {
          display: inline-block;
          padding: 5px 10px;
          border-radius: 3px;
          font-size: 12px;
          font-weight: bold;
        }
        .critical { background: #f44336; color: white; }
        .high { background: #ff9800; color: white; }
        .medium { background: #2196F3; color: white; }
        .low { background: #4CAF50; color: white; }
      </style>
    </head>
    <body>
      <div class="container">
        <div class="header">
          <h1>{{ $nodes['consolidar-analise'].output.sentiment === 'positive' ? '😊' : ($nodes['consolidar-analise'].output.sentiment === 'negative' ? '😞' : '😐') }} Novo Feedback</h1>
          <div class="rating">{{ $nodes['normalizar'].output.rating || 'N/A' }}/{{ $nodes['normalizar'].output.type === 'nps' ? '10' : '5' }}</div>
          <p>{{ $nodes['normalizar'].output.type.toUpperCase() }}</p>
        </div>

        <div class="section">
          <h3>👤 Cliente</h3>
          <p><span class="label">Nome:</span> {{ $nodes['normalizar'].output.customer.name || 'N/A' }}</p>
          <p><span class="label">Email:</span> <a href="mailto:{{ $nodes['normalizar'].output.customer.email }}">{{ $nodes['normalizar'].output.customer.email }}</a></p>
          <p><span class="label">Telefone:</span> {{ $nodes['normalizar'].output.customer.phone || 'N/A' }}</p>
        </div>

        <div class="section">
          <h3>💬 Comentário</h3>
          <p style="font-style: italic; background: white; padding: 15px; border-left: 4px solid #2196F3;">
            "{{ $nodes['normalizar'].output.comment }}"
          </p>
        </div>

        <div class="section">
          <h3>📊 Análise</h3>
          <p><span class="label">Sentimento:</span> <strong>{{ $nodes['consolidar-analise'].output.sentiment.toUpperCase() }}</strong> ({{ ($nodes['consolidar-analise'].output.sentimentScore * 100).toFixed(0) }}%)</p>
          <p><span class="label">Categoria:</span> {{ $nodes['consolidar-analise'].output.category }}</p>
          <p><span class="label">Prioridade:</span> <span class="badge {{ $nodes['consolidar-analise'].output.priority }}">{{ $nodes['consolidar-analise'].output.priority.toUpperCase() }}</span></p>
          <p><span class="label">Urgência:</span> {{ $nodes['consolidar-analise'].output.urgency }}/100</p>
          <p><span class="label">Tópicos:</span> {{ ($nodes['consolidar-analise'].output.topics || []).join(', ') }}</p>
          <p><span class="label">Resumo:</span> {{ $nodes['consolidar-analise'].output.summary || 'N/A' }}</p>
        </div>

        <div class="section">
          <h3>🎯 Ação Requerida</h3>
          <p><span class="label">Departamento:</span> {{ $nodes['determinar-depto'].output.department }}</p>
          <p><span class="label">Ticket ID:</span> <a href="{{ $nodes['salvar-ticket-id'].output.ticket_url }}">{{ $nodes['criar-zendesk'].output.ticket.id }}</a></p>
          {{ $nodes['consolidar-analise'].output.isCritical ? '<p style="color: #f44336; font-weight: bold;">⚡ RESPOSTA IMEDIATA NECESSÁRIA!</p>' : '' }}
        </div>

        <div style="text-align: center; margin: 30px 0;">
          <a href="{{ $nodes['salvar-ticket-id'].output.ticket_url }}" style="display: inline-block; background: #2196F3; color: white; padding: 15px 30px; text-decoration: none; border-radius: 5px;">
            Ver Ticket Completo
          </a>
        </div>

        <div style="text-align: center; color: #666; font-size: 12px; margin-top: 30px; border-top: 1px solid #ddd; padding-top: 15px;">
          <p>Feedback recebido via {{ $nodes['normalizar'].output.source }} em {{ new Date().toLocaleString('pt-BR') }}</p>
          <p>ID: {{ $nodes['normalizar'].output.feedbackId }}</p>
        </div>
      </div>
    </body>
    </html>
  `
}

Parte 7: Resposta Automática ao Cliente

7.1. Enviar Agradecimento Imediato

// If/Else: "Cliente Forneceu Email?"
{{
  $nodes['normalizar'].output.customer.email &&
  $nodes['normalizar'].output.customer.email.length > 0
}}

Branch TRUE:

// Send Email (Resposta ao cliente)
{
  "to": "{{ $nodes['normalizar'].output.customer.email }}",
  "from": "contato@empresa.com",
  "fromName": "Equipe Lumina",
  "subject": "{{ $nodes['consolidar-analise'].output.sentiment === 'positive' ? 'Obrigado pelo seu feedback! ❤️' : 'Recebemos seu feedback' }}",
  "html": `
    <!DOCTYPE html>
    <html>
    <head>
      <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
        .container { max-width: 600px; margin: 0 auto; padding: 20px; }
        .header { background: #5c6ac4; color: white; padding: 20px; text-align: center; border-radius: 5px 5px 0 0; }
        .content { background: #f9f9f9; padding: 20px; border-radius: 0 0 5px 5px; }
      </style>
    </head>
    <body>
      <div class="container">
        <div class="header">
          <h1>{{ $nodes['consolidar-analise'].output.sentiment === 'positive' ? '❤️' : '📝' }}</h1>
          <h2>{{ $nodes['consolidar-analise'].output.sentiment === 'positive' ? 'Obrigado!' : 'Recebemos seu feedback' }}</h2>
        </div>
        <div class="content">
          <p>Olá, <strong>{{ $nodes['normalizar'].output.customer.name || 'Cliente' }}</strong>!</p>

          {{ $nodes['consolidar-analise'].output.sentiment === 'positive' ?
            '<p>Ficamos muito felizes com seu feedback positivo! É muito importante para nós saber que estamos no caminho certo. 😊</p><p>Seu apoio nos motiva a continuar melhorando e entregando o melhor serviço possível.</p>' :
            ($nodes['consolidar-analise'].output.sentiment === 'negative' ?
              '<p>Lamentamos que sua experiência não tenha sido positiva. Levamos seu feedback muito a sério e já estamos trabalhando para resolver a situação.</p><p>Nossa equipe irá analisar detalhadamente seu comentário e entrar em contato em breve.</p>' :
              '<p>Obrigado por compartilhar sua opinião conosco. Seu feedback é essencial para nos ajudar a melhorar continuamente.</p>')
          }}

          <p>Seu feedback:</p>
          <blockquote style="background: white; padding: 15px; border-left: 4px solid #5c6ac4; font-style: italic;">
            "{{ $nodes['normalizar'].output.comment }}"
          </blockquote>

          {{ $nodes['consolidar-analise'].output.isCritical ?
            '<p style="background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107;"><strong>⚡ Prioridade Alta:</strong> Nossa equipe já foi notificada e você receberá retorno em breve.</p>' :
            ''
          }}

          <p>Número do protocolo: <strong>{{ $nodes['criar-zendesk'].output.ticket.id }}</strong></p>

          <p>Se precisar de mais alguma coisa, estamos à disposição!</p>

          <p>Atenciosamente,<br><strong>Equipe Lumina</strong></p>
        </div>
      </div>
    </body>
    </html>
  `
}
// Database Insert: "Registrar Resposta Enviada"
{
  "table": "feedback_responses",
  "data": {
    "feedback_id": "{{ $nodes['salvar-feedback'].output.id }}",
    "response_type": "auto_ack",
    "channel": "email",
    "message": "Email de agradecimento automático enviado",
    "sent_at": "{{ new Date().toISOString() }}"
  }
}

7.2. Enviar WhatsApp (Se Disponível)

// If/Else: "Tem WhatsApp e É Negativo?"
{{
  $nodes['normalizar'].output.customer.phone &&
  $nodes['normalizar'].output.customer.phone.length > 0 &&
  ($nodes['consolidar-analise'].output.sentiment === 'negative' || $nodes['consolidar-analise'].output.isCritical)
}}

Branch TRUE:

// WhatsApp - Send Message
{
  "to": "{{ $nodes['normalizar'].output.customer.phone }}",
  "type": "text",
  "text": {
    "body": `Olá, {{ $nodes['normalizar'].output.customer.name || 'Cliente' }}! 👋

Recebemos seu feedback e lamentamos que sua experiência não tenha sido positiva. 😞

Nossa equipe já está trabalhando para resolver a situação e entraremos em contato em breve.

*Protocolo: {{ $nodes['criar-zendesk'].output.ticket.id }}*

Qualquer dúvida, estamos aqui! 💬`
  }
}

Parte 8: Relatórios e Métricas

Crie um segundo flow agendado (diário) para gerar relatórios:

8.1. Calcular Métricas Diárias

// Database Query: "Métricas do Dia"
SELECT
  COUNT(*) as total_feedbacks,
  AVG(rating) as avg_rating,
  COUNT(*) FILTER (WHERE sentiment = 'positive') as positive_count,
  COUNT(*) FILTER (WHERE sentiment = 'negative') as negative_count,
  COUNT(*) FILTER (WHERE sentiment = 'neutral') as neutral_count,
  COUNT(*) FILTER (WHERE priority = 'critical') as critical_count,
  AVG(sentiment_score) as avg_sentiment_score,
  AVG(urgency_score) as avg_urgency,
  json_agg(DISTINCT category) as categories,
  json_agg(DISTINCT unnest(topics)) as all_topics
FROM customer_feedback
WHERE created_at >= CURRENT_DATE
  AND created_at < CURRENT_DATE + INTERVAL '1 day';

8.2. Gerar Relatório HTML

// Function Node: "Gerar Relatório"
{{
  (() => {
    const metrics = $nodes['metricas-dia'].output.rows[0];

    const positivePercent = (metrics.positive_count / metrics.total_feedbacks * 100).toFixed(1);
    const negativePercent = (metrics.negative_count / metrics.total_feedbacks * 100).toFixed(1);
    const neutralPercent = (metrics.neutral_count / metrics.total_feedbacks * 100).toFixed(1);

    return {
      html: `
        <h2>📊 Relatório de Feedback - ${new Date().toLocaleDateString('pt-BR')}</h2>

        <h3>Resumo Geral</h3>
        <ul>
          <li><strong>Total de Feedbacks:</strong> ${metrics.total_feedbacks}</li>
          <li><strong>Rating Médio:</strong> ${metrics.avg_rating?.toFixed(2) || 'N/A'}</li>
          <li><strong>Sentimento Médio:</strong> ${(metrics.avg_sentiment_score * 100).toFixed(0)}%</li>
        </ul>

        <h3>Distribuição de Sentimento</h3>
        <ul>
          <li>😊 Positivo: ${metrics.positive_count} (${positivePercent}%)</li>
          <li>😐 Neutro: ${metrics.neutral_count} (${neutralPercent}%)</li>
          <li>😞 Negativo: ${metrics.negative_count} (${negativePercent}%)</li>
        </ul>

        <h3>Prioridades</h3>
        <ul>
          <li>🚨 Crítico: ${metrics.critical_count}</li>
        </ul>

        <h3>Categorias Mais Mencionadas</h3>
        <ul>
          ${(metrics.categories || []).map(cat => `<li>${cat}</li>`).join('')}
        </ul>

        <h3>Tópicos em Destaque</h3>
        <ul>
          ${(metrics.all_topics || []).slice(0, 10).map(topic => `<li>${topic}</li>`).join('')}
        </ul>
      `,
      summary: `${metrics.total_feedbacks} feedbacks | ${positivePercent}% positivo | ${metrics.critical_count} críticos`
    };
  })()
}}

8.3. Enviar Relatório por Email

// Send Email (Para gerência)
{
  "to": "gerencia@empresa.com",
  "cc": ["produto@empresa.com", "suporte@empresa.com"],
  "subject": "📊 Relatório Diário de Feedback - {{ new Date().toLocaleDateString('pt-BR') }}",
  "html": "{{ $nodes['gerar-relatorio'].output.html }}"
}

8.4. Postar no Slack

// Slack - Send Message
{
  "channel": "{{ $vars.slackChannels.feedbackGeneral }}",
  "text": "📊 Relatório Diário de Feedback",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "📊 Relatório Diário de Feedback"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "{{ $nodes['gerar-relatorio'].output.summary }}"
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "Ver Relatório Completo"
          },
          "url": "https://dashboard.empresa.com/feedback-report"
        }
      ]
    }
  ]
}

Teste do Sistema

# Teste 1: Feedback Positivo
curl -X POST https://api.lumina.app.br/v1/webhooks/wh_feedback_xyz \
  -H "Content-Type: application/json" \
  -d '{
    "email": "cliente@example.com",
    "name": "João Silva",
    "rating": 10,
    "comment": "Adorei o produto! A plataforma é incrível e o suporte foi excepcional. Recomendo muito!"
  }'

# Teste 2: Feedback Negativo Crítico
curl -X POST https://api.lumina.app.br/v1/webhooks/wh_feedback_xyz \
  -H "Content-Type: application/json" \
  -d '{
    "email": "insatisfeito@example.com",
    "name": "Maria Santos",
    "rating": 2,
    "comment": "Péssima experiência. Sistema com bugs graves, perdi dados importantes. Preciso de solução urgente!"
  }'

# Teste 3: Feedback Neutro com Sugestão
curl -X POST https://api.lumina.app.br/v1/webhooks/wh_feedback_xyz \
  -H "Content-Type: application/json" \
  -d '{
    "email": "usuario@example.com",
    "name": "Pedro Costa",
    "rating": 7,
    "comment": "Produto bom, mas poderia ter integração com Slack. Seria muito útil para minha equipe."
  }'

Troubleshooting

Problema 1: AWS Comprehend falha

Solução: Configure fallback para OpenAI ou use análise manual baseada em palavras-chave

Problema 2: Muitos feedbacks spam

Solução: Adicione filtro de emails descartáveis e rate limiting

Problema 3: Tickets duplicados

Solução: Verifique feedback_id único antes de criar ticket

Próximos Passos

  1. Lead Scoring - Qualifique prospects
  2. Carrinhos Abandonados - Recupere vendas
  3. Campanha Multicanal - Engaje clientes

Última atualização: Janeiro 2025