Visão Geral

Script Retool que realiza enriquecimento de dados de leads a partir de múltiplas fontes, com aplicação de filtros configuráveis, normalização de dados e merge inteligente para geração de base final processada.

Objetivo

Enriquecer e validar dados de leads importados, garantindo:

  • Integridade e normalização de CPFs e telefones
  • Enriquecimento com dados do MongoDB (nome, data nascimento, telefones etc.)
  • Aplicação de filtros de restrição (deny list, deal stage, NMP)
  • Validação de idade máxima permitida
  • Identificação de User IDs quando necessário
  • Geração de base limpa e enriquecida para uso em campanhas

Diagrama de Fluxo

graph TD
    A[Início<br/>fileInput4.parsedValue] --> B[Formatação e Validação<br/>normalizarCPF]
    B --> C{switch_restrictions<br/>ativado?}
    
    C -->|Sim| D1[Query Paralela 1<br/>find_restrictions_mongo]
    C -->|Sim| D2[Query Paralela 2<br/>deal_stage]
    C -->|Sim| D3[Query Paralela 3<br/>deny_list_general]
    C -->|Sim| D4[Query Paralela 4<br/>nao_me_perturbe]
    C -->|Sim| D5[Query Paralela 5<br/>disparos_recentes_tabela]
    C -->|Sim| D6[Query Paralela 6<br/>app_buyers]
    
    C -->|Não| E[Busca MongoDB<br/>buscar_dados_mongo2]
    D1 --> E
    D2 --> E
    D3 --> E
    D4 --> E
    D5 --> E
    D6 --> E
    
    E --> F{is_app_switch<br/>ativado?}
    F -->|Sim| G[Buscar User ID<br/>user_id2]
    F -->|Não| H[Merge e Enriquecimento<br/>Combinar fontes]
    G --> H
    
    H --> I{switch_restrictions<br/>Aplicar Filtros?}
    
    I -->|Sim| J[Filtragem de Restrições<br/>Remove Inválidos]
    I -->|Não| K[Base Enriquecida<br/>baseMesclada2]
    J --> K
    
    K --> L{É Carteira?}
    L -->|Sim| M{Soma Comissões < Corte?}
    L -->|Não| L2{É Ativação?}
    
    L2 -->|Sim| N[Grupo D - CS Ativação]
    L2 -->|Não| AA
    
    M -->|Sim| O[Grupo A - Baixa Comissão]
    M -->|Não| P{Comprou no app?}
    
    P -->|Não| Q[Grupo B - Alta Comissão / Não Comprou no app]
    P -->|Sim| R[Grupo C - Alta Comissão / Comprou no app]
    
    O --> S[Touch: Lowtouch - 100%]
    Q --> T[Touch: Hightouch - 100%]
    R --> U[Touch: Hightouch y% / Lowtouch Resto]
    
    N --> V[Touch: null]
    
    S --> W[Lead AI: Não]
    T --> X[Lead AI: Sim z% / Não Resto]
    U --> Y{É Hightouch?}
    Y -->|Sim| X
    Y -->|Não| W
    V --> Z[Lead AI: Sim w% / Não Resto]
    
    W --> AA[Geração da Tabela Final]
    X --> AA
    Z --> AA
    
    AA --> AB[Saída<br/>Lista filtrada]

    classDef inicio fill:#e1f5ff,stroke:#0066cc,stroke-width:2px
    classDef processo fill:#fff4e1,stroke:#ff9900,stroke-width:2px
    classDef decisao fill:#ffe1e1,stroke:#cc0000,stroke-width:2px
    classDef query fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    classDef saida fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    classDef grupo fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    
    class A,AB inicio
    class B,H,J,AA,W,X,Z,S,T,U,V processo
    class C,F,I,L,L2,M,P,Y decisao
    class D1,D2,D3,D4,D5,D6,E,G query
    class K,O,Q,R,N grupo

Variáveis de Configuração

Switches/Parâmetros

