[{"data":1,"prerenderedAt":2692},["ShallowReactive",2],{"article-\u002Fintegracoes\u002Fintegrar-erp-com-api-passo-a-passo":3},{"id":4,"title":5,"author":6,"body":7,"category":2678,"date":2679,"description":2680,"extension":2681,"keywords":2682,"meta":2686,"navigation":582,"path":2687,"seo":2688,"slug":2689,"stem":2690,"updated":2679,"__hash__":2691},"content\u002Fintegracoes\u002Fintegrar-erp-com-api-passo-a-passo.md","Integrar ERP com API: passo a passo técnico","Inicialize Tec",{"type":8,"value":9,"toc":2641},"minimark",[10,15,80,84,92,99,126,144,161,168,172,175,180,187,193,273,276,283,286,290,297,300,313,317,324,327,331,338,341,345,352,358,362,372,380,385,411,415,426,433,680,694,705,709,712,738,745,749,968,1188,1196,1200,1211,1215,1222,1268,1387,1391,1394,1406,1492,1504,1511,1515,1522,1621,1629,1634,1659,1666,1670,1676,1683,1687,1694,1819,1827,1831,1834,1860,1864,1867,1926,1930,1937,1945,1949,1953,1959,1965,2000,2005,2039,2043,2052,2056,2094,2098,2156,2160,2165,2169,2199,2203,2249,2253,2261,2265,2308,2312,2371,2375,2384,2388,2437,2441,2504,2508,2513,2518,2532,2536,2539,2544,2558,2564,2596,2601,2618,2624,2627,2637],[11,12,14],"h2",{"id":13},"índice","Índice",[16,17,18,26,32,38,44,50,56,62,68,74],"ol",{},[19,20,21],"li",{},[22,23,25],"a",{"href":24},"#problema","O problema da integração",[19,27,28],{},[22,29,31],{"href":30},"#conceitos","Conceitos que você precisa entender",[19,33,34],{},[22,35,37],{"href":36},"#arquitetura","Arquitetura de referência",[19,39,40],{},[22,41,43],{"href":42},"#passo-1","Passo 1: Mapear dados do ERP",[19,45,46],{},[22,47,49],{"href":48},"#passo-2","Passo 2: Criar camada de middleware",[19,51,52],{},[22,53,55],{"href":54},"#passo-3","Passo 3: Implementar sincronização",[19,57,58],{},[22,59,61],{"href":60},"#passo-4","Passo 4: Webhooks para tempo real",[19,63,64],{},[22,65,67],{"href":66},"#passo-5","Passo 5: Observabilidade",[19,69,70],{},[22,71,73],{"href":72},"#erros","Erros comuns",[19,75,76],{},[22,77,79],{"href":78},"#caso-real","Caso real: e-commerce que integrou ERP em 6 semanas",[11,81,83],{"id":82},"o-problema-da-integração-problema","O problema da integração {#problema}",[85,86,87,91],"p",{},[88,89,90],"strong",{},"ERP"," (Enterprise Resource Planning, ou \"Sistema de Gestão Empresarial\") é\no sistema que controla o coração da empresa: pedidos, estoque, financeiro,\nclientes. Exemplos comuns no Brasil: SAP, TOTVS, RM, e sistemas feitos\nsob medida.",[85,93,94,95,98],{},"O problema é que ERPs legados (sistemas antigos, instalados há 10-20 anos)\nraramente expõem ",[88,96,97],{},"APIs REST"," modernas. Eles foram desenhados para operar\nisoladamente, como uma ilha. Suas integrações costumam ser via:",[100,101,102,108,114,120],"ul",{},[19,103,104,107],{},[88,105,106],{},"Arquivos em disco",": o ERP gera um CSV\u002FTXT\u002FXML em uma pasta, outro sistema\nlê essa pasta periodicamente",[19,109,110,113],{},[88,111,112],{},"Tabelas compartilhadas no banco",": dois sistemas leem\u002Fescrevem na mesma\ntabela do banco de dados",[19,115,116,119],{},[88,117,118],{},"WebServices SOAP",": um formato antigo de comunicação baseado em XML,\nverboso e complexo (SOAP significa Simple Object Access Protocol — irônico,\njá que não é simples)",[19,121,122,125],{},[88,123,124],{},"Conexões diretas ao banco de dados",": um sistema acessa o banco do outro\ndiretamente (arriscado e frágil)",[85,127,128,129,131,132,135,136,139,140,143],{},"Sistemas modernos esperam ",[88,130,97],{}," (um padrão de comunicação pela web,\nusando HTTP — o mesmo protocolo dos sites) com autenticação ",[88,133,134],{},"OAuth","\n(um sistema de \"carteirinha\" que prova quem você é), respostas em ",[88,137,138],{},"JSON","\n(formato de dados leve e legível) e ",[88,141,142],{},"webhooks"," (explico adiante).",[145,146,147],"blockquote",{},[85,148,149,152,153,156,157,160],{},[88,150,151],{},"Analogia",": Imagine que o ERP é um funcionário que só fala alemão e se\ncomunica por cartas escritas à mão. O sistema moderno é um funcionário que\nsó fala português e se comunica por WhatsApp. Para eles conversarem, você\nprecisa de um ",[88,154,155],{},"tradutor"," que entende ambos os idiomas e formatos. Esse\ntradutor é o que chamamos de ",[88,158,159],{},"middleware",".",[85,162,163,164,167],{},"A lacuna entre os dois mundos é a fonte de ",[88,165,166],{},"80% dos problemas de integração","\nem empresas. A boa notícia: com a arquitetura certa, essa ponte é construída\nde forma robusta e manutenível.",[11,169,171],{"id":170},"conceitos-que-você-precisa-entender-conceitos","Conceitos que você precisa entender {#conceitos}",[85,173,174],{},"Antes de mergulhar na implementação, vamos alinhar 6 conceitos-chave:",[176,177,179],"h3",{"id":178},"api-rest","API REST",[145,181,182],{},[85,183,184,186],{},[88,185,151],{},": Uma API é como um garçom de restaurante. Você (o cliente) faz\num pedido (request) seguindo o cardápio (os endpoints disponíveis). O garçom\nleva o pedido à cozinha (o servidor), e traz o prato pronto (a resposta).\nVocê não precisa saber como a cozinha funciona — só precisa saber pedir.",[85,188,189,192],{},[88,190,191],{},"REST"," (Representational State Transfer) é um conjunto de regras para\nconstruir APIs usando HTTP. As operações básicas (CRUD — Create, Read, Update,\nDelete) mapeiam para verbos HTTP:",[194,195,196,212],"table",{},[197,198,199],"thead",{},[200,201,202,206,209],"tr",{},[203,204,205],"th",{},"Operação",[203,207,208],{},"Verbo HTTP",[203,210,211],{},"Exemplo",[213,214,215,231,245,259],"tbody",{},[200,216,217,221,224],{},[218,219,220],"td",{},"Criar",[218,222,223],{},"POST",[218,225,226,230],{},[227,228,229],"code",{},"POST \u002Fpedidos"," — cria um pedido novo",[200,232,233,236,239],{},[218,234,235],{},"Ler",[218,237,238],{},"GET",[218,240,241,244],{},[227,242,243],{},"GET \u002Fpedidos\u002F123"," — busca o pedido #123",[200,246,247,250,253],{},[218,248,249],{},"Atualizar",[218,251,252],{},"PUT\u002FPATCH",[218,254,255,258],{},[227,256,257],{},"PUT \u002Fpedidos\u002F123"," — atualiza o pedido #123",[200,260,261,264,267],{},[218,262,263],{},"Deletar",[218,265,266],{},"DELETE",[218,268,269,272],{},[227,270,271],{},"DELETE \u002Fpedidos\u002F123"," — remove o pedido #123",[176,274,275],{"id":159},"Middleware",[145,277,278],{},[85,279,280,282],{},[88,281,151],{},": Middleware é como um intérprete em uma reunião de negócios\ninternacional. O brasileiro fala português, o alemão fala alemão. O\nintérprete ouve um, traduz para o outro, e vice-versa. Nenhum dos dois\nprecisa aprender o idioma do outro.",[85,284,285],{},"O middleware é a camada que fica entre o ERP e o sistema moderno, traduzindo\nformatos, protocolos e garantindo que a comunicação flua de forma confiável.",[176,287,289],{"id":288},"fila-de-mensagens-message-queue","Fila de mensagens (Message Queue)",[145,291,292],{},[85,293,294,296],{},[88,295,151],{},": Uma fila de mensagens é como uma caixa de correio comum da\nempresa. Quando chega uma carta, ela fica na caixa até alguém pegar. Se\no carteiro (produtor) entrega uma carta e o destinatário (consumidor) não\nestá, a carta não se perde — fica esperando. O carteiro não precisa esperar\no destinatário chegar para ir embora.",[85,298,299],{},"Isso é fundamental para integrações: o ERP pode gerar 100 mudanças em 1\nminuto. Se tentarmos processar todas síncronas (uma por vez, esperando cada\numa terminar), o ERP trava. Com fila, as mudanças entram na fila\ninstantaneamente e são processadas no ritmo do sistema moderno.",[85,301,302,303,306,307,306,310,160],{},"Ferramentas comuns: ",[88,304,305],{},"RabbitMQ",", ",[88,308,309],{},"Kafka",[88,311,312],{},"Amazon SQS",[176,314,316],{"id":315},"webhook","Webhook",[145,318,319],{},[85,320,321,323],{},[88,322,151],{},": Um webhook é como um alarme de incêndio. Em vez de você\nficar checando a cada 5 minutos \"tem fogo? tem fogo?\", o alarme te avisa\nno momento em que o fogo começa. Você não pergunta — é avisado.",[85,325,326],{},"Um webhook é um endpoint (URL) no seu sistema que o ERP chama automaticamente\nquando algo acontece. Em vez de você perguntar ao ERP a cada minuto \"tem\npedido novo?\" (polling), o ERP te avisa no momento exato em que o pedido é\ncriado.",[176,328,330],{"id":329},"idempotência","Idempotência",[145,332,333],{},[85,334,335,337],{},[88,336,151],{},": Idempotência é como um carimbo de \"PAGO\" em uma conta. Se\nvocê carimbar uma, dez ou cem vezes, o resultado é o mesmo: a conta está\nmarcada como paga. Não cria múltiplas contas pagas.",[85,339,340],{},"Na prática: se a mesma mensagem for processada 2 vezes (por exemplo, devido\na um retry), o resultado deve ser o mesmo de processar 1 vez. Não cria\nduplicatas.",[176,342,344],{"id":343},"cdc-change-data-capture","CDC (Change Data Capture)",[145,346,347],{},[85,348,349,351],{},[88,350,151],{},": CDC é como uma câmera de segurança que grava tudo que acontece\nno banco de dados. Em vez de você perguntar \"mudou alguma coisa?\" a cada\nminuto, a câmera te entrega um log de tudo que mudou, no momento exato.",[85,353,354,355,160],{},"Tecnicamente: CDC monitora o log de transações do banco de dados e captura\nmudanças (insert, update, delete) em tempo real, sem precisar de polling.\nFerramenta popular: ",[88,356,357],{},"Debezium",[11,359,361],{"id":360},"arquitetura-de-referência-arquitetura","Arquitetura de referência {#arquitetura}",[363,364,369],"pre",{"className":365,"code":367,"language":368},[366],"language-text","[ERP Legado]                         [Sistema Moderno]\n     │                                      │\n     │ (arquivo\u002FSQL\u002FSOAP)                   │ (REST API + JSON)\n     ↓                                      ↓\n[Adapter ERP] ────→ [Middleware] ────→ [Adapter API]\n                          │\n                   [Fila de Mensagens (RabbitMQ)]\n                          │\n                   [Logger + Monitor + Alertas]\n","text",[227,370,367],{"__ignoreMap":371},"",[145,373,374],{},[85,375,376,379],{},[88,377,378],{},"Analogia completa",": O ERP \"fala\" gerando arquivos e registros no banco.\nO Adapter ERP é como um \"ouvidor\" que captura essas mudanças. O Middleware\né o \"tradutor\" que converte para o formato que o sistema moderno entende.\nA Fila é a \"caixa de correio\" que segura as mensagens até serem processadas.\nO Adapter API é o \"garçom\" que entrega as mensagens ao sistema moderno.",[85,381,382],{},[88,383,384],{},"Princípios da arquitetura:",[16,386,387,393,399,405],{},[19,388,389,392],{},[88,390,391],{},"Adapter Pattern"," (padrão de adaptador): cada sistema tem um adaptador\nque traduz seu protocolo. Se o ERP mudar, só o adapter muda — o resto\nfica intacto.",[19,394,395,398],{},[88,396,397],{},"Assíncrono",": comunicação via fila, não síncrona. O ERP não espera o\nsistema moderno responder.",[19,400,401,404],{},[88,402,403],{},"Idempotente",": reprocessar uma mensagem não causa duplicação.",[19,406,407,410],{},[88,408,409],{},"Observável",": cada mensagem tem um ID único e é rastreável do início ao\nfim.",[11,412,414],{"id":413},"passo-1-mapear-dados-do-erp-passo-1","Passo 1: Mapear dados do ERP {#passo-1}",[85,416,417,418,421,422,425],{},"Antes de escrever qualquer código, documente ",[88,419,420],{},"quais dados"," do ERP serão\nintegrados e ",[88,423,424],{},"como"," eles estão estruturados.",[145,427,428],{},[85,429,430,432],{},[88,431,151],{},": Antes de construir uma ponte entre duas ilhas, você precisa\nmapear o terreno de cada lado. Quais estradas chegam à margem? Qual o\ntipo de solo? Sem esse mapeamento, a ponte pode desabar.",[363,434,438],{"className":435,"code":436,"language":437,"meta":371,"style":371},"language-yaml shiki shiki-themes github-light github-dark","# mapeamento-erp.yaml — Documentação dos dados a integrar\nentidades:\n  pedido:\n    origem: tabela TBL_PEDIDO (SQL Server)\n    campos:\n      - nr_pedido: \"ID único do pedido (int, chave primária)\"\n      - dt_emissao: \"Data de emissão (datetime)\"\n      - cd_cliente: \"Código do cliente — FK para TBL_CLIENTE\"\n      - vl_total: \"Valor total (Decimal 12,2 — até 99.999.999,99)\"\n      - st_pedido: \"Status: A=Aberto, F=Faturado, C=Cancelado\"\n    trigger: \"INSERT ou UPDATE em TBL_PEDIDO\"\n    frequencia: \"Aproximadamente 200 pedidos\u002Fdia\"\n\n  cliente:\n    origem: tabela TBL_CLIENTE (SQL Server)\n    campos:\n      - cd_cliente: \"ID único do cliente (int)\"\n      - nm_cliente: \"Nome ou razão social (varchar 100)\"\n      - nr_cnpj: \"CNPJ sem máscara (varchar 14)\"\n      - ds_email: \"E-mail de contato (varchar 200)\"\n    trigger: \"INSERT ou UPDATE em TBL_CLIENTE\"\n    frequencia: \"Aproximadamente 20 clientes novos\u002Fdia\"\n","yaml",[227,439,440,449,460,468,481,489,503,516,529,542,555,566,577,584,592,602,609,621,634,647,660,670],{"__ignoreMap":371},[441,442,445],"span",{"class":443,"line":444},"line",1,[441,446,448],{"class":447},"sJ8bj","# mapeamento-erp.yaml — Documentação dos dados a integrar\n",[441,450,452,456],{"class":443,"line":451},2,[441,453,455],{"class":454},"s9eBZ","entidades",[441,457,459],{"class":458},"sVt8B",":\n",[441,461,463,466],{"class":443,"line":462},3,[441,464,465],{"class":454},"  pedido",[441,467,459],{"class":458},[441,469,471,474,477],{"class":443,"line":470},4,[441,472,473],{"class":454},"    origem",[441,475,476],{"class":458},": ",[441,478,480],{"class":479},"sZZnC","tabela TBL_PEDIDO (SQL Server)\n",[441,482,484,487],{"class":443,"line":483},5,[441,485,486],{"class":454},"    campos",[441,488,459],{"class":458},[441,490,492,495,498,500],{"class":443,"line":491},6,[441,493,494],{"class":458},"      - ",[441,496,497],{"class":454},"nr_pedido",[441,499,476],{"class":458},[441,501,502],{"class":479},"\"ID único do pedido (int, chave primária)\"\n",[441,504,506,508,511,513],{"class":443,"line":505},7,[441,507,494],{"class":458},[441,509,510],{"class":454},"dt_emissao",[441,512,476],{"class":458},[441,514,515],{"class":479},"\"Data de emissão (datetime)\"\n",[441,517,519,521,524,526],{"class":443,"line":518},8,[441,520,494],{"class":458},[441,522,523],{"class":454},"cd_cliente",[441,525,476],{"class":458},[441,527,528],{"class":479},"\"Código do cliente — FK para TBL_CLIENTE\"\n",[441,530,532,534,537,539],{"class":443,"line":531},9,[441,533,494],{"class":458},[441,535,536],{"class":454},"vl_total",[441,538,476],{"class":458},[441,540,541],{"class":479},"\"Valor total (Decimal 12,2 — até 99.999.999,99)\"\n",[441,543,545,547,550,552],{"class":443,"line":544},10,[441,546,494],{"class":458},[441,548,549],{"class":454},"st_pedido",[441,551,476],{"class":458},[441,553,554],{"class":479},"\"Status: A=Aberto, F=Faturado, C=Cancelado\"\n",[441,556,558,561,563],{"class":443,"line":557},11,[441,559,560],{"class":454},"    trigger",[441,562,476],{"class":458},[441,564,565],{"class":479},"\"INSERT ou UPDATE em TBL_PEDIDO\"\n",[441,567,569,572,574],{"class":443,"line":568},12,[441,570,571],{"class":454},"    frequencia",[441,573,476],{"class":458},[441,575,576],{"class":479},"\"Aproximadamente 200 pedidos\u002Fdia\"\n",[441,578,580],{"class":443,"line":579},13,[441,581,583],{"emptyLinePlaceholder":582},true,"\n",[441,585,587,590],{"class":443,"line":586},14,[441,588,589],{"class":454},"  cliente",[441,591,459],{"class":458},[441,593,595,597,599],{"class":443,"line":594},15,[441,596,473],{"class":454},[441,598,476],{"class":458},[441,600,601],{"class":479},"tabela TBL_CLIENTE (SQL Server)\n",[441,603,605,607],{"class":443,"line":604},16,[441,606,486],{"class":454},[441,608,459],{"class":458},[441,610,612,614,616,618],{"class":443,"line":611},17,[441,613,494],{"class":458},[441,615,523],{"class":454},[441,617,476],{"class":458},[441,619,620],{"class":479},"\"ID único do cliente (int)\"\n",[441,622,624,626,629,631],{"class":443,"line":623},18,[441,625,494],{"class":458},[441,627,628],{"class":454},"nm_cliente",[441,630,476],{"class":458},[441,632,633],{"class":479},"\"Nome ou razão social (varchar 100)\"\n",[441,635,637,639,642,644],{"class":443,"line":636},19,[441,638,494],{"class":458},[441,640,641],{"class":454},"nr_cnpj",[441,643,476],{"class":458},[441,645,646],{"class":479},"\"CNPJ sem máscara (varchar 14)\"\n",[441,648,650,652,655,657],{"class":443,"line":649},20,[441,651,494],{"class":458},[441,653,654],{"class":454},"ds_email",[441,656,476],{"class":458},[441,658,659],{"class":479},"\"E-mail de contato (varchar 200)\"\n",[441,661,663,665,667],{"class":443,"line":662},21,[441,664,560],{"class":454},[441,666,476],{"class":458},[441,668,669],{"class":479},"\"INSERT ou UPDATE em TBL_CLIENTE\"\n",[441,671,673,675,677],{"class":443,"line":672},22,[441,674,571],{"class":454},[441,676,476],{"class":458},[441,678,679],{"class":479},"\"Aproximadamente 20 clientes novos\u002Fdia\"\n",[85,681,682,685,686,689,690,693],{},[88,683,684],{},"Dica",": Use ",[88,687,688],{},"database triggers"," (gatilhos no banco que disparam quando\nalgo muda) ou ",[88,691,692],{},"CDC"," (Change Data Capture) para detectar mudanças em vez de\npolling (ficar perguntando \"tem novidade?\" a cada minuto). Isso reduz a\ncarga no ERP e diminui o tempo de latência.",[145,695,696],{},[85,697,698,701,702,704],{},[88,699,700],{},"Erro comum",": Pular o mapeamento e ir direto ao código. Resultado: 3\nsemanas depois, descobre que o campo ",[227,703,549],{}," pode ter 6 valores\ndiferentes, não 3 como você assumiu. O mapeamento é a parte mais importante\n— invista tempo nele.",[11,706,708],{"id":707},"passo-2-criar-camada-de-middleware-passo-2","Passo 2: Criar camada de middleware {#passo-2}",[85,710,711],{},"O middleware é o coração da integração. Ele faz 4 coisas:",[16,713,714,720,726,732],{},[19,715,716,719],{},[88,717,718],{},"Lê"," mudanças do ERP (via trigger, CDC ou polling)",[19,721,722,725],{},[88,723,724],{},"Transforma"," para um formato canônico (JSON padronizado que o sistema\nmoderno entende)",[19,727,728,731],{},[88,729,730],{},"Publica"," em fila (RabbitMQ\u002FKafka)",[19,733,734,737],{},[88,735,736],{},"Consome"," da fila e envia para a API moderna",[145,739,740],{},[85,741,742,744],{},[88,743,151],{},": O middleware é como uma central de triagem de correios.\nRecebe cartas de diversos remetentes (ERP), padroniza o formato (envelope\npadrão), coloca na esteira (fila), e a esteira leva para o destinatário\n(API moderna) no ritmo certo.",[176,746,748],{"id":747},"exemplo-em-python-fastapi-rabbitmq","Exemplo em Python (FastAPI + RabbitMQ)",[363,750,754],{"className":751,"code":752,"language":753,"meta":371,"style":371},"language-python shiki shiki-themes github-light github-dark","# adapter_erp.py — Adapter que lê mudanças do ERP e publica na fila\nimport json\nfrom datetime import datetime\nfrom pika import BlockingConnection, ConnectionParameters, BasicProperties\n\ndef on_erp_change(entity: str, action: str, data: dict):\n    \"\"\"\n    Chamado quando o ERP notifica uma mudança (via trigger ou CDC).\n\n    Args:\n        entity: nome da entidade que mudou ('pedido', 'cliente', etc.)\n        action: o que aconteceu ('created', 'updated', 'deleted')\n        data: os dados do registro que mudou\n    \"\"\"\n    # Constrói mensagem no formato canônico (padrão para todos os sistemas)\n    message = {\n        # ID único da mensagem — usado para idempotência (evitar duplicação)\n        \"id\": f\"{entity}-{data['id']}-{action}-{datetime.utcnow().isoformat()}\",\n        \"entity\": entity,        # qual entidade mudou\n        \"action\": action,        # o que aconteceu: created | updated | deleted\n        \"data\": data,            # os dados em si\n        \"timestamp\": datetime.utcnow().isoformat(),  # quando ocorreu\n    }\n\n    # Conecta ao RabbitMQ e publica a mensagem na fila\n    connection = BlockingConnection(ConnectionParameters(\"rabbitmq\"))\n    channel = connection.channel()\n\n    # Declara a fila como 'durable' — sobrevive a reinícios do RabbitMQ\n    channel.queue_declare(queue=\"erp_changes\", durable=True)\n\n    # Publica a mensagem\n    channel.basic_publish(\n        exchange=\"\",\n        routing_key=\"erp_changes\",\n        body=json.dumps(message),\n        # delivery_mode=2 = mensagem persistente (não se perde em restart)\n        properties=BasicProperties(delivery_mode=2),\n    )\n    connection.close()\n","python",[227,755,756,761,766,771,776,780,785,790,795,799,804,809,814,819,823,828,833,838,843,848,853,858,863,869,874,880,886,892,897,903,909,914,920,926,932,938,944,950,956,962],{"__ignoreMap":371},[441,757,758],{"class":443,"line":444},[441,759,760],{},"# adapter_erp.py — Adapter que lê mudanças do ERP e publica na fila\n",[441,762,763],{"class":443,"line":451},[441,764,765],{},"import json\n",[441,767,768],{"class":443,"line":462},[441,769,770],{},"from datetime import datetime\n",[441,772,773],{"class":443,"line":470},[441,774,775],{},"from pika import BlockingConnection, ConnectionParameters, BasicProperties\n",[441,777,778],{"class":443,"line":483},[441,779,583],{"emptyLinePlaceholder":582},[441,781,782],{"class":443,"line":491},[441,783,784],{},"def on_erp_change(entity: str, action: str, data: dict):\n",[441,786,787],{"class":443,"line":505},[441,788,789],{},"    \"\"\"\n",[441,791,792],{"class":443,"line":518},[441,793,794],{},"    Chamado quando o ERP notifica uma mudança (via trigger ou CDC).\n",[441,796,797],{"class":443,"line":531},[441,798,583],{"emptyLinePlaceholder":582},[441,800,801],{"class":443,"line":544},[441,802,803],{},"    Args:\n",[441,805,806],{"class":443,"line":557},[441,807,808],{},"        entity: nome da entidade que mudou ('pedido', 'cliente', etc.)\n",[441,810,811],{"class":443,"line":568},[441,812,813],{},"        action: o que aconteceu ('created', 'updated', 'deleted')\n",[441,815,816],{"class":443,"line":579},[441,817,818],{},"        data: os dados do registro que mudou\n",[441,820,821],{"class":443,"line":586},[441,822,789],{},[441,824,825],{"class":443,"line":594},[441,826,827],{},"    # Constrói mensagem no formato canônico (padrão para todos os sistemas)\n",[441,829,830],{"class":443,"line":604},[441,831,832],{},"    message = {\n",[441,834,835],{"class":443,"line":611},[441,836,837],{},"        # ID único da mensagem — usado para idempotência (evitar duplicação)\n",[441,839,840],{"class":443,"line":623},[441,841,842],{},"        \"id\": f\"{entity}-{data['id']}-{action}-{datetime.utcnow().isoformat()}\",\n",[441,844,845],{"class":443,"line":636},[441,846,847],{},"        \"entity\": entity,        # qual entidade mudou\n",[441,849,850],{"class":443,"line":649},[441,851,852],{},"        \"action\": action,        # o que aconteceu: created | updated | deleted\n",[441,854,855],{"class":443,"line":662},[441,856,857],{},"        \"data\": data,            # os dados em si\n",[441,859,860],{"class":443,"line":672},[441,861,862],{},"        \"timestamp\": datetime.utcnow().isoformat(),  # quando ocorreu\n",[441,864,866],{"class":443,"line":865},23,[441,867,868],{},"    }\n",[441,870,872],{"class":443,"line":871},24,[441,873,583],{"emptyLinePlaceholder":582},[441,875,877],{"class":443,"line":876},25,[441,878,879],{},"    # Conecta ao RabbitMQ e publica a mensagem na fila\n",[441,881,883],{"class":443,"line":882},26,[441,884,885],{},"    connection = BlockingConnection(ConnectionParameters(\"rabbitmq\"))\n",[441,887,889],{"class":443,"line":888},27,[441,890,891],{},"    channel = connection.channel()\n",[441,893,895],{"class":443,"line":894},28,[441,896,583],{"emptyLinePlaceholder":582},[441,898,900],{"class":443,"line":899},29,[441,901,902],{},"    # Declara a fila como 'durable' — sobrevive a reinícios do RabbitMQ\n",[441,904,906],{"class":443,"line":905},30,[441,907,908],{},"    channel.queue_declare(queue=\"erp_changes\", durable=True)\n",[441,910,912],{"class":443,"line":911},31,[441,913,583],{"emptyLinePlaceholder":582},[441,915,917],{"class":443,"line":916},32,[441,918,919],{},"    # Publica a mensagem\n",[441,921,923],{"class":443,"line":922},33,[441,924,925],{},"    channel.basic_publish(\n",[441,927,929],{"class":443,"line":928},34,[441,930,931],{},"        exchange=\"\",\n",[441,933,935],{"class":443,"line":934},35,[441,936,937],{},"        routing_key=\"erp_changes\",\n",[441,939,941],{"class":443,"line":940},36,[441,942,943],{},"        body=json.dumps(message),\n",[441,945,947],{"class":443,"line":946},37,[441,948,949],{},"        # delivery_mode=2 = mensagem persistente (não se perde em restart)\n",[441,951,953],{"class":443,"line":952},38,[441,954,955],{},"        properties=BasicProperties(delivery_mode=2),\n",[441,957,959],{"class":443,"line":958},39,[441,960,961],{},"    )\n",[441,963,965],{"class":443,"line":964},40,[441,966,967],{},"    connection.close()\n",[363,969,971],{"className":751,"code":970,"language":753,"meta":371,"style":371},"# consumer_api.py — Consumer que lê da fila e envia para a API moderna\nimport json\nfrom pika import BlockingConnection, ConnectionParameters\n\ndef on_message(ch, method, properties, body):\n    \"\"\"\n    Chamado quando uma mensagem chega na fila.\n\n    O RabbitMQ chama esta função automaticamente para cada mensagem.\n    \"\"\"\n    message = json.loads(body)\n\n    # === IDEMPOTÊNCIA ===\n    # Verifica se esta mensagem já foi processada (pode chegar duplicada\n    # devido a retries). Usa o ID único da mensagem.\n    if already_processed(message[\"id\"]):\n        # Já processou? Confirma o recebimento e ignora\n        ch.basic_ack(method.delivery_tag)\n        return\n\n    # === ENVIO PARA API MODERNA ===\n    # Conforme a ação, chama o endpoint correto da API REST\n    if message[\"action\"] == \"created\":\n        # POST: cria um novo registro no sistema moderno\n        response = api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n    elif message[\"action\"] == \"updated\":\n        # PUT: atualiza o registro existente\n        response = api.put(\n            f\"\u002F{message['entity']}\u002F{message['data']['id']}\",\n            json=message[\"data\"]\n        )\n    elif message[\"action\"] == \"deleted\":\n        # DELETE: remove o registro\n        response = api.delete(f\"\u002F{message['entity']}\u002F{message['data']['id']}\")\n\n    # === TRATAMENTO DE RESPOSTA ===\n    if response.ok:\n        # Sucesso: marca como processado (para idempotência) e confirma\n        mark_processed(message[\"id\"])\n        ch.basic_ack(method.delivery_tag)\n    else:\n        # Falha: rejeita e coloca de volta na fila (com retry)\n        # Em produção: usar dead letter queue após N tentativas\n        ch.basic_nack(method.delivery_tag, requeue=True)\n",[227,972,973,978,982,987,991,996,1000,1005,1009,1014,1018,1023,1027,1032,1037,1042,1047,1052,1057,1062,1066,1071,1076,1081,1086,1091,1096,1101,1106,1111,1116,1121,1126,1131,1136,1140,1145,1150,1155,1160,1164,1170,1176,1182],{"__ignoreMap":371},[441,974,975],{"class":443,"line":444},[441,976,977],{},"# consumer_api.py — Consumer que lê da fila e envia para a API moderna\n",[441,979,980],{"class":443,"line":451},[441,981,765],{},[441,983,984],{"class":443,"line":462},[441,985,986],{},"from pika import BlockingConnection, ConnectionParameters\n",[441,988,989],{"class":443,"line":470},[441,990,583],{"emptyLinePlaceholder":582},[441,992,993],{"class":443,"line":483},[441,994,995],{},"def on_message(ch, method, properties, body):\n",[441,997,998],{"class":443,"line":491},[441,999,789],{},[441,1001,1002],{"class":443,"line":505},[441,1003,1004],{},"    Chamado quando uma mensagem chega na fila.\n",[441,1006,1007],{"class":443,"line":518},[441,1008,583],{"emptyLinePlaceholder":582},[441,1010,1011],{"class":443,"line":531},[441,1012,1013],{},"    O RabbitMQ chama esta função automaticamente para cada mensagem.\n",[441,1015,1016],{"class":443,"line":544},[441,1017,789],{},[441,1019,1020],{"class":443,"line":557},[441,1021,1022],{},"    message = json.loads(body)\n",[441,1024,1025],{"class":443,"line":568},[441,1026,583],{"emptyLinePlaceholder":582},[441,1028,1029],{"class":443,"line":579},[441,1030,1031],{},"    # === IDEMPOTÊNCIA ===\n",[441,1033,1034],{"class":443,"line":586},[441,1035,1036],{},"    # Verifica se esta mensagem já foi processada (pode chegar duplicada\n",[441,1038,1039],{"class":443,"line":594},[441,1040,1041],{},"    # devido a retries). Usa o ID único da mensagem.\n",[441,1043,1044],{"class":443,"line":604},[441,1045,1046],{},"    if already_processed(message[\"id\"]):\n",[441,1048,1049],{"class":443,"line":611},[441,1050,1051],{},"        # Já processou? Confirma o recebimento e ignora\n",[441,1053,1054],{"class":443,"line":623},[441,1055,1056],{},"        ch.basic_ack(method.delivery_tag)\n",[441,1058,1059],{"class":443,"line":636},[441,1060,1061],{},"        return\n",[441,1063,1064],{"class":443,"line":649},[441,1065,583],{"emptyLinePlaceholder":582},[441,1067,1068],{"class":443,"line":662},[441,1069,1070],{},"    # === ENVIO PARA API MODERNA ===\n",[441,1072,1073],{"class":443,"line":672},[441,1074,1075],{},"    # Conforme a ação, chama o endpoint correto da API REST\n",[441,1077,1078],{"class":443,"line":865},[441,1079,1080],{},"    if message[\"action\"] == \"created\":\n",[441,1082,1083],{"class":443,"line":871},[441,1084,1085],{},"        # POST: cria um novo registro no sistema moderno\n",[441,1087,1088],{"class":443,"line":876},[441,1089,1090],{},"        response = api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n",[441,1092,1093],{"class":443,"line":882},[441,1094,1095],{},"    elif message[\"action\"] == \"updated\":\n",[441,1097,1098],{"class":443,"line":888},[441,1099,1100],{},"        # PUT: atualiza o registro existente\n",[441,1102,1103],{"class":443,"line":894},[441,1104,1105],{},"        response = api.put(\n",[441,1107,1108],{"class":443,"line":899},[441,1109,1110],{},"            f\"\u002F{message['entity']}\u002F{message['data']['id']}\",\n",[441,1112,1113],{"class":443,"line":905},[441,1114,1115],{},"            json=message[\"data\"]\n",[441,1117,1118],{"class":443,"line":911},[441,1119,1120],{},"        )\n",[441,1122,1123],{"class":443,"line":916},[441,1124,1125],{},"    elif message[\"action\"] == \"deleted\":\n",[441,1127,1128],{"class":443,"line":922},[441,1129,1130],{},"        # DELETE: remove o registro\n",[441,1132,1133],{"class":443,"line":928},[441,1134,1135],{},"        response = api.delete(f\"\u002F{message['entity']}\u002F{message['data']['id']}\")\n",[441,1137,1138],{"class":443,"line":934},[441,1139,583],{"emptyLinePlaceholder":582},[441,1141,1142],{"class":443,"line":940},[441,1143,1144],{},"    # === TRATAMENTO DE RESPOSTA ===\n",[441,1146,1147],{"class":443,"line":946},[441,1148,1149],{},"    if response.ok:\n",[441,1151,1152],{"class":443,"line":952},[441,1153,1154],{},"        # Sucesso: marca como processado (para idempotência) e confirma\n",[441,1156,1157],{"class":443,"line":958},[441,1158,1159],{},"        mark_processed(message[\"id\"])\n",[441,1161,1162],{"class":443,"line":964},[441,1163,1056],{},[441,1165,1167],{"class":443,"line":1166},41,[441,1168,1169],{},"    else:\n",[441,1171,1173],{"class":443,"line":1172},42,[441,1174,1175],{},"        # Falha: rejeita e coloca de volta na fila (com retry)\n",[441,1177,1179],{"class":443,"line":1178},43,[441,1180,1181],{},"        # Em produção: usar dead letter queue após N tentativas\n",[441,1183,1185],{"class":443,"line":1184},44,[441,1186,1187],{},"        ch.basic_nack(method.delivery_tag, requeue=True)\n",[145,1189,1190],{},[85,1191,1192,1195],{},[88,1193,1194],{},"Explicação do fluxo",": O Adapter ERP detecta uma mudança (ex: novo pedido\nno ERP) → cria uma mensagem JSON → publica na fila RabbitMQ. O Consumer lê\na mensagem da fila → verifica se já processou (idempotência) → envia para\na API moderna (POST\u002FPUT\u002FDELETE) → confirma ou rejeita a mensagem.",[11,1197,1199],{"id":1198},"passo-3-implementar-sincronização-passo-3","Passo 3: Implementar sincronização {#passo-3}",[85,1201,1202,1203,1206,1207,1210],{},"Há dois tipos de sincronização: ",[88,1204,1205],{},"carga completa"," (one-time, no início) e\n",[88,1208,1209],{},"incremental"," (contínua, após a carga inicial).",[176,1212,1214],{"id":1213},"carga-completa-one-time","Carga completa (one-time)",[85,1216,1217,1218,1221],{},"A primeira vez que você liga a integração, precisa sincronizar todos os dados\nhistóricos. Faça em ",[88,1219,1220],{},"batches"," (lotes) para não sobrecarregar o ERP.",[363,1223,1227],{"className":1224,"code":1225,"language":1226,"meta":371,"style":371},"language-sql shiki shiki-themes github-light github-dark","-- Carga inicial: busca pedidos em lotes de 10.000\n-- OFFSET\u002FFETCH NEXT garante que pegamos os dados em páginas, sem travar o ERP\nSELECT * FROM TBL_PEDIDO\nWHERE dt_emissao >= '2024-01-01'\nORDER BY nr_pedido\nOFFSET 0 ROWS FETCH NEXT 10000 ROWS ONLY;\n\n-- Depois: OFFSET 10000, 20000, 30000... até terminar\n","sql",[227,1228,1229,1234,1239,1244,1249,1254,1259,1263],{"__ignoreMap":371},[441,1230,1231],{"class":443,"line":444},[441,1232,1233],{},"-- Carga inicial: busca pedidos em lotes de 10.000\n",[441,1235,1236],{"class":443,"line":451},[441,1237,1238],{},"-- OFFSET\u002FFETCH NEXT garante que pegamos os dados em páginas, sem travar o ERP\n",[441,1240,1241],{"class":443,"line":462},[441,1242,1243],{},"SELECT * FROM TBL_PEDIDO\n",[441,1245,1246],{"class":443,"line":470},[441,1247,1248],{},"WHERE dt_emissao >= '2024-01-01'\n",[441,1250,1251],{"class":443,"line":483},[441,1252,1253],{},"ORDER BY nr_pedido\n",[441,1255,1256],{"class":443,"line":491},[441,1257,1258],{},"OFFSET 0 ROWS FETCH NEXT 10000 ROWS ONLY;\n",[441,1260,1261],{"class":443,"line":505},[441,1262,583],{"emptyLinePlaceholder":582},[441,1264,1265],{"class":443,"line":518},[441,1266,1267],{},"-- Depois: OFFSET 10000, 20000, 30000... até terminar\n",[363,1269,1271],{"className":751,"code":1270,"language":753,"meta":371,"style":371},"# sincronizacao_inicial.py — Carga completa em batches\ndef sincronizar_pedidos_completos():\n    offset = 0\n    batch_size = 10000\n\n    while True:\n        # Busca um lote de pedidos do ERP\n        pedidos = erp_db.execute(\n            f\"SELECT * FROM TBL_PEDIDO ORDER BY nr_pedido \"\n            f\"OFFSET {offset} ROWS FETCH NEXT {batch_size} ROWS ONLY\"\n        )\n\n        if not pedidos:\n            break  # não há mais dados\n\n        # Envia cada pedido para a fila (mesmo fluxo do adapter)\n        for pedido in pedidos:\n            on_erp_change(\"pedido\", \"created\", pedido)\n\n        offset += batch_size\n        print(f\"Sincronizados {offset} pedidos...\")\n\n        # Pausa entre batches para não saturar o ERP\n        time.sleep(2)\n",[227,1272,1273,1278,1283,1288,1293,1297,1302,1307,1312,1317,1322,1326,1330,1335,1340,1344,1349,1354,1359,1363,1368,1373,1377,1382],{"__ignoreMap":371},[441,1274,1275],{"class":443,"line":444},[441,1276,1277],{},"# sincronizacao_inicial.py — Carga completa em batches\n",[441,1279,1280],{"class":443,"line":451},[441,1281,1282],{},"def sincronizar_pedidos_completos():\n",[441,1284,1285],{"class":443,"line":462},[441,1286,1287],{},"    offset = 0\n",[441,1289,1290],{"class":443,"line":470},[441,1291,1292],{},"    batch_size = 10000\n",[441,1294,1295],{"class":443,"line":483},[441,1296,583],{"emptyLinePlaceholder":582},[441,1298,1299],{"class":443,"line":491},[441,1300,1301],{},"    while True:\n",[441,1303,1304],{"class":443,"line":505},[441,1305,1306],{},"        # Busca um lote de pedidos do ERP\n",[441,1308,1309],{"class":443,"line":518},[441,1310,1311],{},"        pedidos = erp_db.execute(\n",[441,1313,1314],{"class":443,"line":531},[441,1315,1316],{},"            f\"SELECT * FROM TBL_PEDIDO ORDER BY nr_pedido \"\n",[441,1318,1319],{"class":443,"line":544},[441,1320,1321],{},"            f\"OFFSET {offset} ROWS FETCH NEXT {batch_size} ROWS ONLY\"\n",[441,1323,1324],{"class":443,"line":557},[441,1325,1120],{},[441,1327,1328],{"class":443,"line":568},[441,1329,583],{"emptyLinePlaceholder":582},[441,1331,1332],{"class":443,"line":579},[441,1333,1334],{},"        if not pedidos:\n",[441,1336,1337],{"class":443,"line":586},[441,1338,1339],{},"            break  # não há mais dados\n",[441,1341,1342],{"class":443,"line":594},[441,1343,583],{"emptyLinePlaceholder":582},[441,1345,1346],{"class":443,"line":604},[441,1347,1348],{},"        # Envia cada pedido para a fila (mesmo fluxo do adapter)\n",[441,1350,1351],{"class":443,"line":611},[441,1352,1353],{},"        for pedido in pedidos:\n",[441,1355,1356],{"class":443,"line":623},[441,1357,1358],{},"            on_erp_change(\"pedido\", \"created\", pedido)\n",[441,1360,1361],{"class":443,"line":636},[441,1362,583],{"emptyLinePlaceholder":582},[441,1364,1365],{"class":443,"line":649},[441,1366,1367],{},"        offset += batch_size\n",[441,1369,1370],{"class":443,"line":662},[441,1371,1372],{},"        print(f\"Sincronizados {offset} pedidos...\")\n",[441,1374,1375],{"class":443,"line":672},[441,1376,583],{"emptyLinePlaceholder":582},[441,1378,1379],{"class":443,"line":865},[441,1380,1381],{},"        # Pausa entre batches para não saturar o ERP\n",[441,1383,1384],{"class":443,"line":871},[441,1385,1386],{},"        time.sleep(2)\n",[176,1388,1390],{"id":1389},"incremental-contínuo","Incremental (contínuo)",[85,1392,1393],{},"Após a carga inicial, você só precisa sincronizar as mudanças novas:",[100,1395,1396],{},[19,1397,1398,1401,1402,1405],{},[88,1399,1400],{},"Abordagem 1 — Watermark com timestamp",": use o campo ",[227,1403,1404],{},"updated_at"," como\n\"marca d'água\". A cada 1-5 minutos, busque registros atualizados desde a\núltima marca.",[363,1407,1409],{"className":751,"code":1408,"language":753,"meta":371,"style":371},"# sincronizacao_incremental.py — Polling com watermark\ndef sincronizar_incremental():\n    # last_watermark = timestamp da última sincronização (salvo em arquivo\u002Fbanco)\n    last_watermark = carregar_ultimo_watermark()\n\n    # Busca apenas registros modificados após o último watermark\n    novos_pedidos = erp_db.execute(\n        \"SELECT * FROM TBL_PEDIDO WHERE updated_at > ? ORDER BY updated_at\",\n        [last_watermark]\n    )\n\n    for pedido in novos_pedidos:\n        on_erp_change(\"pedido\", \"updated\", pedido)\n\n    # Atualiza o watermark para o timestamp do último registro processado\n    if novos_pedidos:\n        salvar_watermark(novos_pedidos[-1][\"updated_at\"])\n",[227,1410,1411,1416,1421,1426,1431,1435,1440,1445,1450,1455,1459,1463,1468,1473,1477,1482,1487],{"__ignoreMap":371},[441,1412,1413],{"class":443,"line":444},[441,1414,1415],{},"# sincronizacao_incremental.py — Polling com watermark\n",[441,1417,1418],{"class":443,"line":451},[441,1419,1420],{},"def sincronizar_incremental():\n",[441,1422,1423],{"class":443,"line":462},[441,1424,1425],{},"    # last_watermark = timestamp da última sincronização (salvo em arquivo\u002Fbanco)\n",[441,1427,1428],{"class":443,"line":470},[441,1429,1430],{},"    last_watermark = carregar_ultimo_watermark()\n",[441,1432,1433],{"class":443,"line":483},[441,1434,583],{"emptyLinePlaceholder":582},[441,1436,1437],{"class":443,"line":491},[441,1438,1439],{},"    # Busca apenas registros modificados após o último watermark\n",[441,1441,1442],{"class":443,"line":505},[441,1443,1444],{},"    novos_pedidos = erp_db.execute(\n",[441,1446,1447],{"class":443,"line":518},[441,1448,1449],{},"        \"SELECT * FROM TBL_PEDIDO WHERE updated_at > ? ORDER BY updated_at\",\n",[441,1451,1452],{"class":443,"line":531},[441,1453,1454],{},"        [last_watermark]\n",[441,1456,1457],{"class":443,"line":544},[441,1458,961],{},[441,1460,1461],{"class":443,"line":557},[441,1462,583],{"emptyLinePlaceholder":582},[441,1464,1465],{"class":443,"line":568},[441,1466,1467],{},"    for pedido in novos_pedidos:\n",[441,1469,1470],{"class":443,"line":579},[441,1471,1472],{},"        on_erp_change(\"pedido\", \"updated\", pedido)\n",[441,1474,1475],{"class":443,"line":586},[441,1476,583],{"emptyLinePlaceholder":582},[441,1478,1479],{"class":443,"line":594},[441,1480,1481],{},"    # Atualiza o watermark para o timestamp do último registro processado\n",[441,1483,1484],{"class":443,"line":604},[441,1485,1486],{},"    if novos_pedidos:\n",[441,1488,1489],{"class":443,"line":611},[441,1490,1491],{},"        salvar_watermark(novos_pedidos[-1][\"updated_at\"])\n",[100,1493,1494],{},[19,1495,1496,1499,1500,1503],{},[88,1497,1498],{},"Abordagem 2 — CDC (Change Data Capture)",": usa ",[88,1501,1502],{},"Debezium + Kafka Connect","\npara capturar mudanças em tempo real, sem polling. Mais eficiente, mas mais\ncomplexo de configurar.",[145,1505,1506],{},[85,1507,1508,1510],{},[88,1509,700],{},": Fazer polling a cada 1 segundo. Isso sobrecarrega o ERP com\nconsultas que 99% das vezes não retornam nada. Use intervalos de 1-5\nminutos, ou melhor, use CDC\u002Fwebhooks.",[11,1512,1514],{"id":1513},"passo-4-webhooks-para-tempo-real-passo-4","Passo 4: Webhooks para tempo real {#passo-4}",[85,1516,1517,1518,1521],{},"Para notificações em ",[88,1519,1520],{},"tempo real"," (sem precisar de polling), use webhooks.\nO sistema moderno expõe um endpoint que o ERP (ou o middleware) chama quando\nalgo acontece.",[363,1523,1527],{"className":1524,"code":1525,"language":1526,"meta":371,"style":371},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F Node.js — endpoint que recebe webhook do sistema moderno\napp.post('\u002Fwebhooks\u002Ferp', async (req, res) => {\n  const { event, entity, id, data } = req.body;\n\n  \u002F\u002F 1. VALIDAR ASSINATURA (segurança obrigatória!)\n  \u002F\u002F Verifica que o webhook realmente veio de quem diz ter vindo\n  \u002F\u002F usando HMAC-SHA256 (uma assinatura criptográfica)\n  if (!verifySignature(req.headers['x-signature'], req.body)) {\n    return res.status(401).send('Invalid signature');\n  }\n\n  \u002F\u002F 2. COLOCAR NA FILA PARA PROCESSAMENTO ASSÍNCRRONO\n  \u002F\u002F Não processa aqui — responde 200 rápido e processa depois\n  await queue.add('erp_event', { event, entity, id, data });\n\n  \u002F\u002F 3. RESPONDER 200 IMEDIATAMENTE\n  \u002F\u002F Não faz o ERP esperar o processamento terminar\n  res.status(200).send('OK');\n});\n","javascript",[227,1528,1529,1534,1539,1544,1548,1553,1558,1563,1568,1573,1578,1582,1587,1592,1597,1601,1606,1611,1616],{"__ignoreMap":371},[441,1530,1531],{"class":443,"line":444},[441,1532,1533],{},"\u002F\u002F Node.js — endpoint que recebe webhook do sistema moderno\n",[441,1535,1536],{"class":443,"line":451},[441,1537,1538],{},"app.post('\u002Fwebhooks\u002Ferp', async (req, res) => {\n",[441,1540,1541],{"class":443,"line":462},[441,1542,1543],{},"  const { event, entity, id, data } = req.body;\n",[441,1545,1546],{"class":443,"line":470},[441,1547,583],{"emptyLinePlaceholder":582},[441,1549,1550],{"class":443,"line":483},[441,1551,1552],{},"  \u002F\u002F 1. VALIDAR ASSINATURA (segurança obrigatória!)\n",[441,1554,1555],{"class":443,"line":491},[441,1556,1557],{},"  \u002F\u002F Verifica que o webhook realmente veio de quem diz ter vindo\n",[441,1559,1560],{"class":443,"line":505},[441,1561,1562],{},"  \u002F\u002F usando HMAC-SHA256 (uma assinatura criptográfica)\n",[441,1564,1565],{"class":443,"line":518},[441,1566,1567],{},"  if (!verifySignature(req.headers['x-signature'], req.body)) {\n",[441,1569,1570],{"class":443,"line":531},[441,1571,1572],{},"    return res.status(401).send('Invalid signature');\n",[441,1574,1575],{"class":443,"line":544},[441,1576,1577],{},"  }\n",[441,1579,1580],{"class":443,"line":557},[441,1581,583],{"emptyLinePlaceholder":582},[441,1583,1584],{"class":443,"line":568},[441,1585,1586],{},"  \u002F\u002F 2. COLOCAR NA FILA PARA PROCESSAMENTO ASSÍNCRRONO\n",[441,1588,1589],{"class":443,"line":579},[441,1590,1591],{},"  \u002F\u002F Não processa aqui — responde 200 rápido e processa depois\n",[441,1593,1594],{"class":443,"line":586},[441,1595,1596],{},"  await queue.add('erp_event', { event, entity, id, data });\n",[441,1598,1599],{"class":443,"line":594},[441,1600,583],{"emptyLinePlaceholder":582},[441,1602,1603],{"class":443,"line":604},[441,1604,1605],{},"  \u002F\u002F 3. RESPONDER 200 IMEDIATAMENTE\n",[441,1607,1608],{"class":443,"line":611},[441,1609,1610],{},"  \u002F\u002F Não faz o ERP esperar o processamento terminar\n",[441,1612,1613],{"class":443,"line":623},[441,1614,1615],{},"  res.status(200).send('OK');\n",[441,1617,1618],{"class":443,"line":636},[441,1619,1620],{},"});\n",[145,1622,1623],{},[85,1624,1625,1628],{},[88,1626,1627],{},"Analogia do webhook",": O webhook é como um interfone. Quando alguém toca,\nvocê responde \"oi, recebido\" imediatamente (HTTP 200), e depois vai abrir\no portão (processar). Você não faz a pessoa esperar no interfone enquanto\nvocê caminha até o portão.",[85,1630,1631],{},[88,1632,1633],{},"Padrões essenciais de webhook:",[100,1635,1636,1642,1648,1654],{},[19,1637,1638,1641],{},[88,1639,1640],{},"Sempre responder 200 em menos de 5 segundos",": não bloquear quem chama.\nSe precisar processar algo demorado, coloque em fila e responda 200.",[19,1643,1644,1647],{},[88,1645,1646],{},"Assinar payloads com HMAC-SHA256",": para garantir que o webhook veio\nde quem diz ter vindo (não de um atacante fingindo ser o ERP)",[19,1649,1650,1653],{},[88,1651,1652],{},"Implementar retry exponencial no envio",": se o receptor não responder\n200, tentar novamente em 1min, 5min, 30min, 2h...",[19,1655,1656,1658],{},[88,1657,330],{},": mesmo evento enviado 2x não deve criar duplicata",[145,1660,1661],{},[85,1662,1663,1665],{},[88,1664,700],{},": Processar o webhook síncronamente. Se o processamento\ndemora 10 segundos e o ERP tem um timeout de 5 segundos, o ERP acha que\nfalhou e reenvia. Você processa 2 vezes. Solução: responder 200\nimediatamente e processar em background.",[11,1667,1669],{"id":1668},"passo-5-observabilidade-passo-5","Passo 5: Observabilidade {#passo-5}",[85,1671,1672,1675],{},[88,1673,1674],{},"Observabilidade"," é a capacidade de saber o que está acontecendo com sua\nintegração sem precisar \"adivinhar\". Integração sem observabilidade é caos\nem produção — você só descobre que parou quando alguém reclama.",[145,1677,1678],{},[85,1679,1680,1682],{},[88,1681,151],{},": Observabilidade é como o painel do carro. Sem ele, você só\ndescobre que acabou a gasolina quando o carro para no meio da estrada.\nCom o painel, você vê o marcador e abastece antes de ficar sem combustível.",[176,1684,1686],{"id":1685},"_1-logging-estruturado","1. Logging estruturado",[85,1688,1689,1690,1693],{},"Cada mensagem deve ser logada com um ",[88,1691,1692],{},"correlation ID"," (ID de correlação)\nque permite rastrear uma mensagem do início ao fim:",[363,1695,1699],{"className":1696,"code":1697,"language":1698,"meta":371,"style":371},"language-json shiki shiki-themes github-light github-dark","{\n  \"correlation_id\": \"pedido-12345-created\",\n  \"entity\": \"pedido\",\n  \"action\": \"created\",\n  \"status\": \"success\",\n  \"latency_ms\": 142,\n  \"timestamp\": \"2026-08-26T10:30:00Z\",\n  \"source\": \"erp_adapter\",\n  \"target\": \"api_moderna\",\n  \"message_size_bytes\": 1240\n}\n","json",[227,1700,1701,1706,1720,1732,1744,1756,1768,1780,1792,1804,1814],{"__ignoreMap":371},[441,1702,1703],{"class":443,"line":444},[441,1704,1705],{"class":458},"{\n",[441,1707,1708,1712,1714,1717],{"class":443,"line":451},[441,1709,1711],{"class":1710},"sj4cs","  \"correlation_id\"",[441,1713,476],{"class":458},[441,1715,1716],{"class":479},"\"pedido-12345-created\"",[441,1718,1719],{"class":458},",\n",[441,1721,1722,1725,1727,1730],{"class":443,"line":462},[441,1723,1724],{"class":1710},"  \"entity\"",[441,1726,476],{"class":458},[441,1728,1729],{"class":479},"\"pedido\"",[441,1731,1719],{"class":458},[441,1733,1734,1737,1739,1742],{"class":443,"line":470},[441,1735,1736],{"class":1710},"  \"action\"",[441,1738,476],{"class":458},[441,1740,1741],{"class":479},"\"created\"",[441,1743,1719],{"class":458},[441,1745,1746,1749,1751,1754],{"class":443,"line":483},[441,1747,1748],{"class":1710},"  \"status\"",[441,1750,476],{"class":458},[441,1752,1753],{"class":479},"\"success\"",[441,1755,1719],{"class":458},[441,1757,1758,1761,1763,1766],{"class":443,"line":491},[441,1759,1760],{"class":1710},"  \"latency_ms\"",[441,1762,476],{"class":458},[441,1764,1765],{"class":1710},"142",[441,1767,1719],{"class":458},[441,1769,1770,1773,1775,1778],{"class":443,"line":505},[441,1771,1772],{"class":1710},"  \"timestamp\"",[441,1774,476],{"class":458},[441,1776,1777],{"class":479},"\"2026-08-26T10:30:00Z\"",[441,1779,1719],{"class":458},[441,1781,1782,1785,1787,1790],{"class":443,"line":518},[441,1783,1784],{"class":1710},"  \"source\"",[441,1786,476],{"class":458},[441,1788,1789],{"class":479},"\"erp_adapter\"",[441,1791,1719],{"class":458},[441,1793,1794,1797,1799,1802],{"class":443,"line":531},[441,1795,1796],{"class":1710},"  \"target\"",[441,1798,476],{"class":458},[441,1800,1801],{"class":479},"\"api_moderna\"",[441,1803,1719],{"class":458},[441,1805,1806,1809,1811],{"class":443,"line":544},[441,1807,1808],{"class":1710},"  \"message_size_bytes\"",[441,1810,476],{"class":458},[441,1812,1813],{"class":1710},"1240\n",[441,1815,1816],{"class":443,"line":557},[441,1817,1818],{"class":458},"}\n",[145,1820,1821],{},[85,1822,1823,1826],{},[88,1824,1825],{},"Por que correlation ID?",": Imagine que um pedido falha no sistema moderno.\nCom o correlation ID, você busca no log e vê exatamente: quando o ERP gerou,\nquando entrou na fila, quando o consumer processou, onde falhou. Sem isso,\nvocê está caçando agulha em palheiro.",[176,1828,1830],{"id":1829},"_2-métricas-prometheus-grafana","2. Métricas (Prometheus + Grafana)",[85,1832,1833],{},"Métricas agregadas que permitem ver tendências e anomalias:",[100,1835,1836,1842,1848,1854],{},[19,1837,1838,1841],{},[227,1839,1840],{},"integration_messages_total{entity, action, status}"," — total de mensagens\nprocessadas, por entidade\u002Fação\u002Fstatus",[19,1843,1844,1847],{},[227,1845,1846],{},"integration_latency_seconds{entity}"," — tempo de processamento por entidade",[19,1849,1850,1853],{},[227,1851,1852],{},"integration_queue_depth"," — quantas mensagens estão na fila esperando",[19,1855,1856,1859],{},[227,1857,1858],{},"integration_error_rate"," — percentual de mensagens que falharam",[176,1861,1863],{"id":1862},"_3-alertas","3. Alertas",[85,1865,1866],{},"Falhas silenciosas são o pior cenário em integração. Configure alertas\nautomáticos:",[194,1868,1869,1882],{},[197,1870,1871],{},[200,1872,1873,1876,1879],{},[203,1874,1875],{},"Condição",[203,1877,1878],{},"Ação",[203,1880,1881],{},"Urgência",[213,1883,1884,1895,1906,1916],{},[200,1885,1886,1889,1892],{},[218,1887,1888],{},"Erro rate > 5%",[218,1890,1891],{},"Slack\u002FPagerDuty",[218,1893,1894],{},"Alta",[200,1896,1897,1900,1903],{},[218,1898,1899],{},"Queue depth > 1000 mensagens",[218,1901,1902],{},"Slack",[218,1904,1905],{},"Média",[200,1907,1908,1911,1914],{},[218,1909,1910],{},"Latência > 30s",[218,1912,1913],{},"Investigar",[218,1915,1905],{},[200,1917,1918,1921,1924],{},[218,1919,1920],{},"Nenhuma mensagem em 1h",[218,1922,1923],{},"Verificar ERP",[218,1925,1894],{},[176,1927,1929],{"id":1928},"_4-dead-letter-queue-dlq","4. Dead Letter Queue (DLQ)",[85,1931,1932,1933,1936],{},"Mensagens que falham N vezes (ex: 5 tentativas) vão para uma fila separada\nchamada ",[88,1934,1935],{},"DLQ"," (Dead Letter Queue, ou \"Fila de Cartas Mortas\"). Lá, elas\nnão bloqueiam a fila principal e podem ser analisadas manualmente.",[145,1938,1939],{},[85,1940,1941,1944],{},[88,1942,1943],{},"Analogia da DLQ",": É como a mesa de \"correspondência devolvida\" nos\ncorreios. Cartas que não puderam ser entregues (endereço errado, destinatário\nausente) vão para lá, onde um funcionário humano analisa e decide o que fazer.\nEnquanto isso, o fluxo normal de entregas continua.",[11,1946,1948],{"id":1947},"erros-comuns-erros","Erros comuns {#erros}",[176,1950,1952],{"id":1951},"erro-1-síncrono-em-vez-de-assíncrono","Erro 1: Síncrono em vez de assíncrono",[85,1954,1955,1958],{},[88,1956,1957],{},"O que acontece",": No trigger do ERP (que dispara quando um pedido é criado),\no código chama a API moderna diretamente e espera a resposta. Se a API\nmoderna cair, ficar lenta ou demorar, o ERP trava — o usuário não consegue\nsalvar o pedido.",[85,1960,1961,1964],{},[88,1962,1963],{},"O que NÃO fazer",":",[363,1966,1968],{"className":751,"code":1967,"language":753,"meta":371,"style":371},"# ❌ ERRADO: chamada síncrona no trigger do ERP\ndef on_pedido_created(pedido):\n    # Chama a API diretamente e espera a resposta\n    response = requests.post(\"https:\u002F\u002Fapi.moderna.com\u002Fpedidos\", json=pedido)\n    response.raise_for_status()  # se falhar, o ERP trava\n    # O usuário do ERP fica esperando isso terminar!\n",[227,1969,1970,1975,1980,1985,1990,1995],{"__ignoreMap":371},[441,1971,1972],{"class":443,"line":444},[441,1973,1974],{},"# ❌ ERRADO: chamada síncrona no trigger do ERP\n",[441,1976,1977],{"class":443,"line":451},[441,1978,1979],{},"def on_pedido_created(pedido):\n",[441,1981,1982],{"class":443,"line":462},[441,1983,1984],{},"    # Chama a API diretamente e espera a resposta\n",[441,1986,1987],{"class":443,"line":470},[441,1988,1989],{},"    response = requests.post(\"https:\u002F\u002Fapi.moderna.com\u002Fpedidos\", json=pedido)\n",[441,1991,1992],{"class":443,"line":483},[441,1993,1994],{},"    response.raise_for_status()  # se falhar, o ERP trava\n",[441,1996,1997],{"class":443,"line":491},[441,1998,1999],{},"    # O usuário do ERP fica esperando isso terminar!\n",[85,2001,2002,1964],{},[88,2003,2004],{},"O que fazer",[363,2006,2008],{"className":751,"code":2007,"language":753,"meta":371,"style":371},"# ✅ CERTO: publica na fila e retorna imediatamente\ndef on_pedido_created(pedido):\n    # Coloca na fila (microsegundos) e retorna\n    on_erp_change(\"pedido\", \"created\", pedido)\n    # O ERP continua funcionando normalmente\n    # A API moderna será chamada assíncronamente pelo consumer\n",[227,2009,2010,2015,2019,2024,2029,2034],{"__ignoreMap":371},[441,2011,2012],{"class":443,"line":444},[441,2013,2014],{},"# ✅ CERTO: publica na fila e retorna imediatamente\n",[441,2016,2017],{"class":443,"line":451},[441,2018,1979],{},[441,2020,2021],{"class":443,"line":462},[441,2022,2023],{},"    # Coloca na fila (microsegundos) e retorna\n",[441,2025,2026],{"class":443,"line":470},[441,2027,2028],{},"    on_erp_change(\"pedido\", \"created\", pedido)\n",[441,2030,2031],{"class":443,"line":483},[441,2032,2033],{},"    # O ERP continua funcionando normalmente\n",[441,2035,2036],{"class":443,"line":491},[441,2037,2038],{},"    # A API moderna será chamada assíncronamente pelo consumer\n",[176,2040,2042],{"id":2041},"erro-2-sem-idempotência","Erro 2: Sem idempotência",[85,2044,2045,2047,2048,2051],{},[88,2046,1957],{},": Uma mensagem é processada, mas o ",[227,2049,2050],{},"basic_ack"," não chega\nao RabbitMQ (problema de rede). O RabbitMQ reenvia a mensagem. O consumer\nprocessa de novo → cria um pedido duplicado no sistema moderno.",[85,2053,2054,1964],{},[88,2055,1963],{},[363,2057,2059],{"className":751,"code":2058,"language":753,"meta":371,"style":371},"# ❌ ERRADO: sem verificação de idempotência\ndef on_message(ch, method, properties, body):\n    message = json.loads(body)\n    # Processa direto, sem verificar se já processou\n    api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n    ch.basic_ack(method.delivery_tag)\n    # Se a mensagem chegar 2x, cria 2 registros!\n",[227,2060,2061,2066,2070,2074,2079,2084,2089],{"__ignoreMap":371},[441,2062,2063],{"class":443,"line":444},[441,2064,2065],{},"# ❌ ERRADO: sem verificação de idempotência\n",[441,2067,2068],{"class":443,"line":451},[441,2069,995],{},[441,2071,2072],{"class":443,"line":462},[441,2073,1022],{},[441,2075,2076],{"class":443,"line":470},[441,2077,2078],{},"    # Processa direto, sem verificar se já processou\n",[441,2080,2081],{"class":443,"line":483},[441,2082,2083],{},"    api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n",[441,2085,2086],{"class":443,"line":491},[441,2087,2088],{},"    ch.basic_ack(method.delivery_tag)\n",[441,2090,2091],{"class":443,"line":505},[441,2092,2093],{},"    # Se a mensagem chegar 2x, cria 2 registros!\n",[85,2095,2096,1964],{},[88,2097,2004],{},[363,2099,2101],{"className":751,"code":2100,"language":753,"meta":371,"style":371},"# ✅ CERTO: verificar idempotência antes de processar\ndef on_message(ch, method, properties, body):\n    message = json.loads(body)\n\n    # Verifica no banco se este ID já foi processado\n    if ja_processado(message[\"id\"]):\n        ch.basic_ack(method.delivery_tag)  # confirma e ignora\n        return\n\n    api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n    marcar_como_processado(message[\"id\"])  # registra no banco\n    ch.basic_ack(method.delivery_tag)\n",[227,2102,2103,2108,2112,2116,2120,2125,2130,2135,2139,2143,2147,2152],{"__ignoreMap":371},[441,2104,2105],{"class":443,"line":444},[441,2106,2107],{},"# ✅ CERTO: verificar idempotência antes de processar\n",[441,2109,2110],{"class":443,"line":451},[441,2111,995],{},[441,2113,2114],{"class":443,"line":462},[441,2115,1022],{},[441,2117,2118],{"class":443,"line":470},[441,2119,583],{"emptyLinePlaceholder":582},[441,2121,2122],{"class":443,"line":483},[441,2123,2124],{},"    # Verifica no banco se este ID já foi processado\n",[441,2126,2127],{"class":443,"line":491},[441,2128,2129],{},"    if ja_processado(message[\"id\"]):\n",[441,2131,2132],{"class":443,"line":505},[441,2133,2134],{},"        ch.basic_ack(method.delivery_tag)  # confirma e ignora\n",[441,2136,2137],{"class":443,"line":518},[441,2138,1061],{},[441,2140,2141],{"class":443,"line":531},[441,2142,583],{"emptyLinePlaceholder":582},[441,2144,2145],{"class":443,"line":544},[441,2146,2083],{},[441,2148,2149],{"class":443,"line":557},[441,2150,2151],{},"    marcar_como_processado(message[\"id\"])  # registra no banco\n",[441,2153,2154],{"class":443,"line":568},[441,2155,2088],{},[176,2157,2159],{"id":2158},"erro-3-polling-agressivo","Erro 3: Polling agressivo",[85,2161,2162,2164],{},[88,2163,1957],{},": Para detectar mudanças no ERP, o sistema consulta o banco\na cada 1 segundo. Para 99% das consultas, não há nada novo. Isso gera carga\ndesnecessária no banco do ERP, que já tem outros usuários usando.",[85,2166,2167,1964],{},[88,2168,1963],{},[363,2170,2172],{"className":751,"code":2171,"language":753,"meta":371,"style":371},"# ❌ ERRADO: polling a cada 1 segundo\nwhile True:\n    novos = erp_db.execute(\"SELECT * FROM TBL_PEDIDO WHERE updated_at > ?\", [last_check])\n    processar(novos)\n    time.sleep(1)  # muito agressivo!\n",[227,2173,2174,2179,2184,2189,2194],{"__ignoreMap":371},[441,2175,2176],{"class":443,"line":444},[441,2177,2178],{},"# ❌ ERRADO: polling a cada 1 segundo\n",[441,2180,2181],{"class":443,"line":451},[441,2182,2183],{},"while True:\n",[441,2185,2186],{"class":443,"line":462},[441,2187,2188],{},"    novos = erp_db.execute(\"SELECT * FROM TBL_PEDIDO WHERE updated_at > ?\", [last_check])\n",[441,2190,2191],{"class":443,"line":470},[441,2192,2193],{},"    processar(novos)\n",[441,2195,2196],{"class":443,"line":483},[441,2197,2198],{},"    time.sleep(1)  # muito agressivo!\n",[85,2200,2201,1964],{},[88,2202,2004],{},[363,2204,2206],{"className":751,"code":2205,"language":753,"meta":371,"style":371},"# ✅ CERTO: intervalo razoável (1-5 min) ou use CDC\n# Opção 1: polling a cada 2 minutos (aceitável para a maioria dos casos)\nwhile True:\n    novos = erp_db.execute(\"SELECT * FROM TBL_PEDIDO WHERE updated_at > ?\", [last_check])\n    processar(novos)\n    time.sleep(120)  # 2 minutos\n\n# Opção 2 (melhor): CDC com Debezium — captura mudanças em tempo real\n# sem polling. Configura uma vez e esquece.\n",[227,2207,2208,2213,2218,2222,2226,2230,2235,2239,2244],{"__ignoreMap":371},[441,2209,2210],{"class":443,"line":444},[441,2211,2212],{},"# ✅ CERTO: intervalo razoável (1-5 min) ou use CDC\n",[441,2214,2215],{"class":443,"line":451},[441,2216,2217],{},"# Opção 1: polling a cada 2 minutos (aceitável para a maioria dos casos)\n",[441,2219,2220],{"class":443,"line":462},[441,2221,2183],{},[441,2223,2224],{"class":443,"line":470},[441,2225,2188],{},[441,2227,2228],{"class":443,"line":483},[441,2229,2193],{},[441,2231,2232],{"class":443,"line":491},[441,2233,2234],{},"    time.sleep(120)  # 2 minutos\n",[441,2236,2237],{"class":443,"line":505},[441,2238,583],{"emptyLinePlaceholder":582},[441,2240,2241],{"class":443,"line":518},[441,2242,2243],{},"# Opção 2 (melhor): CDC com Debezium — captura mudanças em tempo real\n",[441,2245,2246],{"class":443,"line":531},[441,2247,2248],{},"# sem polling. Configura uma vez e esquece.\n",[176,2250,2252],{"id":2251},"erro-4-sem-tratamento-de-erro-mensagens-se-perdem","Erro 4: Sem tratamento de erro — mensagens se perdem",[85,2254,2255,2257,2258,2260],{},[88,2256,1957],{},": A API moderna retorna erro 500 (servidor indisponível).\nO código não trata o erro, a mensagem é confirmada (",[227,2259,2050],{},") e perdida.\nO pedido existe no ERP mas não no sistema moderno — inconsistência silenciosa.",[85,2262,2263,1964],{},[88,2264,1963],{},[363,2266,2268],{"className":751,"code":2267,"language":753,"meta":371,"style":371},"# ❌ ERRADO: confirma a mensagem mesmo se a API falhar\ndef on_message(ch, method, properties, body):\n    message = json.loads(body)\n    try:\n        api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n    except Exception:\n        pass  # ignora o erro!\n    ch.basic_ack(method.delivery_tag)  # confirma mesmo assim — mensagem perdida!\n",[227,2269,2270,2275,2279,2283,2288,2293,2298,2303],{"__ignoreMap":371},[441,2271,2272],{"class":443,"line":444},[441,2273,2274],{},"# ❌ ERRADO: confirma a mensagem mesmo se a API falhar\n",[441,2276,2277],{"class":443,"line":451},[441,2278,995],{},[441,2280,2281],{"class":443,"line":462},[441,2282,1022],{},[441,2284,2285],{"class":443,"line":470},[441,2286,2287],{},"    try:\n",[441,2289,2290],{"class":443,"line":483},[441,2291,2292],{},"        api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n",[441,2294,2295],{"class":443,"line":491},[441,2296,2297],{},"    except Exception:\n",[441,2299,2300],{"class":443,"line":505},[441,2301,2302],{},"        pass  # ignora o erro!\n",[441,2304,2305],{"class":443,"line":518},[441,2306,2307],{},"    ch.basic_ack(method.delivery_tag)  # confirma mesmo assim — mensagem perdida!\n",[85,2309,2310,1964],{},[88,2311,2004],{},[363,2313,2315],{"className":751,"code":2314,"language":753,"meta":371,"style":371},"# ✅ CERTO: rejeita a mensagem em caso de erro (volta para a fila)\ndef on_message(ch, method, properties, body):\n    message = json.loads(body)\n    try:\n        response = api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n        response.raise_for_status()  # lança exceção se status >= 400\n        ch.basic_ack(method.delivery_tag)\n    except Exception as e:\n        logger.error(f\"Falha ao processar {message['id']}: {e}\")\n        # Rejeita e coloca de volta na fila para retry\n        # Após N falhas, vai automaticamente para DLQ\n        ch.basic_nack(method.delivery_tag, requeue=True)\n",[227,2316,2317,2322,2326,2330,2334,2338,2343,2347,2352,2357,2362,2367],{"__ignoreMap":371},[441,2318,2319],{"class":443,"line":444},[441,2320,2321],{},"# ✅ CERTO: rejeita a mensagem em caso de erro (volta para a fila)\n",[441,2323,2324],{"class":443,"line":451},[441,2325,995],{},[441,2327,2328],{"class":443,"line":462},[441,2329,1022],{},[441,2331,2332],{"class":443,"line":470},[441,2333,2287],{},[441,2335,2336],{"class":443,"line":483},[441,2337,1090],{},[441,2339,2340],{"class":443,"line":491},[441,2341,2342],{},"        response.raise_for_status()  # lança exceção se status >= 400\n",[441,2344,2345],{"class":443,"line":505},[441,2346,1056],{},[441,2348,2349],{"class":443,"line":518},[441,2350,2351],{},"    except Exception as e:\n",[441,2353,2354],{"class":443,"line":531},[441,2355,2356],{},"        logger.error(f\"Falha ao processar {message['id']}: {e}\")\n",[441,2358,2359],{"class":443,"line":544},[441,2360,2361],{},"        # Rejeita e coloca de volta na fila para retry\n",[441,2363,2364],{"class":443,"line":557},[441,2365,2366],{},"        # Após N falhas, vai automaticamente para DLQ\n",[441,2368,2369],{"class":443,"line":568},[441,2370,1187],{},[176,2372,2374],{"id":2373},"erro-5-mapeamento-rígido-de-campos","Erro 5: Mapeamento rígido de campos",[85,2376,2377,2379,2380,2383],{},[88,2378,1957],{},": O ERP adiciona um campo novo na tabela de pedidos\n(ex: ",[227,2381,2382],{},"vl_desconto","). A integração espera um schema fixo e quebra ao\nencontrar o campo novo.",[85,2385,2386,1964],{},[88,2387,1963],{},[363,2389,2391],{"className":751,"code":2390,"language":753,"meta":371,"style":371},"# ❌ ERRADO: schema rígido que quebra com qualquer mudança\ndef transformar_pedido(erp_row):\n    return {\n        \"id\": erp_row[\"nr_pedido\"],          # se esse campo sumir, quebra\n        \"data\": erp_row[\"dt_emissao\"],        # se renomear, quebra\n        \"cliente\": erp_row[\"cd_cliente\"],     # se mudar tipo, quebra\n        \"total\": erp_row[\"vl_total\"],\n        # não há como receber campos novos sem mudar o código\n    }\n",[227,2392,2393,2398,2403,2408,2413,2418,2423,2428,2433],{"__ignoreMap":371},[441,2394,2395],{"class":443,"line":444},[441,2396,2397],{},"# ❌ ERRADO: schema rígido que quebra com qualquer mudança\n",[441,2399,2400],{"class":443,"line":451},[441,2401,2402],{},"def transformar_pedido(erp_row):\n",[441,2404,2405],{"class":443,"line":462},[441,2406,2407],{},"    return {\n",[441,2409,2410],{"class":443,"line":470},[441,2411,2412],{},"        \"id\": erp_row[\"nr_pedido\"],          # se esse campo sumir, quebra\n",[441,2414,2415],{"class":443,"line":483},[441,2416,2417],{},"        \"data\": erp_row[\"dt_emissao\"],        # se renomear, quebra\n",[441,2419,2420],{"class":443,"line":491},[441,2421,2422],{},"        \"cliente\": erp_row[\"cd_cliente\"],     # se mudar tipo, quebra\n",[441,2424,2425],{"class":443,"line":505},[441,2426,2427],{},"        \"total\": erp_row[\"vl_total\"],\n",[441,2429,2430],{"class":443,"line":518},[441,2431,2432],{},"        # não há como receber campos novos sem mudar o código\n",[441,2434,2435],{"class":443,"line":531},[441,2436,868],{},[85,2438,2439,1964],{},[88,2440,2004],{},[363,2442,2444],{"className":751,"code":2443,"language":753,"meta":371,"style":371},"# ✅ CERTO: schema flexível com campos opcionais\ndef transformar_pedido(erp_row):\n    result = {\n        \"id\": erp_row.get(\"nr_pedido\"),\n        \"data\": erp_row.get(\"dt_emissao\"),\n        \"cliente\": erp_row.get(\"cd_cliente\"),\n        \"total\": erp_row.get(\"vl_total\"),\n    }\n    # Campos extras do ERP são preservados (não quebra se ERP adicionar campos)\n    extras = {k: v for k, v in erp_row.items() if k not in result}\n    result[\"_extras\"] = extras  # campos não mapeados ficam aqui\n    return result\n",[227,2445,2446,2451,2455,2460,2465,2470,2475,2480,2484,2489,2494,2499],{"__ignoreMap":371},[441,2447,2448],{"class":443,"line":444},[441,2449,2450],{},"# ✅ CERTO: schema flexível com campos opcionais\n",[441,2452,2453],{"class":443,"line":451},[441,2454,2402],{},[441,2456,2457],{"class":443,"line":462},[441,2458,2459],{},"    result = {\n",[441,2461,2462],{"class":443,"line":470},[441,2463,2464],{},"        \"id\": erp_row.get(\"nr_pedido\"),\n",[441,2466,2467],{"class":443,"line":483},[441,2468,2469],{},"        \"data\": erp_row.get(\"dt_emissao\"),\n",[441,2471,2472],{"class":443,"line":491},[441,2473,2474],{},"        \"cliente\": erp_row.get(\"cd_cliente\"),\n",[441,2476,2477],{"class":443,"line":505},[441,2478,2479],{},"        \"total\": erp_row.get(\"vl_total\"),\n",[441,2481,2482],{"class":443,"line":518},[441,2483,868],{},[441,2485,2486],{"class":443,"line":531},[441,2487,2488],{},"    # Campos extras do ERP são preservados (não quebra se ERP adicionar campos)\n",[441,2490,2491],{"class":443,"line":544},[441,2492,2493],{},"    extras = {k: v for k, v in erp_row.items() if k not in result}\n",[441,2495,2496],{"class":443,"line":557},[441,2497,2498],{},"    result[\"_extras\"] = extras  # campos não mapeados ficam aqui\n",[441,2500,2501],{"class":443,"line":568},[441,2502,2503],{},"    return result\n",[176,2505,2507],{"id":2506},"erro-6-sem-monitoramento","Erro 6: Sem monitoramento",[85,2509,2510,2512],{},[88,2511,1957],{},": A integração para de funcionar (RabbitMQ reiniciou, API\nmudou versão, senha expirou). Ninguém percebe. O cliente liga reclamando que\n\"o sistema não está atualizando\". Você descobre o problema 3 dias depois,\ncom dados desatualizados.",[85,2514,2515,2517],{},[88,2516,2004],{},": Monitoramento proativo é obrigatório. Configure alertas\npara:",[100,2519,2520,2523,2526,2529],{},[19,2521,2522],{},"Erro rate > 5% → alerta imediato",[19,2524,2525],{},"Nenhuma mensagem processada em 1h → verificar se ERP\u002Fmiddleware está vivo",[19,2527,2528],{},"Queue depth crescendo → consumer pode estar travado",[19,2530,2531],{},"Latência aumentando → possível degradação",[11,2533,2535],{"id":2534},"caso-real-e-commerce-que-integrou-erp-em-6-semanas-caso-real","Caso real: e-commerce que integrou ERP em 6 semanas {#caso-real}",[85,2537,2538],{},"Um e-commerce de moda (faturamento R$ 8M\u002Fano) usava um ERP TOTVS e uma\nplataforma de e-commerce (VTEX). Os dados eram sincronizados manualmente:\num funcionário exportava CSVs do ERP e importava na VTEX, 3x ao dia.",[85,2540,2541,1964],{},[88,2542,2543],{},"Problemas",[100,2545,2546,2549,2552,2555],{},[19,2547,2548],{},"Estoque desatualizado: clientes compravam produtos sem estoque",[19,2550,2551],{},"Preços desincronizados: preço promocional na VTEX não refletia no ERP",[19,2553,2554],{},"1 funcionário full-time só fazendo export\u002Fimport manual",[19,2556,2557],{},"Erros frequentes: planilha errada, importação duplicada",[85,2559,2560,2563],{},[88,2561,2562],{},"Solução implementada"," (6 semanas):",[16,2565,2566,2572,2578,2584,2590],{},[19,2567,2568,2571],{},[88,2569,2570],{},"Semana 1-2",": Mapeamento de entidades (produto, estoque, preço, pedido)",[19,2573,2574,2577],{},[88,2575,2576],{},"Semana 2-3",": Adapter ERP lendo mudanças via trigger no SQL Server",[19,2579,2580,2583],{},[88,2581,2582],{},"Semana 3-4",": Middleware + RabbitMQ + consumer enviando para VTEX API",[19,2585,2586,2589],{},[88,2587,2588],{},"Semana 4-5",": Webhook da VTEX para enviar pedidos de volta ao ERP",[19,2591,2592,2595],{},[88,2593,2594],{},"Semana 5-6",": Observabilidade (logs, métricas, alertas) + testes",[85,2597,2598,1964],{},[88,2599,2600],{},"Resultados",[100,2602,2603,2606,2609,2612,2615],{},[19,2604,2605],{},"Sincronização em tempo real (\u003C 30 segundos do ERP → e-commerce)",[19,2607,2608],{},"Estoque sempre atualizado: vendas de produtos sem estoque: 0",[19,2610,2611],{},"1 funcionário realocado para tarefas de maior valor",[19,2613,2614],{},"Erros de sincronização: reduzidos em 98%",[19,2616,2617],{},"ROI em 2 meses (economia de salário + redução de chargebacks)",[85,2619,2620,2623],{},[88,2621,2622],{},"A lição",": A integração bem feita não é apenas técnica — ela resolve\nproblemas de negócio reais (estoque desatualizado = cliente frustrado =\nchargeback = prejuízo).",[2625,2626],"hr",{},[85,2628,2629,2632,2633,160],{},[88,2630,2631],{},"Precisa integrar seu ERP com sistemas modernos?"," A Inicialize Tec\nimplementa integrações robustas com middleware, filas e observabilidade.\n",[22,2634,2636],{"href":2635},"\u002F#top","Conversamos sobre seu caso",[2638,2639,2640],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":371,"searchDepth":462,"depth":462,"links":2642},[2643,2644,2645,2653,2654,2655,2658,2662,2663,2669,2677],{"id":13,"depth":451,"text":14},{"id":82,"depth":451,"text":83},{"id":170,"depth":451,"text":171,"children":2646},[2647,2648,2649,2650,2651,2652],{"id":178,"depth":462,"text":179},{"id":159,"depth":462,"text":275},{"id":288,"depth":462,"text":289},{"id":315,"depth":462,"text":316},{"id":329,"depth":462,"text":330},{"id":343,"depth":462,"text":344},{"id":360,"depth":451,"text":361},{"id":413,"depth":451,"text":414},{"id":707,"depth":451,"text":708,"children":2656},[2657],{"id":747,"depth":462,"text":748},{"id":1198,"depth":451,"text":1199,"children":2659},[2660,2661],{"id":1213,"depth":462,"text":1214},{"id":1389,"depth":462,"text":1390},{"id":1513,"depth":451,"text":1514},{"id":1668,"depth":451,"text":1669,"children":2664},[2665,2666,2667,2668],{"id":1685,"depth":462,"text":1686},{"id":1829,"depth":462,"text":1830},{"id":1862,"depth":462,"text":1863},{"id":1928,"depth":462,"text":1929},{"id":1947,"depth":451,"text":1948,"children":2670},[2671,2672,2673,2674,2675,2676],{"id":1951,"depth":462,"text":1952},{"id":2041,"depth":462,"text":2042},{"id":2158,"depth":462,"text":2159},{"id":2251,"depth":462,"text":2252},{"id":2373,"depth":462,"text":2374},{"id":2506,"depth":462,"text":2507},{"id":2534,"depth":451,"text":2535},"integracoes","2026-08-26","Como integrar sistemas legados (ERP) com APIs modernas. Middleware, filas, webhooks e observabilidade — com exemplos em Python e Node.js.","md",[2683,179,2684,159,2685,142,305],"integrar ERP","sistema legado","integração de sistemas",{},"\u002Fintegracoes\u002Fintegrar-erp-com-api-passo-a-passo",{"title":5,"description":2680},"integrar-erp-com-api-passo-a-passo","integracoes\u002Fintegrar-erp-com-api-passo-a-passo","9lodk1bqeQWoPPamyKdbr-BrPSQGh0sFoYukOlSYYfo",1787796309109]