This workflow follows the HTTP Request → Postgres recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "04_dados_de_teste",
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-224,
-32
],
"id": "0f60856f-52ea-406b-8dbc-b591a337c5e7",
"name": "When clicking \u2018Execute workflow\u2019"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "9ce56f02-6967-4372-b32d-c5f8eada46a2",
"name": "quantidade_de_leads",
"value": 3000,
"type": "number"
},
{
"id": "55cbe6b9-7753-4c03-8e32-e3b7a9719533",
"name": "quantidade_de_empresas",
"value": 3000,
"type": "number"
},
{
"id": "87d7912a-ae5f-4f2e-ad89-097addf9f5c5",
"name": "quantidade_de_contatos",
"value": 3000,
"type": "number"
},
{
"id": "c0b17187-0f58-4061-a513-26654907c797",
"name": "data_inicio",
"value": "01/01/2025",
"type": "string"
},
{
"id": "4c54301a-242e-43a3-8b9f-016c6cf99e0e",
"name": "=data_termino",
"value": "31/12/2025",
"type": "string"
},
{
"id": "acd8dcc0-b009-4f24-83ee-1c4527fbfe83",
"name": "servicos",
"value": "[\"Desenvolvimento Web\", \"Desenvolvimento Mobile\", \"Desenvolvimento de API\", \"Desenvolvimento de Integra\u00e7\u00e3o\", \"Consultoria em TI\", \"Suporte T\u00e9cnico\"]",
"type": "array"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
80,
-288
],
"id": "41f31744-422b-40ef-83a3-9618a948fd23",
"name": "dados_basicos"
},
{
"parameters": {
"operation": "select",
"schema": {
"__rl": true,
"value": "analise_dados",
"mode": "list",
"cachedResultName": "analise_dados"
},
"table": {
"__rl": true,
"value": "funis",
"mode": "list",
"cachedResultName": "funis"
},
"returnAll": true,
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
80,
-128
],
"id": "28b0c09c-f501-404a-9e2d-d1e49c08f25d",
"name": "funis",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "select",
"schema": {
"__rl": true,
"value": "analise_dados",
"mode": "list",
"cachedResultName": "analise_dados"
},
"table": {
"__rl": true,
"value": "etapas",
"mode": "list",
"cachedResultName": "etapas"
},
"returnAll": true,
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
80,
64
],
"id": "87405438-886a-49da-8efa-dd052c81459f",
"name": "etapas",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "select",
"schema": {
"__rl": true,
"value": "analise_dados",
"mode": "list",
"cachedResultName": "analise_dados"
},
"table": {
"__rl": true,
"value": "usuarios",
"mode": "list",
"cachedResultName": "usuarios"
},
"returnAll": true,
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
80,
240
],
"id": "622be8d1-910a-4f0e-a5ee-952c245ab94b",
"name": "usuarios",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "combineBySql",
"numberInputs": 8,
"query": "SELECT * FROM input1 LEFT JOIN input2 LEFT JOIN input3 LEFT JOIN input4 LEFT JOIN input5 LEFT JOIN input6 LEFT JOIN input7 LEFT JOIN input8",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
928,
32
],
"id": "149a3503-f458-4547-9067-97a7d0ebd458",
"name": "Merge"
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "funis",
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
288,
-128
],
"id": "f41edf12-9788-46c5-9028-bcfc955f9f4c",
"name": "Aggregate"
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "etapas",
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
288,
64
],
"id": "9103a52d-8ed8-4b19-962d-07c36cedafff",
"name": "Aggregate1"
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "usuarios",
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
288,
240
],
"id": "0221fb0d-a6ae-4d44-a750-2fd4098ea4c4",
"name": "Aggregate2"
},
{
"parameters": {
"jsCode": "const inputItems = $input.all();\n\nif (inputItems.length === 0) {\n throw new Error('Nenhum item foi recebido pelo node Code.');\n}\n\n/**\n * ============================================================\n * DADOS SINT\u00c9TICOS UTILIZADOS NA GERA\u00c7\u00c3O\n * ============================================================\n */\n\nconst PRIMEIROS_NOMES = [\n 'Amanda', 'Andr\u00e9', 'Bianca', 'Bruno', 'Caio', 'Camila',\n 'Carolina', 'Daniel', 'D\u00e9bora', 'Eduardo', 'Elisa', 'Felipe',\n 'Fernando', 'Gabriel', 'Giovana', 'Helena', 'Igor', 'Isadora',\n 'Joana', 'Jo\u00e3o', 'J\u00falia', 'Larissa', 'Leonardo', 'Lucas',\n 'Mariana', 'Mateus', 'Nat\u00e1lia', 'Ot\u00e1vio', 'Patr\u00edcia', 'Paula',\n 'Rafael', 'Renata', 'Rodrigo', 'Sabrina', 'Samuel', 'Tatiana',\n 'Vin\u00edcius', 'Vit\u00f3ria'\n];\n\nconst NOMES_COMPOSTOS = [\n 'Ana Clara',\n 'Ana J\u00falia',\n 'Ana Luiza',\n 'Ana Paula',\n 'Carlos Eduardo',\n 'Jo\u00e3o Gabriel',\n 'Jo\u00e3o Lucas',\n 'Jo\u00e3o Pedro',\n 'Jos\u00e9 Carlos',\n 'Jos\u00e9 Eduardo',\n 'Luiz Felipe',\n 'Luiz Gustavo',\n 'Maria Clara',\n 'Maria Eduarda',\n 'Maria Fernanda',\n 'Maria J\u00falia',\n 'Maria Luiza',\n 'Pedro Henrique',\n 'Paulo Henrique'\n];\n\nconst NOMES_INTERMEDIARIOS = [\n 'Aparecida',\n 'Augusto',\n 'Cristina',\n 'Eduardo',\n 'Fernanda',\n 'Gabriel',\n 'Gustavo',\n 'Helena',\n 'Henrique',\n 'J\u00falia',\n 'Luiza',\n 'Maria',\n 'Paulo',\n 'Pedro',\n 'Rafael',\n 'Regina',\n 'Renato',\n 'Ricardo'\n];\n\nconst SOBRENOMES = [\n 'Albuquerque', 'Almeida', 'Alves', 'Amaral', 'Andrade',\n 'Ara\u00fajo', 'Barbosa', 'Batista', 'Campos', 'Cardoso',\n 'Carvalho', 'Castro', 'Cavalcante', 'Correia', 'Costa',\n 'Cunha', 'Dias', 'Duarte', 'Farias', 'Ferreira',\n 'Freitas', 'Gomes', 'Lima', 'Lopes', 'Machado',\n 'Marques', 'Martins', 'Medeiros', 'Melo', 'Monteiro',\n 'Moraes', 'Moreira', 'Nascimento', 'Nogueira', 'Oliveira',\n 'Pereira', 'Ramos', 'Rezende', 'Ribeiro', 'Rocha',\n 'Santana', 'Santos', 'Silva', 'Souza', 'Teixeira',\n 'Torres', 'Vasconcelos', 'Vieira'\n];\n\nconst PREFIXOS_EMPRESA = [\n '\u00c1pice', 'Atlas', 'Aurora', 'Cobalto', 'Delta', 'Elo',\n 'Fluxo', 'Horizonte', 'Impulso', 'Integra', 'Lumina',\n 'M\u00e9trica', 'Nexa', 'Nova', 'Orbe', 'Ponto', 'Prisma',\n 'V\u00e9rtice', 'Via', 'Viva'\n];\n\nconst PREFIXOS_INSTITUCIONAIS_EMPRESA = [\n 'Grupo',\n 'Instituto',\n 'N\u00facleo',\n 'Rede',\n 'Central',\n 'Hub',\n 'Base',\n 'Alian\u00e7a'\n];\n\nconst SEGMENTOS_EMPRESA = [\n 'Comercial',\n 'Consultoria',\n 'Digital',\n 'Engenharia',\n 'Gest\u00e3o',\n 'Ind\u00fastria',\n 'Log\u00edstica',\n 'Neg\u00f3cios',\n 'Opera\u00e7\u00f5es',\n 'Servi\u00e7os',\n 'Solu\u00e7\u00f5es',\n 'Tecnologia',\n 'Varejo'\n];\n\nconst COMPLEMENTOS_EMPRESA = [\n 'Brasil',\n 'Corporativo',\n 'Empresarial',\n 'Estrat\u00e9gica',\n 'Integrada',\n 'Inteligente',\n 'Nacional',\n 'Prime',\n 'Pro',\n 'Tech',\n 'Digital',\n 'Global'\n];\n\nconst TERMINACOES_EMPRESA = [\n 'Ltda.',\n 'Solu\u00e7\u00f5es',\n 'Servi\u00e7os',\n 'Tecnologia',\n 'Consultoria',\n 'Neg\u00f3cios',\n 'Opera\u00e7\u00f5es'\n];\n\nconst CARGOS = [\n 'Diretor Comercial',\n 'Gerente de Opera\u00e7\u00f5es',\n 'Coordenador de TI',\n 'Analista de Sistemas',\n 'Gerente de Projetos',\n 'Diretor Executivo',\n 'Coordenador Comercial',\n 'Gerente Administrativo',\n 'Product Owner',\n 'S\u00f3cio Administrador',\n 'Head de Tecnologia',\n 'Analista de Neg\u00f3cios'\n];\n\n/**\n * Pesos sazonais mensais.\n *\n * N\u00e3o representam limites m\u00e1ximos.\n * Servem somente para criar meses com maior e menor entrada de leads.\n */\nconst PESOS_SAZONAIS_MES = {\n 1: 0.78,\n 2: 0.88,\n 3: 1.12,\n 4: 0.98,\n 5: 1.06,\n 6: 1.17,\n 7: 0.91,\n 8: 1.03,\n 9: 1.15,\n 10: 1.21,\n 11: 1.07,\n 12: 0.84\n};\n\n/**\n * ============================================================\n * FUN\u00c7\u00d5ES UTILIT\u00c1RIAS\n * ============================================================\n */\n\n/**\n * Gerador pseudoaleat\u00f3rio determin\u00edstico.\n *\n * A mesma seed produz novamente os mesmos registros.\n */\nfunction criarGeradorAleatorio(seedInicial) {\n let seed = Number(seedInicial) || 2025;\n\n return function random() {\n seed |= 0;\n seed = (seed + 0x6D2B79F5) | 0;\n\n let resultado = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n\n resultado =\n resultado +\n Math.imul(resultado ^ (resultado >>> 7), 61 | resultado) ^\n resultado;\n\n return ((resultado ^ (resultado >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nfunction inteiroAleatorio(random, minimo, maximo) {\n return Math.floor(random() * (maximo - minimo + 1)) + minimo;\n}\n\nfunction decimalAleatorio(random, minimo, maximo, casas = 2) {\n const valor = random() * (maximo - minimo) + minimo;\n return Number(valor.toFixed(casas));\n}\n\nfunction escolherAleatorio(random, array) {\n if (!Array.isArray(array) || array.length === 0) {\n throw new Error('Tentativa de selecionar um item em uma lista vazia.');\n }\n\n return array[inteiroAleatorio(random, 0, array.length - 1)];\n}\n\nfunction escolherComPeso(random, opcoes) {\n if (!Array.isArray(opcoes) || opcoes.length === 0) {\n throw new Error('A lista ponderada est\u00e1 vazia.');\n }\n\n const total = opcoes.reduce(\n (soma, opcao) => soma + Number(opcao.peso || 0),\n 0\n );\n\n if (total <= 0) {\n throw new Error('A lista ponderada n\u00e3o possui pesos v\u00e1lidos.');\n }\n\n let sorteio = random() * total;\n\n for (const opcao of opcoes) {\n sorteio -= Number(opcao.peso || 0);\n\n if (sorteio <= 0) {\n return opcao.valor;\n }\n }\n\n return opcoes[opcoes.length - 1].valor;\n}\n\nfunction normalizarTexto(valor) {\n return String(valor || '')\n .trim()\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction criarSlug(valor) {\n return normalizarTexto(valor)\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\nfunction pad(numero, tamanho = 2) {\n return String(numero).padStart(tamanho, '0');\n}\n\nfunction formatarNumeroFinanceiro(valor) {\n return Number(Number(valor || 0).toFixed(2));\n}\n\nfunction menorData(dataA, dataB) {\n return dataA.getTime() <= dataB.getTime() ? dataA : dataB;\n}\n\nfunction maiorData(dataA, dataB) {\n return dataA.getTime() >= dataB.getTime() ? dataA : dataB;\n}\n\nfunction adicionarDias(data, quantidade) {\n const novaData = new Date(data.getTime());\n novaData.setUTCDate(novaData.getUTCDate() + quantidade);\n return novaData;\n}\n\nfunction dataAleatoriaEntre(random, inicio, termino) {\n const minimo = inicio.getTime();\n const maximo = termino.getTime();\n\n if (maximo < minimo) {\n throw new Error('O intervalo utilizado para gerar uma data \u00e9 inv\u00e1lido.');\n }\n\n const timestamp = Math.floor(\n random() * (maximo - minimo + 1) + minimo\n );\n\n return new Date(timestamp);\n}\n\n/**\n * Aceita:\n *\n * 01/01/2025\n * 2025-01-01\n * 2025-01-01T10:00:00.000Z\n */\nfunction converterData(valor, nomeCampo) {\n if (!valor) {\n throw new Error(`O campo \"${nomeCampo}\" n\u00e3o foi informado.`);\n }\n\n const texto = String(valor).trim();\n\n const formatoBR = /^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/;\n const correspondenciaBR = texto.match(formatoBR);\n\n if (correspondenciaBR) {\n const [, dia, mes, ano] = correspondenciaBR;\n\n const data = new Date(\n Date.UTC(\n Number(ano),\n Number(mes) - 1,\n Number(dia),\n 0,\n 0,\n 0,\n 0\n )\n );\n\n const dataValida =\n data.getUTCFullYear() === Number(ano) &&\n data.getUTCMonth() === Number(mes) - 1 &&\n data.getUTCDate() === Number(dia);\n\n if (!dataValida) {\n throw new Error(\n `A data \"${texto}\" do campo \"${nomeCampo}\" \u00e9 inv\u00e1lida.`\n );\n }\n\n return data;\n }\n\n const formatoISO = /^\\d{4}-\\d{2}-\\d{2}(?:T.*)?$/;\n\n if (!formatoISO.test(texto)) {\n throw new Error(\n `A data \"${texto}\" do campo \"${nomeCampo}\" possui formato inv\u00e1lido.`\n );\n }\n\n const data = new Date(texto);\n\n if (Number.isNaN(data.getTime())) {\n throw new Error(\n `A data \"${texto}\" do campo \"${nomeCampo}\" \u00e9 inv\u00e1lida.`\n );\n }\n\n return data;\n}\n\nfunction inicioDoDia(data) {\n return new Date(\n Date.UTC(\n data.getUTCFullYear(),\n data.getUTCMonth(),\n data.getUTCDate(),\n 0,\n 0,\n 0,\n 0\n )\n );\n}\n\nfunction fimDoDia(data) {\n return new Date(\n Date.UTC(\n data.getUTCFullYear(),\n data.getUTCMonth(),\n data.getUTCDate(),\n 23,\n 59,\n 59,\n 999\n )\n );\n}\n\n/**\n * ============================================================\n * GERA\u00c7\u00c3O DE NOMES DE PESSOAS\n * ============================================================\n */\n\nfunction criarNomePessoa(random) {\n const usarNomeComposto = random() < 0.28;\n\n const primeiroNome = usarNomeComposto\n ? escolherAleatorio(random, NOMES_COMPOSTOS)\n : escolherAleatorio(random, PRIMEIROS_NOMES);\n\n /**\n * Aproximadamente:\n *\n * 28% com primeiro nome + sobrenome.\n * 47% com primeiro nome + dois sobrenomes.\n * 25% com primeiro nome + nome intermedi\u00e1rio + dois sobrenomes.\n *\n * Caso o primeiro nome j\u00e1 seja composto, o total de partes aumenta.\n */\n const formato = escolherComPeso(random, [\n {\n valor: 'curto',\n peso: 28\n },\n {\n valor: 'medio',\n peso: 47\n },\n {\n valor: 'longo',\n peso: 25\n }\n ]);\n\n const sobrenome1 = escolherAleatorio(\n random,\n SOBRENOMES\n );\n\n let sobrenome2 = escolherAleatorio(\n random,\n SOBRENOMES\n );\n\n while (sobrenome2 === sobrenome1) {\n sobrenome2 = escolherAleatorio(\n random,\n SOBRENOMES\n );\n }\n\n if (formato === 'curto') {\n return `${primeiroNome} ${sobrenome1}`;\n }\n\n if (formato === 'medio') {\n return `${primeiroNome} ${sobrenome1} ${sobrenome2}`;\n }\n\n const nomeIntermediario = escolherAleatorio(\n random,\n NOMES_INTERMEDIARIOS\n );\n\n return (\n `${primeiroNome} ` +\n `${nomeIntermediario} ` +\n `${sobrenome1} ` +\n `${sobrenome2}`\n );\n}\n\n/**\n * ============================================================\n * GERA\u00c7\u00c3O DE NOMES DE EMPRESAS\n * ============================================================\n */\n\nfunction criarNomeEmpresaBase(random) {\n const formato = escolherComPeso(random, [\n {\n valor: 'simples',\n peso: 35\n },\n {\n valor: 'composta',\n peso: 42\n },\n {\n valor: 'institucional',\n peso: 23\n }\n ]);\n\n const prefixo = escolherAleatorio(\n random,\n PREFIXOS_EMPRESA\n );\n\n const segmento = escolherAleatorio(\n random,\n SEGMENTOS_EMPRESA\n );\n\n if (formato === 'simples') {\n return `${prefixo} ${segmento}`;\n }\n\n if (formato === 'composta') {\n const complemento = escolherAleatorio(\n random,\n COMPLEMENTOS_EMPRESA\n );\n\n return `${prefixo} ${segmento} ${complemento}`;\n }\n\n const prefixoInstitucional = escolherAleatorio(\n random,\n PREFIXOS_INSTITUCIONAIS_EMPRESA\n );\n\n const terminacao = escolherAleatorio(\n random,\n TERMINACOES_EMPRESA\n );\n\n return (\n `${prefixoInstitucional} ` +\n `${prefixo} ` +\n `${segmento} ` +\n `${terminacao}`\n );\n}\n\n/**\n * Garante nomes empresariais \u00fanicos.\n *\n * Caso ocorra repeti\u00e7\u00e3o, um complemento num\u00e9rico sint\u00e9tico ser\u00e1 adicionado.\n */\nfunction criarNomeEmpresaUnico(random, nomesUtilizados) {\n const nomeBase = criarNomeEmpresaBase(random);\n\n if (!nomesUtilizados.has(nomeBase)) {\n nomesUtilizados.add(nomeBase);\n return nomeBase;\n }\n\n let contador = 2;\n let nomeComComplemento = `${nomeBase} ${contador}`;\n\n while (nomesUtilizados.has(nomeComComplemento)) {\n contador += 1;\n nomeComComplemento = `${nomeBase} ${contador}`;\n }\n\n nomesUtilizados.add(nomeComComplemento);\n\n return nomeComComplemento;\n}\n\n/**\n * ============================================================\n * DADOS DE CONTATO FICT\u00cdCIOS\n * ============================================================\n */\n\nfunction criarTelefoneContato(indice) {\n /**\n * DDD 00 propositalmente inv\u00e1lido.\n */\n const numero = String(indice)\n .padStart(8, '0')\n .slice(-8);\n\n return `+55 00 9${numero.slice(0, 4)}-${numero.slice(4)}`;\n}\n\nfunction criarTelefoneEmpresa(indice) {\n /**\n * DDD 00 propositalmente inv\u00e1lido.\n */\n const numero = String(indice)\n .padStart(4, '0')\n .slice(-4);\n\n return `+55 00 3000-${numero}`;\n}\n\n/**\n * ============================================================\n * LOCALIZA\u00c7\u00c3O DA ESTRUTURA PRINCIPAL DO INPUT\n * ============================================================\n */\n\n/**\n * Aceita:\n *\n * {\n * \"quantidade_de_leads\": 6000,\n * ...\n * }\n *\n * ou:\n *\n * {\n * \"Estrutura_de_teste\": {\n * \"quantidade_de_leads\": 6000,\n * ...\n * }\n * }\n */\nfunction obterEstrutura(json) {\n if (!json || typeof json !== 'object' || Array.isArray(json)) {\n throw new Error(\n 'O input recebido precisa ser um objeto JSON v\u00e1lido.'\n );\n }\n\n if (\n Object.prototype.hasOwnProperty.call(\n json,\n 'quantidade_de_leads'\n ) ||\n Object.prototype.hasOwnProperty.call(\n json,\n 'funis'\n ) ||\n Object.prototype.hasOwnProperty.call(\n json,\n 'usuarios'\n )\n ) {\n return json;\n }\n\n for (const valor of Object.values(json)) {\n if (\n valor &&\n typeof valor === 'object' &&\n !Array.isArray(valor) &&\n (\n Object.prototype.hasOwnProperty.call(\n valor,\n 'quantidade_de_leads'\n ) ||\n Object.prototype.hasOwnProperty.call(\n valor,\n 'funis'\n ) ||\n Object.prototype.hasOwnProperty.call(\n valor,\n 'usuarios'\n )\n )\n ) {\n return valor;\n }\n }\n\n throw new Error(\n 'N\u00e3o foi poss\u00edvel localizar a estrutura principal no input.'\n );\n}\n\n/**\n * ============================================================\n * LEITURA DIN\u00c2MICA DOS SERVI\u00c7OS\n * ============================================================\n */\n\n/**\n * Aceita:\n *\n * \"servicos\": [\n * \"Desenvolvimento Web\",\n * \"Consultoria em TI\"\n * ]\n *\n * ou:\n *\n * \"servicos\": [\n * { \"nome\": \"Desenvolvimento Web\" },\n * { \"name\": \"Consultoria em TI\" }\n * ]\n *\n * Tamb\u00e9m aceita:\n *\n * \"campos_personalizados_lead\": [\n * {\n * \"name\": \"Servi\u00e7os\",\n * \"type\": \"select\",\n * \"options\": [...]\n * }\n * ]\n */\nfunction obterServicos(estrutura) {\n const listaDireta =\n estrutura.servicos ||\n estrutura['servi\u00e7os'] ||\n estrutura.lista_de_servicos ||\n estrutura['lista_de_servi\u00e7os'];\n\n if (Array.isArray(listaDireta)) {\n const servicos = listaDireta\n .map((servico) => {\n if (typeof servico === 'string') {\n return servico.trim();\n }\n\n if (servico && typeof servico === 'object') {\n return String(\n servico.nome ||\n servico.name ||\n servico.value ||\n ''\n ).trim();\n }\n\n return '';\n })\n .filter(Boolean);\n\n if (servicos.length > 0) {\n return [...new Set(servicos)];\n }\n }\n\n const camposLead = Array.isArray(\n estrutura.campos_personalizados_lead\n )\n ? estrutura.campos_personalizados_lead\n : [];\n\n const campoServicos = camposLead.find((campo) => {\n const nomeCampo = normalizarTexto(\n campo.name ||\n campo.nome\n );\n\n return (\n nomeCampo === 'servicos' ||\n nomeCampo === 'servico'\n );\n });\n\n if (campoServicos) {\n const opcoes =\n campoServicos.options ||\n campoServicos.enums ||\n campoServicos.opcoes ||\n [];\n\n const servicos = opcoes\n .map((opcao) => {\n if (typeof opcao === 'string') {\n return opcao.trim();\n }\n\n if (opcao && typeof opcao === 'object') {\n return String(\n opcao.nome ||\n opcao.name ||\n opcao.value ||\n ''\n ).trim();\n }\n\n return '';\n })\n .filter(Boolean);\n\n if (servicos.length > 0) {\n return [...new Set(servicos)];\n }\n }\n\n throw new Error(\n 'Nenhum servi\u00e7o foi encontrado. Informe a propriedade \"servicos\" no input.'\n );\n}\n\n/**\n * ============================================================\n * DISTRIBUI\u00c7\u00c3O SAZONAL DOS LEADS\n * ============================================================\n */\n\nfunction obterMesesEntreDatas(dataInicio, dataTermino) {\n const meses = [];\n\n let cursor = new Date(\n Date.UTC(\n dataInicio.getUTCFullYear(),\n dataInicio.getUTCMonth(),\n 1\n )\n );\n\n const limite = new Date(\n Date.UTC(\n dataTermino.getUTCFullYear(),\n dataTermino.getUTCMonth(),\n 1\n )\n );\n\n while (cursor.getTime() <= limite.getTime()) {\n const inicioMes = new Date(\n Date.UTC(\n cursor.getUTCFullYear(),\n cursor.getUTCMonth(),\n 1,\n 0,\n 0,\n 0,\n 0\n )\n );\n\n const fimMes = new Date(\n Date.UTC(\n cursor.getUTCFullYear(),\n cursor.getUTCMonth() + 1,\n 0,\n 23,\n 59,\n 59,\n 999\n )\n );\n\n meses.push({\n ano: cursor.getUTCFullYear(),\n mes: cursor.getUTCMonth() + 1,\n inicio: maiorData(inicioMes, dataInicio),\n termino: menorData(fimMes, dataTermino)\n });\n\n cursor = new Date(\n Date.UTC(\n cursor.getUTCFullYear(),\n cursor.getUTCMonth() + 1,\n 1\n )\n );\n }\n\n return meses;\n}\n\nfunction distribuirQuantidadePorMes(\n random,\n quantidade,\n meses\n) {\n const mesesComPeso = meses.map((mes) => {\n const pesoBase =\n PESOS_SAZONAIS_MES[mes.mes] || 1;\n\n /**\n * Varia\u00e7\u00e3o adicional para evitar uma distribui\u00e7\u00e3o mec\u00e2nica.\n */\n const variacao = decimalAleatorio(\n random,\n 0.88,\n 1.12,\n 4\n );\n\n return {\n ...mes,\n peso: pesoBase * variacao\n };\n });\n\n const somaPesos = mesesComPeso.reduce(\n (soma, mes) => soma + mes.peso,\n 0\n );\n\n const distribuicao = mesesComPeso.map((mes) => {\n const quantidadeExata =\n quantidade * (mes.peso / somaPesos);\n\n const quantidadeInicial =\n Math.floor(quantidadeExata);\n\n return {\n ...mes,\n quantidade: quantidadeInicial,\n fracao: quantidadeExata - quantidadeInicial\n };\n });\n\n const quantidadeDistribuida =\n distribuicao.reduce(\n (soma, mes) => soma + mes.quantidade,\n 0\n );\n\n let restante =\n quantidade - quantidadeDistribuida;\n\n distribuicao\n .sort((a, b) => b.fracao - a.fracao)\n .forEach((mes) => {\n if (restante > 0) {\n mes.quantidade += 1;\n restante -= 1;\n }\n });\n\n return distribuicao.sort((a, b) => {\n if (a.ano !== b.ano) {\n return a.ano - b.ano;\n }\n\n return a.mes - b.mes;\n });\n}\n\n/**\n * ============================================================\n * CLASSIFICA\u00c7\u00c3O DAS ETAPAS\n * ============================================================\n */\n\nfunction obterPesoEtapa(nomeEtapa) {\n const nome = normalizarTexto(nomeEtapa);\n\n if (nome.includes('incoming')) return 10;\n if (nome.includes('novo lead qualificado')) return 13;\n if (nome.includes('novo lead')) return 16;\n if (nome.includes('contato inicial')) return 15;\n if (nome.includes('qualificacao em andamento')) return 14;\n if (nome === 'qualificado') return 11;\n if (nome.includes('desqualificado')) return 8;\n if (nome.includes('contato de vendas')) return 12;\n if (nome.includes('proposta')) return 10;\n if (nome.includes('negociacao')) return 8;\n if (nome.includes('closed - won')) return 7;\n if (nome.includes('closed - lost')) return 9;\n\n return 5;\n}\n\nfunction etapaEhGanha(nomeEtapa) {\n const nome = normalizarTexto(nomeEtapa);\n\n return (\n nome.includes('closed - won') ||\n nome.includes('fechado - ganhou') ||\n nome.includes('fechado ganho') ||\n nome === 'ganho' ||\n nome === 'ganhou'\n );\n}\n\nfunction etapaEhDesqualificada(nomeEtapa) {\n const nome = normalizarTexto(nomeEtapa);\n\n return nome.includes('desqualificado');\n}\n\nfunction etapaEhPerdida(nomeEtapa) {\n const nome = normalizarTexto(nomeEtapa);\n\n return (\n etapaEhDesqualificada(nomeEtapa) ||\n nome.includes('closed - lost') ||\n nome.includes('fechado - perdido') ||\n nome.includes('fechado perdido') ||\n nome === 'perdido'\n );\n}\n\nfunction etapaEhPropostaOuNegociacao(nomeEtapa) {\n const nome = normalizarTexto(nomeEtapa);\n\n return (\n nome.includes('proposta') ||\n nome.includes('negociacao')\n );\n}\n\n/**\n * Define os indicadores comerciais do lead.\n */\nfunction classificarEtapaComercial(\n random,\n nomeEtapa\n) {\n const vendaRealizada =\n etapaEhGanha(nomeEtapa);\n\n const leadPerdido =\n etapaEhPerdida(nomeEtapa);\n\n const desqualificado =\n etapaEhDesqualificada(nomeEtapa);\n\n const propostaOuNegociacao =\n etapaEhPropostaOuNegociacao(nomeEtapa);\n\n if (vendaRealizada) {\n return {\n proposta_gerada: true,\n possui_valor_comercial: true,\n venda_realizada: true,\n lead_perdido: false\n };\n }\n\n if (propostaOuNegociacao) {\n return {\n proposta_gerada: true,\n possui_valor_comercial: true,\n venda_realizada: false,\n lead_perdido: false\n };\n }\n\n if (desqualificado) {\n return {\n proposta_gerada: false,\n possui_valor_comercial: false,\n venda_realizada: false,\n lead_perdido: true\n };\n }\n\n if (leadPerdido) {\n /**\n * Parte das oportunidades perdidas recebeu proposta\n * antes do encerramento.\n */\n const propostaGerada = random() < 0.7;\n\n return {\n proposta_gerada: propostaGerada,\n possui_valor_comercial: propostaGerada,\n venda_realizada: false,\n lead_perdido: true\n };\n }\n\n return {\n proposta_gerada: false,\n possui_valor_comercial: false,\n venda_realizada: false,\n lead_perdido: false\n };\n}\n\n/**\n * ============================================================\n * UTMS\n * ============================================================\n */\n\nfunction gerarUtm(random, servico, criadoEm) {\n const trimestre =\n Math.floor(criadoEm.getUTCMonth() / 3) + 1;\n\n const ano =\n criadoEm.getUTCFullYear();\n\n const servicoSlug =\n criarSlug(servico);\n\n const plataforma = escolherComPeso(random, [\n {\n valor: 'meta',\n peso: 58\n },\n {\n valor: 'google',\n peso: 42\n }\n ]);\n\n if (plataforma === 'meta') {\n const source = escolherComPeso(random, [\n {\n valor: 'instagram',\n peso: 62\n },\n {\n valor: 'facebook',\n peso: 38\n }\n ]);\n\n return {\n utm_source: source,\n utm_campaing:\n `meta_${servicoSlug}_q${trimestre}_${ano}`,\n utm_medium: 'paid_social',\n utm_content: escolherAleatorio(random, [\n 'carrossel_case',\n 'video_demonstracao',\n 'imagem_beneficio',\n 'remarketing_visitantes',\n 'formulario_leads'\n ])\n };\n }\n\n return {\n utm_source: 'google',\n utm_campaing:\n `google_${servicoSlug}_q${trimestre}_${ano}`,\n utm_medium: 'cpc',\n utm_content: escolherAleatorio(random, [\n 'pesquisa_servico',\n 'pesquisa_orcamento',\n 'pesquisa_empresa',\n 'display_remarketing',\n 'performance_max'\n ])\n };\n}\n\n/**\n * ============================================================\n * PROCESSAMENTO PRINCIPAL\n * ============================================================\n */\n\nconst resultados = [];\n\nfor (const inputItem of inputItems) {\n const estrutura =\n obterEstrutura(inputItem.json);\n\n const quantidade = Number(\n estrutura.quantidade_de_leads\n );\n\n const seed = Number(\n estrutura.seed ?? 2025\n );\n\n const valorMinimoVenda = Number(\n estrutura.valor_minimo_venda ?? 2500\n );\n\n const valorMaximoVenda = Number(\n estrutura.valor_maximo_venda ?? 120000\n );\n\n if (\n !Number.isInteger(quantidade) ||\n quantidade <= 0\n ) {\n throw new Error(\n 'O campo \"quantidade_de_leads\" precisa ser um n\u00famero inteiro maior que zero.'\n );\n }\n\n if (quantidade > 100000) {\n throw new Error(\n 'A quantidade solicitada excede o limite de 100000 registros por execu\u00e7\u00e3o.'\n );\n }\n\n if (\n !Number.isFinite(valorMinimoVenda) ||\n valorMinimoVenda < 0\n ) {\n throw new Error(\n 'O campo \"valor_minimo_venda\" precisa ser maior ou igual a zero.'\n );\n }\n\n if (\n !Number.isFinite(valorMaximoVenda) ||\n valorMaximoVenda <= 0\n ) {\n throw new Error(\n 'O campo \"valor_maximo_venda\" precisa ser maior que zero.'\n );\n }\n\n if (valorMaximoVenda < valorMinimoVenda) {\n throw new Error(\n 'O valor m\u00e1ximo da venda precisa ser maior ou igual ao valor m\u00ednimo.'\n );\n }\n\n const dataInicio = inicioDoDia(\n converterData(\n estrutura.data_inicio,\n 'data_inicio'\n )\n );\n\n const dataTermino = fimDoDia(\n converterData(\n estrutura.data_termino,\n 'data_termino'\n )\n );\n\n if (dataTermino.getTime() < dataInicio.getTime()) {\n throw new Error(\n 'A data de t\u00e9rmino precisa ser maior ou igual \u00e0 data de in\u00edcio.'\n );\n }\n\n const funis = Array.isArray(\n estrutura.funis\n )\n ? estrutura.funis\n : [];\n\n const etapas = Array.isArray(\n estrutura.etapas\n )\n ? estrutura.etapas\n : [];\n\n const usuarios = Array.isArray(\n estrutura.usuarios\n )\n ? estrutura.usuarios\n : [];\n\n const servicos =\n obterServicos(estrutura);\n\n if (funis.length === 0) {\n throw new Error(\n 'Nenhum funil foi recebido no campo \"funis\".'\n );\n }\n\n if (etapas.length === 0) {\n throw new Error(\n 'Nenhuma etapa foi recebida no campo \"etapas\".'\n );\n }\n\n if (usuarios.length === 0) {\n throw new Error(\n 'Nenhum usu\u00e1rio foi recebido no campo \"usuarios\".'\n );\n }\n\n const random =\n criarGeradorAleatorio(seed);\n\n const meses =\n obterMesesEntreDatas(\n dataInicio,\n dataTermino\n );\n\n const distribuicaoMensal =\n distribuirQuantidadePorMes(\n random,\n quantidade,\n meses\n );\n\n /**\n * Cerca de 72% dos leads ter\u00e3o empresas distintas.\n *\n * Algumas empresas poder\u00e3o aparecer em mais de um lead,\n * simulando recorr\u00eancia ou novas demandas.\n */\n const quantidadeEmpresas = Math.max(\n 1,\n Math.ceil(quantidade * 0.72)\n );\n\n const empresas = [];\n const nomesEmpresasUtilizados = new Set();\n\n for (\n let indice = 1;\n indice <= quantidadeEmpresas;\n indice++\n ) {\n empresas.push({\n empresa_id_simulado:\n `EMP-${pad(indice, 6)}`,\n\n nome:\n criarNomeEmpresaUnico(\n random,\n nomesEmpresasUtilizados\n ),\n\n telefone:\n criarTelefoneEmpresa(indice),\n\n email:\n `empresa${pad(indice, 6)}@exemplo.invalid`,\n\n ltv: 0,\n\n data_ultimo_contrato: null,\n\n criado_em: null,\n\n atualizado_em: null\n });\n }\n\n /**\n * Gera datas de cria\u00e7\u00e3o respeitando a sazonalidade mensal.\n */\n const datasCriacao = [];\n\n for (const mes of distribuicaoMensal) {\n for (\n let indice = 0;\n indice < mes.quantidade;\n indice++\n ) {\n datasCriacao.push(\n dataAleatoriaEntre(\n random,\n mes.inicio,\n mes.termino\n )\n );\n }\n }\n\n datasCriacao.sort(\n (a, b) =>\n a.getTime() - b.getTime()\n );\n\n /**\n * Registros tempor\u00e1rios necess\u00e1rios para calcular\n * o LTV final das empresas.\n */\n const registrosTemporarios = [];\n\n for (\n let indice = 0;\n indice < quantidade;\n indice++\n ) {\n const numeroSequencial =\n indice + 1;\n\n const criadoEm =\n datasCriacao[indice];\n\n const limiteAtualizacao = menorData(\n adicionarDias(\n criadoEm,\n inteiroAleatorio(\n random,\n 1,\n 90\n )\n ),\n dataTermino\n );\n\n const atualizadoEm =\n dataAleatoriaEntre(\n random,\n criadoEm,\n limiteAtualizacao\n );\n\n /**\n * Algumas empresas recebem mais de um lead.\n */\n const indiceEmpresa =\n Math.floor(\n Math.pow(random(), 1.35) *\n empresas.length\n );\n\n const empresa = empresas[\n Math.min(\n indiceEmpresa,\n empresas.length - 1\n )\n ];\n\n const contatoNome =\n criarNomePessoa(random);\n\n const cargo =\n escolherAleatorio(\n random,\n CARGOS\n );\n\n const servico =\n escolherAleatorio(\n random,\n servicos\n );\n\n const usuario =\n escolherAleatorio(\n random,\n usuarios\n );\n\n const funil =\n escolherAleatorio(\n random,\n funis\n );\n\n const etapasDoFunil =\n etapas.filter(\n (etapa) =>\n String(etapa.funil_id) ===\n String(funil.id)\n );\n\n if (etapasDoFunil.length === 0) {\n throw new Error(\n `Nenhuma etapa foi encontrada para o funil \"${funil.nome}\".`\n );\n }\n\n const etapa =\n escolherComPeso(\n random,\n etapasDoFunil.map(\n (item) => ({\n valor: item,\n peso: obterPesoEtapa(\n item.nome\n )\n })\n )\n );\n\n const classificacao =\n classificarEtapaComercial(\n random,\n etapa.nome\n );\n\n /**\n * O valor representa o potencial comercial da oportunidade.\n *\n * Pode existir em:\n *\n * - propostas enviadas;\n * - negocia\u00e7\u00f5es;\n * - vendas ganhas;\n * - parte das oportunidades perdidas.\n */\n const valorVenda =\n classificacao.possui_valor_comercial\n ? decimalAleatorio(\n random,\n valorMinimoVenda,\n valorMaximoVenda,\n 2\n )\n : 0;\n\n /**\n * A data do contrato somente existe para vendas ganhas.\n */\n const dataContrato =\n classificacao.venda_realizada\n ? dataAleatoriaEntre(\n random,\n criadoEm,\n atualizadoEm\n )\n : null;\n\n const utm =\n gerarUtm(\n random,\n servico,\n criadoEm\n );\n\n if (\n !empresa.criado_em ||\n criadoEm < empresa.criado_em\n ) {\n empresa.criado_em = criadoEm;\n }\n\n if (\n !empresa.atualizado_em ||\n atualizadoEm > empresa.atualizado_em\n ) {\n empresa.atualizado_em =\n atualizadoEm;\n }\n\n /**\n * LTV considera exclusivamente contratos ganhos.\n */\n if (\n classificacao.venda_realizada\n ) {\n empresa.ltv +=\n valorVenda;\n\n if (\n !empresa.data_ultimo_contrato ||\n dataContrato >\n empresa.data_ultimo_contrato\n ) {\n empresa.data_ultimo_contrato =\n dataContrato;\n }\n }\n\n registrosTemporarios.push({\n numeroSequencial,\n criadoEm,\n atualizadoEm,\n dataContrato,\n classificacao,\n valorVenda,\n empresa,\n contatoNome,\n cargo,\n servico,\n usuario,\n funil,\n etapa,\n utm\n });\n }\n\n /**\n * Monta o retorno final ap\u00f3s o c\u00e1lculo definitivo do LTV.\n */\n for (\n const registro\n of registrosTemporarios\n ) {\n const {\n numeroSequencial,\n criadoEm,\n atualizadoEm,\n dataContrato,\n classificacao,\n valorVenda,\n empresa,\n contatoNome,\n cargo,\n servico,\n usuario,\n funil,\n etapa,\n utm\n } = registro;\n\n resultados.push({\n json: {\n lead: {\n lead_id_simulado:\n `LEAD-${pad(\n numeroSequencial,\n 6\n )}`,\n\n nome:\n `Projeto ${servico} - ${empresa.nome}`,\n\n funil_id:\n String(\n funil.id || ''\n ),\n\n funil_id_externo:\n String(\n funil.id_externo || ''\n ),\n\n funil_name:\n String(\n funil.nome || ''\n ),\n\n etapa_id:\n String(\n etapa.id || ''\n ),\n\n etapa_id_externo:\n String(\n etapa.id_externo || ''\n ),\n\n etapa_name:\n String(\n etapa.nome || ''\n ),\n\n /**\n * Valor potencial ou realizado da oportunidade.\n */\n venda:\n formatarNumeroFinanceiro(\n valorVenda\n ),\n\n /**\n * true somente quando houve fechamento ganho.\n */\n venda_realizada:\n classificacao.venda_realizada,\n\n /**\n * true quando a oportunidade recebeu uma proposta.\n */\n proposta_gerada:\n classificacao.proposta_gerada,\n\n /**\n * true para perdas e desqualifica\u00e7\u00f5es.\n */\n lead_perdido:\n classificacao.lead_perdido,\n\n usuario_responsavel_id:\n String(\n usuario.id || ''\n ),\n\n usuario_responsavel_id_externo:\n String(\n usuario.id_externo || ''\n ),\n\n usuario_responsavel:\n String(\n usuario.nome || ''\n ),\n\n usuario_responsavel_papel:\n String(\n usuario.papel || ''\n ),\n\n utm_source:\n utm.utm_source,\n\n /**\n * Mantido como \"utm_campaing\" para respeitar\n * a estrutura originalmente solicitada.\n *\n * A grafia convencional \u00e9 \"utm_campaign\".\n */\n utm_campaing:\n utm.utm_campaing,\n\n utm_medium:\n utm.utm_medium,\n\n utm_content:\n utm.utm_content,\n\n servicos:\n servico,\n\n data_contrato:\n dataContrato\n ? dataContrato.toISOString()\n : null,\n\n criado_em:\n criadoEm.toISOString(),\n\n atualizado_em:\n atualizadoEm.toISOString(),\n\n mes_referencia:\n `${criadoEm.getUTCFullYear()}-${pad(\n criadoEm.getUTCMonth() + 1\n )}`\n },\n\n contato: {\n contato_id_simulado:\n `CON-${pad(\n numeroSequencial,\n 6\n )}`,\n\n nome:\n contatoNome,\n\n telefone:\n criarTelefoneContato(\n numeroSequencial\n ),\n\n email:\n `contato${pad(\n numeroSequencial,\n 6\n )}@exemplo.invalid`,\n\n cargo,\n\n criado_em:\n criadoEm.toISOString(),\n\n atualizado_em:\n atualizadoEm.toISOString()\n },\n\n empresa: {\n empresa_id_simulado:\n empresa.empresa_id_simulado,\n\n nome:\n empresa.nome,\n\n telefone:\n empresa.telefone,\n\n email:\n empresa.email,\n\n /**\n * LTV considera somente contratos efetivamente ganhos.\n */\n ltv:\n formatarNumeroFinanceiro(\n empresa.ltv\n ),\n\n data_ultimo_contrato:\n empresa.data_ultimo_contrato\n ? empresa.data_ultimo_contrato.toISOString()\n : null,\n\n criado_em:\n empresa.criado_em\n ? empresa.criado_em.toISOString()\n : criadoEm.toISOString(),\n\n atualizado_em:\n empresa.atualizado_em\n ? empresa.atualizado_em.toISOString()\n : atualizadoEm.toISOString()\n }\n }\n });\n }\n}\n\nreturn resultados;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4432,
848
],
"id": "6145387a-13ef-4be6-a2c2-081e95ce5b8c",
"name": "Code in JavaScript"
},
{
"parameters": {
"jsCode": "const inputItems = $input.all();\n\nconst PRIMEIROS_NOMES = [\n 'Amanda', 'Andr\u00e9', 'Bianca', 'Bruno', 'Caio', 'Camila',\n 'Carolina', 'Daniel', 'D\u00e9bora', 'Eduardo', 'Elisa', 'Felipe',\n 'Fernando', 'Gabriel', 'Giovana', 'Helena', 'Igor', 'Isadora',\n 'Joana', 'Jo\u00e3o', 'J\u00falia', 'Larissa', 'Leonardo', 'Lucas',\n 'Mariana', 'Mateus', 'Nat\u00e1lia', 'Ot\u00e1vio', 'Patr\u00edcia', 'Paula',\n 'Rafael', 'Renata', 'Rodrigo', 'Sabrina', 'Samuel', 'Tatiana',\n 'Vin\u00edcius', 'Vit\u00f3ria'\n];\n\nconst NOMES_COMPOSTOS = [\n 'Ana Clara', 'Ana J\u00falia', 'Ana Luiza', 'Ana Paula',\n 'Carlos Eduardo', 'Jo\u00e3o Gabriel', 'Jo\u00e3o Lucas', 'Jo\u00e3o Pedro',\n 'Jos\u00e9 Carlos', 'Jos\u00e9 Eduardo', 'Luiz Felipe', 'Luiz Gustavo',\n 'Maria Clara', 'Maria Eduarda', 'Maria Fernanda', 'Maria J\u00falia',\n 'Maria Luiza', 'Pedro Henrique', 'Paulo Henrique'\n];\n\nconst NOMES_INTERMEDIARIOS = [\n 'Aparecida', 'Augusto', 'Cristina', 'Eduardo', 'Fernanda',\n 'Gabriel', 'Gustavo', 'Helena', 'Henrique', 'J\u00falia',\n 'Luiza', 'Maria', 'Paulo', 'Pedro', 'Rafael', 'Regina',\n 'Renato', 'Ricardo'\n];\n\nconst SOBRENOMES = [\n 'Albuquerque', 'Almeida', 'Alves', 'Amaral', 'Andrade',\n 'Ara\u00fajo', 'Barbosa', 'Batista', 'Campos', 'Cardoso',\n 'Carvalho', 'Castro', 'Cavalcante', 'Correia', 'Costa',\n 'Cunha', 'Dias', 'Duarte', 'Farias', 'Ferreira',\n 'Freitas', 'Gomes', 'Lima', 'Lopes', 'Machado',\n 'Marques', 'Martins', 'Medeiros', 'Melo', 'Monteiro',\n 'Moraes', 'Moreira', 'Nascimento', 'Nogueira', 'Oliveira',\n 'Pereira', 'Ramos', 'Rezende', 'Ribeiro', 'Rocha',\n 'Santana', 'Santos', 'Silva', 'Souza', 'Teixeira',\n 'Torres', 'Vasconcelos', 'Vieira'\n];\n\nconst CARGOS = [\n 'Diretor Comercial',\n 'Gerente de Opera\u00e7\u00f5es',\n 'Coordenador de TI',\n 'Analista de Sistemas',\n 'Gerente de Projetos',\n 'Diretor Executivo',\n 'Coordenador Comercial',\n 'Gerente Administrativo',\n 'Product Owner',\n 'S\u00f3cio Administrador',\n 'Head de Tecnologia',\n 'Analista de Neg\u00f3cios',\n 'Gerente Financeiro',\n 'Coordenador de Marketing',\n 'Supervisor de Atendimento'\n];\n\nconst LIMITE_CONTATOS_POR_BODY = 250;\nconst LIMITE_CONTATOS_POR_EXECUCAO = 25000;\n\nif (inputItems.length === 0) {\n throw new Error('Nenhum item foi recebido pelo node Code.');\n}\n\nconst empresas = normalizarEmpresas(inputItems);\nconst configuracao = obterConfiguracaoGeral();\nconst quantidade = Number(configuracao.quantidade_de_contatos);\nconst seed = Number(configuracao.seed ?? 2025);\n\nif (!Number.isInteger(quantidade) || quantidade <= 0) {\n throw new Error(\n 'O campo \"quantidade_de_contatos\" precisa ser um n\u00famero inteiro maior que zero no node Merge.'\n );\n}\n\nif (quantidade > LIMITE_CONTATOS_POR_EXECUCAO) {\n throw new Error(\n `A quantidade solicitada excede o limite de ${LIMITE_CONTATOS_POR_EXECUCAO} contatos por execu\u00e7\u00e3o.`\n );\n}\n\nif (empresas.length === 0) {\n throw new Error('Nenhuma empresa v\u00e1lida foi recebida no input do node Code.');\n}\n\nconst dataInicio = inicioDoDia(converterData(configuracao.data_inicio, 'data_inicio'));\nconst dataTermino = fimDoDia(converterData(configuracao.data_termino, 'data_termino'));\n\nif (dataTermino.getTime() < dataInicio.getTime()) {\n throw new Error('A data de t\u00e9rmino precisa ser maior ou igual \u00e0 data de in\u00edcio.');\n}\n\nconst random = criarGeradorAleatorio(seed);\nconst emailsUtilizados = new Set();\nconst contatos = [];\nconst body = [];\n\nfor (let indice = 1; indice <= quantidade; indice++) {\n const empresa = escolherEmpresaParaContato(random, empresas, indice);\n validarEmpresaParaVinculo(empresa, indice);\n\n const nome = criarNomePessoa(random);\n const cargo = escolherAleatorio(random, CARGOS);\n const dataMinimaContato = obterDataMinimaContato(empresa, dataInicio, dataTermino);\n const criadoEm = dataAleatoriaEntre(random, dataMinimaContato, dataTermino);\n const atualizadoEm = dataAleatoriaEntre(random, criadoEm, dataTermino);\n const email = criarEmailContato(nome, indice, emailsUtilizados);\n\n const contato = criarContatoLocal(indice, empresa, nome, cargo, email, criadoEm, atualizadoEm);\n\n contatos.push(contato);\n body.push(criarBodyContatoKommo(contato, configuracao));\n}\n\nconst lotesBody = dividirEmLotes(body, LIMITE_CONTATOS_POR_BODY);\nconst lotesContatos = dividirEmLotes(contatos, LIMITE_CONTATOS_POR_BODY);\nconst resultados = [];\n\nfor (let indiceLote = 0; indiceLote < lotesBody.length; indiceLote++) {\n resultados.push({\n json: {\n body: lotesBody[indiceLote],\n contatos: lotesContatos[indiceLote],\n lote: indiceLote + 1,\n total_lotes: lotesBody.length,\n quantidade_no_lote: lotesBody[indiceLote].length\n }\n });\n}\n\nreturn resultados;\n\nfunction obterConfiguracaoGeral() {\n let itensMerge = [];\n\n try {\n itensMerge = $('Merge').all();\n } catch (erro) {\n itensMerge = [];\n }\n\n const configuracao = itensMerge\n .map((item) => item?.json ?? item)\n .find((json) => json && typeof json === 'object' && !Array.isArray(json));\n\n if (!configuracao) {\n throw new Error('N\u00e3o foi poss\u00edvel localizar os dados de configura\u00e7\u00e3o no node \"Merge\".');\n }\n\n return configuracao;\n}\n\nfunction normalizarEmpresas(itens) {\n return itens\n .flatMap((item) => {\n const json = item?.json ?? item;\n return Array.isArray(json) ? json : [json];\n })\n .map((empresa) => empresa?.json ?? empresa)\n .filter((empresa) => empresa && typeof empresa === 'object' && !Array.isArray(empresa));\n}\n\nfunction validarEmpresaParaVinculo(empresa, indiceContato) {\n if (!valorPreenchido(empresa.id)) {\n throw new Error(`A empresa selecionada para o contato ${indiceContato} n\u00e3o possui \"id\" do banco.`);\n }\n\n if (!valorPreenchido(empresa.id_externo)) {\n throw new Error(\n `A empresa \"${empresa.nome || empresa.id}\" selecionada para o contato ${indiceContato} n\u00e3o possui \"id_externo\" da Kommo.`\n );\n }\n\n const idExterno = Number(empresa.id_externo);\n\n if (!Number.isInteger(idExterno) || idExterno <= 0) {\n throw new Error(\n `A empresa \"${empresa.nome || empresa.id}\" possui \"id_externo\" inv\u00e1lido para v\u00ednculo na Kommo.`\n );\n }\n}\n\nfunction escolherEmpresaParaContato(random, listaEmpresas, indice) {\n if (indice <= listaEmpresas.length) {\n return listaEmpresas[indice - 1];\n }\n\n return listaEmpresas[Math.floor(random() * listaEmpresas.length)];\n}\n\nfunction criarContatoLocal(indice, empresa, nome, cargo, email, criadoEm, atualizadoEm) {\n return {\n contato_id_simulado: `CON-${pad(indice, 6)}`,\n id_externo: '',\n empresa_id: String(empresa.id),\n empresa_id_externo: String(empresa.id_externo),\n empresa_nome: empresa.nome || '',\n nome,\n telefone: criarTelefoneContato(indice),\n email,\n cargo,\n criado_em: criadoEm.toISOString(),\n atualizado_em: atualizadoEm.toISOString()\n };\n}\n\nfunction criarBodyContatoKommo(contato, configuracao) {\n const customFieldsValues = [];\n\n adicionarCampoPersonalizado(\n customFieldsValues,\n { field_code: 'PHONE' },\n contato.telefone,\n { enum_code: 'WORK' }\n );\n\n adicionarCampoPersonalizado(\n customFieldsValues,\n { field_code: 'EMAIL' },\n contato.email,\n { enum_code: 'WORK' }\n );\n\n adicionarCampoPersonalizado(customFieldsValues, { field_code: 'POSITION' }, contato.cargo);\n\n const bodyContato = {\n request_id: contato.contato_id_simulado,\n name: contato.nome,\n created_at: unixSegundos(contato.criado_em),\n updated_at: unixSegundos(contato.atualizado_em),\n custom_fields_values: customFieldsValues,\n _embedded: {\n companies: [\n {\n id: Number(contato.empresa_id_externo)\n }\n ]\n }\n };\n\n const responsibleUserId = valorOuNulo(\n configuracao.responsible_user_id || configuracao.usuario_responsavel_id || configuracao.responsavel_id\n );\n\n if (responsibleUserId !== null) {\n bodyContato.responsible_user_id = Number(responsibleUserId);\n }\n\n const createdBy = valorOuNulo(configuracao.created_by || configuracao.criado_por);\n const updatedBy = valorOuNulo(configuracao.updated_by || configuracao.atualizado_por);\n\n if (createdBy !== null) {\n bodyContato.created_by = Number(createdBy);\n }\n\n if (updatedBy !== null) {\n bodyContato.updated_by = Number(updatedBy);\n }\n\n return bodyContato;\n}\n\nfunction adicionarCampoPersonalizado(campos, campo, valor, opcoes = {}) {\n if (!campo || valor === undefined || valor === null || valor === '') {\n return;\n }\n\n const item = {\n ...campo,\n values: [\n {\n value: valor\n }\n ]\n };\n\n if (opcoes.enum_code) {\n item.values[0].enum_code = opcoes.enum_code;\n }\n\n campos.push(item);\n}\n\nfunction obterDataMinimaContato(empresa, dataInicio, dataTermino) {\n const criadoEmpresa = converterDataOpcional(empresa.criado_em);\n\n if (!criadoEmpresa || criadoEmpresa.getTime() < dataInicio.getTime()) {\n return dataInicio;\n }\n\n if (criadoEmpresa.getTime() > dataTermino.getTime()) {\n return dataInicio;\n }\n\n return criadoEmpresa;\n}\n\nfunction criarNomePessoa(random) {\n const usarNomeComposto = random() < 0.28;\n\n const primeiroNome = usarNomeComposto\n ? escolherAleatorio(random, NOMES_COMPOSTOS)\n : escolherAleatorio(random, PRIMEIROS_NOMES);\n\n const formato = escolherComPeso(random, [\n { valor: 'curto', peso: 28 },\n { valor: 'medio', peso: 47 },\n { valor: 'longo', peso: 25 }\n ]);\n\n const sobrenome1 = escolherAleatorio(random, SOBRENOMES);\n let sobrenome2 = escolherAleatorio(random, SOBRENOMES);\n\n while (sobrenome2 === sobrenome1) {\n sobrenome2 = escolherAleatorio(random, SOBRENOMES);\n }\n\n if (formato === 'curto') {\n return `${primeiroNome} ${sobrenome1}`;\n }\n\n if (formato === 'medio') {\n return `${primeiroNome} ${sobrenome1} ${sobrenome2}`;\n }\n\n const nomeIntermediario = escolherAleatorio(random, NOMES_INTERMEDIARIOS);\n\n return `${primeiroNome} ${nomeIntermediario} ${sobrenome1} ${sobrenome2}`;\n}\n\nfunction criarTelefoneContato(indice) {\n const numero = String(indice).padStart(8, '0').slice(-8);\n return `+55 00 9${numero.slice(0, 4)}-${numero.slice(4)}`;\n}\n\nfunction criarEmailContato(nome, indice, emailsUtilizados) {\n const base = criarSlug(nome);\n let email = `${base}.${pad(indice, 6)}@exemplo.invalid`;\n\n let contador = 2;\n while (emailsUtilizados.has(email)) {\n email = `${base}.${pad(indice, 6)}.${contador}@exemplo.invalid`;\n contador += 1;\n }\n\n emailsUtilizados.add(email);\n return email;\n}\n\nfunction criarGeradorAleatorio(seedInicial) {\n let seed = Number(seedInicial) || 2025;\n\n return function random() {\n seed |= 0;\n seed = (seed + 0x6D2B79F5) | 0;\n\n let resultado = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n\n resultado =\n resultado +\n Math.imul(resultado ^ (resultado >>> 7), 61 | resultado) ^\n resultado;\n\n return ((resultado ^ (resultado >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nfunction escolherAleatorio(random, array) {\n if (!Array.isArray(array) || array.length === 0) {\n throw new Error('Tentativa de selecionar um item em uma lista vazia.');\n }\n\n return array[Math.floor(random() * array.length)];\n}\n\nfunction escolherComPeso(random, opcoes) {\n const total = opcoes.reduce(\n (soma, opcao) => soma + Number(opcao.peso || 0),\n 0\n );\n\n let sorteio = random() * total;\n\n for (const opcao of opcoes) {\n sorteio -= Number(opcao.peso || 0);\n\n if (sorteio <= 0) {\n return opcao.valor;\n }\n }\n\n return opcoes[opcoes.length - 1].valor;\n}\n\nfunction pad(numero, tamanho = 2) {\n return String(numero).padStart(tamanho, '0');\n}\n\nfunction normalizarTexto(valor) {\n return String(valor || '')\n .trim()\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction criarSlug(valor) {\n return normalizarTexto(valor)\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\nfunction valorPreenchido(valor) {\n return valor !== undefined && valor !== null && String(valor).trim() !== '';\n}\n\nfunction valorOuNulo(valor) {\n if (valor === undefined || valor === null || valor === '') {\n return null;\n }\n\n return valor;\n}\n\nfunction converterData(valor, nomeCampo) {\n if (!valor) {\n throw new Error(`O campo \"${nomeCampo}\" n\u00e3o foi informado.`);\n }\n\n const data = converterDataOpcional(valor);\n\n if (!data) {\n throw new Error(`A data \"${valor}\" do campo \"${nomeCampo}\" \u00e9 inv\u00e1lida.`);\n }\n\n return data;\n}\n\nfunction converterDataOpcional(valor) {\n if (!valor) {\n return null;\n }\n\n const texto = String(valor).trim();\n const formatoBR = /^(\\d{2})\\/(\\d{2})\\/(\\d{4})$/;\n const matchBR = texto.match(formatoBR);\n\n if (matchBR) {\n const [, dia, mes, ano] = matchBR;\n const data = new Date(Date.UTC(Number(ano), Number(mes) - 1, Number(dia)));\n\n const dataValida =\n data.getUTCFullYear() === Number(ano) &&\n data.getUTCMonth() === Number(mes) - 1 &&\n data.getUTCDate() === Number(dia);\n\n return dataValida ? data : null;\n }\n\n const data = new Date(texto);\n return Number.isNaN(data.getTime()) ? null : data;\n}\n\nfunction inicioDoDia(data) {\n return new Date(Date.UTC(\n data.getUTCFullYear(),\n data.getUTCMonth(),\n data.getUTCDate(),\n 0, 0, 0, 0\n ));\n}\n\nfunction fimDoDia(data) {\n return new Date(Date.UTC(\n data.getUTCFullYear(),\n data.getUTCMonth(),\n data.getUTCDate(),\n 23, 59, 59, 999\n ));\n}\n\nfunction dataAleatoriaEntre(random, inicio, termino) {\n const minimo = inicio.getTime();\n const maximo = termino.getTime();\n\n if (maximo < minimo) {\n throw new Error('O intervalo de datas \u00e9 inv\u00e1lido.');\n }\n\n const timestamp = Math.floor(random() * (maximo - minimo + 1) + minimo);\n return new Date(timestamp);\n}\n\nfunction unixSegundos(data) {\n if (!data) {\n return null;\n }\n\n const dataConvertida = data instanceof Date ? data : new Date(data);\n\n if (Number.isNaN(dataConvertida.getTime())) {\n return null;\n }\n\n return Math.floor(dataConvertida.getTime() / 1000);\n}\n\nfunction dividirEmLotes(array, tamanhoLote) {\n const lotes = [];\n\n for (let indice = 0; indice < array.length; indice += tamanhoLote) {\n lotes.push(array.slice(indice, indice + tamanhoLote));\n }\n\n return lotes;\n}\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2320,
64
],
"id": "7ccc4995-4a69-4544-a26b-de90c28ce2a3",
"name": "Code in JavaScript2"
},
{
"parameters": {
"method": "POST",
"url": "=https://{{ $('Merge').item.json.kommo_subdomain }}.kommo.com/api/v4/companies",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "accept",
"value": "application/json"
},
{
"name": "authorization",
"value": "=Bearer {{ $('Merge').item.json.token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ $json.body.toJsonString()}}",
"options": {
"batching": {
"batch": {
"batchSize": 1,
"batchInterval": 300
}
},
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1424,
-144
],
"id": "6eb578bb-0fc1-40de-af63-320b4bdc3bfc",
"name": "HTTP Request"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "U2zPJpKifuM8xMJS",
"mode": "list",
"cachedResultUrl": "/workflow/U2zPJpKifuM8xMJS",
"cachedResultName": "01_kommo_credenciais"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"options": {}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
postgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
04_dados_de_teste. Uses postgres, httpRequest. Event-driven trigger; 41 nodes.
Source: https://github.com/Israelgoularte/tcc_A_D_C_IA/blob/d73d04a31b2add0c3accfb6b50968ec9dacd2e15/infra/n8n/workflows/setup/04_dados_de_teste.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Reagendamiento_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 89 nodes.
This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod
Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.
Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.
Tarım Haberleri (AI + HTTP). Uses rssFeedRead, httpRequest, postgres. Event-driven trigger; 26 nodes.