VariávelOrigemTipoObrigatórioDescriçãoUso no Fluxo
switch_restrictions.valueToggle RetoolbooleanNão (default: false)Ativa/desativa queries de restrição e filtragemControla execução de 6 queries paralelas e aplicação de filtros
is_app_switch.valueToggle RetoolbooleanNão (default: false)Ativa/desativa busca de User IDDetermina se query user_id2 será executada
input_maximum_age.valueInput RetoolnumberNão (default: 120)Idade máxima permitida para leadsFiltro aplicado durante validação de dados
input_percentual_hightouch_appbuyer.valueNumber Input RetoolnumberNão (default: 50)% de app_buyers (comissão ≥ 200) que recebem tag hightouchDefine exatamente y% que vão para hightouch, resto lowtouch
input_percentual_ai_csapp.valueNumber Input RetoolnumberNão (default: 0)% de csapp hightouch elegíveis para IADefine exatamente z% que recebem lead_ai = “sim”
input_percentual_ai_csativacao.valueNumber Input RetoolnumberNão (default: 0)% de csativacao elegíveis para IADefine exatamente w% que recebem lead_ai = “sim”

Constantes Internas

ConstanteValorDescrição
TIMEOUT_MS30000Timeout padrão para queries (30s)
MAX_RETRIES3Número máximo de retentativas
BACKOFF_MS1000Tempo de espera entre retries (1s)

Entrada (Input)

Estrutura Esperada

Fonte: fileInput4.parsedValue

{
  "CPF": "12345678900",
  "Nome_Cliente": "João da Silva",
  "FONE1": "11987654321",
  "Data_Nascimento": "1990-01-01",
  ...
}

Campos

CampoTipoObrigatórioDescrição
CPFstringSimCPF do lead (com ou sem formatação)
Nome_ClientestringNãoNome do cliente
FONE1stringNãoTelefone principal
Data_NascimentostringNãoData de nascimento (YYYY-MM-DD)

Validações

  • CPF: Deve conter apenas números após normalização; CPFs inválidos são removidos
  • FONE1: Normalizado para formato sem caracteres especiais
  • Data_Nascimento: Validação de idade baseada em input_maximum_age.value

Fluxo Detalhado

Fase 1: Inicialização e Formatação

Função: iniciarProcessamento()

Ações:

  1. Recupera dados de fileInput4.parsedValue
  2. Extrai lista de CPFs únicos
  3. Aplica função normalizarCPF() em cada CPF
  4. Remove CPFs inválidos ou duplicados

Variáveis criadas:

  • cpfList: Array de CPFs normalizados
  • baseInicial: Cópia dos dados de entrada

Critérios de validação:

  • CPF deve ter 11 dígitos após normalização
  • CPF não pode ser nulo/undefined

Fase 2: Queries de Restrição (Condicional)

Condição: switch_restrictions.value === true

Execução: Paralela (todas as 6 queries executam simultaneamente)

Query 1: find_restrictions_mongo

  • Propósito: Recuperar contatos com restrições de comunicação
  • Collection: poc growth - growth - restrictions (Mongo)
  • Filtro: CPF in cpfList
  • Retorno: { CPF, has_restriction, restriction_type }
  • Timeout: 30s com 3 retries

Query 2: deal_stage

  • Propósito: Verificar stage atual de deals associados
  • Tabelas: mvw_hubspot_deals, mvw_hubspot_deals_customer (Redshift)
  • Filtro: CPF in cpfList
  • Retorno: { CPF, stage, deal_id }
  • Regra: Leads em stages específicos podem ser filtrados

Query 3: deny_list_general

  • Propósito: Verificar presença em lista de bloqueio geral
  • Tabelas: tb_konsidb_deny_list_user, mvw_mongo_needs_info (Redshift)
  • Filtro: CPF in cpfList
  • Retorno: { CPF, denied, reason }

Query 4: nao_me_perturbe

  • Propósito: Verificar opt-out de comunicações
  • Tabela: nao_me_perturbe (Retool)
  • Filtro: CPF in cpfList
  • Retorno: { CPF, opted_out, date }

