Blog

Integração ERP + CRM sem retrabalho operacional

O vendedor cadastra o cliente no CRM. O financeiro cadastra o mesmo cliente no ERP. Duas fichas nascem do nada, com CNPJ digitado de jeitos diferentes e endereço divergente. A partir daí todo relatório mente e alguém gasta a tarde reconciliando planilha. Este guia mostra como ligar ERP e CRM com padrões de sincronização, fonte da verdade por entidade e idempotência, para que o dado entre uma vez e nunca mais precise de cadastro manual.

2026-06-16 / Integrações / 12 min

01

O problema: dois cadastros do mesmo cliente

Sem integração, cada área opera sua própria base. O CRM tem o cliente do ponto de vista comercial; o ERP tem o mesmo cliente do ponto de vista fiscal e financeiro. Como ninguém combinou quem cria o registro primeiro, o mesmo cliente aparece duas vezes, com pequenas diferenças: nome com ou sem acento, telefone com ou sem DDD, CNPJ com ou sem pontuação. O resultado é retrabalho crônico.

  • Cadastro em dobro: o mesmo cliente vira dois IDs, um no CRM e outro no ERP, sem ligação entre eles.
  • Dado divergente: endereço de cobrança atualizado no ERP nunca chega ao CRM, e o vendedor liga para o lugar errado.
  • Digitação manual repetida: alguém reescreve no ERP o que já existia no CRM, abrindo espaço para erro de digitação.
  • Relatório que não fecha: faturamento por cliente no ERP não bate com pipeline no CRM porque são chaves diferentes.

A solução não é escolher um sistema e abandonar o outro. É definir regras claras de quem manda em cada campo e fazer os dois conversarem sem duplicar.

02

Fonte da verdade por entidade e por campo

O erro mais comum é tentar sincronizar tudo nos dois sentidos. O caminho saudável é decidir, campo a campo, qual sistema é o dono. O dono escreve; o outro apenas lê e reflete. Isso elimina a briga de quem sobrescreve quem.

EntidadeCampoDono (fonte da verdade)Sistema que reflete
ClienteDados de contato e oportunidadeCRMERP lê para emitir nota
ClienteCNPJ, regime fiscal, limite de créditoERPCRM lê para qualificar
ProdutoNome comercial e descrição de vendaCRMERP reflete no catálogo
ProdutoPreço, estoque e código fiscalERPCRM lê para cotar
PedidoNegociação e propostaCRMERP recebe ao fechar
PedidoFaturamento, status fiscal e pagamentoERPCRM lê para acompanhar

Com a tabela acima documentada, qualquer divergência tem resposta objetiva: o valor correto é sempre o do sistema dono daquele campo. Reconciliação deixa de ser debate e vira regra.

03

Estratégias de sincronização

Definido o dono de cada campo, escolha como o dado viaja. Três eixos importam: sentido (one-way ou two-way), gatilho (batch ou orientado a evento) e latência (periódico ou near-real-time).

One-way (recomendado por campo):
  CRM ==(contato, proposta)==> ERP
  ERP ==(preço, estoque, fiscal)==> CRM

Event-driven near-real-time:
  CRM --webhook--> [Middleware] --upsert--> ERP
  ERP --webhook--> [Middleware] --upsert--> CRM
                       |
                 mapping table

Batch (fallback noturno):
  [Job 02:00] --lê delta--> compara --> aplica diferenças
  • One-way por campo: cada campo flui em um único sentido a partir do seu dono. Simples, previsível e evita loop de sobrescrita.
  • Two-way: só quando os dois sistemas precisam editar a mesma entidade. Exige regra de conflito explícita, nunca improvise.
  • Batch: job periódico que lê um delta e aplica diferenças. Barato e tolerante a falha, porém com atraso de minutos a horas.
  • Event-driven via webhook: o sistema dono emite um evento na mudança e o middleware propaga em segundos, near-real-time.
  • Padrão prático: event-driven para o fluxo principal e um batch noturno de reconciliação como rede de segurança.

04

Chave de correlação e idempotência

Para nunca duplicar, cada entidade precisa de uma chave que ligue o registro do CRM ao do ERP. Guarde esse vínculo numa mapping table: external_id de um lado, external_id do outro. Toda escrita vira um upsert por essa chave, não um insert cego. Assim, reprocessar o mesmo evento dez vezes produz o mesmo resultado: idempotência.

  • Mapping table: tabela que relaciona crm_id, erp_id e a chave natural (CNPJ, SKU) para resolver o par sem ambiguidade.
  • Chave natural: quando não há mapping ainda, casa pelo CNPJ do cliente ou SKU do produto, normalizados antes de comparar.
  • Upsert por chave: insere se não existe, atualiza se existe. Nunca um insert direto que cria duplicata em retry.
  • Idempotência: a mesma mensagem aplicada N vezes deixa o sistema no mesmo estado, essencial porque webhook reenvia.
// sync-customer.js
// Upsert idempotente de cliente entre CRM e ERP usando mapping table.
// Roda no middleware ao receber um evento "customer.updated" do CRM.

