Pular para o conteúdo principal

Edição de Dados

A edição de dados é fundamental para transformar, limpar e preparar informações para uso em workflows. Esta seção aborda técnicas avançadas de edição, transformação e manipulação de dados no n8n.

Visão Geral

A edição de dados envolve modificar, limpar e transformar informações para atender às necessidades específicas dos workflows. No n8n, você pode editar dados usando:

  • Transformações básicas de tipos e formatos
  • Limpeza e normalização de dados
  • Enriquecimento com informações adicionais
  • Validação e correção automática
  • Transformações complexas com JavaScript

Tipos de Edição

Transformações Básicas

Operações fundamentais de transformação de dados:

javascript\n// Converter tipos de dados\nconst converterTipos = (dados) => {\n  return dados.map(item => ({\n    ...item,\n    // Converter string para número\n    valor: parseFloat(item.valor) || 0,\n    // Converter string para boolean\n    ativo: item.ativo === 'true' || item.ativo === true,\n    // Converter string para data\n    dataCriacao: new Date(item.dataCriacao),\n    // Converter número para string\n    id: item.id.toString()\n  }));\n};\n\n// Formatar valores monetários\nconst formatarMoeda = (valor, moeda = 'BRL') => {\n  return new Intl.NumberFormat('pt-BR', {\n    style: 'currency',\n    currency: moeda\n  }).format(valor);\n};\n\n// Exemplo: Formatar preços\nconst produtosFormatados = produtos.map(produto => ({\n  ...produto,\n  precoFormatado: formatarMoeda(produto.preco),\n  precoComDesconto: formatarMoeda(produto.preco * 0.9)\n}));\n
\n\n### Limpeza de Dados\n\nRemover inconsistências e padronizar dados:\n\n
javascript\n// Limpar e normalizar texto\nconst limparTexto = (texto) => {\n  if (!texto) return '';\n  \n  return texto\n    .trim()\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[\u0300-\u036f]/g, '') // Remove acentos\n    .replace(/[^\w\s]/g, '') // Remove caracteres especiais\n    .replace(/\s+/g, ' '); // Remove espaços múltiplos\n};\n\n// Limpar dados de clientes\nconst limparDadosCliente = (cliente) => {\n  return {\n    ...cliente,\n    nome: limparTexto(cliente.nome),\n    email: cliente.email?.toLowerCase().trim(),\n    telefone: cliente.telefone?.replace(/[^\d]/g, ''),\n    cpf: cliente.cpf?.replace(/[^\d]/g, ''),\n    endereco: {\n      ...cliente.endereco,\n      cep: cliente.endereco?.cep?.replace(/[^\d]/g, ''),\n      cidade: limparTexto(cliente.endereco?.cidade || ''),\n      estado: cliente.endereco?.estado?.toUpperCase()\n    }\n  };\n};\n\n// Remover duplicatas\nconst removerDuplicatas = (dados, campo) => {\n  const unicos = new Set();\n  return dados.filter(item => {\n    const valor = item[campo];\n    if (unicos.has(valor)) {\n      return false;\n    }\n    unicos.add(valor);\n    return true;\n  });\n};\n\n// Exemplo: Remover clientes duplicados por email\nconst clientesUnicos = removerDuplicatas(clientes, 'email');\n
\n\n### Enriquecimento de Dados\n\nAdicionar informações complementares aos dados:\n\n
javascript\n// Enriquecer dados de endereço com informações do CEP\nconst enriquecerEndereco = async (dados) => {\n  const dadosEnriquecidos = [];\n  \n  for (const item of dados) {\n    if (item.endereco?.cep) {\n      try {\n        // Consultar API do ViaCEP\n        const response = await fetch(`https://viacep.com.br/ws/${item.endereco.cep}/json/`);\n        const cepData = await response.json();\n        \n        if (!cepData.erro) {\n          dadosEnriquecidos.push({\n            ...item,\n            endereco: {\n              ...item.endereco,\n              bairro: cepData.bairro || item.endereco.bairro,\n              cidade: cepData.localidade || item.endereco.cidade,\n              estado: cepData.uf || item.endereco.estado,\n              logradouro: cepData.logradouro || item.endereco.logradouro\n            }\n          });\n        } else {\n          dadosEnriquecidos.push(item);\n        }\n      } catch (error) {\n        console.error(`Erro ao consultar CEP ${item.endereco.cep}:`, error);\n        dadosEnriquecidos.push(item);\n      }\n    } else {\n      dadosEnriquecidos.push(item);\n    }\n  }\n  \n  return dadosEnriquecidos;\n};\n\n// Enriquecer com dados de geolocalização\nconst enriquecerGeolocalizacao = async (dados) => {\n  const dadosComGeo = [];\n  \n  for (const item of dados) {\n    if (item.endereco?.cidade && item.endereco?.estado) {\n      try {\n        const endereco = `${item.endereco.logradouro}, ${item.endereco.cidade}, ${item.endereco.estado}`;\n        const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(endereco)}&key=YOUR_API_KEY`);\n        const geoData = await response.json();\n        \n        if (geoData.results.length > 0) {\n          const location = geoData.results[0].geometry.location;\n          dadosComGeo.push({\n            ...item,\n            geolocalizacao: {\n              latitude: location.lat,\n              longitude: location.lng\n            }\n          });\n        } else {\n          dadosComGeo.push(item);\n        }\n      } catch (error) {\n        console.error('Erro ao obter geolocalização:', error);\n        dadosComGeo.push(item);\n      }\n    } else {\n      dadosComGeo.push(item);\n    }\n  }\n  \n  return dadosComGeo;\n};\n
\n\n## Edição de Dados Brasileiros\n\n### Formatação de Documentos\n\n
javascript\n// Formatar CPF\nconst formatarCPF = (cpf) => {\n  if (!cpf) return '';\n  \n  const numeros = cpf.replace(/[^\d]/g, '');\n  if (numeros.length !== 11) return cpf;\n  \n  return numeros.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');\n};\n\n// Formatar CNPJ\nconst formatarCNPJ = (cnpj) => {\n  if (!cnpj) return '';\n  \n  const numeros = cnpj.replace(/[^\d]/g, '');\n  if (numeros.length !== 14) return cnpj;\n  \n  return numeros.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, '$1.$2.$3/$4-$5');\n};\n\n// Formatar telefone brasileiro\nconst formatarTelefone = (telefone) => {\n  if (!telefone) return '';\n  \n  const numeros = telefone.replace(/[^\d]/g, '');\n  \n  if (numeros.length === 11) {\n    return numeros.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3');\n  } else if (numeros.length === 10) {\n    return numeros.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3');\n  }\n  \n  return telefone;\n};\n\n// Formatar CEP\nconst formatarCEP = (cep) => {\n  if (!cep) return '';\n  \n  const numeros = cep.replace(/[^\d]/g, '');\n  if (numeros.length !== 8) return cep;\n  \n  return numeros.replace(/(\d{5})(\d{3})/, '$1-$2');\n};\n\n// Aplicar formatação em lote\nconst formatarDocumentos = (dados) => {\n  return dados.map(item => ({\n    ...item,\n    cpf: formatarCPF(item.cpf),\n    cnpj: formatarCNPJ(item.cnpj),\n    telefone: formatarTelefone(item.telefone),\n    endereco: {\n      ...item.endereco,\n      cep: formatarCEP(item.endereco?.cep)\n    }\n  }));\n};\n
\n\n### Validação e Correção\n\n
javascript\n// Validar e corrigir CPF\nconst validarECorrigirCPF = (cpf) => {\n  if (!cpf) return { valido: false, corrigido: null };\n  \n  const numeros = cpf.replace(/[^\d]/g, '');\n  \n  // Verificar se tem 11 dígitos\n  if (numeros.length !== 11) {\n    return { valido: false, corrigido: null };\n  }\n  \n  // Verificar se não são todos iguais\n  if (/^(\d)\1{10}$/.test(numeros)) {\n    return { valido: false, corrigido: null };\n  }\n  \n  // Calcular dígitos verificadores\n  let soma = 0;\n  for (let i = 0; i < 9; i++) {\n    soma += parseInt(numeros[i]) * (10 - i);\n  }\n  const digito1 = ((soma * 10) % 11) % 10;\n  \n  soma = 0;\n  for (let i = 0; i < 10; i++) {\n    soma += parseInt(numeros[i]) * (11 - i);\n  }\n  const digito2 = ((soma * 10) % 11) % 10;\n  \n  const valido = parseInt(numeros[9]) === digito1 && parseInt(numeros[10]) === digito2;\n  \n  return {\n    valido,\n    corrigido: valido ? formatarCPF(numeros) : null\n  };\n};\n\n// Validar e corrigir email\nconst validarECorrigirEmail = (email) => {\n  if (!email) return { valido: false, corrigido: null };\n  \n  const emailLimpo = email.toLowerCase().trim();\n  const regexEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;\n  \n  if (regexEmail.test(emailLimpo)) {\n    return { valido: true, corrigido: emailLimpo };\n  }\n  \n  // Tentar corrigir erros comuns\n  const correcoes = [\n    emailLimpo.replace(/\s+/g, ''), // Remove espaços\n    emailLimpo.replace(/\.com\.br$/g, '.com.br'), // Corrige domínio\n    emailLimpo.replace(/@gmail\.com$/g, '@gmail.com'), // Corrige Gmail\n    emailLimpo.replace(/@hotmail\.com$/g, '@hotmail.com') // Corrige Hotmail\n  ];\n  \n  for (const correcao of correcoes) {\n    if (regexEmail.test(correcao)) {\n      return { valido: true, corrigido: correcao };\n    }\n  }\n  \n  return { valido: false, corrigido: null };\n};\n\n// Aplicar validação e correção em lote\nconst validarECorrigirDados = (dados) => {\n  const resultados = {\n    validos: [],\n    corrigidos: [],\n    invalidos: []\n  };\n  \n  dados.forEach(item => {\n    const cpfResultado = validarECorrigirCPF(item.cpf);\n    const emailResultado = validarECorrigirEmail(item.email);\n    \n    const itemCorrigido = {\n      ...item,\n      cpf: cpfResultado.corrigido || item.cpf,\n      email: emailResultado.corrigido || item.email\n    };\n    \n    if (cpfResultado.valido && emailResultado.valido) {\n      resultados.validos.push(itemCorrigido);\n    } else if (cpfResultado.corrigido || emailResultado.corrigido) {\n      resultados.corrigidos.push(itemCorrigido);\n    } else {\n      resultados.invalidos.push(item);\n    }\n  });\n  \n  return resultados;\n};\n
\n\n## Transformações Avançadas\n\n### Transformações de Arrays\n\n
javascript\n// Agrupar dados por campo\nconst agruparPorCampo = (dados, campo) => {\n  return dados.reduce((grupos, item) => {\n    const valor = item[campo];\n    if (!grupos[valor]) {\n      grupos[valor] = [];\n    }\n    grupos[valor].push(item);\n    return grupos;\n  }, {});\n};\n\n// Exemplo: Agrupar clientes por estado\nconst clientesPorEstado = agruparPorCampo(clientes, 'estado');\n\n// Transformar array em objeto\nconst arrayParaObjeto = (dados, chave, valor) => {\n  return dados.reduce((obj, item) => {\n    obj[item[chave]] = item[valor];\n    return obj;\n  }, {});\n};\n\n// Exemplo: Criar mapa de produtos por ID\nconst produtosMap = arrayParaObjeto(produtos, 'id', 'nome');\n\n// Achatamento de arrays aninhados\nconst achatarArray = (dados, campo) => {\n  return dados.reduce((resultado, item) => {\n    const array = item[campo];\n    if (Array.isArray(array)) {\n      resultado.push(...array);\n    }\n    return resultado;\n  }, []);\n};\n\n// Exemplo: Achatamento de pedidos\nconst todosOsItens = achatarArray(pedidos, 'itens');\n
\n\n### Transformações Condicionais\n\n
javascript\n// Aplicar transformações baseadas em condições\nconst transformarCondicional = (dados, condicoes) => {\n  return dados.map(item => {\n    let itemTransformado = { ...item };\n    \n    condicoes.forEach(condicao => {\n      const { campo, valor, operador, transformacao } = condicao;\n      \n      let aplicar = false;\n      switch (operador) {\n        case '==':\n          aplicar = item[campo] === valor;\n          break;\n        case '>':\n          aplicar = parseFloat(item[campo]) > valor;\n          break;\n        case '<':\n          aplicar = parseFloat(item[campo]) < valor;\n          break;\n        case 'contains':\n          aplicar = item[campo].toLowerCase().includes(valor.toLowerCase());\n          break;\n      }\n      \n      if (aplicar) {\n        itemTransformado = { ...itemTransformado, ...transformacao(item) };\n      }\n    });\n    \n    return itemTransformado;\n  });\n};\n\n// Exemplo: Aplicar descontos baseados em valor\nconst produtosComDesconto = transformarCondicional(produtos, [\n  {\n    campo: 'preco',\n    valor: 100,\n    operador: '>',\n    transformacao: (item) => ({\n      precoComDesconto: item.preco * 0.9,\n      desconto: '10%'\n    })\n  },\n  {\n    campo: 'preco',\n    valor: 50,\n    operador: '>',\n    transformacao: (item) => ({\n      precoComDesconto: item.preco * 0.95,\n      desconto: '5%'\n    })\n  }\n]);\n
\n\n### Transformações de Datas\n\n
javascript\n// Normalizar formatos de data\nconst normalizarData = (data) => {\n  if (!data) return null;\n  \n  // Tentar diferentes formatos\n  const formatos = [\n    'YYYY-MM-DD',\n    'DD/MM/YYYY',\n    'MM/DD/YYYY',\n    'DD-MM-YYYY',\n    'MM-DD-YYYY'\n  ];\n  \n  for (const formato of formatos) {\n    try {\n      const dataObj = new Date(data);\n      if (!isNaN(dataObj.getTime())) {\n        return dataObj.toISOString().split('T')[0];\n      }\n    } catch (error) {\n      continue;\n    }\n  }\n  \n  return null;\n};\n\n// Calcular idade a partir da data de nascimento\nconst calcularIdade = (dataNascimento) => {\n  if (!dataNascimento) return null;\n  \n  const hoje = new Date();\n  const nascimento = new Date(dataNascimento);\n  let idade = hoje.getFullYear() - nascimento.getFullYear();\n  \n  const mesAtual = hoje.getMonth();\n  const mesNascimento = nascimento.getMonth();\n  \n  if (mesAtual < mesNascimento || \n      (mesAtual === mesNascimento && hoje.getDate() < nascimento.getDate())) {\n    idade--;\n  }\n  \n  return idade;\n};\n\n// Formatar data para português brasileiro\nconst formatarDataBR = (data) => {\n  if (!data) return '';\n  \n  const dataObj = new Date(data);\n  return dataObj.toLocaleDateString('pt-BR', {\n    day: '2-digit',\n    month: '2-digit',\n    year: 'numeric'\n  });\n};\n\n// Aplicar transformações de data\nconst transformarDatas = (dados) => {\n  return dados.map(item => ({\n    ...item,\n    dataNascimento: normalizarData(item.dataNascimento),\n    idade: calcularIdade(item.dataNascimento),\n    dataFormatada: formatarDataBR(item.dataCriacao)\n  }));\n};\n
\n\n## Edição em Tempo Real\n\n### Stream de Transformações\n\n
javascript\n// Processar dados em stream para grandes volumes\nconst processarStream = async (dados, transformacoes, tamanhoLote = 1000) => {\n  const resultados = [];\n  \n  for (let i = 0; i < dados.length; i += tamanhoLote) {\n    const lote = dados.slice(i, i + tamanhoLote);\n    \n    // Aplicar transformações no lote\n    let loteProcessado = lote;\n    for (const transformacao of transformacoes) {\n      loteProcessado = await transformacao(loteProcessado);\n    }\n    \n    resultados.push(...loteProcessado);\n    \n    // Log de progresso\n    console.log(`Processados ${Math.min(i + tamanhoLote, dados.length)} de ${dados.length} registros`);\n  }\n  \n  return resultados;\n};\n\n// Exemplo de uso\nconst transformacoes = [\n  limparDadosCliente,\n  formatarDocumentos,\n  validarECorrigirDados\n];\n\nconst dadosProcessados = await processarStream(clientes, transformacoes);\n
\n\n### Transformações Assíncronas\n\n
javascript\n// Transformações que requerem chamadas de API\nconst transformacoesAssincronas = async (dados) => {\n  const resultados = [];\n  \n  // Processar em paralelo com limite de concorrência\n  const limiteConcorrencia = 5;\n  const chunks = [];\n  \n  for (let i = 0; i < dados.length; i += limiteConcorrencia) {\n    chunks.push(dados.slice(i, i + limiteConcorrencia));\n  }\n  \n  for (const chunk of chunks) {\n    const promises = chunk.map(async (item) => {\n      try {\n        // Enriquecer com dados externos\n        const dadosEnriquecidos = await enriquecerEndereco([item]);\n        const dadosComGeo = await enriquecerGeolocalizacao(dadosEnriquecidos);\n        \n        return dadosComGeo[0];\n      } catch (error) {\n        console.error('Erro ao processar item:', error);\n        return item;\n      }\n    });\n    \n    const chunkResultados = await Promise.all(promises);\n    resultados.push(...chunkResultados);\n  }\n  \n  return resultados;\n};\n
\n\n## Validação de Transformações\n\n### Verificação de Integridade\n\n
javascript\n// Verificar integridade após transformações\nconst verificarIntegridade = (dadosOriginais, dadosTransformados) => {\n  const verificacoes = {\n    totalRegistros: dadosOriginais.length === dadosTransformados.length,\n    camposObrigatorios: true,\n    tiposCorretos: true,\n    valoresValidos: true,\n    erros: []\n  };\n  \n  // Verificar campos obrigatórios\n  const camposObrigatorios = ['id', 'nome', 'email'];\n  \n  dadosTransformados.forEach((item, index) => {\n    camposObrigatorios.forEach(campo => {\n      if (!item[campo]) {\n        verificacoes.camposObrigatorios = false;\n        verificacoes.erros.push(`Campo obrigatório '${campo}' ausente no item ${index}`);\n      }\n    });\n    \n    // Verificar tipos\n    if (typeof item.valor !== 'number') {\n      verificacoes.tiposCorretos = false;\n      verificacoes.erros.push(`Tipo incorreto para 'valor' no item ${index}`);\n    }\n    \n    // Verificar valores válidos\n    if (item.valor < 0) {\n      verificacoes.valoresValidos = false;\n      verificacoes.erros.push(`Valor negativo no item ${index}`);\n    }\n  });\n  \n  return verificacoes;\n};\n\n// Aplicar transformações com validação\nconst transformarComValidacao = async (dados, transformacoes) => {\n  const dadosOriginais = [...dados];\n  let dadosAtuais = dados;\n  \n  for (const transformacao of transformacoes) {\n    dadosAtuais = await transformacao(dadosAtuais);\n    \n    // Validar após cada transformação\n    const integridade = verificarIntegridade(dadosOriginais, dadosAtuais);\n    \n    if (!integridade.totalRegistros) {\n      throw new Error('Perda de registros durante transformação');\n    }\n    \n    if (integridade.erros.length > 0) {\n      console.warn('Avisos de integridade:', integridade.erros);\n    }\n  }\n  \n  return dadosAtuais;\n};\n
\n\n## Workflows de Edição\n\n### Workflow: Pipeline de Limpeza de Dados\n\n
mermaid\ngraph TD\n    A[HTTP Request: Dados Brutos] --> B[Code: Limpeza Básica]\n    B --> C[Code: Validação]\n    C --> D[Code: Formatação]\n    D --> E[Code: Enriquecimento]\n    E --> F[HTTP Request: Salvar Dados Limpos]\n    F --> G[Send Notification]\n
\n\n### Workflow: Transformação em Tempo Real\n\n
mermaid\ngraph TD\n    A[Webhook Trigger] --> B[Code: Validação Rápida]\n    B --> C[Code: Transformação]\n    C --> D[HTTP Request: API Externa]\n    D --> E[Code: Enriquecimento]\n    E --> F[HTTP Request: Salvar]\n    F --> G[Send Response]\n

Boas Práticas

Performance

  • Processe em lotes para grandes volumes
  • Use cache para transformações repetitivas
  • Paralelize operações independentes
  • Monitore uso de memória durante transformações
  • Otimize loops e operações custosas

Qualidade

  • Valide dados antes e depois das transformações
  • Mantenha logs de todas as alterações
  • Teste transformações com dados reais
  • Documente regras de transformação
  • Implemente rollback para transformações críticas

Manutenibilidade

  • Modularize transformações em funções reutilizáveis
  • Use configurações para parâmetros de transformação
  • Implemente versionamento de transformações
  • Crie testes unitários para transformações
  • Documente dependências entre transformações

Recursos Adicionais

Bibliotecas Úteis

  • Lodash: Utilitários para manipulação de dados
  • Ramda: Programação funcional para transformações
  • date-fns: Manipulação avançada de datas
  • validator.js: Validação robusta de dados

Padrões de Transformação

  • Builder Pattern: Construir transformações complexas
  • Pipeline Pattern: Encadear transformações
  • Strategy Pattern: Diferentes algoritmos de transformação
  • Observer Pattern: Monitorar mudanças em dados

Próximo: Agregações Estatísticas - Calcule métricas e estatísticas