Query 5: disparos_recentes_tabela

  • Propósito: Verificar disparos recentes para evitar overcontact
  • Tabela**: tb_growth_campaign_triggers_v2 (Redshift)
  • Filtro: CPF in cpfList AND date > (hoje - X dias)
  • Retorno: { CPF, last_contact_date, campaign_id }

Query 6: app_buyers

  • Propósito: Identificar compradores via app
  • Collection: mvw_mongo_compra_recompra (Redshift)
  • Filtro: CPF in cpfList
  • Retorno: { CPF, is_app_buyer, purchase_count }

Tratamento de erros: Se qualquer query falhar, continua processamento sem os dados dessa query


Fase 3: Enriquecimento MongoDB

Query: buscar_dados_mongo2

Ações:

  1. Executa busca principal no MongoDB com lista de CPFs
  2. Recupera dados completos de cada lead
  3. Normaliza campos (telefone, nome, etc.)

Dados recuperados:

  • Nome completo atualizado
  • Data de nascimento
  • Telefones atualizados

Variáveis criadas:

  • dadosMongo: Map<CPF, dadosLead>

Fase 4: Busca User ID (Condicional)

Condição: is_app_switch.value === true

Query: user_id2

Propósito: Recuperar User IDs de aplicativo para integração

Retorno: { CPF, user_id, app_platform }

Variáveis criadas:

  • userIdMap: Map<CPF, user_id>

Fase 5: Classificação e Merge de Dados

Local: Dentro de iniciarProcessamento() (linhas 111-168)

Esta fase agora é dividida em 2 etapas distintas:

Etapa 5.1: Pré-processamento com Classificação (NOVA)

Função: classificarLeadsComPercentuaisExatos()

Objetivo: Processar TODA a base de uma só vez e definir todas as decisões de classificação

Ações:

  1. Cria mapaMongo (Map) com CPF como chave e dados do MongoDB
  2. Chama classificarLeadsComPercentuaisExatos() passando:
    • dadosOriginais: Base completa
    • setAppBuyers: CPFs de compradores do app
    • mapaUserId: Map com user_id e data_de_finalizacao
    • input_percentual_hightouch_appbuyer.value: Percentual y%
    • input_percentual_ai_csapp.value: Percentual z%
    • input_percentual_ai_csativacao.value: Percentual w%

Processo Interno (dentro de classificarLeadsComPercentuaisExatos):

  1. Classificação em 4 grupos:

    • Grupo A: csapp com comissão < 200
    • Grupo B: csapp com comissão ≥ 200, não é app_buyer
    • Grupo C: csapp com comissão ≥ 200, é app_buyer
    • Grupo D: csativacao
  2. Atribuição de tipoTouch:

    • Grupo A → 100% lowtouch
    • Grupo B → 100% hightouch
    • Grupo C → Embaralha, y% hightouch, resto lowtouch (percentual EXATO)
    • Grupo D → null (csativacao não tem tag)
  3. Atribuição de lead_ai:

    • Pega todos csapp hightouch → embaralha → z% recebe “sim” (percentual EXATO)
    • Pega todos csativacao → embaralha → w% recebe “sim” (percentual EXATO)
    • Demais → “não”

Variáveis criadas:

  • mapaDecisoes: Map<CPF, {tipoTouch, lead_ai}> com decisões para cada lead

Garantias:

  • Percentuais são EXATOS (não aproximados)
  • Distribuição é imparcial (Fisher-Yates shuffle)
  • Todas as decisões são tomadas de uma vez

Etapa 5.2: Merge com Decisões Pré-calculadas

Processo: .map() sobre dadosOriginais

Ações:

Para cada linha:

  1. Consulta mapaDecisoes para obter decisões pré-calculadas
  2. Busca dados do MongoDB e mapaUserId
  3. Monta objeto final com todos os campos