async function syncCustomerFromCrm(event) {
  const crmId = event.data.id;
  const cnpj = normalizeCnpj(event.data.cnpj); // remove pontuacao, valida

  // 1. Resolve o par via mapping table; cai para chave natural se nao houver vinculo
  let mapping = await db.mapping.findOne({ crm_id: crmId });
  if (!mapping) {
    const erp = await erpApi.findCustomerByCnpj(cnpj);
    if (erp) {
      // Cliente ja existe no ERP, so faltava o vinculo: nao duplica
      mapping = await db.mapping.upsert({ crm_id: crmId, erp_id: erp.id, cnpj });
    }
  }

  // 2. Monta apenas os campos que o CRM e dono (contato e proposta)
  const payload = {
    nome: event.data.nome,
    email: event.data.email,
    telefone: event.data.telefone,
  };

  // 3. Upsert idempotente: chave de negocio = erp_id (se existe) ou CNPJ
  const erpCustomer = await erpApi.upsertCustomer({
    matchBy: mapping?.erp_id ? { id: mapping.erp_id } : { cnpj },
    data: payload,
    // dedupe_key garante que reprocessar o mesmo evento nao gere efeito duplo
    dedupeKey: `crm:${crmId}:${event.version}`,
  });

  // 4. Persiste/atualiza o vinculo para a proxima sincronizacao
  await db.mapping.upsert({ crm_id: crmId, erp_id: erpCustomer.id, cnpj });
  return erpCustomer.id;
}

Repare que o código nunca faz insert direto: ele resolve o par antes, casa por CNPJ quando o vínculo ainda não existe e usa um dedupeKey baseado na versão do evento. Esse trio (mapping, chave natural e dedupe) é o que mata a duplicata na origem.

05

Reconciliação e detecção de divergência

Mesmo com sincronização em tempo real, eventos se perdem: webhook que falhou, deploy no meio de um lote, registro editado direto no banco. Por isso um job de reconciliação periódico é obrigatório. Ele compara os dois lados, alerta o que divergiu e, quando seguro, corrige sozinho.

  1. Selecione o conjunto a comparar: todos os clientes ativos com atividade nas últimas 24 horas, por exemplo.
  2. Para cada registro, resolva o par pela mapping table e busque o estado atual nos dois sistemas.
  3. Compare apenas os campos que têm dono definido, normalizando antes (CNPJ sem pontuação, texto em caixa única).
  4. Classifique a divergência: ausente de um lado, valor diferente ou par quebrado (mapping sem correspondente).
  5. Para campo com dono claro, reaplique o valor do dono via upsert idempotente e registre a correção.
  6. Para casos ambíguos, gere um alerta e envie para a fila de revisão humana em vez de adivinhar.

Registre cada reconciliação com contagem de divergências encontradas e corrigidas. Se esse número começa a subir, é sinal de que o fluxo event-driven está perdendo eventos e merece investigação antes de virar incidente.

06

Tratamento de conflito

Conflito acontece quando os dois sistemas mudam o mesmo campo antes de sincronizar. A fonte da verdade por campo elimina a maioria dos casos, mas em fluxo two-way você precisa de uma política explícita. Escolha conforme o risco do campo.

  • Last-write-wins: vence a escrita mais recente por timestamp. Simples, bom para campos de baixo risco como observação livre.
  • Resolução campo-a-campo: cada campo segue seu dono mesmo no two-way, ignorando a alteração do lado que não manda naquele campo.
  • Fila de revisão humana: campos críticos (limite de crédito, regime fiscal) param em uma fila e um humano decide, sem auto-correção.
  • Versionamento otimista: cada registro carrega uma versão; escrita com versão defasada é rejeitada e reenfileirada para reavaliar.

Regra geral: quanto maior o impacto financeiro ou fiscal do campo, menos automática deve ser a resolução. Observação comercial pode ser last-write-wins; limite de crédito merece revisão humana.

FAQ

Perguntas frequentes

Posso sincronizar tudo nos dois sentidos para garantir que nada falte?

Não é recomendado. Two-way em todos os campos cria loops de sobrescrita e conflitos constantes: o CRM escreve, o ERP devolve, e os dois ficam piscando. Defina o dono por campo e deixe a maioria dos fluxos one-way. Reserve o two-way apenas para as poucas entidades que os dois sistemas realmente precisam editar, sempre com regra de conflito explícita.

O que uso como chave de correlação se os IDs dos dois sistemas são diferentes?

Use uma mapping table que guarda crm_id, erp_id e uma chave natural estável, como CNPJ para cliente ou SKU para produto. No primeiro encontro, você casa pela chave natural normalizada e grava o vínculo; nas próximas vezes, resolve direto pelo mapping. Assim os IDs internos podem ser diferentes sem nunca gerar duplicata.

Por que preciso de reconciliação se já tenho sincronização via webhook?

Porque webhook falha. Entrega perdida, timeout, deploy no meio de um lote ou edição direta no banco deixam os dois lados fora de sincronia sem ninguém perceber. O job de reconciliação é a rede de segurança: compara periodicamente, alerta divergências e corrige o que tem dono claro. Webhook entrega velocidade; reconciliação entrega confiança.

Dado entra uma vez e nunca mais

Integrar ERP e CRM sem retrabalho não depende de mais ferramenta, depende de três decisões: definir a fonte da verdade por campo, ligar os sistemas com upsert idempotente sobre uma mapping table e fechar a conta com reconciliação periódica. Com isso o cliente entra uma única vez, o relatório fecha e ninguém mais reescreve cadastro a mão. Posso ajudar a desenhar essa integração na sua operação.