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:

// Converter tipos de dados
const converterTipos = (dados) => {
  return dados.map(item => ({
    ...item,
    // Converter string para número
    valor: parseFloat(item.valor) || 0,
    // Converter string para boolean
    ativo: item.ativo === 'true' || item.ativo === true,
    // Converter string para data
    dataCriacao: new Date(item.dataCriacao),
    // Converter número para string
    id: item.id.toString()
  }));
};

// Formatar valores monetários
const formatarMoeda = (valor, moeda = 'BRL') => {
  return new Intl.NumberFormat('pt-BR', {
    style: 'currency',
    currency: moeda
  }).format(valor);
};

// Exemplo: Formatar preços
const produtosFormatados = produtos.map(produto => ({
  ...produto,
  precoFormatado: formatarMoeda(produto.preco),
  precoComDesconto: formatarMoeda(produto.preco * 0.9)
}));

Limpeza de Dados

Remover inconsistências e padronizar dados:

// Limpar e normalizar texto
const limparTexto = (texto) => {
  if (!texto) return '';
  
  return texto
    .trim()
    .toLowerCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '') // Remove acentos
    .replace(/[^\w\s]/g, '') // Remove caracteres especiais
    .replace(/\s+/g, ' '); // Remove espaços múltiplos
};

// Limpar dados de clientes
const limparDadosCliente = (cliente) => {
  return {
    ...cliente,
    nome: limparTexto(cliente.nome),
    email: cliente.email?.toLowerCase().trim(),
    telefone: cliente.telefone?.replace(/[^\d]/g, ''),
    cpf: cliente.cpf?.replace(/[^\d]/g, ''),
    endereco: {
      ...cliente.endereco,
      cep: cliente.endereco?.cep?.replace(/[^\d]/g, ''),
      cidade: limparTexto(cliente.endereco?.cidade || ''),
      estado: cliente.endereco?.estado?.toUpperCase()
    }
  };
};

// Remover duplicatas
const removerDuplicatas = (dados, campo) => {
  const unicos = new Set();
  return dados.filter(item => {
    const valor = item[campo];
    if (unicos.has(valor)) {
      return false;
    }
    unicos.add(valor);
    return true;
  });
};

// Exemplo: Remover clientes duplicados por email
const clientesUnicos = removerDuplicatas(clientes, 'email');

Enriquecimento de Dados

Adicionar informações complementares aos dados:

// Enriquecer dados de endereço com informações do CEP
const enriquecerEndereco = async (dados) => {
  const dadosEnriquecidos = [];
  
  for (const item of dados) {
    if (item.endereco?.cep) {
      try {
        // Consultar API do ViaCEP
        const response = await fetch(`https://viacep.com.br/ws/${item.endereco.cep}/json/`);
        const cepData = await response.json();
        
        if (!cepData.erro) {
          dadosEnriquecidos.push({
            ...item,
            endereco: {
              ...item.endereco,
              bairro: cepData.bairro || item.endereco.bairro,
              cidade: cepData.localidade || item.endereco.cidade,
              estado: cepData.uf || item.endereco.estado,
              logradouro: cepData.logradouro || item.endereco.logradouro
            }
          });
        } else {
          dadosEnriquecidos.push(item);
        }
      } catch (error) {
        console.error(`Erro ao consultar CEP ${item.endereco.cep}:`, error);
        dadosEnriquecidos.push(item);
      }
    } else {
      dadosEnriquecidos.push(item);
    }
  }
  
  return dadosEnriquecidos;
};