let tabelaFinal = dadosOriginais.map(linha => {
  const cpfFormatado = normalizarCPF(linha.CPF);
  const encontradoMongo = mapaMongo[cpfFormatado];
  const encontradoUserId = mapaUserId[cpfFormatado];
  const isAppBuyer = setAppBuyers.has(cpfFormatado);
  
  // Consultar decisões pré-calculadas
  const decisoes = mapaDecisoes.get(cpfFormatado) || { tipoTouch: null, lead_ai: 'não' };
  
  return {
    ...linha,
    CPF: cpfFormatado,
    Nome_Cliente: linha.Nome_Cliente || encontradoMongo?.name,
    Campanha: processarCampanha(
      linha.Campanha,
      encontradoUserId?.data_de_finalizacao,
      linha,
      setAppBuyers,
      decisoes.tipoTouch  // ← Aplica decisão pré-calculada
    ),
    // ... outros campos ...
    app_buyer: isAppBuyer ? "sim" : "não",
    lead_ai: decisoes.lead_ai  // ← Aplica decisão pré-calculada
  };
});

Lógica Condicional de Campos:

Se is_app_switch.value === true:

  • FONE1: Usa obterTelefonePrioritario() (busca telefone com importance=1)
  • Campanha: Aplica tipoTouch pré-calculado via processarCampanha()

Se is_app_switch.value === false:

  • FONE1, FONE2, FONE3, FONE4: Prioriza MongoDB, fallback para original
  • Campanha: Aplica tipoTouch pré-calculado via processarCampanha()

Campos sempre mesclados:

  • Data_Nascimento: MongoDB birthdate ou linha original
  • Convenio: MongoDB ou linha original
  • app_buyer: “sim” ou “não” (baseado em setAppBuyers)
  • lead_ai: “sim” ou “não” (decisão pré-calculada)

Mudança Fundamental:

  • processarCampanha() agora apenas aplica a decisão recebida, não decide mais
  • Todas as decisões são feitas ANTES do merge, garantindo percentuais exatos

Variáveis criadas:

  • tabelaFinal: Array com todos os dados mesclados e classificados

Fase 6: Filtragem de Restrições (Condicional)

Condição: switch_restrictions.value === true

Função: aplicarFiltragemRestricoes(dados, dadosRestricoes)

Esta é a fase mais complexa, com múltiplos filtros aplicados sequencialmente.

Estruturas de Dados Criadas

A função primeiro cria estruturas otimizadas (Sets e Maps) para lookups rápidos:

  1. set_telefones_restritos (Set): Chaves no formato "telefone|cpf" com restrições
  2. map_telefones_restritos_motivos (Map): Motivos de cada restrição
  3. set_telefones_disparos_recentes (Set): Telefones com disparos recentes
  4. map_cpfs_com_deals (Map): CPFs com deals em aberto (LEAD, OPORTUNIDADE, CONTRATAÇÃO, PAGO)
  5. set_deny_list (Set): CPFs na deny list
  6. set_nao_me_perturbe (Set): Nomes (lowercase) na lista “não me perturbe”

Filtros Aplicados (Ordem de Execução)

Para cada linha, verifica sequencialmente (se encontrar restrição, para):

1. Deals em Aberto (exclusão total do registro)

  • Condição: CPF tem deal com stage = LEAD, OPORTUNIDADE, CONTRATAÇÃO ou PAGO
  • Ação: Remove registro completamente
  • Log: Adiciona em restricoes_encontradas com motivo e updated_at

2. Não Me Perturbe (exclusão total do registro)

  • Condição: Nome_Cliente (lowercase) está na lista
  • Ação: Remove registro completamente
  • Log: Adiciona em restricoes_encontradas

3. Deny List (exclusão total do registro)

  • Condição: CPF está na deny list
  • Ação: Remove registro completamente
  • Log: Adiciona em restricoes_encontradas

4. Idade Acima do Limite (exclusão total do registro)

  • Condição: calcularIdade(Data_Nascimento) > input_maximum_age.value
  • Ação: Remove registro completamente
  • Log: Adiciona em restricoes_encontradas com idade calculada
  • Nota: Função calcularIdade() interna suporta formatos DD/MM/AAAA e YYYY-MM-DD

