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ável | Origem | Tipo | Obrigatório | Descrição | Uso no Fluxo |
|---|---|---|---|---|---|
switch_restrictions.value | Toggle Retool | boolean | Não (default: false) | Ativa/desativa queries de restrição e filtragem | Controla execução de 6 queries paralelas e aplicação de filtros |
is_app_switch.value | Toggle Retool | boolean | Não (default: false) | Ativa/desativa busca de User ID | Determina se query user_id2 será executada |
input_maximum_age.value | Input Retool | number | Não (default: 120) | Idade máxima permitida para leads | Filtro aplicado durante validação de dados |
input_percentual_hightouch_appbuyer.value | Number Input Retool | number | Não (default: 50) | % de app_buyers (comissão ≥ 200) que recebem tag hightouch | Define exatamente y% que vão para hightouch, resto lowtouch |
input_percentual_ai_csapp.value | Number Input Retool | number | Não (default: 0) | % de csapp hightouch elegíveis para IA | Define exatamente z% que recebem lead_ai = “sim” |
input_percentual_ai_csativacao.value | Number Input Retool | number | Não (default: 0) | % de csativacao elegíveis para IA | Define exatamente w% que recebem lead_ai = “sim” |
Constantes Internas
| Constante | Valor | Descrição |
|---|---|---|
TIMEOUT_MS | 30000 | Timeout padrão para queries (30s) |
MAX_RETRIES | 3 | Número máximo de retentativas |
BACKOFF_MS | 1000 | Tempo 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
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
CPF | string | Sim | CPF do lead (com ou sem formatação) |
Nome_Cliente | string | Não | Nome do cliente |
FONE1 | string | Não | Telefone principal |
Data_Nascimento | string | Não | Data de nascimento (YYYY-MM-DD) |
| … | … | … | … |
Validações
CPF: Deve conter apenas números após normalização; CPFs inválidos são removidosFONE1: Normalizado para formato sem caracteres especiaisData_Nascimento: Validação de idade baseada eminput_maximum_age.value
Fluxo Detalhado
Fase 1: Inicialização e Formatação
Função: iniciarProcessamento()
Ações:
- Recupera dados de
fileInput4.parsedValue - Extrai lista de CPFs únicos
- Aplica função
normalizarCPF()em cada CPF - Remove CPFs inválidos ou duplicados
Variáveis criadas:
cpfList: Array de CPFs normalizadosbaseInicial: 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:
- Executa busca principal no MongoDB com lista de CPFs
- Recupera dados completos de cada lead
- 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:
- Cria
mapaMongo(Map) com CPF como chave e dados do MongoDB - Chama
classificarLeadsComPercentuaisExatos()passando:dadosOriginais: Base completasetAppBuyers: CPFs de compradores do appmapaUserId: Map com user_id e data_de_finalizacaoinput_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):
-
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
-
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)
-
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:
- Consulta
mapaDecisoespara obter decisões pré-calculadas - Busca dados do MongoDB e mapaUserId
- 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: UsaobterTelefonePrioritario()(busca telefone com importance=1)Campanha: AplicatipoTouchpré-calculado viaprocessarCampanha()
Se is_app_switch.value === false:
FONE1,FONE2,FONE3,FONE4: Prioriza MongoDB, fallback para originalCampanha: AplicatipoTouchpré-calculado viaprocessarCampanha()
Campos sempre mesclados:
Data_Nascimento: MongoDB birthdate ou linha originalConvenio: MongoDB ou linha originalapp_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:
set_telefones_restritos(Set): Chaves no formato"telefone|cpf"com restriçõesmap_telefones_restritos_motivos(Map): Motivos de cada restriçãoset_telefones_disparos_recentes(Set): Telefones com disparos recentesmap_cpfs_com_deals(Map): CPFs com deals em aberto (LEAD, OPORTUNIDADE, CONTRATAÇÃO, PAGO)set_deny_list(Set): CPFs na deny listset_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ÇÃOouPAGO - Ação: Remove registro completamente
- Log: Adiciona em
restricoes_encontradascom 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_encontradascom 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|cpfestá 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
- Limpa apenas os telefones restritos (seta campo como
- Log: Adiciona em
restricoes_encontradascom motivo específico
Critérios de Exclusão Detalhados
| Filtro | Condição de Exclusão | Tipo de Ação |
|---|---|---|
| Deals em Aberto | stage in ['LEAD', 'OPORTUNIDADE', 'CONTRATAÇÃO', 'PAGO'] | Exclusão total |
| Não Me Perturbe | Nome_Cliente.lowercase in set_nao_me_perturbe | Exclusão total |
| Deny List | CPF in set_deny_list | Exclusão total |
| Idade Máxima | idade > input_maximum_age.value | Exclusão total |
| Disparos Recentes | telefone in set_telefones_disparos_recentes | Exclusão total |
| Telefones Restritos | telefone|cpf in set_telefones_restritos | Limpeza 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 filtrosrestricoes_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:
- Atualiza
tabelaFinalcom resultado da filtragem (se filtros habilitados) - Define
baseMesclada2.setValue(tabelaFinal)com dados finais - 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
baseMesclada2em 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
| Campo | Tipo | Descrição | Valores Possíveis |
|---|---|---|---|
app_buyer | string | Indica se CPF está na lista de compradores do app | ”sim” ou “não” |
lead_ai | string | Indica 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
hightouchpodem 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
| Campo | Modificação |
|---|---|
Campanha | Agora 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 receberamlead_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
| Query | Tipo | Timeout | Retries | Crítico |
|---|---|---|---|---|
find_restrictions_mongo | MongoDB | 30s | 3 | Não |
deal_stage | MongoDB | 30s | 3 | Não |
deny_list_general | MongoDB | 30s | 3 | Não |
nao_me_perturbe | MongoDB | 30s | 3 | Não |
disparos_recentes_tabela | MongoDB | 30s | 3 | Não |
app_buyers | MongoDB | 30s | 3 | Não |
user_id2 | MongoDB | 30s | 3 | Não |
buscar_dados_mongo2 | MongoDB | 30s | 3 | Sim |
Componentes Retool
| Componente | Tipo | Descrição |
|---|---|---|
fileInput4 | File Input | Upload de arquivo CSV/XLSX com leads |
baseMesclada2 | Table | Tabela de output com dados processados e filtrados |
restrictions_log | Table | Tabela de output com log de restrições encontradas (CPF, Telefone, Motivo) |
switch_restrictions | Toggle | Ativa/desativa filtros de restrição |
is_app_switch | Toggle | Ativa/desativa busca de User ID |
input_maximum_age | Number Input | Idade máxima permitida |
input_last_campaign | Number Input | Dias para considerar disparos recentes |
input_percentual_hightouch_appbuyer | Number Input | Percentual (0-100) de app_buyers com comissão alta que vão para hightouch |
input_percentual_ai_csapp | Number Input | Percentual (0-100) de csapp hightouch que são elegíveis para IA |
input_percentual_ai_csativacao | Number Input | Percentual (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 loggingqueryObj: Objeto da query Retooloptions: Opções adicionaistimeoutMs: Timeout em milissegundosretries: Número de tentativasbackoffMs: 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:
- Valida entrada
- Formata CPFs
- Executa queries condicionalmente
- Realiza merge
- Aplica filtros
- 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_idlinha(object): Linha de dados com CPFsetAppBuyers(Set): Set com CPFs de compradores do apptipoTouch(string|null): Tag pré-calculada (‘hightouch’, ‘lowtouch’ ou null)
Retorno: String com nome ajustado da campanha
Lógica:
- Divide campanha por
_para obter partes - Verifica última parte (equipe):
- Se for
cscp,cscdxoucsport→ retorna sem alteração - Caso contrário:
- Com
dataFinalizacao→ muda paracsapp - Sem
dataFinalizacao→ muda paracsativacao
- Com
- Se for
- Se ficou
csappE 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_equipecom 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:
- Cria array com todos os telefones e suas importâncias
- Remove telefones nulos/vazios
- Busca primeiro telefone com
importance === 1 - 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 leadsdadosRestricoes: 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 fileInput4setAppBuyers(Set): Set com CPFs de compradores do appmapaUserId(Object): Map com user_id e data_de_finalizacao por CPFy(number): Percentual de app_buyers (comissão ≥ 200) que vão para hightouchz(number): Percentual de csapp hightouch elegíveis para IAw(number): Percentual de csativacao elegíveis para IA
Retorno: Map<CPF, {tipoTouch, lead_ai}> - Decisões para cada CPF
Processo:
- 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)
- 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
- 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étrica | Valor Esperado | Descrição |
|---|---|---|
| Tempo de execução total | 30-60s | Depende do volume e queries habilitadas |
| Timeout por query | 30s | Com 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 recomendado | 10.000 leads | Por 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
- Volume: Não recomendado para bases >100k registros (limitação de memória Retool)
- Timeout: Queries lentas podem falhar mesmo com retries
- Validação CPF: Não valida dígitos verificadores
- Sincronização: Dados do MongoDB podem estar desatualizados
Melhorias Sugeridas
- Adicionar validação completa de CPF (dígitos verificadores)
- Implementar processamento em lotes para volumes grandes
- Cache de queries de restrição para múltiplas execuções
- Exportar relatório de leads filtrados com motivos
- Adicionar validação de formato de telefone e email
Troubleshooting
Erros Comuns
| Erro | Causa Provável | Solução |
|---|---|---|
CPF inválido | CPF com formatação incorreta ou incompleto | Verificar formato no arquivo input |
Timeout em buscar_dados_mongo2 | Volume muito alto ou MongoDB lento | Reduzir volume ou otimizar query |
baseMesclada2 vazio | Todos os leads filtrados | Revisar critérios de filtro |
user_id2 falhou | Toggle ativado sem query configurada | Verificar configuração da query |
Leads duplicados | CPFs repetidos no input | Aplicar 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
| Data | Versão | Alteração | Autor |
|---|---|---|---|
| 2024-11-19 | 1.0 | Documentação inicial completa | Equipe |
Referências
- Documentação Retool - Queries
- MongoDB Query Performance
- Fluxo relacionado: Enriquecedor de Dados (aplicativo Retool)