// Enriquecer com dados de geolocalização
const enriquecerGeolocalizacao = async (dados) => {
  const dadosComGeo = [];
  
  for (const item of dados) {
    if (item.endereco?.cidade && item.endereco?.estado) {
      try {
        const endereco = `${item.endereco.logradouro}, ${item.endereco.cidade}, ${item.endereco.estado}`;
        const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(endereco)}&key=YOUR_API_KEY`);
        const geoData = await response.json();
        
        if (geoData.results.length > 0) {
          const location = geoData.results[0].geometry.location;
          dadosComGeo.push({
            ...item,
            geolocalizacao: {
              latitude: location.lat,
              longitude: location.lng
            }
          });
        } else {
          dadosComGeo.push(item);
        }
      } catch (error) {
        console.error('Erro ao obter geolocalização:', error);
        dadosComGeo.push(item);
      }
    } else {
      dadosComGeo.push(item);
    }
  }
  
  return dadosComGeo;
};

Edição de Dados Brasileiros

Formatação de Documentos

// Formatar CPF
const formatarCPF = (cpf) => {
  if (!cpf) return '';
  
  const numeros = cpf.replace(/[^\d]/g, '');
  if (numeros.length !== 11) return cpf;
  
  return numeros.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
};

// Formatar CNPJ
const formatarCNPJ = (cnpj) => {
  if (!cnpj) return '';
  
  const numeros = cnpj.replace(/[^\d]/g, '');
  if (numeros.length !== 14) return cnpj;
  
  return numeros.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, '$1.$2.$3/$4-$5');
};

// Formatar telefone brasileiro
const formatarTelefone = (telefone) => {
  if (!telefone) return '';
  
  const numeros = telefone.replace(/[^\d]/g, '');
  
  if (numeros.length === 11) {
    return numeros.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3');
  } else if (numeros.length === 10) {
    return numeros.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3');
  }
  
  return telefone;
};

// Formatar CEP
const formatarCEP = (cep) => {
  if (!cep) return '';
  
  const numeros = cep.replace(/[^\d]/g, '');
  if (numeros.length !== 8) return cep;
  
  return numeros.replace(/(\d{5})(\d{3})/, '$1-$2');
};

// Aplicar formatação em lote
const formatarDocumentos = (dados) => {
  return dados.map(item => ({
    ...item,
    cpf: formatarCPF(item.cpf),
    cnpj: formatarCNPJ(item.cnpj),
    telefone: formatarTelefone(item.telefone),
    endereco: {
      ...item.endereco,
      cep: formatarCEP(item.endereco?.cep)
    }
  }));
};

Validação e Correção

// Validar e corrigir CPF
const validarECorrigirCPF = (cpf) => {
  if (!cpf) return { valido: false, corrigido: null };
  
  const numeros = cpf.replace(/[^\d]/g, '');
  
  // Verificar se tem 11 dígitos
  if (numeros.length !== 11) {
    return { valido: false, corrigido: null };
  }
  
  // Verificar se não são todos iguais
  if (/^(\d)\1{10}$/.test(numeros)) {
    return { valido: false, corrigido: null };
  }
  
  // Calcular dígitos verificadores
  let soma = 0;
  for (let i = 0; i < 9; i++) {
    soma += parseInt(numeros[i]) * (10 - i);
  }
  const digito1 = ((soma * 10) % 11) % 10;
  
  soma = 0;
  for (let i = 0; i < 10; i++) {
    soma += parseInt(numeros[i]) * (11 - i);
  }
  const digito2 = ((soma * 10) % 11) % 10;
  
  const valido = parseInt(numeros[9]) === digito1 && parseInt(numeros[10]) === digito2;
  
  return {
    valido,
    corrigido: valido ? formatarCPF(numeros) : null
  };
};

// Validar e corrigir email
const validarECorrigirEmail = (email) => {
  if (!email) return { valido: false, corrigido: null };
  
  const emailLimpo = email.toLowerCase().trim();
  const regexEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  
  if (regexEmail.test(emailLimpo)) {
    return { valido: true, corrigido: emailLimpo };
  }
  
  // Tentar corrigir erros comuns
  const correcoes = [
    emailLimpo.replace(/\s+/g, ''), // Remove espaços
    emailLimpo.replace(/\.com\.br$/g, '.com.br'), // Corrige domínio
    emailLimpo.replace(/@gmail\.com$/g, '@gmail.com'), // Corrige Gmail
    emailLimpo.replace(/@hotmail\.com$/g, '@hotmail.com') // Corrige Hotmail
  ];
  
  for (const correcao of correcoes) {
    if (regexEmail.test(correcao)) {
      return { valido: true, corrigido: correcao };
    }
  }
  
  return { valido: false, corrigido: null };
};

// Aplicar validação e correção em lote
const validarECorrigirDados = (dados) => {
  const resultados = {
    validos: [],
    corrigidos: [],
    invalidos: []
  };
  
  dados.forEach(item => {
    const cpfResultado = validarECorrigirCPF(item.cpf);
    const emailResultado = validarECorrigirEmail(item.email);
    
    const itemCorrigido = {
      ...item,
      cpf: cpfResultado.corrigido || item.cpf,
      email: emailResultado.corrigido || item.email
    };
    
    if (cpfResultado.valido && emailResultado.valido) {
      resultados.validos.push(itemCorrigido);
    } else if (cpfResultado.corrigido || emailResultado.corrigido) {
      resultados.corrigidos.push(itemCorrigido);
    } else {
      resultados.invalidos.push(item);
    }
  });
  
  return resultados;
};

Transformações Avançadas

Transformações de Arrays

// Agrupar dados por campo
const agruparPorCampo = (dados, campo) => {
  return dados.reduce((grupos, item) => {
    const valor = item[campo];
    if (!grupos[valor]) {
      grupos[valor] = [];
    }
    grupos[valor].push(item);
    return grupos;
  }, {});
};

// Exemplo: Agrupar clientes por estado
const clientesPorEstado = agruparPorCampo(clientes, 'estado');

// Transformar array em objeto
const arrayParaObjeto = (dados, chave, valor) => {
  return dados.reduce((obj, item) => {
    obj[item[chave]] = item[valor];
    return obj;
  }, {});
};

// Exemplo: Criar mapa de produtos por ID
const produtosMap = arrayParaObjeto(produtos, 'id', 'nome');

// Achatamento de arrays aninhados
const achatarArray = (dados, campo) => {
  return dados.reduce((resultado, item) => {
    const array = item[campo];
    if (Array.isArray(array)) {
      resultado.push(...array);
    }
    return resultado;
  }, []);
};

// Exemplo: Achatamento de pedidos
const todosOsItens = achatarArray(pedidos, 'itens');

Transformações Condicionais

// Aplicar transformações baseadas em condições
const transformarCondicional = (dados, condicoes) => {
  return dados.map(item => {
    let itemTransformado = { ...item };
    
    condicoes.forEach(condicao => {
      const { campo, valor, operador, transformacao } = condicao;
      
      let aplicar = false;
      switch (operador) {
        case '==':
          aplicar = item[campo] === valor;
          break;
        case '>':
          aplicar = parseFloat(item[campo]) > valor;
          break;
        case '<':
          aplicar = parseFloat(item[campo]) < valor;
          break;
        case 'contains':
          aplicar = item[campo].toLowerCase().includes(valor.toLowerCase());
          break;
      }
      
      if (aplicar) {
        itemTransformado = { ...itemTransformado, ...transformacao(item) };
      }
    });
    
    return itemTransformado;
  });
};

// Exemplo: Aplicar descontos baseados em valor
const produtosComDesconto = transformarCondicional(produtos, [
  {
    campo: 'preco',
    valor: 100,
    operador: '>',
    transformacao: (item) => ({
      precoComDesconto: item.preco * 0.9,
      desconto: '10%'
    })
  },
  {
    campo: 'preco',
    valor: 50,
    operador: '>',
    transformacao: (item) => ({
      precoComDesconto: item.preco * 0.95,
      desconto: '5%'
    })
  }
]);

Transformações de Datas

// Normalizar formatos de data
const normalizarData = (data) => {
  if (!data) return null;
  
  // Tentar diferentes formatos
  const formatos = [
    'YYYY-MM-DD',
    'DD/MM/YYYY',
    'MM/DD/YYYY',
    'DD-MM-YYYY',
    'MM-DD-YYYY'
  ];
  
  for (const formato of formatos) {
    try {
      const dataObj = new Date(data);
      if (!isNaN(dataObj.getTime())) {
        return dataObj.toISOString().split('T')[0];
      }
    } catch (error) {
      continue;
    }
  }
  
  return null;
};

// Calcular idade a partir da data de nascimento
const calcularIdade = (dataNascimento) => {
  if (!dataNascimento) return null;
  
  const hoje = new Date();
  const nascimento = new Date(dataNascimento);
  let idade = hoje.getFullYear() - nascimento.getFullYear();
  
  const mesAtual = hoje.getMonth();
  const mesNascimento = nascimento.getMonth();
  
  if (mesAtual < mesNascimento || 
      (mesAtual === mesNascimento && hoje.getDate() < nascimento.getDate())) {
    idade--;
  }
  
  return idade;
};

// Formatar data para português brasileiro
const formatarDataBR = (data) => {
  if (!data) return '';
  
  const dataObj = new Date(data);
  return dataObj.toLocaleDateString('pt-BR', {
    day: '2-digit',
    month: '2-digit',
    year: 'numeric'
  });
};

// Aplicar transformações de data
const transformarDatas = (dados) => {
  return dados.map(item => ({
    ...item,
    dataNascimento: normalizarData(item.dataNascimento),
    idade: calcularIdade(item.dataNascimento),
    dataFormatada: formatarDataBR(item.dataCriacao)
  }));
};

Edição em Tempo Real

Stream de Transformações

// Processar dados em stream para grandes volumes
const processarStream = async (dados, transformacoes, tamanhoLote = 1000) => {
  const resultados = [];
  
  for (let i = 0; i < dados.length; i += tamanhoLote) {
    const lote = dados.slice(i, i + tamanhoLote);
    
    // Aplicar transformações no lote
    let loteProcessado = lote;
    for (const transformacao of transformacoes) {
      loteProcessado = await transformacao(loteProcessado);
    }
    
    resultados.push(...loteProcessado);
    
    // Log de progresso
    console.log(`Processados ${Math.min(i + tamanhoLote, dados.length)} de ${dados.length} registros`);
  }
  
  return resultados;
};

// Exemplo de uso
const transformacoes = [
  limparDadosCliente,
  formatarDocumentos,
  validarECorrigirDados
];

const dadosProcessados = await processarStream(clientes, transformacoes);

Transformações Assíncronas

// Transformações que requerem chamadas de API
const transformacoesAssincronas = async (dados) => {
  const resultados = [];
  
  // Processar em paralelo com limite de concorrência
  const limiteConcorrencia = 5;
  const chunks = [];
  
  for (let i = 0; i < dados.length; i += limiteConcorrencia) {
    chunks.push(dados.slice(i, i + limiteConcorrencia));
  }
  
  for (const chunk of chunks) {
    const promises = chunk.map(async (item) => {
      try {
        // Enriquecer com dados externos
        const dadosEnriquecidos = await enriquecerEndereco([item]);
        const dadosComGeo = await enriquecerGeolocalizacao(dadosEnriquecidos);
        
        return dadosComGeo[0];
      } catch (error) {
        console.error('Erro ao processar item:', error);
        return item;
      }
    });
    
    const chunkResultados = await Promise.all(promises);
    resultados.push(...chunkResultados);
  }
  
  return resultados;
};

Validação de Transformações

Verificação de Integridade

// Verificar integridade após transformações
const verificarIntegridade = (dadosOriginais, dadosTransformados) => {
  const verificacoes = {
    totalRegistros: dadosOriginais.length === dadosTransformados.length,
    camposObrigatorios: true,
    tiposCorretos: true,
    valoresValidos: true,
    erros: []
  };
  
  // Verificar campos obrigatórios
  const camposObrigatorios = ['id', 'nome', 'email'];
  
  dadosTransformados.forEach((item, index) => {
    camposObrigatorios.forEach(campo => {
      if (!item[campo]) {
        verificacoes.camposObrigatorios = false;
        verificacoes.erros.push(`Campo obrigatório '${campo}' ausente no item ${index}`);
      }
    });
    
    // Verificar tipos
    if (typeof item.valor !== 'number') {
      verificacoes.tiposCorretos = false;
      verificacoes.erros.push(`Tipo incorreto para 'valor' no item ${index}`);
    }
    
    // Verificar valores válidos
    if (item.valor < 0) {
      verificacoes.valoresValidos = false;
      verificacoes.erros.push(`Valor negativo no item ${index}`);
    }
  });
  
  return verificacoes;
};

// Aplicar transformações com validação
const transformarComValidacao = async (dados, transformacoes) => {
  const dadosOriginais = [...dados];
  let dadosAtuais = dados;
  
  for (const transformacao of transformacoes) {
    dadosAtuais = await transformacao(dadosAtuais);
    
    // Validar após cada transformação
    const integridade = verificarIntegridade(dadosOriginais, dadosAtuais);
    
    if (!integridade.totalRegistros) {
      throw new Error('Perda de registros durante transformação');
    }
    
    if (integridade.erros.length > 0) {
      console.warn('Avisos de integridade:', integridade.erros);
    }
  }
  
  return dadosAtuais;
};

Workflows de Edição

Workflow: Pipeline de Limpeza de Dados

Workflow: Transformação em Tempo Real

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