5. Disparos Recentes (exclusão total do registro)

  • Condição: Qualquer telefone (FONE1-4) teve disparo nos últimos X dias (input_last_campaign.value)
  • Ação: Remove registro completamente
  • Log: Adiciona com mensagem dinâmica (“Disparo ontem” ou “Disparo nos últimos X dias”)

6. Telefones Restritos (limpeza de campos, não exclui registro)

  • Condição: Par telefone|cpf está nas restrições
  • Ação:
    • Limpa apenas os telefones restritos (seta campo como "")
    • Verifica FONE1, FONE2, FONE3, FONE4 individualmente
    • Se todos os telefones ficarem vazios, aí sim remove o registro
  • Log: Adiciona em restricoes_encontradas com motivo específico

Critérios de Exclusão Detalhados

FiltroCondição de ExclusãoTipo de Ação
Deals em Abertostage in ['LEAD', 'OPORTUNIDADE', 'CONTRATAÇÃO', 'PAGO']Exclusão total
Não Me PerturbeNome_Cliente.lowercase in set_nao_me_perturbeExclusão total
Deny ListCPF in set_deny_listExclusão total
Idade Máximaidade > input_maximum_age.valueExclusão total
Disparos Recentestelefone in set_telefones_disparos_recentesExclusão total
Telefones Restritostelefone|cpf in set_telefones_restritosLimpeza de campos → Exclusão se ficou sem telefones

Saídas da Função

Retorno: Array filtrado com registros válidos

Side Effect:

  • restrictions_log.setValue(restricoes_encontradas) - Tabela Retool com todas as restrições encontradas

Variáveis criadas:

  • resultado_filtrado: Array final após filtros
  • restricoes_encontradas: Array com log de todas as restrições (CPF, Telefone, Motivo)
  • Contadores: contador_deals, contador_nao_me_perturbe, contador_deny_list, contador_idade_acima_limite, contador_disparos_recentes, contador_telefones_restritos, contador_sem_telefones

Logs no console:

  • Tamanho inicial e final
  • Total removido
  • Breakdown por tipo de exclusão

Fase 7: Saída Final

Ações:

  1. Atualiza tabelaFinal com resultado da filtragem (se filtros habilitados)
  2. Define baseMesclada2.setValue(tabelaFinal) com dados finais
  3. Exibe console.log("Processamento concluído!")

Output:

  • Tabela principal: baseMesclada2 - leads processados e filtrados
  • Tabela de log: restrictions_log - todas as restrições encontradas (se filtros habilitados)

Tratamento de erros:

  • Todo o processamento está em try-catch
  • Em caso de erro, loga stack trace completo no console
  • Não atualiza baseMesclada2 em caso de falha

Saída (Output)

Estrutura Final

Destino: baseMesclada2

{
  "CPF": "12345678900",
  "Nome_Cliente": "João da Silva Souza",
  "FONE1": "5511987654321",
  "Data_Nascimento": "1990-01-01",
  "Email": "joao.silva@example.com",
  "Campanha": "campanha_nome_hightouch_csapp",
  "Convenio": "Convenio XYZ",
  "app_buyer": "sim",
  "lead_ai": "não"
}

Campos Adicionados

CampoTipoDescriçãoValores Possíveis
app_buyerstringIndica se CPF está na lista de compradores do app”sim” ou “não”
lead_aistringIndica se lead é elegível para atendimento por IA”sim” ou “não”

Importante sobre lead_ai:

  • O campo garante percentual EXATO configurado nos inputs
  • Para csapp: apenas leads com tag hightouch podem ter lead_ai = “sim”
  • Para csativacao: todos são elegíveis, distribuição baseada no percentual configurado
  • Leads lowtouch sempre têm lead_ai = “não”

Campos Modificados

CampoModificação
CampanhaAgora inclui tags lowtouch/hightouch para csapp baseado em comissões e histórico de compra

Logs de Validação

O script gera logs específicos no console do navegador/Retool para validar a distribuição dos grupos:

  • [VALIDAÇÃO] Grupo C ...: Mostra total e percentual de app_buyers que viraram hightouch.
  • [VALIDAÇÃO] Lead AI ...: Mostra total e percentual de leads elegíveis que receberam lead_ai = "sim".
  • [ALERTA] ...: Exibido caso a diferença entre o esperado e o realizado seja maior que 1 unidade (erro de lógica).

Cenários de Saída

Cenário 1: Processamento Completo (Com Restrições)

Caminho: Fase 1 → Fase 2 → Fase 3 → Fase 5 → Fase 6 → Fase 7

Resultado:

  • Base enriquecida
  • Filtros aplicados
  • Métricas completas disponíveis

Cenário 2: Processamento Simples (Sem Restrições)

Caminho: Fase 1 → Fase 3 → Fase 5 → Fase 7

Resultado:

  • Base enriquecida
  • Sem filtros aplicados
  • Todos os registros preservados

Cenário 3: Processamento com User ID

Caminho: Fase 1 → … → Fase 4 → Fase 5 → …

Resultado:

  • Base enriquecida
  • User IDs incluídos
  • Pronta para integração com app

Dependências Externas

Queries Retool

QueryTipoTimeoutRetriesCrítico
find_restrictions_mongoMongoDB30s3Não
deal_stageMongoDB30s3Não
deny_list_generalMongoDB30s3Não
nao_me_perturbeMongoDB30s3Não
disparos_recentes_tabelaMongoDB30s3Não
app_buyersMongoDB30s3Não
user_id2MongoDB30s3Não
buscar_dados_mongo2MongoDB30s3Sim

Componentes Retool

ComponenteTipoDescrição
fileInput4File InputUpload de arquivo CSV/XLSX com leads
baseMesclada2TableTabela de output com dados processados e filtrados
restrictions_logTableTabela de output com log de restrições encontradas (CPF, Telefone, Motivo)
switch_restrictionsToggleAtiva/desativa filtros de restrição
is_app_switchToggleAtiva/desativa busca de User ID
input_maximum_ageNumber InputIdade máxima permitida
input_last_campaignNumber InputDias para considerar disparos recentes
input_percentual_hightouch_appbuyerNumber InputPercentual (0-100) de app_buyers com comissão alta que vão para hightouch
input_percentual_ai_csappNumber InputPercentual (0-100) de csapp hightouch que são elegíveis para IA
input_percentual_ai_csativacaoNumber InputPercentual (0-100) de csativacao que são elegíveis para IA

Funções Principais

retryTrigger(queryName, queryObj, options, timeoutMs, retries, backoffMs)

Descrição: Executa query com retry automático e timeout

Parâmetros:

  • queryName: Nome da query para logging
  • queryObj: Objeto da query Retool
  • options: Opções adicionais
  • timeoutMs: Timeout em milissegundos
  • retries: Número de tentativas
  • backoffMs: Tempo entre retries

Retorno: Promise com resultado da query

Tratamento de erros: Loga cada tentativa; lança erro após esgotar retries


iniciarProcessamento()

Descrição: Função principal que orquestra todo o fluxo

Fluxo:

  1. Valida entrada
  2. Formata CPFs
  3. Executa queries condicionalmente
  4. Realiza merge
  5. Aplica filtros
  6. Define output

Retorno: void (atualiza baseMesclada2 diretamente)


normalizarCPF(cpf)

Descrição: Remove formatação e normaliza CPF para 11 dígitos

Parâmetros: cpf (string)

Retorno: String com 11 dígitos (padded com zeros à esquerda se necessário)

Lógica:

const cpfLimpo = cpf.toString().replace(/\D/g, '');
return cpfLimpo.padStart(11, '0');

processarCampanha(campanha, dataFinalizacao, linha, setAppBuyers, tipoTouch)

Descrição: Ajusta o nome da campanha baseado em regras de negócio para equipes CS

Parâmetros:

  • campanha (string): Nome original da campanha (formato com underscores)
  • dataFinalizacao (string|null): Data de finalização do user_id
  • linha (object): Linha de dados com CPF
  • setAppBuyers (Set): Set com CPFs de compradores do app
  • tipoTouch (string|null): Tag pré-calculada (‘hightouch’, ‘lowtouch’ ou null)

Retorno: String com nome ajustado da campanha

Lógica:

  1. Divide campanha por _ para obter partes
  2. Verifica última parte (equipe):
    • Se for cscp, cscdx ou csport → retorna sem alteração
    • Caso contrário:
      • Com dataFinalizacao → muda para csapp
      • Sem dataFinalizacao → muda para csativacao
  3. Se ficou csapp E tipoTouch não é null:
    • Adiciona tipoTouch na posição 2 (se ainda não houver tag)

Mudança importante: Função agora apenas APLICA a decisão recebida via tipoTouch, não decide mais internamente

Exemplo:

  • Input: campanha_nome_equipe com dataFinalizacao e tipoTouch=‘lowtouch’
  • Output: campanha_nome_lowtouch_csapp

obterTelefonePrioritario(dadosMongo)

Descrição: Busca telefone com maior importância (importance=1) dentre FONE1-4

Parâmetros: dadosMongo (object) - Objeto com campos FONE1-4 e FONE1_importance-4

Retorno: String com telefone prioritário ou null

Lógica:

  1. Cria array com todos os telefones e suas importâncias
  2. Remove telefones nulos/vazios
  3. Busca primeiro telefone com importance === 1
  4. Se não encontrar, retorna FONE1 como fallback

Uso: Chamado durante merge quando is_app_switch === true para priorizar telefone principal


aplicarFiltragemRestricoes(dados, dadosRestricoes)

Descrição: Filtra dados baseado em múltiplos critérios

Parâmetros:

  • dados: Array de leads
  • dadosRestricoes: Map com dados de restrição

Retorno: Array filtrado + métricas de filtros

Lógica: Aplica cada filtro sequencialmente, registrando motivos


calcularIdade(dataNascimento)

Descrição: Calcula idade a partir de data de nascimento

Parâmetros: dataNascimento (string YYYY-MM-DD)

Retorno: number (idade em anos)


normalizarTelefone(telefone)

Descrição: Remove formatação de telefone e adiciona código do país (+55) se necessário

Parâmetros: telefone (string)

Retorno: String com dígitos e prefixo 55 (ex: “5511987654321”)

Lógica:

const telefoneLimpo = telefone.toString().replace(/\D/g, '');
 
if (!telefoneLimpo.startsWith('55')) {
  return '55' + telefoneLimpo;
}
 
return telefoneLimpo;

Nota: Sempre retorna telefone em formato internacional brasileiro


calcularSomaComissoes(linha)

Descrição: Calcula soma total de todas as comissões de um lead

Parâmetros: linha (object) - Objeto com dados do lead

Retorno: number - Soma das comissões (empréstimo + benefício + cartão)

Lógica:

const emprestimo = parseFloat(linha.comissao_emprestimo) || 0;
const beneficio = parseFloat(linha.comissao_beneficio) || 0;
const cartao = parseFloat(linha.comissao_cartao) || 0;
return emprestimo + beneficio + cartao;

Uso: Chamado durante classificação para determinar se lead tem comissão alta (≥ R$200)


embaralhar(array)

Descrição: Embaralha array aleatoriamente usando algoritmo Fisher-Yates

Parâmetros: array (Array) - Array a ser embaralhado

Retorno: Array - Novo array embaralhado (não modifica original)

Importância: Garante distribuição aleatória uniforme e imparcial - todos os elementos têm igual probabilidade de estar em qualquer posição, independente da ordem original

Lógica: Algoritmo Fisher-Yates com complexidade O(n)

Uso: Usado para garantir percentuais exatos na distribuição de tags e elegibilidade para IA


classificarLeadsComPercentuaisExatos(dadosOriginais, setAppBuyers, mapaUserId, y, z, w)

Descrição: Função central que processa toda a base ANTES do merge e define todas as decisões de classificação

Parâmetros:

  • dadosOriginais (Array): Array completo de leads do fileInput4
  • setAppBuyers (Set): Set com CPFs de compradores do app
  • mapaUserId (Object): Map com user_id e data_de_finalizacao por CPF
  • y (number): Percentual de app_buyers (comissão ≥ 200) que vão para hightouch
  • z (number): Percentual de csapp hightouch elegíveis para IA
  • w (number): Percentual de csativacao elegíveis para IA

Retorno: Map<CPF, {tipoTouch, lead_ai}> - Decisões para cada CPF

Processo:

  1. Classificação: Separa leads em 4 grupos (A: csapp baixa comissão, B: csapp alta comissão não-appbuyer, C: csapp alta comissão appbuyer, D: csativacao)
  2. Atribuição de tags: Aplica percentuais exatos com embaralhamento
    • Grupo A: 100% lowtouch
    • Grupo B: 100% hightouch
    • Grupo C: y% hightouch, resto lowtouch (embaralhado)
    • Grupo D: sem tag
  3. Atribuição lead_ai: Embaralha grupos elegíveis e marca percentuais exatos
    • csapp hightouch: z% recebe “sim”
    • csativacao: w% recebe “sim”

Garantia: Percentuais são EXATOS, não aproximados

Métricas e Tempos

MétricaValor EsperadoDescrição
Tempo de execução total30-60sDepende do volume e queries habilitadas
Timeout por query30sCom 3 retries = até 90s por query
Taxa de enriquecimento>90%Leads com dados encontrados no MongoDB
Taxa de filtro (com restrições)10-30%Leads removidos por filtros
Volume máximo recomendado10.000 leadsPor execução

Tratamento de Erros

Estratégia Geral

  • Queries não-críticas: Continuam processamento sem dados
  • Query crítica (buscar_dados_mongo2): Interrompe processamento
  • Erros de formato: Registra e remove registro inválido
  • Timeout: Retry automático até MAX_RETRIES

Logs

console.error(`[${queryName}] Erro:`, error.message)
console.warn(`[${queryName}] Retry ${attempt}/${retries}`)
console.info(`Processamento concluído: ${total} leads`)

Notas Técnicas

Sobre Normalização de CPF

  • Remove todos os caracteres não-numéricos
  • CPFs com menos de 11 dígitos são considerados inválidos
  • Não valida dígitos verificadores (apenas formato)

Sobre Performance

  • Queries de restrição executam em paralelo (ganho de ~6x)
  • Map/Set utilizado para lookups O(1)
  • Processamento em memória (limite de ~100k registros)

Sobre Consistência de Dados

  • Prioridade MongoDB sobre arquivo input
  • Campos null/undefined preservados
  • Telefones sempre normalizados na saída

Limitações Identificadas

  1. Volume: Não recomendado para bases >100k registros (limitação de memória Retool)
  2. Timeout: Queries lentas podem falhar mesmo com retries
  3. Validação CPF: Não valida dígitos verificadores
  4. Sincronização: Dados do MongoDB podem estar desatualizados

Melhorias Sugeridas

  1. Adicionar validação completa de CPF (dígitos verificadores)
  2. Implementar processamento em lotes para volumes grandes
  3. Cache de queries de restrição para múltiplas execuções
  4. Exportar relatório de leads filtrados com motivos
  5. Adicionar validação de formato de telefone e email

Troubleshooting

Erros Comuns

ErroCausa ProvávelSolução
CPF inválidoCPF com formatação incorreta ou incompletoVerificar formato no arquivo input
Timeout em buscar_dados_mongo2Volume muito alto ou MongoDB lentoReduzir volume ou otimizar query
baseMesclada2 vazioTodos os leads filtradosRevisar critérios de filtro
user_id2 falhouToggle ativado sem query configuradaVerificar configuração da query
Leads duplicadosCPFs repetidos no inputAplicar distinct no arquivo origem

Logs Relevantes

  • Abrir Console do Retool (F12)
  • Buscar por: [processar-e-mesclar], [retryTrigger], Erro
  • Verificar contadores de leads processados vs filtrados

Monitoramento

  • Query execution time no Retool
  • Contadores exibidos na interface
  • Comparar volume input vs output

Changelog

DataVersãoAlteraçãoAutor
2024-11-191.0Documentação inicial completaEquipe

Referências