[{"data":1,"prerenderedAt":31929},["ShallowReactive",2],{"blog":3},[4,1043,2931,4869,6883,8608,10072,11446,13716,16037,18972,20933,22807,24255,26807,28097,30752],{"id":5,"title":6,"author":7,"body":8,"category":1027,"date":1028,"description":1029,"extension":1030,"keywords":1031,"meta":1037,"navigation":898,"path":1038,"seo":1039,"slug":1040,"stem":1041,"updated":1028,"__hash__":1042},"content\u002Fautomacao\u002Fconsultoria-de-automacao-quando-contratar.md","Consultoria de automação: quando vale a pena contratar","Inicialize Tec",{"type":9,"value":10,"toc":980},"minimark",[11,16,81,85,93,100,107,111,114,164,275,279,282,287,298,302,309,313,320,324,327,331,334,338,342,345,349,352,356,359,363,366,370,373,444,448,451,455,458,462,465,469,472,476,479,483,486,490,493,497,505,516,523,527,560,564,581,585,588,706,709,718,722,726,750,754,774,778,782,785,789,792,796,799,803,810,814,817,821,883,887,967,970],[12,13,15],"h2",{"id":14},"índice","Índice",[17,18,19,27,33,39,45,51,57,63,69,75],"ol",{},[20,21,22],"li",{},[23,24,26],"a",{"href":25},"#o-que-e","O que é uma consultoria de automação",[20,28,29],{},[23,30,32],{"href":31},"#sinais","Sinais de que sua empresa precisa",[20,34,35],{},[23,36,38],{"href":37},"#o-que-esperar","O que esperar de uma consultoria de qualidade",[20,40,41],{},[23,42,44],{"href":43},"#tipos","Tipos de automação que uma consultoria cobre",[20,46,47],{},[23,48,50],{"href":49},"#processo","O processo típico de uma consultoria",[20,52,53],{},[23,54,56],{"href":55},"#roi","Como medir o ROI do projeto",[20,58,59],{},[23,60,62],{"href":61},"#escolher","Como escolher a consultoria certa",[20,64,65],{},[23,66,68],{"href":67},"#armadilhas","Armadilhas ao contratar",[20,70,71],{},[23,72,74],{"href":73},"#erros","Erros comuns",[20,76,77],{},[23,78,80],{"href":79},"#checklist","Checklist de decisão",[12,82,84],{"id":83},"o-que-é-uma-consultoria-de-automação-o-que-e","O que é uma consultoria de automação {#o-que-e}",[86,87,88,92],"p",{},[89,90,91],"strong",{},"Consultoria de automação"," é um serviço em que um time especializado ajuda\nsua empresa a identificar, projetar e implementar automações que substituem\ntrabalho manual repetitivo por sistemas que executam sozinhos. Pense num\npersonal organizer digital: alguém entra na sua casa (empresa), vê onde o\ntempo está sendo desperdiçado, propõe mudanças e ajuda a colocar tudo nos\ntrilhos — sem que você precise virar especialista em organização.",[86,94,95,96,99],{},"A diferença entre \"comprar uma ferramenta de automação\" e \"contratar uma\nconsultoria\" é parecida com a diferença entre comprar uma esteira de ginástica\ne contratar um personal trainer. A esteira sozinha costuma virar cabideiro.\nCom o personal, você aprende a usar direito, descobre quais exercícios valem\na pena para o seu corpo e mantém consistência. A consultoria entrega\n",[89,97,98],{},"resultado",", não ferramenta.",[86,101,102,103,106],{},"Boa parte do valor não está em escrever o código do bot ou do workflow — está\nem ",[89,104,105],{},"saber o que automatizar, o que não automatizar e em que ordem",". Errar\nnessa decisão custa caro: automatizar o processo errado é pagar para fazer\nmais rápido algo que não deveria ser feito daquele jeito.",[12,108,110],{"id":109},"sinais-de-que-sua-empresa-precisa-sinais","Sinais de que sua empresa precisa {#sinais}",[86,112,113],{},"Nem toda empresa precisa de consultoria de automação agora. Mas se você\nreconhece três ou mais sinais abaixo, vale considerar:",[17,115,116,122,128,134,140,146,152,158],{},[20,117,118,121],{},[89,119,120],{},"Pessoas passam horas copiando dados entre sistemas",": do portal do\ncliente para a planilha, da planilha para o ERP, do ERP para o e-mail.\nTudo na mão, todo dia, sempre igual.",[20,123,124,127],{},[89,125,126],{},"Volume de transações repetitivas cresceu, mas o time não",": as mesmas\n3 pessoas faziam 50 faturas\u002Fdia; agora são 500, e a única resposta foi\nfazer hora extra.",[20,129,130,133],{},[89,131,132],{},"Erros manuais recorrentes causam retrabalho",": digitação errada de\ncódigo, planilha enviada para o cliente errado, relatório com fórmula\nquebrada — e alguém tem que refazer.",[20,135,136,139],{},[89,137,138],{},"Processos importantes travam quando uma pessoa falta",": o único que\nsabe rodar aquele relatório ficou doente; o fechamento do mês atrasou.",[20,141,142,145],{},[89,143,144],{},"Integrações entre sistemas viraram \"colcha de retalhos\"",": scripts\nsoltos em várias máquinas, ninguém sabe qual está rodando, e qualquer\nmudança quebra algo invisível.",[20,147,148,151],{},[89,149,150],{},"Custo de mão de obra em tarefas mecânicas está alto",": o salário de\npessoas qualificadas está sendo gasto em copiar\u002Fcolar, não em pensar.",[20,153,154,157],{},[89,155,156],{},"Concorrência entrega mais rápido e mais barato",": você suspeita que\neles automatizaram o que você ainda faz na mão.",[20,159,160,163],{},[89,161,162],{},"Compliance exige rastreabilidade que o manual não dá",": auditor pede\nquem alterou o quê e quando; no processo manual, a resposta é \"anotação\nno caderno\".",[165,166,167,183],"table",{},[168,169,170],"thead",{},[171,172,173,177,180],"tr",{},[174,175,176],"th",{},"Sinal",[174,178,179],{},"Impacto típico",[174,181,182],{},"Automação que resolve",[184,185,186,198,209,220,231,242,253,264],"tbody",{},[171,187,188,192,195],{},[189,190,191],"td",{},"Cópia entre sistemas",[189,193,194],{},"Horas\u002Fdia perdidas",[189,196,197],{},"RPA ou integração via API",[171,199,200,203,206],{},[189,201,202],{},"Volume cresceu, time não",[189,204,205],{},"Hora extra, erros",[189,207,208],{},"RPA + fila",[171,210,211,214,217],{},[189,212,213],{},"Erros manuais recorrentes",[189,215,216],{},"Retrabalho, clientes irritados",[189,218,219],{},"Validação automática",[171,221,222,225,228],{},[189,223,224],{},"Processo travou na ausência",[189,226,227],{},"Risco operacional",[189,229,230],{},"Bot agendado",[171,232,233,236,239],{},[189,234,235],{},"Colcha de scripts",[189,237,238],{},"Manutenção caótica",[189,240,241],{},"Orquestração central",[171,243,244,247,250],{},[189,245,246],{},"Custo alto em tarefas mecânicas",[189,248,249],{},"Margem comprimida",[189,251,252],{},"Automação ponta-a-ponta",[171,254,255,258,261],{},[189,256,257],{},"Concorrência mais rápida",[189,259,260],{},"Perda de mercado",[189,262,263],{},"Pipeline automatizado",[171,265,266,269,272],{},[189,267,268],{},"Falta de rastreabilidade",[189,270,271],{},"Risco de auditoria",[189,273,274],{},"Logs automáticos",[12,276,278],{"id":277},"o-que-esperar-de-uma-consultoria-de-qualidade-o-que-esperar","O que esperar de uma consultoria de qualidade {#o-que-esperar}",[86,280,281],{},"Uma consultoria séria não chega empurrando uma ferramenta. Chega fazendo\nperguntas. Você deve esperar:",[283,284,286],"h3",{"id":285},"diagnóstico-antes-de-solução","Diagnóstico antes de solução",[86,288,289,290,293,294,297],{},"A primeira entrega deve ser um ",[89,291,292],{},"mapeamento de processos"," com candidatos a\nautomação priorizados por ",[89,295,296],{},"impacto × esforço",". Se a consultoria pula direto\npara \"vamos instalar o UiPath\", desconfie. O diagnóstico responde: onde estão\nas horas perdidas? quais processos são estáveis o suficiente para automatizar?\nquais vão quebrar a automação porque mudam toda semana?",[283,299,301],{"id":300},"prova-de-valor-pequena-primeiro","Prova de valor pequena primeiro",[86,303,304,305,308],{},"Uma boa consultoria propõe um ",[89,306,307],{},"piloto enxuto",": automatizar um processo,\nmedir resultado real em 2-4 semanas, e só então decidir escalar. Isso reduz\nrisco e mostra ROI cedo. Consultoria que quer contrato de 12 meses antes de\nmostrar qualquer resultado é sinal de alerta.",[283,310,312],{"id":311},"transferência-de-conhecimento","Transferência de conhecimento",[86,314,315,316,319],{},"O objetivo de uma consultoria de qualidade é ",[89,317,318],{},"sair",", não se eternizar.\nEla deve treinar seu time, documentar o que foi feito e deixar sua empresa\ncapaz de manter e evoluir as automações. Se a estratégia for criar\ndependência, é consultoria de \"caixa eletrônico\", não de automação.",[283,321,323],{"id":322},"métricas-claras-desde-o-início","Métricas claras desde o início",[86,325,326],{},"Antes de começar, deve haver um acordo de quais métricas vão medir sucesso:\nhoras economizadas, erros reduzidos, tempo de ciclo, custo por transação.\nSem métrica combinada, \"deu certo\" vira opinião.",[283,328,330],{"id":329},"plano-de-evolução","Plano de evolução",[86,332,333],{},"Automação não é um entregável que se faz e esquece. Processos mudam,\nsistemas mudam, volume muda. A consultoria deve deixar um plano de\nmanutenção, monitoramento e melhorias contínuas.",[12,335,337],{"id":336},"tipos-de-automação-que-uma-consultoria-cobre-tipos","Tipos de automação que uma consultoria cobre {#tipos}",[283,339,341],{"id":340},"rpa-robotic-process-automation","RPA (Robotic Process Automation)",[86,343,344],{},"Bots que \"clicam\" em interfaces como um humano. Para sistemas legados sem\nAPI. Bom para volume alto de tarefas idênticas em interface fixa.",[283,346,348],{"id":347},"integração-via-api-mensageria","Integração via API \u002F mensageria",[86,350,351],{},"Conectar sistemas que têm API para que troquem dados automaticamente, sem\nintervenção. Mais robusto que RPA quando existe API.",[283,353,355],{"id":354},"workflows-de-aprovação-e-orquestração","Workflows de aprovação e orquestração",[86,357,358],{},"Automatizar fluxos com etapas, aprovações e notificações (n8n, Zapier,\nPower Automate, Camunda). Para processos que misturam humano e máquina.",[283,360,362],{"id":361},"automação-com-ia","Automação com IA",[86,364,365],{},"Extrair dados de documentos não-estruturados (PDFs, e-mails), classificar\nintenções, tomar decisões simples. Combina com RPA para hyperautomation.",[283,367,369],{"id":368},"pipelines-de-cicd-e-devops","Pipelines de CI\u002FCD e DevOps",[86,371,372],{},"Automatizar o próprio ciclo de entrega de software — deploy, testes,\nrollback. Automação da automação.",[165,374,375,388],{},[168,376,377],{},[171,378,379,382,385],{},[174,380,381],{},"Tipo",[174,383,384],{},"Quando a consultoria recomenda",[174,386,387],{},"Tempo típico de ROI",[184,389,390,401,412,423,433],{},[171,391,392,395,398],{},[189,393,394],{},"RPA",[189,396,397],{},"Sistema legado sem API, tarefa idêntica",[189,399,400],{},"3-6 meses",[171,402,403,406,409],{},[189,404,405],{},"Integração via API",[189,407,408],{},"Sistemas modernos com API disponível",[189,410,411],{},"2-4 meses",[171,413,414,417,420],{},[189,415,416],{},"Workflow\u002Forquestração",[189,418,419],{},"Processo com etapas e aprovações",[189,421,422],{},"1-3 meses",[171,424,425,427,430],{},[189,426,362],{},[189,428,429],{},"Dados não-estruturados, decisão simples",[189,431,432],{},"4-8 meses",[171,434,435,438,441],{},[189,436,437],{},"CI\u002FCD \u002F DevOps",[189,439,440],{},"Time de dev travando entregas",[189,442,443],{},"3-9 meses",[12,445,447],{"id":446},"o-processo-típico-de-uma-consultoria-processo","O processo típico de uma consultoria {#processo}",[86,449,450],{},"Um projeto bem conduzido segue um ciclo previsível:",[283,452,454],{"id":453},"_1-descoberta-1-2-semanas","1. Descoberta (1-2 semanas)",[86,456,457],{},"Entrevistas com quem executa o processo, observação in loco, levantamento de\nsistemas, volume, frequência e dor. Saída: mapa de processos atual.",[283,459,461],{"id":460},"_2-priorização-1-semana","2. Priorização (1 semana)",[86,463,464],{},"Matriz impacto × esforço. Seleciona-se 1-3 processos piloto com alto impacto\ne baixo esforço. Saída: roadmap com os primeiros candidatos.",[283,466,468],{"id":467},"_3-prova-de-valor-2-4-semanas","3. Prova de valor (2-4 semanas)",[86,470,471],{},"Automatiza-se o piloto, roda-se em paralelo com o processo manual, mede-se\nresultado. Saída: relatório de ROI real do piloto.",[283,473,475],{"id":474},"_4-decisão-de-escala-ponto-de-gono-go","4. Decisão de escala (ponto de go\u002Fno-go)",[86,477,478],{},"Com o resultado do piloto na mão, decide-se se expande, ajusta ou para. Sem\npressão contratual para continuar se o ROI não aparecer.",[283,480,482],{"id":481},"_5-implementação-em-escala-2-6-meses","5. Implementação em escala (2-6 meses)",[86,484,485],{},"Automatiza os processos seguintes por ordem de prioridade. Integra\nmonitoramento, alertas e dashboards.",[283,487,489],{"id":488},"_6-transferência-e-sustentação-1-2-meses","6. Transferência e sustentação (1-2 meses)",[86,491,492],{},"Treina o time interno, documenta, cria runbook de incidentes e plano de\nmanutenção. A consultoria reduz presença até sair.",[12,494,496],{"id":495},"como-medir-o-roi-do-projeto-roi","Como medir o ROI do projeto {#roi}",[86,498,499,500,504],{},"ROI (",[501,502,503],"em",{},"Return on Investment",", retorno sobre investimento) é a métrica que diz\nse o projeto se pagou. A fórmula básica:",[506,507,512],"pre",{"className":508,"code":510,"language":511},[509],"language-text","ROI (%) = (Ganho obtido − Custo do projeto) \u002F Custo do projeto × 100\n","text",[513,514,510],"code",{"__ignoreMap":515},"",[86,517,518,519,522],{},"Mas o segredo está em ",[89,520,521],{},"medir ganho e custo de forma honesta",".",[283,524,526],{"id":525},"ganhos-que-contam","Ganhos que contam",[528,529,530,536,542,548,554],"ul",{},[20,531,532,535],{},[89,533,534],{},"Horas economizadas × valor-hora",": o mais óbvio. Mas só conta se a hora\neconomizada for realmente realocada ou se eliminar hora extra\u002Ffolga.",[20,537,538,541],{},[89,539,540],{},"Erros evitados × custo do erro",": um erro de digitação que custava R$ 500\nde retrabalho, 20 vezes\u002Fmês = R$ 10.000\u002Fmês.",[20,543,544,547],{},[89,545,546],{},"Velocidade de ciclo → receita adicional",": pedido processado em 1h em vez\nde 24h pode significar mais conversões. Mais difícil de medir, mas real.",[20,549,550,553],{},[89,551,552],{},"Capacidade adicional sem contratar",": se o volume dobrou e você não\nprecisou contratar 2 pessoas, o salário delas é ganho.",[20,555,556,559],{},[89,557,558],{},"Redução de risco\u002Fauditoria",": multas evitadas, retrabalho de compliance.",[283,561,563],{"id":562},"custos-que-pesam","Custos que pesam",[528,565,566,569,572,575,578],{},[20,567,568],{},"Honorários da consultoria.",[20,570,571],{},"Licenças de ferramentas (RPA, orquestração, IA).",[20,573,574],{},"Infraestrutura (servidores para bots, cloud para integrações).",[20,576,577],{},"Tempo do seu time em entrevistas, validação e manutenção.",[20,579,580],{},"Retreinamento de pessoas cujo trabalho foi automatizado.",[283,582,584],{"id":583},"exemplo-realista","Exemplo realista",[86,586,587],{},"Cenário: 3 pessoas processando 800 faturas\u002Fdia, 4h\u002Fdia cada. Erro de digitação\nem ~5% das faturas, custo médio de R$ 80 por erro.",[165,589,590,603],{},[168,591,592],{},[171,593,594,597,600],{},[174,595,596],{},"Item",[174,598,599],{},"Cálculo",[174,601,602],{},"Valor\u002Fmês",[184,604,605,616,627,641,652,662,676,691],{},[171,606,607,610,613],{},[189,608,609],{},"Horas economizadas",[189,611,612],{},"3 × 4h × 22 dias × R$ 50\u002Fh",[189,614,615],{},"R$ 13.200",[171,617,618,621,624],{},[189,619,620],{},"Erros evitados",[189,622,623],{},"800 × 5% × 22 × R$ 80",[189,625,626],{},"R$ 7.040",[171,628,629,634,636],{},[189,630,631],{},[89,632,633],{},"Ganho mensal",[189,635],{},[189,637,638],{},[89,639,640],{},"R$ 20.240",[171,642,643,646,649],{},[189,644,645],{},"Custo consultoria (amort. 6 meses)",[189,647,648],{},"R$ 60.000 \u002F 6",[189,650,651],{},"R$ 10.000",[171,653,654,657,659],{},[189,655,656],{},"Licenças + infra",[189,658],{},[189,660,661],{},"R$ 2.500",[171,663,664,669,671],{},[189,665,666],{},[89,667,668],{},"Custo mensal",[189,670],{},[189,672,673],{},[89,674,675],{},"R$ 12.500",[171,677,678,683,686],{},[189,679,680],{},[89,681,682],{},"ROI\u002Fmês",[189,684,685],{},"(20.240 − 12.500) \u002F 12.500",[189,687,688],{},[89,689,690],{},"+62%",[171,692,693,698,701],{},[189,694,695],{},[89,696,697],{},"Payback",[189,699,700],{},"60.000 \u002F 7.740 (líq\u002Fmês)",[189,702,703],{},[89,704,705],{},"~8 meses",[86,707,708],{},"Após o payback, o ganho líquido continua todo mês. Esse é o poder da\nautomação bem feita: o custo é pontual, o ganho é recorrente.",[710,711,712],"blockquote",{},[86,713,714,717],{},[89,715,716],{},"Cuidado com ROI inflado",": algumas consultorias apresentam ganho como\n\"horas economizadas × valor-hora\" sem checar se as horas foram realmente\nrealocadas. Horas economizadas que viram ócio não são ganho.",[12,719,721],{"id":720},"como-escolher-a-consultoria-certa-escolher","Como escolher a consultoria certa {#escolher}",[283,723,725],{"id":724},"sinais-verdes-procure","Sinais verdes (procure)",[528,727,728,731,734,741,744,747],{},[20,729,730],{},"Começa com diagnóstico e perguntas, não com proposta de ferramenta.",[20,732,733],{},"Propõe piloto pequeno com métricas antes de contrato longo.",[20,735,736,737,740],{},"Tem portfólio em ",[89,738,739],{},"seu setor"," (indústria, varejo, finanças, saúde).",[20,742,743],{},"Fala abertamente sobre manutenção e sobre sair do projeto.",[20,745,746],{},"Apresenta cases com números (horas economizadas, ROI, payback).",[20,748,749],{},"Tem expertise em mais de um tipo de automação (RPA, API, IA), não só na\nferramenta que ela revende.",[283,751,753],{"id":752},"sinais-vermelhos-fuja","Sinais vermelhos (fuja)",[528,755,756,759,762,765,768,771],{},[20,757,758],{},"\"Vamos automatizar tudo\" sem entender seu processo.",[20,760,761],{},"Contrato de 12 meses sem cláusula de saída nem piloto.",[20,763,764],{},"Só sabe usar uma ferramenta — e é justamente a que ela revende.",[20,766,767],{},"Não fala em métricas, transferência de conhecimento nem sustentação.",[20,769,770],{},"Cases sem números, só adjetivos (\"transformamos\", \"modernizamos\").",[20,772,773],{},"Promessa de ROI garantido em qualquer cenário.",[12,775,777],{"id":776},"armadilhas-ao-contratar-armadilhas","Armadilhas ao contratar {#armadilhas}",[283,779,781],{"id":780},"automatizar-o-processo-errado","Automatizar o processo errado",[86,783,784],{},"A consultoria automata o que você aponta, mas talvez o problema esteja em\noutro lugar. Um bom diagnóstico revela que o gargalo real não é o que você\n\"sentia\" ser. Automatizar o processo errado é o erro mais caro — você paga\npara acelerar ineficiência.",[283,786,788],{"id":787},"ignorar-o-impacto-nas-pessoas","Ignorar o impacto nas pessoas",[86,790,791],{},"Automação muda o trabalho de pessoas reais. Sem plano de comunicação,\nrequalificação e rota para quem perde a tarefa, o projeto encontra\nresistência silenciosa: os usuários \"esquecem\" de alimentar o bot, ou\nboicotam discretamente. Em projetos sérios, pessoas são parte do escopo.",[283,793,795],{"id":794},"subestimar-manutenção","Subestimar manutenção",[86,797,798],{},"Bot quebra quando a interface muda. Integração quebra quando a API muda.\nWorkflow quebra quando o processo muda. Sem plano de manutenção, em 6 meses\na automação vira \"mais uma coisa quebrada que ninguém toca\", e o ganho evapora.",[283,800,802],{"id":801},"métricas-de-vaidade","Métricas de vaidade",[86,804,805,806,809],{},"Dashboard bonito mostrando \"X bots rodando\" não diz se o negócio melhorou.\nMétrica que importa é ",[89,807,808],{},"horas economizadas, erros evitados, custo reduzido"," —\nnão contagem de automações.",[283,811,813],{"id":812},"fazer-e-largar","Fazer e largar",[86,815,816],{},"A consultoria implementa, vai embora, e ninguém na empresa sabe operar. Em\n3 meses, a automação está parada. Sem transferência de conhecimento, o\ninvestimento se perde.",[12,818,820],{"id":819},"erros-comuns-erros","Erros comuns {#erros}",[17,822,823,829,835,841,847,853,859,865,871,877],{},[20,824,825,828],{},[89,826,827],{},"Contratar pela ferramenta, não pela abordagem",": focar em \"quero UiPath\"\nem vez de \"quero reduzir custo de processamento\". A ferramenta é\nconsequência do diagnóstico, não o ponto de partida.",[20,830,831,834],{},[89,832,833],{},"Pular o diagnóstico",": ir direto para implementação. Resultado:\nautomatiza-se algo que muda toda semana e o bot vive quebrando.",[20,836,837,840],{},[89,838,839],{},"Piloto grande demais",": tentar provar valor com 5 processos ao mesmo\ntempo. Cada um falha por motivos diferentes, fica impossível medir.",[20,842,843,846],{},[89,844,845],{},"Métricas combinadas depois",": deixar para definir \"o que é sucesso\"\nno fim do projeto. Cada parte alega sucesso com critério próprio.",[20,848,849,852],{},[89,850,851],{},"Sem dono interno",": a consultoria não pode ser a única que entende o\nprojeto. Designe alguém do seu time como dono desde o dia 1.",[20,854,855,858],{},[89,856,857],{},"Ignorar processos instáveis",": automatizar processo que está sendo\nredesenhado. A automação nasce obsoleta.",[20,860,861,864],{},[89,862,863],{},"Achar que automação dispensa melhoria de processo",": automatizar um\nprocesso ruim faz o ruim mais rápido. Melhore o processo, depois automatize.",[20,866,867,870],{},[89,868,869],{},"Não planejar sustentação",": sem monitoramento, erro de bot só é\npercebido quando o cliente reclama — às vezes dias depois.",[20,872,873,876],{},[89,874,875],{},"Medir ROI só em horas",": esquecer erros evitados, capacidade extra,\nredução de risco. Subestima o valor real do projeto.",[20,878,879,882],{},[89,880,881],{},"Aceitar dependência eterna",": consultoria que não transfere\nconhecimento cria uma mordida perpétua. Contrate quem quer sair.",[12,884,886],{"id":885},"checklist-de-decisão-checklist","Checklist de decisão {#checklist}",[528,888,891,901,907,913,919,925,931,937,943,949,955,961],{"className":889},[890],"contains-task-list",[20,892,895,900],{"className":893},[894],"task-list-item",[896,897],"input",{"disabled":898,"type":899},true,"checkbox"," Você identificou 3+ sinais de que precisa de automação",[20,902,904,906],{"className":903},[894],[896,905],{"disabled":898,"type":899}," Há um dono interno com tempo dedicado ao projeto",[20,908,910,912],{"className":909},[894],[896,911],{"disabled":898,"type":899}," As métricas de sucesso estão definidas antes de contratar",[20,914,916,918],{"className":915},[894],[896,917],{"disabled":898,"type":899}," A consultoria propõe diagnóstico antes de propor solução",[20,920,922,924],{"className":921},[894],[896,923],{"disabled":898,"type":899}," Existe piloto pequeno (2-4 semanas) com ponto de go\u002Fno-go",[20,926,928,930],{"className":927},[894],[896,929],{"disabled":898,"type":899}," O contrato tem cláusula de saída e não amarra 12 meses sem saída",[20,932,934,936],{"className":933},[894],[896,935],{"disabled":898,"type":899}," Há plano de transferência de conhecimento e sustentação",[20,938,940,942],{"className":939},[894],[896,941],{"disabled":898,"type":899}," O impacto em pessoas foi mapeado e comunicado",[20,944,946,948],{"className":945},[894],[896,947],{"disabled":898,"type":899}," O custo total (consultoria + licenças + infra + tempo interno) está\nclaro no início",[20,950,952,954],{"className":951},[894],[896,953],{"disabled":898,"type":899}," O ROI é calculado com ganhos verificáveis, não com \"horas teóricas\"",[20,956,958,960],{"className":957},[894],[896,959],{"disabled":898,"type":899}," Há plano de manutenção e monitoramento das automações",[20,962,964,966],{"className":963},[894],[896,965],{"disabled":898,"type":899}," A consultoria tem cases com números no seu setor",[968,969],"hr",{},[86,971,972,975,976,522],{},[89,973,974],{},"Acha que sua empresa precisa automatizar, mas não sabe por onde começar?","\nA Inicialize Tec faz diagnóstico, piloto de prova de valor e implementação\ncom transferência de conhecimento — sem amarrar contrato. ",[23,977,979],{"href":978},"\u002F#top","Conversamos sobre\nseu caso",{"title":515,"searchDepth":981,"depth":981,"links":982},3,[983,985,986,987,994,1001,1009,1014,1018,1025,1026],{"id":14,"depth":984,"text":15},2,{"id":83,"depth":984,"text":84},{"id":109,"depth":984,"text":110},{"id":277,"depth":984,"text":278,"children":988},[989,990,991,992,993],{"id":285,"depth":981,"text":286},{"id":300,"depth":981,"text":301},{"id":311,"depth":981,"text":312},{"id":322,"depth":981,"text":323},{"id":329,"depth":981,"text":330},{"id":336,"depth":984,"text":337,"children":995},[996,997,998,999,1000],{"id":340,"depth":981,"text":341},{"id":347,"depth":981,"text":348},{"id":354,"depth":981,"text":355},{"id":361,"depth":981,"text":362},{"id":368,"depth":981,"text":369},{"id":446,"depth":984,"text":447,"children":1002},[1003,1004,1005,1006,1007,1008],{"id":453,"depth":981,"text":454},{"id":460,"depth":981,"text":461},{"id":467,"depth":981,"text":468},{"id":474,"depth":981,"text":475},{"id":481,"depth":981,"text":482},{"id":488,"depth":981,"text":489},{"id":495,"depth":984,"text":496,"children":1010},[1011,1012,1013],{"id":525,"depth":981,"text":526},{"id":562,"depth":981,"text":563},{"id":583,"depth":981,"text":584},{"id":720,"depth":984,"text":721,"children":1015},[1016,1017],{"id":724,"depth":981,"text":725},{"id":752,"depth":981,"text":753},{"id":776,"depth":984,"text":777,"children":1019},[1020,1021,1022,1023,1024],{"id":780,"depth":981,"text":781},{"id":787,"depth":981,"text":788},{"id":794,"depth":981,"text":795},{"id":801,"depth":981,"text":802},{"id":812,"depth":981,"text":813},{"id":819,"depth":984,"text":820},{"id":885,"depth":984,"text":886},"automacao","2026-08-26","Sinais de que sua empresa precisa de consultoria de automação, o que esperar de uma consultoria de qualidade e como medir o ROI do projeto.","md",[1032,1033,1034,1035,1036],"consultoria automação","RPA empresa","automação de processos","ROI automação","consultoria tecnologia",{},"\u002Fautomacao\u002Fconsultoria-de-automacao-quando-contratar",{"title":6,"description":1029},"consultoria-de-automacao-quando-contratar","automacao\u002Fconsultoria-de-automacao-quando-contratar","gt_fFRhCGnZYEdyWeuZEnEH7755QlUFzyJO3Mte2DdI",{"id":1044,"title":1045,"author":7,"body":1046,"category":1027,"date":1028,"description":2918,"extension":1030,"keywords":2919,"meta":2925,"navigation":898,"path":2926,"seo":2927,"slug":2928,"stem":2929,"updated":1028,"__hash__":2930},"content\u002Fautomacao\u002Fpipeline-ci-cd-escalavel.md","Pipeline de CI\u002FCD escalável: do commit ao deploy",{"type":9,"value":1047,"toc":2886},[1048,1050,1114,1118,1140,1143,1158,1165,1169,1172,1202,1205,1277,1281,1284,1290,1304,1308,1311,1316,1368,1463,1467,1470,1476,1480,1483,1487,1490,1494,1501,1505,1508,1528,1536,1540,1546,1550,1570,1574,1643,1647,1654,1796,1800,1806,1810,1838,1842,1845,1935,1939,1942,1946,1949,2020,2031,2035,2049,2053,2068,2072,2075,2079,2082,2086,2089,2695,2698,2729,2731,2792,2796,2871,2873,2882],[12,1049,15],{"id":14},[17,1051,1052,1057,1063,1069,1075,1081,1087,1093,1099,1105,1109],{},[20,1053,1054],{},[23,1055,1056],{"href":25},"O que é CI\u002FCD (em palavras simples)",[20,1058,1059],{},[23,1060,1062],{"href":1061},"#por-que-escalar","Por que um pipeline precisa \"escalar\"",[20,1064,1065],{},[23,1066,1068],{"href":1067},"#etapas","As etapas de um pipeline completo",[20,1070,1071],{},[23,1072,1074],{"href":1073},"#build","Build: a fundação do pipeline",[20,1076,1077],{},[23,1078,1080],{"href":1079},"#testes","Testes: a rede de segurança",[20,1082,1083],{},[23,1084,1086],{"href":1085},"#deploy","Deploy automático: do commit à produção",[20,1088,1089],{},[23,1090,1092],{"href":1091},"#rollback","Rollback: o botão \"desfazer\" profissional",[20,1094,1095],{},[23,1096,1098],{"href":1097},"#times-grandes","Estratégias para times grandes",[20,1100,1101],{},[23,1102,1104],{"href":1103},"#exemplo-github","Exemplo real com GitHub Actions",[20,1106,1107],{},[23,1108,74],{"href":73},[20,1110,1111],{},[23,1112,1113],{"href":79},"Checklist final",[12,1115,1117],{"id":1116},"o-que-é-cicd-em-palavras-simples-o-que-e","O que é CI\u002FCD (em palavras simples) {#o-que-e}",[86,1119,1120,1121,1124,1125,1128,1129,1124,1132,1135,1136,1139],{},"Antes de usar a sigla, vamos explicar o que ela significa. ",[89,1122,1123],{},"CI"," é ",[501,1126,1127],{},"Continuous\nIntegration"," (Integração Contínua) e ",[89,1130,1131],{},"CD",[501,1133,1134],{},"Continuous Delivery"," (Entrega\nContínua) ou ",[501,1137,1138],{},"Continuous Deployment"," (Deploy Contínuo), dependendo do nível de\nautomação que você deseja.",[86,1141,1142],{},"Imagine uma cozinha de restaurante. Sem CI, cada cozinheiro prepara seu prato\nsozinho, no seu canto, e só no final tudo é juntado — frequentemente os temperos\nnão combinam e ninguém percebeu até o cliente reclamar. Com CI, a cada passo\ntodo mundo prova a receita juntos e ajusta na hora. Com CD, o prato sai da\ncozinha para a mesa do cliente automaticamente, sem precisar de um garçom\nintermediário que confere tudo de novo.",[86,1144,1145,1146,1148,1149,1151,1152,1154,1155,1157],{},"Em software: ",[89,1147,1123],{}," é integrar o código de todas as pessoas desenvolvedoras\nvárias vezes ao dia em um branch compartilhado, rodando testes automaticamente\na cada integração. ",[89,1150,1131],{}," é garantir que esse código integrado esteja sempre\npronto para ir para produção — seja com um clique humano (",[501,1153,1134],{},")\nou sozinho (",[501,1156,1138],{},").",[86,1159,1160,1161,1164],{},"Um ",[89,1162,1163],{},"pipeline"," é a sequência automática de etapas que executa tudo isso. Pense\nnuma esteira de montagem de uma fábrica: a peça entra crus, passa por várias\nestações (solda, pintura, teste, embalagem) e sai pronta para entrega. Se\nqualquer estação detecta defeito, a peça é rejeitada e volta para correção.",[12,1166,1168],{"id":1167},"por-que-um-pipeline-precisa-escalar-por-que-escalar","Por que um pipeline precisa \"escalar\" {#por-que-escalar}",[86,1170,1171],{},"Um pipeline que funciona para 2 pessoas em um repositório quase sempre quebra\ncom 50 pessoas em 20 repositórios. Os sintomas clássicos:",[528,1173,1174,1184,1190,1196],{},[20,1175,1176,1179,1180,1183],{},[89,1177,1178],{},"Builds lentos",": o pipeline demora 40 minutos, o time para de esperar e\nfaz ",[501,1181,1182],{},"push"," direto na main (pulo do gato para o desastre).",[20,1185,1186,1189],{},[89,1187,1188],{},"Gargalo de infraestrutura",": só há 2 runners disponíveis, 10 PRs esperando\nna fila, ninguém revisa código porque está esperando CI.",[20,1191,1192,1195],{},[89,1193,1194],{},"Falsos negativos",": testes instáveis (\"flaky tests\") quebram aleatoriamente,\no time aprende a ignorar o CI vermelho, e bugs reais passam despercebidos.",[20,1197,1198,1201],{},[89,1199,1200],{},"Custo cloud explosivo",": cada PR roda testes E2E completos em 10 ambientes,\na fatura da AWS triplica sem ninguém perceber.",[86,1203,1204],{},"Um pipeline escalável é aquele que continua rápido, confiável e barato mesmo\nquando o time, o número de repositórios e a frequência de deploys crescem\n5x, 10x, 50x. Isso não acontece por acidente — é projeto.",[165,1206,1207,1220],{},[168,1208,1209],{},[171,1210,1211,1214,1217],{},[174,1212,1213],{},"Sintoma",[174,1215,1216],{},"Pipeline que NÃO escala",[174,1218,1219],{},"Pipeline que escala",[184,1221,1222,1233,1244,1255,1266],{},[171,1223,1224,1227,1230],{},[189,1225,1226],{},"Tempo de build",[189,1228,1229],{},"30-60 min, cresce linearmente",[189,1231,1232],{},"5-10 min, paralelizado",[171,1234,1235,1238,1241],{},[189,1236,1237],{},"Confiabilidade",[189,1239,1240],{},"80% verde, 20% flaky",[189,1242,1243],{},"99%+ verde, flaky = bloqueio",[171,1245,1246,1249,1252],{},[189,1247,1248],{},"Custo por PR",[189,1250,1251],{},"$5-15",[189,1253,1254],{},"$0.50-2",[171,1256,1257,1260,1263],{},[189,1258,1259],{},"Fila em horário pico",[189,1261,1262],{},"20+ min esperando runner",[189,1264,1265],{},"\u003C 2 min",[171,1267,1268,1271,1274],{},[189,1269,1270],{},"Onboarding de novo repo",[189,1272,1273],{},"2 dias de config",[189,1275,1276],{},"1 comando (template reutilizável)",[12,1278,1280],{"id":1279},"as-etapas-de-um-pipeline-completo-etapas","As etapas de um pipeline completo {#etapas}",[86,1282,1283],{},"Um pipeline moderno tem, no mínimo, estas estações:",[506,1285,1288],{"className":1286,"code":1287,"language":511},[509],"[Commit\u002FPR]\n    ↓\n1. Checkout & cache         ← baixar código e dependências cacheadas\n    ↓\n2. Lint & format check      ← estilo de código (rapidíssimo)\n    ↓\n3. Build                    ← compilar, empacotar, criar imagem Docker\n    ↓\n4. Testes unitários         ← isolados, rápidos (\u003C 1 min)\n    ↓\n5. Testes de integração     ← sobem dependências (banco, Redis) em containers\n    ↓\n6. Security scan            ← SAST, dependências vulneráveis, secrets leak\n    ↓\n7. Artefato + imagem        ← publicar em registry\n    ↓\n8. Deploy (staging)         ← ambiente de validação\n    ↓\n9. Testes E2E \u002F smoke       ← fluxo crítico em ambiente real\n    ↓\n10. Deploy (produção)       ← com estratégia + rollback automático\n",[513,1289,1287],{"__ignoreMap":515},[86,1291,1292,1293,1296,1297,1300,1301,522],{},"Cada etapa deve ser ",[89,1294,1295],{},"paralelizável"," quando possível e ",[89,1298,1299],{},"cachear"," tudo que\npuder. A regra de ouro: ",[89,1302,1303],{},"o que muda pouco deve ser reaproveitado, o que muda\nmuito deve rodar em paralelo",[12,1305,1307],{"id":1306},"build-a-fundação-do-pipeline-build","Build: a fundação do pipeline {#build}",[86,1309,1310],{},"O build é onde o código-fonte vira algo executável. Em linguagens compiladas\n(Go, Rust, Java) é a compilação; em linguagens interpretadas (Python, Node) é\na instalação de dependências + transpilação (TypeScript → JavaScript, por\nexemplo).",[86,1312,1313],{},[89,1314,1315],{},"Dicas de build escalável:",[17,1317,1318,1339,1349,1362],{},[20,1319,1320,1323,1324,1327,1328,1327,1331,1334,1335,1338],{},[89,1321,1322],{},"Cache de dependências",": reusar ",[513,1325,1326],{},"node_modules\u002F",", ",[513,1329,1330],{},"~\u002F.m2",[513,1332,1333],{},"~\u002F.cargo"," entre\nexecuções economiza minutos. GitHub Actions tem ",[513,1336,1337],{},"actions\u002Fcache"," nativo.",[20,1340,1341,1344,1345,1348],{},[89,1342,1343],{},"Build em container",": garantir que o build rode igual na máquina de\nqualquer pessoa e no CI. Um ",[513,1346,1347],{},"Dockerfile"," de build padroniza o ambiente.",[20,1350,1351,1354,1355,1327,1358,1361],{},[89,1352,1353],{},"Docker layer caching",": em imagens Docker, copie primeiro os arquivos de\ndependência (",[513,1356,1357],{},"package.json",[513,1359,1360],{},"go.mod",") e instale antes de copiar o código.\nAssim a layer de dependências fica em cache e só invalida quando muda.",[20,1363,1364,1367],{},[89,1365,1366],{},"Build matrix",": testar em múltiplas versões (Node 18 e 20, Python 3.10 e\n3.12) em paralelo, não em sequência.",[506,1369,1373],{"className":1370,"code":1371,"language":1372,"meta":515,"style":515},"language-dockerfile shiki shiki-themes github-light github-dark","# Dockerfile otimizado para cache de layers\nFROM node:20-alpine AS builder\nWORKDIR \u002Fapp\n\n# Copia só manifestos primeiro → layer de deps cacheada\nCOPY package*.json .\u002F\nRUN npm ci --omit=dev\n\n# Só agora copia o código → só esta layer invalida a cada commit\nCOPY . .\nRUN npm run build\n\n# Imagem final enxuta (multi-stage)\nFROM nginx:alpine\nCOPY --from=builder \u002Fapp\u002Fdist \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\n","dockerfile",[513,1374,1375,1383,1388,1393,1399,1405,1411,1417,1422,1428,1434,1440,1445,1451,1457],{"__ignoreMap":515},[1376,1377,1380],"span",{"class":1378,"line":1379},"line",1,[1376,1381,1382],{},"# Dockerfile otimizado para cache de layers\n",[1376,1384,1385],{"class":1378,"line":984},[1376,1386,1387],{},"FROM node:20-alpine AS builder\n",[1376,1389,1390],{"class":1378,"line":981},[1376,1391,1392],{},"WORKDIR \u002Fapp\n",[1376,1394,1396],{"class":1378,"line":1395},4,[1376,1397,1398],{"emptyLinePlaceholder":898},"\n",[1376,1400,1402],{"class":1378,"line":1401},5,[1376,1403,1404],{},"# Copia só manifestos primeiro → layer de deps cacheada\n",[1376,1406,1408],{"class":1378,"line":1407},6,[1376,1409,1410],{},"COPY package*.json .\u002F\n",[1376,1412,1414],{"class":1378,"line":1413},7,[1376,1415,1416],{},"RUN npm ci --omit=dev\n",[1376,1418,1420],{"class":1378,"line":1419},8,[1376,1421,1398],{"emptyLinePlaceholder":898},[1376,1423,1425],{"class":1378,"line":1424},9,[1376,1426,1427],{},"# Só agora copia o código → só esta layer invalida a cada commit\n",[1376,1429,1431],{"class":1378,"line":1430},10,[1376,1432,1433],{},"COPY . .\n",[1376,1435,1437],{"class":1378,"line":1436},11,[1376,1438,1439],{},"RUN npm run build\n",[1376,1441,1443],{"class":1378,"line":1442},12,[1376,1444,1398],{"emptyLinePlaceholder":898},[1376,1446,1448],{"class":1378,"line":1447},13,[1376,1449,1450],{},"# Imagem final enxuta (multi-stage)\n",[1376,1452,1454],{"class":1378,"line":1453},14,[1376,1455,1456],{},"FROM nginx:alpine\n",[1376,1458,1460],{"class":1378,"line":1459},15,[1376,1461,1462],{},"COPY --from=builder \u002Fapp\u002Fdist \u002Fusr\u002Fshare\u002Fnginx\u002Fhtml\n",[12,1464,1466],{"id":1465},"testes-a-rede-de-segurança-testes","Testes: a rede de segurança {#testes}",[86,1468,1469],{},"Sem testes, CI é só um empacotador caro. A pirâmide de testes clássica continua\nválida e é o que mantém o pipeline rápido:",[506,1471,1474],{"className":1472,"code":1473,"language":511},[509],"            ▲\n           \u002FE2E\\          ← poucos, lentos, em ambiente real\n          \u002F──────\\\n         \u002FIntegração\\     ← médios, sobem dependências\n        \u002F────────────\\\n       \u002F   Unitários   \\   ← muitos, rápidos, isolados\n      \u002F──────────────────\\\n",[513,1475,1473],{"__ignoreMap":515},[283,1477,1479],{"id":1478},"testes-unitários","Testes unitários",[86,1481,1482],{},"Rápidos (milissegundos), sem rede nem banco. Devem cobrir a maior parte da\nlógica de negócio. Se um teste unitário demora mais que 200ms, algo está errado\n— provavelmente ele não é unitário de verdade.",[283,1484,1486],{"id":1485},"testes-de-integração","Testes de integração",[86,1488,1489],{},"Sobem dependências reais em containers (Testcontainers é a ferramenta padrão).\nValidam que seu código conversa certo com Postgres, Redis, Kafka. Demoram\nsegundos, não milissegundos.",[283,1491,1493],{"id":1492},"testes-e2e","Testes E2E",[86,1495,1496,1497,1500],{},"Simulam um usuário real navegando (Playwright, Cypress). São lentos e frágeis,\npor isso devem ser poucos e focar nos fluxos críticos: login, checkout, pagamento.\nRodar E2E em ",[89,1498,1499],{},"todo"," PR é o erro mais comum de custo de CI.",[283,1502,1504],{"id":1503},"lidando-com-testes-flaky","Lidando com testes flaky",[86,1506,1507],{},"Teste flaky é aquele que ora passa, ora falha, sem mudança de código. É o pior\ninimigo da confiança no pipeline. Estratégia:",[17,1509,1510,1516,1522],{},[20,1511,1512,1515],{},[89,1513,1514],{},"Detectar",": marcar testes que falham e passam no retry como suspeitos.",[20,1517,1518,1521],{},[89,1519,1520],{},"Quarentena",": mover para uma suite separada que não bloqueia o PR.",[20,1523,1524,1527],{},[89,1525,1526],{},"Corrigir ou remover",": um teste flaky é pior que não ter teste, porque\nensina o time a ignorar o CI vermelho.",[710,1529,1530],{},[86,1531,1532,1535],{},[89,1533,1534],{},"Regra",": um pipeline que pisca verde\u002Fvermelho é tratado como enfeite.\nPreferência absoluta: CI confiável, mesmo que mais lento, do que CI rápido\ne instável.",[12,1537,1539],{"id":1538},"deploy-automático-do-commit-à-produção-deploy","Deploy automático: do commit à produção {#deploy}",[86,1541,1542,1543,522],{},"Deploy automatizado é onde o \"CD\" brilha. Mas automático não significa\nirresponsável — significa ",[89,1544,1545],{},"repetível e auditável",[283,1547,1549],{"id":1548},"ambientes","Ambientes",[528,1551,1552,1558,1564],{},[20,1553,1554,1557],{},[89,1555,1556],{},"Dev",": local da pessoa desenvolvedora.",[20,1559,1560,1563],{},[89,1561,1562],{},"Staging",": cópia fiel de produção (mesma versão de libs, dados anonimizados).",[20,1565,1566,1569],{},[89,1567,1568],{},"Produção",": onde o usuário real está.",[283,1571,1573],{"id":1572},"estratégias-de-deploy","Estratégias de deploy",[165,1575,1576,1589],{},[168,1577,1578],{},[171,1579,1580,1583,1586],{},[174,1581,1582],{},"Estratégia",[174,1584,1585],{},"Como funciona",[174,1587,1588],{},"Quando usar",[184,1590,1591,1604,1617,1630],{},[171,1592,1593,1598,1601],{},[189,1594,1595],{},[89,1596,1597],{},"Rolling",[189,1599,1600],{},"Substitui instâncias antigas por novas aos poucos",[189,1602,1603],{},"Default, sem downtime",[171,1605,1606,1611,1614],{},[189,1607,1608],{},[89,1609,1610],{},"Blue\u002FGreen",[189,1612,1613],{},"Dois ambientes idênticos; switch de tráfego num botão",[189,1615,1616],{},"Rollback instantâneo",[171,1618,1619,1624,1627],{},[189,1620,1621],{},[89,1622,1623],{},"Canary",[189,1625,1626],{},"1% → 5% → 25% → 100% do tráfego na nova versão",[189,1628,1629],{},"Risco alto de regressão",[171,1631,1632,1637,1640],{},[189,1633,1634],{},[89,1635,1636],{},"Feature flag",[189,1638,1639],{},"Código novo dorme em produção, liga por flag",[189,1641,1642],{},"Experimentação A\u002FB",[283,1644,1646],{"id":1645},"pipeline-de-deploy-vs-deploy-manual","Pipeline de deploy vs. deploy manual",[86,1648,1649,1650,1653],{},"O deploy manual depende de uma pessoa rodar ",[513,1651,1652],{},"kubectl apply"," ou clicar num painel.\nÉ lento, propenso a erro e não escalável. O deploy via pipeline é determinístico:\nas mesmas ações sempre na mesma ordem, com logs auditáveis e gates de qualidade.",[506,1655,1659],{"className":1656,"code":1657,"language":1658,"meta":515,"style":515},"language-yaml shiki shiki-themes github-light github-dark","# Trecho de deploy com gate de aprovação\n- name: Deploy to staging\n  run: .\u002Fscripts\u002Fdeploy.sh staging\n\n- name: Smoke tests\n  run: .\u002Fscripts\u002Fsmoke.sh staging-url\n\n- name: Aguardar aprovação manual (produção)\n  uses: trstringer\u002Fmanual-approval-gateway@v1\n  with:\n    approvers: tech-leads\n    minimum-approvals: 1\n\n- name: Deploy to production (canary 10%)\n  run: .\u002Fscripts\u002Fdeploy.sh production --canary 10\n","yaml",[513,1660,1661,1667,1684,1694,1698,1709,1718,1722,1733,1743,1751,1761,1772,1776,1787],{"__ignoreMap":515},[1376,1662,1663],{"class":1378,"line":1379},[1376,1664,1666],{"class":1665},"sJ8bj","# Trecho de deploy com gate de aprovação\n",[1376,1668,1669,1673,1677,1680],{"class":1378,"line":984},[1376,1670,1672],{"class":1671},"sVt8B","- ",[1376,1674,1676],{"class":1675},"s9eBZ","name",[1376,1678,1679],{"class":1671},": ",[1376,1681,1683],{"class":1682},"sZZnC","Deploy to staging\n",[1376,1685,1686,1689,1691],{"class":1378,"line":981},[1376,1687,1688],{"class":1675},"  run",[1376,1690,1679],{"class":1671},[1376,1692,1693],{"class":1682},".\u002Fscripts\u002Fdeploy.sh staging\n",[1376,1695,1696],{"class":1378,"line":1395},[1376,1697,1398],{"emptyLinePlaceholder":898},[1376,1699,1700,1702,1704,1706],{"class":1378,"line":1401},[1376,1701,1672],{"class":1671},[1376,1703,1676],{"class":1675},[1376,1705,1679],{"class":1671},[1376,1707,1708],{"class":1682},"Smoke tests\n",[1376,1710,1711,1713,1715],{"class":1378,"line":1407},[1376,1712,1688],{"class":1675},[1376,1714,1679],{"class":1671},[1376,1716,1717],{"class":1682},".\u002Fscripts\u002Fsmoke.sh staging-url\n",[1376,1719,1720],{"class":1378,"line":1413},[1376,1721,1398],{"emptyLinePlaceholder":898},[1376,1723,1724,1726,1728,1730],{"class":1378,"line":1419},[1376,1725,1672],{"class":1671},[1376,1727,1676],{"class":1675},[1376,1729,1679],{"class":1671},[1376,1731,1732],{"class":1682},"Aguardar aprovação manual (produção)\n",[1376,1734,1735,1738,1740],{"class":1378,"line":1424},[1376,1736,1737],{"class":1675},"  uses",[1376,1739,1679],{"class":1671},[1376,1741,1742],{"class":1682},"trstringer\u002Fmanual-approval-gateway@v1\n",[1376,1744,1745,1748],{"class":1378,"line":1430},[1376,1746,1747],{"class":1675},"  with",[1376,1749,1750],{"class":1671},":\n",[1376,1752,1753,1756,1758],{"class":1378,"line":1436},[1376,1754,1755],{"class":1675},"    approvers",[1376,1757,1679],{"class":1671},[1376,1759,1760],{"class":1682},"tech-leads\n",[1376,1762,1763,1766,1768],{"class":1378,"line":1442},[1376,1764,1765],{"class":1675},"    minimum-approvals",[1376,1767,1679],{"class":1671},[1376,1769,1771],{"class":1770},"sj4cs","1\n",[1376,1773,1774],{"class":1378,"line":1447},[1376,1775,1398],{"emptyLinePlaceholder":898},[1376,1777,1778,1780,1782,1784],{"class":1378,"line":1453},[1376,1779,1672],{"class":1671},[1376,1781,1676],{"class":1675},[1376,1783,1679],{"class":1671},[1376,1785,1786],{"class":1682},"Deploy to production (canary 10%)\n",[1376,1788,1789,1791,1793],{"class":1378,"line":1459},[1376,1790,1688],{"class":1675},[1376,1792,1679],{"class":1671},[1376,1794,1795],{"class":1682},".\u002Fscripts\u002Fdeploy.sh production --canary 10\n",[12,1797,1799],{"id":1798},"rollback-o-botão-desfazer-profissional-rollback","Rollback: o botão \"desfazer\" profissional {#rollback}",[86,1801,1802,1803,522],{},"Todo deploy vai falhar um dia. A diferença entre um incidente de 5 minutos e\num de 5 horas é ter rollback ",[89,1804,1805],{},"automático e testado",[283,1807,1809],{"id":1808},"tipos-de-rollback","Tipos de rollback",[528,1811,1812,1818,1832],{},[20,1813,1814,1817],{},[89,1815,1816],{},"Rollback de versão (imagem)",": voltar para a imagem Docker anterior. Rápido,\nresolve bugs de código.",[20,1819,1820,1823,1824,1827,1828,1831],{},[89,1821,1822],{},"Rollback de schema (banco)",": mais delicado. Migrações destrutivas\n(",[513,1825,1826],{},"DROP COLUMN",") não têm volta trivial. Por isso, ",[89,1829,1830],{},"migrações devem ser\nreversíveis"," e feitas em duas fases (expand → contract).",[20,1833,1834,1837],{},[89,1835,1836],{},"Rollback de feature flag",": desligar a flag. O mais barato — não precisa\nnem redeployar.",[283,1839,1841],{"id":1840},"automatizando-o-rollback","Automatizando o rollback",[86,1843,1844],{},"Defina métricas que disparam rollback sozinhas: taxa de erro > 1% por 2 minutos,\nlatência P99 > 500ms, taxa de 5xx acima do baseline. Ferramentas como Argo Rollouts\ne Flagger fazem isso nativamente no Kubernetes.",[506,1846,1848],{"className":1656,"code":1847,"language":1658,"meta":515,"style":515},"# Argo Rollouts: análise automática que reverte se erro subir\nanalysis:\n  templates:\n    - templateName: success-rate\n  startingStep: 2   # só analisa depois de 10% de tráfego\nmetrics:\n  - name: success-rate\n    successCondition: result >= 0.99\n    failureLimit: 2   # 2 falhas consecutivas → rollback automático\n",[513,1849,1850,1855,1862,1869,1882,1895,1902,1913,1923],{"__ignoreMap":515},[1376,1851,1852],{"class":1378,"line":1379},[1376,1853,1854],{"class":1665},"# Argo Rollouts: análise automática que reverte se erro subir\n",[1376,1856,1857,1860],{"class":1378,"line":984},[1376,1858,1859],{"class":1675},"analysis",[1376,1861,1750],{"class":1671},[1376,1863,1864,1867],{"class":1378,"line":981},[1376,1865,1866],{"class":1675},"  templates",[1376,1868,1750],{"class":1671},[1376,1870,1871,1874,1877,1879],{"class":1378,"line":1395},[1376,1872,1873],{"class":1671},"    - ",[1376,1875,1876],{"class":1675},"templateName",[1376,1878,1679],{"class":1671},[1376,1880,1881],{"class":1682},"success-rate\n",[1376,1883,1884,1887,1889,1892],{"class":1378,"line":1401},[1376,1885,1886],{"class":1675},"  startingStep",[1376,1888,1679],{"class":1671},[1376,1890,1891],{"class":1770},"2",[1376,1893,1894],{"class":1665},"   # só analisa depois de 10% de tráfego\n",[1376,1896,1897,1900],{"class":1378,"line":1407},[1376,1898,1899],{"class":1675},"metrics",[1376,1901,1750],{"class":1671},[1376,1903,1904,1907,1909,1911],{"class":1378,"line":1413},[1376,1905,1906],{"class":1671},"  - ",[1376,1908,1676],{"class":1675},[1376,1910,1679],{"class":1671},[1376,1912,1881],{"class":1682},[1376,1914,1915,1918,1920],{"class":1378,"line":1419},[1376,1916,1917],{"class":1675},"    successCondition",[1376,1919,1679],{"class":1671},[1376,1921,1922],{"class":1682},"result >= 0.99\n",[1376,1924,1925,1928,1930,1932],{"class":1378,"line":1424},[1376,1926,1927],{"class":1675},"    failureLimit",[1376,1929,1679],{"class":1671},[1376,1931,1891],{"class":1770},[1376,1933,1934],{"class":1665},"   # 2 falhas consecutivas → rollback automático\n",[12,1936,1938],{"id":1937},"estratégias-para-times-grandes-times-grandes","Estratégias para times grandes {#times-grandes}",[86,1940,1941],{},"Quando o time passa de ~10 pessoas, surgem problemas novos. Aqui vão as\nestratégias que separam pipelines que escalam de pipelines que travam.",[283,1943,1945],{"id":1944},"_1-reusable-workflows","1. Reusable workflows",[86,1947,1948],{},"No GitHub Actions, você pode reutilizar workflows inteiros. Em vez de cada\nrepositório copiar 200 linhas de YAML, chame um workflow central:",[506,1950,1952],{"className":1656,"code":1951,"language":1658,"meta":515,"style":515},"# .github\u002Fworkflows\u002Fdeploy.yml em qualquer repo\njobs:\n  deploy:\n    uses: org\u002Finicialize-pipeline\u002F.github\u002Fworkflows\u002Fdeploy-template.yml@v3\n    with:\n      service-name: ${{ github.repository }}\n      environment: production\n    secrets: inherit\n",[513,1953,1954,1959,1966,1973,1983,1990,2000,2010],{"__ignoreMap":515},[1376,1955,1956],{"class":1378,"line":1379},[1376,1957,1958],{"class":1665},"# .github\u002Fworkflows\u002Fdeploy.yml em qualquer repo\n",[1376,1960,1961,1964],{"class":1378,"line":984},[1376,1962,1963],{"class":1675},"jobs",[1376,1965,1750],{"class":1671},[1376,1967,1968,1971],{"class":1378,"line":981},[1376,1969,1970],{"class":1675},"  deploy",[1376,1972,1750],{"class":1671},[1376,1974,1975,1978,1980],{"class":1378,"line":1395},[1376,1976,1977],{"class":1675},"    uses",[1376,1979,1679],{"class":1671},[1376,1981,1982],{"class":1682},"org\u002Finicialize-pipeline\u002F.github\u002Fworkflows\u002Fdeploy-template.yml@v3\n",[1376,1984,1985,1988],{"class":1378,"line":1401},[1376,1986,1987],{"class":1675},"    with",[1376,1989,1750],{"class":1671},[1376,1991,1992,1995,1997],{"class":1378,"line":1407},[1376,1993,1994],{"class":1675},"      service-name",[1376,1996,1679],{"class":1671},[1376,1998,1999],{"class":1682},"${{ github.repository }}\n",[1376,2001,2002,2005,2007],{"class":1378,"line":1413},[1376,2003,2004],{"class":1675},"      environment",[1376,2006,1679],{"class":1671},[1376,2008,2009],{"class":1682},"production\n",[1376,2011,2012,2015,2017],{"class":1378,"line":1419},[1376,2013,2014],{"class":1675},"    secrets",[1376,2016,1679],{"class":1671},[1376,2018,2019],{"class":1682},"inherit\n",[86,2021,2022,2023,2026,2027,2030],{},"Mudou a versão do template? Atualize ",[513,2024,2025],{},"@v3"," para ",[513,2028,2029],{},"@v4"," e todos os repositórios\nherdam a melhoria num commit.",[283,2032,2034],{"id":2033},"_2-monorepo-vs-multi-repo","2. Monorepo vs. multi-repo",[528,2036,2037,2043],{},[20,2038,2039,2042],{},[89,2040,2041],{},"Monorepo",": um repositório, pipeline precisa ser inteligente para só rodar\ntestes dos pacotes afetados (ferramentas: Nx, Turborepo, Bazel).",[20,2044,2045,2048],{},[89,2046,2047],{},"Multi-repo",": cada repo tem seu pipeline, mas compartilha templates\nreutilizáveis para não repetir configuração.",[283,2050,2052],{"id":2051},"_3-cache-agressivo","3. Cache agressivo",[528,2054,2055,2058,2065],{},[20,2056,2057],{},"Cache de build tools (npm, pip, cargo, gradle).",[20,2059,2060,2061,2064],{},"Cache de Docker (actions\u002Fcache para ",[513,2062,2063],{},"\u002Fvar\u002Flib\u002Fdocker"," ou BuildKit).",[20,2066,2067],{},"Cache de testes (Playwright armazena screenshots e traces).",[283,2069,2071],{"id":2070},"_4-self-hosted-runners-runners-sob-demanda","4. Self-hosted runners \u002F runners sob demanda",[86,2073,2074],{},"Runners do GitHub são limitados e pagos por minuto. Para times grandes,\nrunners auto-hospedados com auto-scaling (Actions Runner Controller no K8s)\nreduzem custo e fila. Para builds esporádicos pesados, use runners efêmeros\nsubidos on-demand.",[283,2076,2078],{"id":2077},"_5-gates-de-qualidade-não-de-egos","5. Gates de qualidade, não de egos",[86,2080,2081],{},"Bons gates: cobertura não pode cair, testes novos obrigatórios para novos\narquivos, SAST sem vulnerabilidades críticas. Maus gates: \"precisa de 3\napprovals de staff engineers\" — vira gargalo humano e não escala.",[12,2083,2085],{"id":2084},"exemplo-real-com-github-actions-exemplo-github","Exemplo real com GitHub Actions {#exemplo-github}",[86,2087,2088],{},"Aqui está um pipeline completo, comentado, pronto para adaptar:",[506,2090,2092],{"className":1656,"code":2091,"language":1658,"meta":515,"style":515},"# .github\u002Fworkflows\u002Fci-cd.yml\nname: CI\u002FCD\n\non:\n  push:\n    branches: [main]\n  pull_request:\n\n# Cancela runs anteriores do mesmo PR — economiza runners\nconcurrency:\n  group: ci-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  # Job 1: tudo rápido e paralelo\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with: { node-version: 20, cache: npm }\n      - run: npm ci\n      - run: npm run lint        # ESLint, Prettier\n\n  test-unit:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with: { node-version: 20, cache: npm }\n      - run: npm ci\n      - run: npm test -- --coverage   # Vitest\u002FJest\n\n  # Job 2: build só roda se lint+test passarem\n  build:\n    needs: [lint, test-unit]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: docker\u002Fsetup-buildx-action@v3\n      - uses: docker\u002Fbuild-push-action@v5\n        with:\n          context: .\n          push: false\n          tags: app:${{ github.sha }}\n          cache-from: type=gha     # cache de layers no GitHub\n          cache-to: type=gha,mode=max\n      - uses: actions\u002Fupload-artifact@v4\n        with: { name: image, path: image.tar }\n\n  # Job 3: deploy só na main, nunca em PR\n  deploy:\n    if: github.ref == 'refs\u002Fheads\u002Fmain'\n    needs: build\n    environment: production\n    steps:\n      - uses: actions\u002Fdownload-artifact@v4\n        with: { name: image }\n      - run: .\u002Fscripts\u002Fdeploy.sh ${{ github.sha }}\n",[513,2093,2094,2099,2108,2112,2119,2126,2140,2147,2151,2156,2163,2173,2183,2187,2193,2198,2206,2217,2225,2239,2251,2281,2294,2309,2314,2322,2331,2338,2349,2360,2383,2394,2409,2414,2420,2428,2446,2455,2462,2473,2485,2497,2504,2515,2526,2537,2551,2562,2574,2600,2605,2611,2618,2629,2639,2649,2656,2668,2683],{"__ignoreMap":515},[1376,2095,2096],{"class":1378,"line":1379},[1376,2097,2098],{"class":1665},"# .github\u002Fworkflows\u002Fci-cd.yml\n",[1376,2100,2101,2103,2105],{"class":1378,"line":984},[1376,2102,1676],{"class":1675},[1376,2104,1679],{"class":1671},[1376,2106,2107],{"class":1682},"CI\u002FCD\n",[1376,2109,2110],{"class":1378,"line":981},[1376,2111,1398],{"emptyLinePlaceholder":898},[1376,2113,2114,2117],{"class":1378,"line":1395},[1376,2115,2116],{"class":1770},"on",[1376,2118,1750],{"class":1671},[1376,2120,2121,2124],{"class":1378,"line":1401},[1376,2122,2123],{"class":1675},"  push",[1376,2125,1750],{"class":1671},[1376,2127,2128,2131,2134,2137],{"class":1378,"line":1407},[1376,2129,2130],{"class":1675},"    branches",[1376,2132,2133],{"class":1671},": [",[1376,2135,2136],{"class":1682},"main",[1376,2138,2139],{"class":1671},"]\n",[1376,2141,2142,2145],{"class":1378,"line":1413},[1376,2143,2144],{"class":1675},"  pull_request",[1376,2146,1750],{"class":1671},[1376,2148,2149],{"class":1378,"line":1419},[1376,2150,1398],{"emptyLinePlaceholder":898},[1376,2152,2153],{"class":1378,"line":1424},[1376,2154,2155],{"class":1665},"# Cancela runs anteriores do mesmo PR — economiza runners\n",[1376,2157,2158,2161],{"class":1378,"line":1430},[1376,2159,2160],{"class":1675},"concurrency",[1376,2162,1750],{"class":1671},[1376,2164,2165,2168,2170],{"class":1378,"line":1436},[1376,2166,2167],{"class":1675},"  group",[1376,2169,1679],{"class":1671},[1376,2171,2172],{"class":1682},"ci-${{ github.ref }}\n",[1376,2174,2175,2178,2180],{"class":1378,"line":1442},[1376,2176,2177],{"class":1675},"  cancel-in-progress",[1376,2179,1679],{"class":1671},[1376,2181,2182],{"class":1770},"true\n",[1376,2184,2185],{"class":1378,"line":1447},[1376,2186,1398],{"emptyLinePlaceholder":898},[1376,2188,2189,2191],{"class":1378,"line":1453},[1376,2190,1963],{"class":1675},[1376,2192,1750],{"class":1671},[1376,2194,2195],{"class":1378,"line":1459},[1376,2196,2197],{"class":1665},"  # Job 1: tudo rápido e paralelo\n",[1376,2199,2201,2204],{"class":1378,"line":2200},16,[1376,2202,2203],{"class":1675},"  lint",[1376,2205,1750],{"class":1671},[1376,2207,2209,2212,2214],{"class":1378,"line":2208},17,[1376,2210,2211],{"class":1675},"    runs-on",[1376,2213,1679],{"class":1671},[1376,2215,2216],{"class":1682},"ubuntu-latest\n",[1376,2218,2220,2223],{"class":1378,"line":2219},18,[1376,2221,2222],{"class":1675},"    steps",[1376,2224,1750],{"class":1671},[1376,2226,2228,2231,2234,2236],{"class":1378,"line":2227},19,[1376,2229,2230],{"class":1671},"      - ",[1376,2232,2233],{"class":1675},"uses",[1376,2235,1679],{"class":1671},[1376,2237,2238],{"class":1682},"actions\u002Fcheckout@v4\n",[1376,2240,2242,2244,2246,2248],{"class":1378,"line":2241},20,[1376,2243,2230],{"class":1671},[1376,2245,2233],{"class":1675},[1376,2247,1679],{"class":1671},[1376,2249,2250],{"class":1682},"actions\u002Fsetup-node@v4\n",[1376,2252,2254,2257,2260,2263,2265,2268,2270,2273,2275,2278],{"class":1378,"line":2253},21,[1376,2255,2256],{"class":1675},"        with",[1376,2258,2259],{"class":1671},": { ",[1376,2261,2262],{"class":1675},"node-version",[1376,2264,1679],{"class":1671},[1376,2266,2267],{"class":1770},"20",[1376,2269,1327],{"class":1671},[1376,2271,2272],{"class":1675},"cache",[1376,2274,1679],{"class":1671},[1376,2276,2277],{"class":1682},"npm",[1376,2279,2280],{"class":1671}," }\n",[1376,2282,2284,2286,2289,2291],{"class":1378,"line":2283},22,[1376,2285,2230],{"class":1671},[1376,2287,2288],{"class":1675},"run",[1376,2290,1679],{"class":1671},[1376,2292,2293],{"class":1682},"npm ci\n",[1376,2295,2297,2299,2301,2303,2306],{"class":1378,"line":2296},23,[1376,2298,2230],{"class":1671},[1376,2300,2288],{"class":1675},[1376,2302,1679],{"class":1671},[1376,2304,2305],{"class":1682},"npm run lint",[1376,2307,2308],{"class":1665},"        # ESLint, Prettier\n",[1376,2310,2312],{"class":1378,"line":2311},24,[1376,2313,1398],{"emptyLinePlaceholder":898},[1376,2315,2317,2320],{"class":1378,"line":2316},25,[1376,2318,2319],{"class":1675},"  test-unit",[1376,2321,1750],{"class":1671},[1376,2323,2325,2327,2329],{"class":1378,"line":2324},26,[1376,2326,2211],{"class":1675},[1376,2328,1679],{"class":1671},[1376,2330,2216],{"class":1682},[1376,2332,2334,2336],{"class":1378,"line":2333},27,[1376,2335,2222],{"class":1675},[1376,2337,1750],{"class":1671},[1376,2339,2341,2343,2345,2347],{"class":1378,"line":2340},28,[1376,2342,2230],{"class":1671},[1376,2344,2233],{"class":1675},[1376,2346,1679],{"class":1671},[1376,2348,2238],{"class":1682},[1376,2350,2352,2354,2356,2358],{"class":1378,"line":2351},29,[1376,2353,2230],{"class":1671},[1376,2355,2233],{"class":1675},[1376,2357,1679],{"class":1671},[1376,2359,2250],{"class":1682},[1376,2361,2363,2365,2367,2369,2371,2373,2375,2377,2379,2381],{"class":1378,"line":2362},30,[1376,2364,2256],{"class":1675},[1376,2366,2259],{"class":1671},[1376,2368,2262],{"class":1675},[1376,2370,1679],{"class":1671},[1376,2372,2267],{"class":1770},[1376,2374,1327],{"class":1671},[1376,2376,2272],{"class":1675},[1376,2378,1679],{"class":1671},[1376,2380,2277],{"class":1682},[1376,2382,2280],{"class":1671},[1376,2384,2386,2388,2390,2392],{"class":1378,"line":2385},31,[1376,2387,2230],{"class":1671},[1376,2389,2288],{"class":1675},[1376,2391,1679],{"class":1671},[1376,2393,2293],{"class":1682},[1376,2395,2397,2399,2401,2403,2406],{"class":1378,"line":2396},32,[1376,2398,2230],{"class":1671},[1376,2400,2288],{"class":1675},[1376,2402,1679],{"class":1671},[1376,2404,2405],{"class":1682},"npm test -- --coverage",[1376,2407,2408],{"class":1665},"   # Vitest\u002FJest\n",[1376,2410,2412],{"class":1378,"line":2411},33,[1376,2413,1398],{"emptyLinePlaceholder":898},[1376,2415,2417],{"class":1378,"line":2416},34,[1376,2418,2419],{"class":1665},"  # Job 2: build só roda se lint+test passarem\n",[1376,2421,2423,2426],{"class":1378,"line":2422},35,[1376,2424,2425],{"class":1675},"  build",[1376,2427,1750],{"class":1671},[1376,2429,2431,2434,2436,2439,2441,2444],{"class":1378,"line":2430},36,[1376,2432,2433],{"class":1675},"    needs",[1376,2435,2133],{"class":1671},[1376,2437,2438],{"class":1682},"lint",[1376,2440,1327],{"class":1671},[1376,2442,2443],{"class":1682},"test-unit",[1376,2445,2139],{"class":1671},[1376,2447,2449,2451,2453],{"class":1378,"line":2448},37,[1376,2450,2211],{"class":1675},[1376,2452,1679],{"class":1671},[1376,2454,2216],{"class":1682},[1376,2456,2458,2460],{"class":1378,"line":2457},38,[1376,2459,2222],{"class":1675},[1376,2461,1750],{"class":1671},[1376,2463,2465,2467,2469,2471],{"class":1378,"line":2464},39,[1376,2466,2230],{"class":1671},[1376,2468,2233],{"class":1675},[1376,2470,1679],{"class":1671},[1376,2472,2238],{"class":1682},[1376,2474,2476,2478,2480,2482],{"class":1378,"line":2475},40,[1376,2477,2230],{"class":1671},[1376,2479,2233],{"class":1675},[1376,2481,1679],{"class":1671},[1376,2483,2484],{"class":1682},"docker\u002Fsetup-buildx-action@v3\n",[1376,2486,2488,2490,2492,2494],{"class":1378,"line":2487},41,[1376,2489,2230],{"class":1671},[1376,2491,2233],{"class":1675},[1376,2493,1679],{"class":1671},[1376,2495,2496],{"class":1682},"docker\u002Fbuild-push-action@v5\n",[1376,2498,2500,2502],{"class":1378,"line":2499},42,[1376,2501,2256],{"class":1675},[1376,2503,1750],{"class":1671},[1376,2505,2507,2510,2512],{"class":1378,"line":2506},43,[1376,2508,2509],{"class":1675},"          context",[1376,2511,1679],{"class":1671},[1376,2513,2514],{"class":1770},".\n",[1376,2516,2518,2521,2523],{"class":1378,"line":2517},44,[1376,2519,2520],{"class":1675},"          push",[1376,2522,1679],{"class":1671},[1376,2524,2525],{"class":1770},"false\n",[1376,2527,2529,2532,2534],{"class":1378,"line":2528},45,[1376,2530,2531],{"class":1675},"          tags",[1376,2533,1679],{"class":1671},[1376,2535,2536],{"class":1682},"app:${{ github.sha }}\n",[1376,2538,2540,2543,2545,2548],{"class":1378,"line":2539},46,[1376,2541,2542],{"class":1675},"          cache-from",[1376,2544,1679],{"class":1671},[1376,2546,2547],{"class":1682},"type=gha",[1376,2549,2550],{"class":1665},"     # cache de layers no GitHub\n",[1376,2552,2554,2557,2559],{"class":1378,"line":2553},47,[1376,2555,2556],{"class":1675},"          cache-to",[1376,2558,1679],{"class":1671},[1376,2560,2561],{"class":1682},"type=gha,mode=max\n",[1376,2563,2565,2567,2569,2571],{"class":1378,"line":2564},48,[1376,2566,2230],{"class":1671},[1376,2568,2233],{"class":1675},[1376,2570,1679],{"class":1671},[1376,2572,2573],{"class":1682},"actions\u002Fupload-artifact@v4\n",[1376,2575,2577,2579,2581,2583,2585,2588,2590,2593,2595,2598],{"class":1378,"line":2576},49,[1376,2578,2256],{"class":1675},[1376,2580,2259],{"class":1671},[1376,2582,1676],{"class":1675},[1376,2584,1679],{"class":1671},[1376,2586,2587],{"class":1682},"image",[1376,2589,1327],{"class":1671},[1376,2591,2592],{"class":1675},"path",[1376,2594,1679],{"class":1671},[1376,2596,2597],{"class":1682},"image.tar",[1376,2599,2280],{"class":1671},[1376,2601,2603],{"class":1378,"line":2602},50,[1376,2604,1398],{"emptyLinePlaceholder":898},[1376,2606,2608],{"class":1378,"line":2607},51,[1376,2609,2610],{"class":1665},"  # Job 3: deploy só na main, nunca em PR\n",[1376,2612,2614,2616],{"class":1378,"line":2613},52,[1376,2615,1970],{"class":1675},[1376,2617,1750],{"class":1671},[1376,2619,2621,2624,2626],{"class":1378,"line":2620},53,[1376,2622,2623],{"class":1675},"    if",[1376,2625,1679],{"class":1671},[1376,2627,2628],{"class":1682},"github.ref == 'refs\u002Fheads\u002Fmain'\n",[1376,2630,2632,2634,2636],{"class":1378,"line":2631},54,[1376,2633,2433],{"class":1675},[1376,2635,1679],{"class":1671},[1376,2637,2638],{"class":1682},"build\n",[1376,2640,2642,2645,2647],{"class":1378,"line":2641},55,[1376,2643,2644],{"class":1675},"    environment",[1376,2646,1679],{"class":1671},[1376,2648,2009],{"class":1682},[1376,2650,2652,2654],{"class":1378,"line":2651},56,[1376,2653,2222],{"class":1675},[1376,2655,1750],{"class":1671},[1376,2657,2659,2661,2663,2665],{"class":1378,"line":2658},57,[1376,2660,2230],{"class":1671},[1376,2662,2233],{"class":1675},[1376,2664,1679],{"class":1671},[1376,2666,2667],{"class":1682},"actions\u002Fdownload-artifact@v4\n",[1376,2669,2671,2673,2675,2677,2679,2681],{"class":1378,"line":2670},58,[1376,2672,2256],{"class":1675},[1376,2674,2259],{"class":1671},[1376,2676,1676],{"class":1675},[1376,2678,1679],{"class":1671},[1376,2680,2587],{"class":1682},[1376,2682,2280],{"class":1671},[1376,2684,2686,2688,2690,2692],{"class":1378,"line":2685},59,[1376,2687,2230],{"class":1671},[1376,2689,2288],{"class":1675},[1376,2691,1679],{"class":1671},[1376,2693,2694],{"class":1682},".\u002Fscripts\u002Fdeploy.sh ${{ github.sha }}\n",[86,2696,2697],{},"Pontos de atenção nesse exemplo:",[528,2699,2700,2705,2711,2717,2723],{},[20,2701,2702,2704],{},[513,2703,2160],{}," cancela runs velhas do mesmo PR → economia de runner.",[20,2706,2707,2710],{},[513,2708,2709],{},"needs"," define a ordem e falha rápida: se lint quebra, build nem roda.",[20,2712,2713,2716],{},[513,2714,2715],{},"cache-from: type=gha"," reaproveita layers Docker entre execuções.",[20,2718,2719,2722],{},[513,2720,2721],{},"environment: production"," cria um gate de aprovação configurável no GitHub.",[20,2724,2725,2726,2728],{},"Deploy só na ",[513,2727,2136],{}," — PRs não tocam produção.",[12,2730,820],{"id":819},[17,2732,2733,2742,2752,2762,2768,2774,2780,2786],{},[20,2734,2735,2738,2739,2741],{},[89,2736,2737],{},"Rodar tudo em série",": cada job esperando o anterior terminar. Solução:\nparalelize jobs independentes com ",[513,2740,2709],{}," apenas onde há dependência real.",[20,2743,2744,2747,2748,2751],{},[89,2745,2746],{},"Ignorar cache",": instalar dependências do zero a cada run. Cada ",[513,2749,2750],{},"npm ci","\nsem cache custa 1-3 minutos que se somam em milhares de runs por mês.",[20,2753,2754,2757,2758,2761],{},[89,2755,2756],{},"Segredos no código",": colocar chaves AWS direto no YAML. Use secrets do\nGitHub\u002FOIDC e ",[89,2759,2760],{},"nunca"," logue tokens.",[20,2763,2764,2767],{},[89,2765,2766],{},"Testes E2E em todo PR",": lentidão e custo explosivo. Separe E2E para\nagendamento noturno ou apenas na main.",[20,2769,2770,2773],{},[89,2771,2772],{},"Sem rollback testado",": \"vamos voltar manual se quebrar\". No pânico do\nincidente, ninguém lembra os passos. Teste o rollback em staging periodicamente.",[20,2775,2776,2779],{},[89,2777,2778],{},"Deploy direto na main sem gate",": em times grandes, um push errado derruba\nprodução. Use protection rules + revisão + checks obrigatórios.",[20,2781,2782,2785],{},[89,2783,2784],{},"Migrações de banco no mesmo pipeline do app",": quebra se a nova versão do\napp for rejeitada mas o schema já mudou. Separe migrações em pipeline próprio\ncom deploy antes do app (padrão expand\u002Fcontract).",[20,2787,2788,2791],{},[89,2789,2790],{},"Achar que CI\u002FCD é ferramenta, não cultura",": comprar Jenkins\u002FGitHub Actions\nnão entrega valor se o time não segue a disciplina de \"main sempre verde\".",[12,2793,2795],{"id":2794},"checklist-final-checklist","Checklist final {#checklist}",[528,2797,2799,2805,2811,2817,2823,2829,2835,2841,2847,2853,2859,2865],{"className":2798},[890],[20,2800,2802,2804],{"className":2801},[894],[896,2803],{"disabled":898,"type":899}," Pipeline roda em menos de 10 minutos para 90% dos PRs",[20,2806,2808,2810],{"className":2807},[894],[896,2809],{"disabled":898,"type":899}," Dependências cacheadas (build tools + Docker layers)",[20,2812,2814,2816],{"className":2813},[894],[896,2815],{"disabled":898,"type":899}," Testes flaky em quarentena, não no caminho crítico",[20,2818,2820,2822],{"className":2819},[894],[896,2821],{"disabled":898,"type":899}," Lint, unit, integração, security scan em paralelo",[20,2824,2826,2828],{"className":2825},[894],[896,2827],{"disabled":898,"type":899}," Deploy com estratégia (rolling\u002Fblue-green\u002Fcanary) e gate",[20,2830,2832,2834],{"className":2831},[894],[896,2833],{"disabled":898,"type":899}," Rollback automático disparado por métricas (erro rate, latência)",[20,2836,2838,2840],{"className":2837},[894],[896,2839],{"disabled":898,"type":899}," Migrações de banco separadas e reversíveis (expand\u002Fcontract)",[20,2842,2844,2846],{"className":2843},[894],[896,2845],{"disabled":898,"type":899}," Templates reutilizáveis para novos repositórios",[20,2848,2850,2852],{"className":2849},[894],[896,2851],{"disabled":898,"type":899}," Runners com auto-scaling para evitar fila em horário pico",[20,2854,2856,2858],{"className":2855},[894],[896,2857],{"disabled":898,"type":899}," Custo de CI monitorado e com alertas (orçamento mensal)",[20,2860,2862,2864],{"className":2861},[894],[896,2863],{"disabled":898,"type":899}," main sempre deployable — protection rules + checks obrigatórios",[20,2866,2868,2870],{"className":2867},[894],[896,2869],{"disabled":898,"type":899}," Runners e secrets auditados periodicamente",[968,2872],{},[86,2874,2875,2878,2879,522],{},[89,2876,2877],{},"Quer um pipeline que escala com seu time?"," A Inicialize Tec implementa CI\u002FCD\ndo commit ao deploy, com rollback automático, observabilidade e templates\nreutilizáveis para múltiplos repositórios. ",[23,2880,2881],{"href":978},"Conversamos sobre seu caso",[2883,2884,2885],"style",{},"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":515,"searchDepth":981,"depth":981,"links":2887},[2888,2889,2890,2891,2892,2893,2899,2904,2908,2915,2916,2917],{"id":14,"depth":984,"text":15},{"id":1116,"depth":984,"text":1117},{"id":1167,"depth":984,"text":1168},{"id":1279,"depth":984,"text":1280},{"id":1306,"depth":984,"text":1307},{"id":1465,"depth":984,"text":1466,"children":2894},[2895,2896,2897,2898],{"id":1478,"depth":981,"text":1479},{"id":1485,"depth":981,"text":1486},{"id":1492,"depth":981,"text":1493},{"id":1503,"depth":981,"text":1504},{"id":1538,"depth":984,"text":1539,"children":2900},[2901,2902,2903],{"id":1548,"depth":981,"text":1549},{"id":1572,"depth":981,"text":1573},{"id":1645,"depth":981,"text":1646},{"id":1798,"depth":984,"text":1799,"children":2905},[2906,2907],{"id":1808,"depth":981,"text":1809},{"id":1840,"depth":981,"text":1841},{"id":1937,"depth":984,"text":1938,"children":2909},[2910,2911,2912,2913,2914],{"id":1944,"depth":981,"text":1945},{"id":2033,"depth":981,"text":2034},{"id":2051,"depth":981,"text":2052},{"id":2070,"depth":981,"text":2071},{"id":2077,"depth":981,"text":2078},{"id":2084,"depth":984,"text":2085},{"id":819,"depth":984,"text":820},{"id":2794,"depth":984,"text":2795},"Como montar um pipeline de CI\u002FCD que escala com seu time: build, test, deploy automático, rollback e estratégias para times grandes.",[2920,1163,2921,2922,2923,2924],"CI\u002FCD","GitHub Actions","deploy automatizado","DevOps","integração contínua",{},"\u002Fautomacao\u002Fpipeline-ci-cd-escalavel",{"title":1045,"description":2918},"pipeline-ci-cd-escalavel","automacao\u002Fpipeline-ci-cd-escalavel","jI-EIrG_5zCP1q40kPTdCt8GAm-ABBMGbDKkmZnGLEU",{"id":2932,"title":2933,"author":7,"body":2934,"category":1027,"date":1028,"description":4858,"extension":1030,"keywords":4859,"meta":4863,"navigation":898,"path":4864,"seo":4865,"slug":4866,"stem":4867,"updated":1028,"__hash__":4868},"content\u002Fautomacao\u002Frpa-vs-automacao-com-ia.md","RPA vs automação com IA: qual escolher?",{"type":9,"value":2935,"toc":4829},[2936,2938,2992,2996,3005,3013,3027,3032,3064,3072,3076,3083,3088,3102,3106,3118,3133,3136,3141,3181,3185,3217,3221,3378,3386,3390,3393,3431,3437,3560,3568,3572,3575,3613,3618,3841,3849,3853,3866,3873,3879,3882,3902,3906,3909,3936,3942,3946,3950,4002,4010,4012,4062,4070,4074,4081,4085,4146,4154,4156,4160,4166,4172,4178,4183,4189,4193,4198,4202,4246,4250,4323,4327,4332,4336,4342,4346,4476,4480,4485,4489,4514,4518,4626,4630,4635,4640,4723,4728,4732,4735,4746,4752,4758,4785,4790,4810,4816,4818,4826],[12,2937,15],{"id":14},[17,2939,2940,2946,2952,2958,2964,2970,2976,2982,2986],{},[20,2941,2942],{},[23,2943,2945],{"href":2944},"#rpa","O que é RPA",[20,2947,2948],{},[23,2949,2951],{"href":2950},"#ia","O que é automação com IA",[20,2953,2954],{},[23,2955,2957],{"href":2956},"#comparacao","Comparação direta",[20,2959,2960],{},[23,2961,2963],{"href":2962},"#quando-rpa","Quando usar RPA",[20,2965,2966],{},[23,2967,2969],{"href":2968},"#quando-ia","Quando usar IA",[20,2971,2972],{},[23,2973,2975],{"href":2974},"#combinacao","Combinação: o melhor dos dois mundos",[20,2977,2978],{},[23,2979,2981],{"href":2980},"#custos","Custos",[20,2983,2984],{},[23,2985,74],{"href":73},[20,2987,2988],{},[23,2989,2991],{"href":2990},"#caso-real","Caso real: financeira que automatizou 80% do back-office",[12,2993,2995],{"id":2994},"o-que-é-rpa-rpa","O que é RPA {#rpa}",[86,2997,2998,3000,3001,3004],{},[89,2999,394],{}," (Robotic Process Automation, ou \"Automação de Processos por Robôs\")\né a automação de tarefas repetitivas através de ",[89,3002,3003],{},"bots"," — programas de\ncomputador que interagem com interfaces gráficas da mesma forma que um humano\nclicaria, digitaria e navegaria.",[710,3006,3007],{},[86,3008,3009,3012],{},[89,3010,3011],{},"Analogia",": Imagine um funcionário que faz exatamente o que você manda,\nsem nunca errar, sem cansar e sem pedir aumento. Você ensina: \"abra o\nnavegador, vá no site do banco, faça login, clique em 'extrato', baixe o\nPDF, salve na pasta X, abra o ERP, cole os valores nos campos Y e Z\". Ele\nrepete isso 500 vezes por dia, sempre igual. Isso é RPA.",[86,3014,3015,3016,1327,3019,3022,3023,3026],{},"Ferramentas como ",[89,3017,3018],{},"UiPath",[89,3020,3021],{},"Automation Anywhere"," e ",[89,3024,3025],{},"Power Automate","\ndominam este mercado. Elas oferecem interfaces visuais (arrastar e soltar)\npara criar esses bots sem precisar programar muito.",[86,3028,3029],{},[89,3030,3031],{},"Características do RPA:",[528,3033,3034,3040,3046,3052,3058],{},[20,3035,3036,3039],{},[89,3037,3038],{},"Automatiza interações com interfaces",": cliques, digitação, copiar\u002Fcolar,\nnavegar entre telas",[20,3041,3042,3045],{},[89,3043,3044],{},"Não requer API"," — funciona em sistemas sem integração formal (sistemas\nantigos que só têm tela, não têm \"porta de comunicação\" para outros programas)",[20,3047,3048,3051],{},[89,3049,3050],{},"Regras determinísticas",": \"se X, então Y\". Sempre o mesmo resultado para\na mesma entrada",[20,3053,3054,3057],{},[89,3055,3056],{},"Não aprende"," — executa exatamente o que foi programado, ponto a ponto",[20,3059,3060,3063],{},[89,3061,3062],{},"Quebra quando a interface muda",": se o site do banco mudou a posição de\num botão, o bot não encontra e falha",[710,3065,3066],{},[86,3067,3068,3071],{},[89,3069,3070],{},"Analogia para entender \"determinístico\"",": Uma calculadora é determinística.\nSe você digita 2+2, ela sempre responde 4. Nunca 3, nunca \"acho que é 4\".\nRPA é igual: sempre faz exatamente o que foi programado.",[283,3073,3075],{"id":3074},"onde-rpa-brilha","Onde RPA brilha",[86,3077,3078,3079,3082],{},"RPA é especialmente útil quando você precisa trabalhar com ",[89,3080,3081],{},"sistemas legados","\n— aqueles programas antigos (anos 90, 2000) que a empresa usa há décadas e que\nnão têm API. O RPA \"simula\" um humano operando esses sistemas pela interface\ngráfica.",[86,3084,3085],{},[89,3086,3087],{},"Exemplos típicos:",[528,3089,3090,3093,3096,3099],{},[20,3091,3092],{},"Baixar relatórios de um portal bancário antigo",[20,3094,3095],{},"Copiar dados de um sistema em Excel e colar em outro",[20,3097,3098],{},"Preencher formulários web repetitivos",[20,3100,3101],{},"Extrair dados de PDFs com layout fixo (sempre na mesma posição)",[12,3103,3105],{"id":3104},"o-que-é-automação-com-ia-ia","O que é automação com IA {#ia}",[86,3107,3108,3110,3111,3114,3115,522],{},[89,3109,362],{}," (Inteligência Artificial) usa modelos de ",[89,3112,3113],{},"machine\nlearning"," (aprendizado de máquina — programas que aprendem padrões a partir\nde dados, em vez de seguir regras fixas) para executar tarefas que exigem\n",[89,3116,3117],{},"compreensão, decisão e adaptação",[710,3119,3120],{},[86,3121,3122,3124,3125,3128,3129,3132],{},[89,3123,3011],{},": Se o RPA é um funcionário que segue instruções passo a passo,\na automação com IA é um funcionário que ",[89,3126,3127],{},"entende"," o que está fazendo.\nVocê diz: \"leia estes 500 e-mails, descubra quais são urgentes, extraia o\nnome do cliente e o problema, e encaminhe para a pessoa certa\". Você não\nprecisa dizer ",[501,3130,3131],{},"como"," ler cada e-mail (cada um é diferente) — a IA entende\nlinguagem natural e se adapta.",[86,3134,3135],{},"Em vez de seguir regras rígidas, o modelo interpreta dados não-estruturados\n(texto livre, imagens, áudio) e decide a melhor ação com base no contexto.",[86,3137,3138],{},[89,3139,3140],{},"Características da automação com IA:",[528,3142,3143,3149,3155,3169,3175],{},[20,3144,3145,3148],{},[89,3146,3147],{},"Processa dados não-estruturados",": texto livre, e-mails, imagens, PDFs com\nlayout variável, áudio",[20,3150,3151,3154],{},[89,3152,3153],{},"Adapta-se a variações",": cada documento pode ter um formato diferente e\nmesmo assim a IA extrai a informação correta",[20,3156,3157,3160,3161,3164,3165,3168],{},[89,3158,3159],{},"Aprende com feedback",": técnicas como ",[89,3162,3163],{},"fine-tuning"," (ajuste fino do\nmodelo com seus dados) e ",[89,3166,3167],{},"RLHF"," (Reinforcement Learning from Human\nFeedback — aprendizado com feedback humano) melhoram a performance ao longo\ndo tempo",[20,3170,3171,3174],{},[89,3172,3173],{},"Requer dados para validação",": precisa de exemplos para testar e calibrar",[20,3176,3177,3180],{},[89,3178,3179],{},"Custos variáveis",": paga por uso (API de LLM, processamento por inferência)",[283,3182,3184],{"id":3183},"onde-automação-com-ia-brilha","Onde automação com IA brilha",[528,3186,3187,3193,3199,3205,3211],{},[20,3188,3189,3192],{},[89,3190,3191],{},"Classificação de texto",": \"este e-mail é reclamação, dúvida ou elogio?\"",[20,3194,3195,3198],{},[89,3196,3197],{},"Extração de dados",": \"neste contrato de 20 páginas, quais são as cláusulas\nde rescisão?\"",[20,3200,3201,3204],{},[89,3202,3203],{},"Geração de conteúdo",": \"escreva um resumo deste relatório de 50 páginas\"",[20,3206,3207,3210],{},[89,3208,3209],{},"Decisão",": \"este pedido de crédito deve ser aprovado ou recusado?\"",[20,3212,3213,3216],{},[89,3214,3215],{},"Tradução e adaptação",": \"traduza este manual técnico mantendo o tom da\nmarca\"",[12,3218,3220],{"id":3219},"comparação-direta-comparacao","Comparação direta {#comparacao}",[165,3222,3223,3234],{},[168,3224,3225],{},[171,3226,3227,3230,3232],{},[174,3228,3229],{},"Critério",[174,3231,394],{},[174,3233,362],{},[184,3235,3236,3249,3262,3275,3287,3300,3313,3326,3339,3352,3365],{},[171,3237,3238,3243,3246],{},[189,3239,3240],{},[89,3241,3242],{},"Tipo de tarefa",[189,3244,3245],{},"Estruturada, repetitiva, baseada em regras",[189,3247,3248],{},"Não-estruturada, varia, exige decisão",[171,3250,3251,3256,3259],{},[189,3252,3253],{},[89,3254,3255],{},"Dados de entrada",[189,3257,3258],{},"Formulários, campos fixos, planilhas",[189,3260,3261],{},"Texto livre, e-mails, imagens, PDFs variados",[171,3263,3264,3269,3272],{},[189,3265,3266],{},[89,3267,3268],{},"Precisão",[189,3270,3271],{},"100% se a regra cobrir o caso",[189,3273,3274],{},"90-98% (melhora com iteração)",[171,3276,3277,3282,3285],{},[189,3278,3279],{},[89,3280,3281],{},"Manutenção",[189,3283,3284],{},"Quebra quando interface muda",[189,3286,3153],{},[171,3288,3289,3294,3297],{},[189,3290,3291],{},[89,3292,3293],{},"Tempo de implementação",[189,3295,3296],{},"2-8 semanas por processo",[189,3298,3299],{},"4-12 semanas",[171,3301,3302,3307,3310],{},[189,3303,3304],{},[89,3305,3306],{},"Custo inicial",[189,3308,3309],{},"Médio (licenças RPA)",[189,3311,3312],{},"Médio (desenvolvimento + API)",[171,3314,3315,3320,3323],{},[189,3316,3317],{},[89,3318,3319],{},"Custo operacional",[189,3321,3322],{},"Licença por bot + infra",[189,3324,3325],{},"API por uso + infra",[171,3327,3328,3333,3336],{},[189,3329,3330],{},[89,3331,3332],{},"Escalabilidade",[189,3334,3335],{},"Um bot por instância",[189,3337,3338],{},"Uma API atende milhares simultaneamente",[171,3340,3341,3346,3349],{},[189,3342,3343],{},[89,3344,3345],{},"Auditabilidade",[189,3347,3348],{},"Total (log de cada clique)",[189,3350,3351],{},"Requer logging adicional",[171,3353,3354,3359,3362],{},[189,3355,3356],{},[89,3357,3358],{},"Curva de aprendizado",[189,3360,3361],{},"Baixa (interface visual)",[189,3363,3364],{},"Média-alta (Python + ML)",[171,3366,3367,3372,3375],{},[189,3368,3369],{},[89,3370,3371],{},"Lida com exceções",[189,3373,3374],{},"Para e reporta erro",[189,3376,3377],{},"Tenta adaptar e decidir",[710,3379,3380],{},[86,3381,3382,3385],{},[89,3383,3384],{},"Analogia para a diferença",": RPA é como um trem nos trilhos — vai rápido\ne direto, mas se um trilho quebra, para. Automação com IA é como um motorista\n— pode desviar de obstáculos, escolher rotas alternativas, mas às vezes pode\ntomar a decisão errada.",[12,3387,3389],{"id":3388},"quando-usar-rpa-quando-rpa","Quando usar RPA {#quando-rpa}",[86,3391,3392],{},"RPA é a escolha certa quando:",[17,3394,3395,3401,3407,3413,3419,3425],{},[20,3396,3397,3400],{},[89,3398,3399],{},"O sistema não tem API",": você precisa automatizar um sistema legado\n(antigo) que só funciona via interface gráfica. Não há outra forma de\nintegrar.",[20,3402,3403,3406],{},[89,3404,3405],{},"A tarefa é 100% determinística",": sempre a mesma sequência de passos,\nsem variação. \"Abra, clique, copie, cole, feche\" — sempre igual.",[20,3408,3409,3412],{},[89,3410,3411],{},"Há volume alto de transações idênticas",": processar 10.000 faturas com\no mesmo layout todo dia. Se todas são iguais, não precisa de IA.",[20,3414,3415,3418],{},[89,3416,3417],{},"Compliance exige rastreabilidade total",": cada ação precisa ser logada\ne auditável (setor financeiro, regulado).",[20,3420,3421,3424],{},[89,3422,3423],{},"O time não tem conhecimento de ML",": RPA é mais acessível, com interfaces\nvisuais de arrastar e soltar.",[20,3426,3427,3430],{},[89,3428,3429],{},"Precisa de resultado rápido",": RPA pode ser implementado em 2 semanas\npara processos simples.",[86,3432,3433,3436],{},[89,3434,3435],{},"Exemplo prático",": Uma empresa precisa baixar relatórios de um portal\nbancário antigo, extrair valores de posições fixas no PDF (sempre no mesmo\nlugar) e inserir no ERP. Sem API, formato fixo, 500 vezes por dia. RPA é\nperfeito aqui.",[506,3438,3442],{"className":3439,"code":3440,"language":3441,"meta":515,"style":515},"language-python shiki shiki-themes github-light github-dark","# Exemplo conceitual de RPA com Python (BotCity)\n# RPA \"simula\" cliques e digitação como um humano faria\nfrom botcity.web import WebBot, By\n\nbot = WebBot()\nbot.headless = False  # mostra o navegador (útil para debug)\n\n# Passo 1: Acessar o portal bancário\nbot.browse(\"https:\u002F\u002Fportal.bancoantigo.com.br\u002Flogin\")\nbot.find_element(\"usuario\", By.ID).send_keys(\"empresa_x\")\nbot.find_element(\"senha\", By.ID).send_keys(\"senha_secreta\")\nbot.find_element(\"btn_entrar\", By.ID).click()\n\n# Passo 2: Navegar até a página de extrato\nbot.find_element(\"menu_extrato\", By.ID).click()\nbot.wait_for_element(\"data_inicio\", By.ID, timeout=10)\nbot.find_element(\"data_inicio\", By.ID).send_keys(\"01\u002F08\u002F2026\")\nbot.find_element(\"data_fim\", By.ID).send_keys(\"31\u002F08\u002F2026\")\nbot.find_element(\"btn_gerar\", By.ID).click()\n\n# Passo 3: Baixar o PDF\nbot.wait_for_element(\"btn_download\", By.ID, timeout=30)\nbot.find_element(\"btn_download\", By.ID).click()\n# O arquivo é salvo na pasta de downloads — sempre o mesmo processo, sempre igual\n","python",[513,3443,3444,3449,3454,3459,3463,3468,3473,3477,3482,3487,3492,3497,3502,3506,3511,3516,3521,3526,3531,3536,3540,3545,3550,3555],{"__ignoreMap":515},[1376,3445,3446],{"class":1378,"line":1379},[1376,3447,3448],{},"# Exemplo conceitual de RPA com Python (BotCity)\n",[1376,3450,3451],{"class":1378,"line":984},[1376,3452,3453],{},"# RPA \"simula\" cliques e digitação como um humano faria\n",[1376,3455,3456],{"class":1378,"line":981},[1376,3457,3458],{},"from botcity.web import WebBot, By\n",[1376,3460,3461],{"class":1378,"line":1395},[1376,3462,1398],{"emptyLinePlaceholder":898},[1376,3464,3465],{"class":1378,"line":1401},[1376,3466,3467],{},"bot = WebBot()\n",[1376,3469,3470],{"class":1378,"line":1407},[1376,3471,3472],{},"bot.headless = False  # mostra o navegador (útil para debug)\n",[1376,3474,3475],{"class":1378,"line":1413},[1376,3476,1398],{"emptyLinePlaceholder":898},[1376,3478,3479],{"class":1378,"line":1419},[1376,3480,3481],{},"# Passo 1: Acessar o portal bancário\n",[1376,3483,3484],{"class":1378,"line":1424},[1376,3485,3486],{},"bot.browse(\"https:\u002F\u002Fportal.bancoantigo.com.br\u002Flogin\")\n",[1376,3488,3489],{"class":1378,"line":1430},[1376,3490,3491],{},"bot.find_element(\"usuario\", By.ID).send_keys(\"empresa_x\")\n",[1376,3493,3494],{"class":1378,"line":1436},[1376,3495,3496],{},"bot.find_element(\"senha\", By.ID).send_keys(\"senha_secreta\")\n",[1376,3498,3499],{"class":1378,"line":1442},[1376,3500,3501],{},"bot.find_element(\"btn_entrar\", By.ID).click()\n",[1376,3503,3504],{"class":1378,"line":1447},[1376,3505,1398],{"emptyLinePlaceholder":898},[1376,3507,3508],{"class":1378,"line":1453},[1376,3509,3510],{},"# Passo 2: Navegar até a página de extrato\n",[1376,3512,3513],{"class":1378,"line":1459},[1376,3514,3515],{},"bot.find_element(\"menu_extrato\", By.ID).click()\n",[1376,3517,3518],{"class":1378,"line":2200},[1376,3519,3520],{},"bot.wait_for_element(\"data_inicio\", By.ID, timeout=10)\n",[1376,3522,3523],{"class":1378,"line":2208},[1376,3524,3525],{},"bot.find_element(\"data_inicio\", By.ID).send_keys(\"01\u002F08\u002F2026\")\n",[1376,3527,3528],{"class":1378,"line":2219},[1376,3529,3530],{},"bot.find_element(\"data_fim\", By.ID).send_keys(\"31\u002F08\u002F2026\")\n",[1376,3532,3533],{"class":1378,"line":2227},[1376,3534,3535],{},"bot.find_element(\"btn_gerar\", By.ID).click()\n",[1376,3537,3538],{"class":1378,"line":2241},[1376,3539,1398],{"emptyLinePlaceholder":898},[1376,3541,3542],{"class":1378,"line":2253},[1376,3543,3544],{},"# Passo 3: Baixar o PDF\n",[1376,3546,3547],{"class":1378,"line":2283},[1376,3548,3549],{},"bot.wait_for_element(\"btn_download\", By.ID, timeout=30)\n",[1376,3551,3552],{"class":1378,"line":2296},[1376,3553,3554],{},"bot.find_element(\"btn_download\", By.ID).click()\n",[1376,3556,3557],{"class":1378,"line":2311},[1376,3558,3559],{},"# O arquivo é salvo na pasta de downloads — sempre o mesmo processo, sempre igual\n",[710,3561,3562],{},[86,3563,3564,3567],{},[89,3565,3566],{},"Note",": Cada linha é uma instrução exata. Se o banco mudar a posição de\num botão ou o ID de um elemento, o bot quebra. É por isso que RPA requer\nmanutenção constante quando interfaces mudam.",[12,3569,3571],{"id":3570},"quando-usar-ia-quando-ia","Quando usar IA {#quando-ia}",[86,3573,3574],{},"Automação com IA é a escolha certa quando:",[17,3576,3577,3583,3589,3595,3601,3607],{},[20,3578,3579,3582],{},[89,3580,3581],{},"Os dados são não-estruturados",": e-mails, contratos, documentos com\nformatos variados, imagens, áudio",[20,3584,3585,3588],{},[89,3586,3587],{},"A tarefa exige interpretação",": classificar intenção, extrair informação\nespecífica, resumir, traduzir",[20,3590,3591,3594],{},[89,3592,3593],{},"Há variação nos inputs",": cada documento é diferente do anterior. Cada\ne-mail tem um tom, um formato, uma intenção diferente.",[20,3596,3597,3600],{},[89,3598,3599],{},"A tarefa envolve decisão",": priorizar, categorizar, recomendar, aprovar\nou recusar",[20,3602,3603,3606],{},[89,3604,3605],{},"O volume justifica investimento em modelo",": 1.000+ interações\u002Fdia",[20,3608,3609,3612],{},[89,3610,3611],{},"A regra é difícil de expressar em \"se\u002Fsenão\"",": \"este e-mail é urgente?\"\ndepende de contexto, tom, palavras-chave — não dá para fazer com regras\nfixas.",[86,3614,3615,3617],{},[89,3616,3435],{},": Uma empresa recebe 500 e-mails de clientes por dia.\nCada e-mail é único: alguns são reclamações, outros são dúvidas, outros são\nelogios. Alguns têm dados do pedido, outros não. A IA classifica por urgência,\nextrai dados do problema (número do pedido, produto, descrição) e direciona\npara o time certo. Cada e-mail é único, a intenção varia, a decisão depende\ndo contexto.",[506,3619,3621],{"className":3439,"code":3620,"language":3441,"meta":515,"style":515},"# Exemplo: classificação de e-mails com LLM\nfrom openai import OpenAI\nimport json\n\nclient = OpenAI()\n\ndef classificar_email(corpo_email: str) -> dict:\n    \"\"\"Classifica um e-mail de cliente por urgência e categoria.\"\"\"\n    prompt = f\"\"\"\n    Você é um assistente de triagem de e-mails. Analise o e-mail abaixo e retorne JSON:\n    {{\n      \"categoria\": \"reclamacao|duvida|elogio|solicitacao\",\n      \"urgencia\": \"alta|media|baixa\",\n      \"numero_pedido\": \"extraia se mencionado, ou null\",\n      \"resumo\": \"resumo em uma frase\"\n    }}\n\n    E-mail: {corpo_email}\n    \"\"\"\n\n    resposta = client.chat.completions.create(\n        model=\"gpt-4o-mini\",\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n        temperature=0.2,  # baixa temperatura = respostas mais consistentes\n        response_format={\"type\": \"json_object\"},  # força saída em JSON válido\n    )\n\n    return json.loads(resposta.choices[0].message.content)\n\n# Exemplo de uso\nemail = \"\"\"\nOlá, comprei o produto #4521 semana passada e ainda não chegou!\nJá se passaram 10 dias e o rastreio parou. Estou muito insatisfeito!\nUrgente!!\n\"\"\"\n\nresultado = classificar_email(email)\nprint(resultado)\n# {\n#   \"categoria\": \"reclamacao\",\n#   \"urgencia\": \"alta\",\n#   \"numero_pedido\": \"4521\",\n#   \"resumo\": \"Cliente reclama de atraso na entrega do pedido #4521\"\n# }\n# → Sistema encaminha automaticamente para o time de pós-venda com prioridade alta\n",[513,3622,3623,3628,3633,3638,3642,3647,3651,3656,3661,3666,3671,3676,3681,3686,3691,3696,3701,3705,3710,3715,3719,3724,3729,3734,3739,3744,3749,3753,3758,3762,3767,3772,3777,3782,3787,3792,3796,3801,3806,3811,3816,3821,3826,3831,3836],{"__ignoreMap":515},[1376,3624,3625],{"class":1378,"line":1379},[1376,3626,3627],{},"# Exemplo: classificação de e-mails com LLM\n",[1376,3629,3630],{"class":1378,"line":984},[1376,3631,3632],{},"from openai import OpenAI\n",[1376,3634,3635],{"class":1378,"line":981},[1376,3636,3637],{},"import json\n",[1376,3639,3640],{"class":1378,"line":1395},[1376,3641,1398],{"emptyLinePlaceholder":898},[1376,3643,3644],{"class":1378,"line":1401},[1376,3645,3646],{},"client = OpenAI()\n",[1376,3648,3649],{"class":1378,"line":1407},[1376,3650,1398],{"emptyLinePlaceholder":898},[1376,3652,3653],{"class":1378,"line":1413},[1376,3654,3655],{},"def classificar_email(corpo_email: str) -> dict:\n",[1376,3657,3658],{"class":1378,"line":1419},[1376,3659,3660],{},"    \"\"\"Classifica um e-mail de cliente por urgência e categoria.\"\"\"\n",[1376,3662,3663],{"class":1378,"line":1424},[1376,3664,3665],{},"    prompt = f\"\"\"\n",[1376,3667,3668],{"class":1378,"line":1430},[1376,3669,3670],{},"    Você é um assistente de triagem de e-mails. Analise o e-mail abaixo e retorne JSON:\n",[1376,3672,3673],{"class":1378,"line":1436},[1376,3674,3675],{},"    {{\n",[1376,3677,3678],{"class":1378,"line":1442},[1376,3679,3680],{},"      \"categoria\": \"reclamacao|duvida|elogio|solicitacao\",\n",[1376,3682,3683],{"class":1378,"line":1447},[1376,3684,3685],{},"      \"urgencia\": \"alta|media|baixa\",\n",[1376,3687,3688],{"class":1378,"line":1453},[1376,3689,3690],{},"      \"numero_pedido\": \"extraia se mencionado, ou null\",\n",[1376,3692,3693],{"class":1378,"line":1459},[1376,3694,3695],{},"      \"resumo\": \"resumo em uma frase\"\n",[1376,3697,3698],{"class":1378,"line":2200},[1376,3699,3700],{},"    }}\n",[1376,3702,3703],{"class":1378,"line":2208},[1376,3704,1398],{"emptyLinePlaceholder":898},[1376,3706,3707],{"class":1378,"line":2219},[1376,3708,3709],{},"    E-mail: {corpo_email}\n",[1376,3711,3712],{"class":1378,"line":2227},[1376,3713,3714],{},"    \"\"\"\n",[1376,3716,3717],{"class":1378,"line":2241},[1376,3718,1398],{"emptyLinePlaceholder":898},[1376,3720,3721],{"class":1378,"line":2253},[1376,3722,3723],{},"    resposta = client.chat.completions.create(\n",[1376,3725,3726],{"class":1378,"line":2283},[1376,3727,3728],{},"        model=\"gpt-4o-mini\",\n",[1376,3730,3731],{"class":1378,"line":2296},[1376,3732,3733],{},"        messages=[{\"role\": \"user\", \"content\": prompt}],\n",[1376,3735,3736],{"class":1378,"line":2311},[1376,3737,3738],{},"        temperature=0.2,  # baixa temperatura = respostas mais consistentes\n",[1376,3740,3741],{"class":1378,"line":2316},[1376,3742,3743],{},"        response_format={\"type\": \"json_object\"},  # força saída em JSON válido\n",[1376,3745,3746],{"class":1378,"line":2324},[1376,3747,3748],{},"    )\n",[1376,3750,3751],{"class":1378,"line":2333},[1376,3752,1398],{"emptyLinePlaceholder":898},[1376,3754,3755],{"class":1378,"line":2340},[1376,3756,3757],{},"    return json.loads(resposta.choices[0].message.content)\n",[1376,3759,3760],{"class":1378,"line":2351},[1376,3761,1398],{"emptyLinePlaceholder":898},[1376,3763,3764],{"class":1378,"line":2362},[1376,3765,3766],{},"# Exemplo de uso\n",[1376,3768,3769],{"class":1378,"line":2385},[1376,3770,3771],{},"email = \"\"\"\n",[1376,3773,3774],{"class":1378,"line":2396},[1376,3775,3776],{},"Olá, comprei o produto #4521 semana passada e ainda não chegou!\n",[1376,3778,3779],{"class":1378,"line":2411},[1376,3780,3781],{},"Já se passaram 10 dias e o rastreio parou. Estou muito insatisfeito!\n",[1376,3783,3784],{"class":1378,"line":2416},[1376,3785,3786],{},"Urgente!!\n",[1376,3788,3789],{"class":1378,"line":2422},[1376,3790,3791],{},"\"\"\"\n",[1376,3793,3794],{"class":1378,"line":2430},[1376,3795,1398],{"emptyLinePlaceholder":898},[1376,3797,3798],{"class":1378,"line":2448},[1376,3799,3800],{},"resultado = classificar_email(email)\n",[1376,3802,3803],{"class":1378,"line":2457},[1376,3804,3805],{},"print(resultado)\n",[1376,3807,3808],{"class":1378,"line":2464},[1376,3809,3810],{},"# {\n",[1376,3812,3813],{"class":1378,"line":2475},[1376,3814,3815],{},"#   \"categoria\": \"reclamacao\",\n",[1376,3817,3818],{"class":1378,"line":2487},[1376,3819,3820],{},"#   \"urgencia\": \"alta\",\n",[1376,3822,3823],{"class":1378,"line":2499},[1376,3824,3825],{},"#   \"numero_pedido\": \"4521\",\n",[1376,3827,3828],{"class":1378,"line":2506},[1376,3829,3830],{},"#   \"resumo\": \"Cliente reclama de atraso na entrega do pedido #4521\"\n",[1376,3832,3833],{"class":1378,"line":2517},[1376,3834,3835],{},"# }\n",[1376,3837,3838],{"class":1378,"line":2528},[1376,3839,3840],{},"# → Sistema encaminha automaticamente para o time de pós-venda com prioridade alta\n",[710,3842,3843],{},[86,3844,3845,3848],{},[89,3846,3847],{},"Compare",": Com RPA, você teria que escrever regras: \"se contém 'urgente',\nmarcar como alta\". Mas e se o cliente escreve \"PRECISO DE RESPOSTA HOJE\"\nsem usar a palavra urgente? A IA entende; as regras não.",[12,3850,3852],{"id":3851},"combinação-o-melhor-dos-dois-mundos-combinacao","Combinação: o melhor dos dois mundos {#combinacao}",[86,3854,3855,3856,3858,3859,3862,3863,522],{},"A abordagem mais poderosa é combinar RPA e IA em um único ",[89,3857,1163],{}," (fluxo\nde processamento). Isso se chama ",[89,3860,3861],{},"Hyperautomation"," (Hyperautomação) ou\n",[89,3864,3865],{},"Intelligent Process Automation (IPA)",[710,3867,3868],{},[86,3869,3870,3872],{},[89,3871,3011],{},": Imagine um restaurante. A IA é o maître que entende o pedido\ndo cliente, interpreta preferências e decide o que enviar para a cozinha.\nO RPA é o estoquista que vai ao depósito pegar os ingredientes (sistema\nantigo sem automação) e traz para a cozinha. O humano é o chef que prepara\nos pratos especiais que ninguém mais consegue. Cada um faz o que faz melhor.",[506,3874,3877],{"className":3875,"code":3876,"language":511},[509],"[E-mail recebido]\n    ↓\n[IA classifica intenção e extrai dados]     ← Automação com IA (interpreta)\n    ↓\n[IA decide: simples ou complexo?]            ← Automação com IA (decide)\n    ↓ simples                                    ↓ complexo\n[RPA executa no sistema legado]              [Encaminha para humano]\n    ↓                                             ↓\n[Confirmação enviada ao cliente]             [Humano resolve e registra]\n",[513,3878,3876],{"__ignoreMap":515},[86,3880,3881],{},"Neste modelo:",[528,3883,3884,3890,3896],{},[20,3885,3886,3889],{},[89,3887,3888],{},"IA faz o trabalho cognitivo",": entender, classificar, decidir",[20,3891,3892,3895],{},[89,3893,3894],{},"RPA faz o trabalho mecânico",": clicar, digitar, navegar em sistemas antigos",[20,3897,3898,3901],{},[89,3899,3900],{},"Humano faz o trabalho complexo",": exceções, casos especiais, decisões que\nexigem julgamento",[283,3903,3905],{"id":3904},"exemplo-real-de-combinação","Exemplo real de combinação",[86,3907,3908],{},"Uma seguradora recebe aviso de sinistro por e-mail:",[17,3910,3911,3917,3922,3930],{},[20,3912,3913,3916],{},[89,3914,3915],{},"IA"," lê o e-mail, entende que é um sinistro, extrai: número da apólice,\ndata, descrição do evento, valor estimado",[20,3918,3919,3921],{},[89,3920,3915],{}," decide: é um sinistro simples (valor \u003C R$ 5.000, sem terceiros) ou\ncomplexo?",[20,3923,3924,1679,3927,3929],{},[89,3925,3926],{},"Simples",[89,3928,394],{}," abre o sistema legado de sinistros (sem API), preenche\nos campos, registra, envia confirmação",[20,3931,3932,3935],{},[89,3933,3934],{},"Complexo",": encaminha para um analista humano com todos os dados já\nextraídos pela IA (economizando 80% do tempo do analista)",[86,3937,3938,3941],{},[89,3939,3940],{},"Resultado",": 70% dos sinistros simples processados automaticamente em\nsegundos. Analistas humanos focam nos 30% complexos.",[12,3943,3945],{"id":3944},"custos-custos","Custos {#custos}",[283,3947,3949],{"id":3948},"rpa-uipath-por-exemplo","RPA (UiPath, por exemplo)",[165,3951,3952,3961],{},[168,3953,3954],{},[171,3955,3956,3958],{},[174,3957,596],{},[174,3959,3960],{},"Custo",[184,3962,3963,3971,3979,3987,3995],{},[171,3964,3965,3968],{},[189,3966,3967],{},"Licença UiPath (Attended — bot que roda com humano presente)",[189,3969,3970],{},"~$420\u002Fmês por bot",[171,3972,3973,3976],{},[189,3974,3975],{},"Licença UiPath (Unattended — bot que roda sozinho em servidor)",[189,3977,3978],{},"~$1.500\u002Fmês por bot",[171,3980,3981,3984],{},[189,3982,3983],{},"Power Automate (alternativa mais barata, Microsoft)",[189,3985,3986],{},"~$15-30\u002Fmês por usuário",[171,3988,3989,3992],{},[189,3990,3991],{},"Desenvolvimento",[189,3993,3994],{},"2-8 semanas × R$ 100-200\u002Fhora",[171,3996,3997,3999],{},[189,3998,3281],{},[189,4000,4001],{},"10-20% do custo de desenvolvimento por ano",[710,4003,4004],{},[86,4005,4006,4009],{},[89,4007,4008],{},"Atenção",": o custo das licenças UiPath pode surpreender. Um único bot\nUnattended custa ~$18.000\u002Fano. Para pequenas empresas, Power Automate ou\nsoluções open source (BotCity, Automemphis) são alternativas mais acessíveis.",[283,4011,362],{"id":361},[165,4013,4014,4022],{},[168,4015,4016],{},[171,4017,4018,4020],{},[174,4019,596],{},[174,4021,3960],{},[184,4023,4024,4032,4040,4047,4055],{},[171,4025,4026,4029],{},[189,4027,4028],{},"API LLM (GPT-4o-mini)",[189,4030,4031],{},"~$0.001 por interação (menos de 1 centavo)",[171,4033,4034,4037],{},[189,4035,4036],{},"API LLM (GPT-4o — mais potente)",[189,4038,4039],{},"$0.01-0.10 por interação",[171,4041,4042,4044],{},[189,4043,3991],{},[189,4045,4046],{},"4-12 semanas × R$ 150-300\u002Fhora",[171,4048,4049,4052],{},[189,4050,4051],{},"Infraestrutura (API + vector store)",[189,4053,4054],{},"$10-50\u002Fmês",[171,4056,4057,4059],{},[189,4058,3281],{},[189,4060,4061],{},"5-15% do custo de desenvolvimento por ano",[710,4063,4064],{},[86,4065,4066,4069],{},[89,4067,4068],{},"Vantagem de custo da IA",": com IA, uma única API atende milhares de\ninterações simultâneas. Com RPA, cada bot roda uma tarefa por vez. Para\nalto volume, IA sai muito mais barato por interação.",[283,4071,4073],{"id":4072},"híbrido-ipa-hyperautomation","Híbrido (IPA \u002F Hyperautomation)",[86,4075,4076,4077,4080],{},"Combina os custos acima, mas com ",[89,4078,4079],{},"ROI superior"," por automatizar ponta-a-ponta\nprocessos que nem RPA nem IA resolveriam sozinhos. A economia vem de eliminar\netapas manuais no meio do processo que sempre eram o gargalo.",[283,4082,4084],{"id":4083},"comparação-de-custo-por-volume","Comparação de custo por volume",[165,4086,4087,4102],{},[168,4088,4089],{},[171,4090,4091,4094,4097,4099],{},[174,4092,4093],{},"Volume\u002Fdia",[174,4095,4096],{},"RPA (UiPath)",[174,4098,362],{},[174,4100,4101],{},"Híbrido",[184,4103,4104,4118,4132],{},[171,4105,4106,4109,4112,4115],{},[189,4107,4108],{},"100 transações",[189,4110,4111],{},"$420\u002Fmês (1 bot)",[189,4113,4114],{},"~$3\u002Fmês",[189,4116,4117],{},"~$423\u002Fmês",[171,4119,4120,4123,4126,4129],{},[189,4121,4122],{},"1.000 transações",[189,4124,4125],{},"$1.500\u002Fmês (1 bot Unattended)",[189,4127,4128],{},"~$30\u002Fmês",[189,4130,4131],{},"~$1.530\u002Fmês",[171,4133,4134,4137,4140,4143],{},[189,4135,4136],{},"10.000 transações",[189,4138,4139],{},"$3.000+\u002Fmês (múltiplos bots)",[189,4141,4142],{},"~$300\u002Fmês",[189,4144,4145],{},"~$3.300\u002Fmês",[710,4147,4148],{},[86,4149,4150,4153],{},[89,4151,4152],{},"Insight",": Para baixo volume, RPA pode ser mais simples de implementar.\nPara alto volume, IA é dramaticamente mais barata. O híbrido paga o custo\ndo RPA mas ganha a flexibilidade da IA.",[12,4155,820],{"id":819},[283,4157,4159],{"id":4158},"erro-1-usar-rpa-onde-uma-api-resolveria","Erro 1: Usar RPA onde uma API resolveria",[86,4161,4162,4165],{},[89,4163,4164],{},"O que acontece",": Em vez de pedir ao fornecedor do sistema uma API (ou\nconstruir uma), a empresa cria um bot RPA para \"simular\" um humano acessando\no sistema. Funciona, mas é frágil: cada mudança de tela quebra o bot.",[86,4167,4168,4171],{},[89,4169,4170],{},"O que NÃO fazer",":",[506,4173,4176],{"className":4174,"code":4175,"language":511},[509],"❌ Bot RPA para acessar um sistema que TEM API:\n\"Vou criar um bot para logar, navegar 5 telas, clicar em exportar,\nbaixar o CSV, abrir no Excel, copiar os valores...\"\n\nProblemas:\n- Se mudar qualquer tela, o bot quebra\n- Mais lento que uma chamada de API direta\n- Mais caro de manter que uma integração de API\n",[513,4177,4175],{"__ignoreMap":515},[86,4179,4180,4171],{},[89,4181,4182],{},"O que fazer",[506,4184,4187],{"className":4185,"code":4186,"language":511},[509],"✅ Verifique se o sistema tem API primeiro:\n1. Documentação do sistema tem seção \"API\"?\n2. O fornecedor oferece integração?\n3. Existem webhooks disponíveis?\n\nSe sim: use API. É mais rápido, estável e barato de manter.\nSe não (sistema legado sem API): aí sim, RPA é justificado.\n",[513,4188,4186],{"__ignoreMap":515},[283,4190,4192],{"id":4191},"erro-2-usar-ia-onde-regras-simples-bastam","Erro 2: Usar IA onde regras simples bastam",[86,4194,4195,4197],{},[89,4196,4164],{},": A empresa quer usar GPT-4 para classificar e-mails em\n\"urgente\" e \"não urgente\". Gasta 4 semanas desenvolvendo, paga $0.05 por\ne-mail — quando uma simples regra \"se contém 'urgente' OU 'ASAP' OU\n'hospital'\" resolveria 80% dos casos em 1 dia, de graça.",[86,4199,4200,4171],{},[89,4201,4170],{},[506,4203,4205],{"className":3439,"code":4204,"language":3441,"meta":515,"style":515},"# ❌ ERRADO: usar LLM para classificação trivial\ndef eh_urgente(email):\n    response = client.chat.completions.create(\n        model=\"gpt-4o\",\n        messages=[{\"role\": \"user\", \"content\": f\"Este e-mail é urgente? {email}\"}],\n    )\n    return \"sim\" in response.choices[0].message.content.lower()\n# Custo: $0.05\u002Femail, latência 2s, dependência de API\n",[513,4206,4207,4212,4217,4222,4227,4232,4236,4241],{"__ignoreMap":515},[1376,4208,4209],{"class":1378,"line":1379},[1376,4210,4211],{},"# ❌ ERRADO: usar LLM para classificação trivial\n",[1376,4213,4214],{"class":1378,"line":984},[1376,4215,4216],{},"def eh_urgente(email):\n",[1376,4218,4219],{"class":1378,"line":981},[1376,4220,4221],{},"    response = client.chat.completions.create(\n",[1376,4223,4224],{"class":1378,"line":1395},[1376,4225,4226],{},"        model=\"gpt-4o\",\n",[1376,4228,4229],{"class":1378,"line":1401},[1376,4230,4231],{},"        messages=[{\"role\": \"user\", \"content\": f\"Este e-mail é urgente? {email}\"}],\n",[1376,4233,4234],{"class":1378,"line":1407},[1376,4235,3748],{},[1376,4237,4238],{"class":1378,"line":1413},[1376,4239,4240],{},"    return \"sim\" in response.choices[0].message.content.lower()\n",[1376,4242,4243],{"class":1378,"line":1419},[1376,4244,4245],{},"# Custo: $0.05\u002Femail, latência 2s, dependência de API\n",[86,4247,4248,4171],{},[89,4249,4182],{},[506,4251,4253],{"className":3439,"code":4252,"language":3441,"meta":515,"style":515},"# ✅ CERTO: regra simples para casos simples, IA para casos complexos\nPALAVRAS_URGENTES = [\"urgente\", \"asap\", \"hospital\", \"imediato\", \"agora\"]\n\ndef eh_urgente_simples(email):\n    email_lower = email.lower()\n    return any(palavra in email_lower for palavra in PALAVRAS_URGENTES)\n# Custo: $0, latência \u003C1ms, sem dependência externa\n\n# Use IA apenas para os e-mails que a regra simples não consegue classificar:\ndef classificar_com_fallback(email):\n    if eh_urgente_simples(email):\n        return \"urgente\"\n    # Só chama a IA para e-mails ambíguos (economiza 80% das chamadas de API)\n    return classificar_com_ia(email)\n",[513,4254,4255,4260,4265,4269,4274,4279,4284,4289,4293,4298,4303,4308,4313,4318],{"__ignoreMap":515},[1376,4256,4257],{"class":1378,"line":1379},[1376,4258,4259],{},"# ✅ CERTO: regra simples para casos simples, IA para casos complexos\n",[1376,4261,4262],{"class":1378,"line":984},[1376,4263,4264],{},"PALAVRAS_URGENTES = [\"urgente\", \"asap\", \"hospital\", \"imediato\", \"agora\"]\n",[1376,4266,4267],{"class":1378,"line":981},[1376,4268,1398],{"emptyLinePlaceholder":898},[1376,4270,4271],{"class":1378,"line":1395},[1376,4272,4273],{},"def eh_urgente_simples(email):\n",[1376,4275,4276],{"class":1378,"line":1401},[1376,4277,4278],{},"    email_lower = email.lower()\n",[1376,4280,4281],{"class":1378,"line":1407},[1376,4282,4283],{},"    return any(palavra in email_lower for palavra in PALAVRAS_URGENTES)\n",[1376,4285,4286],{"class":1378,"line":1413},[1376,4287,4288],{},"# Custo: $0, latência \u003C1ms, sem dependência externa\n",[1376,4290,4291],{"class":1378,"line":1419},[1376,4292,1398],{"emptyLinePlaceholder":898},[1376,4294,4295],{"class":1378,"line":1424},[1376,4296,4297],{},"# Use IA apenas para os e-mails que a regra simples não consegue classificar:\n",[1376,4299,4300],{"class":1378,"line":1430},[1376,4301,4302],{},"def classificar_com_fallback(email):\n",[1376,4304,4305],{"class":1378,"line":1436},[1376,4306,4307],{},"    if eh_urgente_simples(email):\n",[1376,4309,4310],{"class":1378,"line":1442},[1376,4311,4312],{},"        return \"urgente\"\n",[1376,4314,4315],{"class":1378,"line":1447},[1376,4316,4317],{},"    # Só chama a IA para e-mails ambíguos (economiza 80% das chamadas de API)\n",[1376,4319,4320],{"class":1378,"line":1453},[1376,4321,4322],{},"    return classificar_com_ia(email)\n",[283,4324,4326],{"id":4325},"erro-3-não-planejar-manutenção-do-rpa","Erro 3: Não planejar manutenção do RPA",[86,4328,4329,4331],{},[89,4330,4164],{},": O bot RPA funciona perfeitamente em produção. Três meses\ndepois, o sistema alvo faz um update de interface e o bot para de funcionar.\nNinguém sabe como corrigir porque o desenvolvedor saiu e não há documentação.",[86,4333,4334,4171],{},[89,4335,4170],{},[506,4337,4340],{"className":4338,"code":4339,"language":511},[509],"❌ Criar o bot, colocar em produção e esquecer.\n- Sem documentação dos passos\n- Sem monitoramento de falhas\n- Sem alerta quando o bot quebra\n- O time só descobre o problema quando alguém reclama\n",[513,4341,4339],{"__ignoreMap":515},[86,4343,4344,4171],{},[89,4345,4182],{},[506,4347,4349],{"className":3439,"code":4348,"language":3441,"meta":515,"style":515},"# ✅ Documentar, monitorar e alertar\n# 1. Documentar cada passo do bot (screenshots + descrição)\n# 2. Monitorar sucesso\u002Ffalha de cada execução\n# 3. Alertar automaticamente em caso de falha\n\nimport logging\nfrom datetime import datetime\n\ndef executar_bot_com_monitoramento():\n    try:\n        bot.browse(\"https:\u002F\u002Fportal.banco.com.br\")\n        # ... passos do bot ...\n        logging.info({\n            \"bot\": \"extrato_bancario\",\n            \"status\": \"sucesso\",\n            \"timestamp\": datetime.utcnow().isoformat(),\n        })\n    except Exception as e:\n        logging.error({\n            \"bot\": \"extrato_bancario\",\n            \"status\": \"falha\",\n            \"erro\": str(e),\n            \"timestamp\": datetime.utcnow().isoformat(),\n        })\n        # Alertar via Slack\u002Fe-mail\n        enviar_alerta(f\"Bot extrato_bancario falhou: {e}\")\n",[513,4350,4351,4356,4361,4366,4371,4375,4380,4385,4389,4394,4399,4404,4409,4414,4419,4424,4429,4434,4439,4444,4448,4453,4458,4462,4466,4471],{"__ignoreMap":515},[1376,4352,4353],{"class":1378,"line":1379},[1376,4354,4355],{},"# ✅ Documentar, monitorar e alertar\n",[1376,4357,4358],{"class":1378,"line":984},[1376,4359,4360],{},"# 1. Documentar cada passo do bot (screenshots + descrição)\n",[1376,4362,4363],{"class":1378,"line":981},[1376,4364,4365],{},"# 2. Monitorar sucesso\u002Ffalha de cada execução\n",[1376,4367,4368],{"class":1378,"line":1395},[1376,4369,4370],{},"# 3. Alertar automaticamente em caso de falha\n",[1376,4372,4373],{"class":1378,"line":1401},[1376,4374,1398],{"emptyLinePlaceholder":898},[1376,4376,4377],{"class":1378,"line":1407},[1376,4378,4379],{},"import logging\n",[1376,4381,4382],{"class":1378,"line":1413},[1376,4383,4384],{},"from datetime import datetime\n",[1376,4386,4387],{"class":1378,"line":1419},[1376,4388,1398],{"emptyLinePlaceholder":898},[1376,4390,4391],{"class":1378,"line":1424},[1376,4392,4393],{},"def executar_bot_com_monitoramento():\n",[1376,4395,4396],{"class":1378,"line":1430},[1376,4397,4398],{},"    try:\n",[1376,4400,4401],{"class":1378,"line":1436},[1376,4402,4403],{},"        bot.browse(\"https:\u002F\u002Fportal.banco.com.br\")\n",[1376,4405,4406],{"class":1378,"line":1442},[1376,4407,4408],{},"        # ... passos do bot ...\n",[1376,4410,4411],{"class":1378,"line":1447},[1376,4412,4413],{},"        logging.info({\n",[1376,4415,4416],{"class":1378,"line":1453},[1376,4417,4418],{},"            \"bot\": \"extrato_bancario\",\n",[1376,4420,4421],{"class":1378,"line":1459},[1376,4422,4423],{},"            \"status\": \"sucesso\",\n",[1376,4425,4426],{"class":1378,"line":2200},[1376,4427,4428],{},"            \"timestamp\": datetime.utcnow().isoformat(),\n",[1376,4430,4431],{"class":1378,"line":2208},[1376,4432,4433],{},"        })\n",[1376,4435,4436],{"class":1378,"line":2219},[1376,4437,4438],{},"    except Exception as e:\n",[1376,4440,4441],{"class":1378,"line":2227},[1376,4442,4443],{},"        logging.error({\n",[1376,4445,4446],{"class":1378,"line":2241},[1376,4447,4418],{},[1376,4449,4450],{"class":1378,"line":2253},[1376,4451,4452],{},"            \"status\": \"falha\",\n",[1376,4454,4455],{"class":1378,"line":2283},[1376,4456,4457],{},"            \"erro\": str(e),\n",[1376,4459,4460],{"class":1378,"line":2296},[1376,4461,4428],{},[1376,4463,4464],{"class":1378,"line":2311},[1376,4465,4433],{},[1376,4467,4468],{"class":1378,"line":2316},[1376,4469,4470],{},"        # Alertar via Slack\u002Fe-mail\n",[1376,4472,4473],{"class":1378,"line":2324},[1376,4474,4475],{},"        enviar_alerta(f\"Bot extrato_bancario falhou: {e}\")\n",[283,4477,4479],{"id":4478},"erro-4-ignorar-exceções-no-pipeline-híbrido","Erro 4: Ignorar exceções no pipeline híbrido",[86,4481,4482,4484],{},[89,4483,4164],{},": O pipeline IA + RPA funciona para 90% dos casos. Os 10%\nde exceções (formato inesperado, erro do RPA, decisão ambígua da IA) não têm\ntratamento e o processo fica travado sem ninguém saber.",[86,4486,4487,4171],{},[89,4488,4170],{},[506,4490,4492],{"className":3439,"code":4491,"language":3441,"meta":515,"style":515},"# ❌ ERRADO: sem tratamento de exceções\nresultado_ia = classificar_email(email)       # e se a IA retornar algo inesperado?\nrpa_executar(resultado_ia)                     # e se o RPA falhar?\n# Se algo der errado no meio, o processo fica perdido\n",[513,4493,4494,4499,4504,4509],{"__ignoreMap":515},[1376,4495,4496],{"class":1378,"line":1379},[1376,4497,4498],{},"# ❌ ERRADO: sem tratamento de exceções\n",[1376,4500,4501],{"class":1378,"line":984},[1376,4502,4503],{},"resultado_ia = classificar_email(email)       # e se a IA retornar algo inesperado?\n",[1376,4505,4506],{"class":1378,"line":981},[1376,4507,4508],{},"rpa_executar(resultado_ia)                     # e se o RPA falhar?\n",[1376,4510,4511],{"class":1378,"line":1395},[1376,4512,4513],{},"# Se algo der errado no meio, o processo fica perdido\n",[86,4515,4516,4171],{},[89,4517,4182],{},[506,4519,4521],{"className":3439,"code":4520,"language":3441,"meta":515,"style":515},"# ✅ CERTO: tratar exceções em cada etapa + ter fila de humanos\ntry:\n    resultado_ia = classificar_email(email)\n    # Validar que a IA retornou dados coerentes\n    if not resultado_ia.get(\"categoria\") or not resultado_ia.get(\"urgencia\"):\n        raise ValueError(\"IA retornou dados incompletos\")\n\n    if resultado_ia[\"urgencia\"] == \"alta\":\n        # Casos complexos vão para humano\n        encaminhar_para_humano(email, resultado_ia)\n    else:\n        # Casos simples vão para RPA\n        try:\n            rpa_executar(resultado_ia)\n        except RPAError as e:\n            # RPA falhou? Não perde o trabalho, encaminha para humano\n            encaminhar_para_humano(email, resultado_ia, erro_rpa=str(e))\n\nexcept (APIError, TimeoutError) as e:\n    # IA indisponível? Não perde o e-mail, coloca na fila para depois\n    colocar_na_fila_retry(email, erro=str(e))\n",[513,4522,4523,4528,4533,4538,4543,4548,4553,4557,4562,4567,4572,4577,4582,4587,4592,4597,4602,4607,4611,4616,4621],{"__ignoreMap":515},[1376,4524,4525],{"class":1378,"line":1379},[1376,4526,4527],{},"# ✅ CERTO: tratar exceções em cada etapa + ter fila de humanos\n",[1376,4529,4530],{"class":1378,"line":984},[1376,4531,4532],{},"try:\n",[1376,4534,4535],{"class":1378,"line":981},[1376,4536,4537],{},"    resultado_ia = classificar_email(email)\n",[1376,4539,4540],{"class":1378,"line":1395},[1376,4541,4542],{},"    # Validar que a IA retornou dados coerentes\n",[1376,4544,4545],{"class":1378,"line":1401},[1376,4546,4547],{},"    if not resultado_ia.get(\"categoria\") or not resultado_ia.get(\"urgencia\"):\n",[1376,4549,4550],{"class":1378,"line":1407},[1376,4551,4552],{},"        raise ValueError(\"IA retornou dados incompletos\")\n",[1376,4554,4555],{"class":1378,"line":1413},[1376,4556,1398],{"emptyLinePlaceholder":898},[1376,4558,4559],{"class":1378,"line":1419},[1376,4560,4561],{},"    if resultado_ia[\"urgencia\"] == \"alta\":\n",[1376,4563,4564],{"class":1378,"line":1424},[1376,4565,4566],{},"        # Casos complexos vão para humano\n",[1376,4568,4569],{"class":1378,"line":1430},[1376,4570,4571],{},"        encaminhar_para_humano(email, resultado_ia)\n",[1376,4573,4574],{"class":1378,"line":1436},[1376,4575,4576],{},"    else:\n",[1376,4578,4579],{"class":1378,"line":1442},[1376,4580,4581],{},"        # Casos simples vão para RPA\n",[1376,4583,4584],{"class":1378,"line":1447},[1376,4585,4586],{},"        try:\n",[1376,4588,4589],{"class":1378,"line":1453},[1376,4590,4591],{},"            rpa_executar(resultado_ia)\n",[1376,4593,4594],{"class":1378,"line":1459},[1376,4595,4596],{},"        except RPAError as e:\n",[1376,4598,4599],{"class":1378,"line":2200},[1376,4600,4601],{},"            # RPA falhou? Não perde o trabalho, encaminha para humano\n",[1376,4603,4604],{"class":1378,"line":2208},[1376,4605,4606],{},"            encaminhar_para_humano(email, resultado_ia, erro_rpa=str(e))\n",[1376,4608,4609],{"class":1378,"line":2219},[1376,4610,1398],{"emptyLinePlaceholder":898},[1376,4612,4613],{"class":1378,"line":2227},[1376,4614,4615],{},"except (APIError, TimeoutError) as e:\n",[1376,4617,4618],{"class":1378,"line":2241},[1376,4619,4620],{},"    # IA indisponível? Não perde o e-mail, coloca na fila para depois\n",[1376,4622,4623],{"class":1378,"line":2253},[1376,4624,4625],{},"    colocar_na_fila_retry(email, erro=str(e))\n",[283,4627,4629],{"id":4628},"erro-5-subestimar-o-tempo-de-implementação","Erro 5: Subestimar o tempo de implementação",[86,4631,4632,4634],{},[89,4633,4164],{},": A empresa estima 2 semanas para implementar RPA ou IA.\nSem contar: organização dos dados, testes com casos reais, ajustes de prompt\u002F\nregras, integração com sistemas existentes, treinamento da equipe. Resultado:\n3 meses de atraso.",[86,4636,4637,4171],{},[89,4638,4639],{},"Estimativas realistas",[165,4641,4642,4655],{},[168,4643,4644],{},[171,4645,4646,4649,4652],{},[174,4647,4648],{},"Fase",[174,4650,4651],{},"RPA (processo simples)",[174,4653,4654],{},"IA (caso de uso simples)",[184,4656,4657,4667,4677,4687,4697,4706],{},[171,4658,4659,4662,4665],{},[189,4660,4661],{},"Mapeamento do processo",[189,4663,4664],{},"3-5 dias",[189,4666,4664],{},[171,4668,4669,4671,4674],{},[189,4670,3991],{},[189,4672,4673],{},"5-10 dias",[189,4675,4676],{},"10-20 dias",[171,4678,4679,4682,4684],{},[189,4680,4681],{},"Testes com dados reais",[189,4683,4664],{},[189,4685,4686],{},"7-14 dias",[171,4688,4689,4692,4695],{},[189,4690,4691],{},"Ajustes e otimização",[189,4693,4694],{},"2-5 dias",[189,4696,4673],{},[171,4698,4699,4702,4704],{},[189,4700,4701],{},"Integração + deploy",[189,4703,4664],{},[189,4705,4664],{},[171,4707,4708,4713,4718],{},[189,4709,4710],{},[89,4711,4712],{},"Total realista",[189,4714,4715],{},[89,4716,4717],{},"3-5 semanas",[189,4719,4720],{},[89,4721,4722],{},"5-8 semanas",[710,4724,4725],{},[86,4726,4727],{},"Multiplique sua estimativa inicial por 1.5x. Sempre aparecem coisas que\nvocê não previu.",[12,4729,4731],{"id":4730},"caso-real-financeira-que-automatizou-80-do-back-office-caso-real","Caso real: financeira que automatizou 80% do back-office {#caso-real}",[86,4733,4734],{},"Uma financeira de médio porte (150 funcionários) processava manualmente:",[528,4736,4737,4740,4743],{},[20,4738,4739],{},"300 propostas de crédito\u002Fdia (entrada via e-mail + portal legado)",[20,4741,4742],{},"150 pagamentos\u002Fdia (sistema bancário antigo sem API)",[20,4744,4745],{},"200 conciliações bancárias\u002Fdia (comparar extrato vs. sistema interno)",[86,4747,4748,4751],{},[89,4749,4750],{},"Problema",": 8 funcionários full-time só nessas tarefas. Erros manuais\nfrequentemente. Tempo médio de aprovação de crédito: 48h.",[86,4753,4754,4757],{},[89,4755,4756],{},"Solução híbrida (IPA)"," implementada em 4 meses:",[17,4759,4760,4765,4770,4775,4780],{},[20,4761,4762,4764],{},[89,4763,3915],{}," lê propostas de e-mail, extrai dados do cliente, valor, finalidade",[20,4766,4767,4769],{},[89,4768,3915],{}," pontua risco e decide: aprova automaticamente (baixo risco) ou\nencaminha para analista (alto risco)",[20,4771,4772,4774],{},[89,4773,394],{}," registra proposta aprovada no sistema legado (sem API)",[20,4776,4777,4779],{},[89,4778,394],{}," executa pagamento no portal bancário antigo",[20,4781,4782,4784],{},[89,4783,3915],{}," concilia extrato bancário vs. sistema interno, sinaliza divergências",[86,4786,4787,4171],{},[89,4788,4789],{},"Resultados em 6 meses",[528,4791,4792,4795,4798,4801,4804,4807],{},[20,4793,4794],{},"80% do back-office automatizado",[20,4796,4797],{},"Tempo de aprovação de crédito: 48h → 2h",[20,4799,4800],{},"Erros manuais: reduzidos em 92%",[20,4802,4803],{},"5 dos 8 funcionários realocados para análise de crédito complexa",[20,4805,4806],{},"ROI em 5 meses (economia de salários vs. custo da solução)",[20,4808,4809],{},"Custo mensal da solução: ~$2.500 (RPA + IA + infra)",[86,4811,4812,4815],{},[89,4813,4814],{},"A lição",": Não escolha entre RPA e IA — combine-os. RPA para o trabalho\nmecânico em sistemas antigos, IA para o trabalho cognitivo de interpretação\ne decisão.",[968,4817],{},[86,4819,4820,4823,4824,522],{},[89,4821,4822],{},"Quer automatizar processos na sua empresa?"," A Inicialize Tec implementa\nRPA, automação com IA e soluções híbridas. ",[23,4825,2881],{"href":978},[2883,4827,4828],{},"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);}",{"title":515,"searchDepth":981,"depth":981,"links":4830},[4831,4832,4835,4838,4839,4840,4841,4844,4850,4857],{"id":14,"depth":984,"text":15},{"id":2994,"depth":984,"text":2995,"children":4833},[4834],{"id":3074,"depth":981,"text":3075},{"id":3104,"depth":984,"text":3105,"children":4836},[4837],{"id":3183,"depth":981,"text":3184},{"id":3219,"depth":984,"text":3220},{"id":3388,"depth":984,"text":3389},{"id":3570,"depth":984,"text":3571},{"id":3851,"depth":984,"text":3852,"children":4842},[4843],{"id":3904,"depth":981,"text":3905},{"id":3944,"depth":984,"text":3945,"children":4845},[4846,4847,4848,4849],{"id":3948,"depth":981,"text":3949},{"id":361,"depth":981,"text":362},{"id":4072,"depth":981,"text":4073},{"id":4083,"depth":981,"text":4084},{"id":819,"depth":984,"text":820,"children":4851},[4852,4853,4854,4855,4856],{"id":4158,"depth":981,"text":4159},{"id":4191,"depth":981,"text":4192},{"id":4325,"depth":981,"text":4326},{"id":4478,"depth":981,"text":4479},{"id":4628,"depth":981,"text":4629},{"id":4730,"depth":984,"text":4731},"RPA ou IA para automação de processos? Comparativo técnico, custos e quando combinar os dois em hyperautomation. Guia decisório para empresas.",[394,4860,1034,4861,3018,4862],"automação com IA","RPA vs IA","automação inteligente",{},"\u002Fautomacao\u002Frpa-vs-automacao-com-ia",{"title":2933,"description":4858},"rpa-vs-automacao-com-ia","automacao\u002Frpa-vs-automacao-com-ia","lHpURmGpp8FTdQqbBlWLDk-rkGtOHx3-6PMyGrL5Yls",{"id":4870,"title":4871,"author":7,"body":4872,"category":6869,"date":1028,"description":6870,"extension":1030,"keywords":6871,"meta":6877,"navigation":898,"path":6878,"seo":6879,"slug":6880,"stem":6881,"updated":1028,"__hash__":6882},"content\u002Fia\u002Fautomacao-de-atendimento-com-ia.md","Automação de atendimento com IA: do chatbot ao agente",{"type":9,"value":4873,"toc":6844},[4874,4876,4942,4946,4953,4981,4986,5000,5003,5078,5082,5085,5095,5100,5111,5200,5203,5207,5214,5232,5235,5356,5363,5366,5374,5378,5385,5388,5393,5397,5403,5407,5627,5630,5634,5641,5664,5712,5716,5727,5733,5757,5768,5772,5775,5800,6232,6242,6246,6249,6281,6285,6291,6294,6337,6341,6467,6480,6484,6488,6491,6583,6586,6590,6593,6597,6600,6604,6698,6704,6710,6714,6717,6749,6763,6765,6830,6832,6842],[12,4875,15],{"id":14},[17,4877,4878,4884,4890,4896,4902,4908,4914,4920,4926,4932,4938],{},[20,4879,4880],{},[23,4881,4883],{"href":4882},"#evolucao","Evolução do atendimento automático",[20,4885,4886],{},[23,4887,4889],{"href":4888},"#nivel-1","Nível 1: chatbot de regras",[20,4891,4892],{},[23,4893,4895],{"href":4894},"#nivel-2","Nível 2: chatbot com NLP",[20,4897,4898],{},[23,4899,4901],{"href":4900},"#nivel-3","Nível 3: assistente com RAG",[20,4903,4904],{},[23,4905,4907],{"href":4906},"#nivel-4","Nível 4: agente autônomo",[20,4909,4910],{},[23,4911,4913],{"href":4912},"#arquitetura","Arquitetura de referência",[20,4915,4916],{},[23,4917,4919],{"href":4918},"#ferramentas","Ferramentas e stack",[20,4921,4922],{},[23,4923,4925],{"href":4924},"#canais","Integração com canais",[20,4927,4928],{},[23,4929,4931],{"href":4930},"#metricas","Métricas que importam",[20,4933,4934],{},[23,4935,4937],{"href":4936},"#nao-automatizar","Quando não automatizar",[20,4939,4940],{},[23,4941,74],{"href":73},[12,4943,4945],{"id":4944},"evolução-do-atendimento-automático-evolucao","Evolução do atendimento automático {#evolucao}",[86,4947,4948,4949,4952],{},"Atendimento automático não é novidade. O que muda em 2026 é a ",[89,4950,4951],{},"qualidade","\nda resposta. Vamos por níveis — porque muita gente ainda confunde chatbot de\n2015 (aquele que responde \"não entendi, escolha uma opção\") com assistente\nde IA de 2026 (que entende linguagem natural, consulta seus dados e age).",[86,4954,4955,4958,4959,4962,4963,4958,4966,4969,4970,4958,4973,4976,4977,4980],{},[89,4956,4957],{},"NLP"," (",[501,4960,4961],{},"Natural Language Processing",", processamento de linguagem natural)\né a área de IA que lida com texto e fala humana. ",[89,4964,4965],{},"LLM",[501,4967,4968],{},"Large Language\nModel",") é o modelo de IA treinado para entender e gerar texto — o ChatGPT é\no exemplo mais famoso. ",[89,4971,4972],{},"RAG",[501,4974,4975],{},"Retrieval-Augmented Generation",") é a\ntécnica onde o LLM recebe trechos relevantes dos seus documentos como\ncontexto, antes de responder. ",[89,4978,4979],{},"Agente"," é um LLM que recebe ferramentas e\ndecide sozinho qual usar.",[86,4982,4983,4985],{},[89,4984,3011],{},": pense nos níveis como estágios de um atendente:",[528,4987,4988,4991,4994,4997],{},[20,4989,4990],{},"Nível 1: estagiário com script (só sabe o que está no manual)",[20,4992,4993],{},"Nível 2: atendente experiente (entende variantes da pergunta)",[20,4995,4996],{},"Nível 3: atendente com acesso ao sistema (consulta, responde com dados)",[20,4998,4999],{},"Nível 4: supervisor (decide sozinho, executa, resolve o problema)",[86,5001,5002],{},"Ninguém começa no nível 4. Cada nível resolve um problema, tem um custo e\num risco. Escolher o nível certo é mais importante que escolher o modelo.",[165,5004,5005,5021],{},[168,5006,5007],{},[171,5008,5009,5012,5015,5018],{},[174,5010,5011],{},"Nível",[174,5013,5014],{},"Tecnologia",[174,5016,5017],{},"Resolução esperada",[174,5019,5020],{},"Custo\u002Fquery",[184,5022,5023,5037,5050,5064],{},[171,5024,5025,5028,5031,5034],{},[189,5026,5027],{},"1",[189,5029,5030],{},"Regras\u002Fárvore de decisão",[189,5032,5033],{},"20–30%",[189,5035,5036],{},"~$0",[171,5038,5039,5041,5044,5047],{},[189,5040,1891],{},[189,5042,5043],{},"NLP + intenção",[189,5045,5046],{},"35–45%",[189,5048,5049],{},"~$0,0005",[171,5051,5052,5055,5058,5061],{},[189,5053,5054],{},"3",[189,5056,5057],{},"LLM + RAG",[189,5059,5060],{},"55–70%",[189,5062,5063],{},"~$0,001",[171,5065,5066,5069,5072,5075],{},[189,5067,5068],{},"4",[189,5070,5071],{},"Agente autônomo",[189,5073,5074],{},"65–80%",[189,5076,5077],{},"~$0,005–0,02",[12,5079,5081],{"id":5080},"nível-1-chatbot-de-regras-nivel-1","Nível 1: chatbot de regras {#nivel-1}",[86,5083,5084],{},"O clássico \"Digite 1 para vendas, 2 para suporte\". Funciona com árvore de\ndecisão: o usuário escolhe opções, o sistema responde com texto pré-definido.",[86,5086,5087,5090,5091,5094],{},[89,5088,5089],{},"Pró",": custo zero, previsível, fácil de implementar (um JSON resolve).\n",[89,5092,5093],{},"Contra",": não entende linguagem natural. \"Quero cancelar\" e \"como faço\npara encerrar a conta\" são a mesma intenção, mas um chatbot de regras não\nsabe.",[86,5096,5097,4171],{},[89,5098,5099],{},"Quando ainda faz sentido",[528,5101,5102,5105,5108],{},[20,5103,5104],{},"Triagem inicial (vendas \u002F suporte \u002F financeiro)",[20,5106,5107],{},"Menu de autoatendimento em sistemas internos",[20,5109,5110],{},"Como camada antes do humano ou da IA (roteamento)",[506,5112,5114],{"className":3439,"code":5113,"language":3441,"meta":515,"style":515},"# Exemplo de chatbot de regras - sem IA, só uma árvore\narvore = {\n    \"inicio\": {\n        \"msg\": \"Olá! Escolha: 1) Vendas 2) Suporte 3) Financeiro\",\n        \"opcoes\": {\"1\": \"vendas\", \"2\": \"suporte\", \"3\": \"financeiro\"},\n    },\n    \"vendas\": {\"msg\": \"Vendas: contato@empresa.com\", \"opcoes\": {}},\n    \"suporte\": {\"msg\": \"Suporte: abra um ticket em ajuda.empresa.com\",\n                \"opcoes\": {}},\n    \"financeiro\": {\"msg\": \"Financeiro: fatura@empresa.com\", \"opcoes\": {}},\n}\n\ndef conversar(estado: str, entrada: str) -> tuple[str, str]:\n    \"\"\"Retorna (mensagem, próximo_estado).\"\"\"\n    no = arvore[estado]\n    proximo = no[\"opcoes\"].get(entrada, estado)\n    return arvore[proximo][\"msg\"], proximo\n",[513,5115,5116,5121,5126,5131,5136,5141,5146,5151,5156,5161,5166,5171,5175,5180,5185,5190,5195],{"__ignoreMap":515},[1376,5117,5118],{"class":1378,"line":1379},[1376,5119,5120],{},"# Exemplo de chatbot de regras - sem IA, só uma árvore\n",[1376,5122,5123],{"class":1378,"line":984},[1376,5124,5125],{},"arvore = {\n",[1376,5127,5128],{"class":1378,"line":981},[1376,5129,5130],{},"    \"inicio\": {\n",[1376,5132,5133],{"class":1378,"line":1395},[1376,5134,5135],{},"        \"msg\": \"Olá! Escolha: 1) Vendas 2) Suporte 3) Financeiro\",\n",[1376,5137,5138],{"class":1378,"line":1401},[1376,5139,5140],{},"        \"opcoes\": {\"1\": \"vendas\", \"2\": \"suporte\", \"3\": \"financeiro\"},\n",[1376,5142,5143],{"class":1378,"line":1407},[1376,5144,5145],{},"    },\n",[1376,5147,5148],{"class":1378,"line":1413},[1376,5149,5150],{},"    \"vendas\": {\"msg\": \"Vendas: contato@empresa.com\", \"opcoes\": {}},\n",[1376,5152,5153],{"class":1378,"line":1419},[1376,5154,5155],{},"    \"suporte\": {\"msg\": \"Suporte: abra um ticket em ajuda.empresa.com\",\n",[1376,5157,5158],{"class":1378,"line":1424},[1376,5159,5160],{},"                \"opcoes\": {}},\n",[1376,5162,5163],{"class":1378,"line":1430},[1376,5164,5165],{},"    \"financeiro\": {\"msg\": \"Financeiro: fatura@empresa.com\", \"opcoes\": {}},\n",[1376,5167,5168],{"class":1378,"line":1436},[1376,5169,5170],{},"}\n",[1376,5172,5173],{"class":1378,"line":1442},[1376,5174,1398],{"emptyLinePlaceholder":898},[1376,5176,5177],{"class":1378,"line":1447},[1376,5178,5179],{},"def conversar(estado: str, entrada: str) -> tuple[str, str]:\n",[1376,5181,5182],{"class":1378,"line":1453},[1376,5183,5184],{},"    \"\"\"Retorna (mensagem, próximo_estado).\"\"\"\n",[1376,5186,5187],{"class":1378,"line":1459},[1376,5188,5189],{},"    no = arvore[estado]\n",[1376,5191,5192],{"class":1378,"line":2200},[1376,5193,5194],{},"    proximo = no[\"opcoes\"].get(entrada, estado)\n",[1376,5196,5197],{"class":1378,"line":2208},[1376,5198,5199],{},"    return arvore[proximo][\"msg\"], proximo\n",[86,5201,5202],{},"Código limpo: cada nó da árvore tem uma mensagem e opções que levam a outros\nnós. Simples, mas limitado.",[12,5204,5206],{"id":5205},"nível-2-chatbot-com-nlp-nivel-2","Nível 2: chatbot com NLP {#nivel-2}",[86,5208,5209,5210,5213],{},"Aqui entra NLP para identificar a ",[89,5211,5212],{},"intenção"," do usuário. Em vez de menus,\no usuário digita livremente e o sistema classifica:",[528,5215,5216,5222,5227],{},[20,5217,5218,5219],{},"\"quero cancelar\" → intenção ",[513,5220,5221],{},"cancelamento",[20,5223,5224,5225],{},"\"como cancelo?\" → intenção ",[513,5226,5221],{},[20,5228,5229,5230],{},"\"preciso encerrar minha conta\" → intenção ",[513,5231,5221],{},[86,5233,5234],{},"Três variações, mesma intenção. Antes dos LLMs, usava-se um classificador\ntreinado (ex: Rasa, Dialogflow). Hoje, um LLM pequeno faz isso melhor e mais\nbarato:",[506,5236,5238],{"className":3439,"code":5237,"language":3441,"meta":515,"style":515},"from openai import OpenAI\nclient = OpenAI()\n\nINTENCOES = {\n    \"cancelamento\": \"Roteia para o fluxo de cancelamento.\",\n    \"duvida_produto\": \"Busca na base de conhecimento.\",\n    \"reclamacao\": \"Encaminha para humano com prioridade alta.\",\n    \"elogio\": \"Registra e agradece.\",\n    \"outro\": \"Pede esclarecimento.\",\n}\n\ndef classificar_intencao(mensagem: str) -> str:\n    \"\"\"Classifica a intenção do usuário com um LLM pequeno e barato.\"\"\"\n    prompt = f\"\"\"Classifique a intenção do usuário em uma destas:\n{list(INTENCOES.keys())}\n\nMensagem: \"{mensagem}\"\n\nResponda só com o nome da intenção.\"\"\"\n    r = client.chat.completions.create(\n        model=\"gpt-4o-mini\",  # barato e suficiente para classificação\n        temperature=0,\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n    )\n    return r.choices[0].message.content.strip()\n",[513,5239,5240,5244,5248,5252,5257,5262,5267,5272,5277,5282,5286,5290,5295,5300,5305,5310,5314,5319,5323,5328,5333,5338,5343,5347,5351],{"__ignoreMap":515},[1376,5241,5242],{"class":1378,"line":1379},[1376,5243,3632],{},[1376,5245,5246],{"class":1378,"line":984},[1376,5247,3646],{},[1376,5249,5250],{"class":1378,"line":981},[1376,5251,1398],{"emptyLinePlaceholder":898},[1376,5253,5254],{"class":1378,"line":1395},[1376,5255,5256],{},"INTENCOES = {\n",[1376,5258,5259],{"class":1378,"line":1401},[1376,5260,5261],{},"    \"cancelamento\": \"Roteia para o fluxo de cancelamento.\",\n",[1376,5263,5264],{"class":1378,"line":1407},[1376,5265,5266],{},"    \"duvida_produto\": \"Busca na base de conhecimento.\",\n",[1376,5268,5269],{"class":1378,"line":1413},[1376,5270,5271],{},"    \"reclamacao\": \"Encaminha para humano com prioridade alta.\",\n",[1376,5273,5274],{"class":1378,"line":1419},[1376,5275,5276],{},"    \"elogio\": \"Registra e agradece.\",\n",[1376,5278,5279],{"class":1378,"line":1424},[1376,5280,5281],{},"    \"outro\": \"Pede esclarecimento.\",\n",[1376,5283,5284],{"class":1378,"line":1430},[1376,5285,5170],{},[1376,5287,5288],{"class":1378,"line":1436},[1376,5289,1398],{"emptyLinePlaceholder":898},[1376,5291,5292],{"class":1378,"line":1442},[1376,5293,5294],{},"def classificar_intencao(mensagem: str) -> str:\n",[1376,5296,5297],{"class":1378,"line":1447},[1376,5298,5299],{},"    \"\"\"Classifica a intenção do usuário com um LLM pequeno e barato.\"\"\"\n",[1376,5301,5302],{"class":1378,"line":1453},[1376,5303,5304],{},"    prompt = f\"\"\"Classifique a intenção do usuário em uma destas:\n",[1376,5306,5307],{"class":1378,"line":1459},[1376,5308,5309],{},"{list(INTENCOES.keys())}\n",[1376,5311,5312],{"class":1378,"line":2200},[1376,5313,1398],{"emptyLinePlaceholder":898},[1376,5315,5316],{"class":1378,"line":2208},[1376,5317,5318],{},"Mensagem: \"{mensagem}\"\n",[1376,5320,5321],{"class":1378,"line":2219},[1376,5322,1398],{"emptyLinePlaceholder":898},[1376,5324,5325],{"class":1378,"line":2227},[1376,5326,5327],{},"Responda só com o nome da intenção.\"\"\"\n",[1376,5329,5330],{"class":1378,"line":2241},[1376,5331,5332],{},"    r = client.chat.completions.create(\n",[1376,5334,5335],{"class":1378,"line":2253},[1376,5336,5337],{},"        model=\"gpt-4o-mini\",  # barato e suficiente para classificação\n",[1376,5339,5340],{"class":1378,"line":2283},[1376,5341,5342],{},"        temperature=0,\n",[1376,5344,5345],{"class":1378,"line":2296},[1376,5346,3733],{},[1376,5348,5349],{"class":1378,"line":2311},[1376,5350,3748],{},[1376,5352,5353],{"class":1378,"line":2316},[1376,5354,5355],{},"    return r.choices[0].message.content.strip()\n",[86,5357,5358,5359,5362],{},"Comentários explicam: listamos as intenções possíveis, mandamos a mensagem\ndo usuário e o LLM responde só com o nome da intenção. ",[513,5360,5361],{},"temperature=0"," para\nclassificação determinística.",[86,5364,5365],{},"A partir da intenção, o sistema roteia para o fluxo certo — que pode ser\nautomático (enviar link de cancelamento), um humano, ou um sub-chatbot\nespecializado.",[86,5367,5368,5370,5371,5373],{},[89,5369,5089],{},": entende linguagem natural, variações de frase, erros de digitação.\n",[89,5372,5093],{},": ainda é \"roteamento\" — não responde a pergunta, só decide quem\nresponde.",[12,5375,5377],{"id":5376},"nível-3-assistente-com-rag-nivel-3","Nível 3: assistente com RAG {#nivel-3}",[86,5379,5380,5381,5384],{},"Aqui o sistema ",[89,5382,5383],{},"responde a pergunta",", com base no seu conhecimento\ninterno. É o nível mais comum em 2026 e onde a maioria dos projetos deveria\ncomeçar.",[86,5386,5387],{},"RAG, em uma frase: antes de responder, o sistema busca no seu acavo os\ntrechos mais relevantes e os entrega ao LLM como contexto. O LLM então\nresponde \"olhando\" para os seus dados.",[86,5389,5390,5392],{},[89,5391,3011],{},": é o atendente que tem acesso ao seu sistema de documentos.\nQuando o cliente pergunta, ele consulta o manual e responde com base no que\nleu — em vez de tentar adivinhar.",[283,5394,5396],{"id":5395},"pipeline-típico","Pipeline típico",[506,5398,5401],{"className":5399,"code":5400,"language":511},[509],"Cliente pergunta\n    ↓\n[1] Classifica intenção (nível 2)\n    ↓\n[2] Se for dúvida de produto → busca no banco vetorial (pgvector)\n    ↓\n[3] Recupera 3–5 trechos relevantes\n    ↓\n[4] Monta prompt com contexto + pergunta\n    ↓\n[5] LLM gera resposta citando a fonte\n    ↓\n[6] Se confiança baixa → sugere humano\n    ↓\nResposta ao cliente\n",[513,5402,5400],{"__ignoreMap":515},[283,5404,5406],{"id":5405},"exemplo-de-código","Exemplo de código",[506,5408,5410],{"className":3439,"code":5409,"language":3441,"meta":515,"style":515},"from openai import OpenAI\nimport psycopg\n\nclient = OpenAI()\n\ndef responder_com_rag(pergunta: str) -> dict:\n    \"\"\"Responde a pergunta usando RAG com pgvector.\n    Retorna a resposta e as fontes consultadas.\"\"\"\n\n    # 1. Embedding da pergunta\n    emb = client.embeddings.create(\n        model=\"text-embedding-3-small\", input=pergunta\n    ).data[0].embedding\n\n    # 2. Buscar contexto no Postgres\n    with psycopg.connect(\"postgresql:\u002F\u002F...\") as conn, conn.cursor() as cur:\n        cur.execute(\"\"\"\n            SELECT fonte, conteudo\n            FROM documentos\n            ORDER BY embedding \u003C=> %s::vector\n            LIMIT 4\n        \"\"\", (emb,))\n        contexto = cur.fetchall()\n\n    # 3. Montar prompt\n    ctx = \"\\n\\n\".join(f\"[{f}] {c}\" for f, c in contexto)\n    prompt = f\"\"\"Você é um atendente. Responda só com base no contexto.\nSe não souber, diga que vai transferir para humano. Cite a fonte.\n\nContexto:\n{ctx}\n\nPergunta: {pergunta}\n\nResposta:\"\"\"\n\n    # 4. Gerar resposta\n    r = client.chat.completions.create(\n        model=\"gpt-4o-mini\",\n        temperature=0.2,\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n    )\n    return {\n        \"resposta\": r.choices[0].message.content,\n        \"fontes\": [f for f, _ in contexto],\n    }\n",[513,5411,5412,5416,5421,5425,5429,5433,5438,5443,5448,5452,5457,5462,5467,5472,5476,5481,5486,5491,5496,5501,5506,5511,5516,5521,5525,5530,5535,5540,5545,5549,5554,5559,5563,5568,5572,5577,5581,5586,5590,5594,5599,5603,5607,5612,5617,5622],{"__ignoreMap":515},[1376,5413,5414],{"class":1378,"line":1379},[1376,5415,3632],{},[1376,5417,5418],{"class":1378,"line":984},[1376,5419,5420],{},"import psycopg\n",[1376,5422,5423],{"class":1378,"line":981},[1376,5424,1398],{"emptyLinePlaceholder":898},[1376,5426,5427],{"class":1378,"line":1395},[1376,5428,3646],{},[1376,5430,5431],{"class":1378,"line":1401},[1376,5432,1398],{"emptyLinePlaceholder":898},[1376,5434,5435],{"class":1378,"line":1407},[1376,5436,5437],{},"def responder_com_rag(pergunta: str) -> dict:\n",[1376,5439,5440],{"class":1378,"line":1413},[1376,5441,5442],{},"    \"\"\"Responde a pergunta usando RAG com pgvector.\n",[1376,5444,5445],{"class":1378,"line":1419},[1376,5446,5447],{},"    Retorna a resposta e as fontes consultadas.\"\"\"\n",[1376,5449,5450],{"class":1378,"line":1424},[1376,5451,1398],{"emptyLinePlaceholder":898},[1376,5453,5454],{"class":1378,"line":1430},[1376,5455,5456],{},"    # 1. Embedding da pergunta\n",[1376,5458,5459],{"class":1378,"line":1436},[1376,5460,5461],{},"    emb = client.embeddings.create(\n",[1376,5463,5464],{"class":1378,"line":1442},[1376,5465,5466],{},"        model=\"text-embedding-3-small\", input=pergunta\n",[1376,5468,5469],{"class":1378,"line":1447},[1376,5470,5471],{},"    ).data[0].embedding\n",[1376,5473,5474],{"class":1378,"line":1453},[1376,5475,1398],{"emptyLinePlaceholder":898},[1376,5477,5478],{"class":1378,"line":1459},[1376,5479,5480],{},"    # 2. Buscar contexto no Postgres\n",[1376,5482,5483],{"class":1378,"line":2200},[1376,5484,5485],{},"    with psycopg.connect(\"postgresql:\u002F\u002F...\") as conn, conn.cursor() as cur:\n",[1376,5487,5488],{"class":1378,"line":2208},[1376,5489,5490],{},"        cur.execute(\"\"\"\n",[1376,5492,5493],{"class":1378,"line":2219},[1376,5494,5495],{},"            SELECT fonte, conteudo\n",[1376,5497,5498],{"class":1378,"line":2227},[1376,5499,5500],{},"            FROM documentos\n",[1376,5502,5503],{"class":1378,"line":2241},[1376,5504,5505],{},"            ORDER BY embedding \u003C=> %s::vector\n",[1376,5507,5508],{"class":1378,"line":2253},[1376,5509,5510],{},"            LIMIT 4\n",[1376,5512,5513],{"class":1378,"line":2283},[1376,5514,5515],{},"        \"\"\", (emb,))\n",[1376,5517,5518],{"class":1378,"line":2296},[1376,5519,5520],{},"        contexto = cur.fetchall()\n",[1376,5522,5523],{"class":1378,"line":2311},[1376,5524,1398],{"emptyLinePlaceholder":898},[1376,5526,5527],{"class":1378,"line":2316},[1376,5528,5529],{},"    # 3. Montar prompt\n",[1376,5531,5532],{"class":1378,"line":2324},[1376,5533,5534],{},"    ctx = \"\\n\\n\".join(f\"[{f}] {c}\" for f, c in contexto)\n",[1376,5536,5537],{"class":1378,"line":2333},[1376,5538,5539],{},"    prompt = f\"\"\"Você é um atendente. Responda só com base no contexto.\n",[1376,5541,5542],{"class":1378,"line":2340},[1376,5543,5544],{},"Se não souber, diga que vai transferir para humano. Cite a fonte.\n",[1376,5546,5547],{"class":1378,"line":2351},[1376,5548,1398],{"emptyLinePlaceholder":898},[1376,5550,5551],{"class":1378,"line":2362},[1376,5552,5553],{},"Contexto:\n",[1376,5555,5556],{"class":1378,"line":2385},[1376,5557,5558],{},"{ctx}\n",[1376,5560,5561],{"class":1378,"line":2396},[1376,5562,1398],{"emptyLinePlaceholder":898},[1376,5564,5565],{"class":1378,"line":2411},[1376,5566,5567],{},"Pergunta: {pergunta}\n",[1376,5569,5570],{"class":1378,"line":2416},[1376,5571,1398],{"emptyLinePlaceholder":898},[1376,5573,5574],{"class":1378,"line":2422},[1376,5575,5576],{},"Resposta:\"\"\"\n",[1376,5578,5579],{"class":1378,"line":2430},[1376,5580,1398],{"emptyLinePlaceholder":898},[1376,5582,5583],{"class":1378,"line":2448},[1376,5584,5585],{},"    # 4. Gerar resposta\n",[1376,5587,5588],{"class":1378,"line":2457},[1376,5589,5332],{},[1376,5591,5592],{"class":1378,"line":2464},[1376,5593,3728],{},[1376,5595,5596],{"class":1378,"line":2475},[1376,5597,5598],{},"        temperature=0.2,\n",[1376,5600,5601],{"class":1378,"line":2487},[1376,5602,3733],{},[1376,5604,5605],{"class":1378,"line":2499},[1376,5606,3748],{},[1376,5608,5609],{"class":1378,"line":2506},[1376,5610,5611],{},"    return {\n",[1376,5613,5614],{"class":1378,"line":2517},[1376,5615,5616],{},"        \"resposta\": r.choices[0].message.content,\n",[1376,5618,5619],{"class":1378,"line":2528},[1376,5620,5621],{},"        \"fontes\": [f for f, _ in contexto],\n",[1376,5623,5624],{"class":1378,"line":2539},[1376,5625,5626],{},"    }\n",[86,5628,5629],{},"Cada passo numerado corresponde a uma etapa do pipeline: embedding, busca,\nprompt, geração. A função devolve a resposta e as fontes — para você poder\nauditar de onde veio a informação.",[283,5631,5633],{"id":5632},"quando-o-assistente-deve-escalar-para-humano","Quando o assistente deve escalar para humano",[86,5635,5636,5637,5640],{},"Sempre defina regras claras de ",[89,5638,5639],{},"handoff"," (transferência para humano):",[528,5642,5643,5646,5649,5658,5661],{},[20,5644,5645],{},"Cliente pede explicitamente (\"quero falar com pessoa\")",[20,5647,5648],{},"Confiança baixa (você mede pela distância dos embeddings — acima de 0,7,\nprovavelmente não há contexto relevante)",[20,5650,5651,5652,5655,5656],{},"Intenção classificada como ",[513,5653,5654],{},"reclamacao"," ou ",[513,5657,5221],{},[20,5659,5660],{},"Conversa entra em loop (mesma pergunta 3 vezes)",[20,5662,5663],{},"Palavras-chave sensíveis (urgência, jurídico, dados pessoais)",[506,5665,5667],{"className":3439,"code":5666,"language":3441,"meta":515,"style":515},"def deve_transferir(pergunta: str, distancia_min: float) -> bool:\n    \"\"\"Decide se transfere para humano.\"\"\"\n    if \"humano\" in pergunta.lower() or \"pessoa\" in pergunta.lower():\n        return True\n    if distancia_min > 0.7:  # contexto não foi relevante\n        return True\n    if any(p in pergunta.lower() for p in [\"cancelar\", \"processo\", \"advogado\"]):\n        return True\n    return False\n",[513,5668,5669,5674,5679,5684,5689,5694,5698,5703,5707],{"__ignoreMap":515},[1376,5670,5671],{"class":1378,"line":1379},[1376,5672,5673],{},"def deve_transferir(pergunta: str, distancia_min: float) -> bool:\n",[1376,5675,5676],{"class":1378,"line":984},[1376,5677,5678],{},"    \"\"\"Decide se transfere para humano.\"\"\"\n",[1376,5680,5681],{"class":1378,"line":981},[1376,5682,5683],{},"    if \"humano\" in pergunta.lower() or \"pessoa\" in pergunta.lower():\n",[1376,5685,5686],{"class":1378,"line":1395},[1376,5687,5688],{},"        return True\n",[1376,5690,5691],{"class":1378,"line":1401},[1376,5692,5693],{},"    if distancia_min > 0.7:  # contexto não foi relevante\n",[1376,5695,5696],{"class":1378,"line":1407},[1376,5697,5688],{},[1376,5699,5700],{"class":1378,"line":1413},[1376,5701,5702],{},"    if any(p in pergunta.lower() for p in [\"cancelar\", \"processo\", \"advogado\"]):\n",[1376,5704,5705],{"class":1378,"line":1419},[1376,5706,5688],{},[1376,5708,5709],{"class":1378,"line":1424},[1376,5710,5711],{},"    return False\n",[12,5713,5715],{"id":5714},"nível-4-agente-autônomo-nivel-4","Nível 4: agente autônomo {#nivel-4}",[86,5717,5718,5719,5722,5723,5726],{},"No nível 3, o assistente ",[89,5720,5721],{},"responde",". No nível 4, ele ",[89,5724,5725],{},"age",": executa\nações no sistema, não só fala. Um agente recebe ferramentas (funções que\nele pode chamar) e decide sozinho qual usar.",[86,5728,5729,5732],{},[89,5730,5731],{},"Exemplo",": cliente pergunta \"qual o status do meu pedido #12345?\"",[528,5734,5735,5738,5744,5747],{},[20,5736,5737],{},"Nível 1: \"Escolha: 1) rastrear 2) cancelar\"",[20,5739,5740,5741],{},"Nível 2: classifica intenção ",[513,5742,5743],{},"rastrear_pedido",[20,5745,5746],{},"Nível 3: busca em FAQ \"como rastrear\" e responde com o link",[20,5748,5749,5752,5753,5756],{},[89,5750,5751],{},"Nível 4",": chama a função ",[513,5754,5755],{},"buscar_pedido(12345)",", vê que está em\ntrânsito, e responde \"Seu pedido está em trânsito, chega amanhã\"",[86,5758,5759,5760,5763,5764,5767],{},"O nível 4 ",[89,5761,5762],{},"resolve"," o problema; os outros ",[89,5765,5766],{},"orientam"," o cliente a\nresolver sozinho.",[283,5769,5771],{"id":5770},"estrutura-de-um-agente","Estrutura de um agente",[86,5773,5774],{},"Um agente precisa de:",[17,5776,5777,5782,5788,5794],{},[20,5778,5779,5781],{},[89,5780,4965],{}," que decide qual ferramenta usar",[20,5783,5784,5787],{},[89,5785,5786],{},"Ferramentas"," (funções com descrição para o LLM entender)",[20,5789,5790,5793],{},[89,5791,5792],{},"Estado"," (o que já fez, o que descobriu)",[20,5795,5796,5799],{},[89,5797,5798],{},"Limites"," (máximo de iterações, custo máximo)",[506,5801,5803],{"className":3439,"code":5802,"language":3441,"meta":515,"style":515},"from openai import OpenAI\nclient = OpenAI()\n\n# 1. Definir as ferramentas que o agente pode usar\nferramentas = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"buscar_pedido\",\n            \"description\": \"Busca status de um pedido pelo número.\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"numero\": {\"type\": \"string\",\n                               \"description\": \"Número do pedido\"}\n                },\n                \"required\": [\"numero\"],\n            },\n        },\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"reembolsar_pedido\",\n            \"description\": \"Inicia reembolso de um pedido. Use com cautela.\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"numero\": {\"type\": \"string\"},\n                    \"motivo\": {\"type\": \"string\"}\n                },\n                \"required\": [\"numero\", \"motivo\"],\n            },\n        },\n    },\n]\n\n# 2. Implementar as ferramentas (funções reais)\ndef buscar_pedido(numero: str) -> dict:\n    # Aqui você consulta seu ERP\u002Fbanco\n    return {\"numero\": numero, \"status\": \"em_transito\",\n            \"previsao\": \"2026-08-28\"}\n\ndef reembolsar_pedido(numero: str, motivo: str) -> dict:\n    # Aqui você chama a API do seu sistema financeiro\n    return {\"ok\": True, \"protocolo\": \"RB-9876\"}\n\nFERRAMENTAS_IMPL = {\n    \"buscar_pedido\": buscar_pedido,\n    \"reembolsar_pedido\": reembolsar_pedido,\n}\n\n# 3. Loop do agente\ndef agir(mensagem: str, max_iter: int = 5) -> str:\n    \"\"\"Agente decide qual ferramenta usar e age até resolver.\"\"\"\n    mensagens = [\n        {\"role\": \"system\", \"content\": \"Você é um atendente de e-commerce. \"\n         \"Use as ferramentas para resolver o problema do cliente.\"},\n        {\"role\": \"user\", \"content\": mensagem},\n    ]\n\n    for _ in range(max_iter):  # limite de iterações: segurança\n        r = client.chat.completions.create(\n            model=\"gpt-4o-mini\",\n            messages=mensagens,\n            tools=ferramentas,\n        )\n        msg = r.choices[0].message\n        mensagens.append(msg)\n\n        # Se o LLM não pediu ferramenta, terminou\n        if not msg.tool_calls:\n            return msg.content\n\n        # Executa cada ferramenta pedida\n        for chamada in msg.tool_calls:\n            args = eval(chamada.function.arguments)  # simplificado\n            resultado = FERRAMENTAS_IMPL[chamada.function.name](**args)\n            mensagens.append({\n                \"role\": \"tool\",\n                \"tool_call_id\": chamada.id,\n                \"content\": str(resultado),\n            })\n\n    return \"Não consegui resolver em poucos passos. Transferindo para humano.\"\n",[513,5804,5805,5809,5813,5817,5822,5827,5832,5837,5842,5847,5852,5857,5862,5867,5872,5877,5882,5887,5892,5897,5901,5905,5909,5913,5918,5923,5927,5931,5935,5940,5945,5949,5954,5958,5962,5966,5970,5974,5979,5984,5989,5994,5999,6003,6008,6013,6018,6022,6027,6032,6037,6041,6045,6050,6055,6060,6065,6070,6075,6080,6086,6091,6097,6103,6109,6115,6121,6127,6133,6139,6144,6150,6156,6162,6167,6173,6179,6185,6191,6197,6203,6209,6215,6221,6226],{"__ignoreMap":515},[1376,5806,5807],{"class":1378,"line":1379},[1376,5808,3632],{},[1376,5810,5811],{"class":1378,"line":984},[1376,5812,3646],{},[1376,5814,5815],{"class":1378,"line":981},[1376,5816,1398],{"emptyLinePlaceholder":898},[1376,5818,5819],{"class":1378,"line":1395},[1376,5820,5821],{},"# 1. Definir as ferramentas que o agente pode usar\n",[1376,5823,5824],{"class":1378,"line":1401},[1376,5825,5826],{},"ferramentas = [\n",[1376,5828,5829],{"class":1378,"line":1407},[1376,5830,5831],{},"    {\n",[1376,5833,5834],{"class":1378,"line":1413},[1376,5835,5836],{},"        \"type\": \"function\",\n",[1376,5838,5839],{"class":1378,"line":1419},[1376,5840,5841],{},"        \"function\": {\n",[1376,5843,5844],{"class":1378,"line":1424},[1376,5845,5846],{},"            \"name\": \"buscar_pedido\",\n",[1376,5848,5849],{"class":1378,"line":1430},[1376,5850,5851],{},"            \"description\": \"Busca status de um pedido pelo número.\",\n",[1376,5853,5854],{"class":1378,"line":1436},[1376,5855,5856],{},"            \"parameters\": {\n",[1376,5858,5859],{"class":1378,"line":1442},[1376,5860,5861],{},"                \"type\": \"object\",\n",[1376,5863,5864],{"class":1378,"line":1447},[1376,5865,5866],{},"                \"properties\": {\n",[1376,5868,5869],{"class":1378,"line":1453},[1376,5870,5871],{},"                    \"numero\": {\"type\": \"string\",\n",[1376,5873,5874],{"class":1378,"line":1459},[1376,5875,5876],{},"                               \"description\": \"Número do pedido\"}\n",[1376,5878,5879],{"class":1378,"line":2200},[1376,5880,5881],{},"                },\n",[1376,5883,5884],{"class":1378,"line":2208},[1376,5885,5886],{},"                \"required\": [\"numero\"],\n",[1376,5888,5889],{"class":1378,"line":2219},[1376,5890,5891],{},"            },\n",[1376,5893,5894],{"class":1378,"line":2227},[1376,5895,5896],{},"        },\n",[1376,5898,5899],{"class":1378,"line":2241},[1376,5900,5145],{},[1376,5902,5903],{"class":1378,"line":2253},[1376,5904,5831],{},[1376,5906,5907],{"class":1378,"line":2283},[1376,5908,5836],{},[1376,5910,5911],{"class":1378,"line":2296},[1376,5912,5841],{},[1376,5914,5915],{"class":1378,"line":2311},[1376,5916,5917],{},"            \"name\": \"reembolsar_pedido\",\n",[1376,5919,5920],{"class":1378,"line":2316},[1376,5921,5922],{},"            \"description\": \"Inicia reembolso de um pedido. Use com cautela.\",\n",[1376,5924,5925],{"class":1378,"line":2324},[1376,5926,5856],{},[1376,5928,5929],{"class":1378,"line":2333},[1376,5930,5861],{},[1376,5932,5933],{"class":1378,"line":2340},[1376,5934,5866],{},[1376,5936,5937],{"class":1378,"line":2351},[1376,5938,5939],{},"                    \"numero\": {\"type\": \"string\"},\n",[1376,5941,5942],{"class":1378,"line":2362},[1376,5943,5944],{},"                    \"motivo\": {\"type\": \"string\"}\n",[1376,5946,5947],{"class":1378,"line":2385},[1376,5948,5881],{},[1376,5950,5951],{"class":1378,"line":2396},[1376,5952,5953],{},"                \"required\": [\"numero\", \"motivo\"],\n",[1376,5955,5956],{"class":1378,"line":2411},[1376,5957,5891],{},[1376,5959,5960],{"class":1378,"line":2416},[1376,5961,5896],{},[1376,5963,5964],{"class":1378,"line":2422},[1376,5965,5145],{},[1376,5967,5968],{"class":1378,"line":2430},[1376,5969,2139],{},[1376,5971,5972],{"class":1378,"line":2448},[1376,5973,1398],{"emptyLinePlaceholder":898},[1376,5975,5976],{"class":1378,"line":2457},[1376,5977,5978],{},"# 2. Implementar as ferramentas (funções reais)\n",[1376,5980,5981],{"class":1378,"line":2464},[1376,5982,5983],{},"def buscar_pedido(numero: str) -> dict:\n",[1376,5985,5986],{"class":1378,"line":2475},[1376,5987,5988],{},"    # Aqui você consulta seu ERP\u002Fbanco\n",[1376,5990,5991],{"class":1378,"line":2487},[1376,5992,5993],{},"    return {\"numero\": numero, \"status\": \"em_transito\",\n",[1376,5995,5996],{"class":1378,"line":2499},[1376,5997,5998],{},"            \"previsao\": \"2026-08-28\"}\n",[1376,6000,6001],{"class":1378,"line":2506},[1376,6002,1398],{"emptyLinePlaceholder":898},[1376,6004,6005],{"class":1378,"line":2517},[1376,6006,6007],{},"def reembolsar_pedido(numero: str, motivo: str) -> dict:\n",[1376,6009,6010],{"class":1378,"line":2528},[1376,6011,6012],{},"    # Aqui você chama a API do seu sistema financeiro\n",[1376,6014,6015],{"class":1378,"line":2539},[1376,6016,6017],{},"    return {\"ok\": True, \"protocolo\": \"RB-9876\"}\n",[1376,6019,6020],{"class":1378,"line":2553},[1376,6021,1398],{"emptyLinePlaceholder":898},[1376,6023,6024],{"class":1378,"line":2564},[1376,6025,6026],{},"FERRAMENTAS_IMPL = {\n",[1376,6028,6029],{"class":1378,"line":2576},[1376,6030,6031],{},"    \"buscar_pedido\": buscar_pedido,\n",[1376,6033,6034],{"class":1378,"line":2602},[1376,6035,6036],{},"    \"reembolsar_pedido\": reembolsar_pedido,\n",[1376,6038,6039],{"class":1378,"line":2607},[1376,6040,5170],{},[1376,6042,6043],{"class":1378,"line":2613},[1376,6044,1398],{"emptyLinePlaceholder":898},[1376,6046,6047],{"class":1378,"line":2620},[1376,6048,6049],{},"# 3. Loop do agente\n",[1376,6051,6052],{"class":1378,"line":2631},[1376,6053,6054],{},"def agir(mensagem: str, max_iter: int = 5) -> str:\n",[1376,6056,6057],{"class":1378,"line":2641},[1376,6058,6059],{},"    \"\"\"Agente decide qual ferramenta usar e age até resolver.\"\"\"\n",[1376,6061,6062],{"class":1378,"line":2651},[1376,6063,6064],{},"    mensagens = [\n",[1376,6066,6067],{"class":1378,"line":2658},[1376,6068,6069],{},"        {\"role\": \"system\", \"content\": \"Você é um atendente de e-commerce. \"\n",[1376,6071,6072],{"class":1378,"line":2670},[1376,6073,6074],{},"         \"Use as ferramentas para resolver o problema do cliente.\"},\n",[1376,6076,6077],{"class":1378,"line":2685},[1376,6078,6079],{},"        {\"role\": \"user\", \"content\": mensagem},\n",[1376,6081,6083],{"class":1378,"line":6082},60,[1376,6084,6085],{},"    ]\n",[1376,6087,6089],{"class":1378,"line":6088},61,[1376,6090,1398],{"emptyLinePlaceholder":898},[1376,6092,6094],{"class":1378,"line":6093},62,[1376,6095,6096],{},"    for _ in range(max_iter):  # limite de iterações: segurança\n",[1376,6098,6100],{"class":1378,"line":6099},63,[1376,6101,6102],{},"        r = client.chat.completions.create(\n",[1376,6104,6106],{"class":1378,"line":6105},64,[1376,6107,6108],{},"            model=\"gpt-4o-mini\",\n",[1376,6110,6112],{"class":1378,"line":6111},65,[1376,6113,6114],{},"            messages=mensagens,\n",[1376,6116,6118],{"class":1378,"line":6117},66,[1376,6119,6120],{},"            tools=ferramentas,\n",[1376,6122,6124],{"class":1378,"line":6123},67,[1376,6125,6126],{},"        )\n",[1376,6128,6130],{"class":1378,"line":6129},68,[1376,6131,6132],{},"        msg = r.choices[0].message\n",[1376,6134,6136],{"class":1378,"line":6135},69,[1376,6137,6138],{},"        mensagens.append(msg)\n",[1376,6140,6142],{"class":1378,"line":6141},70,[1376,6143,1398],{"emptyLinePlaceholder":898},[1376,6145,6147],{"class":1378,"line":6146},71,[1376,6148,6149],{},"        # Se o LLM não pediu ferramenta, terminou\n",[1376,6151,6153],{"class":1378,"line":6152},72,[1376,6154,6155],{},"        if not msg.tool_calls:\n",[1376,6157,6159],{"class":1378,"line":6158},73,[1376,6160,6161],{},"            return msg.content\n",[1376,6163,6165],{"class":1378,"line":6164},74,[1376,6166,1398],{"emptyLinePlaceholder":898},[1376,6168,6170],{"class":1378,"line":6169},75,[1376,6171,6172],{},"        # Executa cada ferramenta pedida\n",[1376,6174,6176],{"class":1378,"line":6175},76,[1376,6177,6178],{},"        for chamada in msg.tool_calls:\n",[1376,6180,6182],{"class":1378,"line":6181},77,[1376,6183,6184],{},"            args = eval(chamada.function.arguments)  # simplificado\n",[1376,6186,6188],{"class":1378,"line":6187},78,[1376,6189,6190],{},"            resultado = FERRAMENTAS_IMPL[chamada.function.name](**args)\n",[1376,6192,6194],{"class":1378,"line":6193},79,[1376,6195,6196],{},"            mensagens.append({\n",[1376,6198,6200],{"class":1378,"line":6199},80,[1376,6201,6202],{},"                \"role\": \"tool\",\n",[1376,6204,6206],{"class":1378,"line":6205},81,[1376,6207,6208],{},"                \"tool_call_id\": chamada.id,\n",[1376,6210,6212],{"class":1378,"line":6211},82,[1376,6213,6214],{},"                \"content\": str(resultado),\n",[1376,6216,6218],{"class":1378,"line":6217},83,[1376,6219,6220],{},"            })\n",[1376,6222,6224],{"class":1378,"line":6223},84,[1376,6225,1398],{"emptyLinePlaceholder":898},[1376,6227,6229],{"class":1378,"line":6228},85,[1376,6230,6231],{},"    return \"Não consegui resolver em poucos passos. Transferindo para humano.\"\n",[86,6233,6234,6235,1124,6238,6241],{},"Cada bloco numerado explica: definimos as ferramentas (com descrição para o\nLLM entender quando usar), implementamos, e o loop chama o LLM repetidamente\n— se ele pede uma ferramenta, executamos e devolvemos o resultado; quando\npara de pedir, devolvemos a resposta final. O ",[513,6236,6237],{},"max_iter",[89,6239,6240],{},"crucial",": sem\nele, um agente pode entrar em loop e consumir dezenas de dólares em uma\núnica conversa.",[283,6243,6245],{"id":6244},"segurança-em-agentes","Segurança em agentes",[86,6247,6248],{},"Agentes executam ações. Isso muda o risco completamente. Boas práticas:",[528,6250,6251,6257,6263,6269,6275],{},[20,6252,6253,6256],{},[89,6254,6255],{},"Confirmar ações destrutivas",": reembolso, cancelamento, alteração de\ndados — sempre peça confirmação antes de executar",[20,6258,6259,6262],{},[89,6260,6261],{},"Limite de custo por conversa",": monitore tokens gastos e aborte acima de\num teto",[20,6264,6265,6268],{},[89,6266,6267],{},"Audit log",": registre toda decisão do agente (qual ferramenta, com quais\nargumentos, em qual momento)",[20,6270,6271,6274],{},[89,6272,6273],{},"Sandbox em homologação",": rode o agente semanas em ambiente de teste\nantes de produção",[20,6276,6277,6280],{},[89,6278,6279],{},"Permissões mínimas",": o agente só tem acesso às ferramentas que precisa",[12,6282,6284],{"id":6283},"arquitetura-de-referência-arquitetura","Arquitetura de referência {#arquitetura}",[506,6286,6289],{"className":6287,"code":6288,"language":511},[509],"[WhatsApp \u002F Web \u002F App]\n        ↓\n   [API Gateway]\n        ↓\n   [Serviço de chat]  ← mantém sessão\u002Fconversa\n        ↓\n   ┌────────────────────┐\n   │  Orquestrador      │\n   │  (LangChain \u002F      │\n   │   LlamaIndex \u002F     │\n   │   código próprio)  │\n   └────────────────────┘\n        ↓        ↓        ↓\n   [LLM API]  [pgvector]  [Ferramentas]\n              (RAG)       (ERP, CRM, ticket)\n        ↓\n   [Resposta ao canal]\n        ↓\n   [Logging + métricas]\n",[513,6290,6288],{"__ignoreMap":515},[86,6292,6293],{},"Componentes:",[528,6295,6296,6302,6308,6314,6320,6326,6331],{},[20,6297,6298,6301],{},[89,6299,6300],{},"API Gateway",": recebe mensagens de qualquer canal (WhatsApp, web, app)",[20,6303,6304,6307],{},[89,6305,6306],{},"Serviço de chat",": mantém contexto da conversa (Redis ou banco)",[20,6309,6310,6313],{},[89,6311,6312],{},"Orquestrador",": decide o que fazer (RAG, ferramenta, humano)",[20,6315,6316,6319],{},[89,6317,6318],{},"LLM API",": gera texto (OpenAI, Anthropic, etc.)",[20,6321,6322,6325],{},[89,6323,6324],{},"pgvector",": busca em base de conhecimento",[20,6327,6328,6330],{},[89,6329,5786],{},": integrações com sistemas internos",[20,6332,6333,6336],{},[89,6334,6335],{},"Logging",": registra tudo para auditoria e melhoria",[12,6338,6340],{"id":6339},"ferramentas-e-stack-ferramentas","Ferramentas e stack {#ferramentas}",[165,6342,6343,6356],{},[168,6344,6345],{},[171,6346,6347,6350,6353],{},[174,6348,6349],{},"Camada",[174,6351,6352],{},"Opção recomendada",[174,6354,6355],{},"Alternativa",[184,6357,6358,6368,6379,6390,6401,6412,6423,6434,6445,6456],{},[171,6359,6360,6362,6365],{},[189,6361,4965],{},[189,6363,6364],{},"GPT-4o-mini (comece)",[189,6366,6367],{},"Claude Haiku, Gemini Flash",[171,6369,6370,6373,6376],{},[189,6371,6372],{},"Embeddings",[189,6374,6375],{},"OpenAI text-embedding-3-small",[189,6377,6378],{},"Cohere embed-multilingual",[171,6380,6381,6384,6387],{},[189,6382,6383],{},"Banco vetorial",[189,6385,6386],{},"PostgreSQL + pgvector",[189,6388,6389],{},"Qdrant, Chroma",[171,6391,6392,6395,6398],{},[189,6393,6394],{},"Orquestração",[189,6396,6397],{},"LangChain ou LlamaIndex",[189,6399,6400],{},"Código próprio (simples)",[171,6402,6403,6406,6409],{},[189,6404,6405],{},"Agentes",[189,6407,6408],{},"LangGraph",[189,6410,6411],{},"CrewAI, OpenAI Assistants",[171,6413,6414,6417,6420],{},[189,6415,6416],{},"API",[189,6418,6419],{},"FastAPI (Python)",[189,6421,6422],{},"Node + Express",[171,6424,6425,6428,6431],{},[189,6426,6427],{},"Sessão",[189,6429,6430],{},"Redis",[189,6432,6433],{},"Postgres",[171,6435,6436,6439,6442],{},[189,6437,6438],{},"Observabilidade",[189,6440,6441],{},"LangSmith, Langfuse",[189,6443,6444],{},"Logs próprios",[171,6446,6447,6450,6453],{},[189,6448,6449],{},"Canal WhatsApp",[189,6451,6452],{},"Z-API, Evolution API, Meta Cloud API",[189,6454,6455],{},"Twilio",[171,6457,6458,6461,6464],{},[189,6459,6460],{},"Canal Web",[189,6462,6463],{},"Socket.io + React",[189,6465,6466],{},"Streamlit (protótipo)",[86,6468,6469,6472,6473,6476,6477,522],{},[89,6470,6471],{},"Dica",": para o primeiro projeto, ",[89,6474,6475],{},"não"," use framework de agentes. Use\ncódigo próprio — você entende cada decisão, fica fácil debugar. Adote\nLangGraph só quando o fluxo virar complexo demais para ",[513,6478,6479],{},"if\u002Felse",[12,6481,6483],{"id":6482},"integração-com-canais-canais","Integração com canais {#canais}",[283,6485,6487],{"id":6486},"whatsapp","WhatsApp",[86,6489,6490],{},"O WhatsApp Business Cloud API (oficial da Meta) é o caminho mais barato e\noficial. Para volume alto, provedores como Z-API ou Evolution API facilitam\na integração:",[506,6492,6494],{"className":3439,"code":6493,"language":3441,"meta":515,"style":515},"# Exemplo esquemático - receber mensagem do WhatsApp\nfrom fastapi import FastAPI, Request\napp = FastAPI()\n\n@app.post(\"\u002Fwebhook\u002Fwhatsapp\")\nasync def webhook(req: Request):\n    body = await req.json()\n    # Meta envia as mensagens nesse formato\n    msg = body[\"entry\"][0][\"changes\"][0][\"value\"][\"messages\"][0]\n    telefone = msg[\"from\"]\n    texto = msg[\"text\"][\"body\"]\n\n    # Chama seu pipeline de atendimento\n    resposta = agir(texto)\n\n    # Responde pelo WhatsApp (chamada à API da Meta)\n    enviar_whatsapp(telefone, resposta)\n    return {\"status\": \"ok\"}\n",[513,6495,6496,6501,6506,6511,6515,6520,6525,6530,6535,6540,6545,6550,6554,6559,6564,6568,6573,6578],{"__ignoreMap":515},[1376,6497,6498],{"class":1378,"line":1379},[1376,6499,6500],{},"# Exemplo esquemático - receber mensagem do WhatsApp\n",[1376,6502,6503],{"class":1378,"line":984},[1376,6504,6505],{},"from fastapi import FastAPI, Request\n",[1376,6507,6508],{"class":1378,"line":981},[1376,6509,6510],{},"app = FastAPI()\n",[1376,6512,6513],{"class":1378,"line":1395},[1376,6514,1398],{"emptyLinePlaceholder":898},[1376,6516,6517],{"class":1378,"line":1401},[1376,6518,6519],{},"@app.post(\"\u002Fwebhook\u002Fwhatsapp\")\n",[1376,6521,6522],{"class":1378,"line":1407},[1376,6523,6524],{},"async def webhook(req: Request):\n",[1376,6526,6527],{"class":1378,"line":1413},[1376,6528,6529],{},"    body = await req.json()\n",[1376,6531,6532],{"class":1378,"line":1419},[1376,6533,6534],{},"    # Meta envia as mensagens nesse formato\n",[1376,6536,6537],{"class":1378,"line":1424},[1376,6538,6539],{},"    msg = body[\"entry\"][0][\"changes\"][0][\"value\"][\"messages\"][0]\n",[1376,6541,6542],{"class":1378,"line":1430},[1376,6543,6544],{},"    telefone = msg[\"from\"]\n",[1376,6546,6547],{"class":1378,"line":1436},[1376,6548,6549],{},"    texto = msg[\"text\"][\"body\"]\n",[1376,6551,6552],{"class":1378,"line":1442},[1376,6553,1398],{"emptyLinePlaceholder":898},[1376,6555,6556],{"class":1378,"line":1447},[1376,6557,6558],{},"    # Chama seu pipeline de atendimento\n",[1376,6560,6561],{"class":1378,"line":1453},[1376,6562,6563],{},"    resposta = agir(texto)\n",[1376,6565,6566],{"class":1378,"line":1459},[1376,6567,1398],{"emptyLinePlaceholder":898},[1376,6569,6570],{"class":1378,"line":2200},[1376,6571,6572],{},"    # Responde pelo WhatsApp (chamada à API da Meta)\n",[1376,6574,6575],{"class":1378,"line":2208},[1376,6576,6577],{},"    enviar_whatsapp(telefone, resposta)\n",[1376,6579,6580],{"class":1378,"line":2219},[1376,6581,6582],{},"    return {\"status\": \"ok\"}\n",[86,6584,6585],{},"Comentários: o webhook recebe a mensagem, extrai telefone e texto, processa\nno seu pipeline e devolve a resposta pela API da Meta.",[283,6587,6589],{"id":6588},"web-chat","Web chat",[86,6591,6592],{},"Para web, um WebSocket é mais natural que HTTP polling. Stack simples:\nReact no front, FastAPI com Socket.io no back.",[283,6594,6596],{"id":6595},"e-mail-e-helpdesk","E-mail e helpdesk",[86,6598,6599],{},"Integração com Zendesk, Freshdesk ou Jira Service Desk via API. O pipeline é\no mesmo — só muda o trigger (novo ticket em vez de nova mensagem).",[12,6601,6603],{"id":6602},"métricas-que-importam-metricas","Métricas que importam {#metricas}",[165,6605,6606,6619],{},[168,6607,6608],{},[171,6609,6610,6613,6616],{},[174,6611,6612],{},"Métrica",[174,6614,6615],{},"O que mede",[174,6617,6618],{},"Meta inicial",[184,6620,6621,6632,6643,6654,6665,6676,6687],{},[171,6622,6623,6626,6629],{},[189,6624,6625],{},"Taxa de resolução",[189,6627,6628],{},"% de tickets fechados sem humano",[189,6630,6631],{},"50–65%",[171,6633,6634,6637,6640],{},[189,6635,6636],{},"Tempo médio de resposta",[189,6638,6639],{},"Latência primeira resposta",[189,6641,6642],{},"\u003C 5s",[171,6644,6645,6648,6651],{},[189,6646,6647],{},"CSAT após IA",[189,6649,6650],{},"Nota de satisfação pós-atendimento",[189,6652,6653],{},"> 4\u002F5",[171,6655,6656,6659,6662],{},[189,6657,6658],{},"Handoff rate",[189,6660,6661],{},"% de conversas transferidas para humano",[189,6663,6664],{},"\u003C 30%",[171,6666,6667,6670,6673],{},[189,6668,6669],{},"Custo por ticket",[189,6671,6672],{},"$ gasto em API \u002F tickets resolvidos",[189,6674,6675],{},"\u003C R$ 0,30",[171,6677,6678,6681,6684],{},[189,6679,6680],{},"Hallucination rate",[189,6682,6683],{},"% de respostas com informação errada",[189,6685,6686],{},"\u003C 3%",[171,6688,6689,6692,6695],{},[189,6690,6691],{},"Containment rate",[189,6693,6694],{},"% de clientes que não precisam de humano",[189,6696,6697],{},"60–75%",[86,6699,6700,6703],{},[89,6701,6702],{},"A mais importante é a taxa de resolução sem humano",". Se a IA não resolve\n— só transfere —, você não economizou nada, só adicionou uma camada.",[86,6705,6706,6709],{},[89,6707,6708],{},"A mais perigosa é o hallucination rate",". Resposta errada em atendimento\nvira cliente insatisfeito, ou pior, processo. Meça com amostragem humana\nsemanal.",[12,6711,6713],{"id":6712},"quando-não-automatizar-nao-automatizar","Quando não automatizar {#nao-automatizar}",[86,6715,6716],{},"Nem todo atendimento deve ser automatizado. Não automatize:",[528,6718,6719,6725,6731,6737,6743],{},[20,6720,6721,6724],{},[89,6722,6723],{},"Onboarding de clientes premium",": o valor está no contato humano",[20,6726,6727,6730],{},[89,6728,6729],{},"Suporte de produto técnico complexo (B2B)",": cliente quer especialista,\nnão bot",[20,6732,6733,6736],{},[89,6734,6735],{},"Reclamações graves",": cliente bravo com bot vira cliente ex-bravo",[20,6738,6739,6742],{},[89,6740,6741],{},"Vendas de ticket alto",": closing é relacional",[20,6744,6745,6748],{},[89,6746,6747],{},"Casos regulados que exigem responsável técnico",": médico, jurídico,\nfinanceiro certificado",[86,6750,6751,6752,6755,6756,6759,6760,522],{},"Use IA para ",[89,6753,6754],{},"volume repetitivo",", libere humano para ",[89,6757,6758],{},"valor relacional",".\nA meta não é \"substituir gente\", é ",[89,6761,6762],{},"realocar gente para o que importa",[12,6764,820],{"id":819},[17,6766,6767,6773,6779,6785,6791,6797,6803,6812,6818,6824],{},[20,6768,6769,6772],{},[89,6770,6771],{},"Começar pelo nível 4",": agente autônomo sem ter RAG estável. Resultado:\nações erradas, custo alto, cliente frustrado. Suba os níveis em ordem.",[20,6774,6775,6778],{},[89,6776,6777],{},"Não ter handoff",": IA falha e cliente fica preso. Sempre tenha caminho\nclaro para humano.",[20,6780,6781,6784],{},[89,6782,6783],{},"Ocultar que é IA",": tentar passar IA por humano. Cliente percebe, perde\nconfiança. Seja transparente: \"sou o assistente virtual, posso te ajudar\nou transferir para um atendente\".",[20,6786,6787,6790],{},[89,6788,6789],{},"Não medir taxa de resolução",": ficar feliz com \"tempo de resposta\nbaixo\" enquanto 80% vai para humano. Métrica que importa é resolução.",[20,6792,6793,6796],{},[89,6794,6795],{},"Resolver tudo com LLM",": perguntas de FAQ simples (horário, endereço)\nnão precisam de LLM. Use regra para o óbvio, LLM para o complexo.",[20,6798,6799,6802],{},[89,6800,6801],{},"Não atualizar a base de conhecimento",": produto mudou, RAG responde com\ninformação velha. Defina processo de atualização semanal.",[20,6804,6805,6808,6809,6811],{},[89,6806,6807],{},"Não limitar custo do agente",": agente em loop consumindo $50 numa\nconversa. Sempre defina ",[513,6810,6237],{}," e budget.",[20,6813,6814,6817],{},[89,6815,6816],{},"Confiar na classificação de intenção cegamente",": 5% de erro é normal.\nPara ações destrutivas, sempre confirme com o cliente.",[20,6819,6820,6823],{},[89,6821,6822],{},"Ignorar contexto da conversa",": tratar cada mensagem isolada. Use\nhistórico (últimas 5–10 mensagens) para entender a intenção real.",[20,6825,6826,6829],{},[89,6827,6828],{},"Não treinar a equipe de atendimento humano",": humanos precisam saber\no que a IA já fez antes de assumir. Repasse o contexto na transferência.",[968,6831],{},[86,6833,6834,6837,6838,6841],{},[89,6835,6836],{},"Quer automatizar o atendimento da sua empresa com IA?"," A Inicialize Tec\nmonta do chatbot ao agente, integrado com WhatsApp, web e seu sistema\natual — com métricas e handoff pensados do dia 1. ",[23,6839,6840],{"href":978},"Entre em contato","\ne conversamos sobre seu volume e canais.",[2883,6843,4828],{},{"title":515,"searchDepth":981,"depth":981,"links":6845},[6846,6847,6848,6849,6850,6855,6859,6860,6861,6866,6867,6868],{"id":14,"depth":984,"text":15},{"id":4944,"depth":984,"text":4945},{"id":5080,"depth":984,"text":5081},{"id":5205,"depth":984,"text":5206},{"id":5376,"depth":984,"text":5377,"children":6851},[6852,6853,6854],{"id":5395,"depth":981,"text":5396},{"id":5405,"depth":981,"text":5406},{"id":5632,"depth":981,"text":5633},{"id":5714,"depth":984,"text":5715,"children":6856},[6857,6858],{"id":5770,"depth":981,"text":5771},{"id":6244,"depth":981,"text":6245},{"id":6283,"depth":984,"text":6284},{"id":6339,"depth":984,"text":6340},{"id":6482,"depth":984,"text":6483,"children":6862},[6863,6864,6865],{"id":6486,"depth":981,"text":6487},{"id":6588,"depth":981,"text":6589},{"id":6595,"depth":981,"text":6596},{"id":6602,"depth":984,"text":6603},{"id":6712,"depth":984,"text":6713},{"id":819,"depth":984,"text":820},"inteligencia-artificial","Como automatizar atendimento ao cliente com IA: chatbots, assistentes com RAG e agentes autônomos. Arquitetura, ferramentas e métricas reais.",[6872,6873,4957,6874,6875,6876],"chatbot IA","automação atendimento","atendimento automático","RAG atendimento","agente IA",{},"\u002Fia\u002Fautomacao-de-atendimento-com-ia",{"title":4871,"description":6870},"automacao-de-atendimento-com-ia","ia\u002Fautomacao-de-atendimento-com-ia","xVYwe8Z7DbV62_Qd0pwX1MidtbZcksPC0wsq_DSKgK4",{"id":6884,"title":6885,"author":7,"body":6886,"category":6869,"date":1028,"description":8596,"extension":1030,"keywords":8597,"meta":8602,"navigation":898,"path":8603,"seo":8604,"slug":8605,"stem":8606,"updated":1028,"__hash__":8607},"content\u002Fia\u002Fcomo-implementar-ia-em-empresa-pequena.md","Como implementar IA em empresa pequena: guia prático",{"type":9,"value":6887,"toc":8555},[6888,6890,6942,6946,6955,6965,6972,6985,6989,7035,7039,7042,7068,7075,7078,7182,7186,7217,7225,7229,7232,7258,7268,7272,7275,7320,7323,7327,7330,7334,7348,7351,7355,7362,7369,7372,7383,7386,7390,7401,7404,7408,7411,7415,7434,7438,7457,7461,7477,7481,7494,7500,7508,7512,7732,7740,7744,7748,7754,7774,7781,7785,7790,7841,7845,7850,7883,7890,7894,7897,7973,7976,7980,8040,8042,8046,8051,8055,8080,8084,8119,8123,8128,8132,8164,8168,8208,8216,8220,8225,8229,8248,8252,8346,8350,8355,8359,8365,8369,8375,8382,8386,8391,8395,8414,8418,8482,8486,8489,8495,8515,8520,8537,8542,8544,8553],[12,6889,15],{"id":14},[17,6891,6892,6898,6904,6910,6916,6922,6928,6933,6937],{},[20,6893,6894],{},[23,6895,6897],{"href":6896},"#por-que","Por que IA não é privilégio de grande empresa",[20,6899,6900],{},[23,6901,6903],{"href":6902},"#diagnostico","Diagnóstico: onde a IA gera valor",[20,6905,6906],{},[23,6907,6909],{"href":6908},"#caso-de-uso","Escolhendo o primeiro caso de uso",[20,6911,6912],{},[23,6913,6915],{"href":6914},"#conceitos","Entendendo os conceitos antes de começar",[20,6917,6918],{},[23,6919,6921],{"href":6920},"#stack","Stack técnica mínima",[20,6923,6924],{},[23,6925,6927],{"href":6926},"#producao","Do prototype ao produção",[20,6929,6930],{},[23,6931,6932],{"href":2980},"Custos reais",[20,6934,6935],{},[23,6936,74],{"href":73},[20,6938,6939],{},[23,6940,6941],{"href":2990},"Caso real: consultoria que economizou 30h\u002Fsemana",[12,6943,6945],{"id":6944},"por-que-ia-não-é-privilégio-de-grande-empresa-por-que","Por que IA não é privilégio de grande empresa {#por-que}",[86,6947,6948,6949,6951,6952,6954],{},"A percepção de que inteligência artificial exige orçamentos de milhões e times\nde 50 engenheiros está desatualizada. Em 2026, modelos de linguagem\npré-treinados — conhecidos pela sigla ",[89,6950,4965],{}," (Large Language Model, ou \"Modelo\nde Linguagem Grande\") — como GPT-4, Claude e Llama 3 estão disponíveis via\n",[89,6953,6416],{}," (uma API é como um garçom: você faz o pedido, ele leva à cozinha e\ntraz o prato pronto) a custos de centavos por mil palavras processadas.",[86,6956,6957,6958,6961,6962,6964],{},"Ferramentas de código aberto (open source) como ",[89,6959,6960],{},"LangChain"," e técnicas como\n",[89,6963,4972],{}," (Retrieval-Augmented Generation, ou \"Geração Aumentada por Recuperação\"\n— explico melhor na seção de conceitos) permitem construir soluções de IA\nfuncionais em semanas, não anos.",[710,6966,6967],{},[86,6968,6969,6971],{},[89,6970,3011],{},": Imagine que você quer contratar um estagiário super inteligente\nque leu toda a internet, mas que às vezes inventa coisas. Sua missão é dar a\nele acesso aos manuais da sua empresa para que ele responda com base nos\ndados corretos, e não no que ele \"lembra\" da internet. Isso é basicamente\no que faremos aqui.",[86,6973,6974,6977,6978,6981,6982,6984],{},[89,6975,6976],{},"73% das empresas brasileiras planejam investir em IA em 2026",", segundo\npesquisa da McKinsey. A questão não é ",[501,6979,6980],{},"se",", mas ",[501,6983,3131],{},". E a boa notícia é que\no \"como\" ficou muito mais barato e acessível.",[283,6986,6988],{"id":6987},"o-que-mudou-nos-últimos-2-anos","O que mudou nos últimos 2 anos",[165,6990,6991,7001],{},[168,6992,6993],{},[171,6994,6995,6998],{},[174,6996,6997],{},"Antes (2022)",[174,6999,7000],{},"Agora (2026)",[184,7002,7003,7011,7019,7027],{},[171,7004,7005,7008],{},[189,7006,7007],{},"Treinar um modelo custava $50K+",[189,7009,7010],{},"Usar modelo pronto custa centavos por consulta",[171,7012,7013,7016],{},[189,7014,7015],{},"Precisava de PhD em ML",[189,7017,7018],{},"Basta saber Python básico",[171,7020,7021,7024],{},[189,7022,7023],{},"Infraestrutura própria (GPUs caras)",[189,7025,7026],{},"API em nuvem (pague pelo uso)",[171,7028,7029,7032],{},[189,7030,7031],{},"6-12 meses até primeiro resultado",[189,7033,7034],{},"2-8 semanas até primeiro resultado",[12,7036,7038],{"id":7037},"diagnóstico-onde-a-ia-gera-valor-diagnostico","Diagnóstico: onde a IA gera valor {#diagnostico}",[86,7040,7041],{},"Antes de escolher tecnologia, mapeie processos que tenham estas características:",[528,7043,7044,7050,7056,7062],{},[20,7045,7046,7049],{},[89,7047,7048],{},"Volume repetitivo",": tarefas executadas centenas de vezes por dia",[20,7051,7052,7055],{},[89,7053,7054],{},"Baseada em dados",": decisões que dependem de análise de texto, números ou imagens",[20,7057,7058,7061],{},[89,7059,7060],{},"Lenta ou custosa",": tempo humano que poderia ser realocado para tarefas mais estratégicas",[20,7063,7064,7067],{},[89,7065,7066],{},"Tolerante a erro iterativo",": onde 90% de acerto já gera valor (os 10% restantes\num humano corrige rapidamente)",[710,7069,7070],{},[86,7071,7072,7074],{},[89,7073,3011],{},": Pense como escolher onde colocar um auxiliar novo na empresa.\nVocê não o coloca para tomar decisões estratégicas — você o coloca onde há\nmuito trabalho repetitivo que toma tempo dos seus melhores funcionários.\nIA funciona igual.",[86,7076,7077],{},"Exemplos práticos para pequenas empresas:",[165,7079,7080,7096],{},[168,7081,7082],{},[171,7083,7084,7087,7090,7093],{},[174,7085,7086],{},"Processo",[174,7088,7089],{},"Aplicação de IA",[174,7091,7092],{},"Impacto",[174,7094,7095],{},"Tempo de retorno",[184,7097,7098,7112,7126,7140,7154,7168],{},[171,7099,7100,7103,7106,7109],{},[189,7101,7102],{},"Atendimento ao cliente",[189,7104,7105],{},"Chatbot com base de conhecimento",[189,7107,7108],{},"Reduz 60% de tickets simples",[189,7110,7111],{},"2-4 semanas",[171,7113,7114,7117,7120,7123],{},[189,7115,7116],{},"Análise de contratos",[189,7118,7119],{},"Extração de cláusulas-chave",[189,7121,7122],{},"10x mais rápido que manual",[189,7124,7125],{},"4-6 semanas",[171,7127,7128,7131,7134,7137],{},[189,7129,7130],{},"Categorização de e-mails",[189,7132,7133],{},"Classificação automática por urgência",[189,7135,7136],{},"Equipe foca no que importa",[189,7138,7139],{},"1-2 semanas",[171,7141,7142,7145,7148,7151],{},[189,7143,7144],{},"Previsão de demanda",[189,7146,7147],{},"Modelo de séries temporais",[189,7149,7150],{},"Reduz estoque parado em 20-30%",[189,7152,7153],{},"6-8 semanas",[171,7155,7156,7159,7162,7165],{},[189,7157,7158],{},"Geração de relatórios",[189,7160,7161],{},"LLM + dados internos",[189,7163,7164],{},"Relatório em segundos vs. 2h manual",[189,7166,7167],{},"1-3 semanas",[171,7169,7170,7173,7176,7179],{},[189,7171,7172],{},"Triagem de currículos",[189,7174,7175],{},"Extração de competências e ranking",[189,7177,7178],{},"Reduz 80% do tempo de RH",[189,7180,7181],{},"2-3 semanas",[283,7183,7185],{"id":7184},"como-fazer-o-diagnóstico-em-1-hora","Como fazer o diagnóstico em 1 hora",[17,7187,7188,7194,7200,7206,7212],{},[20,7189,7190,7193],{},[89,7191,7192],{},"Liste todos os processos manuais"," da empresa (planilha simples)",[20,7195,7196,7199],{},[89,7197,7198],{},"Marque os repetitivos"," com volume > 50x\u002Fdia",[20,7201,7202,7205],{},[89,7203,7204],{},"Estime horas\u002Fmês"," gastas em cada um",[20,7207,7208,7211],{},[89,7209,7210],{},"Classifique por risco",": \"erro aqui custa quanto?\"",[20,7213,7214],{},[89,7215,7216],{},"Escolha o de maior volume + menor risco + maior dor",[710,7218,7219],{},[86,7220,7221,7224],{},[89,7222,7223],{},"Erro comum",": O dono da empresa quer IA para \"prever o mercado\" ou\n\"automatizar todas as vendas\". Esses são projetos de alto risco e alto\ncomplexidade. Comece pelo chatbot de atendimento que responde \"qual o\nhorário de funcionamento?\" — parece simples, mas economiza 15h\u002Fsemana\nda equipe.",[12,7226,7228],{"id":7227},"escolhendo-o-primeiro-caso-de-uso-caso-de-uso","Escolhendo o primeiro caso de uso {#caso-de-uso}",[86,7230,7231],{},"O primeiro projeto de IA deve ser:",[17,7233,7234,7240,7246,7252],{},[20,7235,7236,7239],{},[89,7237,7238],{},"De baixo risco",": erro não causa dano financeiro ou reputacional grave.\nExemplo: se o chatbot erra uma resposta, o cliente pode falar com um humano.",[20,7241,7242,7245],{},[89,7243,7244],{},"De alto valor visível",": resultado mensurável e óbvio para a equipe.\nExemplo: \"reduzimos 60% dos tickets de suporte\".",[20,7247,7248,7251],{},[89,7249,7250],{},"Tecnicamente factível",": dados disponíveis, volume adequado, equipe capaz",[20,7253,7254,7257],{},[89,7255,7256],{},"De escopo reduzido",": resolvível em 4-8 semanas, não 6 meses",[86,7259,7260,7263,7264,7267],{},[89,7261,7262],{},"Recomendação",": comece com um ",[89,7265,7266],{},"assistente de atendimento"," que usa RAG sobre\na base de conhecimento existente da empresa (FAQ, manuais, políticas). É o\nprojeto de menor risco, maior impacto percebido e implementação mais rápida.",[283,7269,7271],{"id":7270},"critérios-de-avaliação-checklist","Critérios de avaliação — checklist",[86,7273,7274],{},"Use esta checklist para validar se seu primeiro projeto é uma boa escolha:",[528,7276,7278,7284,7290,7296,7302,7308,7314],{"className":7277},[890],[20,7279,7281,7283],{"className":7280},[894],[896,7282],{"disabled":898,"type":899}," Os dados de treinamento\u002Fbase já existem e estão acessíveis?",[20,7285,7287,7289],{"className":7286},[894],[896,7288],{"disabled":898,"type":899}," O volume justifica automatização (pelo menos 50 interações\u002Fdia)?",[20,7291,7293,7295],{"className":7292},[894],[896,7294],{"disabled":898,"type":899}," Um erro da IA tem consequências recuperáveis?",[20,7297,7299,7301],{"className":7298},[894],[896,7300],{"disabled":898,"type":899}," Consegue medir sucesso com uma métrica clara?",[20,7303,7305,7307],{"className":7304},[894],[896,7306],{"disabled":898,"type":899}," A equipe está disposta a testar e dar feedback?",[20,7309,7311,7313],{"className":7310},[894],[896,7312],{"disabled":898,"type":899}," Há orçamento de $50-100\u002Fmês para infraestrutura?",[20,7315,7317,7319],{"className":7316},[894],[896,7318],{"disabled":898,"type":899}," Alguém na equipe sabe (ou pode aprender) Python básico?",[86,7321,7322],{},"Se marcou 6 ou mais, está pronto para começar.",[12,7324,7326],{"id":7325},"entendendo-os-conceitos-antes-de-começar-conceitos","Entendendo os conceitos antes de começar {#conceitos}",[86,7328,7329],{},"Antes de mergulhar na tecnologia, vamos entender os 4 conceitos-chave usando\nanalogias do dia a dia:",[283,7331,7333],{"id":7332},"llm-large-language-model","LLM (Large Language Model)",[710,7335,7336],{},[86,7337,7338,7340,7341,7344,7345,522],{},[89,7339,3011],{},": Um LLM é como um estagiário brilhante que leu bilhões de\npáginas da internet. Ele sabe redigir textos, responder perguntas e resumir\ndocumentos. Mas ele ",[89,7342,7343],{},"não conhece sua empresa"," — se você perguntar \"qual\na política de férias da Acme Ltda?\", ele vai inventar uma resposta que\nparece correta, mas pode estar completamente errada. Esse fenômeno se chama\n",[89,7346,7347],{},"alucinação",[86,7349,7350],{},"Modelos populares em 2026: GPT-4o (OpenAI), Claude (Anthropic), Llama 3 (Meta).",[283,7352,7354],{"id":7353},"rag-retrieval-augmented-generation","RAG (Retrieval-Augmented Generation)",[710,7356,7357],{},[86,7358,7359,7361],{},[89,7360,3011],{},": RAG é como dar uma prova de livro aberto para esse estagiário.\nEm vez de depender da memória (que pode estar errada), ele consulta os\ndocumentos da empresa antes de responder. Você pergunta \"qual a política de\nférias?\" → ele busca no manual da empresa → lê a seção correta → responde\ncom base no que leu.",[86,7363,7364,7365,7368],{},"RAG resolve o problema das alucinações: a IA responde com base nos ",[89,7366,7367],{},"seus","\ndados, não no que \"lembra\" da internet.",[283,7370,6372],{"id":7371},"embeddings",[710,7373,7374],{},[86,7375,7376,7378,7379,7382],{},[89,7377,3011],{},": Embeddings são como o índice remissivo de um livro, mas em\nvez de listar palavras exatas, ele entende ",[89,7380,7381],{},"significado",". Se você busca\n\"férias\", encontra também \"licença remunerada\" e \"descanso\", porque o\nsistema entende que significam a mesma coisa.",[86,7384,7385],{},"Tecnicamente: embeddings convertem texto em vetores numéricos (listas de\nnúmeros) que representam o significado. Textos com significado parecido têm\nnúmeros parecidos, permitindo busca por semelhança.",[283,7387,7389],{"id":7388},"vector-store-banco-de-vetores","Vector Store (Banco de vetores)",[710,7391,7392],{},[86,7393,7394,7396,7397,7400],{},[89,7395,3011],{},": É como uma biblioteca onde os livros não estão organizados\npor ordem alfabética, mas por ",[89,7398,7399],{},"tema\u002Fsemelhança",". Quando a IA precisa\nresponder uma pergunta, ela vai à biblioteca e pega os livros mais\nrelevantes automaticamente.",[86,7402,7403],{},"Ferramentas: PostgreSQL com extensão pgvector, ChromaDB, Qdrant.",[12,7405,7407],{"id":7406},"stack-técnica-mínima-stack","Stack técnica mínima {#stack}",[86,7409,7410],{},"Para uma empresa pequena, a stack ideal é enxuta e barata:",[283,7412,7414],{"id":7413},"camada-de-modelo-o-cérebro","Camada de modelo (o \"cérebro\")",[528,7416,7417,7423,7428],{},[20,7418,7419,7422],{},[89,7420,7421],{},"LLM via API",": OpenAI GPT-4o-mini ou Claude Haiku (custo baixo, qualidade\nsuficiente para a maioria dos casos)",[20,7424,7425,7427],{},[89,7426,6372],{},": OpenAI text-embedding-3-small (converte texto em vetores)",[20,7429,7430,7433],{},[89,7431,7432],{},"Alternativa open source",": Llama 3.1 8B hospedado em instância EC2 t3.medium\n(~$30\u002Fmês, sem dependência de API externa)",[283,7435,7437],{"id":7436},"camada-de-dados-a-memória","Camada de dados (a \"memória\")",[528,7439,7440,7446,7451],{},[20,7441,7442,7445],{},[89,7443,7444],{},"Vector store",": PostgreSQL com extensão pgvector (se já usa PostgreSQL,\nnão precisa de infraestrutura adicional)",[20,7447,7448,7450],{},[89,7449,6355],{},": ChromaDB ou Qdrant (código aberto, fácil de instalar)",[20,7452,7453,7456],{},[89,7454,7455],{},"Armazenamento de documentos",": pasta simples com arquivos markdown\u002Ftxt\u002FPDF",[283,7458,7460],{"id":7459},"camada-de-orquestração-o-maestro","Camada de orquestração (o \"maestro\")",[528,7462,7463,7471],{},[20,7464,7465,5655,7467,7470],{},[89,7466,6960],{},[89,7468,7469],{},"LlamaIndex",": frameworks que conectam modelo + dados +\nlógica de negócio. São como \"blocos de montar\" que evitam escrever tudo do zero",[20,7472,7473,7476],{},[89,7474,7475],{},"Python",": linguagem padrão para IA — vasta comunidade, tutoriais, bibliotecas",[283,7478,7480],{"id":7479},"camada-de-interface-a-porta-de-entrada","Camada de interface (a \"porta de entrada\")",[528,7482,7483,7488],{},[20,7484,7485,7487],{},[89,7486,6416],{},": FastAPI (Python, rápido e simples) ou integração direta no sistema\nexistente",[20,7489,7490,7493],{},[89,7491,7492],{},"Frontend",": um componente de chat no site ou sistema web atual",[506,7495,7498],{"className":7496,"code":7497,"language":511},[509],"[Usuário pergunta]\n      ↓\n[API FastAPI recebe a pergunta]\n      ↓\n[LangChain converte pergunta em embedding]\n      ↓\n[pgvector busca documentos similares na base]\n      ↓\n[LangChain monta prompt: pergunta + documentos encontrados]\n      ↓\n[LLM GPT-4o-mini gera resposta com base nos documentos]\n      ↓\n[Resposta retorna para o usuário]\n",[513,7499,7497],{"__ignoreMap":515},[710,7501,7502],{},[86,7503,7504,7507],{},[89,7505,7506],{},"Analogia completa",": O usuário pergunta ao garçom (API). O garçom anota o\npedido e entrega ao maestro (LangChain). O maestro pede ao bibliotecário\n(pgvector) os livros relevantes. Com os livros em mãos, o maestro entrega\ntudo ao estagiário (LLM), que lê e escreve a resposta. O garçom traz a\nresposta à mesa.",[283,7509,7511],{"id":7510},"exemplo-de-código-chain-rag-simples","Exemplo de código: chain RAG simples",[506,7513,7515],{"className":3439,"code":7514,"language":3441,"meta":515,"style":515},"# assistente_rag.py — Assistente de atendimento com RAG\nfrom langchain_openai import ChatOpenAI, OpenAIEmbeddings\nfrom langchain_community.vectorstores import PGVector\nfrom langchain.chains import RetrievalQA\nfrom langchain.text_splitter import RecursiveCharacterTextSplitter\n\n# 1. Preparar os documentos da empresa\n# Divide textos longos em pedaços menores (chunks) para busca eficiente\ntext_splitter = RecursiveCharacterTextSplitter(\n    chunk_size=1000,      # cada pedaço tem até 1000 caracteres\n    chunk_overlap=200,    # sobrepõe 200 caracteres para não cortar contexto\n)\n\n# 'documents' = lista de arquivos markdown\u002Ftxt carregados da pasta da empresa\nchunks = text_splitter.split_documents(documents)\n\n# 2. Criar embeddings e armazenar no banco de vetores\n# Converte cada pedaço de texto em um vetor numérico (embedding)\nembeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n\n# Armazena no PostgreSQL com pgvector (connection_string da sua instância)\nvectorstore = PGVector.from_documents(\n    documents=chunks,\n    embedding=embeddings,\n    connection_string=\"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fempresa\",\n    collection_name=\"base_conhecimento\",\n)\n\n# 3. Criar a chain de Q&A (pergunta e resposta)\n# O retriever busca os 4 pedaços de documento mais relevantes para cada pergunta\nllm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.3)  # temperature baixa = respostas mais precisas\n\nqa_chain = RetrievalQA.from_chain_type(\n    llm=llm,\n    chain_type=\"stuff\",          # junta todos os documentos no prompt\n    retriever=vectorstore.as_retriever(search_kwargs={\"k\": 4}),  # top 4 resultados\n    return_source_documents=True,  # retorna quais documentos foram usados (para auditoria)\n)\n\n# 4. Fazer uma pergunta\npergunta = \"Qual o prazo para solicitar férias?\"\nresposta = qa_chain.invoke({\"query\": pergunta})\n\nprint(resposta[\"result\"])          # texto da resposta\nprint(resposta[\"source_documents\"]) # documentos usados como base\n",[513,7516,7517,7522,7527,7532,7537,7542,7546,7551,7556,7561,7566,7571,7576,7580,7585,7590,7594,7599,7604,7609,7613,7618,7623,7628,7633,7638,7643,7647,7651,7656,7661,7666,7670,7675,7680,7685,7690,7695,7699,7703,7708,7713,7718,7722,7727],{"__ignoreMap":515},[1376,7518,7519],{"class":1378,"line":1379},[1376,7520,7521],{},"# assistente_rag.py — Assistente de atendimento com RAG\n",[1376,7523,7524],{"class":1378,"line":984},[1376,7525,7526],{},"from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n",[1376,7528,7529],{"class":1378,"line":981},[1376,7530,7531],{},"from langchain_community.vectorstores import PGVector\n",[1376,7533,7534],{"class":1378,"line":1395},[1376,7535,7536],{},"from langchain.chains import RetrievalQA\n",[1376,7538,7539],{"class":1378,"line":1401},[1376,7540,7541],{},"from langchain.text_splitter import RecursiveCharacterTextSplitter\n",[1376,7543,7544],{"class":1378,"line":1407},[1376,7545,1398],{"emptyLinePlaceholder":898},[1376,7547,7548],{"class":1378,"line":1413},[1376,7549,7550],{},"# 1. Preparar os documentos da empresa\n",[1376,7552,7553],{"class":1378,"line":1419},[1376,7554,7555],{},"# Divide textos longos em pedaços menores (chunks) para busca eficiente\n",[1376,7557,7558],{"class":1378,"line":1424},[1376,7559,7560],{},"text_splitter = RecursiveCharacterTextSplitter(\n",[1376,7562,7563],{"class":1378,"line":1430},[1376,7564,7565],{},"    chunk_size=1000,      # cada pedaço tem até 1000 caracteres\n",[1376,7567,7568],{"class":1378,"line":1436},[1376,7569,7570],{},"    chunk_overlap=200,    # sobrepõe 200 caracteres para não cortar contexto\n",[1376,7572,7573],{"class":1378,"line":1442},[1376,7574,7575],{},")\n",[1376,7577,7578],{"class":1378,"line":1447},[1376,7579,1398],{"emptyLinePlaceholder":898},[1376,7581,7582],{"class":1378,"line":1453},[1376,7583,7584],{},"# 'documents' = lista de arquivos markdown\u002Ftxt carregados da pasta da empresa\n",[1376,7586,7587],{"class":1378,"line":1459},[1376,7588,7589],{},"chunks = text_splitter.split_documents(documents)\n",[1376,7591,7592],{"class":1378,"line":2200},[1376,7593,1398],{"emptyLinePlaceholder":898},[1376,7595,7596],{"class":1378,"line":2208},[1376,7597,7598],{},"# 2. Criar embeddings e armazenar no banco de vetores\n",[1376,7600,7601],{"class":1378,"line":2219},[1376,7602,7603],{},"# Converte cada pedaço de texto em um vetor numérico (embedding)\n",[1376,7605,7606],{"class":1378,"line":2227},[1376,7607,7608],{},"embeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\n",[1376,7610,7611],{"class":1378,"line":2241},[1376,7612,1398],{"emptyLinePlaceholder":898},[1376,7614,7615],{"class":1378,"line":2253},[1376,7616,7617],{},"# Armazena no PostgreSQL com pgvector (connection_string da sua instância)\n",[1376,7619,7620],{"class":1378,"line":2283},[1376,7621,7622],{},"vectorstore = PGVector.from_documents(\n",[1376,7624,7625],{"class":1378,"line":2296},[1376,7626,7627],{},"    documents=chunks,\n",[1376,7629,7630],{"class":1378,"line":2311},[1376,7631,7632],{},"    embedding=embeddings,\n",[1376,7634,7635],{"class":1378,"line":2316},[1376,7636,7637],{},"    connection_string=\"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fempresa\",\n",[1376,7639,7640],{"class":1378,"line":2324},[1376,7641,7642],{},"    collection_name=\"base_conhecimento\",\n",[1376,7644,7645],{"class":1378,"line":2333},[1376,7646,7575],{},[1376,7648,7649],{"class":1378,"line":2340},[1376,7650,1398],{"emptyLinePlaceholder":898},[1376,7652,7653],{"class":1378,"line":2351},[1376,7654,7655],{},"# 3. Criar a chain de Q&A (pergunta e resposta)\n",[1376,7657,7658],{"class":1378,"line":2362},[1376,7659,7660],{},"# O retriever busca os 4 pedaços de documento mais relevantes para cada pergunta\n",[1376,7662,7663],{"class":1378,"line":2385},[1376,7664,7665],{},"llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.3)  # temperature baixa = respostas mais precisas\n",[1376,7667,7668],{"class":1378,"line":2396},[1376,7669,1398],{"emptyLinePlaceholder":898},[1376,7671,7672],{"class":1378,"line":2411},[1376,7673,7674],{},"qa_chain = RetrievalQA.from_chain_type(\n",[1376,7676,7677],{"class":1378,"line":2416},[1376,7678,7679],{},"    llm=llm,\n",[1376,7681,7682],{"class":1378,"line":2422},[1376,7683,7684],{},"    chain_type=\"stuff\",          # junta todos os documentos no prompt\n",[1376,7686,7687],{"class":1378,"line":2430},[1376,7688,7689],{},"    retriever=vectorstore.as_retriever(search_kwargs={\"k\": 4}),  # top 4 resultados\n",[1376,7691,7692],{"class":1378,"line":2448},[1376,7693,7694],{},"    return_source_documents=True,  # retorna quais documentos foram usados (para auditoria)\n",[1376,7696,7697],{"class":1378,"line":2457},[1376,7698,7575],{},[1376,7700,7701],{"class":1378,"line":2464},[1376,7702,1398],{"emptyLinePlaceholder":898},[1376,7704,7705],{"class":1378,"line":2475},[1376,7706,7707],{},"# 4. Fazer uma pergunta\n",[1376,7709,7710],{"class":1378,"line":2487},[1376,7711,7712],{},"pergunta = \"Qual o prazo para solicitar férias?\"\n",[1376,7714,7715],{"class":1378,"line":2499},[1376,7716,7717],{},"resposta = qa_chain.invoke({\"query\": pergunta})\n",[1376,7719,7720],{"class":1378,"line":2506},[1376,7721,1398],{"emptyLinePlaceholder":898},[1376,7723,7724],{"class":1378,"line":2517},[1376,7725,7726],{},"print(resposta[\"result\"])          # texto da resposta\n",[1376,7728,7729],{"class":1378,"line":2528},[1376,7730,7731],{},"print(resposta[\"source_documents\"]) # documentos usados como base\n",[710,7733,7734],{},[86,7735,7736,7739],{},[89,7737,7738],{},"Nota",": Os comentários no código explicam cada passo. Se você é iniciante\nem Python, comece copiando este exemplo e adaptando a pasta de documentos.\nNão tente entender tudo de uma vez — rode, veja funcionar, depois estude\ncada parte.",[12,7741,7743],{"id":7742},"do-prototype-ao-produção-producao","Do prototype ao produção {#producao}",[283,7745,7747],{"id":7746},"fase-1-prototype-1-2-semanas","Fase 1: Prototype (1-2 semanas)",[86,7749,7750,7753],{},[89,7751,7752],{},"Objetivo",": provar que a ideia funciona com dados reais da empresa.",[528,7755,7756,7759,7762,7765,7768],{},[20,7757,7758],{},"Carregue sua base de conhecimento em markdown\u002Ftxt (FAQ, manuais, políticas)",[20,7760,7761],{},"Crie embeddings e armazene no pgvector",[20,7763,7764],{},"Construa a chain LangChain simples (como no código acima)",[20,7766,7767],{},"Teste com 20 perguntas reais que clientes costumam fazer",[20,7769,7770,7773],{},[89,7771,7772],{},"Marque acertos e erros",": quantas das 20 respostas estavam corretas?",[710,7775,7776],{},[86,7777,7778,7780],{},[89,7779,7223],{},": Testar só com perguntas fáceis. \"Qual o horário de\nfuncionamento?\" todo mundo acerta. Teste perguntas difíceis: \"posso\ncancelar o plano e reembolsar os 3 meses que já paguei?\" — esse tipo de\npergunta revela se a base de conhecimento está completa.",[283,7782,7784],{"id":7783},"fase-2-validação-2-3-semanas","Fase 2: Validação (2-3 semanas)",[86,7786,7787,7789],{},[89,7788,7752],{},": validar com usuários reais antes do lançamento oficial.",[528,7791,7792,7795,7818,7835,7838],{},[20,7793,7794],{},"Adicione logging de todas as interações (quem perguntou, o que perguntou,\nqual resposta, se foi útil)",[20,7796,7797,7798],{},"Meça três métricas essenciais:\n",[528,7799,7800,7806,7812],{},[20,7801,7802,7805],{},[89,7803,7804],{},"Taxa de resposta útil",": % de respostas que o usuário classificou como\n\"útil\" (coloque um botão de thumbs up\u002Fdown)",[20,7807,7808,7811],{},[89,7809,7810],{},"Tempo de resposta",": quanto tempo até a resposta aparecer (meta: \u003C 5s)",[20,7813,7814,7817],{},[89,7815,7816],{},"Custo por query",": quanto custa cada interação em USD (meta: \u003C $0.01)",[20,7819,7820,7821],{},"Ajuste o prompt e parâmetros:\n",[528,7822,7823,7829],{},[20,7824,7825,7828],{},[513,7826,7827],{},"temperature",": 0.1-0.3 para respostas factuais, 0.7+ para criativas",[20,7830,7831,7834],{},[513,7832,7833],{},"top_k"," (número de documentos recuperados): comece com 4, ajuste conforme\npreciso\u002Frecall",[20,7836,7837],{},"Teste com 5 usuários reais por 1 semana",[20,7839,7840],{},"Colete feedback estruturado: \"a resposta ajudou? O que faltou?\"",[283,7842,7844],{"id":7843},"fase-3-produção-1-2-semanas","Fase 3: Produção (1-2 semanas)",[86,7846,7847,7849],{},[89,7848,7752],{},": lançar com segurança e monitoramento.",[528,7851,7852,7855,7858,7864,7880],{},[20,7853,7854],{},"Deploy em ambiente estável (Docker + Railway\u002FFly.io\u002FEC2)",[20,7856,7857],{},"Adicione monitoramento: latência, taxa de erro, custo acumulado",[20,7859,7860,7863],{},[89,7861,7862],{},"Implemente fallback obrigatório",": se a API do LLM falhar (timeout, erro\n500, limite excedido), responder com uma busca simples por palavra-chave ou\ndirecionar para um humano",[20,7865,7866,1750,7869],{},[89,7867,7868],{},"Rollout gradual",[528,7870,7871,7874,7877],{},[20,7872,7873],{},"Semana 1: 10% do tráfego vê o chatbot",[20,7875,7876],{},"Semana 2: 50% do tráfego",[20,7878,7879],{},"Semana 3: 100% (se métricas estiverem boas)",[20,7881,7882],{},"Tenha um humano de plantão para os casos que o bot não resolve",[710,7884,7885],{},[86,7886,7887,7889],{},[89,7888,7223],{},": Lançar para 100% dos usuários de uma vez. Se o prompt tem\num bug, todos os clientes têm uma experiência ruim. Rollout gradual permite\ndetectar problemas cedo e ajustar sem afetar toda a base.",[12,7891,7893],{"id":7892},"custos-reais-custos","Custos reais {#custos}",[86,7895,7896],{},"Para um assistente de atendimento com 1.000 consultas\u002Fmês (uma empresa pequena\ntípica):",[165,7898,7899,7912],{},[168,7900,7901],{},[171,7902,7903,7906,7909],{},[174,7904,7905],{},"Componente",[174,7907,7908],{},"Custo mensal (estimado)",[174,7910,7911],{},"Observação",[184,7913,7914,7925,7936,7947,7958],{},[171,7915,7916,7919,7922],{},[189,7917,7918],{},"GPT-4o-mini (~500 tokens\u002Fquery)",[189,7920,7921],{},"~$2-5 USD",[189,7923,7924],{},"$0.15 por 1M tokens de entrada",[171,7926,7927,7930,7933],{},[189,7928,7929],{},"Embeddings (indexação inicial)",[189,7931,7932],{},"~$0.50 USD (one-time)",[189,7934,7935],{},"Indexar 100 documentos",[171,7937,7938,7941,7944],{},[189,7939,7940],{},"pgvector (instância existente)",[189,7942,7943],{},"$0",[189,7945,7946],{},"Se já tem PostgreSQL",[171,7948,7949,7952,7955],{},[189,7950,7951],{},"Hospedagem API (Railway\u002FFly.io)",[189,7953,7954],{},"$5-10 USD",[189,7956,7957],{},"Plano inicial",[171,7959,7960,7965,7970],{},[189,7961,7962],{},[89,7963,7964],{},"Total",[189,7966,7967],{},[89,7968,7969],{},"~$7-15 USD\u002Fmês",[189,7971,7972],{},"Menos de R$ 80\u002Fmês",[86,7974,7975],{},"Comparado: um atendente humano custa R$ 2.500-4.000\u002Fmês (salário + encargos).\nO ROI é evidente mesmo com adoção parcial. Se o chatbot reduz 60% dos tickets\ntier-1 (perguntas simples), e você tem 300 tickets\u002Fmês, economiza ~180 horas\nde atendimento humano por mês.",[283,7977,7979],{"id":7978},"cenários-de-escala","Cenários de escala",[165,7981,7982,7994],{},[168,7983,7984],{},[171,7985,7986,7989,7991],{},[174,7987,7988],{},"Volume",[174,7990,668],{},[174,7992,7993],{},"Economia estimada",[184,7995,7996,8007,8018,8029],{},[171,7997,7998,8001,8004],{},[189,7999,8000],{},"1.000 queries\u002Fmês",[189,8002,8003],{},"$7-15",[189,8005,8006],{},"~30h humanas\u002Fmês",[171,8008,8009,8012,8015],{},[189,8010,8011],{},"5.000 queries\u002Fmês",[189,8013,8014],{},"$20-40",[189,8016,8017],{},"~150h humanas\u002Fmês",[171,8019,8020,8023,8026],{},[189,8021,8022],{},"20.000 queries\u002Fmês",[189,8024,8025],{},"$60-120",[189,8027,8028],{},"~600h humanas\u002Fmês",[171,8030,8031,8034,8037],{},[189,8032,8033],{},"100.000 queries\u002Fmês",[189,8035,8036],{},"$250-500",[189,8038,8039],{},"~3.000h humanas\u002Fmês",[12,8041,820],{"id":819},[283,8043,8045],{"id":8044},"erro-1-começar-pelo-modelo-não-pelo-problema","Erro 1: Começar pelo modelo, não pelo problema",[86,8047,8048,8050],{},[89,8049,4164],{},": O dono ouve \"GPT-4 é o melhor\" e quer usar GPT-4 para tudo.\nGasta semanas integrando a API mais cara sem ter clareza do problema.",[86,8052,8053,4171],{},[89,8054,4170],{},[506,8056,8058],{"className":3439,"code":8057,"language":3441,"meta":515,"style":515},"# ❌ ERRADO: escolher modelo antes de entender o problema\nllm = ChatOpenAI(model=\"gpt-4\")  # $30 por 1M tokens — 200x mais caro\n# \"Vou usar o melhor modelo para garantir qualidade\"\n# Mas: qual problema você está resolvendo? Quais dados? Qual volume?\n",[513,8059,8060,8065,8070,8075],{"__ignoreMap":515},[1376,8061,8062],{"class":1378,"line":1379},[1376,8063,8064],{},"# ❌ ERRADO: escolher modelo antes de entender o problema\n",[1376,8066,8067],{"class":1378,"line":984},[1376,8068,8069],{},"llm = ChatOpenAI(model=\"gpt-4\")  # $30 por 1M tokens — 200x mais caro\n",[1376,8071,8072],{"class":1378,"line":981},[1376,8073,8074],{},"# \"Vou usar o melhor modelo para garantir qualidade\"\n",[1376,8076,8077],{"class":1378,"line":1395},[1376,8078,8079],{},"# Mas: qual problema você está resolvendo? Quais dados? Qual volume?\n",[86,8081,8082,4171],{},[89,8083,4182],{},[506,8085,8087],{"className":3439,"code":8086,"language":3441,"meta":515,"style":515},"# ✅ CERTO: entender o problema primeiro, depois escolher modelo\n# 1. Qual o problema? Atendimento ao cliente (FAQ)\n# 2. Qual o volume? 1.000\u002Fdia\n# 3. Qual a complexidade? Baixa (perguntas recorrentes)\n# Conclusão: GPT-4o-mini é suficiente (100x mais barato que GPT-4)\nllm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.3)\n",[513,8088,8089,8094,8099,8104,8109,8114],{"__ignoreMap":515},[1376,8090,8091],{"class":1378,"line":1379},[1376,8092,8093],{},"# ✅ CERTO: entender o problema primeiro, depois escolher modelo\n",[1376,8095,8096],{"class":1378,"line":984},[1376,8097,8098],{},"# 1. Qual o problema? Atendimento ao cliente (FAQ)\n",[1376,8100,8101],{"class":1378,"line":981},[1376,8102,8103],{},"# 2. Qual o volume? 1.000\u002Fdia\n",[1376,8105,8106],{"class":1378,"line":1395},[1376,8107,8108],{},"# 3. Qual a complexidade? Baixa (perguntas recorrentes)\n",[1376,8110,8111],{"class":1378,"line":1401},[1376,8112,8113],{},"# Conclusão: GPT-4o-mini é suficiente (100x mais barato que GPT-4)\n",[1376,8115,8116],{"class":1378,"line":1407},[1376,8117,8118],{},"llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.3)\n",[283,8120,8122],{"id":8121},"erro-2-ignorar-qualidade-dos-dados","Erro 2: Ignorar qualidade dos dados",[86,8124,8125,8127],{},[89,8126,4164],{},": A empresa tem manuais desatualizados, contraditórios e\nespalhados em PDFs, Word e e-mails. O RAG busca esses documentos e responde\ncom informações erradas ou conflitantes.",[86,8129,8130,4171],{},[89,8131,4170],{},[506,8133,8135],{"className":3439,"code":8134,"language":3441,"meta":515,"style":515},"# ❌ ERRADO: jogar tudo no vector store sem limpar\nvectorstore = PGVector.from_documents(\n    documents=todos_arquivos_aleatorios,  # inclui versão 2019 do manual + versão 2026\n    embedding=embeddings,\n)\n# Resultado: IA responde com política de 2019 que já foi cancelada\n",[513,8136,8137,8142,8146,8151,8155,8159],{"__ignoreMap":515},[1376,8138,8139],{"class":1378,"line":1379},[1376,8140,8141],{},"# ❌ ERRADO: jogar tudo no vector store sem limpar\n",[1376,8143,8144],{"class":1378,"line":984},[1376,8145,7622],{},[1376,8147,8148],{"class":1378,"line":981},[1376,8149,8150],{},"    documents=todos_arquivos_aleatorios,  # inclui versão 2019 do manual + versão 2026\n",[1376,8152,8153],{"class":1378,"line":1395},[1376,8154,7632],{},[1376,8156,8157],{"class":1378,"line":1401},[1376,8158,7575],{},[1376,8160,8161],{"class":1378,"line":1407},[1376,8162,8163],{},"# Resultado: IA responde com política de 2019 que já foi cancelada\n",[86,8165,8166,4171],{},[89,8167,4182],{},[506,8169,8171],{"className":3439,"code":8170,"language":3441,"meta":515,"style":515},"# ✅ CERTO: limpar e versionar antes de indexar\n# 1. Remover documentos antigos (manuais de 2019, políticas revogadas)\n# 2. Consolidar versões (um único manual atual, não 5 versões)\n# 3. Padronizar formato (markdown é ideal)\n# 4. Adicionar metadados: data, versão, departamento\ndocuments = limpar_e_versionar(pasta_documentos)\n# Cada documento tem metadata: {\"versao\": \"2026.1\", \"atualizado\": \"2026-08-01\"}\n",[513,8172,8173,8178,8183,8188,8193,8198,8203],{"__ignoreMap":515},[1376,8174,8175],{"class":1378,"line":1379},[1376,8176,8177],{},"# ✅ CERTO: limpar e versionar antes de indexar\n",[1376,8179,8180],{"class":1378,"line":984},[1376,8181,8182],{},"# 1. Remover documentos antigos (manuais de 2019, políticas revogadas)\n",[1376,8184,8185],{"class":1378,"line":981},[1376,8186,8187],{},"# 2. Consolidar versões (um único manual atual, não 5 versões)\n",[1376,8189,8190],{"class":1378,"line":1395},[1376,8191,8192],{},"# 3. Padronizar formato (markdown é ideal)\n",[1376,8194,8195],{"class":1378,"line":1401},[1376,8196,8197],{},"# 4. Adicionar metadados: data, versão, departamento\n",[1376,8199,8200],{"class":1378,"line":1407},[1376,8201,8202],{},"documents = limpar_e_versionar(pasta_documentos)\n",[1376,8204,8205],{"class":1378,"line":1413},[1376,8206,8207],{},"# Cada documento tem metadata: {\"versao\": \"2026.1\", \"atualizado\": \"2026-08-01\"}\n",[710,8209,8210],{},[86,8211,8212,8215],{},[89,8213,8214],{},"Regra de ouro",": \"Garbage in, garbage out\" (lixo entra, lixo sai). A IA é\ntão boa quanto os dados que consultou. Invista 50% do tempo do projeto em\nlimpar e organizar a base de conhecimento.",[283,8217,8219],{"id":8218},"erro-3-não-medir-nada","Erro 3: Não medir nada",[86,8221,8222,8224],{},[89,8223,4164],{},": O chatbot entra em produção e ninguém sabe se está\najudando ou atrapalhando. Três meses depois, descobre que 40% das respostas\neram erradas e os clientes estavam frustrados.",[86,8226,8227,4171],{},[89,8228,4170],{},[506,8230,8232],{"className":3439,"code":8231,"language":3441,"meta":515,"style":515},"# ❌ ERRADO: sem logging, sem métricas\nresposta = qa_chain.invoke({\"query\": pergunta})\nreturn resposta[\"result\"]  # pronto, jogou a resposta fora sem registrar nada\n",[513,8233,8234,8239,8243],{"__ignoreMap":515},[1376,8235,8236],{"class":1378,"line":1379},[1376,8237,8238],{},"# ❌ ERRADO: sem logging, sem métricas\n",[1376,8240,8241],{"class":1378,"line":984},[1376,8242,7717],{},[1376,8244,8245],{"class":1378,"line":981},[1376,8246,8247],{},"return resposta[\"result\"]  # pronto, jogou a resposta fora sem registrar nada\n",[86,8249,8250,4171],{},[89,8251,4182],{},[506,8253,8255],{"className":3439,"code":8254,"language":3441,"meta":515,"style":515},"# ✅ CERTO: logar toda interação\nimport logging\nfrom datetime import datetime\n\nlogger = logging.getLogger(\"assistente\")\n\nresposta = qa_chain.invoke({\"query\": pergunta})\n\n# Log estruturado para análise posterior\nlogger.info({\n    \"timestamp\": datetime.utcnow().isoformat(),\n    \"pergunta\": pergunta,\n    \"resposta\": resposta[\"result\"],\n    \"documentos_usados\": [doc.metadata for doc in resposta[\"source_documents\"]],\n    \"tempo_ms\": tempo_resposta_ms,\n    \"tokens_entrada\": resposta[\"usage\"][\"prompt_tokens\"],\n    \"tokens_saida\": resposta[\"usage\"][\"completion_tokens\"],\n    \"custo_estimado\": calcular_custo(resposta[\"usage\"]),\n})\n",[513,8256,8257,8262,8266,8270,8274,8279,8283,8287,8291,8296,8301,8306,8311,8316,8321,8326,8331,8336,8341],{"__ignoreMap":515},[1376,8258,8259],{"class":1378,"line":1379},[1376,8260,8261],{},"# ✅ CERTO: logar toda interação\n",[1376,8263,8264],{"class":1378,"line":984},[1376,8265,4379],{},[1376,8267,8268],{"class":1378,"line":981},[1376,8269,4384],{},[1376,8271,8272],{"class":1378,"line":1395},[1376,8273,1398],{"emptyLinePlaceholder":898},[1376,8275,8276],{"class":1378,"line":1401},[1376,8277,8278],{},"logger = logging.getLogger(\"assistente\")\n",[1376,8280,8281],{"class":1378,"line":1407},[1376,8282,1398],{"emptyLinePlaceholder":898},[1376,8284,8285],{"class":1378,"line":1413},[1376,8286,7717],{},[1376,8288,8289],{"class":1378,"line":1419},[1376,8290,1398],{"emptyLinePlaceholder":898},[1376,8292,8293],{"class":1378,"line":1424},[1376,8294,8295],{},"# Log estruturado para análise posterior\n",[1376,8297,8298],{"class":1378,"line":1430},[1376,8299,8300],{},"logger.info({\n",[1376,8302,8303],{"class":1378,"line":1436},[1376,8304,8305],{},"    \"timestamp\": datetime.utcnow().isoformat(),\n",[1376,8307,8308],{"class":1378,"line":1442},[1376,8309,8310],{},"    \"pergunta\": pergunta,\n",[1376,8312,8313],{"class":1378,"line":1447},[1376,8314,8315],{},"    \"resposta\": resposta[\"result\"],\n",[1376,8317,8318],{"class":1378,"line":1453},[1376,8319,8320],{},"    \"documentos_usados\": [doc.metadata for doc in resposta[\"source_documents\"]],\n",[1376,8322,8323],{"class":1378,"line":1459},[1376,8324,8325],{},"    \"tempo_ms\": tempo_resposta_ms,\n",[1376,8327,8328],{"class":1378,"line":2200},[1376,8329,8330],{},"    \"tokens_entrada\": resposta[\"usage\"][\"prompt_tokens\"],\n",[1376,8332,8333],{"class":1378,"line":2208},[1376,8334,8335],{},"    \"tokens_saida\": resposta[\"usage\"][\"completion_tokens\"],\n",[1376,8337,8338],{"class":1378,"line":2219},[1376,8339,8340],{},"    \"custo_estimado\": calcular_custo(resposta[\"usage\"]),\n",[1376,8342,8343],{"class":1378,"line":2227},[1376,8344,8345],{},"})\n",[283,8347,8349],{"id":8348},"erro-4-over-engineering","Erro 4: Over-engineering",[86,8351,8352,8354],{},[89,8353,4164],{},": Time começa com microserviços, Kubernetes, Kafka, Redis\ne 3 ambientes para um chatbot que recebe 100 consultas\u002Fdia. Gastam 3 meses\nem infraestrutura antes de ter uma resposta funcional.",[86,8356,8357,4171],{},[89,8358,4170],{},[506,8360,8363],{"className":8361,"code":8362,"language":511},[509],"❌ Arquitetura para 100 queries\u002Fdia:\n[Chatbot] → [Kubernetes] → [Kafka] → [Redis] → [3 microserviços] → [K8s cluster]\nCusto: $500+\u002Fmês em infra, 3 meses de setup, 5 serviços para manter\n",[513,8364,8362],{"__ignoreMap":515},[86,8366,8367,4171],{},[89,8368,4182],{},[506,8370,8373],{"className":8371,"code":8372,"language":511},[509],"✅ Arquitetura para 100 queries\u002Fdia:\n[Chatbot] → [FastAPI] → [pgvector] → [OpenAI API]\nCusto: $10\u002Fmês, 1 semana de setup, 1 serviço para manter\n",[513,8374,8372],{"__ignoreMap":515},[710,8376,8377],{},[86,8378,8379,8381],{},[89,8380,1534],{},": Comece simples. Um arquivo Python rodando em uma instância\npequena. Escale a arquitetura só quando o volume justificar. Se está\ngastando mais tempo em DevOps do que em IA, as prioridades estão erradas.",[283,8383,8385],{"id":8384},"erro-5-não-ter-fallback","Erro 5: Não ter fallback",[86,8387,8388,8390],{},[89,8389,4164],{},": A OpenAI tem uma instabilidade de 2 horas. Seu chatbot\nfica offline. Clientes não conseguem falar com ninguém.",[86,8392,8393,4171],{},[89,8394,4170],{},[506,8396,8398],{"className":3439,"code":8397,"language":3441,"meta":515,"style":515},"# ❌ ERRADO: sem fallback, se a API cai, o sistema cai\nresposta = qa_chain.invoke({\"query\": pergunta})\nreturn resposta[\"result\"]  # se der timeout, retorna erro 500 para o cliente\n",[513,8399,8400,8405,8409],{"__ignoreMap":515},[1376,8401,8402],{"class":1378,"line":1379},[1376,8403,8404],{},"# ❌ ERRADO: sem fallback, se a API cai, o sistema cai\n",[1376,8406,8407],{"class":1378,"line":984},[1376,8408,7717],{},[1376,8410,8411],{"class":1378,"line":981},[1376,8412,8413],{},"return resposta[\"result\"]  # se der timeout, retorna erro 500 para o cliente\n",[86,8415,8416,4171],{},[89,8417,4182],{},[506,8419,8421],{"className":3439,"code":8420,"language":3441,"meta":515,"style":515},"# ✅ CERTO: fallback graceful\ntry:\n    resposta = qa_chain.invoke({\"query\": pergunta})\n    return resposta[\"result\"]\nexcept (TimeoutError, APIError) as e:\n    logger.error(f\"LLM indisponível: {e}\")\n    # Fallback 1: busca simples por palavra-chave na base\n    resultados = busca_simples_por_keyword(pergunta, base_conhecimento)\n    if resultados:\n        return f\"Encontrei isto na nossa base:\\n\\n{resultados[0]}\"\n    # Fallback 2: direcionar para humano\n    return \"Não consigo responder agora, mas um atendente entrará em contato em breve.\"\n",[513,8422,8423,8428,8432,8437,8442,8447,8452,8457,8462,8467,8472,8477],{"__ignoreMap":515},[1376,8424,8425],{"class":1378,"line":1379},[1376,8426,8427],{},"# ✅ CERTO: fallback graceful\n",[1376,8429,8430],{"class":1378,"line":984},[1376,8431,4532],{},[1376,8433,8434],{"class":1378,"line":981},[1376,8435,8436],{},"    resposta = qa_chain.invoke({\"query\": pergunta})\n",[1376,8438,8439],{"class":1378,"line":1395},[1376,8440,8441],{},"    return resposta[\"result\"]\n",[1376,8443,8444],{"class":1378,"line":1401},[1376,8445,8446],{},"except (TimeoutError, APIError) as e:\n",[1376,8448,8449],{"class":1378,"line":1407},[1376,8450,8451],{},"    logger.error(f\"LLM indisponível: {e}\")\n",[1376,8453,8454],{"class":1378,"line":1413},[1376,8455,8456],{},"    # Fallback 1: busca simples por palavra-chave na base\n",[1376,8458,8459],{"class":1378,"line":1419},[1376,8460,8461],{},"    resultados = busca_simples_por_keyword(pergunta, base_conhecimento)\n",[1376,8463,8464],{"class":1378,"line":1424},[1376,8465,8466],{},"    if resultados:\n",[1376,8468,8469],{"class":1378,"line":1430},[1376,8470,8471],{},"        return f\"Encontrei isto na nossa base:\\n\\n{resultados[0]}\"\n",[1376,8473,8474],{"class":1378,"line":1436},[1376,8475,8476],{},"    # Fallback 2: direcionar para humano\n",[1376,8478,8479],{"class":1378,"line":1442},[1376,8480,8481],{},"    return \"Não consigo responder agora, mas um atendente entrará em contato em breve.\"\n",[12,8483,8485],{"id":8484},"caso-real-consultoria-que-economizou-30hsemana-caso-real","Caso real: consultoria que economizou 30h\u002Fsemana {#caso-real}",[86,8487,8488],{},"Uma consultoria contábil com 12 funcionários gastava ~30h\u002Fsemana respondendo\nperguntas repetitivas de clientes via WhatsApp e e-mail (\"qual o prazo do\nDCTF?\", \"como emitir NFSe?\", \"qual a alíquota de ISS em São Paulo?\").",[86,8490,8491,8494],{},[89,8492,8493],{},"Solução implementada"," (6 semanas, ~$15\u002Fmês):",[17,8496,8497,8500,8503,8506,8509,8512],{},[20,8498,8499],{},"Organizaram 80 documentos de FAQ em markdown (3 dias de trabalho)",[20,8501,8502],{},"Indexaram com pgvector + OpenAI embeddings (1 dia)",[20,8504,8505],{},"Criaram chain RAG com GPT-4o-mini (2 dias)",[20,8507,8508],{},"Integraram com WhatsApp Business API (1 semana)",[20,8510,8511],{},"Validaram com 5 clientes por 2 semanas",[20,8513,8514],{},"Lançaram para todos os clientes",[86,8516,8517,4171],{},[89,8518,8519],{},"Resultados em 3 meses",[528,8521,8522,8525,8528,8531,8534],{},[20,8523,8524],{},"65% das perguntas respondidas pela IA sem intervenção humana",[20,8526,8527],{},"Tempo médio de resposta: 8 segundos (antes: 4 horas)",[20,8529,8530],{},"Satisfação dos clientes: 4.3\u002F5 (antes: 3.1\u002F5 — demorava muito)",[20,8532,8533],{},"30h\u002Fsemana liberadas para trabalho contábil de maior valor",[20,8535,8536],{},"Custo: $12\u002Fmês (1.800 mensagens\u002Fmês)",[86,8538,8539,8541],{},[89,8540,4814],{},": Comece com o problema mais repetitivo e visível. A tecnologia é\nbarata, a organização dos dados é que demanda esforço.",[968,8543],{},[86,8545,8546,8549,8550,8552],{},[89,8547,8548],{},"Precisa de ajuda para implementar IA na sua empresa?"," A Inicialize Tec\ndesenvolve soluções de inteligência artificial sob medida — do diagnóstico\nao deploy. ",[23,8551,6840],{"href":978}," e conversamos sobre seu caso.",[2883,8554,4828],{},{"title":515,"searchDepth":981,"depth":981,"links":8556},[8557,8558,8561,8564,8567,8573,8580,8585,8588,8595],{"id":14,"depth":984,"text":15},{"id":6944,"depth":984,"text":6945,"children":8559},[8560],{"id":6987,"depth":981,"text":6988},{"id":7037,"depth":984,"text":7038,"children":8562},[8563],{"id":7184,"depth":981,"text":7185},{"id":7227,"depth":984,"text":7228,"children":8565},[8566],{"id":7270,"depth":981,"text":7271},{"id":7325,"depth":984,"text":7326,"children":8568},[8569,8570,8571,8572],{"id":7332,"depth":981,"text":7333},{"id":7353,"depth":981,"text":7354},{"id":7371,"depth":981,"text":6372},{"id":7388,"depth":981,"text":7389},{"id":7406,"depth":984,"text":7407,"children":8574},[8575,8576,8577,8578,8579],{"id":7413,"depth":981,"text":7414},{"id":7436,"depth":981,"text":7437},{"id":7459,"depth":981,"text":7460},{"id":7479,"depth":981,"text":7480},{"id":7510,"depth":981,"text":7511},{"id":7742,"depth":984,"text":7743,"children":8581},[8582,8583,8584],{"id":7746,"depth":981,"text":7747},{"id":7783,"depth":981,"text":7784},{"id":7843,"depth":981,"text":7844},{"id":7892,"depth":984,"text":7893,"children":8586},[8587],{"id":7978,"depth":981,"text":7979},{"id":819,"depth":984,"text":820,"children":8589},[8590,8591,8592,8593,8594],{"id":8044,"depth":981,"text":8045},{"id":8121,"depth":981,"text":8122},{"id":8218,"depth":981,"text":8219},{"id":8348,"depth":981,"text":8349},{"id":8384,"depth":981,"text":8385},{"id":8484,"depth":984,"text":8485},"Aprenda a implementar inteligência artificial em pequenas empresas sem orçamento de grande corporação. Stack técnica, custos reais e erros comuns.",[8598,8599,8600,8601,4860],"inteligência artificial","IA para pequenas empresas","implementar IA","machine learning",{},"\u002Fia\u002Fcomo-implementar-ia-em-empresa-pequena",{"title":6885,"description":8596},"como-implementar-ia-em-empresa-pequena","ia\u002Fcomo-implementar-ia-em-empresa-pequena","Q1jcO2NmntuHC4ijkMvONJaaCLY7I1P3se5qkid_peE",{"id":8609,"title":8610,"author":7,"body":8611,"category":6869,"date":1028,"description":10059,"extension":1030,"keywords":10060,"meta":10066,"navigation":898,"path":10067,"seo":10068,"slug":10069,"stem":10070,"updated":1028,"__hash__":10071},"content\u002Fia\u002Fcusto-de-implementar-inteligencia-artificial.md","Custo de implementar inteligência artificial: guia completo",{"type":9,"value":8612,"toc":10025},[8613,8615,8680,8684,8703,8706,8713,8717,8720,8724,8731,8736,8744,8748,8751,8756,8764,8768,8847,8853,8857,8864,8870,8884,8887,8891,9014,9019,9023,9030,9051,9054,9067,9070,9083,9089,9093,9203,9206,9210,9217,9221,9294,9297,9301,9304,9377,9380,9390,9394,9401,9404,9509,9512,9516,9542,9546,9549,9569,9573,9580,9584,9664,9668,9671,9685,9692,9696,9703,9794,9797,9801,9807,9811,9818,9821,9835,9842,9846,9852,9858,9872,9876,9918,9922,9960,9962,10012,10014,10023],[12,8614,15],{"id":14},[17,8616,8617,8623,8629,8635,8641,8647,8653,8659,8664,8670,8676],{},[20,8618,8619],{},[23,8620,8622],{"href":8621},"#o-que-custa","O que realmente custa em IA",[20,8624,8625],{},[23,8626,8628],{"href":8627},"#modelos-de-custo","Modelos de custo: API vs modelo próprio",[20,8630,8631],{},[23,8632,8634],{"href":8633},"#custo-api","Custo de API: tokens explicados",[20,8636,8637],{},[23,8638,8640],{"href":8639},"#infraestrutura","Infraestrutura: o que você precisa hospedar",[20,8642,8643],{},[23,8644,8646],{"href":8645},"#desenvolvimento","Desenvolvimento: gente e tempo",[20,8648,8649],{},[23,8650,8652],{"href":8651},"#manutencao","Manutenção e custos recorrentes",[20,8654,8655],{},[23,8656,8658],{"href":8657},"#tabela-consolidada","Tabela consolidada de custos",[20,8660,8661],{},[23,8662,8663],{"href":55},"Calculando o ROI",[20,8665,8666],{},[23,8667,8669],{"href":8668},"#escondidos","Custos escondidos que ninguém conta",[20,8671,8672],{},[23,8673,8675],{"href":8674},"#reduzir","Como reduzir custos sem perder qualidade",[20,8677,8678],{},[23,8679,74],{"href":73},[12,8681,8683],{"id":8682},"o-que-realmente-custa-em-ia-o-que-custa","O que realmente custa em IA {#o-que-custa}",[86,8685,8686,8687,8690,8691,1327,8694,1327,8697,3022,8700,522],{},"Quando alguém fala \"vamos colocar IA na empresa\", a primeira pergunta do\nfinanceiro é sempre a mesma: ",[89,8688,8689],{},"quanto isso vai custar?",". A resposta honesta\né \"depende\", mas depende de coisas que dá para enumerar. Neste guia vamos\nabrir em quatro grandes blocos o que entra no orçamento de um projeto de\ninteligência artificial: ",[89,8692,8693],{},"API (o uso do modelo)",[89,8695,8696],{},"infraestrutura (onde\nroda)",[89,8698,8699],{},"desenvolvimento (quem constrói)",[89,8701,8702],{},"manutenção (quem cuida\ndepois)",[86,8704,8705],{},"Pense em IA como montar uma cozinha profissional. O modelo (o \"chef\") é só\numa parte. Você também precisa do fogão (infraestrutura), de quem projeta o\ncardápio (desenvolvimento) e de quem mantém tudo limpo e funcionando\n(manutenção). Ignorar qualquer um desses blocos é a receita certa para um\nprojeto que estoura o orçamento.",[86,8707,8708,8709,8712],{},"Um modelo de linguagem (LLM, de ",[501,8710,8711],{},"Large Language Model",") é um modelo de IA\ntreinado para entender e gerar texto — o ChatGPT é o exemplo mais conhecido.\nVocê não compra um LLM; você o aluga por uso (via API) ou o hospeda você\nmesmo. Essa escolha muda tudo no orçamento, então vamos por partes.",[12,8714,8716],{"id":8715},"modelos-de-custo-api-vs-modelo-próprio-modelos-de-custo","Modelos de custo: API vs modelo próprio {#modelos-de-custo}",[86,8718,8719],{},"Existem duas formas principais de usar um modelo de IA, e elas têm perfis de\ncusto completamente diferentes.",[283,8721,8723],{"id":8722},"caminho-1-api-de-um-provedor","Caminho 1: API de um provedor",[86,8725,8726,8727,8730],{},"Você chama o modelo pela internet, igual chama qualquer API. Paga por uso\n(medido em ",[501,8728,8729],{},"tokens",", que explicamos adiante). O provedor (OpenAI, Anthropic,\nGoogle, Cohere) cuida de tudo: servidores, atualizações, escala.",[86,8732,8733,8735],{},[89,8734,3011],{},": é como pedir comida no iFood. Você não tem cozinha, não tem\nchef, paga pelo que consome. Ótimo para começar, péssimo se você serve 10.000\npratos por dia.",[528,8737,8738,8741],{},[20,8739,8740],{},"Pró: custo quase zero para começar, escala sozinho, sempre atualizado",[20,8742,8743],{},"Contra: custo cresce linearmente com o uso, dependência de terceiro, dados\nsaem da sua infraestrutura",[283,8745,8747],{"id":8746},"caminho-2-modelo-open-source-hospedado-por-você","Caminho 2: Modelo open source hospedado por você",[86,8749,8750],{},"Você pega um modelo de código aberto (Llama, Mistral, Qwen) e roda na sua\nprópria infraestrutura. Paga pela máquina (GPU\u002FCPU), independentemente de\nquantas chamadas faz.",[86,8752,8753,8755],{},[89,8754,3011],{},": é como abrir seu próprio restaurante. Investimento alto no\ncomeço, mas o custo por prato cai drasticamente quando você tem volume.",[528,8757,8758,8761],{},[20,8759,8760],{},"Pró: custo previsível, dados ficam com você, sem dependência",[20,8762,8763],{},"Contra: precisa de gente especializada, investimento inicial alto,\nvocê cuida da atualização",[283,8765,8767],{"id":8766},"quando-cada-um-vale","Quando cada um vale?",[165,8769,8770,8782],{},[168,8771,8772],{},[171,8773,8774,8777,8779],{},[174,8775,8776],{},"Cenário",[174,8778,7262],{},[174,8780,8781],{},"Por quê",[184,8783,8784,8794,8805,8816,8827,8837],{},[171,8785,8786,8789,8791],{},[189,8787,8788],{},"Até ~50k tokens\u002Fdia",[189,8790,6416],{},[189,8792,8793],{},"Custo baixo, zero infra",[171,8795,8796,8799,8802],{},[189,8797,8798],{},"50k–500k tokens\u002Fdia",[189,8800,8801],{},"API, mas monitorar",[189,8803,8804],{},"Ainda vale a pena, acompanhe a curva",[171,8806,8807,8810,8813],{},[189,8808,8809],{},"500k–2M tokens\u002Fdia",[189,8811,8812],{},"Avaliar modelo próprio",[189,8814,8815],{},"Ponto de equilíbrio aparece",[171,8817,8818,8821,8824],{},[189,8819,8820],{},"Acima de 2M tokens\u002Fdia",[189,8822,8823],{},"Modelo próprio",[189,8825,8826],{},"Economia mensal significativa",[171,8828,8829,8832,8834],{},[189,8830,8831],{},"Dados sensíveis\u002Fregulados",[189,8833,8823],{},[189,8835,8836],{},"Compliance exige dado interno",[171,8838,8839,8842,8844],{},[189,8840,8841],{},"Protótipo \u002F POC",[189,8843,6416],{},[189,8845,8846],{},"Velocidade acima de custo",[86,8848,8849,8852],{},[89,8850,8851],{},"Regra prática",": comece com API. Só migre para modelo próprio quando a\nconta de API passar de R$ 8–12 mil\u002Fmês — esse é o ponto onde o custo de uma\ninstância com GPU costuma se pagar.",[12,8854,8856],{"id":8855},"custo-de-api-tokens-explicados-custo-api","Custo de API: tokens explicados {#custo-api}",[86,8858,8859,8860,8863],{},"O conceito central para entender o custo de API é o ",[89,8861,8862],{},"token",". Token é o\npedacinho de texto que o modelo processa — grosso modo, 1 token equivale a\n~0,75 palavra em inglês ou ~0,5 palavra em português (nossa língua usa mais\ntokens por palavra por causa dos acentos e conjugações).",[86,8865,8866,8867,4171],{},"Toda chamada de API cobra ",[89,8868,8869],{},"duas coisas",[528,8871,8872,8878],{},[20,8873,8874,8877],{},[89,8875,8876],{},"Input tokens",": o texto que você envia (pergunta + contexto + instrução)",[20,8879,8880,8883],{},[89,8881,8882],{},"Output tokens",": o texto que o modelo gera de volta",[86,8885,8886],{},"O output costuma custar de 3 a 5 vezes mais que o input. Por isso, mandar\npouco contexto e gerar respostas curtas economiza muito.",[283,8888,8890],{"id":8889},"tabela-de-preços-de-api-referência-2026","Tabela de preços de API (referência 2026)",[165,8892,8893,8912],{},[168,8894,8895],{},[171,8896,8897,8900,8903,8906,8909],{},[174,8898,8899],{},"Modelo",[174,8901,8902],{},"Input (por 1M tokens)",[174,8904,8905],{},"Output (por 1M tokens)",[174,8907,8908],{},"Contexto",[174,8910,8911],{},"Uso típico",[184,8913,8914,8931,8947,8964,8980,8997],{},[171,8915,8916,8919,8922,8925,8928],{},[189,8917,8918],{},"GPT-4o-mini",[189,8920,8921],{},"~$0,15",[189,8923,8924],{},"~$0,60",[189,8926,8927],{},"128k",[189,8929,8930],{},"Atendimento, classificação",[171,8932,8933,8936,8939,8942,8944],{},[189,8934,8935],{},"GPT-4o",[189,8937,8938],{},"~$2,50",[189,8940,8941],{},"~$10,00",[189,8943,8927],{},[189,8945,8946],{},"Tarefas complexas, raciocínio",[171,8948,8949,8952,8955,8958,8961],{},[189,8950,8951],{},"Claude 3.5 Haiku",[189,8953,8954],{},"~$0,80",[189,8956,8957],{},"~$4,00",[189,8959,8960],{},"200k",[189,8962,8963],{},"Rápido e barato, bom em PT-BR",[171,8965,8966,8969,8972,8975,8977],{},[189,8967,8968],{},"Claude 3.5 Sonnet",[189,8970,8971],{},"~$3,00",[189,8973,8974],{},"~$15,00",[189,8976,8960],{},[189,8978,8979],{},"Melhor custo-benefício geral",[171,8981,8982,8985,8988,8991,8994],{},[189,8983,8984],{},"Gemini 1.5 Flash",[189,8986,8987],{},"~$0,075",[189,8989,8990],{},"~$0,30",[189,8992,8993],{},"1M",[189,8995,8996],{},"Volume alto, contexto gigante",[171,8998,8999,9002,9005,9008,9011],{},[189,9000,9001],{},"Gemini 1.5 Pro",[189,9003,9004],{},"~$1,25",[189,9006,9007],{},"~$5,00",[189,9009,9010],{},"2M",[189,9012,9013],{},"Contexto enorme",[710,9015,9016],{},[86,9017,9018],{},"Preços em USD, sujeitos a mudança. Consulte a página oficial do provedor\nantes de fechar orçamento.",[283,9020,9022],{"id":9021},"exemplo-de-cálculo-real","Exemplo de cálculo real",[86,9024,9025,9026,9029],{},"Imagine um assistente de atendimento que recebe ",[89,9027,9028],{},"1.000 perguntas por dia",".\nCada pergunta envia, em média:",[528,9031,9032,9035,9038,9041,9046],{},[20,9033,9034],{},"Prompt do sistema: 300 tokens",[20,9036,9037],{},"Contexto recuperado (RAG): 800 tokens",[20,9039,9040],{},"Pergunta do usuário: 50 tokens",[20,9042,9043],{},[89,9044,9045],{},"Total input: ~1.150 tokens",[20,9047,9048],{},[89,9049,9050],{},"Resposta gerada: ~300 tokens",[86,9052,9053],{},"Custo por query com GPT-4o-mini:",[528,9055,9056,9059,9062],{},[20,9057,9058],{},"Input: 1.150 × $0,15\u002F1M = $0,00017",[20,9060,9061],{},"Output: 300 × $0,60\u002F1M = $0,00018",[20,9063,9064],{},[89,9065,9066],{},"Total por query: ~$0,00035",[86,9068,9069],{},"Por mês (30 dias × 1.000):",[528,9071,9072,9075,9078],{},[20,9073,9074],{},"30.000.000 tokens input → $4,50",[20,9076,9077],{},"9.000.000 tokens output → $5,40",[20,9079,9080],{},[89,9081,9082],{},"Total: ~$10 USD\u002Fmês (≈ R$ 55)",[86,9084,9085,9086,522],{},"Esse é o ponto que costuma surpreender: para volume pequeno\u002Fmédio, o custo\nde API é quase irrelevante. O que pesa no orçamento é o ",[89,9087,9088],{},"desenvolvimento",[283,9090,9092],{"id":9091},"código-medir-tokens-antes-de-pagar","Código: medir tokens antes de pagar",[506,9094,9096],{"className":3439,"code":9095,"language":3441,"meta":515,"style":515},"import tiktoken\n\n# tiktoken é a biblioteca oficial da OpenAI para contar tokens\n# Instale com: pip install tiktoken\n\nenc = tiktoken.encoding_for_model(\"gpt-4o-mini\")\n\ndef contar_tokens(texto: str) -> int:\n    \"\"\"Retorna quantos tokens o texto vai consumir na API.\"\"\"\n    return len(enc.encode(texto))\n\n# Antes de chamar a API, estime o custo\ndef estimar_custo(input_tokens: int, output_tokens: int,\n                  preco_in: float = 0.15, preco_out: float = 0.60) -> float:\n    \"\"\"Estima custo em USD. Preços por 1M tokens.\"\"\"\n    return (input_tokens \u002F 1_000_000 * preco_in) + \\\n           (output_tokens \u002F 1_000_000 * preco_out)\n\n# Exemplo\nprompt = \"Resuma o contrato abaixo em 5 bullet points: ...\"\nprint(f\"Tokens do prompt: {contar_tokens(prompt)}\")\nprint(f\"Custo estimado: ${estimar_custo(contar_tokens(prompt), 300):.5f}\")\n",[513,9097,9098,9103,9107,9112,9117,9121,9126,9130,9135,9140,9145,9149,9154,9159,9164,9169,9174,9179,9183,9188,9193,9198],{"__ignoreMap":515},[1376,9099,9100],{"class":1378,"line":1379},[1376,9101,9102],{},"import tiktoken\n",[1376,9104,9105],{"class":1378,"line":984},[1376,9106,1398],{"emptyLinePlaceholder":898},[1376,9108,9109],{"class":1378,"line":981},[1376,9110,9111],{},"# tiktoken é a biblioteca oficial da OpenAI para contar tokens\n",[1376,9113,9114],{"class":1378,"line":1395},[1376,9115,9116],{},"# Instale com: pip install tiktoken\n",[1376,9118,9119],{"class":1378,"line":1401},[1376,9120,1398],{"emptyLinePlaceholder":898},[1376,9122,9123],{"class":1378,"line":1407},[1376,9124,9125],{},"enc = tiktoken.encoding_for_model(\"gpt-4o-mini\")\n",[1376,9127,9128],{"class":1378,"line":1413},[1376,9129,1398],{"emptyLinePlaceholder":898},[1376,9131,9132],{"class":1378,"line":1419},[1376,9133,9134],{},"def contar_tokens(texto: str) -> int:\n",[1376,9136,9137],{"class":1378,"line":1424},[1376,9138,9139],{},"    \"\"\"Retorna quantos tokens o texto vai consumir na API.\"\"\"\n",[1376,9141,9142],{"class":1378,"line":1430},[1376,9143,9144],{},"    return len(enc.encode(texto))\n",[1376,9146,9147],{"class":1378,"line":1436},[1376,9148,1398],{"emptyLinePlaceholder":898},[1376,9150,9151],{"class":1378,"line":1442},[1376,9152,9153],{},"# Antes de chamar a API, estime o custo\n",[1376,9155,9156],{"class":1378,"line":1447},[1376,9157,9158],{},"def estimar_custo(input_tokens: int, output_tokens: int,\n",[1376,9160,9161],{"class":1378,"line":1453},[1376,9162,9163],{},"                  preco_in: float = 0.15, preco_out: float = 0.60) -> float:\n",[1376,9165,9166],{"class":1378,"line":1459},[1376,9167,9168],{},"    \"\"\"Estima custo em USD. Preços por 1M tokens.\"\"\"\n",[1376,9170,9171],{"class":1378,"line":2200},[1376,9172,9173],{},"    return (input_tokens \u002F 1_000_000 * preco_in) + \\\n",[1376,9175,9176],{"class":1378,"line":2208},[1376,9177,9178],{},"           (output_tokens \u002F 1_000_000 * preco_out)\n",[1376,9180,9181],{"class":1378,"line":2219},[1376,9182,1398],{"emptyLinePlaceholder":898},[1376,9184,9185],{"class":1378,"line":2227},[1376,9186,9187],{},"# Exemplo\n",[1376,9189,9190],{"class":1378,"line":2241},[1376,9191,9192],{},"prompt = \"Resuma o contrato abaixo em 5 bullet points: ...\"\n",[1376,9194,9195],{"class":1378,"line":2253},[1376,9196,9197],{},"print(f\"Tokens do prompt: {contar_tokens(prompt)}\")\n",[1376,9199,9200],{"class":1378,"line":2283},[1376,9201,9202],{},"print(f\"Custo estimado: ${estimar_custo(contar_tokens(prompt), 300):.5f}\")\n",[86,9204,9205],{},"Comentários explicam cada passo: primeiro contamos tokens, depois\nmultiplicamos pelo preço por milhão. Rodar isso antes de mandar volume para\nprodução evita surpresas no fim do mês.",[12,9207,9209],{"id":9208},"infraestrutura-o-que-você-precisa-hospedar-infraestrutura","Infraestrutura: o que você precisa hospedar {#infraestrutura}",[86,9211,9212,9213,9216],{},"Mesmo no caminho da API (sem hospedar modelo), você precisa de\ninfraestrutura para a ",[89,9214,9215],{},"aplicação"," que orquestra tudo. No caminho do modelo\npróprio, a infraestrutura fica bem mais cara por causa de GPU.",[283,9218,9220],{"id":9219},"caminho-api-infra-mínima","Caminho API — infra mínima",[165,9222,9223,9235],{},[168,9224,9225],{},[171,9226,9227,9229,9232],{},[174,9228,7905],{},[174,9230,9231],{},"O que é",[174,9233,9234],{},"Custo\u002Fmês",[184,9236,9237,9248,9259,9270,9281],{},[171,9238,9239,9242,9245],{},[189,9240,9241],{},"API backend (FastAPI)",[189,9243,9244],{},"Recebe chamadas, chama a API do LLM",[189,9246,9247],{},"$5–20",[171,9249,9250,9253,9256],{},[189,9251,9252],{},"Banco vetorial (pgvector)",[189,9254,9255],{},"Guarda embeddings para RAG",[189,9257,9258],{},"$10–30",[171,9260,9261,9264,9267],{},[189,9262,9263],{},"Cache (Redis)",[189,9265,9266],{},"Evita chamadas repetidas à API",[189,9268,9269],{},"$5–15",[171,9271,9272,9275,9278],{},[189,9273,9274],{},"Monitoramento",[189,9276,9277],{},"Latência, erros, custo por query",[189,9279,9280],{},"$0–20",[171,9282,9283,9287,9289],{},[189,9284,9285],{},[89,9286,7964],{},[189,9288],{},[189,9290,9291],{},[89,9292,9293],{},"$20–85\u002Fmês",[86,9295,9296],{},"Serviços como Railway, Fly.io, Render ou AWS ECS Fargate cobram nessa faixa\npara uma aplicação pequena. Se você já tem um PostgreSQL, o pgvector é uma\nextensão gratuita — então o banco vetorial pode custar zero adicional.",[283,9298,9300],{"id":9299},"caminho-modelo-próprio-infra-com-gpu","Caminho modelo próprio — infra com GPU",[86,9302,9303],{},"Aqui o custo muda de figura. Um modelo como Llama 3.1 8B roda em uma GPU\nT4 (16GB), enquanto um modelo de 70B precisa de uma A100 (80GB) — e o preço\npula de ~$0,50\u002Fhora para ~$4\u002Fhora.",[165,9305,9306,9322],{},[168,9307,9308],{},[171,9309,9310,9313,9316,9319],{},[174,9311,9312],{},"Configuração",[174,9314,9315],{},"Hardware",[174,9317,9318],{},"Custo\u002Fhora (spot)",[174,9320,9321],{},"Custo\u002Fmês (24×30)",[184,9323,9324,9337,9350,9364],{},[171,9325,9326,9329,9332,9334],{},[189,9327,9328],{},"Llama 3.1 8B",[189,9330,9331],{},"1× T4 16GB",[189,9333,8990],{},[189,9335,9336],{},"~$216",[171,9338,9339,9341,9344,9347],{},[189,9340,9328],{},[189,9342,9343],{},"1× A10G 24GB",[189,9345,9346],{},"~$0,75",[189,9348,9349],{},"~$540",[171,9351,9352,9355,9358,9361],{},[189,9353,9354],{},"Llama 3.1 70B",[189,9356,9357],{},"2× A100 80GB",[189,9359,9360],{},"~$8,00",[189,9362,9363],{},"~$5.760",[171,9365,9366,9369,9372,9374],{},[189,9367,9368],{},"Mixtral 8x7B",[189,9370,9371],{},"1× A100 80GB",[189,9373,8957],{},[189,9375,9376],{},"~$2.880",[86,9378,9379],{},"GPU spot (preço mais barato, máquina pode ser interrompida) é viável para\nambientes de dev\u002Fteste. Para produção, use GPU sob demanda ou reservada —\nmais cara, mas estável.",[86,9381,9382,9385,9386,9389],{},[89,9383,9384],{},"Dica de ouro",": para volume médio, use ",[89,9387,9388],{},"inferência serverless"," (serviços\ncomo Together AI, Fireworks, Groq) que cobram por token mas rodam modelos\nopen source. Você pega o melhor dos dois mundos: preço de API, sem\ndependência dos grandes provedores, e modelos que você escolhe.",[12,9391,9393],{"id":9392},"desenvolvimento-gente-e-tempo-desenvolvimento","Desenvolvimento: gente e tempo {#desenvolvimento}",[86,9395,9396,9397,9400],{},"Aqui está o maior custo de um projeto de IA pequeno e médio — e o que mais\nsubestimado. Não é o modelo, não é a infra: é o ",[89,9398,9399],{},"tempo de gente\nespecializada"," para construir a solução.",[86,9402,9403],{},"Um projeto típico de assistente com RAG (o caso mais comum) tem estas fases:",[165,9405,9406,9421],{},[168,9407,9408],{},[171,9409,9410,9412,9415,9418],{},[174,9411,4648],{},[174,9413,9414],{},"Duração",[174,9416,9417],{},"Quem",[174,9419,9420],{},"Custo (freela BR)",[184,9422,9423,9437,9451,9465,9477,9491],{},[171,9424,9425,9428,9431,9434],{},[189,9426,9427],{},"Discovery \u002F levantamento",[189,9429,9430],{},"1–2 semanas",[189,9432,9433],{},"Tech lead + PM",[189,9435,9436],{},"R$ 4–10k",[171,9438,9439,9442,9445,9448],{},[189,9440,9441],{},"Protótipo (POC)",[189,9443,9444],{},"2–3 semanas",[189,9446,9447],{},"1 dev IA",[189,9449,9450],{},"R$ 6–15k",[171,9452,9453,9456,9459,9462],{},[189,9454,9455],{},"Desenvolvimento completo",[189,9457,9458],{},"4–8 semanas",[189,9460,9461],{},"1–2 devs + tech lead",[189,9463,9464],{},"R$ 20–60k",[171,9466,9467,9470,9472,9475],{},[189,9468,9469],{},"QA e testes",[189,9471,9430],{},[189,9473,9474],{},"QA + dev",[189,9476,9436],{},[171,9478,9479,9482,9485,9488],{},[189,9480,9481],{},"Deploy + observabilidade",[189,9483,9484],{},"1 semana",[189,9486,9487],{},"DevOps + dev",[189,9489,9490],{},"R$ 5–12k",[171,9492,9493,9497,9502,9504],{},[189,9494,9495],{},[89,9496,7964],{},[189,9498,9499],{},[89,9500,9501],{},"10–16 semanas",[189,9503],{},[189,9505,9506],{},[89,9507,9508],{},"R$ 40–110k",[86,9510,9511],{},"Valores para desenvolvedores\u002Ffreelancers no Brasil em 2026. Consultoria\nespecializada em IA (não dev genérico) cobra entre R$ 80–200\u002Fhora. Uma\nboutique como a Inicialize Tec costuma trabalhar com proposta fechada por\nprojeto, nessa faixa.",[283,9513,9515],{"id":9514},"o-que-influencia-o-custo-de-desenvolvimento","O que influencia o custo de desenvolvimento",[528,9517,9518,9524,9530,9536],{},[20,9519,9520,9523],{},[89,9521,9522],{},"Complexidade do RAG",": dados limpos e bem estruturados = 2 semanas. Dados\nespalhados em PDFs, e-mails e planilhas = 6 semanas.",[20,9525,9526,9529],{},[89,9527,9528],{},"Integrações",": conectar com o sistema atual (ERP, CRM, helpdesk) adiciona\n1–3 semanas por integração.",[20,9531,9532,9535],{},[89,9533,9534],{},"Qualidade exigida",": um protótipo \"funciona mais ou menos\" custa 1\u002F3 de\numa solução com 95% de acerto validado.",[20,9537,9538,9541],{},[89,9539,9540],{},"Interface",": um endpoint de API é rápido. Um chat com UI rica,\nhistórico, feedback do usuário, dobra o esforço.",[283,9543,9545],{"id":9544},"custos-de-gente-além-do-código","Custos de gente além do código",[86,9547,9548],{},"Não esqueça de:",[528,9550,9551,9557,9563],{},[20,9552,9553,9556],{},[89,9554,9555],{},"Curadoria de dados",": alguém que entende do negócio precisa revisar e\norganizar a base de conhecimento (R$ 3–8k para um projeto médio)",[20,9558,9559,9562],{},[89,9560,9561],{},"Testes com usuários reais",": tempo da equipe de atendimento validando\nrespostas antes de abrir para clientes",[20,9564,9565,9568],{},[89,9566,9567],{},"Treinamento da equipe",": quem vai usar precisa aprender (R$ 2–5k)",[12,9570,9572],{"id":9571},"manutenção-e-custos-recorrentes-manutencao","Manutenção e custos recorrentes {#manutencao}",[86,9574,9575,9576,9579],{},"IA não é \"faça uma vez e esqueça\". Modelos mudam, dados envelhecem, prompts\nquebram. Separe entre ",[89,9577,9578],{},"15% e 25% do custo de desenvolvimento por ano"," como\nmanutenção.",[283,9581,9583],{"id":9582},"o-que-entra-na-manutenção","O que entra na manutenção",[165,9585,9586,9597],{},[168,9587,9588],{},[171,9589,9590,9592,9595],{},[174,9591,596],{},[174,9593,9594],{},"Frequência",[174,9596,9234],{},[184,9598,9599,9610,9621,9632,9643,9653],{},[171,9600,9601,9604,9607],{},[189,9602,9603],{},"Ajuste de prompt (drift de qualidade)",[189,9605,9606],{},"Mensal",[189,9608,9609],{},"R$ 1–3k",[171,9611,9612,9615,9618],{},[189,9613,9614],{},"Atualização da base de conhecimento",[189,9616,9617],{},"Semanal\u002Fmensal",[189,9619,9620],{},"R$ 1–4k",[171,9622,9623,9626,9629],{},[189,9624,9625],{},"Monitoramento e alertas",[189,9627,9628],{},"Contínuo",[189,9630,9631],{},"$20–100 (ferramentas)",[171,9633,9634,9637,9640],{},[189,9635,9636],{},"Re-treino de embeddings (quando dados mudam muito)",[189,9638,9639],{},"Trimestral",[189,9641,9642],{},"$10–80 (API)",[171,9644,9645,9648,9650],{},[189,9646,9647],{},"Bug fix e melhorias",[189,9649,9628],{},[189,9651,9652],{},"R$ 2–6k",[171,9654,9655,9658,9661],{},[189,9656,9657],{},"Atualização de versão do modelo",[189,9659,9660],{},"Semestral",[189,9662,9663],{},"R$ 3–8k (migração)",[283,9665,9667],{"id":9666},"por-que-a-qualidade-decai-com-o-tempo","Por que a qualidade decai com o tempo",[86,9669,9670],{},"Dois motivos principais:",[17,9672,9673,9679],{},[20,9674,9675,9678],{},[89,9676,9677],{},"Atualização dos modelos",": provedores mudam o GPT-4o, e o prompt que\nfuncionava para de funcionar. Precisa revalidar.",[20,9680,9681,9684],{},[89,9682,9683],{},"Drift dos dados",": sua base de conhecimento muda (novos produtos, novas\npolíticas), e o RAG passa a recuperar contexto desatualizado.",[86,9686,9687,9688,9691],{},"Por isso, ",[89,9689,9690],{},"monitoramento não é opcional",". Se você não mede a qualidade das\nrespostas ao longo do tempo, descobre o problema só quando o cliente reclama.",[12,9693,9695],{"id":9694},"tabela-consolidada-de-custos-tabela-consolidada","Tabela consolidada de custos {#tabela-consolidada}",[86,9697,9698,9699,9702],{},"Para um assistente de atendimento com RAG, ",[89,9700,9701],{},"1.000 queries\u002Fdia",", usando API:",[165,9704,9705,9717],{},[168,9706,9707],{},[171,9708,9709,9712,9714],{},[174,9710,9711],{},"Bloco",[174,9713,3306],{},[174,9715,9716],{},"Custo mensal recorrente",[184,9718,9719,9729,9740,9750,9759,9768,9777],{},[171,9720,9721,9724,9726],{},[189,9722,9723],{},"API (GPT-4o-mini)",[189,9725,7943],{},[189,9727,9728],{},"~$10 USD (R$ 55)",[171,9730,9731,9734,9737],{},[189,9732,9733],{},"Infraestrutura",[189,9735,9736],{},"$0–50 (setup)",[189,9738,9739],{},"~$30–85 USD (R$ 165–470)",[171,9741,9742,9744,9747],{},[189,9743,3991],{},[189,9745,9746],{},"R$ 40–110k (único)",[189,9748,9749],{},"—",[171,9751,9752,9754,9757],{},[189,9753,9555],{},[189,9755,9756],{},"R$ 3–8k (único)",[189,9758,9749],{},[171,9760,9761,9763,9765],{},[189,9762,3281],{},[189,9764,9749],{},[189,9766,9767],{},"R$ 3–15k",[171,9769,9770,9772,9774],{},[189,9771,9274],{},[189,9773,9749],{},[189,9775,9776],{},"$20–100 USD (R$ 110–550)",[171,9778,9779,9784,9789],{},[189,9780,9781],{},[89,9782,9783],{},"Total 1º ano",[189,9785,9786],{},[89,9787,9788],{},"R$ 45–120k",[189,9790,9791],{},[89,9792,9793],{},"R$ 4–16k\u002Fmês",[86,9795,9796],{},"No primeiro ano, o desenvolvimento domina. Do segundo ano em diante, só\nrodam os custos recorrentes — e aí o ROI aparece com força.",[12,9798,9800],{"id":9799},"calculando-o-roi-roi","Calculando o ROI {#roi}",[86,9802,9803,9804,522],{},"A matemática do ROI em IA é simples quando você para de pensar em \"custo de\nIA\" e passa a pensar em ",[89,9805,9806],{},"custo do processo atual",[283,9808,9810],{"id":9809},"exemplo-atendimento-ao-cliente","Exemplo: atendimento ao cliente",[86,9812,9813,9814,9817],{},"Uma empresa tem 3 atendentes, cada um custa R$ 3.500\u002Fmês (salário + encargos)\n= ",[89,9815,9816],{},"R$ 10.500\u002Fmês",". Eles respondem 1.000 tickets\u002Fmês, sendo 60% tier 1\n(perguntas repetitivas).",[86,9819,9820],{},"Com um assistente de IA cobrindo os tickets tier 1:",[528,9822,9823,9826,9829],{},[20,9824,9825],{},"600 tickets\u002Fmês resolvidos pela IA (60% do volume)",[20,9827,9828],{},"Cada atendente agora faz ~130 tickets\u002Fmês (em vez de 333)",[20,9830,9831,9834],{},[89,9832,9833],{},"Redução de 2 atendentes",": economia de R$ 7.000\u002Fmês",[86,9836,9837,9838,9841],{},"Custo da solução de IA: R$ 4–16k\u002Fmês (incluindo manutenção). Mesmo no pior\ncaso, ",[89,9839,9840],{},"payback em 2–3 meses",". No melhor caso, economia líquida desde o\nmês 1.",[283,9843,9845],{"id":9844},"fórmula-prática","Fórmula prática",[506,9847,9850],{"className":9848,"code":9849,"language":511},[509],"ROI = (Economia mensal - Custo mensal IA) \u002F Custo mensal IA × 100\n\nPayback (meses) = Custo de desenvolvimento \u002F (Economia mensal - Custo mensal IA)\n",[513,9851,9849],{"__ignoreMap":515},[86,9853,9854,9857],{},[89,9855,9856],{},"Economia mensal"," pode ser:",[528,9859,9860,9863,9866,9869],{},[20,9861,9862],{},"Redução de headcount",[20,9864,9865],{},"Aumento de produtividade (mais casos resolvidos com o mesmo time)",[20,9867,9868],{},"Redução de tempo de resposta (impacto em satisfação e retenção)",[20,9870,9871],{},"Novas receitas (IA que gera leads, por exemplo)",[12,9873,9875],{"id":9874},"custos-escondidos-que-ninguém-conta-escondidos","Custos escondidos que ninguém conta {#escondidos}",[17,9877,9878,9884,9894,9900,9906,9912],{},[20,9879,9880,9883],{},[89,9881,9882],{},"Custo de token inesperado em loops",": um agente que chama o LLM em\nloop pode consumir 50× o orçamento previsto em poucas horas. Sempre\ncoloque limite (max iterations) e alerta de custo.",[20,9885,9886,9889,9890,9893],{},[89,9887,9888],{},"Reprocessamento de embeddings",": cada vez que você muda o modelo de\nembedding (ex: saiu uma versão melhor), ",[89,9891,9892],{},"todos"," os embeddings precisam\nser recalculados. Para uma base de 100k documentos, isso custa $5–20 em\nAPI, mas dá trabalho.",[20,9895,9896,9899],{},[89,9897,9898],{},"Custo de saída de dados da API",": alguns provedores cobram por\ntransferência de dados. Para volumes altos de contexto (2M tokens de\ninput por query), a banda também pesa.",[20,9901,9902,9905],{},[89,9903,9904],{},"Custo de compliance",": LGPD exige Auditoria de Dados Pessoais (DPA),\ncontrato com o provedor e, às vezes, consultoria jurídica. R$ 5–20k para um\nprojeto médio.",[20,9907,9908,9911],{},[89,9909,9910],{},"Custo de vendor lock-in",": se você constrói tudo acoplado à API da\nOpenAI e ela dobra o preço, você troca de provedor — mas isso custa\n1–3 semanas de refatoração. Sempre abstraia o provedor.",[20,9913,9914,9917],{},[89,9915,9916],{},"Custo de erro de IA",": uma resposta errada que gera processo ou perda\nde cliente não entra na planilha, mas é real. Para casos sensíveis\n(jurídico, médico, financeiro), sempre tenha humano na revisão.",[12,9919,9921],{"id":9920},"como-reduzir-custos-sem-perder-qualidade-reduzir","Como reduzir custos sem perder qualidade {#reduzir}",[528,9923,9924,9930,9936,9942,9948,9954],{},[20,9925,9926,9929],{},[89,9927,9928],{},"Use modelos pequenos quando dá",": GPT-4o-mini resolve 80% dos casos que\nGPT-4o resolve, por 1\u002F15 do preço. Use o grande só quando o pequeno não\nbasta.",[20,9931,9932,9935],{},[89,9933,9934],{},"Cache de respostas",": perguntas repetidas (FAQ) não precisam ir ao LLM\ntoda vez. Cache em Redis por 24h economiza 30–60% em atendimento.",[20,9937,9938,9941],{},[89,9939,9940],{},"Roteamento inteligente",": um classificador barato decide se a pergunta\nvai para o modelo pequeno ou o grande. Economia de 50%+ mantendo qualidade.",[20,9943,9944,9947],{},[89,9945,9946],{},"Comprima o contexto",": em vez de mandar 10 documentos inteiros, mande\nsó os 3 mais relevantes (top-k menor). Reduz input tokens drasticamente.",[20,9949,9950,9953],{},[89,9951,9952],{},"Batch API",": alguns provedores oferecem desconto de 50% para chamadas\nassíncronas em lote. Vale para processamentos noturnos.",[20,9955,9956,9959],{},[89,9957,9958],{},"Misture API + modelo próprio",": use API para volume baixo e switches para\nmodelo próprio nos picos. Estratégia híbrida maximiza economia.",[12,9961,820],{"id":819},[17,9963,9964,9970,9976,9982,9988,9994,10000,10006],{},[20,9965,9966,9969],{},[89,9967,9968],{},"Orçar só a API, esquecer desenvolvimento",": o erro mais comum. \"Ah, a\nIA custa R$ 50\u002Fmês\". Sim, mas construir custou R$ 80k. Separe CapEx\n(desenvolvimento) de OpEx (recorrente).",[20,9971,9972,9975],{},[89,9973,9974],{},"Começar pelo modelo mais caro",": GPT-4o em tudo. Na maioria dos casos,\no mini resolve. Comece barato, escale qualidade só onde precisa.",[20,9977,9978,9981],{},[89,9979,9980],{},"Não provisionar para pico",": orçar pela média mensal e quebrar no Black\nFriday. Custeie pela média do pior dia, não do dia comum.",[20,9983,9984,9987],{},[89,9985,9986],{},"Ignorar manutenção no orçamento",": \"já construímos, acabou\". No mês 4\na qualidade cai, ninguém sabe por quê, e a solução morre. Reserve 20%\ndo desenvolvimento para manutenção do ano.",[20,9989,9990,9993],{},[89,9991,9992],{},"Não medir custo por query",": sem essa métrica, você não sabe se está\ngastando demais. Implemente logging de custo desde o dia 1.",[20,9995,9996,9999],{},[89,9997,9998],{},"Confiar em estimativa de token \"no olho\"",": sempre conte tokens com\nbiblioteca (tiktoken). Em português, o número de tokens surpreende —\nsempre mais alto que se imagina.",[20,10001,10002,10005],{},[89,10003,10004],{},"Subestimar curadoria de dados",": \"vamos jogar todos os PDFs no RAG\".\nDados sujos = respostas ruins = projeto fracassa. Invista em limpar\nantes.",[20,10007,10008,10011],{},[89,10009,10010],{},"Não ter plano B quando a API cai",": provedores caem. Sem fallback\n(resposta degradada, fila, retry), sua operação para. Sempre planeje\nresiliência.",[968,10013],{},[86,10015,10016,10019,10020,10022],{},[89,10017,10018],{},"Quer colocar IA na sua empresa sem surpresas no orçamento?"," A Inicialize\nTec faz o diagnóstico, estima custos reais para o seu caso e implementa do\nprotótipo ao deploy. ",[23,10021,6840],{"href":978}," e montamos uma proposta com\nnúmeros que fecham.",[2883,10024,4828],{},{"title":515,"searchDepth":981,"depth":981,"links":10026},[10027,10028,10029,10034,10039,10043,10047,10051,10052,10056,10057,10058],{"id":14,"depth":984,"text":15},{"id":8682,"depth":984,"text":8683},{"id":8715,"depth":984,"text":8716,"children":10030},[10031,10032,10033],{"id":8722,"depth":981,"text":8723},{"id":8746,"depth":981,"text":8747},{"id":8766,"depth":981,"text":8767},{"id":8855,"depth":984,"text":8856,"children":10035},[10036,10037,10038],{"id":8889,"depth":981,"text":8890},{"id":9021,"depth":981,"text":9022},{"id":9091,"depth":981,"text":9092},{"id":9208,"depth":984,"text":9209,"children":10040},[10041,10042],{"id":9219,"depth":981,"text":9220},{"id":9299,"depth":981,"text":9300},{"id":9392,"depth":984,"text":9393,"children":10044},[10045,10046],{"id":9514,"depth":981,"text":9515},{"id":9544,"depth":981,"text":9545},{"id":9571,"depth":984,"text":9572,"children":10048},[10049,10050],{"id":9582,"depth":981,"text":9583},{"id":9666,"depth":981,"text":9667},{"id":9694,"depth":984,"text":9695},{"id":9799,"depth":984,"text":9800,"children":10053},[10054,10055],{"id":9809,"depth":981,"text":9810},{"id":9844,"depth":981,"text":9845},{"id":9874,"depth":984,"text":9875},{"id":9920,"depth":984,"text":9921},{"id":819,"depth":984,"text":820},"Quanto custa implementar IA na sua empresa? Breakdown de custos de API, infraestrutura, desenvolvimento e manutenção — com números reais do Brasil.",[10061,10062,10063,10064,10065],"custo IA","orçamento IA","ROI inteligência artificial","implementação IA","GPT API custo",{},"\u002Fia\u002Fcusto-de-implementar-inteligencia-artificial",{"title":8610,"description":10059},"custo-de-implementar-inteligencia-artificial","ia\u002Fcusto-de-implementar-inteligencia-artificial","k6t7R4v3xWw9vyO2gAqGvoYx3fV4jcz5uHtif60xbjE",{"id":10073,"title":10074,"author":7,"body":10075,"category":6869,"date":1028,"description":11435,"extension":1030,"keywords":11436,"meta":11440,"navigation":898,"path":11441,"seo":11442,"slug":11443,"stem":11444,"updated":1028,"__hash__":11445},"content\u002Fia\u002Flangchain-vs-llamaindex.md","LangChain vs LlamaIndex: qual framework de IA usar?",{"type":9,"value":10076,"toc":11416},[10077,10079,10137,10141,10153,10173,10179,10190,10195,10199,10206,10210,10260,10264,10271,10343,10350,10354,10368,10371,10375,10418,10421,10428,10504,10507,10509,10640,10644,10676,10682,10687,10691,10723,10728,10732,11023,11026,11030,11243,11246,11250,11264,11323,11332,11339,11341,11403,11405,11414],[12,10078,15],{"id":14},[17,10080,10081,10087,10093,10099,10103,10109,10115,10121,10127,10133],{},[20,10082,10083],{},[23,10084,10086],{"href":10085},"#o-que-sao","O que são frameworks de LLM",[20,10088,10089],{},[23,10090,10092],{"href":10091},"#langchain","LangChain: o canivete suíço",[20,10094,10095],{},[23,10096,10098],{"href":10097},"#llamaindex","LlamaIndex: o especialista em dados",[20,10100,10101],{},[23,10102,2957],{"href":2956},[20,10104,10105],{},[23,10106,10108],{"href":10107},"#quando-langchain","Quando usar LangChain",[20,10110,10111],{},[23,10112,10114],{"href":10113},"#quando-llamaindex","Quando usar LlamaIndex",[20,10116,10117],{},[23,10118,10120],{"href":10119},"#codigo-langchain","Exemplo de código: RAG com LangChain",[20,10122,10123],{},[23,10124,10126],{"href":10125},"#codigo-llamaindex","Exemplo de código: RAG com LlamaIndex",[20,10128,10129],{},[23,10130,10132],{"href":10131},"#agentes","E os agentes?",[20,10134,10135],{},[23,10136,74],{"href":73},[12,10138,10140],{"id":10139},"o-que-são-frameworks-de-llm-o-que-sao","O que são frameworks de LLM {#o-que-sao}",[86,10142,10143,10144,4958,10146,10148,10149,10152],{},"Antes de comparar, vamos ao conceito básico. Um ",[89,10145,4965],{},[501,10147,4968],{},") é um modelo de IA treinado para entender e gerar texto — o ChatGPT é\no exemplo mais famoso. Sozinho, um LLM faz uma coisa: recebe texto e devolve\ntexto. Para virar um produto (um chatbot, um assistente que usa seus dados,\num agente que executa tarefas), você precisa de ",[89,10150,10151],{},"código ao redor"," que:",[528,10154,10155,10158,10161,10164,10167,10170],{},[20,10156,10157],{},"Conecta ao modelo via API",[20,10159,10160],{},"Formata a pergunta do usuário (prompt)",[20,10162,10163],{},"Busca dados relevantes e injeta no prompt",[20,10165,10166],{},"Mantém o histórico da conversa",[20,10168,10169],{},"Decide quando chamar ferramentas externas",[20,10171,10172],{},"Trata erros e limita custos",[86,10174,10175,10176,522],{},"Esse \"código ao redor\" é repetitivo. Da mesma forma que você não escreve um\nservidor HTTP do zero a cada projeto (usa Express, FastAPI, Django), você\nnão escreve a orquestração de LLM do zero: usa um ",[89,10177,10178],{},"framework",[86,10180,10181,3022,10183,10185,10186,10189],{},[89,10182,6960],{},[89,10184,7469],{}," são os dois frameworks Python mais populares\npara isso. Ambos são open source, ambos rodam em qualquer LLM (OpenAI,\nAnthropic, open source hospedado por você), ambos têm versão em TypeScript.\nMas têm ",[89,10187,10188],{},"filosofias diferentes"," — e entender essa diferença economiza\nsemanas de dor de cabeça.",[86,10191,10192,10194],{},[89,10193,3011],{},": LangChain é um canivete suíço — tem ferramenta para tudo, mas\ncada uma é \"boa o suficiente\". LlamaIndex é uma caixa de chaves de boca\nprecisa — menos peças, mas cada uma é especializada e bem ajustada.",[12,10196,10198],{"id":10197},"langchain-o-canivete-suíço-langchain","LangChain: o canivete suíço {#langchain}",[86,10200,10201,10202,10205],{},"LangChain nasceu em 2022 com uma proposta ampla: ",[89,10203,10204],{},"ser a camada de\nabstração para qualquer aplicação com LLM",". Ele não assume que você vai\nfazer RAG (Recall-Augmented Generation, técnica que explicamos adiante). Ele\nassume que você pode fazer qualquer coisa: RAG, agentes, chains de\nraciocínio, sumarização, extração, chat com memória, etc.",[283,10207,10209],{"id":10208},"o-que-o-langchain-oferece","O que o LangChain oferece",[528,10211,10212,10218,10224,10230,10236,10242,10248,10254],{},[20,10213,10214,10217],{},[89,10215,10216],{},"Chains",": sequências de chamadas encadeadas (prompt → modelo → parser)",[20,10219,10220,10223],{},[89,10221,10222],{},"Agents",": LLMs que decidem sozinhos quais ferramentas usar",[20,10225,10226,10229],{},[89,10227,10228],{},"Memory",": gerenciar histórico de conversa",[20,10231,10232,10235],{},[89,10233,10234],{},"Retrievers",": integração com vector stores (pgvector, Pinecone, Chroma)",[20,10237,10238,10241],{},[89,10239,10240],{},"Document loaders",": ler PDF, HTML, Notion, GitHub, etc.",[20,10243,10244,10247],{},[89,10245,10246],{},"Callbacks",": hooks para logging, streaming, monitoramento",[20,10249,10250,10253],{},[89,10251,10252],{},"LangServe",": publicar chains como API REST",[20,10255,10256,10259],{},[89,10257,10258],{},"LangSmith",": plataforma de observabilidade (paga)",[283,10261,10263],{"id":10262},"estilo-de-uso","Estilo de uso",[86,10265,10266,10267,10270],{},"LangChain é ",[89,10268,10269],{},"componível e flexível",". Você monta peças pequenas e encaixa:",[506,10272,10274],{"className":3439,"code":10273,"language":3441,"meta":515,"style":515},"from langchain_openai import ChatOpenAI\nfrom langchain_core.prompts import ChatPromptTemplate\nfrom langchain_core.output_parsers import StrOutputParser\n\n# Cada peça é um objeto que pode ser encadeado com o operador |\nprompt = ChatPromptTemplate.from_template(\"Resuma: {texto}\")\nmodelo = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.2)\nparser = StrOutputParser()\n\n# O operador | encadeia: saída de um vira entrada do próximo\nchain = prompt | modelo | parser\n\n# Executa\nresumo = chain.invoke({\"texto\": \"Contrudo longo aqui...\"})\n",[513,10275,10276,10281,10286,10291,10295,10300,10305,10310,10315,10319,10324,10329,10333,10338],{"__ignoreMap":515},[1376,10277,10278],{"class":1378,"line":1379},[1376,10279,10280],{},"from langchain_openai import ChatOpenAI\n",[1376,10282,10283],{"class":1378,"line":984},[1376,10284,10285],{},"from langchain_core.prompts import ChatPromptTemplate\n",[1376,10287,10288],{"class":1378,"line":981},[1376,10289,10290],{},"from langchain_core.output_parsers import StrOutputParser\n",[1376,10292,10293],{"class":1378,"line":1395},[1376,10294,1398],{"emptyLinePlaceholder":898},[1376,10296,10297],{"class":1378,"line":1401},[1376,10298,10299],{},"# Cada peça é um objeto que pode ser encadeado com o operador |\n",[1376,10301,10302],{"class":1378,"line":1407},[1376,10303,10304],{},"prompt = ChatPromptTemplate.from_template(\"Resuma: {texto}\")\n",[1376,10306,10307],{"class":1378,"line":1413},[1376,10308,10309],{},"modelo = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.2)\n",[1376,10311,10312],{"class":1378,"line":1419},[1376,10313,10314],{},"parser = StrOutputParser()\n",[1376,10316,10317],{"class":1378,"line":1424},[1376,10318,1398],{"emptyLinePlaceholder":898},[1376,10320,10321],{"class":1378,"line":1430},[1376,10322,10323],{},"# O operador | encadeia: saída de um vira entrada do próximo\n",[1376,10325,10326],{"class":1378,"line":1436},[1376,10327,10328],{},"chain = prompt | modelo | parser\n",[1376,10330,10331],{"class":1378,"line":1442},[1376,10332,1398],{"emptyLinePlaceholder":898},[1376,10334,10335],{"class":1378,"line":1447},[1376,10336,10337],{},"# Executa\n",[1376,10339,10340],{"class":1378,"line":1453},[1376,10341,10342],{},"resumo = chain.invoke({\"texto\": \"Contrudo longo aqui...\"})\n",[86,10344,10345,10346,10349],{},"Comentários explicam: cada peça é um objeto; o operador ",[513,10347,10348],{},"|"," (pipe) liga a\nsaída de um na entrada do próximo, como uma linha de montagem. Isso é o\ncoração do LangChain — componibilidade.",[12,10351,10353],{"id":10352},"llamaindex-o-especialista-em-dados-llamaindex","LlamaIndex: o especialista em dados {#llamaindex}",[86,10355,10356,10357,10360,10361,10364,10365,10367],{},"LlamaIndex nasceu em 2022 com o nome ",[501,10358,10359],{},"GPT Index",", e o nome original já entrega\no foco: ",[89,10362,10363],{},"conectar LLMs aos seus dados",". Enquanto LangChain quer ser geral,\nLlamaIndex quer ser o melhor do mundo em ",[89,10366,4972],{}," (Retrieval-Augmented\nGeneration), a técnica onde o LLM responde com base nos seus documentos.",[86,10369,10370],{},"RAG, em uma frase: antes de o LLM responder, você busca no seu acervo os\ntrechos mais relevantes e os entrega como contexto. O LLM então responde \"olhando\"\npara os seus dados, em vez de depender só do que memorizou no treino.",[283,10372,10374],{"id":10373},"o-que-o-llamaindex-oferece","O que o LlamaIndex oferece",[528,10376,10377,10383,10389,10395,10401,10406,10412],{},[20,10378,10379,10382],{},[89,10380,10381],{},"Data connectors",": conectores especializados para 100+ fontes (Notion,\nSalesforce, GitHub, Slack, bancos SQL, etc.)",[20,10384,10385,10388],{},[89,10386,10387],{},"Index structures",": índices otimizados (vetorial, hierárquico, em árvore,\nem grafo de conhecimento)",[20,10390,10391,10394],{},[89,10392,10393],{},"Advanced retrieval",": estratégias sofisticadas (reranking, hybrid search,\nsub-questões, roteamento)",[20,10396,10397,10400],{},[89,10398,10399],{},"Response synthesis",": modos de geração de resposta (refine, compact,\ntree-summarize)",[20,10402,10403,10405],{},[89,10404,10222],{},": agentes com ferramentas de consulta (mais focado em dados)",[20,10407,10408,10411],{},[89,10409,10410],{},"LlamaCloud",": plataforma de ingestão e parsing (paga)",[20,10413,10414,10417],{},[89,10415,10416],{},"Workflows",": orquestração de fluxos multi-etapa (mais novo)",[283,10419,10263],{"id":10420},"estilo-de-uso-1",[86,10422,10423,10424,10427],{},"LlamaIndex é ",[89,10425,10426],{},"declarativo e alto nível",". Você descreve o que quer, ele\ncuida dos detalhes:",[506,10429,10431],{"className":3439,"code":10430,"language":3441,"meta":515,"style":515},"from llama_index.core import VectorStoreIndex, SimpleDirectoryReader\n\n# Carrega documentos de uma pasta\ndocumentos = SimpleDirectoryReader(\".\u002Fmeus_docs\").load_data()\n\n# Constrói o índice vetorial automaticamente\n# (gera embeddings, armazena, configura retriever)\nindice = VectorStoreIndex.from_documents(documentos)\n\n# Cria um \"query engine\" - a interface para fazer perguntas\nmotor = indice.as_query_engine(similarity_top_k=3)\n\n# Pergunta\nresposta = motor.query(\"Qual a política de reembolso?\")\nprint(resposta)\n",[513,10432,10433,10438,10442,10447,10452,10456,10461,10466,10471,10475,10480,10485,10489,10494,10499],{"__ignoreMap":515},[1376,10434,10435],{"class":1378,"line":1379},[1376,10436,10437],{},"from llama_index.core import VectorStoreIndex, SimpleDirectoryReader\n",[1376,10439,10440],{"class":1378,"line":984},[1376,10441,1398],{"emptyLinePlaceholder":898},[1376,10443,10444],{"class":1378,"line":981},[1376,10445,10446],{},"# Carrega documentos de uma pasta\n",[1376,10448,10449],{"class":1378,"line":1395},[1376,10450,10451],{},"documentos = SimpleDirectoryReader(\".\u002Fmeus_docs\").load_data()\n",[1376,10453,10454],{"class":1378,"line":1401},[1376,10455,1398],{"emptyLinePlaceholder":898},[1376,10457,10458],{"class":1378,"line":1407},[1376,10459,10460],{},"# Constrói o índice vetorial automaticamente\n",[1376,10462,10463],{"class":1378,"line":1413},[1376,10464,10465],{},"# (gera embeddings, armazena, configura retriever)\n",[1376,10467,10468],{"class":1378,"line":1419},[1376,10469,10470],{},"indice = VectorStoreIndex.from_documents(documentos)\n",[1376,10472,10473],{"class":1378,"line":1424},[1376,10474,1398],{"emptyLinePlaceholder":898},[1376,10476,10477],{"class":1378,"line":1430},[1376,10478,10479],{},"# Cria um \"query engine\" - a interface para fazer perguntas\n",[1376,10481,10482],{"class":1378,"line":1436},[1376,10483,10484],{},"motor = indice.as_query_engine(similarity_top_k=3)\n",[1376,10486,10487],{"class":1378,"line":1442},[1376,10488,1398],{"emptyLinePlaceholder":898},[1376,10490,10491],{"class":1378,"line":1447},[1376,10492,10493],{},"# Pergunta\n",[1376,10495,10496],{"class":1378,"line":1453},[1376,10497,10498],{},"resposta = motor.query(\"Qual a política de reembolso?\")\n",[1376,10500,10501],{"class":1378,"line":1459},[1376,10502,10503],{},"print(resposta)\n",[86,10505,10506],{},"Veja a diferença: em LlamaIndex, três linhas construíram um RAG completo. Em\nLangChain, você teria que escolher o vector store, configurar o retriever,\nmontar a chain de QA, definir o prompt manualmente. Mais controle — também\nmais trabalho.",[12,10508,3220],{"id":3219},[165,10510,10511,10521],{},[168,10512,10513],{},[171,10514,10515,10517,10519],{},[174,10516,3229],{},[174,10518,6960],{},[174,10520,7469],{},[184,10522,10523,10534,10544,10555,10566,10576,10587,10597,10608,10619,10630],{},[171,10524,10525,10528,10531],{},[189,10526,10527],{},"Foco principal",[189,10529,10530],{},"Aplicações gerais com LLM",[189,10532,10533],{},"RAG e dados",[171,10535,10536,10538,10541],{},[189,10537,3358],{},[189,10539,10540],{},"Íngreme (muitas abstrações)",[189,10542,10543],{},"Moderada (API mais direta)",[171,10545,10546,10549,10552],{},[189,10547,10548],{},"Flexibilidade",[189,10550,10551],{},"Muito alta",[189,10553,10554],{},"Alta em RAG, média fora disso",[171,10556,10557,10560,10563],{},[189,10558,10559],{},"Documentação",[189,10561,10562],{},"Volumosa, às vezes desatualizada",[189,10564,10565],{},"Mais enxuta e atual",[171,10567,10568,10570,10573],{},[189,10569,10222],{},[189,10571,10572],{},"Maduro, com LangGraph",[189,10574,10575],{},"Bom, mas mais focado em consulta",[171,10577,10578,10581,10584],{},[189,10579,10580],{},"Melhor RAG simples",[189,10582,10583],{},"OK",[189,10585,10586],{},"Excelente",[171,10588,10589,10592,10594],{},[189,10590,10591],{},"Melhor RAG avançado",[189,10593,10583],{},[189,10595,10596],{},"Excelente (reranking, hybrid)",[171,10598,10599,10602,10605],{},[189,10600,10601],{},"Comunidade",[189,10603,10604],{},"Maior",[189,10606,10607],{},"Grande, crescendo rápido",[171,10609,10610,10613,10616],{},[189,10611,10612],{},"Estabilidade de API",[189,10614,10615],{},"Mudou muito no passado",[189,10617,10618],{},"Mais estável recentemente",[171,10620,10621,10624,10627],{},[189,10622,10623],{},"TypeScript",[189,10625,10626],{},"Sim (separado)",[189,10628,10629],{},"Sim",[171,10631,10632,10634,10637],{},[189,10633,6438],{},[189,10635,10636],{},"LangSmith (pago)",[189,10638,10639],{},"LlamaCloud + integrações",[12,10641,10643],{"id":10642},"quando-usar-langchain-quando-langchain","Quando usar LangChain {#quando-langchain}",[528,10645,10646,10652,10658,10664,10670],{},[20,10647,10648,10651],{},[89,10649,10650],{},"Quando o problema não é RAG",": classificação de textos, extração\nestruturada, sumarização em pipeline, geração de código.",[20,10653,10654,10657],{},[89,10655,10656],{},"Quando você quer agentes complexos",": LangGraph (subprojeto do\nLangChain) é hoje a ferramenta mais madura para agentes com estado,\nloops e múltiplos papéis.",[20,10659,10660,10663],{},[89,10661,10662],{},"Quando você quer controle fino",": montar cada etapa do pipeline do\njeito que faz sentido para o seu caso.",[20,10665,10666,10669],{},[89,10667,10668],{},"Quando você precisa de muitas integrações",": conectores para tudo que\nexiste (vector stores, ferramentas, fontes de dados).",[20,10671,10672,10675],{},[89,10673,10674],{},"Quando vai publicar como API",": LangServe gera endpoints REST\nautomaticamente a partir de chains.",[86,10677,10678,10681],{},[89,10679,10680],{},"Caso típico",": \"Quero um agente que recebe um e-mail do cliente, decide se\nencaminha para vendas ou suporte, busca o histórico no CRM, e responde. Tudo\nautomático.\" Esse é um problema de agente com várias ferramentas — LangChain",[528,10683,10684],{},[20,10685,10686],{},"LangGraph brilha aqui.",[12,10688,10690],{"id":10689},"quando-usar-llamaindex-quando-llamaindex","Quando usar LlamaIndex {#quando-llamaindex}",[528,10692,10693,10699,10705,10711,10717],{},[20,10694,10695,10698],{},[89,10696,10697],{},"Quando o problema é RAG",": chatbot que responde com base na sua base de\nconhecimento, assistente que consulta seus documentos internos.",[20,10700,10701,10704],{},[89,10702,10703],{},"Quando você tem dados heterogêneos",": PDFs, Notion, Salesforce, banco\nSQL — LlamaIndex tem conectores prontos e otimizados.",[20,10706,10707,10710],{},[89,10708,10709],{},"Quando você precisa de RAG avançado",": reranking, hybrid search,\nsub-questões, consulta multi-documento.",[20,10712,10713,10716],{},[89,10714,10715],{},"Quando você quer velocidade de protótipo",": o caminho do \"documento →\nRAG funcionando\" é o mais rápido do mercado.",[20,10718,10719,10722],{},[89,10720,10721],{},"Quando você quer qualidade de recuperação",": as estratégias padrão do\nLlamaIndex costumam acertar mais que as do LangChain out-of-the-box.",[86,10724,10725,10727],{},[89,10726,10680],{},": \"Tenho 500 PDFs de manuais técnicos e quero um chatbot que\nresponda perguntas dos clientes só com base nesses manuais.\" LlamaIndex\nresolve isso em 30 linhas de código e com qualidade alta.",[12,10729,10731],{"id":10730},"exemplo-de-código-rag-com-langchain-codigo-langchain","Exemplo de código: RAG com LangChain {#codigo-langchain}",[506,10733,10735],{"className":3439,"code":10734,"language":3441,"meta":515,"style":515},"# pip install langchain langchain-openai langchain-community \\\n#             langchain-postgres tiktoken\n\nfrom langchain_openai import ChatOpenAI, OpenAIEmbeddings\nfrom langchain_community.document_loaders import DirectoryLoader\nfrom langchain_text_splitters import RecursiveCharacterTextSplitter\nfrom langchain_postgres.vectorstores import PGVector\nfrom langchain_core.prompts import ChatPromptTemplate\nfrom langchain_core.runnables import RunnablePassthrough\n\n# 1. Carregar documentos (PDFs, MDs, TXTs da pasta)\nloader = DirectoryLoader(\".\u002Fdocs\", glob=\"**\u002F*.md\")\ndocs = loader.load()\n\n# 2. Quebrar em pedaços menores (chunks) - o LLM não lê tudo de uma vez\n#    chunk_size: tamanho de cada pedaço em caracteres\n#    chunk_overlap: sobreposição para não cortar ideias no meio\nsplitter = RecursiveCharacterTextSplitter(\n    chunk_size=1000, chunk_overlap=200\n)\nchunks = splitter.split_documents(docs)\n\n# 3. Gerar embeddings e salvar no Postgres com pgvector\n#    embeddings: vetores numéricos que representam o sentido do texto\nembeddings = OpenAIEmbeddings(model=\"text-embedding-3-small\")\nvectorstore = PGVector.from_documents(\n    documents=chunks,\n    embedding=embeddings,\n    connection_string=\"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fia\",\n    collection_name=\"meus_docs\",\n)\n\n# 4. Criar o retriever - a parte que busca chunks relevantes\nretriever = vectorstore.as_retriever(search_kwargs={\"k\": 4})\n\n# 5. Montar o prompt - como falar com o LLM\nprompt = ChatPromptTemplate.from_template(\"\"\"\nVocê é um assistente que responde SÓ com base no contexto abaixo.\nSe não souber, diga que não tem informação.\n\nContexto:\n{contexto}\n\nPergunta: {pergunta}\n\"\"\")\n\n# 6. Modelo\nmodelo = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.2)\n\n# 7. Montar a chain: busca contexto -> formata prompt -> chama LLM\ndef formatar_docs(docs):\n    return \"\\n\\n\".join(d.page_content for d in docs)\n\nchain = (\n    {\"contexto\": retriever | formatar_docs, \"pergunta\": RunnablePassthrough()}\n    | prompt\n    | modelo\n)\n\n# 8. Usar\nresposta = chain.invoke(\"Qual a política de reembolso?\")\nprint(resposta.content)\n",[513,10736,10737,10742,10747,10751,10755,10760,10765,10770,10774,10779,10783,10788,10793,10798,10802,10807,10812,10817,10822,10827,10831,10836,10840,10845,10850,10854,10858,10862,10866,10871,10876,10880,10884,10889,10894,10898,10903,10908,10913,10918,10922,10926,10931,10935,10939,10944,10948,10953,10957,10961,10966,10971,10976,10980,10985,10990,10995,11000,11004,11008,11013,11018],{"__ignoreMap":515},[1376,10738,10739],{"class":1378,"line":1379},[1376,10740,10741],{},"# pip install langchain langchain-openai langchain-community \\\n",[1376,10743,10744],{"class":1378,"line":984},[1376,10745,10746],{},"#             langchain-postgres tiktoken\n",[1376,10748,10749],{"class":1378,"line":981},[1376,10750,1398],{"emptyLinePlaceholder":898},[1376,10752,10753],{"class":1378,"line":1395},[1376,10754,7526],{},[1376,10756,10757],{"class":1378,"line":1401},[1376,10758,10759],{},"from langchain_community.document_loaders import DirectoryLoader\n",[1376,10761,10762],{"class":1378,"line":1407},[1376,10763,10764],{},"from langchain_text_splitters import RecursiveCharacterTextSplitter\n",[1376,10766,10767],{"class":1378,"line":1413},[1376,10768,10769],{},"from langchain_postgres.vectorstores import PGVector\n",[1376,10771,10772],{"class":1378,"line":1419},[1376,10773,10285],{},[1376,10775,10776],{"class":1378,"line":1424},[1376,10777,10778],{},"from langchain_core.runnables import RunnablePassthrough\n",[1376,10780,10781],{"class":1378,"line":1430},[1376,10782,1398],{"emptyLinePlaceholder":898},[1376,10784,10785],{"class":1378,"line":1436},[1376,10786,10787],{},"# 1. Carregar documentos (PDFs, MDs, TXTs da pasta)\n",[1376,10789,10790],{"class":1378,"line":1442},[1376,10791,10792],{},"loader = DirectoryLoader(\".\u002Fdocs\", glob=\"**\u002F*.md\")\n",[1376,10794,10795],{"class":1378,"line":1447},[1376,10796,10797],{},"docs = loader.load()\n",[1376,10799,10800],{"class":1378,"line":1453},[1376,10801,1398],{"emptyLinePlaceholder":898},[1376,10803,10804],{"class":1378,"line":1459},[1376,10805,10806],{},"# 2. Quebrar em pedaços menores (chunks) - o LLM não lê tudo de uma vez\n",[1376,10808,10809],{"class":1378,"line":2200},[1376,10810,10811],{},"#    chunk_size: tamanho de cada pedaço em caracteres\n",[1376,10813,10814],{"class":1378,"line":2208},[1376,10815,10816],{},"#    chunk_overlap: sobreposição para não cortar ideias no meio\n",[1376,10818,10819],{"class":1378,"line":2219},[1376,10820,10821],{},"splitter = RecursiveCharacterTextSplitter(\n",[1376,10823,10824],{"class":1378,"line":2227},[1376,10825,10826],{},"    chunk_size=1000, chunk_overlap=200\n",[1376,10828,10829],{"class":1378,"line":2241},[1376,10830,7575],{},[1376,10832,10833],{"class":1378,"line":2253},[1376,10834,10835],{},"chunks = splitter.split_documents(docs)\n",[1376,10837,10838],{"class":1378,"line":2283},[1376,10839,1398],{"emptyLinePlaceholder":898},[1376,10841,10842],{"class":1378,"line":2296},[1376,10843,10844],{},"# 3. Gerar embeddings e salvar no Postgres com pgvector\n",[1376,10846,10847],{"class":1378,"line":2311},[1376,10848,10849],{},"#    embeddings: vetores numéricos que representam o sentido do texto\n",[1376,10851,10852],{"class":1378,"line":2316},[1376,10853,7608],{},[1376,10855,10856],{"class":1378,"line":2324},[1376,10857,7622],{},[1376,10859,10860],{"class":1378,"line":2333},[1376,10861,7627],{},[1376,10863,10864],{"class":1378,"line":2340},[1376,10865,7632],{},[1376,10867,10868],{"class":1378,"line":2351},[1376,10869,10870],{},"    connection_string=\"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fia\",\n",[1376,10872,10873],{"class":1378,"line":2362},[1376,10874,10875],{},"    collection_name=\"meus_docs\",\n",[1376,10877,10878],{"class":1378,"line":2385},[1376,10879,7575],{},[1376,10881,10882],{"class":1378,"line":2396},[1376,10883,1398],{"emptyLinePlaceholder":898},[1376,10885,10886],{"class":1378,"line":2411},[1376,10887,10888],{},"# 4. Criar o retriever - a parte que busca chunks relevantes\n",[1376,10890,10891],{"class":1378,"line":2416},[1376,10892,10893],{},"retriever = vectorstore.as_retriever(search_kwargs={\"k\": 4})\n",[1376,10895,10896],{"class":1378,"line":2422},[1376,10897,1398],{"emptyLinePlaceholder":898},[1376,10899,10900],{"class":1378,"line":2430},[1376,10901,10902],{},"# 5. Montar o prompt - como falar com o LLM\n",[1376,10904,10905],{"class":1378,"line":2448},[1376,10906,10907],{},"prompt = ChatPromptTemplate.from_template(\"\"\"\n",[1376,10909,10910],{"class":1378,"line":2457},[1376,10911,10912],{},"Você é um assistente que responde SÓ com base no contexto abaixo.\n",[1376,10914,10915],{"class":1378,"line":2464},[1376,10916,10917],{},"Se não souber, diga que não tem informação.\n",[1376,10919,10920],{"class":1378,"line":2475},[1376,10921,1398],{"emptyLinePlaceholder":898},[1376,10923,10924],{"class":1378,"line":2487},[1376,10925,5553],{},[1376,10927,10928],{"class":1378,"line":2499},[1376,10929,10930],{},"{contexto}\n",[1376,10932,10933],{"class":1378,"line":2506},[1376,10934,1398],{"emptyLinePlaceholder":898},[1376,10936,10937],{"class":1378,"line":2517},[1376,10938,5567],{},[1376,10940,10941],{"class":1378,"line":2528},[1376,10942,10943],{},"\"\"\")\n",[1376,10945,10946],{"class":1378,"line":2539},[1376,10947,1398],{"emptyLinePlaceholder":898},[1376,10949,10950],{"class":1378,"line":2553},[1376,10951,10952],{},"# 6. Modelo\n",[1376,10954,10955],{"class":1378,"line":2564},[1376,10956,10309],{},[1376,10958,10959],{"class":1378,"line":2576},[1376,10960,1398],{"emptyLinePlaceholder":898},[1376,10962,10963],{"class":1378,"line":2602},[1376,10964,10965],{},"# 7. Montar a chain: busca contexto -> formata prompt -> chama LLM\n",[1376,10967,10968],{"class":1378,"line":2607},[1376,10969,10970],{},"def formatar_docs(docs):\n",[1376,10972,10973],{"class":1378,"line":2613},[1376,10974,10975],{},"    return \"\\n\\n\".join(d.page_content for d in docs)\n",[1376,10977,10978],{"class":1378,"line":2620},[1376,10979,1398],{"emptyLinePlaceholder":898},[1376,10981,10982],{"class":1378,"line":2631},[1376,10983,10984],{},"chain = (\n",[1376,10986,10987],{"class":1378,"line":2641},[1376,10988,10989],{},"    {\"contexto\": retriever | formatar_docs, \"pergunta\": RunnablePassthrough()}\n",[1376,10991,10992],{"class":1378,"line":2651},[1376,10993,10994],{},"    | prompt\n",[1376,10996,10997],{"class":1378,"line":2658},[1376,10998,10999],{},"    | modelo\n",[1376,11001,11002],{"class":1378,"line":2670},[1376,11003,7575],{},[1376,11005,11006],{"class":1378,"line":2685},[1376,11007,1398],{"emptyLinePlaceholder":898},[1376,11009,11010],{"class":1378,"line":6082},[1376,11011,11012],{},"# 8. Usar\n",[1376,11014,11015],{"class":1378,"line":6088},[1376,11016,11017],{},"resposta = chain.invoke(\"Qual a política de reembolso?\")\n",[1376,11019,11020],{"class":1378,"line":6093},[1376,11021,11022],{},"print(resposta.content)\n",[86,11024,11025],{},"Cada bloco numerado é uma etapa do RAG. LangChain te faz explicitar todas —\nmais código, mais controle. Note como você escolhe splitter, retriever,\nprompt e modelo separadamente.",[12,11027,11029],{"id":11028},"exemplo-de-código-rag-com-llamaindex-codigo-llamaindex","Exemplo de código: RAG com LlamaIndex {#codigo-llamaindex}",[506,11031,11033],{"className":3439,"code":11032,"language":3441,"meta":515,"style":515},"# pip install llama-index llama-index-vector-stores-postgres \\\n#             llama-index-embeddings-openai llama-index-llms-openai\n\nfrom llama_index.core import VectorStoreIndex, SimpleDirectoryReader\nfrom llama_index.core import Settings\nfrom llama_index.llms.openai import OpenAI\nfrom llama_index.embeddings.openai import OpenAIEmbedding\nfrom llama_index.vector_stores.postgres import PGVectorStore\nfrom sqlalchemy import create_engine\n\n# 1. Configurar modelos globais (LLM e embeddings)\nSettings.llm = OpenAI(model=\"gpt-4o-mini\", temperature=0.2)\nSettings.embed_model = OpenAIEmbedding(model=\"text-embedding-3-small\")\n\n# 2. Carregar documentos - LlamaIndex cuida da leitura e parsing\ndocumentos = SimpleDirectoryReader(\".\u002Fdocs\").load_data()\n\n# 3. Conectar ao Postgres com pgvector\nengine = create_engine(\"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fia\")\nvector_store = PGVectorStore.from_params(\n    database=\"ia\",\n    host=\"localhost\",\n    password=\"pass\",\n    port=5432,\n    user=\"user\",\n    table_name=\"meus_docs\",\n    embed_dim=1536,  # tamanho do vetor do text-embedding-3-small\n)\n\n# 4. Construir índice (chunking + embeddings + armazenamento automáticos)\nindice = VectorStoreIndex.from_documents(\n    documentos,\n    vector_store=vector_store,\n    chunk_size=1000,\n    chunk_overlap=200,\n)\n\n# 5. Criar o query engine - interface de pergunta\n#    similarity_top_k: quantos chunks relevantes recuperar\nmotor = indice.as_query_engine(similarity_top_k=4)\n\n# 6. Perguntar\nresposta = motor.query(\"Qual a política de reembolso?\")\nprint(resposta)\n",[513,11034,11035,11040,11045,11049,11053,11058,11063,11068,11073,11078,11082,11087,11092,11097,11101,11106,11111,11115,11120,11125,11130,11135,11140,11145,11150,11155,11160,11165,11169,11173,11178,11183,11188,11193,11198,11203,11207,11211,11216,11221,11226,11230,11235,11239],{"__ignoreMap":515},[1376,11036,11037],{"class":1378,"line":1379},[1376,11038,11039],{},"# pip install llama-index llama-index-vector-stores-postgres \\\n",[1376,11041,11042],{"class":1378,"line":984},[1376,11043,11044],{},"#             llama-index-embeddings-openai llama-index-llms-openai\n",[1376,11046,11047],{"class":1378,"line":981},[1376,11048,1398],{"emptyLinePlaceholder":898},[1376,11050,11051],{"class":1378,"line":1395},[1376,11052,10437],{},[1376,11054,11055],{"class":1378,"line":1401},[1376,11056,11057],{},"from llama_index.core import Settings\n",[1376,11059,11060],{"class":1378,"line":1407},[1376,11061,11062],{},"from llama_index.llms.openai import OpenAI\n",[1376,11064,11065],{"class":1378,"line":1413},[1376,11066,11067],{},"from llama_index.embeddings.openai import OpenAIEmbedding\n",[1376,11069,11070],{"class":1378,"line":1419},[1376,11071,11072],{},"from llama_index.vector_stores.postgres import PGVectorStore\n",[1376,11074,11075],{"class":1378,"line":1424},[1376,11076,11077],{},"from sqlalchemy import create_engine\n",[1376,11079,11080],{"class":1378,"line":1430},[1376,11081,1398],{"emptyLinePlaceholder":898},[1376,11083,11084],{"class":1378,"line":1436},[1376,11085,11086],{},"# 1. Configurar modelos globais (LLM e embeddings)\n",[1376,11088,11089],{"class":1378,"line":1442},[1376,11090,11091],{},"Settings.llm = OpenAI(model=\"gpt-4o-mini\", temperature=0.2)\n",[1376,11093,11094],{"class":1378,"line":1447},[1376,11095,11096],{},"Settings.embed_model = OpenAIEmbedding(model=\"text-embedding-3-small\")\n",[1376,11098,11099],{"class":1378,"line":1453},[1376,11100,1398],{"emptyLinePlaceholder":898},[1376,11102,11103],{"class":1378,"line":1459},[1376,11104,11105],{},"# 2. Carregar documentos - LlamaIndex cuida da leitura e parsing\n",[1376,11107,11108],{"class":1378,"line":2200},[1376,11109,11110],{},"documentos = SimpleDirectoryReader(\".\u002Fdocs\").load_data()\n",[1376,11112,11113],{"class":1378,"line":2208},[1376,11114,1398],{"emptyLinePlaceholder":898},[1376,11116,11117],{"class":1378,"line":2219},[1376,11118,11119],{},"# 3. Conectar ao Postgres com pgvector\n",[1376,11121,11122],{"class":1378,"line":2227},[1376,11123,11124],{},"engine = create_engine(\"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fia\")\n",[1376,11126,11127],{"class":1378,"line":2241},[1376,11128,11129],{},"vector_store = PGVectorStore.from_params(\n",[1376,11131,11132],{"class":1378,"line":2253},[1376,11133,11134],{},"    database=\"ia\",\n",[1376,11136,11137],{"class":1378,"line":2283},[1376,11138,11139],{},"    host=\"localhost\",\n",[1376,11141,11142],{"class":1378,"line":2296},[1376,11143,11144],{},"    password=\"pass\",\n",[1376,11146,11147],{"class":1378,"line":2311},[1376,11148,11149],{},"    port=5432,\n",[1376,11151,11152],{"class":1378,"line":2316},[1376,11153,11154],{},"    user=\"user\",\n",[1376,11156,11157],{"class":1378,"line":2324},[1376,11158,11159],{},"    table_name=\"meus_docs\",\n",[1376,11161,11162],{"class":1378,"line":2333},[1376,11163,11164],{},"    embed_dim=1536,  # tamanho do vetor do text-embedding-3-small\n",[1376,11166,11167],{"class":1378,"line":2340},[1376,11168,7575],{},[1376,11170,11171],{"class":1378,"line":2351},[1376,11172,1398],{"emptyLinePlaceholder":898},[1376,11174,11175],{"class":1378,"line":2362},[1376,11176,11177],{},"# 4. Construir índice (chunking + embeddings + armazenamento automáticos)\n",[1376,11179,11180],{"class":1378,"line":2385},[1376,11181,11182],{},"indice = VectorStoreIndex.from_documents(\n",[1376,11184,11185],{"class":1378,"line":2396},[1376,11186,11187],{},"    documentos,\n",[1376,11189,11190],{"class":1378,"line":2411},[1376,11191,11192],{},"    vector_store=vector_store,\n",[1376,11194,11195],{"class":1378,"line":2416},[1376,11196,11197],{},"    chunk_size=1000,\n",[1376,11199,11200],{"class":1378,"line":2422},[1376,11201,11202],{},"    chunk_overlap=200,\n",[1376,11204,11205],{"class":1378,"line":2430},[1376,11206,7575],{},[1376,11208,11209],{"class":1378,"line":2448},[1376,11210,1398],{"emptyLinePlaceholder":898},[1376,11212,11213],{"class":1378,"line":2457},[1376,11214,11215],{},"# 5. Criar o query engine - interface de pergunta\n",[1376,11217,11218],{"class":1378,"line":2464},[1376,11219,11220],{},"#    similarity_top_k: quantos chunks relevantes recuperar\n",[1376,11222,11223],{"class":1378,"line":2475},[1376,11224,11225],{},"motor = indice.as_query_engine(similarity_top_k=4)\n",[1376,11227,11228],{"class":1378,"line":2487},[1376,11229,1398],{"emptyLinePlaceholder":898},[1376,11231,11232],{"class":1378,"line":2499},[1376,11233,11234],{},"# 6. Perguntar\n",[1376,11236,11237],{"class":1378,"line":2506},[1376,11238,10498],{},[1376,11240,11241],{"class":1378,"line":2517},[1376,11242,10503],{},[86,11244,11245],{},"Veja: mesmo resultado, bem menos código. LlamaIndex assume defaults\nsensatos e expõe só o que importa. Você troca 8 etapas explícitas por 6\ndeclarativas — e ganha estratégias de reranking \"de graça\" quando precisar.",[12,11247,11249],{"id":11248},"e-os-agentes-agentes","E os agentes? {#agentes}",[86,11251,11252,11253,11256,11257,11260,11261,522],{},"Agente de IA é um LLM que recebe ferramentas e decide sozinho qual usar para\nresolver uma tarefa — em vez de seguir um fluxo fixo. Exemplo: um agente que\nrecebe \"agende uma reunião com o João\" pode decidir chamar a ferramenta\n",[513,11254,11255],{},"buscar_email(joao)",", depois ",[513,11258,11259],{},"verificar_agenda(quinta)",", depois\n",[513,11262,11263],{},"criar_evento(...)",[165,11265,11266,11278],{},[168,11267,11268],{},[171,11269,11270,11273,11276],{},[174,11271,11272],{},"Ferramenta",[174,11274,11275],{},"Para que",[174,11277,1588],{},[184,11279,11280,11291,11301,11312],{},[171,11281,11282,11285,11288],{},[189,11283,11284],{},"LangChain Agents",[189,11286,11287],{},"Agentes simples, few-shot",[189,11289,11290],{},"Provas de conceito rápidas",[171,11292,11293,11295,11298],{},[189,11294,6408],{},[189,11296,11297],{},"Agentes com estado, loops, múltiplos papéis",[189,11299,11300],{},"Agentes de produção complexos",[171,11302,11303,11306,11309],{},[189,11304,11305],{},"LlamaIndex Agents",[189,11307,11308],{},"Agentes que consultam dados",[189,11310,11311],{},"Quando o trabalho principal é RAG",[171,11313,11314,11317,11320],{},[189,11315,11316],{},"CrewAI \u002F AutoGen",[189,11318,11319],{},"Multi-agentes colaborativos",[189,11321,11322],{},"Quando você quer \"uma equipe de IAs\"",[86,11324,11325,11328,11329,11331],{},[89,11326,11327],{},"Recomendação prática",": para agentes complexos com estado e fluxos\nelaborados, ",[89,11330,6408],{}," (do ecossistema LangChain) é hoje a escolha mais\nmadura. Para agentes cujo trabalho principal é consultar documentos e\nbancos, LlamaIndex Agents é mais direto.",[86,11333,11334,11335,11338],{},"Importante: ",[89,11336,11337],{},"agente não é bala de prata",". Antes de adotar um agente,\navalie se uma chain simples (fluxo fixo) não resolve. Agentes são mais\nflexíveis, mas mais lentos, mais caros e mais difíceis de debugar. Use só\nquando a decisão \"qual ferramenta usar\" realmente varia entre chamadas.",[12,11340,820],{"id":819},[17,11342,11343,11349,11355,11361,11371,11381,11387,11393],{},[20,11344,11345,11348],{},[89,11346,11347],{},"Escolher framework antes do problema",": \"vou usar LangChain porque é o\nmais famoso\". Comece pelo problema, depois escolha a ferramenta.",[20,11350,11351,11354],{},[89,11352,11353],{},"Misturar os dois sem motivo",": usar LangChain pra agents e LlamaIndex\npra RAG no mesmo projeto, sem necessidade. Dá pra fazer, mas você paga\nduas curvas de aprendizado. Escolha um e vá fundo.",[20,11356,11357,11360],{},[89,11358,11359],{},"Achar que framework resolve tudo",": framework orquestra, mas a qualidade\nvem dos dados, do prompt e do modelo. Sem curadoria, nem LlamaIndex salva.",[20,11362,11363,11366,11367,11370],{},[89,11364,11365],{},"Usar defaults sem entender",": LlamaIndex tem ",[513,11368,11369],{},"chunk_size=1024"," default.\nPara manuais técnicos curtos, isso corta seções no meio. Entenda os\nparâmetros antes de aceitar.",[20,11372,11373,11376,11377,11380],{},[89,11374,11375],{},"Subestimar a instabilidade do LangChain",": a API mudou muito entre 2022\ne 2024. Travou em uma versão no ",[513,11378,11379],{},"requirements.txt"," para não quebrar.",[20,11382,11383,11386],{},[89,11384,11385],{},"Ignorar observabilidade",": sem LangSmith ou equivalente, você não sabe\no que o agente está decidindo. Em produção, isso vira caixa-preta.",[20,11388,11389,11392],{},[89,11390,11391],{},"Construir agente logo no primeiro projeto",": agentes são tentadores, mas\ncomeçar com uma chain simples evita 80% dos problemas de custo e\nimprevisibilidade.",[20,11394,11395,11398,11399,11402],{},[89,11396,11397],{},"Não isolar o framework do seu código",": importar ",[513,11400,11401],{},"langchain"," em todo\narquivo do sistema. Crie uma camada de abstração para poder trocar de\nframework (ou versão) sem reescrever tudo.",[968,11404],{},[86,11406,11407,11410,11411,11413],{},[89,11408,11409],{},"Não sabe qual framework escolher para o seu caso?"," A Inicialize Tec\navalia sua necessidade e implementa com a ferramenta certa — sem viés de\nmodinha. ",[23,11412,6840],{"href":978}," e conversamos sobre seu projeto.",[2883,11415,4828],{},{"title":515,"searchDepth":981,"depth":981,"links":11417},[11418,11419,11420,11424,11428,11429,11430,11431,11432,11433,11434],{"id":14,"depth":984,"text":15},{"id":10139,"depth":984,"text":10140},{"id":10197,"depth":984,"text":10198,"children":11421},[11422,11423],{"id":10208,"depth":981,"text":10209},{"id":10262,"depth":981,"text":10263},{"id":10352,"depth":984,"text":10353,"children":11425},[11426,11427],{"id":10373,"depth":981,"text":10374},{"id":10420,"depth":981,"text":10263},{"id":3219,"depth":984,"text":3220},{"id":10642,"depth":984,"text":10643},{"id":10689,"depth":984,"text":10690},{"id":10730,"depth":984,"text":10731},{"id":11028,"depth":984,"text":11029},{"id":11248,"depth":984,"text":11249},{"id":819,"depth":984,"text":820},"Comparativo dos dois frameworks mais populares para construir aplicações com LLMs. O que cada um faz, quando usar e exemplos de código.",[11401,11437,11438,4965,4972,7475,11439],"llamaindex","framework IA","agentes",{},"\u002Fia\u002Flangchain-vs-llamaindex",{"title":10074,"description":11435},"langchain-vs-llamaindex","ia\u002Flangchain-vs-llamaindex","18EQfFzsztHwZZansyawe1zPtUtoZb5rndGhpd9lVeM",{"id":11447,"title":11448,"author":7,"body":11449,"category":6869,"date":1028,"description":13706,"extension":1030,"keywords":13707,"meta":13710,"navigation":898,"path":13711,"seo":13712,"slug":13713,"stem":13714,"updated":1028,"__hash__":13715},"content\u002Fia\u002Frag-com-postgresql-pgvector.md","RAG com PostgreSQL e pgvector: IA com seus dados",{"type":9,"value":11450,"toc":13663},[11451,11453,11530,11534,11545,11552,11570,11575,11584,11588,11598,11607,11611,11712,11718,11722,11725,11727,11737,11742,11745,11749,11752,11772,11775,11807,11817,11821,11825,11896,11903,11907,11910,11919,11923,11981,11984,11998,12002,12005,12118,12125,12129,12133,12270,12281,12285,12294,12302,12305,12309,12408,12412,12415,12592,12598,12602,12615,12619,12622,12729,12736,12740,12743,12790,12794,12797,13063,13074,13078,13082,13089,13140,13148,13167,13181,13185,13191,13248,13251,13272,13316,13320,13324,13327,13338,13341,13409,13413,13416,13454,13461,13465,13468,13472,13475,13479,13489,13561,13571,13575,13578,13580,13649,13651,13660],[12,11452,15],{"id":14},[17,11454,11455,11461,11467,11472,11478,11484,11490,11496,11502,11508,11514,11520,11526],{},[20,11456,11457],{},[23,11458,11460],{"href":11459},"#o-que-e-rag","O que é RAG e por que você precisa",[20,11462,11463],{},[23,11464,11466],{"href":11465},"#por-que-pgvector","Por que PostgreSQL e pgvector",[20,11468,11469],{},[23,11470,11471],{"href":6914},"Conceitos: embeddings e similaridade",[20,11473,11474],{},[23,11475,11477],{"href":11476},"#instalando","Instalando o pgvector",[20,11479,11480],{},[23,11481,11483],{"href":11482},"#esquema","Esquema do banco para RAG",[20,11485,11486],{},[23,11487,11489],{"href":11488},"#gerando","Gerando e armazenando embeddings",[20,11491,11492],{},[23,11493,11495],{"href":11494},"#buscando","Buscando contexto: a mágica do RAG",[20,11497,11498],{},[23,11499,11501],{"href":11500},"#prompt","Prompt e geração de resposta",[20,11503,11504],{},[23,11505,11507],{"href":11506},"#pipeline","Pipeline completo em Python",[20,11509,11510],{},[23,11511,11513],{"href":11512},"#otimizando","Otimizando: índices e chunking",[20,11515,11516],{},[23,11517,11519],{"href":11518},"#avancado","RAG avançado: reranking e filtros",[20,11521,11522],{},[23,11523,11525],{"href":11524},"#avaliando","Avaliando qualidade do RAG",[20,11527,11528],{},[23,11529,74],{"href":73},[12,11531,11533],{"id":11532},"o-que-é-rag-e-por-que-você-precisa-o-que-e-rag","O que é RAG e por que você precisa {#o-que-e-rag}",[86,11535,1160,11536,4958,11538,11540,11541,11544],{},[89,11537,4965],{},[501,11539,8711],{},") é um modelo de IA treinado para entender\ne gerar texto — o ChatGPT é o exemplo mais conhecido. O problema do LLM\npuro: ele só sabe o que aprendeu no treino. Pergunte ao GPT-4 qual a política\nde reembolso da ",[89,11542,11543],{},"sua"," empresa e ele vai inventar algo plausível — porque\nnão tem como saber.",[86,11546,11547,4958,11549,11551],{},[89,11548,4972],{},[501,11550,4975],{},", algo como \"geração aumentada por\nrecuperação\") resolve isso. A ideia é simples:",[17,11553,11554,11557,11560,11563],{},[20,11555,11556],{},"Você guarda seus documentos em um banco preparado para busca semântica",[20,11558,11559],{},"Quando o usuário pergunta algo, o sistema busca os trechos relevantes",[20,11561,11562],{},"Esses trechos são injetados no prompt enviado ao LLM, como \"contexto\"",[20,11564,11565,11566,11569],{},"O LLM responde ",[89,11567,11568],{},"olhando"," para o contexto, em vez de adivinhar",[86,11571,11572,11574],{},[89,11573,3011],{},": imagina que o LLM é um médico generalista muito bem formado.\nSem RAG, ele atende no escuro — só com o que lembra da faculdade. Com RAG,\nvocê entrega o prontuário do paciente antes da consulta. Ele continua sendo\no mesmo médico, mas agora responde com base naquele caso específico.",[86,11576,11577,11578,11581,11582,522],{},"RAG é o que transforma um LLM genérico em um assistente que entende do\n",[89,11579,11580],{},"seu"," negócio. E a boa notícia: você não precisa de um banco de dados\nnovo. Se já usa PostgreSQL, basta instalar a extensão ",[89,11583,6324],{},[12,11585,11587],{"id":11586},"por-que-postgresql-e-pgvector-por-que-pgvector","Por que PostgreSQL e pgvector {#por-que-pgvector}",[86,11589,1160,11590,11593,11594,11597],{},[89,11591,11592],{},"vector database"," (banco vetorial) é um banco otimizado para armazenar\ne buscar vetores numéricos — que é como representamos o \"sentido\" dos\ntextos. Existem bancos dedicados (Pinecone, Qdrant, Weaviate, Milvus), mas\npara a maioria dos projetos, ",[89,11595,11596],{},"usar o PostgreSQL que você já tem"," é a\nescolha mais inteligente.",[86,11599,11600,11602,11603,11606],{},[89,11601,6324],{}," é uma extensão open source do PostgreSQL que adiciona o tipo\n",[513,11604,11605],{},"vector"," e operações de busca por similaridade. É mantida pela comunidade,\nsuportada por AWS RDS, Google Cloud SQL e Supabase, e roda em qualquer\nPostgres 13+.",[283,11608,11610],{"id":11609},"vantagens-do-pgvector-sobre-bancos-dedicados","Vantagens do pgvector sobre bancos dedicados",[165,11612,11613,11624],{},[168,11614,11615],{},[171,11616,11617,11619,11621],{},[174,11618,3229],{},[174,11620,6324],{},[174,11622,11623],{},"Banco vetorial dedicado",[184,11625,11626,11636,11647,11658,11669,11680,11691,11701],{},[171,11627,11628,11631,11634],{},[189,11629,11630],{},"Infraestrutura nova",[189,11632,11633],{},"Não (reaproveita PG)",[189,11635,10629],{},[171,11637,11638,11641,11644],{},[189,11639,11640],{},"Transações ACID",[189,11642,11643],{},"Sim (nativo do PG)",[189,11645,11646],{},"Geralmente não",[171,11648,11649,11652,11655],{},[189,11650,11651],{},"Join com dados relacionais",[189,11653,11654],{},"Sim, na mesma query",[189,11656,11657],{},"Não, precisa sincronizar",[171,11659,11660,11663,11666],{},[189,11661,11662],{},"Backup e replicação",[189,11664,11665],{},"Igual ao seu PG atual",[189,11667,11668],{},"Configuração separada",[171,11670,11671,11674,11677],{},[189,11672,11673],{},"Custo adicional",[189,11675,11676],{},"$0 (extensão livre)",[189,11678,11679],{},"$20–200+\u002Fmês",[171,11681,11682,11685,11688],{},[189,11683,11684],{},"Escala para 100M+ vetores",[189,11686,11687],{},"So-so, precisa tuning",[189,11689,11690],{},"Sim, nativo",[171,11692,11693,11695,11698],{},[189,11694,3358],{},[189,11696,11697],{},"Baixa (sabe SQL? sabe usar)",[189,11699,11700],{},"Nova stack",[171,11702,11703,11706,11709],{},[189,11704,11705],{},"Tipos de índice",[189,11707,11708],{},"IVFFlat, HNSW",[189,11710,11711],{},"Vários",[86,11713,11714,11717],{},[89,11715,11716],{},"Quando NÃO usar pgvector",": se você tem 50M+ vetores e precisa de\nlatência abaixo de 50ms em toda busca, um banco dedicado (Qdrant, Milvus)\ntende a performar melhor. Para 99% dos projetos corporativos, pgvector\nresolve com folga.",[12,11719,11721],{"id":11720},"conceitos-embeddings-e-similaridade-conceitos","Conceitos: embeddings e similaridade {#conceitos}",[86,11723,11724],{},"Dois conceitos-chave. Vamos com calma.",[283,11726,6372],{"id":7371},[86,11728,1160,11729,11732,11733,11736],{},[89,11730,11731],{},"embedding"," é uma lista de números (um vetor) que representa o sentido\nde um texto. Textos com significado parecido têm vetores parecidos. É\ngerado por um modelo treinado para isso — não pelo LLM que gera as respostas,\nmas por um modelo especializado em embeddings (ex: ",[513,11734,11735],{},"text-embedding-3-small","\nda OpenAI).",[86,11738,11739,11741],{},[89,11740,3011],{},": pense em embeddings como coordenadas GPS do sentido. \"Cachorro\nlate\" e \"O perro ladra\" ficam em pontos próximos do mapa, porque querem dizer\na mesma coisa. \"Cachorro late\" e \"Bicicleta vermelha\" ficam longe um do outro.",[86,11743,11744],{},"Um embedding típico tem 384, 768, 1536 ou 3072 dimensões (depende do modelo).\nQuanto mais dimensões, mais nuance captura — e mais espaço ocupa.",[283,11746,11748],{"id":11747},"similaridade-distância","Similaridade (distância)",[86,11750,11751],{},"Para encontrar documentos relevantes, comparamos vetores. As duas métricas\nmais comuns:",[528,11753,11754,11763],{},[20,11755,11756,4958,11759,11762],{},[89,11757,11758],{},"Cosseno",[513,11760,11761],{},"\u003C=>"," no pgvector): mede o ângulo entre vetores. Ideal para\ntexto, porque ignora o tamanho (quantas palavras) e olha só a direção\n(sentido).",[20,11764,11765,4958,11768,11771],{},[89,11766,11767],{},"L2 \u002F Euclidiana",[513,11769,11770],{},"\u003C->"," no pgvector): mede a distância reta. Funciona,\nmas é menos comum em RAG.",[86,11773,11774],{},"Valores menores = mais similares (no cosseno, 0 = idêntico, 2 = oposto).",[506,11776,11780],{"className":11777,"code":11778,"language":11779,"meta":515,"style":515},"language-sql shiki shiki-themes github-light github-dark","-- Exemplo: buscar os 5 documentos mais parecidos com um vetor de consulta\nSELECT id, conteudo\nFROM documentos\nORDER BY embedding \u003C=> '[0.1, 0.2, ...]'  -- vetor da pergunta\nLIMIT 5;\n","sql",[513,11781,11782,11787,11792,11797,11802],{"__ignoreMap":515},[1376,11783,11784],{"class":1378,"line":1379},[1376,11785,11786],{},"-- Exemplo: buscar os 5 documentos mais parecidos com um vetor de consulta\n",[1376,11788,11789],{"class":1378,"line":984},[1376,11790,11791],{},"SELECT id, conteudo\n",[1376,11793,11794],{"class":1378,"line":981},[1376,11795,11796],{},"FROM documentos\n",[1376,11798,11799],{"class":1378,"line":1395},[1376,11800,11801],{},"ORDER BY embedding \u003C=> '[0.1, 0.2, ...]'  -- vetor da pergunta\n",[1376,11803,11804],{"class":1378,"line":1401},[1376,11805,11806],{},"LIMIT 5;\n",[86,11808,11809,11810,11812,11813,11816],{},"Comentário: o operador ",[513,11811,11761],{}," calcula a distância-cosseno entre cada\nembedding da tabela e o vetor da pergunta; o ",[513,11814,11815],{},"ORDER BY ... LIMIT 5"," pega os\n5 mais próximos. Simples assim — é só SQL.",[12,11818,11820],{"id":11819},"instalando-o-pgvector-instalando","Instalando o pgvector {#instalando}",[283,11822,11824],{"id":11823},"em-docker-recomendado-para-desenvolvimento","Em Docker (recomendado para desenvolvimento)",[506,11826,11830],{"className":11827,"code":11828,"language":11829,"meta":515,"style":515},"language-bash shiki shiki-themes github-light github-dark","# Imagem oficial do Postgres já com pgvector instalado\ndocker run -d \\\n  --name pgvector-db \\\n  -e POSTGRES_PASSWORD=suasenha \\\n  -e POSTGRES_DB=ia \\\n  -p 5432:5432 \\\n  pgvector\u002Fpgvector:pg16\n","bash",[513,11831,11832,11837,11852,11862,11872,11881,11891],{"__ignoreMap":515},[1376,11833,11834],{"class":1378,"line":1379},[1376,11835,11836],{"class":1665},"# Imagem oficial do Postgres já com pgvector instalado\n",[1376,11838,11839,11843,11846,11849],{"class":1378,"line":984},[1376,11840,11842],{"class":11841},"sScJk","docker",[1376,11844,11845],{"class":1682}," run",[1376,11847,11848],{"class":1770}," -d",[1376,11850,11851],{"class":1770}," \\\n",[1376,11853,11854,11857,11860],{"class":1378,"line":981},[1376,11855,11856],{"class":1770},"  --name",[1376,11858,11859],{"class":1682}," pgvector-db",[1376,11861,11851],{"class":1770},[1376,11863,11864,11867,11870],{"class":1378,"line":1395},[1376,11865,11866],{"class":1770},"  -e",[1376,11868,11869],{"class":1682}," POSTGRES_PASSWORD=suasenha",[1376,11871,11851],{"class":1770},[1376,11873,11874,11876,11879],{"class":1378,"line":1401},[1376,11875,11866],{"class":1770},[1376,11877,11878],{"class":1682}," POSTGRES_DB=ia",[1376,11880,11851],{"class":1770},[1376,11882,11883,11886,11889],{"class":1378,"line":1407},[1376,11884,11885],{"class":1770},"  -p",[1376,11887,11888],{"class":1682}," 5432:5432",[1376,11890,11851],{"class":1770},[1376,11892,11893],{"class":1378,"line":1413},[1376,11894,11895],{"class":1682},"  pgvector\u002Fpgvector:pg16\n",[86,11897,11898,11899,11902],{},"Comentário: a imagem ",[513,11900,11901],{},"pgvector\u002Fpgvector:pg16"," é Postgres 16 com a extensão\npré-instalada. Sem compilação, sem dor de cabeça.",[283,11904,11906],{"id":11905},"em-aws-rds","Em AWS RDS",[86,11908,11909],{},"A partir do Postgres 13, o pgvector já vem pré-instalado em RDS — basta\nativar:",[506,11911,11913],{"className":11777,"code":11912,"language":11779,"meta":515,"style":515},"CREATE EXTENSION IF NOT EXISTS vector;\n",[513,11914,11915],{"__ignoreMap":515},[1376,11916,11917],{"class":1378,"line":1379},[1376,11918,11912],{},[283,11920,11922],{"id":11921},"em-postgres-self-hosted","Em Postgres self-hosted",[506,11924,11926],{"className":11827,"code":11925,"language":11829,"meta":515,"style":515},"# Compilar e instalar a extensão\ncd \u002Ftmp\ngit clone --branch v0.7.4 https:\u002F\u002Fgithub.com\u002Fpgvector\u002Fpgvector.git\ncd pgvector\nmake\nmake install  # pode precisar de sudo\n",[513,11927,11928,11933,11941,11958,11965,11970],{"__ignoreMap":515},[1376,11929,11930],{"class":1378,"line":1379},[1376,11931,11932],{"class":1665},"# Compilar e instalar a extensão\n",[1376,11934,11935,11938],{"class":1378,"line":984},[1376,11936,11937],{"class":1770},"cd",[1376,11939,11940],{"class":1682}," \u002Ftmp\n",[1376,11942,11943,11946,11949,11952,11955],{"class":1378,"line":981},[1376,11944,11945],{"class":11841},"git",[1376,11947,11948],{"class":1682}," clone",[1376,11950,11951],{"class":1770}," --branch",[1376,11953,11954],{"class":1682}," v0.7.4",[1376,11956,11957],{"class":1682}," https:\u002F\u002Fgithub.com\u002Fpgvector\u002Fpgvector.git\n",[1376,11959,11960,11962],{"class":1378,"line":1395},[1376,11961,11937],{"class":1770},[1376,11963,11964],{"class":1682}," pgvector\n",[1376,11966,11967],{"class":1378,"line":1401},[1376,11968,11969],{"class":11841},"make\n",[1376,11971,11972,11975,11978],{"class":1378,"line":1407},[1376,11973,11974],{"class":11841},"make",[1376,11976,11977],{"class":1682}," install",[1376,11979,11980],{"class":1665},"  # pode precisar de sudo\n",[86,11982,11983],{},"Depois, no banco:",[506,11985,11987],{"className":11777,"code":11986,"language":11779,"meta":515,"style":515},"CREATE EXTENSION IF NOT EXISTS vector;\nSELECT * FROM pg_extension WHERE extname = 'vector';\n",[513,11988,11989,11993],{"__ignoreMap":515},[1376,11990,11991],{"class":1378,"line":1379},[1376,11992,11912],{},[1376,11994,11995],{"class":1378,"line":984},[1376,11996,11997],{},"SELECT * FROM pg_extension WHERE extname = 'vector';\n",[12,11999,12001],{"id":12000},"esquema-do-banco-para-rag-esquema","Esquema do banco para RAG {#esquema}",[86,12003,12004],{},"A estrutura típica de uma tabela de RAG:",[506,12006,12008],{"className":11777,"code":12007,"language":11779,"meta":515,"style":515},"CREATE TABLE documentos (\n    id          BIGSERIAL PRIMARY KEY,\n    -- Origem do documento: arquivo, URL, sistema\n    fonte       TEXT NOT NULL,\n    -- Metadados para filtro (JSONB é flexível)\n    -- ex: {\"tipo\": \"manual\", \"produto\": \"X100\", \"data\": \"2026-01\"}\n    metadados   JSONB DEFAULT '{}'::jsonb,\n    -- Texto original do chunk (pedaço do documento)\n    conteudo    TEXT NOT NULL,\n    -- Vetor (dimensão depende do modelo de embedding)\n    embedding   vector(1536) NOT NULL,\n    -- Quando foi inserido\u002Fatualizado (para re-indexar só o que mudou)\n    criado_em   TIMESTAMPTZ DEFAULT now()\n);\n\n-- Índice para filtrar por metadados (ex: só documentos de um produto)\nCREATE INDEX idx_documentos_metadados\n    ON documentos USING gin (metadados);\n\n-- Índice vetorial - explicado adiante em \"Otimizando\"\nCREATE INDEX idx_documentos_embedding\n    ON documentos USING hnsw (embedding vector_cosine_ops);\n",[513,12009,12010,12015,12020,12025,12030,12035,12040,12045,12050,12055,12060,12065,12070,12075,12080,12084,12089,12094,12099,12103,12108,12113],{"__ignoreMap":515},[1376,12011,12012],{"class":1378,"line":1379},[1376,12013,12014],{},"CREATE TABLE documentos (\n",[1376,12016,12017],{"class":1378,"line":984},[1376,12018,12019],{},"    id          BIGSERIAL PRIMARY KEY,\n",[1376,12021,12022],{"class":1378,"line":981},[1376,12023,12024],{},"    -- Origem do documento: arquivo, URL, sistema\n",[1376,12026,12027],{"class":1378,"line":1395},[1376,12028,12029],{},"    fonte       TEXT NOT NULL,\n",[1376,12031,12032],{"class":1378,"line":1401},[1376,12033,12034],{},"    -- Metadados para filtro (JSONB é flexível)\n",[1376,12036,12037],{"class":1378,"line":1407},[1376,12038,12039],{},"    -- ex: {\"tipo\": \"manual\", \"produto\": \"X100\", \"data\": \"2026-01\"}\n",[1376,12041,12042],{"class":1378,"line":1413},[1376,12043,12044],{},"    metadados   JSONB DEFAULT '{}'::jsonb,\n",[1376,12046,12047],{"class":1378,"line":1419},[1376,12048,12049],{},"    -- Texto original do chunk (pedaço do documento)\n",[1376,12051,12052],{"class":1378,"line":1424},[1376,12053,12054],{},"    conteudo    TEXT NOT NULL,\n",[1376,12056,12057],{"class":1378,"line":1430},[1376,12058,12059],{},"    -- Vetor (dimensão depende do modelo de embedding)\n",[1376,12061,12062],{"class":1378,"line":1436},[1376,12063,12064],{},"    embedding   vector(1536) NOT NULL,\n",[1376,12066,12067],{"class":1378,"line":1442},[1376,12068,12069],{},"    -- Quando foi inserido\u002Fatualizado (para re-indexar só o que mudou)\n",[1376,12071,12072],{"class":1378,"line":1447},[1376,12073,12074],{},"    criado_em   TIMESTAMPTZ DEFAULT now()\n",[1376,12076,12077],{"class":1378,"line":1453},[1376,12078,12079],{},");\n",[1376,12081,12082],{"class":1378,"line":1459},[1376,12083,1398],{"emptyLinePlaceholder":898},[1376,12085,12086],{"class":1378,"line":2200},[1376,12087,12088],{},"-- Índice para filtrar por metadados (ex: só documentos de um produto)\n",[1376,12090,12091],{"class":1378,"line":2208},[1376,12092,12093],{},"CREATE INDEX idx_documentos_metadados\n",[1376,12095,12096],{"class":1378,"line":2219},[1376,12097,12098],{},"    ON documentos USING gin (metadados);\n",[1376,12100,12101],{"class":1378,"line":2227},[1376,12102,1398],{"emptyLinePlaceholder":898},[1376,12104,12105],{"class":1378,"line":2241},[1376,12106,12107],{},"-- Índice vetorial - explicado adiante em \"Otimizando\"\n",[1376,12109,12110],{"class":1378,"line":2253},[1376,12111,12112],{},"CREATE INDEX idx_documentos_embedding\n",[1376,12114,12115],{"class":1378,"line":2283},[1376,12116,12117],{},"    ON documentos USING hnsw (embedding vector_cosine_ops);\n",[86,12119,12120,12121,12124],{},"Cada linha é um ",[89,12122,12123],{},"chunk"," (pedaço) do documento original. O motivo de\nquebrar em pedaços é que o LLM tem limite de contexto e fica confuso com\ntextos muito longos. Em vez de armazenar o PDF inteiro, armazenamos\npedaços de ~1000 caracteres cada, com seus embeddings.",[12,12126,12128],{"id":12127},"gerando-e-armazenando-embeddings-gerando","Gerando e armazenando embeddings {#gerando}",[283,12130,12132],{"id":12131},"em-python-com-a-biblioteca-oficial-da-openai","Em Python, com a biblioteca oficial da OpenAI",[506,12134,12136],{"className":3439,"code":12135,"language":3441,"meta":515,"style":515},"# pip install openai psycopg[binary] tiktoken\n\nfrom openai import OpenAI\nimport psycopg\n\nclient = OpenAI()  # lê OPENAI_API_KEY do ambiente\n\ndef gerar_embedding(texto: str) -> list[float]:\n    \"\"\"Gera embedding do texto usando o modelo small da OpenAI.\n    Retorna um vetor de 1536 dimensões.\"\"\"\n    resposta = client.embeddings.create(\n        model=\"text-embedding-3-small\",\n        input=texto,\n    )\n    return resposta.data[0].embedding\n\ndef inserir_documento(conn, fonte: str, conteudo: str, metadados: dict):\n    \"\"\"Insere um chunk no banco com seu embedding.\"\"\"\n    embedding = gerar_embedding(conteudo)\n    with conn.cursor() as cur:\n        cur.execute(\n            \"\"\"\n            INSERT INTO documentos (fonte, metadados, conteudo, embedding)\n            VALUES (%s, %s, %s, %s)\n            \"\"\",\n            (fonte, metadados, conteudo, embedding),\n        )\n    conn.commit()\n",[513,12137,12138,12143,12147,12151,12155,12159,12164,12168,12173,12178,12183,12188,12193,12198,12202,12207,12211,12216,12221,12226,12231,12236,12241,12246,12251,12256,12261,12265],{"__ignoreMap":515},[1376,12139,12140],{"class":1378,"line":1379},[1376,12141,12142],{},"# pip install openai psycopg[binary] tiktoken\n",[1376,12144,12145],{"class":1378,"line":984},[1376,12146,1398],{"emptyLinePlaceholder":898},[1376,12148,12149],{"class":1378,"line":981},[1376,12150,3632],{},[1376,12152,12153],{"class":1378,"line":1395},[1376,12154,5420],{},[1376,12156,12157],{"class":1378,"line":1401},[1376,12158,1398],{"emptyLinePlaceholder":898},[1376,12160,12161],{"class":1378,"line":1407},[1376,12162,12163],{},"client = OpenAI()  # lê OPENAI_API_KEY do ambiente\n",[1376,12165,12166],{"class":1378,"line":1413},[1376,12167,1398],{"emptyLinePlaceholder":898},[1376,12169,12170],{"class":1378,"line":1419},[1376,12171,12172],{},"def gerar_embedding(texto: str) -> list[float]:\n",[1376,12174,12175],{"class":1378,"line":1424},[1376,12176,12177],{},"    \"\"\"Gera embedding do texto usando o modelo small da OpenAI.\n",[1376,12179,12180],{"class":1378,"line":1430},[1376,12181,12182],{},"    Retorna um vetor de 1536 dimensões.\"\"\"\n",[1376,12184,12185],{"class":1378,"line":1436},[1376,12186,12187],{},"    resposta = client.embeddings.create(\n",[1376,12189,12190],{"class":1378,"line":1442},[1376,12191,12192],{},"        model=\"text-embedding-3-small\",\n",[1376,12194,12195],{"class":1378,"line":1447},[1376,12196,12197],{},"        input=texto,\n",[1376,12199,12200],{"class":1378,"line":1453},[1376,12201,3748],{},[1376,12203,12204],{"class":1378,"line":1459},[1376,12205,12206],{},"    return resposta.data[0].embedding\n",[1376,12208,12209],{"class":1378,"line":2200},[1376,12210,1398],{"emptyLinePlaceholder":898},[1376,12212,12213],{"class":1378,"line":2208},[1376,12214,12215],{},"def inserir_documento(conn, fonte: str, conteudo: str, metadados: dict):\n",[1376,12217,12218],{"class":1378,"line":2219},[1376,12219,12220],{},"    \"\"\"Insere um chunk no banco com seu embedding.\"\"\"\n",[1376,12222,12223],{"class":1378,"line":2227},[1376,12224,12225],{},"    embedding = gerar_embedding(conteudo)\n",[1376,12227,12228],{"class":1378,"line":2241},[1376,12229,12230],{},"    with conn.cursor() as cur:\n",[1376,12232,12233],{"class":1378,"line":2253},[1376,12234,12235],{},"        cur.execute(\n",[1376,12237,12238],{"class":1378,"line":2283},[1376,12239,12240],{},"            \"\"\"\n",[1376,12242,12243],{"class":1378,"line":2296},[1376,12244,12245],{},"            INSERT INTO documentos (fonte, metadados, conteudo, embedding)\n",[1376,12247,12248],{"class":1378,"line":2311},[1376,12249,12250],{},"            VALUES (%s, %s, %s, %s)\n",[1376,12252,12253],{"class":1378,"line":2316},[1376,12254,12255],{},"            \"\"\",\n",[1376,12257,12258],{"class":1378,"line":2324},[1376,12259,12260],{},"            (fonte, metadados, conteudo, embedding),\n",[1376,12262,12263],{"class":1378,"line":2333},[1376,12264,6126],{},[1376,12266,12267],{"class":1378,"line":2340},[1376,12268,12269],{},"    conn.commit()\n",[86,12271,12272,12273,12276,12277,12280],{},"Comentários: ",[513,12274,12275],{},"gerar_embedding"," chama a API da OpenAI, que devolve o vetor.\n",[513,12278,12279],{},"inserir_documento"," salva no Postgres junto com metadados — esses metadados\npermitem filtrar a busca depois (ex: só buscar em manuais do produto X).",[283,12282,12284],{"id":12283},"custo-da-geração-de-embeddings","Custo da geração de embeddings",[86,12286,12287,12288,1679,12290,12293],{},"Modelo ",[513,12289,11735],{},[89,12291,12292],{},"$0,02 por 1 milhão de tokens",". Para\numa base de 10.000 documentos de ~1.000 tokens cada:",[528,12295,12296],{},[20,12297,12298,12299],{},"10M tokens × $0,02\u002F1M = ",[89,12300,12301],{},"$0,20 USD",[86,12303,12304],{},"Ou seja, indexar uma base inteira custa menos que um café. Por isso, sempre\nuse a OpenAI (ou Cohere) para embeddings, mesmo que seu LLM de resposta seja\nopen source. Não vale a pena hospedar seu próprio modelo de embedding até\nter volume enorme.",[283,12306,12308],{"id":12307},"alternativas-de-modelo-de-embedding","Alternativas de modelo de embedding",[165,12310,12311,12326],{},[168,12312,12313],{},[171,12314,12315,12317,12320,12323],{},[174,12316,8899],{},[174,12318,12319],{},"Dimensão",[174,12321,12322],{},"Custo (1M tok)",[174,12324,12325],{},"Notas",[184,12327,12328,12341,12355,12369,12382,12395],{},[171,12329,12330,12332,12335,12338],{},[189,12331,6375],{},[189,12333,12334],{},"1536",[189,12336,12337],{},"$0,02",[189,12339,12340],{},"Padrão da indústria",[171,12342,12343,12346,12349,12352],{},[189,12344,12345],{},"OpenAI text-embedding-3-large",[189,12347,12348],{},"3072",[189,12350,12351],{},"$0,13",[189,12353,12354],{},"Mais qualidade",[171,12356,12357,12360,12363,12366],{},[189,12358,12359],{},"Cohere embed-multilingual-v3",[189,12361,12362],{},"1024",[189,12364,12365],{},"$0,10",[189,12367,12368],{},"Muito bom em PT-BR",[171,12370,12371,12374,12376,12379],{},[189,12372,12373],{},"Voyage voyage-3",[189,12375,12362],{},[189,12377,12378],{},"$0,12",[189,12380,12381],{},"Estado da arte em RAG",[171,12383,12384,12387,12389,12392],{},[189,12385,12386],{},"BGE-large-zh-v1.5 (open source)",[189,12388,12362],{},[189,12390,12391],{},"$0 (self-host)",[189,12393,12394],{},"Chines\u002Finglês",[171,12396,12397,12400,12403,12405],{},[189,12398,12399],{},"SBERT multilingual (open source)",[189,12401,12402],{},"384",[189,12404,12391],{},[189,12406,12407],{},"Leve, menos preciso",[12,12409,12411],{"id":12410},"buscando-contexto-a-mágica-do-rag-buscando","Buscando contexto: a mágica do RAG {#buscando}",[86,12413,12414],{},"A função central do RAG: dada a pergunta, buscar chunks relevantes.",[506,12416,12418],{"className":3439,"code":12417,"language":3441,"meta":515,"style":515},"def buscar_contexto(conn, pergunta: str, top_k: int = 4,\n                    filtro_metadados: dict | None = None) -> list[dict]:\n    \"\"\"Busca os top_k chunks mais parecidos com a pergunta.\n    Opcionalmente filtra por metadados (ex: {\"produto\": \"X100\"}).\"\"\"\n\n    # 1. Gerar embedding da pergunta (mesmo modelo dos documentos!)\n    embedding_pergunta = gerar_embedding(pergunta)\n\n    # 2. Construir a query SQL\n    #    O operador \u003C=> é a distância-cosseno (pgvector)\n    #    Quanto menor, mais parecido\n    sql = \"\"\"\n        SELECT id, fonte, conteudo, metadados,\n               embedding \u003C=> %s::vector AS distancia\n        FROM documentos\n    \"\"\"\n    params = [embedding_pergunta]\n\n    # 3. Aplicar filtro de metadados se vier\n    if filtro_metadados:\n        clausulas = []\n        for chave, valor in filtro_metadados.items():\n            clausulas.append(\"metadados @> %s::jsonb\")\n            params.append({chave: valor})\n        sql += \" WHERE \" + \" AND \".join(clausulas)\n\n    # 4. Ordenar por distância (mais parecido primeiro) e limitar\n    sql += \" ORDER BY distancia LIMIT %s\"\n    params.append(top_k)\n\n    with conn.cursor() as cur:\n        cur.execute(sql, params)\n        colunas = [desc[0] for desc in cur.description]\n        resultados = [dict(zip(colunas, row)) for row in cur.fetchall()]\n\n    return resultados\n",[513,12419,12420,12425,12430,12435,12440,12444,12449,12454,12458,12463,12468,12473,12478,12483,12488,12493,12497,12502,12506,12511,12516,12521,12526,12531,12536,12541,12545,12550,12555,12560,12564,12568,12573,12578,12583,12587],{"__ignoreMap":515},[1376,12421,12422],{"class":1378,"line":1379},[1376,12423,12424],{},"def buscar_contexto(conn, pergunta: str, top_k: int = 4,\n",[1376,12426,12427],{"class":1378,"line":984},[1376,12428,12429],{},"                    filtro_metadados: dict | None = None) -> list[dict]:\n",[1376,12431,12432],{"class":1378,"line":981},[1376,12433,12434],{},"    \"\"\"Busca os top_k chunks mais parecidos com a pergunta.\n",[1376,12436,12437],{"class":1378,"line":1395},[1376,12438,12439],{},"    Opcionalmente filtra por metadados (ex: {\"produto\": \"X100\"}).\"\"\"\n",[1376,12441,12442],{"class":1378,"line":1401},[1376,12443,1398],{"emptyLinePlaceholder":898},[1376,12445,12446],{"class":1378,"line":1407},[1376,12447,12448],{},"    # 1. Gerar embedding da pergunta (mesmo modelo dos documentos!)\n",[1376,12450,12451],{"class":1378,"line":1413},[1376,12452,12453],{},"    embedding_pergunta = gerar_embedding(pergunta)\n",[1376,12455,12456],{"class":1378,"line":1419},[1376,12457,1398],{"emptyLinePlaceholder":898},[1376,12459,12460],{"class":1378,"line":1424},[1376,12461,12462],{},"    # 2. Construir a query SQL\n",[1376,12464,12465],{"class":1378,"line":1430},[1376,12466,12467],{},"    #    O operador \u003C=> é a distância-cosseno (pgvector)\n",[1376,12469,12470],{"class":1378,"line":1436},[1376,12471,12472],{},"    #    Quanto menor, mais parecido\n",[1376,12474,12475],{"class":1378,"line":1442},[1376,12476,12477],{},"    sql = \"\"\"\n",[1376,12479,12480],{"class":1378,"line":1447},[1376,12481,12482],{},"        SELECT id, fonte, conteudo, metadados,\n",[1376,12484,12485],{"class":1378,"line":1453},[1376,12486,12487],{},"               embedding \u003C=> %s::vector AS distancia\n",[1376,12489,12490],{"class":1378,"line":1459},[1376,12491,12492],{},"        FROM documentos\n",[1376,12494,12495],{"class":1378,"line":2200},[1376,12496,3714],{},[1376,12498,12499],{"class":1378,"line":2208},[1376,12500,12501],{},"    params = [embedding_pergunta]\n",[1376,12503,12504],{"class":1378,"line":2219},[1376,12505,1398],{"emptyLinePlaceholder":898},[1376,12507,12508],{"class":1378,"line":2227},[1376,12509,12510],{},"    # 3. Aplicar filtro de metadados se vier\n",[1376,12512,12513],{"class":1378,"line":2241},[1376,12514,12515],{},"    if filtro_metadados:\n",[1376,12517,12518],{"class":1378,"line":2253},[1376,12519,12520],{},"        clausulas = []\n",[1376,12522,12523],{"class":1378,"line":2283},[1376,12524,12525],{},"        for chave, valor in filtro_metadados.items():\n",[1376,12527,12528],{"class":1378,"line":2296},[1376,12529,12530],{},"            clausulas.append(\"metadados @> %s::jsonb\")\n",[1376,12532,12533],{"class":1378,"line":2311},[1376,12534,12535],{},"            params.append({chave: valor})\n",[1376,12537,12538],{"class":1378,"line":2316},[1376,12539,12540],{},"        sql += \" WHERE \" + \" AND \".join(clausulas)\n",[1376,12542,12543],{"class":1378,"line":2324},[1376,12544,1398],{"emptyLinePlaceholder":898},[1376,12546,12547],{"class":1378,"line":2333},[1376,12548,12549],{},"    # 4. Ordenar por distância (mais parecido primeiro) e limitar\n",[1376,12551,12552],{"class":1378,"line":2340},[1376,12553,12554],{},"    sql += \" ORDER BY distancia LIMIT %s\"\n",[1376,12556,12557],{"class":1378,"line":2351},[1376,12558,12559],{},"    params.append(top_k)\n",[1376,12561,12562],{"class":1378,"line":2362},[1376,12563,1398],{"emptyLinePlaceholder":898},[1376,12565,12566],{"class":1378,"line":2385},[1376,12567,12230],{},[1376,12569,12570],{"class":1378,"line":2396},[1376,12571,12572],{},"        cur.execute(sql, params)\n",[1376,12574,12575],{"class":1378,"line":2411},[1376,12576,12577],{},"        colunas = [desc[0] for desc in cur.description]\n",[1376,12579,12580],{"class":1378,"line":2416},[1376,12581,12582],{},"        resultados = [dict(zip(colunas, row)) for row in cur.fetchall()]\n",[1376,12584,12585],{"class":1378,"line":2422},[1376,12586,1398],{"emptyLinePlaceholder":898},[1376,12588,12589],{"class":1378,"line":2430},[1376,12590,12591],{},"    return resultados\n",[86,12593,12594,12595,12597],{},"Cada passo numerado explica o que está acontecendo: gera embedding da\npergunta, monta a query, aplica filtro opcional, ordena por distância. O\npulo do gato é o operador ",[513,12596,11761],{}," que o pgvector adiciona ao SQL.",[283,12599,12601],{"id":12600},"por-que-o-mesmo-modelo-de-embedding","Por que o mesmo modelo de embedding?",[86,12603,12604,12605,12607,12608,12611,12612],{},"Embeddings só são comparáveis se vierem do mesmo modelo. Se você gerou os\ndocumentos com ",[513,12606,11735],{}," e a pergunta com ",[513,12609,12610],{},"text-embedding-3-large",",\na busca fica sem sentido — são vetores em \"mapas\" diferentes. ",[89,12613,12614],{},"Sempre use o\nmesmo modelo para indexar e para consultar.",[12,12616,12618],{"id":12617},"prompt-e-geração-de-resposta-prompt","Prompt e geração de resposta {#prompt}",[86,12620,12621],{},"Agora que temos contexto, montamos o prompt e chamamos o LLM:",[506,12623,12625],{"className":3439,"code":12624,"language":3441,"meta":515,"style":515},"def responder(pergunta: str, contexto: list[dict]) -> str:\n    \"\"\"Gera resposta do LLM usando o contexto recuperado.\"\"\"\n    contexto_texto = \"\\n\\n---\\n\\n\".join(\n        f\"[Fonte: {c['fonte']}]\\n{c['conteudo']}\" for c in contexto\n    )\n\n    prompt = f\"\"\"Você é um assistente que responde estritamente com base\nno contexto fornecido. Se a informação não estiver no contexto, diga\nexplicitamente \"Não tenho essa informação na base de conhecimento.\"\n\nContexto:\n{contexto_texto}\n\nPergunta: {pergunta}\n\nResposta:\"\"\"\n\n    resposta = client.chat.completions.create(\n        model=\"gpt-4o-mini\",\n        temperature=0.2,  # baixa: respostas mais determinísticas\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n    )\n    return resposta.choices[0].message.content\n",[513,12626,12627,12632,12637,12642,12647,12651,12655,12660,12665,12670,12674,12678,12683,12687,12691,12695,12699,12703,12707,12711,12716,12720,12724],{"__ignoreMap":515},[1376,12628,12629],{"class":1378,"line":1379},[1376,12630,12631],{},"def responder(pergunta: str, contexto: list[dict]) -> str:\n",[1376,12633,12634],{"class":1378,"line":984},[1376,12635,12636],{},"    \"\"\"Gera resposta do LLM usando o contexto recuperado.\"\"\"\n",[1376,12638,12639],{"class":1378,"line":981},[1376,12640,12641],{},"    contexto_texto = \"\\n\\n---\\n\\n\".join(\n",[1376,12643,12644],{"class":1378,"line":1395},[1376,12645,12646],{},"        f\"[Fonte: {c['fonte']}]\\n{c['conteudo']}\" for c in contexto\n",[1376,12648,12649],{"class":1378,"line":1401},[1376,12650,3748],{},[1376,12652,12653],{"class":1378,"line":1407},[1376,12654,1398],{"emptyLinePlaceholder":898},[1376,12656,12657],{"class":1378,"line":1413},[1376,12658,12659],{},"    prompt = f\"\"\"Você é um assistente que responde estritamente com base\n",[1376,12661,12662],{"class":1378,"line":1419},[1376,12663,12664],{},"no contexto fornecido. Se a informação não estiver no contexto, diga\n",[1376,12666,12667],{"class":1378,"line":1424},[1376,12668,12669],{},"explicitamente \"Não tenho essa informação na base de conhecimento.\"\n",[1376,12671,12672],{"class":1378,"line":1430},[1376,12673,1398],{"emptyLinePlaceholder":898},[1376,12675,12676],{"class":1378,"line":1436},[1376,12677,5553],{},[1376,12679,12680],{"class":1378,"line":1442},[1376,12681,12682],{},"{contexto_texto}\n",[1376,12684,12685],{"class":1378,"line":1447},[1376,12686,1398],{"emptyLinePlaceholder":898},[1376,12688,12689],{"class":1378,"line":1453},[1376,12690,5567],{},[1376,12692,12693],{"class":1378,"line":1459},[1376,12694,1398],{"emptyLinePlaceholder":898},[1376,12696,12697],{"class":1378,"line":2200},[1376,12698,5576],{},[1376,12700,12701],{"class":1378,"line":2208},[1376,12702,1398],{"emptyLinePlaceholder":898},[1376,12704,12705],{"class":1378,"line":2219},[1376,12706,3723],{},[1376,12708,12709],{"class":1378,"line":2227},[1376,12710,3728],{},[1376,12712,12713],{"class":1378,"line":2241},[1376,12714,12715],{},"        temperature=0.2,  # baixa: respostas mais determinísticas\n",[1376,12717,12718],{"class":1378,"line":2253},[1376,12719,3733],{},[1376,12721,12722],{"class":1378,"line":2283},[1376,12723,3748],{},[1376,12725,12726],{"class":1378,"line":2296},[1376,12727,12728],{},"    return resposta.choices[0].message.content\n",[86,12730,12731,12732,12735],{},"Comentários explicam cada parte: montamos o contexto com marcadores de\nfonte (para citar depois), o prompt diz ao LLM o que fazer, e\n",[513,12733,12734],{},"temperature=0.2"," deixa a resposta mais conservadora — em RAG, você quer\nfidelidade ao contexto, não criatividade.",[283,12737,12739],{"id":12738},"template-de-prompt-recomendado","Template de prompt recomendado",[86,12741,12742],{},"Elementos de um bom prompt de RAG:",[17,12744,12745,12751,12757,12763,12773,12778,12784],{},[20,12746,12747,12750],{},[89,12748,12749],{},"Papel",": \"Você é um assistente de atendimento...\"",[20,12752,12753,12756],{},[89,12754,12755],{},"Regra de fonte",": \"Responda só com base no contexto.\"",[20,12758,12759,12762],{},[89,12760,12761],{},"Regra de honestidade",": \"Se não souber, diga que não sabe.\"",[20,12764,12765,12768,12769,12772],{},[89,12766,12767],{},"Citação",": \"Cite a fonte entre colchetes: ",[1376,12770,12771],{},"Fonte: ...","\"",[20,12774,12775,12777],{},[89,12776,8908],{},": o que foi recuperado do banco",[20,12779,12780,12783],{},[89,12781,12782],{},"Pergunta",": o que o usuário perguntou",[20,12785,12786,12789],{},[89,12787,12788],{},"Formato",": \"Responda em no máximo 3 parágrafos.\"",[12,12791,12793],{"id":12792},"pipeline-completo-em-python-pipeline","Pipeline completo em Python {#pipeline}",[86,12795,12796],{},"Juntando tudo:",[506,12798,12800],{"className":3439,"code":12799,"language":3441,"meta":515,"style":515},"# pip install openai psycopg[binary] tiktoken python-dotenv\n\nimport os\nfrom openai import OpenAI\nimport psycopg\nfrom dotenv import load_dotenv\n\nload_dotenv()\nclient = OpenAI()\n\ndef conectar():\n    return psycopg.connect(os.getenv(\"DATABASE_URL\"))\n\ndef gerar_embedding(texto: str) -> list[float]:\n    r = client.embeddings.create(model=\"text-embedding-3-small\", input=texto)\n    return r.data[0].embedding\n\ndef buscar_contexto(conn, pergunta: str, top_k: int = 4) -> list[dict]:\n    emb = gerar_embedding(pergunta)\n    with conn.cursor() as cur:\n        cur.execute(\"\"\"\n            SELECT fonte, conteudo, metadados,\n                   embedding \u003C=> %s::vector AS distancia\n            FROM documentos\n            ORDER BY distancia\n            LIMIT %s\n        \"\"\", (emb, top_k))\n        return [dict(zip([\"fonte\",\"conteudo\",\"metadados\",\"distancia\"], r))\n                for r in cur.fetchall()]\n\ndef responder(pergunta: str, contexto: list[dict]) -> str:\n    ctx = \"\\n\\n\".join(f\"[{c['fonte']}] {c['conteudo']}\" for c in contexto)\n    r = client.chat.completions.create(\n        model=\"gpt-4o-mini\",\n        temperature=0.2,\n        messages=[{\"role\":\"user\",\"content\":f\"\"\"\nResponda só com base no contexto. Se não souber, diga que não tem informação.\n\nContexto:\n{ctx}\n\nPergunta: {pergunta}\n\"\"\"}])\n    return r.choices[0].message.content\n\ndef rag(pergunta: str) -> str:\n    \"\"\"Pipeline completo: pergunta -> busca -> resposta.\"\"\"\n    with conectar() as conn:\n        contexto = buscar_contexto(conn, pergunta)\n        print(f\"Recuperados {len(contexto)} chunks\")\n        for c in contexto:\n            print(f\"  - {c['fonte']} (distância: {c['distancia']:.3f})\")\n        return responder(pergunta, contexto)\n\n# Uso\nif __name__ == \"__main__\":\n    print(rag(\"Qual a política de reembolso do produto X100?\"))\n",[513,12801,12802,12807,12811,12816,12820,12824,12829,12833,12838,12842,12846,12851,12856,12860,12864,12869,12874,12878,12883,12888,12892,12896,12901,12906,12910,12915,12920,12925,12930,12935,12939,12943,12948,12952,12956,12960,12965,12970,12974,12978,12982,12986,12990,12995,13000,13004,13009,13014,13019,13024,13029,13034,13039,13044,13048,13053,13058],{"__ignoreMap":515},[1376,12803,12804],{"class":1378,"line":1379},[1376,12805,12806],{},"# pip install openai psycopg[binary] tiktoken python-dotenv\n",[1376,12808,12809],{"class":1378,"line":984},[1376,12810,1398],{"emptyLinePlaceholder":898},[1376,12812,12813],{"class":1378,"line":981},[1376,12814,12815],{},"import os\n",[1376,12817,12818],{"class":1378,"line":1395},[1376,12819,3632],{},[1376,12821,12822],{"class":1378,"line":1401},[1376,12823,5420],{},[1376,12825,12826],{"class":1378,"line":1407},[1376,12827,12828],{},"from dotenv import load_dotenv\n",[1376,12830,12831],{"class":1378,"line":1413},[1376,12832,1398],{"emptyLinePlaceholder":898},[1376,12834,12835],{"class":1378,"line":1419},[1376,12836,12837],{},"load_dotenv()\n",[1376,12839,12840],{"class":1378,"line":1424},[1376,12841,3646],{},[1376,12843,12844],{"class":1378,"line":1430},[1376,12845,1398],{"emptyLinePlaceholder":898},[1376,12847,12848],{"class":1378,"line":1436},[1376,12849,12850],{},"def conectar():\n",[1376,12852,12853],{"class":1378,"line":1442},[1376,12854,12855],{},"    return psycopg.connect(os.getenv(\"DATABASE_URL\"))\n",[1376,12857,12858],{"class":1378,"line":1447},[1376,12859,1398],{"emptyLinePlaceholder":898},[1376,12861,12862],{"class":1378,"line":1453},[1376,12863,12172],{},[1376,12865,12866],{"class":1378,"line":1459},[1376,12867,12868],{},"    r = client.embeddings.create(model=\"text-embedding-3-small\", input=texto)\n",[1376,12870,12871],{"class":1378,"line":2200},[1376,12872,12873],{},"    return r.data[0].embedding\n",[1376,12875,12876],{"class":1378,"line":2208},[1376,12877,1398],{"emptyLinePlaceholder":898},[1376,12879,12880],{"class":1378,"line":2219},[1376,12881,12882],{},"def buscar_contexto(conn, pergunta: str, top_k: int = 4) -> list[dict]:\n",[1376,12884,12885],{"class":1378,"line":2227},[1376,12886,12887],{},"    emb = gerar_embedding(pergunta)\n",[1376,12889,12890],{"class":1378,"line":2241},[1376,12891,12230],{},[1376,12893,12894],{"class":1378,"line":2253},[1376,12895,5490],{},[1376,12897,12898],{"class":1378,"line":2283},[1376,12899,12900],{},"            SELECT fonte, conteudo, metadados,\n",[1376,12902,12903],{"class":1378,"line":2296},[1376,12904,12905],{},"                   embedding \u003C=> %s::vector AS distancia\n",[1376,12907,12908],{"class":1378,"line":2311},[1376,12909,5500],{},[1376,12911,12912],{"class":1378,"line":2316},[1376,12913,12914],{},"            ORDER BY distancia\n",[1376,12916,12917],{"class":1378,"line":2324},[1376,12918,12919],{},"            LIMIT %s\n",[1376,12921,12922],{"class":1378,"line":2333},[1376,12923,12924],{},"        \"\"\", (emb, top_k))\n",[1376,12926,12927],{"class":1378,"line":2340},[1376,12928,12929],{},"        return [dict(zip([\"fonte\",\"conteudo\",\"metadados\",\"distancia\"], r))\n",[1376,12931,12932],{"class":1378,"line":2351},[1376,12933,12934],{},"                for r in cur.fetchall()]\n",[1376,12936,12937],{"class":1378,"line":2362},[1376,12938,1398],{"emptyLinePlaceholder":898},[1376,12940,12941],{"class":1378,"line":2385},[1376,12942,12631],{},[1376,12944,12945],{"class":1378,"line":2396},[1376,12946,12947],{},"    ctx = \"\\n\\n\".join(f\"[{c['fonte']}] {c['conteudo']}\" for c in contexto)\n",[1376,12949,12950],{"class":1378,"line":2411},[1376,12951,5332],{},[1376,12953,12954],{"class":1378,"line":2416},[1376,12955,3728],{},[1376,12957,12958],{"class":1378,"line":2422},[1376,12959,5598],{},[1376,12961,12962],{"class":1378,"line":2430},[1376,12963,12964],{},"        messages=[{\"role\":\"user\",\"content\":f\"\"\"\n",[1376,12966,12967],{"class":1378,"line":2448},[1376,12968,12969],{},"Responda só com base no contexto. Se não souber, diga que não tem informação.\n",[1376,12971,12972],{"class":1378,"line":2457},[1376,12973,1398],{"emptyLinePlaceholder":898},[1376,12975,12976],{"class":1378,"line":2464},[1376,12977,5553],{},[1376,12979,12980],{"class":1378,"line":2475},[1376,12981,5558],{},[1376,12983,12984],{"class":1378,"line":2487},[1376,12985,1398],{"emptyLinePlaceholder":898},[1376,12987,12988],{"class":1378,"line":2499},[1376,12989,5567],{},[1376,12991,12992],{"class":1378,"line":2506},[1376,12993,12994],{},"\"\"\"}])\n",[1376,12996,12997],{"class":1378,"line":2517},[1376,12998,12999],{},"    return r.choices[0].message.content\n",[1376,13001,13002],{"class":1378,"line":2528},[1376,13003,1398],{"emptyLinePlaceholder":898},[1376,13005,13006],{"class":1378,"line":2539},[1376,13007,13008],{},"def rag(pergunta: str) -> str:\n",[1376,13010,13011],{"class":1378,"line":2553},[1376,13012,13013],{},"    \"\"\"Pipeline completo: pergunta -> busca -> resposta.\"\"\"\n",[1376,13015,13016],{"class":1378,"line":2564},[1376,13017,13018],{},"    with conectar() as conn:\n",[1376,13020,13021],{"class":1378,"line":2576},[1376,13022,13023],{},"        contexto = buscar_contexto(conn, pergunta)\n",[1376,13025,13026],{"class":1378,"line":2602},[1376,13027,13028],{},"        print(f\"Recuperados {len(contexto)} chunks\")\n",[1376,13030,13031],{"class":1378,"line":2607},[1376,13032,13033],{},"        for c in contexto:\n",[1376,13035,13036],{"class":1378,"line":2613},[1376,13037,13038],{},"            print(f\"  - {c['fonte']} (distância: {c['distancia']:.3f})\")\n",[1376,13040,13041],{"class":1378,"line":2620},[1376,13042,13043],{},"        return responder(pergunta, contexto)\n",[1376,13045,13046],{"class":1378,"line":2631},[1376,13047,1398],{"emptyLinePlaceholder":898},[1376,13049,13050],{"class":1378,"line":2641},[1376,13051,13052],{},"# Uso\n",[1376,13054,13055],{"class":1378,"line":2651},[1376,13056,13057],{},"if __name__ == \"__main__\":\n",[1376,13059,13060],{"class":1378,"line":2658},[1376,13061,13062],{},"    print(rag(\"Qual a política de reembolso do produto X100?\"))\n",[86,13064,13065,13066,13069,13070,13073],{},"Comentários no topo mostram como instalar; cada função tem uma\nresponsabilidade clara. O fluxo ",[513,13067,13068],{},"rag()"," é o ponto de entrada: conecta,\nbusca contexto, gera resposta. Em produção, você troca o ",[513,13071,13072],{},"__main__"," por\num endpoint FastAPI.",[12,13075,13077],{"id":13076},"otimizando-índices-e-chunking-otimizando","Otimizando: índices e chunking {#otimizando}",[283,13079,13081],{"id":13080},"índices-vetoriais","Índices vetoriais",[86,13083,13084,13085,13088],{},"Sem índice, o pgvector calcula a distância da pergunta contra ",[89,13086,13087],{},"todas"," as\nlinhas — funciona para 10k documentos, vira lento em 100k+. Existem dois\níndices principais:",[165,13090,13091,13103],{},[168,13092,13093],{},[171,13094,13095,13097,13099,13101],{},[174,13096,15],{},[174,13098,1585],{},[174,13100,5089],{},[174,13102,5093],{},[184,13104,13105,13126],{},[171,13106,13107,13110,13113,13116],{},[189,13108,13109],{},"IVFFlat",[189,13111,13112],{},"Agrupa vetores em listas, busca só nas listas próximas",[189,13114,13115],{},"Rápido, leve",[189,13117,13118,13119,1327,13122,13125],{},"Precisa de tuning (",[513,13120,13121],{},"lists",[513,13123,13124],{},"probes",")",[171,13127,13128,13131,13134,13137],{},[189,13129,13130],{},"HNSW",[189,13132,13133],{},"Grafo hierárquico de vizinhos próximos",[189,13135,13136],{},"Qualidade alta, build rápido",[189,13138,13139],{},"Ocupa mais memória",[86,13141,13142,13144,13145,13147],{},[89,13143,7262],{},": use ",[89,13146,13130],{}," para a maioria dos casos. Configuração\npadrão já funciona bem:",[506,13149,13151],{"className":11777,"code":13150,"language":11779,"meta":515,"style":515},"CREATE INDEX idx_documentos_embedding\n    ON documentos USING hnsw (embedding vector_cosine_ops)\n    WITH (m = 16, ef_construction = 64);\n",[513,13152,13153,13157,13162],{"__ignoreMap":515},[1376,13154,13155],{"class":1378,"line":1379},[1376,13156,12112],{},[1376,13158,13159],{"class":1378,"line":984},[1376,13160,13161],{},"    ON documentos USING hnsw (embedding vector_cosine_ops)\n",[1376,13163,13164],{"class":1378,"line":981},[1376,13165,13166],{},"    WITH (m = 16, ef_construction = 64);\n",[86,13168,13169,13172,13173,13176,13177,13180],{},[513,13170,13171],{},"m = 16"," é o número máximo de conexões por nó (mais = mais qualidade, mais\nmemória); ",[513,13174,13175],{},"ef_construction = 64"," controla a qualidade da construção do\níndice. Para queries, ajuste ",[513,13178,13179],{},"ef_search"," (padrão 40) — mais alto, mais\nprecisão e mais lento.",[283,13182,13184],{"id":13183},"chunking-o-tamanho-do-pedaço","Chunking: o tamanho do pedaço",[86,13186,13187,13188,4171],{},"A decisão mais impactante na qualidade do RAG é ",[89,13189,13190],{},"como cortar os\ndocumentos",[165,13192,13193,13206],{},[168,13194,13195],{},[171,13196,13197,13200,13202,13204],{},[174,13198,13199],{},"chunk_size",[174,13201,1588],{},[174,13203,5089],{},[174,13205,5093],{},[184,13207,13208,13221,13234],{},[171,13209,13210,13213,13216,13218],{},[189,13211,13212],{},"256–512",[189,13214,13215],{},"Documentos curtos, FAQ",[189,13217,3268],{},[189,13219,13220],{},"Perde contexto amplo",[171,13222,13223,13226,13229,13232],{},[189,13224,13225],{},"800–1200",[189,13227,13228],{},"Padrão, maioria dos casos",[189,13230,13231],{},"Equilíbrio",[189,13233,9749],{},[171,13235,13236,13239,13242,13245],{},[189,13237,13238],{},"1500–3000",[189,13240,13241],{},"Documentos longos, técnicos",[189,13243,13244],{},"Contexto amplo",[189,13246,13247],{},"Pode diluir relevância",[86,13249,13250],{},"Regras práticas:",[528,13252,13253,13263,13266,13269],{},[20,13254,13255,13256,13259,13260,13262],{},"Sempre use ",[513,13257,13258],{},"chunk_overlap"," de 10–20% do ",[513,13261,13199],{}," para não cortar\nideias no meio",[20,13264,13265],{},"Para PDFs com seções, quebre por cabeçalho (H1, H2) em vez de tamanho\nfixo — preserve a estrutura",[20,13267,13268],{},"Para código, quebre por função\u002Fclasse",[20,13270,13271],{},"Para manuais, quebre por seção do sumário",[506,13273,13275],{"className":3439,"code":13274,"language":3441,"meta":515,"style":515},"from langchain_text_splitters import RecursiveCharacterTextSplitter\n\nsplitter = RecursiveCharacterTextSplitter(\n    chunk_size=1000,\n    chunk_overlap=200,\n    separators=[\"\\n## \", \"\\n### \", \"\\n\\n\", \"\\n\", \". \", \" \"],\n)\n# Tenta quebrar primeiro por cabeçalhos H2, depois H3,\n# depois parágrafos, depois linhas, etc. - preserva estrutura.\n",[513,13276,13277,13281,13285,13289,13293,13297,13302,13306,13311],{"__ignoreMap":515},[1376,13278,13279],{"class":1378,"line":1379},[1376,13280,10764],{},[1376,13282,13283],{"class":1378,"line":984},[1376,13284,1398],{"emptyLinePlaceholder":898},[1376,13286,13287],{"class":1378,"line":981},[1376,13288,10821],{},[1376,13290,13291],{"class":1378,"line":1395},[1376,13292,11197],{},[1376,13294,13295],{"class":1378,"line":1401},[1376,13296,11202],{},[1376,13298,13299],{"class":1378,"line":1407},[1376,13300,13301],{},"    separators=[\"\\n## \", \"\\n### \", \"\\n\\n\", \"\\n\", \". \", \" \"],\n",[1376,13303,13304],{"class":1378,"line":1413},[1376,13305,7575],{},[1376,13307,13308],{"class":1378,"line":1419},[1376,13309,13310],{},"# Tenta quebrar primeiro por cabeçalhos H2, depois H3,\n",[1376,13312,13313],{"class":1378,"line":1424},[1376,13314,13315],{},"# depois parágrafos, depois linhas, etc. - preserva estrutura.\n",[12,13317,13319],{"id":13318},"rag-avançado-reranking-e-filtros-avancado","RAG avançado: reranking e filtros {#avancado}",[283,13321,13323],{"id":13322},"reranking","Reranking",[86,13325,13326],{},"Recuperar por similaridade de embeddings é rápido, mas impreciso — às vezes\nos 4 chunks mais \"parecidos\" não são os mais úteis. Reranking resolve isso:",[17,13328,13329,13332,13335],{},[20,13330,13331],{},"Recupere 20 chunks por similaridade (barato, aproximado)",[20,13333,13334],{},"Use um modelo de reranking (ex: Cohere Rerank, BGE-reranker) para\nreordenar os 20 por relevância real",[20,13336,13337],{},"Use só os top 4 do reranking no prompt",[86,13339,13340],{},"Custo: a chamada de reranking é pequena (20 documentos) e barata. Qualidade\nsobe significativamente — especialmente em bases grandes.",[506,13342,13344],{"className":3439,"code":13343,"language":3441,"meta":515,"style":515},"# pip install cohere\nimport cohere\nco = cohere.Client(os.getenv(\"COHERE_API_KEY\"))\n\ndef rerank(pergunta: str, documentos: list[dict], top_n: int = 4) -> list[dict]:\n    \"\"\"Reordena documentos por relevância real, não só similaridade vetorial.\"\"\"\n    r = co.rerank(\n        model=\"rerank-multilingual-v3.0\",\n        query=pergunta,\n        documents=[d[\"conteudo\"] for d in documentos],\n        top_n=top_n,\n    )\n    return [documentos[doc.index] for doc in r.results]\n",[513,13345,13346,13351,13356,13361,13365,13370,13375,13380,13385,13390,13395,13400,13404],{"__ignoreMap":515},[1376,13347,13348],{"class":1378,"line":1379},[1376,13349,13350],{},"# pip install cohere\n",[1376,13352,13353],{"class":1378,"line":984},[1376,13354,13355],{},"import cohere\n",[1376,13357,13358],{"class":1378,"line":981},[1376,13359,13360],{},"co = cohere.Client(os.getenv(\"COHERE_API_KEY\"))\n",[1376,13362,13363],{"class":1378,"line":1395},[1376,13364,1398],{"emptyLinePlaceholder":898},[1376,13366,13367],{"class":1378,"line":1401},[1376,13368,13369],{},"def rerank(pergunta: str, documentos: list[dict], top_n: int = 4) -> list[dict]:\n",[1376,13371,13372],{"class":1378,"line":1407},[1376,13373,13374],{},"    \"\"\"Reordena documentos por relevância real, não só similaridade vetorial.\"\"\"\n",[1376,13376,13377],{"class":1378,"line":1413},[1376,13378,13379],{},"    r = co.rerank(\n",[1376,13381,13382],{"class":1378,"line":1419},[1376,13383,13384],{},"        model=\"rerank-multilingual-v3.0\",\n",[1376,13386,13387],{"class":1378,"line":1424},[1376,13388,13389],{},"        query=pergunta,\n",[1376,13391,13392],{"class":1378,"line":1430},[1376,13393,13394],{},"        documents=[d[\"conteudo\"] for d in documentos],\n",[1376,13396,13397],{"class":1378,"line":1436},[1376,13398,13399],{},"        top_n=top_n,\n",[1376,13401,13402],{"class":1378,"line":1442},[1376,13403,3748],{},[1376,13405,13406],{"class":1378,"line":1447},[1376,13407,13408],{},"    return [documentos[doc.index] for doc in r.results]\n",[283,13410,13412],{"id":13411},"filtros-por-metadados","Filtros por metadados",[86,13414,13415],{},"Já vimos filtro na query SQL. Para casos comuns:",[528,13417,13418,13427,13436,13445],{},[20,13419,13420,1679,13423,13426],{},[89,13421,13422],{},"Por produto",[513,13424,13425],{},"{\"produto\": \"X100\"}"," — só busca no manual daquele\nproduto",[20,13428,13429,1679,13432,13435],{},[89,13430,13431],{},"Por tipo",[513,13433,13434],{},"{\"tipo\": \"faq\"}"," — só em FAQs, ignora manuais longos",[20,13437,13438,1679,13441,13444],{},[89,13439,13440],{},"Por data",[513,13442,13443],{},"{\"data\": {\"$gte\": \"2026-01-01\"}}"," — só documentos recentes",[20,13446,13447,1679,13450,13453],{},[89,13448,13449],{},"Por idioma",[513,13451,13452],{},"{\"idioma\": \"pt-BR\"}"," — só em português",[86,13455,13456,13457,13460],{},"Filtrar ",[89,13458,13459],{},"antes"," da busca vetorial reduz o conjunto e melhora precisão.\nSempre que o usuário puder especificar escopo (produto, período, categoria),\nuse isso como filtro.",[12,13462,13464],{"id":13463},"avaliando-qualidade-do-rag-avaliando","Avaliando qualidade do RAG {#avaliando}",[86,13466,13467],{},"RAG sem avaliação é fé. Você não sabe se está bom ou se só \"parece bom\".\nDuas métricas centrais:",[283,13469,13471],{"id":13470},"_1-recallk","1. Recall@k",[86,13473,13474],{},"Dos chunks que deveriam estar no top-k, quantos aparecem? Para medir, crie\num conjunto de ~50 perguntas com a resposta esperada e os documentos que\ndeveriam ser recuperados. Rode o RAG e compare.",[283,13476,13478],{"id":13477},"_2-fidelidade-da-resposta-faithfulness","2. Fidelidade da resposta (faithfulness)",[86,13480,13481,13482,5655,13485,13488],{},"A resposta do LLM está apoiada no contexto recuperado, ou ele alucinou?\nFerramentas como ",[89,13483,13484],{},"Ragas",[89,13486,13487],{},"TruLens"," automatizam essa avaliação:",[506,13490,13492],{"className":3439,"code":13491,"language":3441,"meta":515,"style":515},"# pip install ragas\nfrom ragas.metrics import faithfulness, answer_relevancy\nfrom ragas import evaluate\nfrom datasets import Dataset\n\n# dados: perguntas, respostas geradas, contextos recuperados, respostas esperadas\ndados = Dataset.from_dict({\n    \"question\": [...],\n    \"answer\": [...],         # o que o LLM respondeu\n    \"contexts\": [...],       # o que foi recuperado\n    \"ground_truth\": [...],   # a resposta correta\n})\nresultado = evaluate(dados, metrics=[faithfulness, answer_relevancy])\nprint(resultado)\n",[513,13493,13494,13499,13504,13509,13514,13518,13523,13528,13533,13538,13543,13548,13552,13557],{"__ignoreMap":515},[1376,13495,13496],{"class":1378,"line":1379},[1376,13497,13498],{},"# pip install ragas\n",[1376,13500,13501],{"class":1378,"line":984},[1376,13502,13503],{},"from ragas.metrics import faithfulness, answer_relevancy\n",[1376,13505,13506],{"class":1378,"line":981},[1376,13507,13508],{},"from ragas import evaluate\n",[1376,13510,13511],{"class":1378,"line":1395},[1376,13512,13513],{},"from datasets import Dataset\n",[1376,13515,13516],{"class":1378,"line":1401},[1376,13517,1398],{"emptyLinePlaceholder":898},[1376,13519,13520],{"class":1378,"line":1407},[1376,13521,13522],{},"# dados: perguntas, respostas geradas, contextos recuperados, respostas esperadas\n",[1376,13524,13525],{"class":1378,"line":1413},[1376,13526,13527],{},"dados = Dataset.from_dict({\n",[1376,13529,13530],{"class":1378,"line":1419},[1376,13531,13532],{},"    \"question\": [...],\n",[1376,13534,13535],{"class":1378,"line":1424},[1376,13536,13537],{},"    \"answer\": [...],         # o que o LLM respondeu\n",[1376,13539,13540],{"class":1378,"line":1430},[1376,13541,13542],{},"    \"contexts\": [...],       # o que foi recuperado\n",[1376,13544,13545],{"class":1378,"line":1436},[1376,13546,13547],{},"    \"ground_truth\": [...],   # a resposta correta\n",[1376,13549,13550],{"class":1378,"line":1442},[1376,13551,8345],{},[1376,13553,13554],{"class":1378,"line":1447},[1376,13555,13556],{},"resultado = evaluate(dados, metrics=[faithfulness, answer_relevancy])\n",[1376,13558,13559],{"class":1378,"line":1453},[1376,13560,3805],{},[86,13562,13563,13566,13567,13570],{},[513,13564,13565],{},"faithfulness"," mede o quanto a resposta está apoiada no contexto;\n",[513,13568,13569],{},"answer_relevancy"," mede o quanto ela responde à pergunta. Ideal: ambos\nacima de 0,8.",[283,13572,13574],{"id":13573},"conjunto-de-avaliação","Conjunto de avaliação",[86,13576,13577],{},"Mantenha um conjunto de 30–100 pares pergunta\u002Fresposta esperada. Rode a\navaliação a cada mudança (modelo, prompt, chunking). Sem isso, qualquer\najuste é tiro no escuro.",[12,13579,820],{"id":819},[17,13581,13582,13588,13594,13600,13606,13612,13621,13627,13637,13643],{},[20,13583,13584,13587],{},[89,13585,13586],{},"Misturar modelos de embedding",": indexar com um modelo, consultar com\noutro. Resultado: busca sem sentido. Sempre use o mesmo modelo.",[20,13589,13590,13593],{},[89,13591,13592],{},"Chunk muito grande ou muito pequeno",": 5000 caracteres dilui relevância;\n50 caracteres perde contexto. Teste 800–1200 como ponto de partida.",[20,13595,13596,13599],{},[89,13597,13598],{},"Não usar índice vetorial",": com 50k documentos, busca sem HNSW vira\nsegundo. Crie o índice assim que a tabela crescer.",[20,13601,13602,13605],{},[89,13603,13604],{},"Esquecer do overlap no chunking",": cortar uma ideia no meio faz o LLM\nperder contexto. Use 10–20% de overlap.",[20,13607,13608,13611],{},[89,13609,13610],{},"Não filtrar por metadados",": deixar o usuário perguntar sobre \"produto\nX\" e o RAG buscar em todos os produtos. Use a intenção do usuário como\nfiltro.",[20,13613,13614,1679,13617,13620],{},[89,13615,13616],{},"Temperature alta em RAG",[513,13618,13619],{},"temperature=0.7"," faz o LLM \"criar\" além do\ncontexto. Em RAG, use 0.1–0.3.",[20,13622,13623,13626],{},[89,13624,13625],{},"Não ter avaliação automatizada",": acham que está bom, até cliente\nreclamar. Monte conjunto de teste desde o início.",[20,13628,13629,13632,13633,13636],{},[89,13630,13631],{},"Não atualizar embeddings",": documento mudou, embedding não. Resposta\nfica desatualizada. Use ",[513,13634,13635],{},"updated_at"," para reindexar só o que mudou.",[20,13638,13639,13642],{},[89,13640,13641],{},"Mandar contexto demais",": 20 chunks de 2000 tokens cada = 40k tokens\nde input, caro e confuso. Top-k de 3–6 é o sweet spot.",[20,13644,13645,13648],{},[89,13646,13647],{},"Ignorar a fonte",": RAG sem citação de fonte vira \"diz-que-disse\". Sempre\nmostre de onde veio a informação — aumenta confiança e permite auditoria.",[968,13650],{},[86,13652,13653,13656,13657,13659],{},[89,13654,13655],{},"Quer um RAG funcionando com os dados da sua empresa?"," A Inicialize Tec\nimplementa do esquema do banco ao pipeline completo, com avaliação de\nqualidade e monitoramento. ",[23,13658,6840],{"href":978}," e mostramos como fica\ncom a sua base.",[2883,13661,13662],{},"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":515,"searchDepth":981,"depth":981,"links":13664},[13665,13666,13667,13670,13674,13679,13680,13685,13688,13691,13692,13696,13700,13705],{"id":14,"depth":984,"text":15},{"id":11532,"depth":984,"text":11533},{"id":11586,"depth":984,"text":11587,"children":13668},[13669],{"id":11609,"depth":981,"text":11610},{"id":11720,"depth":984,"text":11721,"children":13671},[13672,13673],{"id":7371,"depth":981,"text":6372},{"id":11747,"depth":981,"text":11748},{"id":11819,"depth":984,"text":11820,"children":13675},[13676,13677,13678],{"id":11823,"depth":981,"text":11824},{"id":11905,"depth":981,"text":11906},{"id":11921,"depth":981,"text":11922},{"id":12000,"depth":984,"text":12001},{"id":12127,"depth":984,"text":12128,"children":13681},[13682,13683,13684],{"id":12131,"depth":981,"text":12132},{"id":12283,"depth":981,"text":12284},{"id":12307,"depth":981,"text":12308},{"id":12410,"depth":984,"text":12411,"children":13686},[13687],{"id":12600,"depth":981,"text":12601},{"id":12617,"depth":984,"text":12618,"children":13689},[13690],{"id":12738,"depth":981,"text":12739},{"id":12792,"depth":984,"text":12793},{"id":13076,"depth":984,"text":13077,"children":13693},[13694,13695],{"id":13080,"depth":981,"text":13081},{"id":13183,"depth":981,"text":13184},{"id":13318,"depth":984,"text":13319,"children":13697},[13698,13699],{"id":13322,"depth":981,"text":13323},{"id":13411,"depth":981,"text":13412},{"id":13463,"depth":984,"text":13464,"children":13701},[13702,13703,13704],{"id":13470,"depth":981,"text":13471},{"id":13477,"depth":981,"text":13478},{"id":13573,"depth":981,"text":13574},{"id":819,"depth":984,"text":820},"Aprenda a construir um sistema de RAG (Retrieval-Augmented Generation) usando PostgreSQL com pgvector — IA que responde com base nos dados da sua empresa.",[4972,6324,13708,7371,4965,13709,11592],"PostgreSQL","IA com dados privados",{},"\u002Fia\u002Frag-com-postgresql-pgvector",{"title":11448,"description":13706},"rag-com-postgresql-pgvector","ia\u002Frag-com-postgresql-pgvector","l_jhoCoEv6kbSvWYB9ftITUR9QCQSY16cK9Q7rSnhZ0",{"id":13717,"title":13718,"author":7,"body":13719,"category":16024,"date":1028,"description":16025,"extension":1030,"keywords":16026,"meta":16031,"navigation":898,"path":16032,"seo":16033,"slug":16034,"stem":16035,"updated":1028,"__hash__":16036},"content\u002Finfraestrutura\u002Fkubernetes-para-iniciantes.md","Kubernetes para iniciantes: guia 2026",{"type":9,"value":13720,"toc":15980},[13721,13723,13783,13787,13790,13793,13798,13813,13816,13823,13827,13830,13865,13869,13872,13878,13883,13886,13890,13983,13994,14010,14055,14059,14062,14079,14082,14088,14120,14124,14196,14200,14206,14212,14216,14219,14245,14249,14252,14272,14278,14282,14288,14295,14301,14306,14310,14330,14462,14469,14473,14479,14484,14489,14836,14840,14843,14865,14870,14874,14877,14887,14892,14896,14963,15074,15078,15084,15104,15108,15111,15115,15156,15160,15171,15244,15248,15297,15301,15308,15340,15344,15347,15405,15408,15429,15433,15440,15445,15621,15624,15636,15640,15695,15698,15702,15710,15717,15730,15734,15739,15752,15756,15761,15774,15778,15783,15792,15868,15872,15877,15886,15967,15969,15977],[12,13722,15],{"id":14},[17,13724,13725,13730,13736,13742,13748,13754,13760,13766,13772,13778],{},[20,13726,13727],{},[23,13728,13729],{"href":25},"O que é Kubernetes, afinal?",[20,13731,13732],{},[23,13733,13735],{"href":13734},"#containers","Containers: a base de tudo",[20,13737,13738],{},[23,13739,13741],{"href":13740},"#por-que-orquestracao","Por que você precisa de orquestração",[20,13743,13744],{},[23,13745,13747],{"href":13746},"#anatomia","Anatomia de um cluster",[20,13749,13750],{},[23,13751,13753],{"href":13752},"#pods","Pods: a menor unidade",[20,13755,13756],{},[23,13757,13759],{"href":13758},"#deployments","Deployments: garantir o estado desejado",[20,13761,13762],{},[23,13763,13765],{"href":13764},"#services","Services: como encontrar seus pods",[20,13767,13768],{},[23,13769,13771],{"href":13770},"#primeira-app","Subindo sua primeira aplicação",[20,13773,13774],{},[23,13775,13777],{"href":13776},"#auto-scaling","Auto-scaling: crescer sob demanda",[20,13779,13780],{},[23,13781,74],{"href":13782},"#erros-comuns",[12,13784,13786],{"id":13785},"o-que-é-kubernetes-afinal-o-que-e","O que é Kubernetes, afinal? {#o-que-e}",[86,13788,13789],{},"Imagine um porto de navios. Chegam milhares de contêineres por dia — cada um\ncom um conteúdo diferente, um destino diferente, um peso diferente. Sem alguém\ncoordenando, vira caos: contêineres empilhados no lugar errado, guindastes\nociosos enquanto outros estão sobrecarregados, cargas perecíveis estragando\nenquanto esperam.",[86,13791,13792],{},"O gerente do porto é quem organiza tudo: decide qual contêiner vai para qual\nnavio, redireciona quando um guindaste quebra, escala mais caminhões no horário\nde pico. Ele não carrega os contêineres — ele orquestra quem carrega.",[86,13794,13795],{},[89,13796,13797],{},"Kubernetes é esse gerente de porto, mas para software.",[86,13799,13800,13801,13804,13805,13808,13809,13812],{},"Kubernetes (pronuncia-se ",[501,13802,13803],{},"koo-ber-NET-ees",") é uma plataforma de código aberto\nque automatiza a implantação, o escalonamento e a operação de aplicações em\n",[89,13806,13807],{},"containers",". Ele foi criado pelo Google em 2014, baseado em um sistema\ninterno chamado Borg que rodava os serviços do Google há mais de uma década.\nHoje é mantido pela ",[89,13810,13811],{},"CNCF"," (Cloud Native Computing Foundation — uma fundação\nque cuida de projetos de software \"cloud native\", ou seja, pensados para rodar\nna nuvem desde o início).",[86,13814,13815],{},"A palavra Kubernetes vem do grego e significa \"timoneiro\" — quem governa o\nleme do navio. Por isso o logo é uma roda de navio.",[86,13817,13818,13819,13822],{},"A abreviação ",[89,13820,13821],{},"k8s"," é comum: o \"8\" representa as oito letras entre o \"k\"\ninicial e o \"s\" final (k-ubernete-s).",[283,13824,13826],{"id":13825},"o-que-kubernetes-não-é","O que Kubernetes NÃO é",[86,13828,13829],{},"É importante alinhar expectativas desde já:",[528,13831,13832,13849,13859],{},[20,13833,13834,13837,13838,13841,13842,5655,13845,13848],{},[89,13835,13836],{},"Não é um sistema de containers",": ele ",[501,13839,13840],{},"gerencia"," containers, mas quem os\ncria e executa é um \"container runtime\" (motor de execução) como\n",[89,13843,13844],{},"containerd",[89,13846,13847],{},"Docker",". Kubernetes é a camada acima.",[20,13850,13851,13854,13855,13858],{},[89,13852,13853],{},"Não é uma nuvem",": Kubernetes roda ",[501,13856,13857],{},"sobre"," uma nuvem (AWS, Google Cloud,\nAzure) ou até em seus próprios servidores. Ele não substitui a infraestrutura\n— ele a orquestra.",[20,13860,13861,13864],{},[89,13862,13863],{},"Não é mágica",": ele automatiza muito, mas exige configuração, conhecimento\ne manutenção. Não é \"instalar e esquecer\".",[12,13866,13868],{"id":13867},"containers-a-base-de-tudo-containers","Containers: a base de tudo {#containers}",[86,13870,13871],{},"Antes de entender Kubernetes, você precisa entender containers.",[86,13873,13874,13877],{},[89,13875,13876],{},"Container"," é uma forma de empacotar uma aplicação junto com tudo o que ela\nprecisa para rodar — código, bibliotecas, configurações, variáveis de ambiente\n— em uma unidade portátil e isolada.",[86,13879,13880,13882],{},[89,13881,3011],{},": pense em um container de navio. Não importa o que tem dentro\n(eletrodomésticos, roupas, comida), o container tem um formato padrão. O navio\nnão precisa saber o que tem dentro — ele só precisa saber as dimensões. Qualquer\nnavio carrega qualquer container.",[86,13884,13885],{},"Da mesma forma, um container de software roda em qualquer servidor que tenha\num container runtime. O servidor não precisa ter as bibliotecas da sua\naplicação instaladas — tudo vem dentro do container.",[283,13887,13889],{"id":13888},"container-vs-máquina-virtual","Container vs máquina virtual",[165,13891,13892,13904],{},[168,13893,13894],{},[171,13895,13896,13899,13902],{},[174,13897,13898],{},"Aspecto",[174,13900,13901],{},"Máquina Virtual (VM)",[174,13903,13876],{},[184,13905,13906,13919,13932,13945,13958,13971],{},[171,13907,13908,13913,13916],{},[189,13909,13910],{},[89,13911,13912],{},"O que inclui",[189,13914,13915],{},"Sistema operacional completo + app",[189,13917,13918],{},"Só a app e dependências",[171,13920,13921,13926,13929],{},[189,13922,13923],{},[89,13924,13925],{},"Tamanho",[189,13927,13928],{},"Gigabytes",[189,13930,13931],{},"Megabytes",[171,13933,13934,13939,13942],{},[189,13935,13936],{},[89,13937,13938],{},"Tempo de inicialização",[189,13940,13941],{},"Minutos",[189,13943,13944],{},"Segundos",[171,13946,13947,13952,13955],{},[189,13948,13949],{},[89,13950,13951],{},"Isolamento",[189,13953,13954],{},"Total (cada VM tem seu SO)",[189,13956,13957],{},"Compartilha o kernel do host",[171,13959,13960,13965,13968],{},[189,13961,13962],{},[89,13963,13964],{},"Densidade",[189,13966,13967],{},"Poucas VMs por servidor",[189,13969,13970],{},"Centenas de containers por servidor",[171,13972,13973,13977,13980],{},[189,13974,13975],{},[89,13976,1588],{},[189,13978,13979],{},"Isolamento máximo, SO diferente",[189,13981,13982],{},"Aplicações na mesma linguagem\u002FSO",[86,13984,13985,13986,13989,13990,13993],{},"A diferença fundamental: uma VM virtualiza o ",[89,13987,13988],{},"hardware"," (cada VM roda um\nsistema operacional completo), enquanto um container virtualiza o ",[89,13991,13992],{},"sistema\noperacional"," (vários containers compartilham o mesmo kernel, o núcleo do SO).",[86,13995,13996,13998,13999,14001,14002,14005,14006,14009],{},[89,13997,13847],{}," é o container runtime mais conhecido. Você escreve um\n",[513,14000,1347],{}," (receita de como montar o container), executa ",[513,14003,14004],{},"docker build"," e\nobtém uma ",[89,14007,14008],{},"imagem"," — um pacote imutável. Ao executar essa imagem, você tem\num container rodando.",[506,14011,14013],{"className":1370,"code":14012,"language":1372,"meta":515,"style":515},"# Dockerfile: receita para criar a imagem da aplicação\nFROM node:20-alpine          # Começa com uma imagem base (Node.js leve)\nWORKDIR \u002Fapp                 # Define o diretório de trabalho dentro do container\nCOPY package.json .          # Copia o arquivo de dependências\nRUN npm install              # Instala as dependências (roda só no build)\nCOPY . .                     # Copia o resto do código\nEXPOSE 3000                  # Documenta que a app usa a porta 3000\nCMD [\"node\", \"server.js\"]    # Comando executado quando o container inicia\n",[513,14014,14015,14020,14025,14030,14035,14040,14045,14050],{"__ignoreMap":515},[1376,14016,14017],{"class":1378,"line":1379},[1376,14018,14019],{},"# Dockerfile: receita para criar a imagem da aplicação\n",[1376,14021,14022],{"class":1378,"line":984},[1376,14023,14024],{},"FROM node:20-alpine          # Começa com uma imagem base (Node.js leve)\n",[1376,14026,14027],{"class":1378,"line":981},[1376,14028,14029],{},"WORKDIR \u002Fapp                 # Define o diretório de trabalho dentro do container\n",[1376,14031,14032],{"class":1378,"line":1395},[1376,14033,14034],{},"COPY package.json .          # Copia o arquivo de dependências\n",[1376,14036,14037],{"class":1378,"line":1401},[1376,14038,14039],{},"RUN npm install              # Instala as dependências (roda só no build)\n",[1376,14041,14042],{"class":1378,"line":1407},[1376,14043,14044],{},"COPY . .                     # Copia o resto do código\n",[1376,14046,14047],{"class":1378,"line":1413},[1376,14048,14049],{},"EXPOSE 3000                  # Documenta que a app usa a porta 3000\n",[1376,14051,14052],{"class":1378,"line":1419},[1376,14053,14054],{},"CMD [\"node\", \"server.js\"]    # Comando executado quando o container inicia\n",[12,14056,14058],{"id":14057},"por-que-você-precisa-de-orquestração-por-que-orquestracao","Por que você precisa de orquestração {#por-que-orquestracao}",[86,14060,14061],{},"Imagine que sua aplicação faz sucesso. Você tinha 1 container rodando, agora\nprecisa de 10. Sem orquestração, você faz tudo manualmente:",[17,14063,14064,14067,14070,14073,14076],{},[20,14065,14066],{},"Em quais servidores colocar cada container?",[20,14068,14069],{},"Se um servidor cair, como mover os containers para outro?",[20,14071,14072],{},"Se o tráfego crescer às 14h, como adicionar containers automaticamente?",[20,14074,14075],{},"Se um container travar, quem reinicia?",[20,14077,14078],{},"Como atualizar a versão sem derrubar o serviço?",[86,14080,14081],{},"Fazer isso manualmente com 10 containers já é trabalhoso. Com 100, é\nimpossível. Com 1.000, é uma pesadelo.",[86,14083,14084,14087],{},[89,14085,14086],{},"É aqui que entra o orquestrador."," Kubernetes resolve todos esses problemas\nautomaticamente:",[528,14089,14090,14096,14102,14108,14114],{},[20,14091,14092,14095],{},[89,14093,14094],{},"Escalonamento",": adicionar ou remover containers conforme a demanda",[20,14097,14098,14101],{},[89,14099,14100],{},"Self-healing"," (auto-cura): reiniciar containers que travaram",[20,14103,14104,14107],{},[89,14105,14106],{},"Balanceamento de carga",": distribuir tráfego entre os containers",[20,14109,14110,14113],{},[89,14111,14112],{},"Rolling updates",": atualizar a versão sem downtime (tempo fora do ar)",[20,14115,14116,14119],{},[89,14117,14118],{},"Service discovery",": containers se encontram automaticamente, sem IPs fixos",[283,14121,14123],{"id":14122},"sem-kubernetes-vs-com-kubernetes","Sem Kubernetes vs com Kubernetes",[165,14125,14126,14139],{},[168,14127,14128],{},[171,14129,14130,14133,14136],{},[174,14131,14132],{},"Situação",[174,14134,14135],{},"Sem orquestração",[174,14137,14138],{},"Com Kubernetes",[184,14140,14141,14152,14163,14174,14185],{},[171,14142,14143,14146,14149],{},[189,14144,14145],{},"Container trava",[189,14147,14148],{},"Você reinicia manualmente",[189,14150,14151],{},"Reinicia sozinho em segundos",[171,14153,14154,14157,14160],{},[189,14155,14156],{},"Tráfego aumenta",[189,14158,14159],{},"Você adiciona servidores manualmente",[189,14161,14162],{},"Auto-scaling adiciona pods",[171,14164,14165,14168,14171],{},[189,14166,14167],{},"Servidor cai",[189,14169,14170],{},"Containers perdidos, app fora do ar",[189,14172,14173],{},"Pods realocados para outro servidor",[171,14175,14176,14179,14182],{},[189,14177,14178],{},"Deploy de nova versão",[189,14180,14181],{},"Derruba a app, sobe a nova",[189,14183,14184],{},"Atualiza gradualmente, sem downtime",[171,14186,14187,14190,14193],{},[189,14188,14189],{},"Descoberta de serviços",[189,14191,14192],{},"Configura IPs manualmente",[189,14194,14195],{},"DNS interno resolve automaticamente",[12,14197,14199],{"id":14198},"anatomia-de-um-cluster-anatomia","Anatomia de um cluster {#anatomia}",[86,14201,1160,14202,14205],{},[89,14203,14204],{},"cluster"," Kubernetes é um conjunto de máquinas que rodam containers\ngerenciados pelo Kubernetes. Ele tem duas partes principais:",[506,14207,14210],{"className":14208,"code":14209,"language":511},[509],"┌─────────────────────────────────────────────────────┐\n│                   CLUSTER K8S                        │\n│                                                      │\n│  ┌─────────────────┐   ┌──────────────────────┐     │\n│  │  CONTROL PLANE  │   │    WORKER NODES      │     │\n│  │  (o cérebro)    │   │    (os músculos)     │     │\n│  │                 │   │                      │     │\n│  │  • API Server   │   │  Node 1  Node 2      │     │\n│  │  • Scheduler    │   │  [pods]  [pods]      │     │\n│  │  • etcd         │   │  Node 3              │     │\n│  │  • Controller   │   │  [pods]              │     │\n│  └────────┬────────┘   └──────────▲───────────┘     │\n│           │                       │                  │\n│           └─────── controla ──────┘                  │\n└─────────────────────────────────────────────────────┘\n",[513,14211,14209],{"__ignoreMap":515},[283,14213,14215],{"id":14214},"control-plane-plano-de-controle-o-cérebro","Control Plane (plano de controle) — o cérebro",[86,14217,14218],{},"É quem toma as decisões. Não roda suas aplicações — apenas gerencia o cluster.",[528,14220,14221,14227,14233,14239],{},[20,14222,14223,14226],{},[89,14224,14225],{},"API Server",": a porta de entrada. Tudo o que você faz (criar pods, escalar,\natualizar) passa pelo API Server. É como a recepção de um prédio.",[20,14228,14229,14232],{},[89,14230,14231],{},"Scheduler"," (agendador): decide em qual worker node cada pod vai rodar,\nbaseado em recursos disponíveis (CPU, memória).",[20,14234,14235,14238],{},[89,14236,14237],{},"etcd",": um banco de dados chave-valor que guarda todo o estado do cluster.\nÉ a \"memória\" do Kubernetes — sem ele, o cluster esquece tudo.",[20,14240,14241,14244],{},[89,14242,14243],{},"Controller Manager",": garante que o estado real corresponde ao estado\ndesejado. Se você pediu 3 pods e só tem 2, ele cria o terceiro.",[283,14246,14248],{"id":14247},"worker-nodes-nós-de-trabalho-os-músculos","Worker Nodes (nós de trabalho) — os músculos",[86,14250,14251],{},"São as máquinas que realmente rodam suas aplicações. Cada worker node tem:",[528,14253,14254,14260,14266],{},[20,14255,14256,14259],{},[89,14257,14258],{},"kubelet",": o agente que recebe ordens do control plane e garante que os\npods estão rodando conforme o esperado.",[20,14261,14262,14265],{},[89,14263,14264],{},"kube-proxy",": responsável pelo roredamento de rede entre os pods.",[20,14267,14268,14271],{},[89,14269,14270],{},"Container runtime",": o motor que executa os containers (containerd, Docker,\nCRI-O).",[86,14273,14274,14277],{},[89,14275,14276],{},"Analogia final",": o control plane é o gerente do porto (decide, planeja,\nmonitora). Os worker nodes são os guindastes e caminhões (executam o trabalho\npesado).",[12,14279,14281],{"id":14280},"pods-a-menor-unidade-pods","Pods: a menor unidade {#pods}",[86,14283,14284,14287],{},[89,14285,14286],{},"Pod"," é a menor unidade gerenciável no Kubernetes. E é o conceito mais\nimportante para entender.",[86,14289,14290,14291,14294],{},"Um pod pode conter um ou mais containers, mas o caso mais comum é ",[89,14292,14293],{},"um pod =\num container",". Os containers dentro do mesmo pod compartilham rede e\narmazenamento — são \"amigos íntimos\" que precisam estar sempre juntos.",[86,14296,14297,14300],{},[89,14298,14299],{},"Por que pods e não containers direto?"," Porque Kubernetes precisa gerenciar\nalgo maior que um container. Um pod pode ter, além do container principal,\num \"sidecar\" — um container auxiliar. Por exemplo: um container com sua\naplicação + um container que coleta logs e envia para um servidor central.",[86,14302,14303,14305],{},[89,14304,3011],{},": se o container é um peixe, o pod é o aquário. Você pode ter\nvários peixes no mesmo aquário (containers no mesmo pod), compartilhando a\nmesma água (rede) e o mesmo fundo (armazenamento). Kubernetes gerencia\naquários, não peixes diretamente.",[283,14307,14309],{"id":14308},"características-dos-pods","Características dos pods",[528,14311,14312,14318,14324],{},[20,14313,14314,14317],{},[89,14315,14316],{},"Efêmeros",": pods nascem e morrem. Se um pod morre, o Kubernetes cria outro\n— mas com um IP novo. Por isso você nunca deve depender do IP de um pod.",[20,14319,14320,14323],{},[89,14321,14322],{},"Não se atualizam",": para atualizar a versão da sua app, o Kubernetes mata o\npod antigo e cria um novo com a nova imagem.",[20,14325,14326,14329],{},[89,14327,14328],{},"Escalam em número",": para suportar mais tráfego, você cria mais réplicas\ndo mesmo pod.",[506,14331,14333],{"className":1656,"code":14332,"language":1658,"meta":515,"style":515},"# pod-simples.yaml — exemplo de um pod básico\napiVersion: v1\nkind: Pod\nmetadata:\n  name: minha-app        # Nome do pod\n  labels:\n    app: minha-app       # Label (etiqueta) para identificar o pod depois\nspec:\n  containers:\n    - name: minha-app    # Nome do container dentro do pod\n      image: nginx:latest  # Imagem que vai rodar (nginx neste exemplo)\n      ports:\n        - containerPort: 80  # Porta que o container expõe\n",[513,14334,14335,14340,14350,14360,14367,14380,14387,14399,14406,14413,14426,14439,14446],{"__ignoreMap":515},[1376,14336,14337],{"class":1378,"line":1379},[1376,14338,14339],{"class":1665},"# pod-simples.yaml — exemplo de um pod básico\n",[1376,14341,14342,14345,14347],{"class":1378,"line":984},[1376,14343,14344],{"class":1675},"apiVersion",[1376,14346,1679],{"class":1671},[1376,14348,14349],{"class":1682},"v1\n",[1376,14351,14352,14355,14357],{"class":1378,"line":981},[1376,14353,14354],{"class":1675},"kind",[1376,14356,1679],{"class":1671},[1376,14358,14359],{"class":1682},"Pod\n",[1376,14361,14362,14365],{"class":1378,"line":1395},[1376,14363,14364],{"class":1675},"metadata",[1376,14366,1750],{"class":1671},[1376,14368,14369,14372,14374,14377],{"class":1378,"line":1401},[1376,14370,14371],{"class":1675},"  name",[1376,14373,1679],{"class":1671},[1376,14375,14376],{"class":1682},"minha-app",[1376,14378,14379],{"class":1665},"        # Nome do pod\n",[1376,14381,14382,14385],{"class":1378,"line":1407},[1376,14383,14384],{"class":1675},"  labels",[1376,14386,1750],{"class":1671},[1376,14388,14389,14392,14394,14396],{"class":1378,"line":1413},[1376,14390,14391],{"class":1675},"    app",[1376,14393,1679],{"class":1671},[1376,14395,14376],{"class":1682},[1376,14397,14398],{"class":1665},"       # Label (etiqueta) para identificar o pod depois\n",[1376,14400,14401,14404],{"class":1378,"line":1419},[1376,14402,14403],{"class":1675},"spec",[1376,14405,1750],{"class":1671},[1376,14407,14408,14411],{"class":1378,"line":1424},[1376,14409,14410],{"class":1675},"  containers",[1376,14412,1750],{"class":1671},[1376,14414,14415,14417,14419,14421,14423],{"class":1378,"line":1430},[1376,14416,1873],{"class":1671},[1376,14418,1676],{"class":1675},[1376,14420,1679],{"class":1671},[1376,14422,14376],{"class":1682},[1376,14424,14425],{"class":1665},"    # Nome do container dentro do pod\n",[1376,14427,14428,14431,14433,14436],{"class":1378,"line":1436},[1376,14429,14430],{"class":1675},"      image",[1376,14432,1679],{"class":1671},[1376,14434,14435],{"class":1682},"nginx:latest",[1376,14437,14438],{"class":1665},"  # Imagem que vai rodar (nginx neste exemplo)\n",[1376,14440,14441,14444],{"class":1378,"line":1442},[1376,14442,14443],{"class":1675},"      ports",[1376,14445,1750],{"class":1671},[1376,14447,14448,14451,14454,14456,14459],{"class":1378,"line":1447},[1376,14449,14450],{"class":1671},"        - ",[1376,14452,14453],{"class":1675},"containerPort",[1376,14455,1679],{"class":1671},[1376,14457,14458],{"class":1770},"80",[1376,14460,14461],{"class":1665},"  # Porta que o container expõe\n",[710,14463,14464],{},[86,14465,14466,14468],{},[89,14467,4008],{},": na prática, você quase nunca cria pods diretamente. Você cria\nDeployments (próxima seção), que gerenciam os pods para você. Criar pods\n\"soltos\" é como construir uma casa sem alicerce — funciona, mas não sobrevive\na uma tempestade.",[12,14470,14472],{"id":14471},"deployments-garantir-o-estado-desejado-deployments","Deployments: garantir o estado desejado {#deployments}",[86,14474,14475,14478],{},[89,14476,14477],{},"Deployment"," é o objeto do Kubernetes que garante que um número de réplicas\nde um pod esteja sempre rodando. É a forma correta de rodar aplicações no\nKubernetes.",[86,14480,14481,14483],{},[89,14482,1585],{},": você diz ao Kubernetes \"quero 3 pods da minha aplicação\nrodando\". O Deployment cria esses 3 pods. Se um morrer, ele cria outro. Se\nvocê quiser atualizar a versão, ele substitui gradualmente os pods antigos por\npods novos. Se você quiser escalar para 10, ele cria mais 7.",[86,14485,14486,14488],{},[89,14487,3011],{},": imagine um gerente de uma fábrica que recebe a ordem \"mantenha\nsempre 3 máquinas de embalagem funcionando\". Se uma quebra, ele liga uma\nreserva. Se você pede para aumentar para 10, ele liga mais máquinas. Ele não\nembala nada — ele garante que as máquinas estejam operando.",[506,14490,14492],{"className":1656,"code":14491,"language":1658,"meta":515,"style":515},"# deployment.yaml — a forma correta de rodar sua aplicação\napiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: minha-app\nspec:\n  replicas: 3              # Quantos pods queremos rodando simultaneamente\n  selector:\n    matchLabels:\n      app: minha-app       # Quais pods este Deployment gerencia (por label)\n  template:                # O \"molde\" para criar cada pod\n    metadata:\n      labels:\n        app: minha-app     # Label que casa com o selector acima\n    spec:\n      containers:\n        - name: minha-app\n          image: minha-app:v1.0.0  # Versão da imagem (use tags, não :latest)\n          ports:\n            - containerPort: 3000\n          resources:        # Importante: defina limites de recursos!\n            requests:       # O mínimo que o pod precisa para ser agendado\n              memory: \"128Mi\"\n              cpu: \"250m\"   # 250m = 250 milicores = 0.25 de um núcleo\n            limits:         # O máximo que o pod pode consumir\n              memory: \"256Mi\"\n              cpu: \"500m\"\n          livenessProbe:    # Verifica se a app está viva (senão, reinicia)\n            httpGet:\n              path: \u002Fhealth\n              port: 3000\n            initialDelaySeconds: 10  # Espera 10s antes de começar a checar\n          readinessProbe:   # Verifica se a app está pronta para receber tráfego\n            httpGet:\n              path: \u002Fready\n              port: 3000\n            initialDelaySeconds: 5\n",[513,14493,14494,14499,14508,14517,14523,14532,14538,14550,14557,14564,14576,14587,14594,14601,14613,14620,14627,14637,14650,14657,14669,14680,14691,14701,14714,14725,14734,14743,14754,14761,14771,14780,14793,14804,14810,14819,14827],{"__ignoreMap":515},[1376,14495,14496],{"class":1378,"line":1379},[1376,14497,14498],{"class":1665},"# deployment.yaml — a forma correta de rodar sua aplicação\n",[1376,14500,14501,14503,14505],{"class":1378,"line":984},[1376,14502,14344],{"class":1675},[1376,14504,1679],{"class":1671},[1376,14506,14507],{"class":1682},"apps\u002Fv1\n",[1376,14509,14510,14512,14514],{"class":1378,"line":981},[1376,14511,14354],{"class":1675},[1376,14513,1679],{"class":1671},[1376,14515,14516],{"class":1682},"Deployment\n",[1376,14518,14519,14521],{"class":1378,"line":1395},[1376,14520,14364],{"class":1675},[1376,14522,1750],{"class":1671},[1376,14524,14525,14527,14529],{"class":1378,"line":1401},[1376,14526,14371],{"class":1675},[1376,14528,1679],{"class":1671},[1376,14530,14531],{"class":1682},"minha-app\n",[1376,14533,14534,14536],{"class":1378,"line":1407},[1376,14535,14403],{"class":1675},[1376,14537,1750],{"class":1671},[1376,14539,14540,14543,14545,14547],{"class":1378,"line":1413},[1376,14541,14542],{"class":1675},"  replicas",[1376,14544,1679],{"class":1671},[1376,14546,5054],{"class":1770},[1376,14548,14549],{"class":1665},"              # Quantos pods queremos rodando simultaneamente\n",[1376,14551,14552,14555],{"class":1378,"line":1419},[1376,14553,14554],{"class":1675},"  selector",[1376,14556,1750],{"class":1671},[1376,14558,14559,14562],{"class":1378,"line":1424},[1376,14560,14561],{"class":1675},"    matchLabels",[1376,14563,1750],{"class":1671},[1376,14565,14566,14569,14571,14573],{"class":1378,"line":1430},[1376,14567,14568],{"class":1675},"      app",[1376,14570,1679],{"class":1671},[1376,14572,14376],{"class":1682},[1376,14574,14575],{"class":1665},"       # Quais pods este Deployment gerencia (por label)\n",[1376,14577,14578,14581,14584],{"class":1378,"line":1436},[1376,14579,14580],{"class":1675},"  template",[1376,14582,14583],{"class":1671},":                ",[1376,14585,14586],{"class":1665},"# O \"molde\" para criar cada pod\n",[1376,14588,14589,14592],{"class":1378,"line":1442},[1376,14590,14591],{"class":1675},"    metadata",[1376,14593,1750],{"class":1671},[1376,14595,14596,14599],{"class":1378,"line":1447},[1376,14597,14598],{"class":1675},"      labels",[1376,14600,1750],{"class":1671},[1376,14602,14603,14606,14608,14610],{"class":1378,"line":1453},[1376,14604,14605],{"class":1675},"        app",[1376,14607,1679],{"class":1671},[1376,14609,14376],{"class":1682},[1376,14611,14612],{"class":1665},"     # Label que casa com o selector acima\n",[1376,14614,14615,14618],{"class":1378,"line":1459},[1376,14616,14617],{"class":1675},"    spec",[1376,14619,1750],{"class":1671},[1376,14621,14622,14625],{"class":1378,"line":2200},[1376,14623,14624],{"class":1675},"      containers",[1376,14626,1750],{"class":1671},[1376,14628,14629,14631,14633,14635],{"class":1378,"line":2208},[1376,14630,14450],{"class":1671},[1376,14632,1676],{"class":1675},[1376,14634,1679],{"class":1671},[1376,14636,14531],{"class":1682},[1376,14638,14639,14642,14644,14647],{"class":1378,"line":2219},[1376,14640,14641],{"class":1675},"          image",[1376,14643,1679],{"class":1671},[1376,14645,14646],{"class":1682},"minha-app:v1.0.0",[1376,14648,14649],{"class":1665},"  # Versão da imagem (use tags, não :latest)\n",[1376,14651,14652,14655],{"class":1378,"line":2227},[1376,14653,14654],{"class":1675},"          ports",[1376,14656,1750],{"class":1671},[1376,14658,14659,14662,14664,14666],{"class":1378,"line":2241},[1376,14660,14661],{"class":1671},"            - ",[1376,14663,14453],{"class":1675},[1376,14665,1679],{"class":1671},[1376,14667,14668],{"class":1770},"3000\n",[1376,14670,14671,14674,14677],{"class":1378,"line":2253},[1376,14672,14673],{"class":1675},"          resources",[1376,14675,14676],{"class":1671},":        ",[1376,14678,14679],{"class":1665},"# Importante: defina limites de recursos!\n",[1376,14681,14682,14685,14688],{"class":1378,"line":2283},[1376,14683,14684],{"class":1675},"            requests",[1376,14686,14687],{"class":1671},":       ",[1376,14689,14690],{"class":1665},"# O mínimo que o pod precisa para ser agendado\n",[1376,14692,14693,14696,14698],{"class":1378,"line":2296},[1376,14694,14695],{"class":1675},"              memory",[1376,14697,1679],{"class":1671},[1376,14699,14700],{"class":1682},"\"128Mi\"\n",[1376,14702,14703,14706,14708,14711],{"class":1378,"line":2311},[1376,14704,14705],{"class":1675},"              cpu",[1376,14707,1679],{"class":1671},[1376,14709,14710],{"class":1682},"\"250m\"",[1376,14712,14713],{"class":1665},"   # 250m = 250 milicores = 0.25 de um núcleo\n",[1376,14715,14716,14719,14722],{"class":1378,"line":2316},[1376,14717,14718],{"class":1675},"            limits",[1376,14720,14721],{"class":1671},":         ",[1376,14723,14724],{"class":1665},"# O máximo que o pod pode consumir\n",[1376,14726,14727,14729,14731],{"class":1378,"line":2324},[1376,14728,14695],{"class":1675},[1376,14730,1679],{"class":1671},[1376,14732,14733],{"class":1682},"\"256Mi\"\n",[1376,14735,14736,14738,14740],{"class":1378,"line":2333},[1376,14737,14705],{"class":1675},[1376,14739,1679],{"class":1671},[1376,14741,14742],{"class":1682},"\"500m\"\n",[1376,14744,14745,14748,14751],{"class":1378,"line":2340},[1376,14746,14747],{"class":1675},"          livenessProbe",[1376,14749,14750],{"class":1671},":    ",[1376,14752,14753],{"class":1665},"# Verifica se a app está viva (senão, reinicia)\n",[1376,14755,14756,14759],{"class":1378,"line":2351},[1376,14757,14758],{"class":1675},"            httpGet",[1376,14760,1750],{"class":1671},[1376,14762,14763,14766,14768],{"class":1378,"line":2362},[1376,14764,14765],{"class":1675},"              path",[1376,14767,1679],{"class":1671},[1376,14769,14770],{"class":1682},"\u002Fhealth\n",[1376,14772,14773,14776,14778],{"class":1378,"line":2385},[1376,14774,14775],{"class":1675},"              port",[1376,14777,1679],{"class":1671},[1376,14779,14668],{"class":1770},[1376,14781,14782,14785,14787,14790],{"class":1378,"line":2396},[1376,14783,14784],{"class":1675},"            initialDelaySeconds",[1376,14786,1679],{"class":1671},[1376,14788,14789],{"class":1770},"10",[1376,14791,14792],{"class":1665},"  # Espera 10s antes de começar a checar\n",[1376,14794,14795,14798,14801],{"class":1378,"line":2411},[1376,14796,14797],{"class":1675},"          readinessProbe",[1376,14799,14800],{"class":1671},":   ",[1376,14802,14803],{"class":1665},"# Verifica se a app está pronta para receber tráfego\n",[1376,14805,14806,14808],{"class":1378,"line":2416},[1376,14807,14758],{"class":1675},[1376,14809,1750],{"class":1671},[1376,14811,14812,14814,14816],{"class":1378,"line":2422},[1376,14813,14765],{"class":1675},[1376,14815,1679],{"class":1671},[1376,14817,14818],{"class":1682},"\u002Fready\n",[1376,14820,14821,14823,14825],{"class":1378,"line":2430},[1376,14822,14775],{"class":1675},[1376,14824,1679],{"class":1671},[1376,14826,14668],{"class":1770},[1376,14828,14829,14831,14833],{"class":1378,"line":2448},[1376,14830,14784],{"class":1675},[1376,14832,1679],{"class":1671},[1376,14834,14835],{"class":1770},"5\n",[283,14837,14839],{"id":14838},"liveness-vs-readiness-a-diferença-importa","Liveness vs Readiness: a diferença importa",[86,14841,14842],{},"Esses dois termos confundem iniciantes, mas a diferença é crucial:",[528,14844,14845,14855],{},[20,14846,14847,14850,14851,14854],{},[89,14848,14849],{},"Liveness probe"," (sonda de vitalidade): \"A aplicação está viva?\" Se falhar,\no Kubernetes ",[89,14852,14853],{},"reinicia"," o pod. Como um médico checando os sinais vitais —\nse o paciente parou de respirar, chama o reanimador.",[20,14856,14857,14860,14861,14864],{},[89,14858,14859],{},"Readiness probe"," (sonda de prontidão): \"A aplicação está pronta para\nreceber tráfego?\" Se falhar, o Kubernetes ",[89,14862,14863],{},"remove o pod do balanceamento de\ncarga"," — não reinicia, só para de enviar requisições. Como um garçom que\nnão anota pedidos de uma mesa onde o chef ainda não chegou.",[86,14866,14867,14869],{},[89,14868,8776],{},": sua app demora 30 segundos para iniciar (precisa carregar dados\nem memória). Sem readiness probe, o Kubernetes já envia tráfego aos 5 segundos\n— a app quebra, o liveness falha, o pod reinicia, e entra em um loop infinito.\nCom readiness probe configurada corretamente, o Kubernetes espera a app ficar\npronta antes de enviar tráfego.",[12,14871,14873],{"id":14872},"services-como-encontrar-seus-pods-services","Services: como encontrar seus pods {#services}",[86,14875,14876],{},"Lembra que pods são efêmeros e mudam de IP? Então como outras aplicações\nencontram o seu pod se o IP muda toda hora?",[86,14878,14879,14882,14883,14886],{},[89,14880,14881],{},"Service"," é a resposta. Um Service é um endereço fixo (IP + DNS) que funciona\ncomo uma porta de entrada para um conjunto de pods. Ele faz ",[89,14884,14885],{},"load balancing","\n(distribuição de carga) entre os pods que casam com seu seletor.",[86,14888,14889,14891],{},[89,14890,3011],{},": pense no Service como o número do telefone da recepção de um\nhospital. Os médicos (pods) entram e saem, trocam de plantão, mas o número da\nrecepção (Service) é sempre o mesmo. Você liga para a recepção e ela direciona\npara um médico disponível.",[283,14893,14895],{"id":14894},"tipos-de-service","Tipos de Service",[165,14897,14898,14909],{},[168,14899,14900],{},[171,14901,14902,14904,14907],{},[174,14903,381],{},[174,14905,14906],{},"Acessível por",[174,14908,1588],{},[184,14910,14911,14924,14937,14950],{},[171,14912,14913,14918,14921],{},[189,14914,14915],{},[89,14916,14917],{},"ClusterIP",[189,14919,14920],{},"Só dentro do cluster",[189,14922,14923],{},"Comunicação interna entre serviços",[171,14925,14926,14931,14934],{},[189,14927,14928],{},[89,14929,14930],{},"NodePort",[189,14932,14933],{},"De fora do cluster (porta alta)",[189,14935,14936],{},"Testes, acesso externo simples",[171,14938,14939,14944,14947],{},[189,14940,14941],{},[89,14942,14943],{},"LoadBalancer",[189,14945,14946],{},"Internet (cria um LB da nuvem)",[189,14948,14949],{},"Expor para a internet em produção",[171,14951,14952,14957,14960],{},[189,14953,14954],{},[89,14955,14956],{},"Ingress",[189,14958,14959],{},"Internet (HTTP\u002FHTTPS com domínio)",[189,14961,14962],{},"Múltiplas apps no mesmo domínio",[506,14964,14966],{"className":1656,"code":14965,"language":1658,"meta":515,"style":515},"# service.yaml — expõe a aplicação dentro do cluster\napiVersion: v1\nkind: Service\nmetadata:\n  name: minha-app-service\nspec:\n  type: ClusterIP         # Endereço acessível só dentro do cluster\n  selector:\n    app: minha-app        # Roteia tráfego para pods com este label\n  ports:\n    - port: 80            # Porta que o Service escuta\n      targetPort: 3000    # Porta do pod para onde o tráfego vai\n",[513,14967,14968,14973,14981,14990,14996,15005,15011,15023,15029,15040,15047,15061],{"__ignoreMap":515},[1376,14969,14970],{"class":1378,"line":1379},[1376,14971,14972],{"class":1665},"# service.yaml — expõe a aplicação dentro do cluster\n",[1376,14974,14975,14977,14979],{"class":1378,"line":984},[1376,14976,14344],{"class":1675},[1376,14978,1679],{"class":1671},[1376,14980,14349],{"class":1682},[1376,14982,14983,14985,14987],{"class":1378,"line":981},[1376,14984,14354],{"class":1675},[1376,14986,1679],{"class":1671},[1376,14988,14989],{"class":1682},"Service\n",[1376,14991,14992,14994],{"class":1378,"line":1395},[1376,14993,14364],{"class":1675},[1376,14995,1750],{"class":1671},[1376,14997,14998,15000,15002],{"class":1378,"line":1401},[1376,14999,14371],{"class":1675},[1376,15001,1679],{"class":1671},[1376,15003,15004],{"class":1682},"minha-app-service\n",[1376,15006,15007,15009],{"class":1378,"line":1407},[1376,15008,14403],{"class":1675},[1376,15010,1750],{"class":1671},[1376,15012,15013,15016,15018,15020],{"class":1378,"line":1413},[1376,15014,15015],{"class":1675},"  type",[1376,15017,1679],{"class":1671},[1376,15019,14917],{"class":1682},[1376,15021,15022],{"class":1665},"         # Endereço acessível só dentro do cluster\n",[1376,15024,15025,15027],{"class":1378,"line":1419},[1376,15026,14554],{"class":1675},[1376,15028,1750],{"class":1671},[1376,15030,15031,15033,15035,15037],{"class":1378,"line":1424},[1376,15032,14391],{"class":1675},[1376,15034,1679],{"class":1671},[1376,15036,14376],{"class":1682},[1376,15038,15039],{"class":1665},"        # Roteia tráfego para pods com este label\n",[1376,15041,15042,15045],{"class":1378,"line":1430},[1376,15043,15044],{"class":1675},"  ports",[1376,15046,1750],{"class":1671},[1376,15048,15049,15051,15054,15056,15058],{"class":1378,"line":1436},[1376,15050,1873],{"class":1671},[1376,15052,15053],{"class":1675},"port",[1376,15055,1679],{"class":1671},[1376,15057,14458],{"class":1770},[1376,15059,15060],{"class":1665},"            # Porta que o Service escuta\n",[1376,15062,15063,15066,15068,15071],{"class":1378,"line":1442},[1376,15064,15065],{"class":1675},"      targetPort",[1376,15067,1679],{"class":1671},[1376,15069,15070],{"class":1770},"3000",[1376,15072,15073],{"class":1665},"    # Porta do pod para onde o tráfego vai\n",[283,15075,15077],{"id":15076},"ingress-o-porteiro-do-prédio","Ingress: o porteiro do prédio",[86,15079,15080,15081,15083],{},"Quando você tem múltiplas aplicações (API, frontend, admin) e quer expor todas\nna internet com um único IP e domínios diferentes, usa ",[89,15082,14956],{},". É como um\nporteiro que recebe todos os visitantes e direciona cada um para o andar certo\nbaseado no nome que procuram:",[528,15085,15086,15092,15098],{},[20,15087,15088,15091],{},[513,15089,15090],{},"api.minhaempresa.com"," → Service da API",[20,15093,15094,15097],{},[513,15095,15096],{},"app.minhaempresa.com"," → Service do frontend",[20,15099,15100,15103],{},[513,15101,15102],{},"admin.minhaempresa.com"," → Service do painel admin",[12,15105,15107],{"id":15106},"subindo-sua-primeira-aplicação-primeira-app","Subindo sua primeira aplicação {#primeira-app}",[86,15109,15110],{},"Vamos juntar tudo. Aqui está o passo a passo para subir uma aplicação Node.js\nno Kubernetes, do zero.",[283,15112,15114],{"id":15113},"passo-1-criar-a-imagem-docker","Passo 1: criar a imagem Docker",[506,15116,15118],{"className":11827,"code":15117,"language":11829,"meta":515,"style":515},"# Constrói a imagem e envia para um registro (registry)\n# Registry = um \"Docker Hub\" privado onde imagens ficam armazenadas\ndocker build -t meu-registro\u002Fminha-app:v1.0.0 .\ndocker push meu-registro\u002Fminha-app:v1.0.0\n",[513,15119,15120,15125,15130,15146],{"__ignoreMap":515},[1376,15121,15122],{"class":1378,"line":1379},[1376,15123,15124],{"class":1665},"# Constrói a imagem e envia para um registro (registry)\n",[1376,15126,15127],{"class":1378,"line":984},[1376,15128,15129],{"class":1665},"# Registry = um \"Docker Hub\" privado onde imagens ficam armazenadas\n",[1376,15131,15132,15134,15137,15140,15143],{"class":1378,"line":981},[1376,15133,11842],{"class":11841},[1376,15135,15136],{"class":1682}," build",[1376,15138,15139],{"class":1770}," -t",[1376,15141,15142],{"class":1682}," meu-registro\u002Fminha-app:v1.0.0",[1376,15144,15145],{"class":1682}," .\n",[1376,15147,15148,15150,15153],{"class":1378,"line":1395},[1376,15149,11842],{"class":11841},[1376,15151,15152],{"class":1682}," push",[1376,15154,15155],{"class":1682}," meu-registro\u002Fminha-app:v1.0.0\n",[283,15157,15159],{"id":15158},"passo-2-criar-o-deployment","Passo 2: criar o Deployment",[86,15161,15162,15163,15166,15167,15170],{},"Salve o arquivo ",[513,15164,15165],{},"deployment.yaml"," (use o exemplo da seção Deployments acima,\ntrocando a imagem por ",[513,15168,15169],{},"meu-registro\u002Fminha-app:v1.0.0",") e aplique:",[506,15172,15174],{"className":11827,"code":15173,"language":11829,"meta":515,"style":515},"# kubectl = a ferramenta de linha de comando que fala com o Kubernetes\n# apply = aplica a configuração do arquivo no cluster\nkubectl apply -f deployment.yaml\n\n# Veja os pods sendo criados\nkubectl get pods\n# Saída esperada:\n# NAME                         READY   STATUS    RESTARTS   AGE\n# minha-app-7d4f5b6c8-x2k9j   1\u002F1     Running   0          30s\n# minha-app-7d4f5b6c8-m8p3q   1\u002F1     Running   0          30s\n# minha-app-7d4f5b6c8-r1n5t   1\u002F1     Running   0          30s\n",[513,15175,15176,15181,15186,15200,15204,15209,15219,15224,15229,15234,15239],{"__ignoreMap":515},[1376,15177,15178],{"class":1378,"line":1379},[1376,15179,15180],{"class":1665},"# kubectl = a ferramenta de linha de comando que fala com o Kubernetes\n",[1376,15182,15183],{"class":1378,"line":984},[1376,15184,15185],{"class":1665},"# apply = aplica a configuração do arquivo no cluster\n",[1376,15187,15188,15191,15194,15197],{"class":1378,"line":981},[1376,15189,15190],{"class":11841},"kubectl",[1376,15192,15193],{"class":1682}," apply",[1376,15195,15196],{"class":1770}," -f",[1376,15198,15199],{"class":1682}," deployment.yaml\n",[1376,15201,15202],{"class":1378,"line":1395},[1376,15203,1398],{"emptyLinePlaceholder":898},[1376,15205,15206],{"class":1378,"line":1401},[1376,15207,15208],{"class":1665},"# Veja os pods sendo criados\n",[1376,15210,15211,15213,15216],{"class":1378,"line":1407},[1376,15212,15190],{"class":11841},[1376,15214,15215],{"class":1682}," get",[1376,15217,15218],{"class":1682}," pods\n",[1376,15220,15221],{"class":1378,"line":1413},[1376,15222,15223],{"class":1665},"# Saída esperada:\n",[1376,15225,15226],{"class":1378,"line":1419},[1376,15227,15228],{"class":1665},"# NAME                         READY   STATUS    RESTARTS   AGE\n",[1376,15230,15231],{"class":1378,"line":1424},[1376,15232,15233],{"class":1665},"# minha-app-7d4f5b6c8-x2k9j   1\u002F1     Running   0          30s\n",[1376,15235,15236],{"class":1378,"line":1430},[1376,15237,15238],{"class":1665},"# minha-app-7d4f5b6c8-m8p3q   1\u002F1     Running   0          30s\n",[1376,15240,15241],{"class":1378,"line":1436},[1376,15242,15243],{"class":1665},"# minha-app-7d4f5b6c8-r1n5t   1\u002F1     Running   0          30s\n",[283,15245,15247],{"id":15246},"passo-3-criar-o-service","Passo 3: criar o Service",[506,15249,15251],{"className":11827,"code":15250,"language":11829,"meta":515,"style":515},"kubectl apply -f service.yaml\n\n# Veja o Service criado com seu IP interno\nkubectl get services\n# Saída:\n# NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)\n# minha-app-service    ClusterIP   10.96.34.72     \u003Cnone>        80\u002FTCP\n",[513,15252,15253,15264,15268,15273,15282,15287,15292],{"__ignoreMap":515},[1376,15254,15255,15257,15259,15261],{"class":1378,"line":1379},[1376,15256,15190],{"class":11841},[1376,15258,15193],{"class":1682},[1376,15260,15196],{"class":1770},[1376,15262,15263],{"class":1682}," service.yaml\n",[1376,15265,15266],{"class":1378,"line":984},[1376,15267,1398],{"emptyLinePlaceholder":898},[1376,15269,15270],{"class":1378,"line":981},[1376,15271,15272],{"class":1665},"# Veja o Service criado com seu IP interno\n",[1376,15274,15275,15277,15279],{"class":1378,"line":1395},[1376,15276,15190],{"class":11841},[1376,15278,15215],{"class":1682},[1376,15280,15281],{"class":1682}," services\n",[1376,15283,15284],{"class":1378,"line":1401},[1376,15285,15286],{"class":1665},"# Saída:\n",[1376,15288,15289],{"class":1378,"line":1407},[1376,15290,15291],{"class":1665},"# NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)\n",[1376,15293,15294],{"class":1378,"line":1413},[1376,15295,15296],{"class":1665},"# minha-app-service    ClusterIP   10.96.34.72     \u003Cnone>        80\u002FTCP\n",[283,15298,15300],{"id":15299},"passo-4-expor-para-a-internet-opcional","Passo 4: expor para a internet (opcional)",[86,15302,15303,15304,15307],{},"Para testar localmente, você pode fazer ",[89,15305,15306],{},"port-forward"," (encaminhamento de\nporta) — cria um túnel da sua máquina para o Service dentro do cluster:",[506,15309,15311],{"className":11827,"code":15310,"language":11829,"meta":515,"style":515},"# Encaminha a porta 8080 da sua máquina para a porta 80 do Service\nkubectl port-forward service\u002Fminha-app-service 8080:80\n\n# Agora acesse http:\u002F\u002Flocalhost:8080 no navegador\n",[513,15312,15313,15318,15331,15335],{"__ignoreMap":515},[1376,15314,15315],{"class":1378,"line":1379},[1376,15316,15317],{"class":1665},"# Encaminha a porta 8080 da sua máquina para a porta 80 do Service\n",[1376,15319,15320,15322,15325,15328],{"class":1378,"line":984},[1376,15321,15190],{"class":11841},[1376,15323,15324],{"class":1682}," port-forward",[1376,15326,15327],{"class":1682}," service\u002Fminha-app-service",[1376,15329,15330],{"class":1682}," 8080:80\n",[1376,15332,15333],{"class":1378,"line":981},[1376,15334,1398],{"emptyLinePlaceholder":898},[1376,15336,15337],{"class":1378,"line":1395},[1376,15338,15339],{"class":1665},"# Agora acesse http:\u002F\u002Flocalhost:8080 no navegador\n",[283,15341,15343],{"id":15342},"passo-5-atualizar-a-versão","Passo 5: atualizar a versão",[86,15345,15346],{},"Quando você lançar a v1.1.0, basta mudar a imagem no Deployment:",[506,15348,15350],{"className":11827,"code":15349,"language":11829,"meta":515,"style":515},"# Muda a imagem do deployment para a nova versão\nkubectl set image deployment\u002Fminha-app minha-app=meu-registro\u002Fminha-app:v1.1.0\n\n# O Kubernetes faz um rolling update: cria pods novos, remove os antigos,\n# um de cada vez, sem derrubar a aplicação\nkubectl rollout status deployment\u002Fminha-app\n# Saída: \"deployment minah-app successfully rolled out\"\n",[513,15351,15352,15357,15373,15377,15382,15387,15400],{"__ignoreMap":515},[1376,15353,15354],{"class":1378,"line":1379},[1376,15355,15356],{"class":1665},"# Muda a imagem do deployment para a nova versão\n",[1376,15358,15359,15361,15364,15367,15370],{"class":1378,"line":984},[1376,15360,15190],{"class":11841},[1376,15362,15363],{"class":1682}," set",[1376,15365,15366],{"class":1682}," image",[1376,15368,15369],{"class":1682}," deployment\u002Fminha-app",[1376,15371,15372],{"class":1682}," minha-app=meu-registro\u002Fminha-app:v1.1.0\n",[1376,15374,15375],{"class":1378,"line":981},[1376,15376,1398],{"emptyLinePlaceholder":898},[1376,15378,15379],{"class":1378,"line":1395},[1376,15380,15381],{"class":1665},"# O Kubernetes faz um rolling update: cria pods novos, remove os antigos,\n",[1376,15383,15384],{"class":1378,"line":1401},[1376,15385,15386],{"class":1665},"# um de cada vez, sem derrubar a aplicação\n",[1376,15388,15389,15391,15394,15397],{"class":1378,"line":1407},[1376,15390,15190],{"class":11841},[1376,15392,15393],{"class":1682}," rollout",[1376,15395,15396],{"class":1682}," status",[1376,15398,15399],{"class":1682}," deployment\u002Fminha-app\n",[1376,15401,15402],{"class":1378,"line":1413},[1376,15403,15404],{"class":1665},"# Saída: \"deployment minah-app successfully rolled out\"\n",[86,15406,15407],{},"Se algo der errado, você pode reverter (rollback) em segundos:",[506,15409,15411],{"className":11827,"code":15410,"language":11829,"meta":515,"style":515},"# Volta para a versão anterior\nkubectl rollout undo deployment\u002Fminha-app\n",[513,15412,15413,15418],{"__ignoreMap":515},[1376,15414,15415],{"class":1378,"line":1379},[1376,15416,15417],{"class":1665},"# Volta para a versão anterior\n",[1376,15419,15420,15422,15424,15427],{"class":1378,"line":984},[1376,15421,15190],{"class":11841},[1376,15423,15393],{"class":1682},[1376,15425,15426],{"class":1682}," undo",[1376,15428,15399],{"class":1682},[12,15430,15432],{"id":15431},"auto-scaling-crescer-sob-demanda-auto-scaling","Auto-scaling: crescer sob demanda {#auto-scaling}",[86,15434,15435,15436,15439],{},"O ",[89,15437,15438],{},"Horizontal Pod Autoscaler (HPA)"," aumenta ou diminui o número de pods\nautomaticamente baseado em métricas (uso de CPU, memória, ou métricas\ncustomizadas).",[86,15441,15442,15444],{},[89,15443,3011],{},": é como um restaurante que abre mais caixas quando a fila fica\ngrande. Quando o movimento cai, fecha os caixas extras para economizar.",[506,15446,15448],{"className":1656,"code":15447,"language":1658,"meta":515,"style":515},"# hpa.yaml — auto-scaling horizontal de pods\napiVersion: autoscaling\u002Fv2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: minha-app-hpa\nspec:\n  scaleTargetRef:\n    apiVersion: apps\u002Fv1\n    kind: Deployment\n    name: minha-app       # Qual Deployment escalar\n  minReplicas: 2          # Mínimo de pods (mesmo sem tráfego)\n  maxReplicas: 20         # Máximo de pods (limite de custo)\n  metrics:\n    - type: Resource\n      resource:\n        name: cpu\n        target:\n          type: Utilization\n          averageUtilization: 70  # Se CPU passar de 70%, adiciona pods\n",[513,15449,15450,15455,15464,15473,15479,15488,15494,15501,15510,15519,15531,15543,15555,15562,15574,15581,15591,15598,15608],{"__ignoreMap":515},[1376,15451,15452],{"class":1378,"line":1379},[1376,15453,15454],{"class":1665},"# hpa.yaml — auto-scaling horizontal de pods\n",[1376,15456,15457,15459,15461],{"class":1378,"line":984},[1376,15458,14344],{"class":1675},[1376,15460,1679],{"class":1671},[1376,15462,15463],{"class":1682},"autoscaling\u002Fv2\n",[1376,15465,15466,15468,15470],{"class":1378,"line":981},[1376,15467,14354],{"class":1675},[1376,15469,1679],{"class":1671},[1376,15471,15472],{"class":1682},"HorizontalPodAutoscaler\n",[1376,15474,15475,15477],{"class":1378,"line":1395},[1376,15476,14364],{"class":1675},[1376,15478,1750],{"class":1671},[1376,15480,15481,15483,15485],{"class":1378,"line":1401},[1376,15482,14371],{"class":1675},[1376,15484,1679],{"class":1671},[1376,15486,15487],{"class":1682},"minha-app-hpa\n",[1376,15489,15490,15492],{"class":1378,"line":1407},[1376,15491,14403],{"class":1675},[1376,15493,1750],{"class":1671},[1376,15495,15496,15499],{"class":1378,"line":1413},[1376,15497,15498],{"class":1675},"  scaleTargetRef",[1376,15500,1750],{"class":1671},[1376,15502,15503,15506,15508],{"class":1378,"line":1419},[1376,15504,15505],{"class":1675},"    apiVersion",[1376,15507,1679],{"class":1671},[1376,15509,14507],{"class":1682},[1376,15511,15512,15515,15517],{"class":1378,"line":1424},[1376,15513,15514],{"class":1675},"    kind",[1376,15516,1679],{"class":1671},[1376,15518,14516],{"class":1682},[1376,15520,15521,15524,15526,15528],{"class":1378,"line":1430},[1376,15522,15523],{"class":1675},"    name",[1376,15525,1679],{"class":1671},[1376,15527,14376],{"class":1682},[1376,15529,15530],{"class":1665},"       # Qual Deployment escalar\n",[1376,15532,15533,15536,15538,15540],{"class":1378,"line":1436},[1376,15534,15535],{"class":1675},"  minReplicas",[1376,15537,1679],{"class":1671},[1376,15539,1891],{"class":1770},[1376,15541,15542],{"class":1665},"          # Mínimo de pods (mesmo sem tráfego)\n",[1376,15544,15545,15548,15550,15552],{"class":1378,"line":1442},[1376,15546,15547],{"class":1675},"  maxReplicas",[1376,15549,1679],{"class":1671},[1376,15551,2267],{"class":1770},[1376,15553,15554],{"class":1665},"         # Máximo de pods (limite de custo)\n",[1376,15556,15557,15560],{"class":1378,"line":1447},[1376,15558,15559],{"class":1675},"  metrics",[1376,15561,1750],{"class":1671},[1376,15563,15564,15566,15569,15571],{"class":1378,"line":1453},[1376,15565,1873],{"class":1671},[1376,15567,15568],{"class":1675},"type",[1376,15570,1679],{"class":1671},[1376,15572,15573],{"class":1682},"Resource\n",[1376,15575,15576,15579],{"class":1378,"line":1459},[1376,15577,15578],{"class":1675},"      resource",[1376,15580,1750],{"class":1671},[1376,15582,15583,15586,15588],{"class":1378,"line":2200},[1376,15584,15585],{"class":1675},"        name",[1376,15587,1679],{"class":1671},[1376,15589,15590],{"class":1682},"cpu\n",[1376,15592,15593,15596],{"class":1378,"line":2208},[1376,15594,15595],{"class":1675},"        target",[1376,15597,1750],{"class":1671},[1376,15599,15600,15603,15605],{"class":1378,"line":2219},[1376,15601,15602],{"class":1675},"          type",[1376,15604,1679],{"class":1671},[1376,15606,15607],{"class":1682},"Utilization\n",[1376,15609,15610,15613,15615,15618],{"class":1378,"line":2227},[1376,15611,15612],{"class":1675},"          averageUtilization",[1376,15614,1679],{"class":1671},[1376,15616,15617],{"class":1770},"70",[1376,15619,15620],{"class":1665},"  # Se CPU passar de 70%, adiciona pods\n",[86,15622,15623],{},"Com essa configuração, se a CPU média dos pods passar de 70%, o Kubernetes\nadiciona mais pods. Se cair, remove. Tudo automático.",[710,15625,15626],{},[86,15627,15628,15631,15632,15635],{},[89,15629,15630],{},"Importante",": para o HPA funcionar com CPU\u002Fmemória, você precisa do\n",[89,15633,15634],{},"Metrics Server"," instalado no cluster. Ele coleta as métricas de\nconsumo de cada pod.",[283,15637,15639],{"id":15638},"tipos-de-auto-scaling-no-kubernetes","Tipos de auto-scaling no Kubernetes",[165,15641,15642,15654],{},[168,15643,15644],{},[171,15645,15646,15648,15651],{},[174,15647,381],{},[174,15649,15650],{},"O que escala",[174,15652,15653],{},"Quando",[184,15655,15656,15669,15682],{},[171,15657,15658,15663,15666],{},[189,15659,15660],{},[89,15661,15662],{},"Horizontal Pod Autoscaler",[189,15664,15665],{},"Número de pods",[189,15667,15668],{},"Demanda variável de tráfego",[171,15670,15671,15676,15679],{},[189,15672,15673],{},[89,15674,15675],{},"Vertical Pod Autoscaler",[189,15677,15678],{},"CPU\u002Fmemória de cada pod",[189,15680,15681],{},"App precisa de mais recursos",[171,15683,15684,15689,15692],{},[189,15685,15686],{},[89,15687,15688],{},"Cluster Autoscaler",[189,15690,15691],{},"Número de worker nodes",[189,15693,15694],{},"Pods não cabem nos nodes atuais",[86,15696,15697],{},"O ideal é combinar HPA (mais pods) com Cluster Autoscaler (mais servidores\nquando os pods não cabem nos servidores existentes).",[12,15699,15701],{"id":15700},"erros-comuns-erros-comuns","Erros comuns {#erros-comuns}",[283,15703,15705,15706,15709],{"id":15704},"_1-usar-latest-como-tag-de-imagem","1. Usar ",[513,15707,15708],{},":latest"," como tag de imagem",[86,15711,15712,1679,15714,15716],{},[89,15713,4750],{},[513,15715,15708],{}," é mutável — hoje é a v1.0, amanhã é a v2.0. O\nKubernetes pode não perceber que a imagem mudou e continuar usando uma versão\nantiga em cache.",[86,15718,15719,15722,15723,1327,15726,15729],{},[89,15720,15721],{},"Solução",": sempre use tags versionadas (",[513,15724,15725],{},"v1.0.0",[513,15727,15728],{},"v1.2.3",") ou o hash\nSHA da imagem.",[283,15731,15733],{"id":15732},"_2-não-definir-resource-requests-e-limits","2. Não definir resource requests e limits",[86,15735,15736,15738],{},[89,15737,4750],{},": sem limites, um pod pode consumir toda a CPU\u002Fmemória do node e\nderrubar outros pods. Sem requests, o scheduler não sabe onde colocar o pod.",[86,15740,15741,15743,15744,15747,15748,15751],{},[89,15742,15721],{},": sempre defina ",[513,15745,15746],{},"resources.requests"," (mínimo) e ",[513,15749,15750],{},"resources.limits","\n(máximo) para cada container.",[283,15753,15755],{"id":15754},"_3-confundir-liveness-e-readiness","3. Confundir liveness e readiness",[86,15757,15758,15760],{},[89,15759,4750],{},": usar liveness probe para checar dependências externas (banco de\ndados, API de terceiros). Se o banco cai, o liveness falha, o pod reinicia —\nmas o banco continua caído, e o pod reinicia de novo em loop.",[86,15762,15763,15765,15766,15769,15770,15773],{},[89,15764,15721],{},": liveness checa se a ",[89,15767,15768],{},"própria aplicação"," está viva. Readiness\ncheca se ela está ",[89,15771,15772],{},"pronta para servir",", incluindo dependências.",[283,15775,15777],{"id":15776},"_4-não-configurar-poddisruptionbudget","4. Não configurar PodDisruptionBudget",[86,15779,15780,15782],{},[89,15781,4750],{},": durante manutenção do cluster, o Kubernetes pode derrubar todos\nos seus pods de uma vez, causando downtime.",[86,15784,15785,15787,15788,15791],{},[89,15786,15721],{},": configure um ",[89,15789,15790],{},"PodDisruptionBudget"," (orçamento de disrupção)\ngarantindo que sempre um número mínimo de pods continue rodando:",[506,15793,15795],{"className":1656,"code":15794,"language":1658,"meta":515,"style":515},"apiVersion: policy\u002Fv1\nkind: PodDisruptionBudget\nmetadata:\n  name: minha-app-pdb\nspec:\n  minAvailable: 2          # Sempre mantém pelo menos 2 pods rodando\n  selector:\n    matchLabels:\n      app: minha-app\n",[513,15796,15797,15806,15815,15821,15830,15836,15848,15854,15860],{"__ignoreMap":515},[1376,15798,15799,15801,15803],{"class":1378,"line":1379},[1376,15800,14344],{"class":1675},[1376,15802,1679],{"class":1671},[1376,15804,15805],{"class":1682},"policy\u002Fv1\n",[1376,15807,15808,15810,15812],{"class":1378,"line":984},[1376,15809,14354],{"class":1675},[1376,15811,1679],{"class":1671},[1376,15813,15814],{"class":1682},"PodDisruptionBudget\n",[1376,15816,15817,15819],{"class":1378,"line":981},[1376,15818,14364],{"class":1675},[1376,15820,1750],{"class":1671},[1376,15822,15823,15825,15827],{"class":1378,"line":1395},[1376,15824,14371],{"class":1675},[1376,15826,1679],{"class":1671},[1376,15828,15829],{"class":1682},"minha-app-pdb\n",[1376,15831,15832,15834],{"class":1378,"line":1401},[1376,15833,14403],{"class":1675},[1376,15835,1750],{"class":1671},[1376,15837,15838,15841,15843,15845],{"class":1378,"line":1407},[1376,15839,15840],{"class":1675},"  minAvailable",[1376,15842,1679],{"class":1671},[1376,15844,1891],{"class":1770},[1376,15846,15847],{"class":1665},"          # Sempre mantém pelo menos 2 pods rodando\n",[1376,15849,15850,15852],{"class":1378,"line":1413},[1376,15851,14554],{"class":1675},[1376,15853,1750],{"class":1671},[1376,15855,15856,15858],{"class":1378,"line":1419},[1376,15857,14561],{"class":1675},[1376,15859,1750],{"class":1671},[1376,15861,15862,15864,15866],{"class":1378,"line":1424},[1376,15863,14568],{"class":1675},[1376,15865,1679],{"class":1671},[1376,15867,14531],{"class":1682},[283,15869,15871],{"id":15870},"_5-ignorar-a-segurança-de-containers","5. Ignorar a segurança de containers",[86,15873,15874,15876],{},[89,15875,4750],{},": rodar containers como root, sem limites de privilégio, abre\nbrechas de segurança.",[86,15878,15879,15881,15882,15885],{},[89,15880,15721],{},": adicione ",[513,15883,15884],{},"securityContext"," nos seus manifests:",[506,15887,15889],{"className":1656,"code":15888,"language":1658,"meta":515,"style":515},"spec:\n  containers:\n    - name: minha-app\n      image: minha-app:v1.0.0\n      securityContext:\n        runAsNonRoot: true       # Não roda como usuário root\n        runAsUser: 1000          # Roda como usuário específico\n        readOnlyRootFilesystem: true  # Sistema de arquivos só-leitura\n",[513,15890,15891,15897,15903,15913,15922,15929,15942,15955],{"__ignoreMap":515},[1376,15892,15893,15895],{"class":1378,"line":1379},[1376,15894,14403],{"class":1675},[1376,15896,1750],{"class":1671},[1376,15898,15899,15901],{"class":1378,"line":984},[1376,15900,14410],{"class":1675},[1376,15902,1750],{"class":1671},[1376,15904,15905,15907,15909,15911],{"class":1378,"line":981},[1376,15906,1873],{"class":1671},[1376,15908,1676],{"class":1675},[1376,15910,1679],{"class":1671},[1376,15912,14531],{"class":1682},[1376,15914,15915,15917,15919],{"class":1378,"line":1395},[1376,15916,14430],{"class":1675},[1376,15918,1679],{"class":1671},[1376,15920,15921],{"class":1682},"minha-app:v1.0.0\n",[1376,15923,15924,15927],{"class":1378,"line":1401},[1376,15925,15926],{"class":1675},"      securityContext",[1376,15928,1750],{"class":1671},[1376,15930,15931,15934,15936,15939],{"class":1378,"line":1407},[1376,15932,15933],{"class":1675},"        runAsNonRoot",[1376,15935,1679],{"class":1671},[1376,15937,15938],{"class":1770},"true",[1376,15940,15941],{"class":1665},"       # Não roda como usuário root\n",[1376,15943,15944,15947,15949,15952],{"class":1378,"line":1413},[1376,15945,15946],{"class":1675},"        runAsUser",[1376,15948,1679],{"class":1671},[1376,15950,15951],{"class":1770},"1000",[1376,15953,15954],{"class":1665},"          # Roda como usuário específico\n",[1376,15956,15957,15960,15962,15964],{"class":1378,"line":1419},[1376,15958,15959],{"class":1675},"        readOnlyRootFilesystem",[1376,15961,1679],{"class":1671},[1376,15963,15938],{"class":1770},[1376,15965,15966],{"class":1665},"  # Sistema de arquivos só-leitura\n",[968,15968],{},[86,15970,15971,15974,15975,522],{},[89,15972,15973],{},"Precisa de ajuda com Kubernetes?"," A Inicialize Tec implementa clusters\nKubernetes, migra aplicações para containers e configura auto-scaling e\nobservabilidade do zero. ",[23,15976,2881],{"href":978},[2883,15978,15979],{},"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 .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 pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":515,"searchDepth":981,"depth":981,"links":15981},[15982,15983,15986,15989,15992,15996,15999,16002,16006,16013,16016],{"id":14,"depth":984,"text":15},{"id":13785,"depth":984,"text":13786,"children":15984},[15985],{"id":13825,"depth":981,"text":13826},{"id":13867,"depth":984,"text":13868,"children":15987},[15988],{"id":13888,"depth":981,"text":13889},{"id":14057,"depth":984,"text":14058,"children":15990},[15991],{"id":14122,"depth":981,"text":14123},{"id":14198,"depth":984,"text":14199,"children":15993},[15994,15995],{"id":14214,"depth":981,"text":14215},{"id":14247,"depth":981,"text":14248},{"id":14280,"depth":984,"text":14281,"children":15997},[15998],{"id":14308,"depth":981,"text":14309},{"id":14471,"depth":984,"text":14472,"children":16000},[16001],{"id":14838,"depth":981,"text":14839},{"id":14872,"depth":984,"text":14873,"children":16003},[16004,16005],{"id":14894,"depth":981,"text":14895},{"id":15076,"depth":981,"text":15077},{"id":15106,"depth":984,"text":15107,"children":16007},[16008,16009,16010,16011,16012],{"id":15113,"depth":981,"text":15114},{"id":15158,"depth":981,"text":15159},{"id":15246,"depth":981,"text":15247},{"id":15299,"depth":981,"text":15300},{"id":15342,"depth":981,"text":15343},{"id":15431,"depth":984,"text":15432,"children":16014},[16015],{"id":15638,"depth":981,"text":15639},{"id":15700,"depth":984,"text":15701,"children":16017},[16018,16020,16021,16022,16023],{"id":15704,"depth":981,"text":16019},"1. Usar :latest como tag de imagem",{"id":15732,"depth":981,"text":15733},{"id":15754,"depth":981,"text":15755},{"id":15776,"depth":981,"text":15777},{"id":15870,"depth":981,"text":15871},"infraestrutura","Aprenda Kubernetes do zero: o que é, como funciona, pods, deployments, services e como subir sua primeira aplicação — explicado com analogias simples.",[16027,13807,16028,16029,16030,13821,11842],"kubernetes","orquestração","pods","deployments",{},"\u002Finfraestrutura\u002Fkubernetes-para-iniciantes",{"title":13718,"description":16025},"kubernetes-para-iniciantes","infraestrutura\u002Fkubernetes-para-iniciantes","rn0F-7wu9RXWRXlx8q7KQRVUKcMzaPO25KLvrCzfqv8",{"id":16038,"title":16039,"author":7,"body":16040,"category":16024,"date":1028,"description":18960,"extension":1030,"keywords":18961,"meta":18966,"navigation":898,"path":18967,"seo":18968,"slug":18969,"stem":18970,"updated":1028,"__hash__":18971},"content\u002Finfraestrutura\u002Fobservabilidade-com-opentelemetry.md","Observabilidade com OpenTelemetry: guia para iniciantes",{"type":9,"value":16041,"toc":18915},[16042,16044,16115,16117,16121,16134,16151,16173,16185,16189,16204,16220,16222,16226,16229,16238,16247,16327,16337,16339,16343,16346,16366,16369,16373,16449,16457,16459,16463,16486,16490,16493,16500,16511,16515,16518,16537,16543,16547,16618,16620,16624,16630,16641,16645,16654,16660,16696,16713,16719,16725,16729,16740,16746,16753,16755,16759,16769,16772,16802,16813,16817,16891,16898,16900,16904,16911,16916,16947,17074,17084,17092,17094,17098,17105,17109,17117,17121,17242,17246,17252,17572,17576,17583,17691,17702,17706,17713,17930,17937,18216,18219,18237,18239,18243,18249,18256,18293,18297,18351,18365,18367,18371,18385,18395,18401,18479,18490,18555,18562,18564,18568,18572,18578,18594,18710,18714,18719,18733,18737,18742,18751,18755,18763,18768,18772,18781,18790,18794,18811,18830,18832,18836,18843,18846,18881,18888,18890,18894,18900,18903,18912],[12,16043,15],{"id":14},[17,16045,16046,16051,16057,16063,16069,16075,16080,16086,16092,16098,16104,16109],{},[20,16047,16048],{},[23,16049,16050],{"href":25},"O que é observabilidade (e por que você deveria se importar)",[20,16052,16053],{},[23,16054,16056],{"href":16055},"#monitoramento-vs","Monitoramento vs. observabilidade: a diferença que importa",[20,16058,16059],{},[23,16060,16062],{"href":16061},"#tres-pilares","Os três pilares: logs, métricas e traces",[20,16064,16065],{},[23,16066,16068],{"href":16067},"#opentelemetry","OpenTelemetry: o que é e por que existe",[20,16070,16071],{},[23,16072,16074],{"href":16073},"#tracing","Tracing explicado com analogias",[20,16076,16077],{},[23,16078,16079],{"href":4930},"Métricas explicadas com analogias",[20,16081,16082],{},[23,16083,16085],{"href":16084},"#logs","Logs explicados com analogias",[20,16087,16088],{},[23,16089,16091],{"href":16090},"#fastapi","Mão na massa: instrumentando uma API FastAPI",[20,16093,16094],{},[23,16095,16097],{"href":16096},"#jaeger","Visualizando traces no Jaeger",[20,16099,16100],{},[23,16101,16103],{"href":16102},"#prometheus","Coletando métricas com Prometheus",[20,16105,16106],{},[23,16107,16108],{"href":73},"Erros comuns (e como evitá-los)",[20,16110,16111],{},[23,16112,16114],{"href":16113},"#conclusao","Conclusão e próximos passos",[968,16116],{},[12,16118,16120],{"id":16119},"o-que-é-observabilidade-e-por-que-você-deveria-se-importar-o-que-e","O que é observabilidade (e por que você deveria se importar) {#o-que-e}",[86,16122,16123,16124,16127,16128,3022,16131,522],{},"Imagine que você dirige um carro. No painel, há alguns indicadores:\nmedidor de combustível, conta-giros, luz de temperatura do motor e a\nluzinha do óleo. Cada um desses indicadores te diz algo sobre o estado\ndo carro ",[89,16125,16126],{},"agora",". Mas se o carro enguiçar no meio da estrada, só olhar\no painel não resolve: você precisa abrir o capô, entender o que aconteceu\ne por que aconteceu. É aí que entra a diferença entre ",[89,16129,16130],{},"saber que algo\ndeu errado",[89,16132,16133],{},"entender por que deu errado",[86,16135,16136,16138,16139,16142,16143,16146,16147,16150],{},[89,16137,6438],{}," é uma palavra emprestada da engenharia de controle\n(uma área da matemática que estuda sistemas dinâmicos). Em termos\nsimples: um sistema é ",[501,16140,16141],{},"observável"," quando, apenas olhando para as\ninformações que ele emite para fora, você consegue deduzir o estado\ninterno dele sem precisar \"abrir a caixa\". Em software, isso significa:\nsua aplicação te dá dados suficientes para que você descubra ",[89,16144,16145],{},"por que","\nalgo deu errado, não apenas ",[89,16148,16149],{},"que"," deu errado.",[710,16152,16153],{},[86,16154,16155,16158,16159,16161,16162,1327,16165,16168,16169,16172],{},[89,16156,16157],{},"Analogia rápida",": Monitorar é como ter um alarme de incêndio — ele\navisa quando o fogo já começou. Observabilidade é como ter sensores de\nfumaça, temperatura, umidade e um histórico de manutenção do prédio\ninteiros — você entende não só ",[501,16160,16149],{}," pegou fogo, mas ",[501,16163,16164],{},"onde",[501,16166,16167],{},"por quê","\ne ",[501,16170,16171],{},"como evitar"," da próxima vez.",[86,16174,16175,16176,16180,16181,16184],{},"Em sistemas modernos, especialmente quando usamos\n",[23,16177,16179],{"href":16178},"..\u002Fsistemas\u002Farquitetura-de-microservicos-escalaveis","microserviços",",\na observabilidade deixa de ser um \"nice to have\" e vira necessidade.\nUma simples requisição de \"finalizar compra\" pode passar por 6, 10 ou\n20 serviços diferentes antes de retornar para o usuário. Quando algo\ndemora 3 segundos a mais, você precisa saber ",[89,16182,16183],{},"em qual daquelas 20\netapas"," o tempo foi gasto.",[283,16186,16188],{"id":16187},"por-que-isso-custa-caro-quando-ignorado","Por que isso custa caro quando ignorado",[86,16190,16191,16192,3022,16198,522],{},"Vamos ser diretos sobre dinheiro. Um incidente de indisponibilidade em\nplataformas de médio porte custa, em média, entre R$ 15 mil e R$ 100 mil\npor hora dependendo do segmento — considerando receita perdida, equipe\ntrabalhando em overtime e dano reputacional. Quando sua equipe leva 4\nhoras para descobrir que o problema era uma query lenta no banco, em vez\nde 15 minutos, a diferença é justamente o que a observabilidade te dá:\n",[89,16193,16194,16195,13125],{},"tempo de detecção menor (MTTD, ",[501,16196,16197],{},"Mean Time To Detect",[89,16199,16200,16201,13125],{},"tempo de\nresolução menor (MTTR, ",[501,16202,16203],{},"Mean Time To Recovery",[710,16205,16206],{},[86,16207,16208,4958,16211,16213,16214,4958,16217,16219],{},[89,16209,16210],{},"MTTD",[501,16212,16197],{},"): tempo médio entre o problema\nacontecer e você descobrir que aconteceu.\n",[89,16215,16216],{},"MTTR",[501,16218,16203],{},"): tempo médio entre o problema\nacontecer e o serviço voltar a funcionar normalmente.",[968,16221],{},[12,16223,16225],{"id":16224},"monitoramento-vs-observabilidade-a-diferença-que-importa-monitoramento-vs","Monitoramento vs. observabilidade: a diferença que importa {#monitoramento-vs}",[86,16227,16228],{},"Esses dois termos são frequentemente usados como sinônimos, mas existe\numa diferença prática importante que vai guiar como você implementa as\ncoisas.",[86,16230,16231,16233,16234,16237],{},[89,16232,9274],{}," é o conjunto de ferramentas e práticas que te dizem\nse algo está funcionando ou não, baseado em ",[89,16235,16236],{},"perguntas que você já\nsabia que ia fazer",". \"A API está respondendo?\", \"O uso de CPU passou\nde 80%?\", \"Quantos erros 500 aconteceram na última hora?\" — tudo isso é\nmonitoramento. Você pré-configura alertas e dashboards para responder a\nessas perguntas.",[86,16239,16240,16242,16243,16246],{},[89,16241,6438],{}," é mais ampla: é a capacidade de fazer perguntas que\n",[89,16244,16245],{},"você não sabia que ia precisar fazer",". \"Por que o checkout ficou lento\nsó para usuários do Brasil usando o navegador Safari entre 14h e 15h de\nontem?\" — essa é uma pergunta que nenhum dashboard pré-configurado\nvai responder sozinho. Você só consegue respondê-la se tiver coletado\ndados ricos o suficiente (traces, métricas com dimensões e logs\nestruturados) durante o incidente.",[165,16248,16249,16259],{},[168,16250,16251],{},[171,16252,16253,16255,16257],{},[174,16254,13898],{},[174,16256,9274],{},[174,16258,6438],{},[184,16260,16261,16272,16283,16294,16305,16316],{},[171,16262,16263,16266,16269],{},[189,16264,16265],{},"Pergunta típica",[189,16267,16268],{},"\"Está funcionando?\"",[189,16270,16271],{},"\"Por que não está funcionando?\"",[171,16273,16274,16277,16280],{},[189,16275,16276],{},"Quando configurar",[189,16278,16279],{},"Antes do problema",[189,16281,16282],{},"Preparado para o desconhecido",[171,16284,16285,16288,16291],{},[189,16286,16287],{},"Tipo de dado",[189,16289,16290],{},"Métricas pré-definidas",[189,16292,16293],{},"Traces + métricas + logs ricos",[171,16295,16296,16299,16302],{},[189,16297,16298],{},"Custo de setup",[189,16300,16301],{},"Baixo\u002Fmédio",[189,16303,16304],{},"Médio\u002Falto",[171,16306,16307,16310,16313],{},[189,16308,16309],{},"Resolve incidentes novos?",[189,16311,16312],{},"Não muito",[189,16314,16315],{},"Sim, é o objetivo principal",[171,16317,16318,16321,16324],{},[189,16319,16320],{},"Exemplo de ferramenta",[189,16322,16323],{},"Alerta de CPU no Grafana",[189,16325,16326],{},"Trace no Jaeger mostrando 200ms em chamada ao Redis",[86,16328,16329,16332,16333,16336],{},[89,16330,16331],{},"A boa notícia",": você não precisa escolher um ou outro. Observabilidade\n",[501,16334,16335],{},"inclui"," monitoramento. Você implementa observabilidade e, como\nsubproduto, ganha monitoramento de graça.",[968,16338],{},[12,16340,16342],{"id":16341},"os-três-pilares-logs-métricas-e-traces-tres-pilares","Os três pilares: logs, métricas e traces {#tres-pilares}",[86,16344,16345],{},"Toda a literatura de observabilidade gira em torno de três tipos de\ndados, frequentemente chamados de \"os três pilares\". Vamos apresentá-los\naqui de forma breve e aprofundar cada um nas seções seguintes.",[17,16347,16348,16354,16360],{},[20,16349,16350,16353],{},[89,16351,16352],{},"Logs",": registros de eventos discretos que aconteceram na sua\naplicação. \"Usuário X fez login às 14:32\", \"Falha ao conectar no\nbanco: timeout após 5s\".",[20,16355,16356,16359],{},[89,16357,16358],{},"Métricas",": números agregados ao longo do tempo. \"Taxa de requisições\npor segundo\", \"Uso de memória agora\", \"Quantidade de erros na última\nhora\".",[20,16361,16362,16365],{},[89,16363,16364],{},"Traces",": o caminho completo de uma única requisição atravessando\ntodos os serviços pelos quais ela passou, com a duração de cada\netapa.",[86,16367,16368],{},"Cada um resolve um problema diferente. Nenhum resolve tudo sozinho.",[283,16370,16372],{"id":16371},"tabela-comparativa-dos-três-pilares","Tabela comparativa dos três pilares",[165,16374,16375,16393],{},[168,16376,16377],{},[171,16378,16379,16382,16385,16388,16391],{},[174,16380,16381],{},"Pilar",[174,16383,16384],{},"Responde a...",[174,16386,16387],{},"Granularidade",[174,16389,16390],{},"Custo de armazenar",[174,16392,1588],{},[184,16394,16395,16413,16431],{},[171,16396,16397,16401,16404,16407,16410],{},[189,16398,16399],{},[89,16400,16352],{},[189,16402,16403],{},"\"O que aconteceu nesse momento exato?\"",[189,16405,16406],{},"Alta (1 evento por linha)",[189,16408,16409],{},"Alto (cresce rápido)",[189,16411,16412],{},"Debugging profundo, auditoria",[171,16414,16415,16419,16422,16425,16428],{},[189,16416,16417],{},[89,16418,16358],{},[189,16420,16421],{},"\"Qual é a tendência ao longo do tempo?\"",[189,16423,16424],{},"Baixa (números agregados)",[189,16426,16427],{},"Baixo (só números)",[189,16429,16430],{},"Dashboards, alertas, tendências",[171,16432,16433,16437,16440,16443,16446],{},[189,16434,16435],{},[89,16436,16364],{},[189,16438,16439],{},"\"Por onde essa requisição passou e onde demorou?\"",[189,16441,16442],{},"Média (1 trace por requisição)",[189,16444,16445],{},"Médio",[189,16447,16448],{},"Diagnóstico de latência, depuração entre serviços",[710,16450,16451],{},[86,16452,16453,16456],{},[89,16454,16455],{},"Dica prática",": se você está começando do zero, comece por métricas\n(mais baratas e já resolvem alertas), depois traces (resolvem\ndepuração de latência) e por último logs estruturados (resolvem\nauditoria e debugging detalhado). Inverter essa ordem é um erro\ncomum — logs sem traces e métricas geram muito ruído e pouco sinal.",[968,16458],{},[12,16460,16462],{"id":16461},"opentelemetry-o-que-é-e-por-que-existe-opentelemetry","OpenTelemetry: o que é e por que existe {#opentelemetry}",[86,16464,16465,16468,16469,16472,16473,4958,16475,16478,16479,3022,16482,16485],{},[89,16466,16467],{},"OpenTelemetry"," (frequentemente abreviado como ",[89,16470,16471],{},"OTel",") é um projeto\nopen source mantido pela ",[89,16474,13811],{},[501,16476,16477],{},"Cloud Native Computing Foundation",",\na mesma fundação que cuida do Kubernetes). Ele nasceu em 2019 da fusão\nde dois projetos anteriores — ",[89,16480,16481],{},"OpenTracing",[89,16483,16484],{},"OpenCensus"," — que\nfaziam coisas parecidas e competiam entre si. A comunidade percebeu\nque fazer dois padrões concorrentes não ajudava ninguém, então unificou\ntudo em um só.",[283,16487,16489],{"id":16488},"o-problema-que-o-opentelemetry-resolve","O problema que o OpenTelemetry resolve",[86,16491,16492],{},"Antes do OTel, se você quisesse coletar traces da sua aplicação, precisava\ninstalar o SDK (Software Development Kit, o pacote de bibliotecas) de\numa ferramenta específica — Jaeger, Zipkin, Datadog, New Relic, etc. Cada\numa com sua própria API. Se depois você quisesse trocar de Jaeger para\nDatadog, precisava reescrever toda a instrumentação do código. Era\ncaro, chato e prendia você a um fornecedor.",[710,16494,16495],{},[86,16496,16497,16499],{},[89,16498,3011],{},": é como comprar uma TV que só funciona com uma marca de\nfone de ouvido. Se você quiser trocar o fone, tem que trocar a TV\njunto. O OpenTelemetry é como uma TV com entrada Bluetooth padrão —\nfunciona com qualquer fone, de qualquer marca.",[86,16501,16502,16503,16506,16507,16510],{},"O OTel resolve isso fornecendo ",[89,16504,16505],{},"uma única API padrão"," para\ninstrumentar sua aplicação. Você escreve a instrumentação uma vez e\ndecide depois para onde enviar os dados (que chamamos de ",[501,16508,16509],{},"backend",",\no destino final dos dados) — Jaeger, Prometheus, Datadog, Honeycomb,\nTempo, o que for. A troca de backend vira uma mudança de configuração,\nnão de código.",[283,16512,16514],{"id":16513},"como-o-opentelemetry-é-organizado","Como o OpenTelemetry é organizado",[86,16516,16517],{},"O projeto se divide em três partes principais:",[528,16519,16520,16525,16531],{},[20,16521,16522,16524],{},[89,16523,6416],{},": as interfaces e padrões que você importa no código (spans,\nmétricas, logs). É o que você \"chama\" dentro da sua aplicação.",[20,16526,16527,16530],{},[89,16528,16529],{},"SDK",": a implementação concreta da API. É o motor que de fato coleta,\nprocessa e exporta os dados para algum backend.",[20,16532,16533,16536],{},[89,16534,16535],{},"Collector",": um processo separado (um \"agente\") que recebe dados de\nvárias aplicações, processa, filtra e envia para um ou mais backends.\nImagine como uma central de triagem dos Correios — todo mundo entrega\nlá e ele distribui para os destinos certos.",[506,16538,16541],{"className":16539,"code":16540,"language":511},[509],"[App A] ─┐\n[App B] ─┼─→ [OpenTelemetry Collector] ─┬─→ [Jaeger] (traces)\n[App C] ─┘                              ├─→ [Prometheus] (métricas)\n                                         └─→ [Loki] (logs)\n",[513,16542,16540],{"__ignoreMap":515},[283,16544,16546],{"id":16545},"por-que-usar-opentelemetry-em-vez-de-ferramentas-diretamente","Por que usar OpenTelemetry em vez de ferramentas diretamente?",[165,16548,16549,16562],{},[168,16550,16551],{},[171,16552,16553,16556,16559],{},[174,16554,16555],{},"Motivo",[174,16557,16558],{},"Sem OTel",[174,16560,16561],{},"Com OTel",[184,16563,16564,16575,16586,16597,16607],{},[171,16565,16566,16569,16572],{},[189,16567,16568],{},"Trocar de backend",[189,16570,16571],{},"Reescrever código",[189,16573,16574],{},"Mudar config",[171,16576,16577,16580,16583],{},[189,16578,16579],{},"Padronizar times",[189,16581,16582],{},"Cada time escolhe uma ferramenta",[189,16584,16585],{},"Todos falam a mesma língua",[171,16587,16588,16591,16594],{},[189,16589,16590],{},"Vendor lock-in",[189,16592,16593],{},"Alto (preso a um fornecedor)",[189,16595,16596],{},"Baixo (padrão aberto)",[171,16598,16599,16601,16604],{},[189,16600,10601],{},[189,16602,16603],{},"Fragmentada",[189,16605,16606],{},"Unificada (CNCF)",[171,16608,16609,16612,16615],{},[189,16610,16611],{},"Suporte a linguagens",[189,16613,16614],{},"Varia por ferramenta",[189,16616,16617],{},"11+ linguagens oficiais",[968,16619],{},[12,16621,16623],{"id":16622},"tracing-explicado-com-analogias-tracing","Tracing explicado com analogias {#tracing}",[86,16625,16626,16629],{},[89,16627,16628],{},"Tracing"," é o pilar que mais confunde iniciantes, mas também é o mais\npoderoso quando se trata de depurar sistemas distribuídos. Vamos de\nanalogia antes de definição técnica.",[710,16631,16632],{},[86,16633,16634,16636,16637,16640],{},[89,16635,3011],{},": Tracing é como rastrear um pacote dos Correios. Quando\nvocê envia uma encomenda, recebe um código de rastreio. Com ele, você\nvê cada etapa: \"Objeto postado em São Paulo\", \"Em trânsito para\ncentro de distribuição do Rio\", \"Saiu para entrega\", \"Entregue\". Para\ncada etapa, há um ",[89,16638,16639],{},"horário",". Subtraindo os horários, você descobre\nque o pacote ficou 2 dias parado no centro de distribuição — foi ali\no gargalo. O tracing de software faz exatamente isso, mas com uma\nrequisição em vez de um pacote.",[283,16642,16644],{"id":16643},"conceitos-chave-do-tracing","Conceitos-chave do tracing",[86,16646,1160,16647,16650,16651,522],{},[89,16648,16649],{},"Trace"," é o caminho completo de uma requisição, do momento em que\nentra no seu sistema até o momento em que a resposta sai. Um trace é\ncomposto por um ou mais ",[89,16652,16653],{},"Spans",[86,16655,1160,16656,16659],{},[89,16657,16658],{},"Span"," é uma unidade de trabalho dentro do trace. Cada chamada a\num serviço, cada query de banco, cada chamada a uma API externa pode\nser um span. Um span tem:",[528,16661,16662,16668,16674,16684,16690],{},[20,16663,16664,16667],{},[89,16665,16666],{},"Nome",": o que foi feito (\"GET \u002Fcheckout\", \"SELECT FROM orders\").",[20,16669,16670,16673],{},[89,16671,16672],{},"Tempo de início e duração",": quanto tempo levou.",[20,16675,16676,16679,16680,16683],{},[89,16677,16678],{},"Atributos"," (ou ",[501,16681,16682],{},"tags","): metadados extras (\"user.id=42\",\n\"http.status_code=500\").",[20,16685,16686,16689],{},[89,16687,16688],{},"Eventos",": marcos pontuais dentro do span (\"cache miss às 14:32:05\").",[20,16691,16692,16695],{},[89,16693,16694],{},"Span pai",": qual span o chamou. É isso que cria a árvore hierárquica.",[86,16697,1160,16698,16701,16702,16705,16706,16709,16710,522],{},[89,16699,16700],{},"SpanContext"," é a identidade do span: um ",[89,16703,16704],{},"Trace ID"," (igual para\ntodos os spans do mesmo trace) e um ",[89,16707,16708],{},"Span ID"," (único por span). É\nessa identidade que permite que spans de serviços diferentes sejam\n\"amarrados\" num único trace — o que chamamos de ",[89,16711,16712],{},"propagação de\ncontexto",[506,16714,16717],{"className":16715,"code":16716,"language":511},[509],"Trace (Trace ID: 7a3f...) — total: 850ms\n├─ Span 1: GET \u002Fcheckout (850ms)        [Serviço: API Gateway]\n   ├─ Span 2: validar_token (12ms)       [Serviço: Auth]\n   ├─ Span 3: buscar_carrinho (180ms)    [Serviço: Carrinho]\n   │  └─ Span 4: SELECT cart_items (165ms) [Banco: PostgreSQL]\n   └─ Span 5: processar_pagamento (640ms) [Serviço: Pagamentos]\n      └─ Span 6: POST \u002Fcharge (620ms)    [API externa: Stripe]\n",[513,16718,16716],{"__ignoreMap":515},[86,16720,16721,16722,522],{},"Nesse exemplo, fica óbvio que o gargalo está na chamada ao Stripe\n(620ms de 850ms totais). Sem tracing, você saberia só que\n\"o checkout está lento\". Com tracing, você sabe ",[89,16723,16724],{},"exatamente onde",[283,16726,16728],{"id":16727},"o-que-é-propagação-de-contexto","O que é propagação de contexto?",[86,16730,16731,16732,16735,16736,16739],{},"Quando a API Gateway chama o serviço de Pagamentos, ela precisa \"passar\nadiante\" o Trace ID, senão o Pagamentos vai gerar um trace novo e os\ndois nunca serão conectados. Isso é feito injetando cabeçalhos HTTP\npadronizados (no padrão ",[89,16733,16734],{},"W3C Trace Context",", um padrão internacional\nreconhecido pelo ",[501,16737,16738],{},"World Wide Web Consortium",") na requisição:",[506,16741,16744],{"className":16742,"code":16743,"language":511},[509],"traceparent: 00-7a3f...-b2c1...-01\n             │  │       │       └── flags\n             │  │       └── span ID atual\n             │  └── trace ID (igual para todo o trace)\n             └── versão do formato\n",[513,16745,16743],{"__ignoreMap":515},[86,16747,16748,16749,16752],{},"O OpenTelemetry faz essa propagação ",[89,16750,16751],{},"automaticamente"," quando você\nusa as bibliotecas de instrumentação oficiais — você não precisa\nescrever esses cabeçalhos à mão.",[968,16754],{},[12,16756,16758],{"id":16757},"métricas-explicadas-com-analogias-metricas","Métricas explicadas com analogias {#metricas}",[710,16760,16761],{},[86,16762,16763,16765,16766,16768],{},[89,16764,3011],{},": Métricas são como o painel do carro. Você não quer\nsaber a velocidade exata em cada milissegundo da viagem (isso seria\num log, e ia gerar uma quantidade absurda de dados). Você quer o\nvelocímetro mostrando ",[89,16767,16126],{},", e um histórico médio de \"km\u002Fh nos\núltimos 10 minutos\". Métricas são números agregados que te dão uma\nvisão geral, sem armazenar cada evento individual.",[86,16770,16771],{},"Métricas são números que descrevem algum aspecto do seu sistema,\ncoletados ao longo do tempo. Exemplos clássicos:",[528,16773,16774,16783,16792],{},[20,16775,16776,4958,16779,16782],{},[89,16777,16778],{},"Contadores",[501,16780,16781],{},"counters","): só aumentam. Ex.: \"total de requisições\ndesde que o serviço subiu\". Útil para calcular taxas (requisições por\nsegundo derivando o contador).",[20,16784,16785,4958,16788,16791],{},[89,16786,16787],{},"Medidores",[501,16789,16790],{},"gauges","): sobem e descem. Ex.: \"uso de memória agora\",\n\"conexões abertas no banco\".",[20,16793,16794,16797,16798,16801],{},[89,16795,16796],{},"Histogramas",": distribuição de valores. Ex.: \"quantas requisições\nlevaram menos de 100ms, entre 100ms e 500ms, mais de 500ms\". É a base\npara calcular percentis como ",[89,16799,16800],{},"p99"," (o tempo abaixo do qual 99% das\nrequisições são respondidas).",[710,16803,16804],{},[86,16805,16806,16808,16809,16812],{},[89,16807,16800],{}," significa que 99% das requisições foram mais rápidas que esse\nvalor. Se o p99 é 800ms, significa que 1 em cada 100 requisições\ndemorou mais que 800ms. É uma métrica muito usada porque a ",[89,16810,16811],{},"média","\nesconde os usuários que tiveram a pior experiência — e geralmente\nsão esses os que reclamam.",[283,16814,16816],{"id":16815},"tipos-de-métricas-no-opentelemetry","Tipos de métricas no OpenTelemetry",[165,16818,16819,16833],{},[168,16820,16821],{},[171,16822,16823,16826,16828,16830],{},[174,16824,16825],{},"Instrumento",[174,16827,6615],{},[174,16829,5731],{},[174,16831,16832],{},"Equivalente em dashboard",[184,16834,16835,16849,16863,16877],{},[171,16836,16837,16840,16843,16846],{},[189,16838,16839],{},"Counter",[189,16841,16842],{},"Só cresce",[189,16844,16845],{},"Total de erros 500",[189,16847,16848],{},"Número que só aumenta",[171,16850,16851,16854,16857,16860],{},[189,16852,16853],{},"Up\u002FDown Counter",[189,16855,16856],{},"Cresce e decresce",[189,16858,16859],{},"Conexões ativas",[189,16861,16862],{},"Vai e volta",[171,16864,16865,16868,16871,16874],{},[189,16866,16867],{},"Gauge",[189,16869,16870],{},"Valor instantâneo",[189,16872,16873],{},"Memória em uso agora",[189,16875,16876],{},"Velocímetro",[171,16878,16879,16882,16885,16888],{},[189,16880,16881],{},"Histogram",[189,16883,16884],{},"Distribuição",[189,16886,16887],{},"Latência das requisições",[189,16889,16890],{},"Gráfico de barras por faixa",[86,16892,16893,16894,16897],{},"Métricas são ",[89,16895,16896],{},"baratas"," de armazenar: são só números com timestamps.\nPor isso são ideais para dashboards de longo prazo (\"como estava a\nlatência no mês passado?\") e para alertas (\"alerte se o p99 passar de\n1 segundo por 5 minutos seguidos\").",[968,16899],{},[12,16901,16903],{"id":16902},"logs-explicados-com-analogias-logs","Logs explicados com analogias {#logs}",[710,16905,16906],{},[86,16907,16908,16910],{},[89,16909,3011],{},": Logs são como o diário de bordo de um navio. O capitão\nanota tudo que acontece: \"14:00 — partiu do porto\", \"18:30 — tempestade\nà vista, reduzindo velocidade\", \"22:15 — motor 2 com vibração anormal,\nequipe de manutenção acionada\". Cada anotação é um evento isolado,\ncom data e hora. Sozinha não conta muito, mas quando você junta todas\nna ordem certa, reconstrói a história inteira.",[86,16912,16913,16915],{},[89,16914,16352],{}," são registros de eventos discretos. Cada log é uma linha (ou\nobjeto) que diz: \"nesse timestamp, aconteceu essa coisa, com esses\ndetalhes\". Existem três níveis de maturidade em como escrever logs:",[17,16917,16918,16928,16934],{},[20,16919,16920,16923,16924,16927],{},[89,16921,16922],{},"Logs em texto livre"," (iniciante): ",[513,16925,16926],{},"print(\"usuário logado\")",". Fácil\nde escrever, difícil de buscar e filtrar depois.",[20,16929,16930,16933],{},[89,16931,16932],{},"Logs estruturados"," (intermediário): JSON com campos. Buscável,\nfiltrável, indexável.",[20,16935,16936,16939,16940,3022,16943,16946],{},[89,16937,16938],{},"Logs com contexto de trace"," (avançado): cada log carrega o\n",[513,16941,16942],{},"trace_id",[513,16944,16945],{},"span_id"," atuais, permitindo pular de um trace no\nJaeger direto para os logs relacionados.",[506,16948,16952],{"className":16949,"code":16950,"language":16951,"meta":515,"style":515},"language-json shiki shiki-themes github-light github-dark","\u002F\u002F Log estruturado SEM contexto de trace\n{\"timestamp\": \"2026-08-26T14:32:05Z\", \"level\": \"error\", \"msg\": \"falha no checkout\"}\n\n\u002F\u002F Log estruturado COM contexto de trace (ideal)\n{\"timestamp\": \"2026-08-26T14:32:05Z\", \"level\": \"error\", \"msg\": \"falha no checkout\",\n \"trace_id\": \"7a3f...\", \"span_id\": \"b2c1...\", \"user.id\": 42, \"cart.total\": 199.90}\n","json",[513,16953,16954,16959,16994,16998,17003,17032],{"__ignoreMap":515},[1376,16955,16956],{"class":1378,"line":1379},[1376,16957,16958],{"class":1665},"\u002F\u002F Log estruturado SEM contexto de trace\n",[1376,16960,16961,16964,16967,16969,16972,16974,16977,16979,16982,16984,16987,16989,16992],{"class":1378,"line":984},[1376,16962,16963],{"class":1671},"{",[1376,16965,16966],{"class":1770},"\"timestamp\"",[1376,16968,1679],{"class":1671},[1376,16970,16971],{"class":1682},"\"2026-08-26T14:32:05Z\"",[1376,16973,1327],{"class":1671},[1376,16975,16976],{"class":1770},"\"level\"",[1376,16978,1679],{"class":1671},[1376,16980,16981],{"class":1682},"\"error\"",[1376,16983,1327],{"class":1671},[1376,16985,16986],{"class":1770},"\"msg\"",[1376,16988,1679],{"class":1671},[1376,16990,16991],{"class":1682},"\"falha no checkout\"",[1376,16993,5170],{"class":1671},[1376,16995,16996],{"class":1378,"line":981},[1376,16997,1398],{"emptyLinePlaceholder":898},[1376,16999,17000],{"class":1378,"line":1395},[1376,17001,17002],{"class":1665},"\u002F\u002F Log estruturado COM contexto de trace (ideal)\n",[1376,17004,17005,17007,17009,17011,17013,17015,17017,17019,17021,17023,17025,17027,17029],{"class":1378,"line":1401},[1376,17006,16963],{"class":1671},[1376,17008,16966],{"class":1770},[1376,17010,1679],{"class":1671},[1376,17012,16971],{"class":1682},[1376,17014,1327],{"class":1671},[1376,17016,16976],{"class":1770},[1376,17018,1679],{"class":1671},[1376,17020,16981],{"class":1682},[1376,17022,1327],{"class":1671},[1376,17024,16986],{"class":1770},[1376,17026,1679],{"class":1671},[1376,17028,16991],{"class":1682},[1376,17030,17031],{"class":1671},",\n",[1376,17033,17034,17037,17039,17042,17044,17047,17049,17052,17054,17057,17059,17062,17064,17067,17069,17072],{"class":1378,"line":1407},[1376,17035,17036],{"class":1770}," \"trace_id\"",[1376,17038,1679],{"class":1671},[1376,17040,17041],{"class":1682},"\"7a3f...\"",[1376,17043,1327],{"class":1671},[1376,17045,17046],{"class":1770},"\"span_id\"",[1376,17048,1679],{"class":1671},[1376,17050,17051],{"class":1682},"\"b2c1...\"",[1376,17053,1327],{"class":1671},[1376,17055,17056],{"class":1770},"\"user.id\"",[1376,17058,1679],{"class":1671},[1376,17060,17061],{"class":1770},"42",[1376,17063,1327],{"class":1671},[1376,17065,17066],{"class":1770},"\"cart.total\"",[1376,17068,1679],{"class":1671},[1376,17070,17071],{"class":1770},"199.90",[1376,17073,5170],{"class":1671},[86,17075,17076,17077,17079,17080,17083],{},"A diferença entre os dois exemplos acima é enorme na prática: com o\n",[513,17078,16942],{}," no log, quando você estiver olhando um trace lento no Jaeger,\npode copiar aquele ID e jogar na ferramenta de logs para ver ",[89,17081,17082],{},"todas as\nmensagens"," geradas durante aquela requisição específica — em todos os\nserviços.",[710,17085,17086],{},[86,17087,17088,17091],{},[89,17089,17090],{},"Aviso de custo",": logs são o pilar mais caro de armazenar. Um\nserviço medianamente usado pode gerar dezenas de GB de logs por dia.\nUse níveis de log (DEBUG, INFO, WARN, ERROR) com consciência e\nconsidere amostragem (sampling) para logs de baixo nível em produção.",[968,17093],{},[12,17095,17097],{"id":17096},"mão-na-massa-instrumentando-uma-api-fastapi-fastapi","Mão na massa: instrumentando uma API FastAPI {#fastapi}",[86,17099,17100,17101,17104],{},"Chegou a hora de ver código real. Vamos criar uma API simples com\n",[89,17102,17103],{},"FastAPI"," (um framework web em Python, rápido e moderno) e\ninstrumentá-la com OpenTelemetry para gerar traces. O objetivo é que,\na cada requisição recebida, a API gere um trace com spans para cada\noperação interna.",[283,17106,17108],{"id":17107},"pré-requisitos","Pré-requisitos",[528,17110,17111,17114],{},[20,17112,17113],{},"Python 3.10 ou superior",[20,17115,17116],{},"Familiaridade básica com terminal e pip",[283,17118,17120],{"id":17119},"passo-1-instalar-as-dependências","Passo 1: instalar as dependências",[506,17122,17124],{"className":11827,"code":17123,"language":11829,"meta":515,"style":515},"# Crie um ambiente virtual (boa prática — isola as bibliotecas do projeto)\npython -m venv .venv\nsource .venv\u002Fbin\u002Factivate  # no Windows: .venv\\Scripts\\activate\n\n# Instale o FastAPI, o servidor ASGI Uvicorn e os pacotes do OpenTelemetry\npip install fastapi uvicorn \\\n    opentelemetry-distro \\\n    opentelemetry-exporter-otlp \\\n    opentelemetry-instrumentation-fastapi \\\n    opentelemetry-instrumentation-requests \\\n    opentelemetry-instrumentation-logging\n\n# O opentelemetry-distro instala automaticamente as instrumentações\n# mais comuns. O \"bootstrap\" abaixo detecta suas libs e ativa as\n# instrumentações correspondentes sem você precisar escrever código.\nopentelemetry-bootstrap -a install\n",[513,17125,17126,17131,17144,17155,17159,17164,17179,17186,17193,17200,17207,17212,17216,17221,17226,17231],{"__ignoreMap":515},[1376,17127,17128],{"class":1378,"line":1379},[1376,17129,17130],{"class":1665},"# Crie um ambiente virtual (boa prática — isola as bibliotecas do projeto)\n",[1376,17132,17133,17135,17138,17141],{"class":1378,"line":984},[1376,17134,3441],{"class":11841},[1376,17136,17137],{"class":1770}," -m",[1376,17139,17140],{"class":1682}," venv",[1376,17142,17143],{"class":1682}," .venv\n",[1376,17145,17146,17149,17152],{"class":1378,"line":981},[1376,17147,17148],{"class":1770},"source",[1376,17150,17151],{"class":1682}," .venv\u002Fbin\u002Factivate",[1376,17153,17154],{"class":1665},"  # no Windows: .venv\\Scripts\\activate\n",[1376,17156,17157],{"class":1378,"line":1395},[1376,17158,1398],{"emptyLinePlaceholder":898},[1376,17160,17161],{"class":1378,"line":1401},[1376,17162,17163],{"class":1665},"# Instale o FastAPI, o servidor ASGI Uvicorn e os pacotes do OpenTelemetry\n",[1376,17165,17166,17169,17171,17174,17177],{"class":1378,"line":1407},[1376,17167,17168],{"class":11841},"pip",[1376,17170,11977],{"class":1682},[1376,17172,17173],{"class":1682}," fastapi",[1376,17175,17176],{"class":1682}," uvicorn",[1376,17178,11851],{"class":1770},[1376,17180,17181,17184],{"class":1378,"line":1413},[1376,17182,17183],{"class":1682},"    opentelemetry-distro",[1376,17185,11851],{"class":1770},[1376,17187,17188,17191],{"class":1378,"line":1419},[1376,17189,17190],{"class":1682},"    opentelemetry-exporter-otlp",[1376,17192,11851],{"class":1770},[1376,17194,17195,17198],{"class":1378,"line":1424},[1376,17196,17197],{"class":1682},"    opentelemetry-instrumentation-fastapi",[1376,17199,11851],{"class":1770},[1376,17201,17202,17205],{"class":1378,"line":1430},[1376,17203,17204],{"class":1682},"    opentelemetry-instrumentation-requests",[1376,17206,11851],{"class":1770},[1376,17208,17209],{"class":1378,"line":1436},[1376,17210,17211],{"class":1682},"    opentelemetry-instrumentation-logging\n",[1376,17213,17214],{"class":1378,"line":1442},[1376,17215,1398],{"emptyLinePlaceholder":898},[1376,17217,17218],{"class":1378,"line":1447},[1376,17219,17220],{"class":1665},"# O opentelemetry-distro instala automaticamente as instrumentações\n",[1376,17222,17223],{"class":1378,"line":1453},[1376,17224,17225],{"class":1665},"# mais comuns. O \"bootstrap\" abaixo detecta suas libs e ativa as\n",[1376,17227,17228],{"class":1378,"line":1459},[1376,17229,17230],{"class":1665},"# instrumentações correspondentes sem você precisar escrever código.\n",[1376,17232,17233,17236,17239],{"class":1378,"line":2200},[1376,17234,17235],{"class":11841},"opentelemetry-bootstrap",[1376,17237,17238],{"class":1770}," -a",[1376,17240,17241],{"class":1682}," install\n",[283,17243,17245],{"id":17244},"passo-2-criar-a-aplicação","Passo 2: criar a aplicação",[86,17247,17248,17249,4171],{},"Crie um arquivo ",[513,17250,17251],{},"app.py",[506,17253,17255],{"className":3439,"code":17254,"language":3441,"meta":515,"style":515},"# app.py — API de exemplo para demonstrar observabilidade com OTel\nimport logging\nfrom fastapi import FastAPI\nimport requests\n\n# O pacote abaixo configura o logging do Python para injetar\n# automaticamente trace_id e span_id em cada log gerado dentro de um span.\nfrom opentelemetry.instrumentation.logging import LoggingInstrumentor\n\n# A instrumentação do FastAPI cria um span automaticamente para cada\n# requisição HTTP recebida, com nome, método, status e duração.\nfrom opentelemetry.instrumentation.fastapi import FastAPIInstrumentor\n\n# A instrumentação do requests cria spans para cada chamada HTTP\n# que sua aplicação fizer para serviços externos (propagando o contexto).\nfrom opentelemetry.instrumentation.requests import RequestsInstrumentor\n\n# Biblioteca principal de tracing — usamos para criar spans manuais.\nfrom opentelemetry import trace\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\napp = FastAPI()\n\n# Instrumenta automaticamente todas as rotas do FastAPI.\n# A partir daqui, cada request gera um trace com um span raiz.\nFastAPIInstrumentor.instrument_app(app)\n\n# Instrumenta chamadas externas com a lib requests.\nRequestsInstrumentor().instrument()\n\n# Faz cada linha de log carregar o trace_id e span_id atuais.\nLoggingInstrumentor().instrument(set_logging_format=True)\n\n\n@app.get(\"\u002F\")\ndef root():\n    # Rota simples — gera um span automático pelo FastAPIInstrumentor.\n    return {\"status\": \"ok\"}\n\n\n@app.get(\"\u002Flento\")\ndef lento():\n    # Simula uma rota que chama um serviço externo demorado.\n    tracer = trace.get_tracer(__name__)\n\n    # Span manual: vamos medir quanto tempo levamos \"pensando\".\n    with tracer.start_as_current_span(\"processar_pagamento\") as span:\n        # Adicionamos atributos ao span — informações extras pesquisáveis.\n        span.set_attribute(\"pagamento.metodo\", \"cartao\")\n        span.set_attribute(\"pagamento.moeda\", \"BRL\")\n\n        # Simula uma chamada externa (instrumentada, vira subspan).\n        # Substitua por uma URL real quando estiver testando.\n        try:\n            resp = requests.get(\"https:\u002F\u002Fhttpbin.org\u002Fdelay\u002F2\", timeout=5)\n            span.set_attribute(\"pagamento.status_http\", resp.status_code)\n        except Exception as e:\n            # Registra um evento de erro dentro do span.\n            span.record_exception(e)\n            span.set_status(trace.Status(trace.StatusCode.ERROR))\n            logger.error(\"falha ao chamar gateway de pagamento: %s\", e)\n            return {\"erro\": \"falha no pagamento\"}\n\n        logger.info(\"pagamento processado com sucesso\")\n        return {\"status\": \"pago\", \"tempo_total_ms\": 2000}\n",[513,17256,17257,17262,17266,17271,17276,17280,17285,17290,17295,17299,17304,17309,17314,17318,17323,17328,17333,17337,17342,17347,17351,17356,17361,17365,17369,17373,17378,17383,17388,17392,17397,17402,17406,17411,17416,17420,17424,17429,17434,17439,17443,17447,17451,17456,17461,17466,17471,17475,17480,17485,17490,17495,17500,17504,17509,17514,17518,17523,17528,17533,17538,17543,17548,17553,17558,17562,17567],{"__ignoreMap":515},[1376,17258,17259],{"class":1378,"line":1379},[1376,17260,17261],{},"# app.py — API de exemplo para demonstrar observabilidade com OTel\n",[1376,17263,17264],{"class":1378,"line":984},[1376,17265,4379],{},[1376,17267,17268],{"class":1378,"line":981},[1376,17269,17270],{},"from fastapi import FastAPI\n",[1376,17272,17273],{"class":1378,"line":1395},[1376,17274,17275],{},"import requests\n",[1376,17277,17278],{"class":1378,"line":1401},[1376,17279,1398],{"emptyLinePlaceholder":898},[1376,17281,17282],{"class":1378,"line":1407},[1376,17283,17284],{},"# O pacote abaixo configura o logging do Python para injetar\n",[1376,17286,17287],{"class":1378,"line":1413},[1376,17288,17289],{},"# automaticamente trace_id e span_id em cada log gerado dentro de um span.\n",[1376,17291,17292],{"class":1378,"line":1419},[1376,17293,17294],{},"from opentelemetry.instrumentation.logging import LoggingInstrumentor\n",[1376,17296,17297],{"class":1378,"line":1424},[1376,17298,1398],{"emptyLinePlaceholder":898},[1376,17300,17301],{"class":1378,"line":1430},[1376,17302,17303],{},"# A instrumentação do FastAPI cria um span automaticamente para cada\n",[1376,17305,17306],{"class":1378,"line":1436},[1376,17307,17308],{},"# requisição HTTP recebida, com nome, método, status e duração.\n",[1376,17310,17311],{"class":1378,"line":1442},[1376,17312,17313],{},"from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor\n",[1376,17315,17316],{"class":1378,"line":1447},[1376,17317,1398],{"emptyLinePlaceholder":898},[1376,17319,17320],{"class":1378,"line":1453},[1376,17321,17322],{},"# A instrumentação do requests cria spans para cada chamada HTTP\n",[1376,17324,17325],{"class":1378,"line":1459},[1376,17326,17327],{},"# que sua aplicação fizer para serviços externos (propagando o contexto).\n",[1376,17329,17330],{"class":1378,"line":2200},[1376,17331,17332],{},"from opentelemetry.instrumentation.requests import RequestsInstrumentor\n",[1376,17334,17335],{"class":1378,"line":2208},[1376,17336,1398],{"emptyLinePlaceholder":898},[1376,17338,17339],{"class":1378,"line":2219},[1376,17340,17341],{},"# Biblioteca principal de tracing — usamos para criar spans manuais.\n",[1376,17343,17344],{"class":1378,"line":2227},[1376,17345,17346],{},"from opentelemetry import trace\n",[1376,17348,17349],{"class":1378,"line":2241},[1376,17350,1398],{"emptyLinePlaceholder":898},[1376,17352,17353],{"class":1378,"line":2253},[1376,17354,17355],{},"logging.basicConfig(level=logging.INFO)\n",[1376,17357,17358],{"class":1378,"line":2283},[1376,17359,17360],{},"logger = logging.getLogger(__name__)\n",[1376,17362,17363],{"class":1378,"line":2296},[1376,17364,1398],{"emptyLinePlaceholder":898},[1376,17366,17367],{"class":1378,"line":2311},[1376,17368,6510],{},[1376,17370,17371],{"class":1378,"line":2316},[1376,17372,1398],{"emptyLinePlaceholder":898},[1376,17374,17375],{"class":1378,"line":2324},[1376,17376,17377],{},"# Instrumenta automaticamente todas as rotas do FastAPI.\n",[1376,17379,17380],{"class":1378,"line":2333},[1376,17381,17382],{},"# A partir daqui, cada request gera um trace com um span raiz.\n",[1376,17384,17385],{"class":1378,"line":2340},[1376,17386,17387],{},"FastAPIInstrumentor.instrument_app(app)\n",[1376,17389,17390],{"class":1378,"line":2351},[1376,17391,1398],{"emptyLinePlaceholder":898},[1376,17393,17394],{"class":1378,"line":2362},[1376,17395,17396],{},"# Instrumenta chamadas externas com a lib requests.\n",[1376,17398,17399],{"class":1378,"line":2385},[1376,17400,17401],{},"RequestsInstrumentor().instrument()\n",[1376,17403,17404],{"class":1378,"line":2396},[1376,17405,1398],{"emptyLinePlaceholder":898},[1376,17407,17408],{"class":1378,"line":2411},[1376,17409,17410],{},"# Faz cada linha de log carregar o trace_id e span_id atuais.\n",[1376,17412,17413],{"class":1378,"line":2416},[1376,17414,17415],{},"LoggingInstrumentor().instrument(set_logging_format=True)\n",[1376,17417,17418],{"class":1378,"line":2422},[1376,17419,1398],{"emptyLinePlaceholder":898},[1376,17421,17422],{"class":1378,"line":2430},[1376,17423,1398],{"emptyLinePlaceholder":898},[1376,17425,17426],{"class":1378,"line":2448},[1376,17427,17428],{},"@app.get(\"\u002F\")\n",[1376,17430,17431],{"class":1378,"line":2457},[1376,17432,17433],{},"def root():\n",[1376,17435,17436],{"class":1378,"line":2464},[1376,17437,17438],{},"    # Rota simples — gera um span automático pelo FastAPIInstrumentor.\n",[1376,17440,17441],{"class":1378,"line":2475},[1376,17442,6582],{},[1376,17444,17445],{"class":1378,"line":2487},[1376,17446,1398],{"emptyLinePlaceholder":898},[1376,17448,17449],{"class":1378,"line":2499},[1376,17450,1398],{"emptyLinePlaceholder":898},[1376,17452,17453],{"class":1378,"line":2506},[1376,17454,17455],{},"@app.get(\"\u002Flento\")\n",[1376,17457,17458],{"class":1378,"line":2517},[1376,17459,17460],{},"def lento():\n",[1376,17462,17463],{"class":1378,"line":2528},[1376,17464,17465],{},"    # Simula uma rota que chama um serviço externo demorado.\n",[1376,17467,17468],{"class":1378,"line":2539},[1376,17469,17470],{},"    tracer = trace.get_tracer(__name__)\n",[1376,17472,17473],{"class":1378,"line":2553},[1376,17474,1398],{"emptyLinePlaceholder":898},[1376,17476,17477],{"class":1378,"line":2564},[1376,17478,17479],{},"    # Span manual: vamos medir quanto tempo levamos \"pensando\".\n",[1376,17481,17482],{"class":1378,"line":2576},[1376,17483,17484],{},"    with tracer.start_as_current_span(\"processar_pagamento\") as span:\n",[1376,17486,17487],{"class":1378,"line":2602},[1376,17488,17489],{},"        # Adicionamos atributos ao span — informações extras pesquisáveis.\n",[1376,17491,17492],{"class":1378,"line":2607},[1376,17493,17494],{},"        span.set_attribute(\"pagamento.metodo\", \"cartao\")\n",[1376,17496,17497],{"class":1378,"line":2613},[1376,17498,17499],{},"        span.set_attribute(\"pagamento.moeda\", \"BRL\")\n",[1376,17501,17502],{"class":1378,"line":2620},[1376,17503,1398],{"emptyLinePlaceholder":898},[1376,17505,17506],{"class":1378,"line":2631},[1376,17507,17508],{},"        # Simula uma chamada externa (instrumentada, vira subspan).\n",[1376,17510,17511],{"class":1378,"line":2641},[1376,17512,17513],{},"        # Substitua por uma URL real quando estiver testando.\n",[1376,17515,17516],{"class":1378,"line":2651},[1376,17517,4586],{},[1376,17519,17520],{"class":1378,"line":2658},[1376,17521,17522],{},"            resp = requests.get(\"https:\u002F\u002Fhttpbin.org\u002Fdelay\u002F2\", timeout=5)\n",[1376,17524,17525],{"class":1378,"line":2670},[1376,17526,17527],{},"            span.set_attribute(\"pagamento.status_http\", resp.status_code)\n",[1376,17529,17530],{"class":1378,"line":2685},[1376,17531,17532],{},"        except Exception as e:\n",[1376,17534,17535],{"class":1378,"line":6082},[1376,17536,17537],{},"            # Registra um evento de erro dentro do span.\n",[1376,17539,17540],{"class":1378,"line":6088},[1376,17541,17542],{},"            span.record_exception(e)\n",[1376,17544,17545],{"class":1378,"line":6093},[1376,17546,17547],{},"            span.set_status(trace.Status(trace.StatusCode.ERROR))\n",[1376,17549,17550],{"class":1378,"line":6099},[1376,17551,17552],{},"            logger.error(\"falha ao chamar gateway de pagamento: %s\", e)\n",[1376,17554,17555],{"class":1378,"line":6105},[1376,17556,17557],{},"            return {\"erro\": \"falha no pagamento\"}\n",[1376,17559,17560],{"class":1378,"line":6111},[1376,17561,1398],{"emptyLinePlaceholder":898},[1376,17563,17564],{"class":1378,"line":6117},[1376,17565,17566],{},"        logger.info(\"pagamento processado com sucesso\")\n",[1376,17568,17569],{"class":1378,"line":6123},[1376,17570,17571],{},"        return {\"status\": \"pago\", \"tempo_total_ms\": 2000}\n",[283,17573,17575],{"id":17574},"passo-3-rodar-com-o-otel-exportando-para-o-collector","Passo 3: rodar com o OTel exportando para o Collector",[86,17577,17578,17579,17582],{},"O OpenTelemetry fornece um comando ",[513,17580,17581],{},"opentelemetry-instrument"," que\nenvolve sua aplicação e configura a exportação dos dados. Aqui vamos\napontar para um Collector rodando localmente na porta 4317 (gRPC).",[506,17584,17586],{"className":11827,"code":17585,"language":11829,"meta":515,"style":515},"# As variáveis de ambiente abaixo dizem ao OTel SDK:\n#  - OTEL_SERVICE_NAME: nome do serviço que aparecerá no Jaeger\n#  - OTEL_EXPORTER_OTLP_ENDPOINT: para onde enviar os dados (Collector)\n#  - OTEL_EXPORTER_OTLP_PROTOCOL: protocolo de transporte (gRPC)\n#  - OTEL_TRACES_EXPORTER: exportar traces via OTLP (protocolo padrão OTel)\nexport OTEL_SERVICE_NAME=\"checkout-api\"\nexport OTEL_EXPORTER_OTLP_ENDPOINT=\"http:\u002F\u002Flocalhost:4317\"\nexport OTEL_EXPORTER_OTLP_PROTOCOL=\"grpc\"\nexport OTEL_TRACES_EXPORTER=\"otlp\"\n\n# Roda a aplicação instrumentada (na porta 8000)\nopentelemetry-instrument uvicorn app:app --reload --port 8000\n",[513,17587,17588,17593,17598,17603,17608,17613,17628,17640,17652,17664,17668,17673],{"__ignoreMap":515},[1376,17589,17590],{"class":1378,"line":1379},[1376,17591,17592],{"class":1665},"# As variáveis de ambiente abaixo dizem ao OTel SDK:\n",[1376,17594,17595],{"class":1378,"line":984},[1376,17596,17597],{"class":1665},"#  - OTEL_SERVICE_NAME: nome do serviço que aparecerá no Jaeger\n",[1376,17599,17600],{"class":1378,"line":981},[1376,17601,17602],{"class":1665},"#  - OTEL_EXPORTER_OTLP_ENDPOINT: para onde enviar os dados (Collector)\n",[1376,17604,17605],{"class":1378,"line":1395},[1376,17606,17607],{"class":1665},"#  - OTEL_EXPORTER_OTLP_PROTOCOL: protocolo de transporte (gRPC)\n",[1376,17609,17610],{"class":1378,"line":1401},[1376,17611,17612],{"class":1665},"#  - OTEL_TRACES_EXPORTER: exportar traces via OTLP (protocolo padrão OTel)\n",[1376,17614,17615,17619,17622,17625],{"class":1378,"line":1407},[1376,17616,17618],{"class":17617},"szBVR","export",[1376,17620,17621],{"class":1671}," OTEL_SERVICE_NAME",[1376,17623,17624],{"class":17617},"=",[1376,17626,17627],{"class":1682},"\"checkout-api\"\n",[1376,17629,17630,17632,17635,17637],{"class":1378,"line":1413},[1376,17631,17618],{"class":17617},[1376,17633,17634],{"class":1671}," OTEL_EXPORTER_OTLP_ENDPOINT",[1376,17636,17624],{"class":17617},[1376,17638,17639],{"class":1682},"\"http:\u002F\u002Flocalhost:4317\"\n",[1376,17641,17642,17644,17647,17649],{"class":1378,"line":1419},[1376,17643,17618],{"class":17617},[1376,17645,17646],{"class":1671}," OTEL_EXPORTER_OTLP_PROTOCOL",[1376,17648,17624],{"class":17617},[1376,17650,17651],{"class":1682},"\"grpc\"\n",[1376,17653,17654,17656,17659,17661],{"class":1378,"line":1424},[1376,17655,17618],{"class":17617},[1376,17657,17658],{"class":1671}," OTEL_TRACES_EXPORTER",[1376,17660,17624],{"class":17617},[1376,17662,17663],{"class":1682},"\"otlp\"\n",[1376,17665,17666],{"class":1378,"line":1430},[1376,17667,1398],{"emptyLinePlaceholder":898},[1376,17669,17670],{"class":1378,"line":1436},[1376,17671,17672],{"class":1665},"# Roda a aplicação instrumentada (na porta 8000)\n",[1376,17674,17675,17677,17679,17682,17685,17688],{"class":1378,"line":1442},[1376,17676,17581],{"class":11841},[1376,17678,17176],{"class":1682},[1376,17680,17681],{"class":1682}," app:app",[1376,17683,17684],{"class":1770}," --reload",[1376,17686,17687],{"class":1770}," --port",[1376,17689,17690],{"class":1770}," 8000\n",[86,17692,17693,17694,17697,17698,17701],{},"A partir de agora, cada vez que alguém bater em ",[513,17695,17696],{},"http:\u002F\u002Flocalhost:8000\u002F","\nou ",[513,17699,17700],{},"http:\u002F\u002Flocalhost:8000\u002Flento",", sua aplicação vai gerar um trace e\nenviá-lo para o Collector (que então repassa para o Jaeger).",[283,17703,17705],{"id":17704},"passo-4-subir-o-collector-jaeger-e-prometheus-com-docker","Passo 4: subir o Collector, Jaeger e Prometheus com Docker",[86,17707,17708,17709,17712],{},"Para não instalar tudo à mão, use este ",[513,17710,17711],{},"docker-compose.yml"," que sobe os\ntrês componentes conectados:",[506,17714,17716],{"className":1656,"code":17715,"language":1658,"meta":515,"style":515},"# docker-compose.yml — pilha de observabilidade local\nversion: \"3.8\"\nservices:\n  # Collector: recebe dados via OTLP e distribui para os backends\n  otel-collector:\n    image: otel\u002Fopentelemetry-collector-contrib:0.108.0\n    command: [\"--config=\u002Fetc\u002Fotelcol\u002Fconfig.yaml\"]\n    volumes:\n      - .\u002Fotel-collector-config.yaml:\u002Fetc\u002Fotelcol\u002Fconfig.yaml\n    ports:\n      - \"4317:4317\"   # OTLP gRPC (onde sua app envia)\n      - \"4318:4318\"   # OTLP HTTP (alternativa)\n\n  # Jaeger: visualizador de traces (UI na porta 16686)\n  jaeger:\n    image: jaegertracing\u002Fall-in-one:1.60\n    environment:\n      - COLLECTOR_OTLP_ENABLED=true\n    ports:\n      - \"16686:16686\"   # interface web do Jaeger\n      - \"4317\"          # Jaeger também aceita OTLP direto\n\n  # Prometheus: armazena e consulta métricas (UI na porta 9090)\n  prometheus:\n    image: prom\u002Fprometheus:v2.54.1\n    volumes:\n      - .\u002Fprometheus.yml:\u002Fetc\u002Fprometheus\u002Fprometheus.yml\n    ports:\n      - \"9090:9090\"\n",[513,17717,17718,17723,17733,17740,17745,17752,17762,17774,17781,17788,17795,17805,17815,17819,17824,17831,17840,17846,17853,17859,17869,17879,17883,17888,17895,17904,17910,17917,17923],{"__ignoreMap":515},[1376,17719,17720],{"class":1378,"line":1379},[1376,17721,17722],{"class":1665},"# docker-compose.yml — pilha de observabilidade local\n",[1376,17724,17725,17728,17730],{"class":1378,"line":984},[1376,17726,17727],{"class":1675},"version",[1376,17729,1679],{"class":1671},[1376,17731,17732],{"class":1682},"\"3.8\"\n",[1376,17734,17735,17738],{"class":1378,"line":981},[1376,17736,17737],{"class":1675},"services",[1376,17739,1750],{"class":1671},[1376,17741,17742],{"class":1378,"line":1395},[1376,17743,17744],{"class":1665},"  # Collector: recebe dados via OTLP e distribui para os backends\n",[1376,17746,17747,17750],{"class":1378,"line":1401},[1376,17748,17749],{"class":1675},"  otel-collector",[1376,17751,1750],{"class":1671},[1376,17753,17754,17757,17759],{"class":1378,"line":1407},[1376,17755,17756],{"class":1675},"    image",[1376,17758,1679],{"class":1671},[1376,17760,17761],{"class":1682},"otel\u002Fopentelemetry-collector-contrib:0.108.0\n",[1376,17763,17764,17767,17769,17772],{"class":1378,"line":1413},[1376,17765,17766],{"class":1675},"    command",[1376,17768,2133],{"class":1671},[1376,17770,17771],{"class":1682},"\"--config=\u002Fetc\u002Fotelcol\u002Fconfig.yaml\"",[1376,17773,2139],{"class":1671},[1376,17775,17776,17779],{"class":1378,"line":1419},[1376,17777,17778],{"class":1675},"    volumes",[1376,17780,1750],{"class":1671},[1376,17782,17783,17785],{"class":1378,"line":1424},[1376,17784,2230],{"class":1671},[1376,17786,17787],{"class":1682},".\u002Fotel-collector-config.yaml:\u002Fetc\u002Fotelcol\u002Fconfig.yaml\n",[1376,17789,17790,17793],{"class":1378,"line":1430},[1376,17791,17792],{"class":1675},"    ports",[1376,17794,1750],{"class":1671},[1376,17796,17797,17799,17802],{"class":1378,"line":1436},[1376,17798,2230],{"class":1671},[1376,17800,17801],{"class":1682},"\"4317:4317\"",[1376,17803,17804],{"class":1665},"   # OTLP gRPC (onde sua app envia)\n",[1376,17806,17807,17809,17812],{"class":1378,"line":1442},[1376,17808,2230],{"class":1671},[1376,17810,17811],{"class":1682},"\"4318:4318\"",[1376,17813,17814],{"class":1665},"   # OTLP HTTP (alternativa)\n",[1376,17816,17817],{"class":1378,"line":1447},[1376,17818,1398],{"emptyLinePlaceholder":898},[1376,17820,17821],{"class":1378,"line":1453},[1376,17822,17823],{"class":1665},"  # Jaeger: visualizador de traces (UI na porta 16686)\n",[1376,17825,17826,17829],{"class":1378,"line":1459},[1376,17827,17828],{"class":1675},"  jaeger",[1376,17830,1750],{"class":1671},[1376,17832,17833,17835,17837],{"class":1378,"line":2200},[1376,17834,17756],{"class":1675},[1376,17836,1679],{"class":1671},[1376,17838,17839],{"class":1682},"jaegertracing\u002Fall-in-one:1.60\n",[1376,17841,17842,17844],{"class":1378,"line":2208},[1376,17843,2644],{"class":1675},[1376,17845,1750],{"class":1671},[1376,17847,17848,17850],{"class":1378,"line":2219},[1376,17849,2230],{"class":1671},[1376,17851,17852],{"class":1682},"COLLECTOR_OTLP_ENABLED=true\n",[1376,17854,17855,17857],{"class":1378,"line":2227},[1376,17856,17792],{"class":1675},[1376,17858,1750],{"class":1671},[1376,17860,17861,17863,17866],{"class":1378,"line":2241},[1376,17862,2230],{"class":1671},[1376,17864,17865],{"class":1682},"\"16686:16686\"",[1376,17867,17868],{"class":1665},"   # interface web do Jaeger\n",[1376,17870,17871,17873,17876],{"class":1378,"line":2253},[1376,17872,2230],{"class":1671},[1376,17874,17875],{"class":1682},"\"4317\"",[1376,17877,17878],{"class":1665},"          # Jaeger também aceita OTLP direto\n",[1376,17880,17881],{"class":1378,"line":2283},[1376,17882,1398],{"emptyLinePlaceholder":898},[1376,17884,17885],{"class":1378,"line":2296},[1376,17886,17887],{"class":1665},"  # Prometheus: armazena e consulta métricas (UI na porta 9090)\n",[1376,17889,17890,17893],{"class":1378,"line":2311},[1376,17891,17892],{"class":1675},"  prometheus",[1376,17894,1750],{"class":1671},[1376,17896,17897,17899,17901],{"class":1378,"line":2316},[1376,17898,17756],{"class":1675},[1376,17900,1679],{"class":1671},[1376,17902,17903],{"class":1682},"prom\u002Fprometheus:v2.54.1\n",[1376,17905,17906,17908],{"class":1378,"line":2324},[1376,17907,17778],{"class":1675},[1376,17909,1750],{"class":1671},[1376,17911,17912,17914],{"class":1378,"line":2333},[1376,17913,2230],{"class":1671},[1376,17915,17916],{"class":1682},".\u002Fprometheus.yml:\u002Fetc\u002Fprometheus\u002Fprometheus.yml\n",[1376,17918,17919,17921],{"class":1378,"line":2340},[1376,17920,17792],{"class":1675},[1376,17922,1750],{"class":1671},[1376,17924,17925,17927],{"class":1378,"line":2351},[1376,17926,2230],{"class":1671},[1376,17928,17929],{"class":1682},"\"9090:9090\"\n",[86,17931,17932,17933,17936],{},"E o arquivo de configuração do Collector (",[513,17934,17935],{},"otel-collector-config.yaml","):",[506,17938,17940],{"className":1656,"code":17939,"language":1658,"meta":515,"style":515},"# otel-collector-config.yaml — define os pipelines do Collector\nreceivers:\n  otlp:\n    protocols:\n      grpc:\n        endpoint: 0.0.0.0:4317\n      http:\n        endpoint: 0.0.0.0:4318\n\nprocessors:\n  batch:               # agrupa spans antes de enviar (eficiência)\n    timeout: 5s\n    send_batch_size: 1000\n\nexporters:\n  otlp\u002Fjaeger:         # envia traces para o Jaeger\n    endpoint: jaeger:4317\n    tls:\n      insecure: true\n  prometheus:          # expõe métricas em formato Prometheus\n    endpoint: 0.0.0.0:8889\n\nservice:\n  pipelines:\n    traces:            # pipeline de traces: recebe → processa → exporta\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [otlp\u002Fjaeger]\n    metrics:           # pipeline de métricas\n      receivers: [otlp]\n      processors: [batch]\n      exporters: [prometheus]\n",[513,17941,17942,17947,17954,17961,17968,17975,17985,17992,18001,18005,18012,18023,18033,18043,18047,18054,18064,18074,18081,18090,18100,18109,18113,18120,18127,18138,18150,18162,18174,18185,18195,18205],{"__ignoreMap":515},[1376,17943,17944],{"class":1378,"line":1379},[1376,17945,17946],{"class":1665},"# otel-collector-config.yaml — define os pipelines do Collector\n",[1376,17948,17949,17952],{"class":1378,"line":984},[1376,17950,17951],{"class":1675},"receivers",[1376,17953,1750],{"class":1671},[1376,17955,17956,17959],{"class":1378,"line":981},[1376,17957,17958],{"class":1675},"  otlp",[1376,17960,1750],{"class":1671},[1376,17962,17963,17966],{"class":1378,"line":1395},[1376,17964,17965],{"class":1675},"    protocols",[1376,17967,1750],{"class":1671},[1376,17969,17970,17973],{"class":1378,"line":1401},[1376,17971,17972],{"class":1675},"      grpc",[1376,17974,1750],{"class":1671},[1376,17976,17977,17980,17982],{"class":1378,"line":1407},[1376,17978,17979],{"class":1675},"        endpoint",[1376,17981,1679],{"class":1671},[1376,17983,17984],{"class":1682},"0.0.0.0:4317\n",[1376,17986,17987,17990],{"class":1378,"line":1413},[1376,17988,17989],{"class":1675},"      http",[1376,17991,1750],{"class":1671},[1376,17993,17994,17996,17998],{"class":1378,"line":1419},[1376,17995,17979],{"class":1675},[1376,17997,1679],{"class":1671},[1376,17999,18000],{"class":1682},"0.0.0.0:4318\n",[1376,18002,18003],{"class":1378,"line":1424},[1376,18004,1398],{"emptyLinePlaceholder":898},[1376,18006,18007,18010],{"class":1378,"line":1430},[1376,18008,18009],{"class":1675},"processors",[1376,18011,1750],{"class":1671},[1376,18013,18014,18017,18020],{"class":1378,"line":1436},[1376,18015,18016],{"class":1675},"  batch",[1376,18018,18019],{"class":1671},":               ",[1376,18021,18022],{"class":1665},"# agrupa spans antes de enviar (eficiência)\n",[1376,18024,18025,18028,18030],{"class":1378,"line":1442},[1376,18026,18027],{"class":1675},"    timeout",[1376,18029,1679],{"class":1671},[1376,18031,18032],{"class":1682},"5s\n",[1376,18034,18035,18038,18040],{"class":1378,"line":1447},[1376,18036,18037],{"class":1675},"    send_batch_size",[1376,18039,1679],{"class":1671},[1376,18041,18042],{"class":1770},"1000\n",[1376,18044,18045],{"class":1378,"line":1453},[1376,18046,1398],{"emptyLinePlaceholder":898},[1376,18048,18049,18052],{"class":1378,"line":1459},[1376,18050,18051],{"class":1675},"exporters",[1376,18053,1750],{"class":1671},[1376,18055,18056,18059,18061],{"class":1378,"line":2200},[1376,18057,18058],{"class":1675},"  otlp\u002Fjaeger",[1376,18060,14721],{"class":1671},[1376,18062,18063],{"class":1665},"# envia traces para o Jaeger\n",[1376,18065,18066,18069,18071],{"class":1378,"line":2208},[1376,18067,18068],{"class":1675},"    endpoint",[1376,18070,1679],{"class":1671},[1376,18072,18073],{"class":1682},"jaeger:4317\n",[1376,18075,18076,18079],{"class":1378,"line":2219},[1376,18077,18078],{"class":1675},"    tls",[1376,18080,1750],{"class":1671},[1376,18082,18083,18086,18088],{"class":1378,"line":2227},[1376,18084,18085],{"class":1675},"      insecure",[1376,18087,1679],{"class":1671},[1376,18089,2182],{"class":1770},[1376,18091,18092,18094,18097],{"class":1378,"line":2241},[1376,18093,17892],{"class":1675},[1376,18095,18096],{"class":1671},":          ",[1376,18098,18099],{"class":1665},"# expõe métricas em formato Prometheus\n",[1376,18101,18102,18104,18106],{"class":1378,"line":2253},[1376,18103,18068],{"class":1675},[1376,18105,1679],{"class":1671},[1376,18107,18108],{"class":1682},"0.0.0.0:8889\n",[1376,18110,18111],{"class":1378,"line":2283},[1376,18112,1398],{"emptyLinePlaceholder":898},[1376,18114,18115,18118],{"class":1378,"line":2296},[1376,18116,18117],{"class":1675},"service",[1376,18119,1750],{"class":1671},[1376,18121,18122,18125],{"class":1378,"line":2311},[1376,18123,18124],{"class":1675},"  pipelines",[1376,18126,1750],{"class":1671},[1376,18128,18129,18132,18135],{"class":1378,"line":2316},[1376,18130,18131],{"class":1675},"    traces",[1376,18133,18134],{"class":1671},":            ",[1376,18136,18137],{"class":1665},"# pipeline de traces: recebe → processa → exporta\n",[1376,18139,18140,18143,18145,18148],{"class":1378,"line":2324},[1376,18141,18142],{"class":1675},"      receivers",[1376,18144,2133],{"class":1671},[1376,18146,18147],{"class":1682},"otlp",[1376,18149,2139],{"class":1671},[1376,18151,18152,18155,18157,18160],{"class":1378,"line":2333},[1376,18153,18154],{"class":1675},"      processors",[1376,18156,2133],{"class":1671},[1376,18158,18159],{"class":1682},"batch",[1376,18161,2139],{"class":1671},[1376,18163,18164,18167,18169,18172],{"class":1378,"line":2340},[1376,18165,18166],{"class":1675},"      exporters",[1376,18168,2133],{"class":1671},[1376,18170,18171],{"class":1682},"otlp\u002Fjaeger",[1376,18173,2139],{"class":1671},[1376,18175,18176,18179,18182],{"class":1378,"line":2351},[1376,18177,18178],{"class":1675},"    metrics",[1376,18180,18181],{"class":1671},":           ",[1376,18183,18184],{"class":1665},"# pipeline de métricas\n",[1376,18186,18187,18189,18191,18193],{"class":1378,"line":2362},[1376,18188,18142],{"class":1675},[1376,18190,2133],{"class":1671},[1376,18192,18147],{"class":1682},[1376,18194,2139],{"class":1671},[1376,18196,18197,18199,18201,18203],{"class":1378,"line":2385},[1376,18198,18154],{"class":1675},[1376,18200,2133],{"class":1671},[1376,18202,18159],{"class":1682},[1376,18204,2139],{"class":1671},[1376,18206,18207,18209,18211,18214],{"class":1378,"line":2396},[1376,18208,18166],{"class":1675},[1376,18210,2133],{"class":1671},[1376,18212,18213],{"class":1682},"prometheus",[1376,18215,2139],{"class":1671},[86,18217,18218],{},"Suba tudo com:",[506,18220,18222],{"className":11827,"code":18221,"language":11829,"meta":515,"style":515},"docker compose up -d\n",[513,18223,18224],{"__ignoreMap":515},[1376,18225,18226,18228,18231,18234],{"class":1378,"line":1379},[1376,18227,11842],{"class":11841},[1376,18229,18230],{"class":1682}," compose",[1376,18232,18233],{"class":1682}," up",[1376,18235,18236],{"class":1770}," -d\n",[968,18238],{},[12,18240,18242],{"id":18241},"visualizando-traces-no-jaeger-jaeger","Visualizando traces no Jaeger {#jaeger}",[86,18244,18245,18248],{},[89,18246,18247],{},"Jaeger"," (pronuncia-se \"iéguer\", é uma palavra alemã que significa\n\"caçador\") é uma ferramenta open source de UI para visualizar traces.\nFoi criada pela Uber e hoje é mantida pela CNCF — mesma casa do\nOpenTelemetry e do Kubernetes.",[86,18250,18251,18252,18255],{},"Depois de subir a pilha e fazer algumas requisições na sua API, abra o\nnavegador em ",[513,18253,18254],{},"http:\u002F\u002Flocalhost:16686",". Você verá:",[17,18257,18258,18271,18281,18287],{},[20,18259,18260,18263,18264,18267,18268,1157],{},[89,18261,18262],{},"Seletor de serviço"," (canto superior esquerdo): um dropdown onde\nvocê escolhe qual serviço quer inspecionar. Deve aparecer\n",[513,18265,18266],{},"checkout-api"," (o nome que definimos em ",[513,18269,18270],{},"OTEL_SERVICE_NAME",[20,18272,18273,18276,18277,18280],{},[89,18274,18275],{},"Filtros de busca",": pode filtrar por tag (ex.:\n",[513,18278,18279],{},"http.status_code=500","), por duração mínima, por operação.",[20,18282,18283,18286],{},[89,18284,18285],{},"Botão \"Find Traces\"",": lista os traces recentes que casam com os\nfiltros.",[20,18288,18289,18292],{},[89,18290,18291],{},"Detalhe do trace",": ao clicar num trace, vê-se a cascata (waterfall)\nde spans com a linha do tempo e a hierarquia pai-filho.",[283,18294,18296],{"id":18295},"o-que-procurar-no-jaeger","O que procurar no Jaeger",[165,18298,18299,18309],{},[168,18300,18301],{},[171,18302,18303,18306],{},[174,18304,18305],{},"Sinal visual no Jaeger",[174,18307,18308],{},"O que provavelmente significa",[184,18310,18311,18319,18327,18335,18343],{},[171,18312,18313,18316],{},[189,18314,18315],{},"Um span muito mais largo que os outros",[189,18317,18318],{},"Gargalo de latência — investigue essa operação",[171,18320,18321,18324],{},[189,18322,18323],{},"Span vermelho (com erro)",[189,18325,18326],{},"Exceção capturada — clique para ver a stack",[171,18328,18329,18332],{},[189,18330,18331],{},"Muitos spans filhos em série",[189,18333,18334],{},"Talvez possa paralelizar as chamadas",[171,18336,18337,18340],{},[189,18338,18339],{},"Span de chamada externa dominando",[189,18341,18342],{},"Backend\u002FAPI de terceiros lento",[171,18344,18345,18348],{},[189,18346,18347],{},"Trace com poucos spans",[189,18349,18350],{},"Instrumentação faltando — serviço \"cego\"",[710,18352,18353],{},[86,18354,18355,18357,18358,18360,18361,18364],{},[89,18356,9384],{},": copie o ",[513,18359,16942],{}," que aparece no detalhe de um trace\nno Jaeger e cole na sua ferramenta de logs (Loki, Elasticsearch, etc.).\nCom isso, você vê ",[89,18362,18363],{},"todas as mensagens de log"," geradas por aquela\nrequisição específica, em todos os serviços. É a mágica do \"logs com\ncontexto de trace\" que mencionamos antes.",[968,18366],{},[12,18368,18370],{"id":18369},"coletando-métricas-com-prometheus-prometheus","Coletando métricas com Prometheus {#prometheus}",[86,18372,18373,18376,18377,18380,18381,18384],{},[89,18374,18375],{},"Prometheus"," é um banco de dados de séries temporais (TSDB — ",[501,18378,18379],{},"Time\nSeries DataBase",") e um sistema de coleta de métricas. É o padrão de\nfato para métricas no mundo cloud native. Diferente do Jaeger (que\nrecebe traces por push), o Prometheus funciona por ",[89,18382,18383],{},"pull",": ele vai\naté sua aplicação e pergunta \"me dá suas métricas\" em intervalos\nregulares (scrape).",[86,18386,18387,18388,18391,18392,18394],{},"Para que o Prometheus encontre sua aplicação, ela precisa expor um\nendpoint HTTP (geralmente ",[513,18389,18390],{},"\u002Fmetrics",") com as métricas num formato de\ntexto específico. No nosso setup, é o ",[89,18393,16535],{}," que expõe esse\nendpoint (na porta 8889, conforme a config), agregando métricas de\ntodas as apps.",[86,18396,18397,18398,4171],{},"Arquivo ",[513,18399,18400],{},"prometheus.yml",[506,18402,18404],{"className":1656,"code":18403,"language":1658,"meta":515,"style":515},"# prometheus.yml — configura o scrape do Prometheus\nglobal:\n  scrape_interval: 15s      # coleta métricas a cada 15s\n\nscrape_configs:\n  - job_name: \"otel-collector\"\n    static_configs:\n      - targets: [\"otel-collector:8889\"]   # onde o Collector expõe métricas\n",[513,18405,18406,18411,18418,18431,18435,18442,18454,18461],{"__ignoreMap":515},[1376,18407,18408],{"class":1378,"line":1379},[1376,18409,18410],{"class":1665},"# prometheus.yml — configura o scrape do Prometheus\n",[1376,18412,18413,18416],{"class":1378,"line":984},[1376,18414,18415],{"class":1675},"global",[1376,18417,1750],{"class":1671},[1376,18419,18420,18423,18425,18428],{"class":1378,"line":981},[1376,18421,18422],{"class":1675},"  scrape_interval",[1376,18424,1679],{"class":1671},[1376,18426,18427],{"class":1682},"15s",[1376,18429,18430],{"class":1665},"      # coleta métricas a cada 15s\n",[1376,18432,18433],{"class":1378,"line":1395},[1376,18434,1398],{"emptyLinePlaceholder":898},[1376,18436,18437,18440],{"class":1378,"line":1401},[1376,18438,18439],{"class":1675},"scrape_configs",[1376,18441,1750],{"class":1671},[1376,18443,18444,18446,18449,18451],{"class":1378,"line":1407},[1376,18445,1906],{"class":1671},[1376,18447,18448],{"class":1675},"job_name",[1376,18450,1679],{"class":1671},[1376,18452,18453],{"class":1682},"\"otel-collector\"\n",[1376,18455,18456,18459],{"class":1378,"line":1413},[1376,18457,18458],{"class":1675},"    static_configs",[1376,18460,1750],{"class":1671},[1376,18462,18463,18465,18468,18470,18473,18476],{"class":1378,"line":1419},[1376,18464,2230],{"class":1671},[1376,18466,18467],{"class":1675},"targets",[1376,18469,2133],{"class":1671},[1376,18471,18472],{"class":1682},"\"otel-collector:8889\"",[1376,18474,18475],{"class":1671},"]   ",[1376,18477,18478],{"class":1665},"# onde o Collector expõe métricas\n",[86,18480,18481,18482,18485,18486,18489],{},"Abra ",[513,18483,18484],{},"http:\u002F\u002Flocalhost:9090"," para acessar a UI do Prometheus. Algumas\nqueries úteis para começar (na linguagem ",[89,18487,18488],{},"PromQL",", a query language do\nPrometheus):",[506,18491,18495],{"className":18492,"code":18493,"language":18494,"meta":515,"style":515},"language-promql shiki shiki-themes github-light github-dark","# Taxa de requisições por segundo nos últimos 5 minutos\nrate(http_server_request_duration_seconds_count[5m])\n\n# p99 da latência das requisições\nhistogram_quantile(0.99, rate(\n  http_server_request_duration_seconds_bucket[5m]\n))\n\n# Erros 500 por minuto\nsum(rate(http_server_request_duration_seconds_count{\n  http_status_code=\"500\"\n}[1m])) by (service_name)\n","promql",[513,18496,18497,18502,18507,18511,18516,18521,18526,18531,18535,18540,18545,18550],{"__ignoreMap":515},[1376,18498,18499],{"class":1378,"line":1379},[1376,18500,18501],{},"# Taxa de requisições por segundo nos últimos 5 minutos\n",[1376,18503,18504],{"class":1378,"line":984},[1376,18505,18506],{},"rate(http_server_request_duration_seconds_count[5m])\n",[1376,18508,18509],{"class":1378,"line":981},[1376,18510,1398],{"emptyLinePlaceholder":898},[1376,18512,18513],{"class":1378,"line":1395},[1376,18514,18515],{},"# p99 da latência das requisições\n",[1376,18517,18518],{"class":1378,"line":1401},[1376,18519,18520],{},"histogram_quantile(0.99, rate(\n",[1376,18522,18523],{"class":1378,"line":1407},[1376,18524,18525],{},"  http_server_request_duration_seconds_bucket[5m]\n",[1376,18527,18528],{"class":1378,"line":1413},[1376,18529,18530],{},"))\n",[1376,18532,18533],{"class":1378,"line":1419},[1376,18534,1398],{"emptyLinePlaceholder":898},[1376,18536,18537],{"class":1378,"line":1424},[1376,18538,18539],{},"# Erros 500 por minuto\n",[1376,18541,18542],{"class":1378,"line":1430},[1376,18543,18544],{},"sum(rate(http_server_request_duration_seconds_count{\n",[1376,18546,18547],{"class":1378,"line":1436},[1376,18548,18549],{},"  http_status_code=\"500\"\n",[1376,18551,18552],{"class":1378,"line":1442},[1376,18553,18554],{},"}[1m])) by (service_name)\n",[86,18556,18557,18558,18561],{},"Não se assuste com a sintaxe — PromQL parece estranha no começo, mas\nsegue uma lógica consistente. O importante agora é saber que essas\nconsultas alimentam dashboards (no ",[89,18559,18560],{},"Grafana",", por exemplo) e alertas.",[968,18563],{},[12,18565,18567],{"id":18566},"erros-comuns-e-como-evitá-los-erros","Erros comuns (e como evitá-los) {#erros}",[283,18569,18571],{"id":18570},"_1-instrumentar-tudo-e-não-filtrar-nada","1. Instrumentar tudo e não filtrar nada",[86,18573,18574,18577],{},[89,18575,18576],{},"O erro",": ligar a instrumentação automática em tudo, sem ajustar\namostragem, e descobrir no fim do mês que você tem 500GB de traces que\nnão consegue pagar para armazenar.",[86,18579,18580,13144,18583,4958,18586,18589,18590,18593],{},[89,18581,18582],{},"A solução",[89,18584,18585],{},"amostragem",[501,18587,18588],{},"sampling","). Em produção, na maioria\ndos casos, você não precisa de 100% dos traces — 1% a 10% costumam ser\nsuficientes para detectar padrões. Reserve amostragem de 100% só para\ntraces com erro. O OpenTelemetry suporta isso via ",[89,18591,18592],{},"tail sampling"," no\nCollector (decide depois de o trace terminar, mantendo os que tiveram\nerro).",[506,18595,18597],{"className":1656,"code":18596,"language":1658,"meta":515,"style":515},"# Exemplo de tail sampling no Collector — mantém 100% dos traces com erro\nprocessors:\n  tail_sampling:\n    decision_wait: 10s\n    policies:\n      - name: errors\n        type: status_code\n        status_code:\n          status_codes: [ERROR]\n      - name: sample-rest\n        type: probabilistic\n        probabilistic:\n          sampling_percentage: 5\n",[513,18598,18599,18604,18610,18617,18627,18634,18645,18655,18662,18674,18685,18694,18701],{"__ignoreMap":515},[1376,18600,18601],{"class":1378,"line":1379},[1376,18602,18603],{"class":1665},"# Exemplo de tail sampling no Collector — mantém 100% dos traces com erro\n",[1376,18605,18606,18608],{"class":1378,"line":984},[1376,18607,18009],{"class":1675},[1376,18609,1750],{"class":1671},[1376,18611,18612,18615],{"class":1378,"line":981},[1376,18613,18614],{"class":1675},"  tail_sampling",[1376,18616,1750],{"class":1671},[1376,18618,18619,18622,18624],{"class":1378,"line":1395},[1376,18620,18621],{"class":1675},"    decision_wait",[1376,18623,1679],{"class":1671},[1376,18625,18626],{"class":1682},"10s\n",[1376,18628,18629,18632],{"class":1378,"line":1401},[1376,18630,18631],{"class":1675},"    policies",[1376,18633,1750],{"class":1671},[1376,18635,18636,18638,18640,18642],{"class":1378,"line":1407},[1376,18637,2230],{"class":1671},[1376,18639,1676],{"class":1675},[1376,18641,1679],{"class":1671},[1376,18643,18644],{"class":1682},"errors\n",[1376,18646,18647,18650,18652],{"class":1378,"line":1413},[1376,18648,18649],{"class":1675},"        type",[1376,18651,1679],{"class":1671},[1376,18653,18654],{"class":1682},"status_code\n",[1376,18656,18657,18660],{"class":1378,"line":1419},[1376,18658,18659],{"class":1675},"        status_code",[1376,18661,1750],{"class":1671},[1376,18663,18664,18667,18669,18672],{"class":1378,"line":1424},[1376,18665,18666],{"class":1675},"          status_codes",[1376,18668,2133],{"class":1671},[1376,18670,18671],{"class":1682},"ERROR",[1376,18673,2139],{"class":1671},[1376,18675,18676,18678,18680,18682],{"class":1378,"line":1430},[1376,18677,2230],{"class":1671},[1376,18679,1676],{"class":1675},[1376,18681,1679],{"class":1671},[1376,18683,18684],{"class":1682},"sample-rest\n",[1376,18686,18687,18689,18691],{"class":1378,"line":1436},[1376,18688,18649],{"class":1675},[1376,18690,1679],{"class":1671},[1376,18692,18693],{"class":1682},"probabilistic\n",[1376,18695,18696,18699],{"class":1378,"line":1442},[1376,18697,18698],{"class":1675},"        probabilistic",[1376,18700,1750],{"class":1671},[1376,18702,18703,18706,18708],{"class":1378,"line":1447},[1376,18704,18705],{"class":1675},"          sampling_percentage",[1376,18707,1679],{"class":1671},[1376,18709,14835],{"class":1770},[283,18711,18713],{"id":18712},"_2-esconder-o-trace_id-dos-logs","2. Esconder o trace_id dos logs",[86,18715,18716,18718],{},[89,18717,18576],{},": usar logging tradicional sem integrar com o OTel. Quando um\nincidente acontece, você tem traces no Jaeger e logs no Loki, mas nenhum\njeito de conectá-los. Cada ferramenta vira uma ilha.",[86,18720,18721,18723,18724,18727,18728,3022,18730,18732],{},[89,18722,18582],{},": sempre use ",[513,18725,18726],{},"LoggingInstrumentor"," (ou equivalente na sua\nlinguagem) para injetar ",[513,18729,16942],{},[513,18731,16945],{}," em cada log. Custo quase\nzero, benefício enorme.",[283,18734,18736],{"id":18735},"_3-usar-a-média-para-latência","3. Usar a média para latência",[86,18738,18739,18741],{},[89,18740,18576],{},": montar um dashboard de \"latência média\" e achar que está\ntudo bem quando ela dá 200ms. O problema: a média esconde os outliers.\nSe 99 pessoas tiveram 50ms e 1 teve 10 segundos, a média parece ok, mas\naquele 1 usuário teve uma experiência terrível.",[86,18743,18744,18746,18747,18750],{},[89,18745,18582],{},": acompanhe ",[89,18748,18749],{},"percentis"," — p50, p90, p95 e p99. O p99\nte diz \"1% dos seus usuários está sofrendo quanto?\".",[283,18752,18754],{"id":18753},"_4-alertar-em-sintoma-não-em-causa","4. Alertar em sintoma, não em causa",[86,18756,18757,18759,18760,18762],{},[89,18758,18576],{},": criar um alerta \"latência > 1s\" sem ter uma forma de saber\n",[501,18761,16145],{}," a latência subiu. O alerta dispara, a equipe acorda, e ninguém\nsabe onde olhar.",[86,18764,18765,18767],{},[89,18766,18582],{},": alertas em métricas (sintoma) devem sempre ter um caminho\nclaro para traces e logs (causa). Se um alerta dispara, o primeiro link\nna notificação deveria ser uma query do Jaeger pré-filtrada para o\nserviço e janela de tempo em questão.",[283,18769,18771],{"id":18770},"_5-tratar-observabilidade-como-coisa-de-sre","5. Tratar observabilidade como \"coisa de SRE\"",[86,18773,18774,18776,18777,18780],{},[89,18775,18576],{},": achar que observabilidade é responsabilidade só do time de\nSRE (",[501,18778,18779],{},"Site Reliability Engineering",", engenharia de confiabilidade) ou\nDevOps. Os desenvolvedores escrevem o código \"cego\" e jogam o problema\nde depuração por cima do muro.",[86,18782,18783,18785,18786,18789],{},[89,18784,18582],{},": observabilidade é responsabilidade de quem escreve o\ncódigo. Se você escreve um serviço, você é quem mais entende quais\nspans, métricas e logs fazem sentido ali. Instrumentar deve fazer parte\nda definição de pronto (DoD — ",[501,18787,18788],{},"Definition of Done",") de qualquer feature.",[283,18791,18793],{"id":18792},"_6-espalhar-nomes-de-spans-inconsistentes","6. Espalhar nomes de spans inconsistentes",[86,18795,18796,18798,18799,18802,18803,18806,18807,18810],{},[89,18797,18576],{},": um desenvolvedor chama o span de ",[513,18800,18801],{},"GET \u002Fusers",", outro de\n",[513,18804,18805],{},"buscar_usuarios",", outro de ",[513,18808,18809],{},"UserController.list",". O mesmo trabalho\naparece com três nomes diferentes no Jaeger, dificultando buscas.",[86,18812,18813,18815,18816,18819,18820,1327,18823,1327,18826,18829],{},[89,18814,18582],{},": defina uma convenção de nomenclatura. O OpenTelemetry\nrecomenda o padrão ",[513,18817,18818],{},"{tipo}.{operação}.{atributo}"," — ex.:\n",[513,18821,18822],{},"http.server.request",[513,18824,18825],{},"db.query.select",[513,18827,18828],{},"messaging.publish",". Padronize\ne documente.",[968,18831],{},[12,18833,18835],{"id":18834},"conclusão-e-próximos-passos-conclusao","Conclusão e próximos passos {#conclusao}",[86,18837,18838,18839,18842],{},"Observabilidade não é uma ferramenta que você compra — é uma ",[89,18840,18841],{},"capacidade","\nque você constrói combinando dados certos (traces, métricas, logs),\nferramentas certas (OpenTelemetry como padrão, Jaeger e Prometheus como\nbackends) e práticas certas (instrumentar desde o início, conectar logs\na traces, alertar com caminho para a causa).",[86,18844,18845],{},"Comece pequeno:",[17,18847,18848,18854,18860,18866,18872],{},[20,18849,18850,18853],{},[89,18851,18852],{},"Instrumente um serviço"," com OpenTelemetry (use nosso exemplo do\nFastAPI como ponto de partida).",[20,18855,18856,18859],{},[89,18857,18858],{},"Suba a pilha local"," com o docker-compose que mostramos.",[20,18861,18862,18865],{},[89,18863,18864],{},"Veja um trace no Jaeger"," — esse \"aha moment\" vale mais que mil\npalavras.",[20,18867,18868,18871],{},[89,18869,18870],{},"Adicione métricas"," e monte um dashboard simples no Grafana.",[20,18873,18874,18877,18878,18880],{},[89,18875,18876],{},"Integre logs"," com ",[513,18879,16942],{}," e sinta a diferença ao depurar.",[86,18882,18883,18884,18887],{},"A beleza de usar OpenTelemetry é que, quando você crescer e quiser\ntrocar Jaeger por Tempo, ou Prometheus por Mimir, ou adicionar um\nbackend comercial como Datadog — ",[89,18885,18886],{},"seu código não muda",". Você\ninstrumentou uma vez e fica livre para escolher o melhor destino para\nseus dados.",[968,18889],{},[283,18891,18893],{"id":18892},"a-inicialize-tec-pode-te-ajudar-com-isso","A Inicialize Tec pode te ajudar com isso",[86,18895,18896,18897,18899],{},"Implementar observabilidade do zero pode parecer intimidador — há muitas\ndecisões de arquitetura, configuração de Collector, escolha de backends,\npadronização entre times. A ",[89,18898,7],{}," ajuda empresas a saírem\ndo \"alarme de incêndio\" e chegarem à observabilidade de verdade:\ninstrumentação com OpenTelemetry, pilhas com Jaeger\u002FPrometheus\u002FGrafana\nou soluções comerciais, dashboards e alertas que realmente reduzem\nMTTD e MTTR.",[86,18901,18902],{},"Fale com a gente para uma consultoria de observabilidade sob medida\npara o seu stack. Transformamos \"por que está lento?\" em resposta em\nminutos, não em horas.",[86,18904,18905,18906],{},"→ ",[89,18907,18908],{},[23,18909,18911],{"href":18910},"\u002F#contato","Converse com a Inicialize Tec",[2883,18913,18914],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":515,"searchDepth":981,"depth":981,"links":18916},[18917,18918,18921,18922,18925,18930,18934,18937,18938,18945,18948,18949,18957],{"id":14,"depth":984,"text":15},{"id":16119,"depth":984,"text":16120,"children":18919},[18920],{"id":16187,"depth":981,"text":16188},{"id":16224,"depth":984,"text":16225},{"id":16341,"depth":984,"text":16342,"children":18923},[18924],{"id":16371,"depth":981,"text":16372},{"id":16461,"depth":984,"text":16462,"children":18926},[18927,18928,18929],{"id":16488,"depth":981,"text":16489},{"id":16513,"depth":981,"text":16514},{"id":16545,"depth":981,"text":16546},{"id":16622,"depth":984,"text":16623,"children":18931},[18932,18933],{"id":16643,"depth":981,"text":16644},{"id":16727,"depth":981,"text":16728},{"id":16757,"depth":984,"text":16758,"children":18935},[18936],{"id":16815,"depth":981,"text":16816},{"id":16902,"depth":984,"text":16903},{"id":17096,"depth":984,"text":17097,"children":18939},[18940,18941,18942,18943,18944],{"id":17107,"depth":981,"text":17108},{"id":17119,"depth":981,"text":17120},{"id":17244,"depth":981,"text":17245},{"id":17574,"depth":981,"text":17575},{"id":17704,"depth":981,"text":17705},{"id":18241,"depth":984,"text":18242,"children":18946},[18947],{"id":18295,"depth":981,"text":18296},{"id":18369,"depth":984,"text":18370},{"id":18566,"depth":984,"text":18567,"children":18950},[18951,18952,18953,18954,18955,18956],{"id":18570,"depth":981,"text":18571},{"id":18712,"depth":981,"text":18713},{"id":18735,"depth":981,"text":18736},{"id":18753,"depth":981,"text":18754},{"id":18770,"depth":981,"text":18771},{"id":18792,"depth":981,"text":18793},{"id":18834,"depth":984,"text":18835,"children":18958},[18959],{"id":18892,"depth":981,"text":18893},"O que é observabilidade, por que importa e como implementar com OpenTelemetry: traces, métricas e logs explicados com analogias do dia-a-dia.",[18962,16467,18963,1899,18964,18965,18247,18375],"observabilidade","tracing","logs","monitoring",{},"\u002Finfraestrutura\u002Fobservabilidade-com-opentelemetry",{"title":16039,"description":18960},"observabilidade-com-opentelemetry","infraestrutura\u002Fobservabilidade-com-opentelemetry","o74CFjeNRpxxEnKaxNOXlzzyoYYHBX1g1-SASroUJYM",{"id":18973,"title":18974,"author":7,"body":18975,"category":16024,"date":1028,"description":20920,"extension":1030,"keywords":20921,"meta":20927,"navigation":898,"path":20928,"seo":20929,"slug":20930,"stem":20931,"updated":1028,"__hash__":20932},"content\u002Finfraestrutura\u002Freduzir-custo-aws-auto-scaling.md","Como reduzir custo na AWS com auto-scaling",{"type":9,"value":18976,"toc":20879},[18977,18979,19025,19029,19032,19037,19040,19060,19063,19067,19073,19078,19085,19089,19092,19112,19258,19261,19264,19318,19384,19387,19398,19402,19405,19476,19479,19483,19489,19494,19498,19564,19568,19729,19732,19736,19821,19832,19836,19842,19847,19851,19858,19861,19887,19891,19894,19990,19996,20000,20006,20012,20084,20099,20103,20106,20109,20113,20116,20120,20123,20200,20204,20207,20227,20230,20316,20322,20326,20329,20333,20339,20350,20354,20357,20380,20383,20433,20498,20501,20505,20572,20576,20579,20583,20586,20600,20604,20607,20699,20703,20706,20774,20781,20783,20787,20792,20797,20801,20810,20819,20823,20828,20833,20837,20842,20850,20854,20859,20867,20869,20877],[12,18978,15],{"id":14},[17,18980,18981,18987,18992,18998,19004,19010,19016,19021],{},[20,18982,18983],{},[23,18984,18986],{"href":18985},"#o-problema","O problema do custo na nuvem",[20,18988,18989],{},[23,18990,18991],{"href":13776},"Auto-scaling: o gancho principal",[20,18993,18994],{},[23,18995,18997],{"href":18996},"#spot","Spot Instances: poder de fogo por menos",[20,18999,19000],{},[23,19001,19003],{"href":19002},"#right-sizing","Right-sizing: o tamanho certo",[20,19005,19006],{},[23,19007,19009],{"href":19008},"#reservas","Reserved Instances e Savings Plans",[20,19011,19012],{},[23,19013,19015],{"href":19014},"#calculo","Cálculo real de economia",[20,19017,19018],{},[23,19019,9625],{"href":19020},"#monitoramento",[20,19022,19023],{},[23,19024,74],{"href":13782},[12,19026,19028],{"id":19027},"o-problema-do-custo-na-nuvem-o-problema","O problema do custo na nuvem {#o-problema}",[86,19030,19031],{},"A nuvem tem uma pegadinha silenciosa. No modelo tradicional, você comprava um\nservidor por R$ 10.000 e ele era seu para sempre. Na nuvem, você paga por hora\n— R$ 0,10 parece barato, mas 24 horas × 365 dias = R$ 876\u002Fano por servidor. E\nraramente são só um.",[86,19033,19034,19036],{},[89,19035,3011],{},": é como um táxi. Uma corrida de R$ 15 é barata. Mas se você usa\ntáxi todo dia, várias vezes ao dia, no fim do mês gastou mais do que compraria\num carro. A nuvem cobra por uso — e o uso, sem supervisão, só cresce.",[86,19038,19039],{},"O problema se agrava por três motivos:",[17,19041,19042,19048,19054],{},[20,19043,19044,19047],{},[89,19045,19046],{},"Recursos ficam esquecidos",": alguém cria um servidor para testar, esquece\nde apagar, ninguém percebe. Meses depois, você está pagando por algo que\nninguém usa.",[20,19049,19050,19053],{},[89,19051,19052],{},"Sizing errado",": servidores superdimensionados (maiores que o necessário)\nrodam por meses usando 10% da capacidade — você paga por 100%.",[20,19055,19056,19059],{},[89,19057,19058],{},"Sem elasticidade",": servidores rodam 24\u002F7 no mesmo tamanho, mesmo quando\no tráfego cai 80% à noite.",[86,19061,19062],{},"A boa notícia: dá para cortar 40-60% da conta sem perder performance. Vamos\nver como.",[12,19064,19066],{"id":19065},"auto-scaling-o-gancho-principal-auto-scaling","Auto-scaling: o gancho principal {#auto-scaling}",[86,19068,19069,19072],{},[89,19070,19071],{},"Auto-scaling"," (escalonamento automático) é a prática de adicionar ou\nremover servidores automaticamente conforme a demanda.",[86,19074,19075,19077],{},[89,19076,3011],{},": imagine uma loja que tem 2 caixas. De segunda a quarta, é\nsuficiente. Na sexta à tarde, a fila explode — a loja abre mais 4 caixas. No\nsábado de manhã, sem movimento, fecha 2. A loja não mantém 6 caixas abertas o\ntempo todo — só quando precisa.",[86,19079,19080,19081,19084],{},"Na AWS, o ",[89,19082,19083],{},"Auto Scaling Group (ASG)"," faz exatamente isso com instâncias EC2\n(servidores). Você define um mínimo, um máximo e regras de quando escalar.",[283,19086,19088],{"id":19087},"como-funciona-um-asg","Como funciona um ASG",[86,19090,19091],{},"Um ASG precisa de três coisas:",[17,19093,19094,19100,19106],{},[20,19095,19096,19099],{},[89,19097,19098],{},"Launch template"," (modelo de inicialização): a \"receita\" do servidor —\ntipo de instância, imagem (AMI), rede, regras de segurança.",[20,19101,19102,19105],{},[89,19103,19104],{},"Grupo de subnets",": em quais zonas (datacenters) os servidores podem ser\ncriados.",[20,19107,19108,19111],{},[89,19109,19110],{},"Políticas de escalonamento",": as regras que dizem quando adicionar ou\nremover instâncias.",[506,19113,19117],{"className":19114,"code":19115,"language":19116,"meta":515,"style":515},"language-hcl shiki shiki-themes github-light github-dark","# Terraform: Auto Scaling Group básico\n\nresource \"aws_launch_template\" \"web\" {\n  name_prefix   = \"web-\"\n  image_id      = \"ami-0c7217cdde317cfec\"  # Amazon Linux 2023\n  instance_type = \"t3.micro\"\n  key_name      = \"minha-chave\"\n\n  user_data = base64encode(\u003C\u003C-EOF\n    #!\u002Fbin\u002Fbash\n    # user_data = script executado quando o servidor nasce\n    yum install -y nginx\n    systemctl start nginx\n  EOF\n  )\n}\n\nresource \"aws_autoscaling_group\" \"web\" {\n  name                = \"web-asg\"\n  vpc_zone_identifier = [aws_subnet.private_a.id, aws_subnet.private_b.id]\n  desired_capacity    = 2   # Quantos servidores queremos agora\n  min_size            = 2   # Mínimo — nunca menos que isso\n  max_size            = 10  # Máximo — limite de custo\n\n  launch_template {\n    id      = aws_launch_template.web.id\n    version = \"$Latest\"\n  }\n}\n","hcl",[513,19118,19119,19124,19128,19133,19138,19143,19148,19153,19157,19162,19167,19172,19177,19182,19187,19192,19196,19200,19205,19210,19215,19220,19225,19230,19234,19239,19244,19249,19254],{"__ignoreMap":515},[1376,19120,19121],{"class":1378,"line":1379},[1376,19122,19123],{},"# Terraform: Auto Scaling Group básico\n",[1376,19125,19126],{"class":1378,"line":984},[1376,19127,1398],{"emptyLinePlaceholder":898},[1376,19129,19130],{"class":1378,"line":981},[1376,19131,19132],{},"resource \"aws_launch_template\" \"web\" {\n",[1376,19134,19135],{"class":1378,"line":1395},[1376,19136,19137],{},"  name_prefix   = \"web-\"\n",[1376,19139,19140],{"class":1378,"line":1401},[1376,19141,19142],{},"  image_id      = \"ami-0c7217cdde317cfec\"  # Amazon Linux 2023\n",[1376,19144,19145],{"class":1378,"line":1407},[1376,19146,19147],{},"  instance_type = \"t3.micro\"\n",[1376,19149,19150],{"class":1378,"line":1413},[1376,19151,19152],{},"  key_name      = \"minha-chave\"\n",[1376,19154,19155],{"class":1378,"line":1419},[1376,19156,1398],{"emptyLinePlaceholder":898},[1376,19158,19159],{"class":1378,"line":1424},[1376,19160,19161],{},"  user_data = base64encode(\u003C\u003C-EOF\n",[1376,19163,19164],{"class":1378,"line":1430},[1376,19165,19166],{},"    #!\u002Fbin\u002Fbash\n",[1376,19168,19169],{"class":1378,"line":1436},[1376,19170,19171],{},"    # user_data = script executado quando o servidor nasce\n",[1376,19173,19174],{"class":1378,"line":1442},[1376,19175,19176],{},"    yum install -y nginx\n",[1376,19178,19179],{"class":1378,"line":1447},[1376,19180,19181],{},"    systemctl start nginx\n",[1376,19183,19184],{"class":1378,"line":1453},[1376,19185,19186],{},"  EOF\n",[1376,19188,19189],{"class":1378,"line":1459},[1376,19190,19191],{},"  )\n",[1376,19193,19194],{"class":1378,"line":2200},[1376,19195,5170],{},[1376,19197,19198],{"class":1378,"line":2208},[1376,19199,1398],{"emptyLinePlaceholder":898},[1376,19201,19202],{"class":1378,"line":2219},[1376,19203,19204],{},"resource \"aws_autoscaling_group\" \"web\" {\n",[1376,19206,19207],{"class":1378,"line":2227},[1376,19208,19209],{},"  name                = \"web-asg\"\n",[1376,19211,19212],{"class":1378,"line":2241},[1376,19213,19214],{},"  vpc_zone_identifier = [aws_subnet.private_a.id, aws_subnet.private_b.id]\n",[1376,19216,19217],{"class":1378,"line":2253},[1376,19218,19219],{},"  desired_capacity    = 2   # Quantos servidores queremos agora\n",[1376,19221,19222],{"class":1378,"line":2283},[1376,19223,19224],{},"  min_size            = 2   # Mínimo — nunca menos que isso\n",[1376,19226,19227],{"class":1378,"line":2296},[1376,19228,19229],{},"  max_size            = 10  # Máximo — limite de custo\n",[1376,19231,19232],{"class":1378,"line":2311},[1376,19233,1398],{"emptyLinePlaceholder":898},[1376,19235,19236],{"class":1378,"line":2316},[1376,19237,19238],{},"  launch_template {\n",[1376,19240,19241],{"class":1378,"line":2324},[1376,19242,19243],{},"    id      = aws_launch_template.web.id\n",[1376,19245,19246],{"class":1378,"line":2333},[1376,19247,19248],{},"    version = \"$Latest\"\n",[1376,19250,19251],{"class":1378,"line":2340},[1376,19252,19253],{},"  }\n",[1376,19255,19256],{"class":1378,"line":2351},[1376,19257,5170],{},[283,19259,19110],{"id":19260},"políticas-de-escalonamento",[86,19262,19263],{},"Existem três tipos principais:",[165,19265,19266,19277],{},[168,19267,19268],{},[171,19269,19270,19273,19275],{},[174,19271,19272],{},"Política",[174,19274,1585],{},[174,19276,1588],{},[184,19278,19279,19292,19305],{},[171,19280,19281,19286,19289],{},[189,19282,19283],{},[89,19284,19285],{},"Target tracking",[189,19287,19288],{},"Mantém uma métrica em um alvo (ex.: CPU em 50%)",[189,19290,19291],{},"Simples, recomendado para começar",[171,19293,19294,19299,19302],{},[189,19295,19296],{},[89,19297,19298],{},"Step scaling",[189,19300,19301],{},"Adiciona\u002Fremove em passos conforme a métrica sobe",[189,19303,19304],{},"Controle fino, regras customizadas",[171,19306,19307,19312,19315],{},[189,19308,19309],{},[89,19310,19311],{},"Scheduled scaling",[189,19313,19314],{},"Escala em horários fixos (ex.: 6h e 20h)",[189,19316,19317],{},"Tráfego previsível (horário comercial)",[506,19319,19321],{"className":19114,"code":19320,"language":19116,"meta":515,"style":515},"# Política target tracking: mantém CPU média em 50%\nresource \"aws_autoscaling_policy\" \"web_cpu\" {\n  name                   = \"web-cpu-policy\"\n  autoscaling_group_name = aws_autoscaling_group.web.name\n  policy_type            = \"TargetTrackingScaling\"\n\n  target_tracking_configuration {\n    predefined_metric_specification {\n      predefined_metric_type = \"ASGAverageCPUUtilization\"\n    }\n    target_value = 50.0  # Mantém CPU em 50%\n  }\n}\n",[513,19322,19323,19328,19333,19338,19343,19348,19352,19357,19362,19367,19371,19376,19380],{"__ignoreMap":515},[1376,19324,19325],{"class":1378,"line":1379},[1376,19326,19327],{},"# Política target tracking: mantém CPU média em 50%\n",[1376,19329,19330],{"class":1378,"line":984},[1376,19331,19332],{},"resource \"aws_autoscaling_policy\" \"web_cpu\" {\n",[1376,19334,19335],{"class":1378,"line":981},[1376,19336,19337],{},"  name                   = \"web-cpu-policy\"\n",[1376,19339,19340],{"class":1378,"line":1395},[1376,19341,19342],{},"  autoscaling_group_name = aws_autoscaling_group.web.name\n",[1376,19344,19345],{"class":1378,"line":1401},[1376,19346,19347],{},"  policy_type            = \"TargetTrackingScaling\"\n",[1376,19349,19350],{"class":1378,"line":1407},[1376,19351,1398],{"emptyLinePlaceholder":898},[1376,19353,19354],{"class":1378,"line":1413},[1376,19355,19356],{},"  target_tracking_configuration {\n",[1376,19358,19359],{"class":1378,"line":1419},[1376,19360,19361],{},"    predefined_metric_specification {\n",[1376,19363,19364],{"class":1378,"line":1424},[1376,19365,19366],{},"      predefined_metric_type = \"ASGAverageCPUUtilization\"\n",[1376,19368,19369],{"class":1378,"line":1430},[1376,19370,5626],{},[1376,19372,19373],{"class":1378,"line":1436},[1376,19374,19375],{},"    target_value = 50.0  # Mantém CPU em 50%\n",[1376,19377,19378],{"class":1378,"line":1442},[1376,19379,19253],{},[1376,19381,19382],{"class":1378,"line":1447},[1376,19383,5170],{},[86,19385,19386],{},"Com essa configuração:",[528,19388,19389,19392,19395],{},[20,19390,19391],{},"Se CPU passar de 50%, o ASG adiciona servidores",[20,19393,19394],{},"Se CPU cair, o ASG remove servidores (até o mínimo de 2)",[20,19396,19397],{},"Nunca passa de 10 servidores (limite de custo)",[283,19399,19401],{"id":19400},"a-economia-do-auto-scaling","A economia do auto-scaling",[86,19403,19404],{},"Considere uma aplicação com tráfego previsível:",[165,19406,19407,19423],{},[168,19408,19409],{},[171,19410,19411,19414,19417,19420],{},[174,19412,19413],{},"Horário",[174,19415,19416],{},"Tráfego",[174,19418,19419],{},"Sem ASG",[174,19421,19422],{},"Com ASG",[184,19424,19425,19439,19451,19464],{},[171,19426,19427,19430,19433,19436],{},[189,19428,19429],{},"00h-06h",[189,19431,19432],{},"Mínimo",[189,19434,19435],{},"5 servidores",[189,19437,19438],{},"2 servidores",[171,19440,19441,19444,19446,19448],{},[189,19442,19443],{},"06h-12h",[189,19445,16445],{},[189,19447,19435],{},[189,19449,19450],{},"4 servidores",[171,19452,19453,19456,19459,19461],{},[189,19454,19455],{},"12h-18h",[189,19457,19458],{},"Pico",[189,19460,19435],{},[189,19462,19463],{},"8 servidores",[171,19465,19466,19469,19471,19473],{},[189,19467,19468],{},"18h-24h",[189,19470,16445],{},[189,19472,19435],{},[189,19474,19475],{},"3 servidores",[86,19477,19478],{},"Sem ASG, você mantém 5 servidores sempre (no pico, talvez não aguente; fora do\npico, você paga demais). Com ASG, o número varia de 2 a 8 — e a média diária é\n~4,2 servidores em vez de 5. Mas a grande economia está em combinar com Spot\nInstances (próxima seção).",[12,19480,19482],{"id":19481},"spot-instances-poder-de-fogo-por-menos-spot","Spot Instances: poder de fogo por menos {#spot}",[86,19484,19485,19488],{},[89,19486,19487],{},"Spot Instances"," são servidores AWS que ficaram sobrando, oferecidos com\ndesconto de até 90%. O truque: a AWS pode interromper seu servidor com 2\nminutos de aviso quando precisar da capacidade de volta.",[86,19490,19491,19493],{},[89,19492,3011],{},": é como aquelas passagens de última hora que as companhias aéreas\nvendem barato para não voar com assentos vazios. O preço é ótimo, mas se\nalguém aparecer com bilhete cheio, você cede o lugar.",[283,19495,19497],{"id":19496},"quando-usar-spot-instances","Quando usar Spot Instances",[165,19499,19500,19511],{},[168,19501,19502],{},[171,19503,19504,19506,19509],{},[174,19505,8776],{},[174,19507,19508],{},"Vale Spot?",[174,19510,8781],{},[184,19512,19513,19523,19533,19543,19554],{},[171,19514,19515,19518,19520],{},[189,19516,19517],{},"Servidores web com ASG",[189,19519,10629],{},[189,19521,19522],{},"Se um cai, o ASG cria outro",[171,19524,19525,19528,19530],{},[189,19526,19527],{},"Batch processing (filas)",[189,19529,10629],{},[189,19531,19532],{},"Tarefa interrompida volta para a fila",[171,19534,19535,19538,19540],{},[189,19536,19537],{},"CI\u002FCD (builds)",[189,19539,10629],{},[189,19541,19542],{},"Build falhada roda de novo",[171,19544,19545,19548,19551],{},[189,19546,19547],{},"Banco de dados de produção",[189,19549,19550],{},"Não",[189,19552,19553],{},"Interrupção = downtime inaceitável",[171,19555,19556,19559,19561],{},[189,19557,19558],{},"Servidor único sem ASG",[189,19560,19550],{},[189,19562,19563],{},"Sem redundância, queda = fora do ar",[283,19565,19567],{"id":19566},"configurando-spot-no-asg","Configurando Spot no ASG",[506,19569,19571],{"className":19114,"code":19570,"language":19116,"meta":515,"style":515},"resource \"aws_autoscaling_group\" \"web\" {\n  name                = \"web-asg-spot\"\n  vpc_zone_identifier = [aws_subnet.private_a.id, aws_subnet.private_b.id]\n  desired_capacity    = 4\n  min_size            = 2\n  max_size            = 12\n\n  # Mix: 2 On-Demand (garantidos) + resto Spot\n  mixed_instances_policy {\n    launch_template {\n      launch_template_specification {\n        launch_template_id = aws_launch_template.web.id\n        version            = \"$Latest\"\n      }\n\n      # Spot: usa tipos equivalentes para aumentar chance de conseguir\n      override {\n        instance_type = \"t3.micro\"\n      }\n      override {\n        instance_type = \"t3a.micro\"   # Variante AMD (equivalente)\n      }\n      override {\n        instance_type = \"t2.micro\"    # Geração anterior (equivalente)\n      }\n    }\n\n    instances_distribution {\n      on_demand_base_capacity                  = 2  # 2 servidores garantidos\n      on_demand_percentage_above_base_capacity = 0  # Resto 100% Spot\n      spot_allocation_strategy                 = \"price-capacity-optimized\"\n    }\n  }\n}\n",[513,19572,19573,19577,19582,19586,19591,19596,19601,19605,19610,19615,19620,19625,19630,19635,19640,19644,19649,19654,19659,19663,19667,19672,19676,19680,19685,19689,19693,19697,19702,19707,19712,19717,19721,19725],{"__ignoreMap":515},[1376,19574,19575],{"class":1378,"line":1379},[1376,19576,19204],{},[1376,19578,19579],{"class":1378,"line":984},[1376,19580,19581],{},"  name                = \"web-asg-spot\"\n",[1376,19583,19584],{"class":1378,"line":981},[1376,19585,19214],{},[1376,19587,19588],{"class":1378,"line":1395},[1376,19589,19590],{},"  desired_capacity    = 4\n",[1376,19592,19593],{"class":1378,"line":1401},[1376,19594,19595],{},"  min_size            = 2\n",[1376,19597,19598],{"class":1378,"line":1407},[1376,19599,19600],{},"  max_size            = 12\n",[1376,19602,19603],{"class":1378,"line":1413},[1376,19604,1398],{"emptyLinePlaceholder":898},[1376,19606,19607],{"class":1378,"line":1419},[1376,19608,19609],{},"  # Mix: 2 On-Demand (garantidos) + resto Spot\n",[1376,19611,19612],{"class":1378,"line":1424},[1376,19613,19614],{},"  mixed_instances_policy {\n",[1376,19616,19617],{"class":1378,"line":1430},[1376,19618,19619],{},"    launch_template {\n",[1376,19621,19622],{"class":1378,"line":1436},[1376,19623,19624],{},"      launch_template_specification {\n",[1376,19626,19627],{"class":1378,"line":1442},[1376,19628,19629],{},"        launch_template_id = aws_launch_template.web.id\n",[1376,19631,19632],{"class":1378,"line":1447},[1376,19633,19634],{},"        version            = \"$Latest\"\n",[1376,19636,19637],{"class":1378,"line":1453},[1376,19638,19639],{},"      }\n",[1376,19641,19642],{"class":1378,"line":1459},[1376,19643,1398],{"emptyLinePlaceholder":898},[1376,19645,19646],{"class":1378,"line":2200},[1376,19647,19648],{},"      # Spot: usa tipos equivalentes para aumentar chance de conseguir\n",[1376,19650,19651],{"class":1378,"line":2208},[1376,19652,19653],{},"      override {\n",[1376,19655,19656],{"class":1378,"line":2219},[1376,19657,19658],{},"        instance_type = \"t3.micro\"\n",[1376,19660,19661],{"class":1378,"line":2227},[1376,19662,19639],{},[1376,19664,19665],{"class":1378,"line":2241},[1376,19666,19653],{},[1376,19668,19669],{"class":1378,"line":2253},[1376,19670,19671],{},"        instance_type = \"t3a.micro\"   # Variante AMD (equivalente)\n",[1376,19673,19674],{"class":1378,"line":2283},[1376,19675,19639],{},[1376,19677,19678],{"class":1378,"line":2296},[1376,19679,19653],{},[1376,19681,19682],{"class":1378,"line":2311},[1376,19683,19684],{},"        instance_type = \"t2.micro\"    # Geração anterior (equivalente)\n",[1376,19686,19687],{"class":1378,"line":2316},[1376,19688,19639],{},[1376,19690,19691],{"class":1378,"line":2324},[1376,19692,5626],{},[1376,19694,19695],{"class":1378,"line":2333},[1376,19696,1398],{"emptyLinePlaceholder":898},[1376,19698,19699],{"class":1378,"line":2340},[1376,19700,19701],{},"    instances_distribution {\n",[1376,19703,19704],{"class":1378,"line":2351},[1376,19705,19706],{},"      on_demand_base_capacity                  = 2  # 2 servidores garantidos\n",[1376,19708,19709],{"class":1378,"line":2362},[1376,19710,19711],{},"      on_demand_percentage_above_base_capacity = 0  # Resto 100% Spot\n",[1376,19713,19714],{"class":1378,"line":2385},[1376,19715,19716],{},"      spot_allocation_strategy                 = \"price-capacity-optimized\"\n",[1376,19718,19719],{"class":1378,"line":2396},[1376,19720,5626],{},[1376,19722,19723],{"class":1378,"line":2411},[1376,19724,19253],{},[1376,19726,19727],{"class":1378,"line":2416},[1376,19728,5170],{},[86,19730,19731],{},"Essa configuração garante 2 servidores On-Demand (pagos cheio, nunca caem) e\npreenche o resto com Spot. Se Spot cair, o ASG recria em outro tipo\nequivalente automaticamente.",[283,19733,19735],{"id":19734},"preços-comparativos-us-east-1-2026","Preços comparativos (us-east-1, 2026)",[165,19737,19738,19753],{},[168,19739,19740],{},[171,19741,19742,19744,19747,19750],{},[174,19743,381],{},[174,19745,19746],{},"On-Demand (preço cheio)",[174,19748,19749],{},"Spot",[174,19751,19752],{},"Economia",[184,19754,19755,19769,19782,19795,19808],{},[171,19756,19757,19760,19763,19766],{},[189,19758,19759],{},"t3.micro",[189,19761,19762],{},"$0,0104\u002Fh",[189,19764,19765],{},"$0,0031\u002Fh",[189,19767,19768],{},"70%",[171,19770,19771,19774,19777,19780],{},[189,19772,19773],{},"t3.medium",[189,19775,19776],{},"$0,0416\u002Fh",[189,19778,19779],{},"$0,0125\u002Fh",[189,19781,19768],{},[171,19783,19784,19787,19790,19793],{},[189,19785,19786],{},"m5.large",[189,19788,19789],{},"$0,096\u002Fh",[189,19791,19792],{},"$0,029\u002Fh",[189,19794,19768],{},[171,19796,19797,19800,19803,19806],{},[189,19798,19799],{},"m5.xlarge",[189,19801,19802],{},"$0,192\u002Fh",[189,19804,19805],{},"$0,058\u002Fh",[189,19807,19768],{},[171,19809,19810,19813,19816,19819],{},[189,19811,19812],{},"c5.2xlarge",[189,19814,19815],{},"$0,384\u002Fh",[189,19817,19818],{},"$0,115\u002Fh",[189,19820,19768],{},[710,19822,19823],{},[86,19824,19825,19827,19828,19831],{},[89,19826,6471],{},": sempre configure um ",[89,19829,19830],{},"Spot Instance interruption handler"," — um\nscript que detecta o aviso de 2 minutos e drena o servidor graciosamente\nantes da queda. No Kubernetes, o Node Termination Handler faz isso\nautomaticamente.",[12,19833,19835],{"id":19834},"right-sizing-o-tamanho-certo-right-sizing","Right-sizing: o tamanho certo {#right-sizing}",[86,19837,19838,19841],{},[89,19839,19840],{},"Right-sizing"," é o processo de ajustar cada servidor para o tamanho que ele\nrealmente precisa — nem mais, nem menos.",[86,19843,19844,19846],{},[89,19845,3011],{},": você não usa um caminhão para entregar uma pizza, nem uma bicicleta\npara transportar mudança. Cada carga precisa do veículo certo. Na nuvem, a\nmesma lógica: se seu servidor usa 10% de CPU e 20% de memória, ele está\nsuperdimensionado.",[283,19848,19850],{"id":19849},"como-identificar-oportunidades-de-right-sizing","Como identificar oportunidades de right-sizing",[86,19852,19853,19854,19857],{},"Use o ",[89,19855,19856],{},"AWS Compute Optimizer"," (serviço gratuito da AWS) ou ferramentas como\nCloudWatch e Datadog para ver o uso real de cada instância durante 2-4 semanas.",[86,19859,19860],{},"Procure por:",[528,19862,19863,19869,19875,19881],{},[20,19864,19865,19868],{},[89,19866,19867],{},"CPU média \u003C 30%",": a instância está grande demais",[20,19870,19871,19874],{},[89,19872,19873],{},"CPU média > 80%",": está pequena demais (e pode estar lenta)",[20,19876,19877,19880],{},[89,19878,19879],{},"Memória média \u003C 40%",": superdimensionada (requere CloudWatch Agent para\nmétricas de memória, que a AWS não coleta por padrão)",[20,19882,19883,19886],{},[89,19884,19885],{},"Network \u003C 10%",": superdimensionada",[283,19888,19890],{"id":19889},"famílias-de-instâncias-ec2","Famílias de instâncias EC2",[86,19892,19893],{},"A AWS tem dezenas de famílias. As mais comuns:",[165,19895,19896,19910],{},[168,19897,19898],{},[171,19899,19900,19903,19906,19908],{},[174,19901,19902],{},"Família",[174,19904,19905],{},"Foco",[174,19907,5731],{},[174,19909,1588],{},[184,19911,19912,19927,19942,19958,19974],{},[171,19913,19914,19919,19922,19924],{},[189,19915,19916],{},[89,19917,19918],{},"t3\u002Ft3a",[189,19920,19921],{},"Custo baixo, burst",[189,19923,19759],{},[189,19925,19926],{},"Dev, apps leves",[171,19928,19929,19934,19937,19939],{},[189,19930,19931],{},[89,19932,19933],{},"m5\u002Fm5a",[189,19935,19936],{},"Equilibrado",[189,19938,19786],{},[189,19940,19941],{},"Web servers, apps gerais",[171,19943,19944,19949,19952,19955],{},[189,19945,19946],{},[89,19947,19948],{},"c5\u002Fc5a",[189,19950,19951],{},"Computação",[189,19953,19954],{},"c5.xlarge",[189,19956,19957],{},"Batch, processamento",[171,19959,19960,19965,19968,19971],{},[189,19961,19962],{},[89,19963,19964],{},"r5\u002Fr5a",[189,19966,19967],{},"Memória",[189,19969,19970],{},"r5.xlarge",[189,19972,19973],{},"Bancos de dados, caches",[171,19975,19976,19981,19984,19987],{},[189,19977,19978],{},[89,19979,19980],{},"g4\u002Fg5",[189,19982,19983],{},"GPU",[189,19985,19986],{},"g4dn.xlarge",[189,19988,19989],{},"ML, renderização",[86,19991,19992,19995],{},[89,19993,19994],{},"Dica de economia",": a variante \"a\" (t3a, m5a, c5a) usa processadores AMD em\nvez de Intel. São ~10% mais baratas e equivalentes em performance para a\nmaioria dos casos.",[283,19997,19999],{"id":19998},"exemplo-prático-de-right-sizing","Exemplo prático de right-sizing",[86,20001,20002,20003,20005],{},"Antes: 5 instâncias ",[513,20004,19799],{}," (4 vCPUs, 16GB RAM), uso médio de CPU 15% e\nmemória 25%.",[86,20007,20008,20009,20011],{},"Depois: 5 instâncias ",[513,20010,19773],{}," (2 vCPUs, 4GB RAM), capacidade suficiente para\no workload real.",[165,20013,20014,20026],{},[168,20015,20016],{},[171,20017,20018,20020,20023],{},[174,20019,6612],{},[174,20021,20022],{},"Antes (m5.xlarge)",[174,20024,20025],{},"Depois (t3.medium)",[184,20027,20028,20039,20049,20060,20071],{},[171,20029,20030,20033,20036],{},[189,20031,20032],{},"CPU disponível",[189,20034,20035],{},"4 vCPUs",[189,20037,20038],{},"2 vCPUs",[171,20040,20041,20043,20046],{},[189,20042,19967],{},[189,20044,20045],{},"16GB",[189,20047,20048],{},"4GB",[171,20050,20051,20054,20057],{},[189,20052,20053],{},"Preço\u002Fh",[189,20055,20056],{},"$0,192",[189,20058,20059],{},"$0,0416",[171,20061,20062,20065,20068],{},[189,20063,20064],{},"Custo mensal (5 instâncias)",[189,20066,20067],{},"$690",[189,20069,20070],{},"$150",[171,20072,20073,20077,20079],{},[189,20074,20075],{},[89,20076,19752],{},[189,20078,9749],{},[189,20080,20081],{},[89,20082,20083],{},"78%",[710,20085,20086],{},[86,20087,20088,20090,20091,20094,20095,20098],{},[89,20089,4008],{},": t3 é uma instância ",[501,20092,20093],{},"burstable"," (permite picos curtos de CPU\nusando créditos). Se sua app tem CPU sustentada alta, t3 não serve — use m5\nou c5. Monitore os créditos de CPU (",[513,20096,20097],{},"CPUSurplusCredits"," no CloudWatch).",[12,20100,20102],{"id":20101},"reserved-instances-e-savings-plans-reservas","Reserved Instances e Savings Plans {#reservas}",[86,20104,20105],{},"Se você tem servidores que rodam 24\u002F7 (e não podem ser Spot), pode comprometer\ncom a AWS por 1 ou 3 anos em troca de desconto.",[86,20107,20108],{},"Existem duas opções principais:",[283,20110,20112],{"id":20111},"reserved-instances-ri","Reserved Instances (RI)",[86,20114,20115],{},"Você reserva um tipo específico de instância em uma região específica por 1\nou 3 anos. Descontos de 30-72% sobre o preço On-Demand.",[283,20117,20119],{"id":20118},"savings-plans","Savings Plans",[86,20121,20122],{},"Você se compromete a gastar um valor por hora em computação (ex.: $1\u002Fh) por\n1 ou 3 anos. Mais flexível que RI — qualquer tipo de instância, qualquer\nregião, desde que some o valor comprometido. Descontos similares.",[165,20124,20125,20138],{},[168,20126,20127],{},[171,20128,20129,20131,20133,20136],{},[174,20130,8899],{},[174,20132,10548],{},[174,20134,20135],{},"Desconto",[174,20137,1588],{},[184,20139,20140,20155,20170,20186],{},[171,20141,20142,20147,20149,20152],{},[189,20143,20144],{},[89,20145,20146],{},"On-Demand",[189,20148,7964],{},[189,20150,20151],{},"0%",[189,20153,20154],{},"Cargas imprevisíveis, testes",[171,20156,20157,20161,20164,20167],{},[189,20158,20159],{},[89,20160,19749],{},[189,20162,20163],{},"Alta (pode cair)",[189,20165,20166],{},"Até 90%",[189,20168,20169],{},"Cargas tolerantes a interrupção",[171,20171,20172,20177,20180,20183],{},[189,20173,20174],{},[89,20175,20176],{},"Reserved Instance",[189,20178,20179],{},"Baixa (tipo fixo)",[189,20181,20182],{},"30-72%",[189,20184,20185],{},"Cargas previsíveis 24\u002F7",[171,20187,20188,20192,20195,20197],{},[189,20189,20190],{},[89,20191,20119],{},[189,20193,20194],{},"Média (família fixa)",[189,20196,20182],{},[189,20198,20199],{},"Cargas previsíveis multi-tipo",[283,20201,20203],{"id":20202},"estratégia-recomendada","Estratégia recomendada",[86,20205,20206],{},"A combinação ideal para a maioria das empresas:",[17,20208,20209,20215,20221],{},[20,20210,20211,20214],{},[89,20212,20213],{},"Baseline em Savings Plans\u002FRI",": comprometa 60-70% do uso previsível\n(servidores que rodam sempre)",[20,20216,20217,20220],{},[89,20218,20219],{},"Spot para o elástico",": use Spot no ASG para a capacidade variável",[20,20222,20223,20226],{},[89,20224,20225],{},"On-Demand para o resto",": o que sobra para picos imprevisíveis",[86,20228,20229],{},"Exemplo de composição de uma frota de 20 servidores:",[165,20231,20232,20248],{},[168,20233,20234],{},[171,20235,20236,20238,20241,20243,20246],{},[174,20237,6349],{},[174,20239,20240],{},"Quantidade",[174,20242,8899],{},[174,20244,20245],{},"Custo\u002Fh",[174,20247,9234],{},[184,20249,20250,20267,20282,20297],{},[171,20251,20252,20255,20258,20261,20264],{},[189,20253,20254],{},"Base (sempre on)",[189,20256,20257],{},"6",[189,20259,20260],{},"Savings Plan (1 ano)",[189,20262,20263],{},"$0,06",[189,20265,20266],{},"$260",[171,20268,20269,20272,20274,20276,20279],{},[189,20270,20271],{},"Variável",[189,20273,14789],{},[189,20275,19749],{},[189,20277,20278],{},"$0,029",[189,20280,20281],{},"$210",[171,20283,20284,20287,20289,20291,20294],{},[189,20285,20286],{},"Pico imprevisível",[189,20288,5068],{},[189,20290,20146],{},[189,20292,20293],{},"$0,096",[189,20295,20296],{},"$276",[171,20298,20299,20303,20307,20309,20311],{},[189,20300,20301],{},[89,20302,7964],{},[189,20304,20305],{},[89,20306,2267],{},[189,20308,9749],{},[189,20310,9749],{},[189,20312,20313],{},[89,20314,20315],{},"$746",[86,20317,20318,20319,522],{},"Comparado a 20 servidores On-Demand ($1.380\u002Fmês), a economia é ",[89,20320,20321],{},"46%",[12,20323,20325],{"id":20324},"cálculo-real-de-economia-calculo","Cálculo real de economia {#calculo}",[86,20327,20328],{},"Vamos juntar tudo em um cenário realista.",[283,20330,20332],{"id":20331},"cenário-inicial-sem-otimização","Cenário inicial (sem otimização)",[86,20334,20335,20336,20338],{},"Uma startup tem 8 instâncias ",[513,20337,19786],{}," rodando 24\u002F7 na us-east-1, sem\nauto-scaling, tudo On-Demand.",[528,20340,20341,20344],{},[20,20342,20343],{},"Preço On-Demand m5.large: $0,096\u002Fh",[20,20345,20346,20347],{},"8 instâncias × $0,096 × 730h = ",[89,20348,20349],{},"$561\u002Fmês",[283,20351,20353],{"id":20352},"cenário-otimizado","Cenário otimizado",[86,20355,20356],{},"Aplicando as quatro estratégias:",[17,20358,20359,20364,20369,20374],{},[20,20360,20361,20363],{},[89,20362,19840],{},": 8 × m5.large → 6 × t3.medium (suficiente para o workload)",[20,20365,20366,20368],{},[89,20367,19071],{},": em vez de 6 fixos, mínimo 2 + máximo 10 (média ~4\u002Fdia)",[20,20370,20371,20373],{},[89,20372,19749],{},": ASG misto — 2 On-Demand + resto Spot",[20,20375,20376,20379],{},[89,20377,20378],{},"Savings Plan",": as 2 On-Demand em Savings Plan de 1 ano",[86,20381,20382],{},"Cálculo:",[165,20384,20385,20395],{},[168,20386,20387],{},[171,20388,20389,20391,20393],{},[174,20390,596],{},[174,20392,599],{},[174,20394,9234],{},[184,20396,20397,20408,20419],{},[171,20398,20399,20402,20405],{},[189,20400,20401],{},"2 On-Demand c\u002F Savings Plan",[189,20403,20404],{},"2 × $0,025 × 730h",[189,20406,20407],{},"$36,50",[171,20409,20410,20413,20416],{},[189,20411,20412],{},"~3 Spot (média)",[189,20414,20415],{},"3 × $0,0125 × 730h",[189,20417,20418],{},"$27,38",[171,20420,20421,20426,20428],{},[189,20422,20423],{},[89,20424,20425],{},"Total otimizado",[189,20427,9749],{},[189,20429,20430],{},[89,20431,20432],{},"$63,88\u002Fmês",[165,20434,20435,20449],{},[168,20436,20437],{},[171,20438,20439,20441,20444,20447],{},[174,20440],{},[174,20442,20443],{},"Antes",[174,20445,20446],{},"Depois",[174,20448,19752],{},[184,20450,20451,20466,20482],{},[171,20452,20453,20457,20460,20463],{},[189,20454,20455],{},[89,20456,9234],{},[189,20458,20459],{},"$561",[189,20461,20462],{},"$64",[189,20464,20465],{},"$497",[171,20467,20468,20473,20476,20479],{},[189,20469,20470],{},[89,20471,20472],{},"Custo\u002Fano",[189,20474,20475],{},"$6.732",[189,20477,20478],{},"$766",[189,20480,20481],{},"$5.966",[171,20483,20484,20489,20491,20493],{},[189,20485,20486],{},[89,20487,20488],{},"% de economia",[189,20490,9749],{},[189,20492,9749],{},[189,20494,20495],{},[89,20496,20497],{},"89%",[86,20499,20500],{},"Claro, 89% é otimista (depende do tráfego ser muito variável). Um cenário mais\nconservador, com menos variação, ficaria em 50-60% de economia — ainda\nextremamente significativo.",[283,20502,20504],{"id":20503},"outras-otimizações-rápidas","Outras otimizações rápidas",[165,20506,20507,20519],{},[168,20508,20509],{},[171,20510,20511,20514,20517],{},[174,20512,20513],{},"Otimização",[174,20515,20516],{},"Esforço",[174,20518,19752],{},[184,20520,20521,20532,20542,20552,20562],{},[171,20522,20523,20526,20529],{},[189,20524,20525],{},"Apagar EBS volumes não usados",[189,20527,20528],{},"Baixo",[189,20530,20531],{},"5-10%",[171,20533,20534,20537,20539],{},[189,20535,20536],{},"Apagar snapshots antigos",[189,20538,20528],{},[189,20540,20541],{},"2-5%",[171,20543,20544,20547,20549],{},[189,20545,20546],{},"Mover S3 antigo para Glacier",[189,20548,20528],{},[189,20550,20551],{},"40-90% (do S3)",[171,20553,20554,20557,20559],{},[189,20555,20556],{},"Usar CloudFront para reduzir egress",[189,20558,16445],{},[189,20560,20561],{},"10-30% (rede)",[171,20563,20564,20567,20569],{},[189,20565,20566],{},"Desligar ambientes dev à noite",[189,20568,16445],{},[189,20570,20571],{},"65% (do dev)",[12,20573,20575],{"id":20574},"monitoramento-e-alertas-monitoramento","Monitoramento e alertas {#monitoramento}",[86,20577,20578],{},"Otimização não é evento único — é processo contínuo. Sem monitoramento, o\ncusto volta a crescer.",[283,20580,20582],{"id":20581},"aws-cost-explorer","AWS Cost Explorer",[86,20584,20585],{},"Ferramenta gratuita da AWS que mostra seus gastos por serviço, por região, por\ntag. Use para:",[528,20587,20588,20591,20594],{},[20,20589,20590],{},"Identificar tendências de crescimento",[20,20592,20593],{},"Ver quais serviços mais consomem",[20,20595,20596,20597,13125],{},"Filtrar por tag (ex.: ",[513,20598,20599],{},"Environment=prod",[283,20601,20603],{"id":20602},"aws-budgets","AWS Budgets",[86,20605,20606],{},"Crie alertas de orçamento para ser avisado antes de estourar:",[506,20608,20610],{"className":19114,"code":20609,"language":19116,"meta":515,"style":515},"# Alerta quando gasto mensal passar de $500\nresource \"aws_budgets_budget\" \"monthly\" {\n  name              = \"orcamento-mensal\"\n  budget_type       = \"COST\"\n  limit_amount      = \"500\"\n  limit_unit        = \"USD\"\n  time_period_end   = \"2087-06-15_00:00\"\n  time_period_start = \"2026-01-01_00:00\"\n  time_unit         = \"MONTHLY\"\n\n  notification {\n    comparison_operator        = \"GREATER_THAN\"\n    threshold                  = 80    # Avisa ao chegar em 80% ($400)\n    threshold_type             = \"PERCENTAGE\"\n    notification_type          = \"ACTUAL\"\n    subscriber_email_addresses = [\"finops@minhaempresa.com\"]\n  }\n}\n",[513,20611,20612,20617,20622,20627,20632,20637,20642,20647,20652,20657,20661,20666,20671,20676,20681,20686,20691,20695],{"__ignoreMap":515},[1376,20613,20614],{"class":1378,"line":1379},[1376,20615,20616],{},"# Alerta quando gasto mensal passar de $500\n",[1376,20618,20619],{"class":1378,"line":984},[1376,20620,20621],{},"resource \"aws_budgets_budget\" \"monthly\" {\n",[1376,20623,20624],{"class":1378,"line":981},[1376,20625,20626],{},"  name              = \"orcamento-mensal\"\n",[1376,20628,20629],{"class":1378,"line":1395},[1376,20630,20631],{},"  budget_type       = \"COST\"\n",[1376,20633,20634],{"class":1378,"line":1401},[1376,20635,20636],{},"  limit_amount      = \"500\"\n",[1376,20638,20639],{"class":1378,"line":1407},[1376,20640,20641],{},"  limit_unit        = \"USD\"\n",[1376,20643,20644],{"class":1378,"line":1413},[1376,20645,20646],{},"  time_period_end   = \"2087-06-15_00:00\"\n",[1376,20648,20649],{"class":1378,"line":1419},[1376,20650,20651],{},"  time_period_start = \"2026-01-01_00:00\"\n",[1376,20653,20654],{"class":1378,"line":1424},[1376,20655,20656],{},"  time_unit         = \"MONTHLY\"\n",[1376,20658,20659],{"class":1378,"line":1430},[1376,20660,1398],{"emptyLinePlaceholder":898},[1376,20662,20663],{"class":1378,"line":1436},[1376,20664,20665],{},"  notification {\n",[1376,20667,20668],{"class":1378,"line":1442},[1376,20669,20670],{},"    comparison_operator        = \"GREATER_THAN\"\n",[1376,20672,20673],{"class":1378,"line":1447},[1376,20674,20675],{},"    threshold                  = 80    # Avisa ao chegar em 80% ($400)\n",[1376,20677,20678],{"class":1378,"line":1453},[1376,20679,20680],{},"    threshold_type             = \"PERCENTAGE\"\n",[1376,20682,20683],{"class":1378,"line":1459},[1376,20684,20685],{},"    notification_type          = \"ACTUAL\"\n",[1376,20687,20688],{"class":1378,"line":2200},[1376,20689,20690],{},"    subscriber_email_addresses = [\"finops@minhaempresa.com\"]\n",[1376,20692,20693],{"class":1378,"line":2208},[1376,20694,19253],{},[1376,20696,20697],{"class":1378,"line":2219},[1376,20698,5170],{},[283,20700,20702],{"id":20701},"tags-a-base-da-governança","Tags: a base da governança",[86,20704,20705],{},"Sem tags, é impossível saber quem gastou o quê. Defina tags obrigatórias:",[165,20707,20708,20720],{},[168,20709,20710],{},[171,20711,20712,20715,20717],{},[174,20713,20714],{},"Tag",[174,20716,5731],{},[174,20718,20719],{},"Para quê",[184,20721,20722,20735,20748,20761],{},[171,20723,20724,20729,20732],{},[189,20725,20726],{},[513,20727,20728],{},"Environment",[189,20730,20731],{},"dev, staging, prod",[189,20733,20734],{},"Separar custos por ambiente",[171,20736,20737,20742,20745],{},[189,20738,20739],{},[513,20740,20741],{},"Project",[189,20743,20744],{},"app-x, site-y",[189,20746,20747],{},"Atribuir custo a projetos",[171,20749,20750,20755,20758],{},[189,20751,20752],{},[513,20753,20754],{},"Owner",[189,20756,20757],{},"time-backend",[189,20759,20760],{},"Responsável pelo recurso",[171,20762,20763,20768,20771],{},[189,20764,20765],{},[513,20766,20767],{},"CostCenter",[189,20769,20770],{},"CC-1001",[189,20772,20773],{},"Atribuir a centro de custo",[86,20775,20776,20777,20780],{},"Use ",[89,20778,20779],{},"AWS Tag Policies"," para obrigar que todo recurso seja criado com essas\ntags.",[12,20782,15701],{"id":15700},[283,20784,20786],{"id":20785},"_1-auto-scaling-sem-cooldown","1. Auto-scaling sem cooldown",[86,20788,20789,20791],{},[89,20790,4750],{},": sem período de cooldown (resfriamento), o ASG pode adicionar e\nremover servidores freneticamente — \"thrashing\". CPU sobe, adiciona; CPU cai,\nremove; CPU sobe de novo...",[86,20793,20794,20796],{},[89,20795,15721],{},": defina um cooldown de 300s (5 minutos) entre ações de escalonamento.",[283,20798,20800],{"id":20799},"_2-mínimo-do-asg-muito-alto","2. Mínimo do ASG muito alto",[86,20802,20803,20805,20806,20809],{},[89,20804,4750],{},": definir ",[513,20807,20808],{},"min_size = 5"," \"para garantir\". À noite, com 0 tráfego,\nvocê mantém 5 servidores rodando sem necessidade.",[86,20811,20812,20814,20815,20818],{},[89,20813,15721],{},": o mínimo deve ser o número de servidores que aguenta seu tráfego\n",[501,20816,20817],{},"de vale"," (mínimo do dia), não o de pico. Combinado com Spot, pode ser baixo.",[283,20820,20822],{"id":20821},"_3-usar-spot-para-banco-de-dados","3. Usar Spot para banco de dados",[86,20824,20825,20827],{},[89,20826,4750],{},": Spot cai sem aviso (com 2 min), banco cai, aplicação para,\ndados podem corromper.",[86,20829,20830,20832],{},[89,20831,15721],{},": banco de dados sempre em On-Demand ou RI\u002FSavings Plans. Spot é\npara camada de aplicação (stateless, sem estado) que tolera quedas.",[283,20834,20836],{"id":20835},"_4-comprar-reserved-instance-sem-analisar-histórico","4. Comprar Reserved Instance sem analisar histórico",[86,20838,20839,20841],{},[89,20840,4750],{},": comprometer 1-3 anos em um tipo de instância que você pode não\nprecisar mais depois de right-sizing.",[86,20843,20844,20846,20847,20849],{},[89,20845,15721],{},": faça right-sizing ",[501,20848,13459],{}," de comprar RI. Depois de estabilizar os\ntipos, compre RI para a baseline. Comece com Savings Plans (mais flexível) se\ninseguro.",[283,20851,20853],{"id":20852},"_5-ignorar-egress-transferência-de-dados","5. Ignorar egress (transferência de dados)",[86,20855,20856,20858],{},[89,20857,4750],{},": focar só em EC2 e esquecer que transferência de dados entre\nregiões ou para a internet custa caro. Uma app que transfere 1TB\u002Fmês para\nclientes pode pagar $90 só de egress.",[86,20860,20861,13144,20863,20866],{},[89,20862,15721],{},[89,20864,20865],{},"CloudFront"," (CDN da AWS) para cachear conteúdo perto dos\nusuários — reduz tanto egress quanto latência. Transferência entre\ndisponibilidades (AZs) na mesma região também cobra: evite arquiteturas que\nfalam entre AZs excessivamente.",[968,20868],{},[86,20870,20871,20874,20875,522],{},[89,20872,20873],{},"Precisa de ajuda com otimização de custos na AWS?"," A Inicialize Tec\nimplementa auto-scaling, right-sizing e estratégias de Reserved\u002FSpot para\nreduzir sua conta em até 60%. ",[23,20876,2881],{"href":978},[2883,20878,4828],{},{"title":515,"searchDepth":981,"depth":981,"links":20880},[20881,20882,20883,20888,20893,20898,20903,20908,20913],{"id":14,"depth":984,"text":15},{"id":19027,"depth":984,"text":19028},{"id":19065,"depth":984,"text":19066,"children":20884},[20885,20886,20887],{"id":19087,"depth":981,"text":19088},{"id":19260,"depth":981,"text":19110},{"id":19400,"depth":981,"text":19401},{"id":19481,"depth":984,"text":19482,"children":20889},[20890,20891,20892],{"id":19496,"depth":981,"text":19497},{"id":19566,"depth":981,"text":19567},{"id":19734,"depth":981,"text":19735},{"id":19834,"depth":984,"text":19835,"children":20894},[20895,20896,20897],{"id":19849,"depth":981,"text":19850},{"id":19889,"depth":981,"text":19890},{"id":19998,"depth":981,"text":19999},{"id":20101,"depth":984,"text":20102,"children":20899},[20900,20901,20902],{"id":20111,"depth":981,"text":20112},{"id":20118,"depth":981,"text":20119},{"id":20202,"depth":981,"text":20203},{"id":20324,"depth":984,"text":20325,"children":20904},[20905,20906,20907],{"id":20331,"depth":981,"text":20332},{"id":20352,"depth":981,"text":20353},{"id":20503,"depth":981,"text":20504},{"id":20574,"depth":984,"text":20575,"children":20909},[20910,20911,20912],{"id":20581,"depth":981,"text":20582},{"id":20602,"depth":981,"text":20603},{"id":20701,"depth":981,"text":20702},{"id":15700,"depth":984,"text":15701,"children":20914},[20915,20916,20917,20918,20919],{"id":20785,"depth":981,"text":20786},{"id":20799,"depth":981,"text":20800},{"id":20821,"depth":981,"text":20822},{"id":20835,"depth":981,"text":20836},{"id":20852,"depth":981,"text":20853},"Aprenda a cortar até 60% da sua conta AWS com auto-scaling, Spot Instances, right-sizing e reservas — com cálculos reais e exemplos práticos.",[20922,20923,20924,20925,19487,20926],"AWS","auto-scaling","custo nuvem","otimização","right-sizing",{},"\u002Finfraestrutura\u002Freduzir-custo-aws-auto-scaling",{"title":18974,"description":20920},"reduzir-custo-aws-auto-scaling","infraestrutura\u002Freduzir-custo-aws-auto-scaling","-Lb2pq8dj7LnRXUvHiIGIXTcIi3v6us3yZ-v2SFwe8M",{"id":20934,"title":20935,"author":7,"body":20936,"category":16024,"date":1028,"description":22796,"extension":1030,"keywords":22797,"meta":22801,"navigation":898,"path":22802,"seo":22803,"slug":22804,"stem":22805,"updated":1028,"__hash__":22806},"content\u002Finfraestrutura\u002Fterraform-infraestrutura-como-codigo.md","Terraform: infraestrutura como código do zero",{"type":9,"value":20937,"toc":22753},[20938,20940,20986,20990,20993,21007,21022,21029,21034,21038,21092,21098,21102,21105,21184,21189,21221,21225,21230,21258,21264,21335,21338,21342,21345,21349,21356,21360,21367,21371,21381,21384,21395,21398,21402,21405,21427,21439,21443,21450,21452,21475,21482,21626,21630,21643,21646,21650,21662,21665,21671,21686,21690,21702,21709,21713,21725,21733,21737,21743,21835,21906,21909,21936,21942,21967,21971,21978,21982,22005,22009,22020,22023,22079,22093,22097,22103,22107,22117,22122,22126,22132,22290,22387,22393,22420,22424,22430,22506,22513,22515,22522,22527,22539,22543,22559,22576,22580,22592,22597,22640,22647,22655,22660,22678,22681,22711,22715,22720,22728,22734,22741,22743,22751],[12,20939,15],{"id":14},[17,20941,20942,20947,20953,20958,20964,20970,20976,20982],{},[20,20943,20944],{},[23,20945,20946],{"href":25},"O que é infraestrutura como código",[20,20948,20949],{},[23,20950,20952],{"href":20951},"#por-que-terraform","Por que Terraform",[20,20954,20955],{},[23,20956,20957],{"href":6914},"Conceitos essenciais",[20,20959,20960],{},[23,20961,20963],{"href":20962},"#primeira-infra","Sua primeira infraestrutura com Terraform",[20,20965,20966],{},[23,20967,20969],{"href":20968},"#variaveis","Variáveis: reutilização e flexibilidade",[20,20971,20972],{},[23,20973,20975],{"href":20974},"#state","State: a memória do Terraform",[20,20977,20978],{},[23,20979,20981],{"href":20980},"#modulos","Módulos: organização em larga escala",[20,20983,20984],{},[23,20985,74],{"href":13782},[12,20987,20989],{"id":20988},"o-que-é-infraestrutura-como-código-o-que-e","O que é infraestrutura como código {#o-que-e}",[86,20991,20992],{},"Imagine que você precisa construir uma casa. Você tem duas opções:",[17,20994,20995,21001],{},[20,20996,20997,21000],{},[89,20998,20999],{},"Ir comprando materiais no depósito e montando no sentindo",", sem planta.\nCada pedreiro faz do seu jeito. Se a casa cair, ninguém lembra exatamente\ncomo construiu.",[20,21002,21003,21006],{},[89,21004,21005],{},"Contratar um arquiteto que desenha a planta completa",", com cada medida,\ncada material, cada etapa. A construtora segue a planta. Se algo cair, você\nsabe exatamente o que refazer — é só seguir a planta de novo.",[86,21008,21009,21010,21013,21014,21017,21018,21021],{},"A opção 2 é obviamente melhor. E é exatamente isso que ",[89,21011,21012],{},"Infraestrutura como\nCódigo"," (em inglês, ",[501,21015,21016],{},"Infrastructure as Code",", ou ",[89,21019,21020],{},"IaC",") faz para seus\nservidores na nuvem.",[86,21023,21024,21025,21028],{},"Em vez de abrir o painel da AWS, clicar em \"criar servidor\", escolher\nconfigurações, clicar em \"criar banco de dados\", preencher mais formulários —\ntudo manualmente, sem rastro —, você escreve um ",[89,21026,21027],{},"arquivo de texto"," que\ndescreve toda a infraestrutura. Um software lê esse arquivo e cria tudo\nautomaticamente.",[86,21030,21031],{},[89,21032,21033],{},"Infraestrutura como Código é a prática de gerenciar sua infraestrutura de\nTI (servidores, bancos de dados, redes, firewalls) através de arquivos de\ntexto versionáveis, ao invés de configurações manuais em painéis.",[283,21035,21037],{"id":21036},"os-problemas-que-iac-resolve","Os problemas que IaC resolve",[165,21039,21040,21050],{},[168,21041,21042],{},[171,21043,21044,21047],{},[174,21045,21046],{},"Problema (sem IaC)",[174,21048,21049],{},"Solução (com IaC)",[184,21051,21052,21060,21068,21076,21084],{},[171,21053,21054,21057],{},[189,21055,21056],{},"\"Como criamos esse servidor?\" — ninguém lembra",[189,21058,21059],{},"Tudo está no código, versionado no Git",[171,21061,21062,21065],{},[189,21063,21064],{},"Recriar o ambiente de teste leva 2 dias",[189,21066,21067],{},"Um comando recria tudo em minutos",[171,21069,21070,21073],{},[189,21071,21072],{},"Um dev configura diferente do outro",[189,21074,21075],{},"O código é a fonte da verdade, idempotente",[171,21077,21078,21081],{},[189,21079,21080],{},"Mudança manual quebra algo e ninguém sabe o quê",[189,21082,21083],{},"Mudanças passam por code review",[171,21085,21086,21089],{},[189,21087,21088],{},"Auditoria de compliance é impossível",[189,21090,21091],{},"O histórico do Git é a auditoria",[86,21093,21094,21097],{},[89,21095,21096],{},"Idempotente"," é uma palavra importante aqui: significa que executar o mesmo\ncomando várias vezes produz o mesmo resultado. Se você pedir \"crie um servidor\nchamado web-01\" e ele já existe, o Terraform não cria outro — ele reconhece\nque já está lá e não faz nada. Isso evita duplicações acidentais.",[12,21099,21101],{"id":21100},"por-que-terraform-por-que-terraform","Por que Terraform {#por-que-terraform}",[86,21103,21104],{},"Existem várias ferramentas de IaC no mercado. As principais são:",[165,21106,21107,21122],{},[168,21108,21109],{},[171,21110,21111,21113,21116,21119],{},[174,21112,11272],{},[174,21114,21115],{},"Criador",[174,21117,21118],{},"Estilo",[174,21120,21121],{},"Cloud-agnostic?",[184,21123,21124,21139,21154,21168],{},[171,21125,21126,21131,21134,21137],{},[189,21127,21128],{},[89,21129,21130],{},"Terraform",[189,21132,21133],{},"HashiCorp",[189,21135,21136],{},"Declarativo",[189,21138,10629],{},[171,21140,21141,21146,21149,21151],{},[189,21142,21143],{},[89,21144,21145],{},"AWS CloudFormation",[189,21147,21148],{},"Amazon",[189,21150,21136],{},[189,21152,21153],{},"Só AWS",[171,21155,21156,21161,21163,21166],{},[189,21157,21158],{},[89,21159,21160],{},"Pulumi",[189,21162,21160],{},[189,21164,21165],{},"Imperativo (linguagens reais)",[189,21167,10629],{},[171,21169,21170,21175,21178,21181],{},[189,21171,21172],{},[89,21173,21174],{},"Ansible",[189,21176,21177],{},"Red Hat",[189,21179,21180],{},"Procedural\u002FDeclarativo",[189,21182,21183],{},"Sim (mais config mgmt)",[86,21185,21186,21188],{},[89,21187,21130],{}," é a ferramenta mais popular de IaC. Algumas razões:",[528,21190,21191,21203,21209,21215],{},[20,21192,21193,21195,21196,21199,21200,21202],{},[89,21194,21136],{},": você descreve ",[501,21197,21198],{},"o que"," quer (um servidor com 4GB de RAM),\nnão ",[501,21201,3131],{}," chegar lá (os 15 cliques no painel). O Terraform descobre o\n\"como\".",[20,21204,21205,21208],{},[89,21206,21207],{},"Cloud-agnostic"," (independente de nuvem): o mesmo Terraform gerencia AWS,\nGoogle Cloud, Azure, e mais de 3.000 outros provedores. Aprende uma vez,\nusa em qualquer nuvem.",[20,21210,21211,21214],{},[89,21212,21213],{},"Gratuito e open source",": a versão OpenTofu (fork da comunidade) é 100%\ngratuita. A HashiCorp oferece versão paga com features enterprise.",[20,21216,21217,21220],{},[89,21218,21219],{},"Comunidade gigante",": milhares de módulos prontos para usar.",[283,21222,21224],{"id":21223},"declarativo-vs-imperativo","Declarativo vs imperativo",[86,21226,21227,21229],{},[89,21228,21136],{}," (Terraform): você diz o resultado final que quer.",[506,21231,21233],{"className":19114,"code":21232,"language":19116,"meta":515,"style":515},"# \"Quero um servidor EC2 com esta configuração\"\nresource \"aws_instance\" \"web\" {\n  ami           = \"ami-123456\"\n  instance_type = \"t3.micro\"\n}\n",[513,21234,21235,21240,21245,21250,21254],{"__ignoreMap":515},[1376,21236,21237],{"class":1378,"line":1379},[1376,21238,21239],{},"# \"Quero um servidor EC2 com esta configuração\"\n",[1376,21241,21242],{"class":1378,"line":984},[1376,21243,21244],{},"resource \"aws_instance\" \"web\" {\n",[1376,21246,21247],{"class":1378,"line":981},[1376,21248,21249],{},"  ami           = \"ami-123456\"\n",[1376,21251,21252],{"class":1378,"line":1395},[1376,21253,19147],{},[1376,21255,21256],{"class":1378,"line":1401},[1376,21257,5170],{},[86,21259,21260,21263],{},[89,21261,21262],{},"Imperativo"," (scripts Bash, por exemplo): você diz cada passo.",[506,21265,21267],{"className":11827,"code":21266,"language":11829,"meta":515,"style":515},"# \"Faça isto, depois aquilo, depois aquilo outro\"\naws ec2 run-instances --image-id ami-123456 --instance-type t3.micro\naws ec2 create-tags --resources i-abc123 --tags Key=Name,Value=web\naws ec2 modify-instance-attribute --instance-id i-abc123 ...\n",[513,21268,21269,21274,21297,21318],{"__ignoreMap":515},[1376,21270,21271],{"class":1378,"line":1379},[1376,21272,21273],{"class":1665},"# \"Faça isto, depois aquilo, depois aquilo outro\"\n",[1376,21275,21276,21279,21282,21285,21288,21291,21294],{"class":1378,"line":984},[1376,21277,21278],{"class":11841},"aws",[1376,21280,21281],{"class":1682}," ec2",[1376,21283,21284],{"class":1682}," run-instances",[1376,21286,21287],{"class":1770}," --image-id",[1376,21289,21290],{"class":1682}," ami-123456",[1376,21292,21293],{"class":1770}," --instance-type",[1376,21295,21296],{"class":1682}," t3.micro\n",[1376,21298,21299,21301,21303,21306,21309,21312,21315],{"class":1378,"line":981},[1376,21300,21278],{"class":11841},[1376,21302,21281],{"class":1682},[1376,21304,21305],{"class":1682}," create-tags",[1376,21307,21308],{"class":1770}," --resources",[1376,21310,21311],{"class":1682}," i-abc123",[1376,21313,21314],{"class":1770}," --tags",[1376,21316,21317],{"class":1682}," Key=Name,Value=web\n",[1376,21319,21320,21322,21324,21327,21330,21332],{"class":1378,"line":1395},[1376,21321,21278],{"class":11841},[1376,21323,21281],{"class":1682},[1376,21325,21326],{"class":1682}," modify-instance-attribute",[1376,21328,21329],{"class":1770}," --instance-id",[1376,21331,21311],{"class":1682},[1376,21333,21334],{"class":1682}," ...\n",[86,21336,21337],{},"O estilo declarativo é melhor para infraestrutura porque você não precisa\nrastrear o estado atual — apenas declara o desejado e a ferramenta calcula a\ndiferença.",[12,21339,21341],{"id":21340},"conceitos-essenciais-conceitos","Conceitos essenciais {#conceitos}",[86,21343,21344],{},"Antes de escrever código, entenda estes quatro conceitos:",[283,21346,21348],{"id":21347},"_1-provider-provedor","1. Provider (provedor)",[86,21350,21351,21352,21355],{},"Um provider é um ",[89,21353,21354],{},"plugin"," que permite ao Terraform falar com um serviço.\nExiste um provider para AWS, um para Google Cloud, um para GitHub, um para\nKubernetes, etc. O provider traduz os comandos do Terraform em chamadas de\nAPI do serviço.",[283,21357,21359],{"id":21358},"_2-resource-recurso","2. Resource (recurso)",[86,21361,21362,21363,21366],{},"Um resource é a ",[89,21364,21365],{},"unidade básica"," de infraestrutura. Um servidor, um banco de\ndados, uma regra de firewall, um bucket de armazenamento — cada um é um\nresource. A maioria dos seus arquivos Terraform será composta de resources.",[283,21368,21370],{"id":21369},"_3-state-estado","3. State (estado)",[86,21372,15435,21373,21376,21377,21380],{},[89,21374,21375],{},"state"," é um arquivo (",[513,21378,21379],{},".tfstate",") que registra o mapeamento entre o que\nestá no seu código e o que existe na nuvem. É a \"memória\" do Terraform.",[86,21382,21383],{},"Por que o state é necessário? Porque o Terraform é declarativo e idempotente.\nPara saber se precisa criar, modificar ou destruir algo, ele compara:",[528,21385,21386,21389,21392],{},[20,21387,21388],{},"O que está no seu código (estado desejado)",[20,21390,21391],{},"O que está no state (o que ele criou da última vez)",[20,21393,21394],{},"O que está na nuvem (estado real)",[86,21396,21397],{},"Sem o state, o Terraform não conseguiria saber se aquele servidor foi criado\npor ele ou por outra pessoa.",[283,21399,21401],{"id":21400},"_4-plan-e-apply","4. Plan e Apply",[86,21403,21404],{},"Terraform opera em dois passos principais:",[528,21406,21407,21419],{},[20,21408,21409,21414,21415,21418],{},[89,21410,21411],{},[513,21412,21413],{},"terraform plan",": mostra o que ",[501,21416,21417],{},"vai"," fazer, sem fazer nada. É uma\nsimulação. \"Vou criar 1 servidor, 1 banco de dados, e destruir 1 regra de\nfirewall antiga.\" Você revisa antes de prosseguir.",[20,21420,21421,21426],{},[89,21422,21423],{},[513,21424,21425],{},"terraform apply",": executa de fato as mudanças mostradas no plan.",[86,21428,21429,1679,21431,21434,21435,21438],{},[89,21430,3011],{},[513,21432,21433],{},"plan"," é o orçamento que o arquiteto te mostra antes de\nconstruir. ",[513,21436,21437],{},"apply"," é quando você aprova e a construtora começa a obra.",[12,21440,21442],{"id":21441},"sua-primeira-infraestrutura-com-terraform-primeira-infra","Sua primeira infraestrutura com Terraform {#primeira-infra}",[86,21444,21445,21446,21449],{},"Vamos criar um servidor EC2 na AWS. ",[89,21447,21448],{},"EC2"," (Elastic Compute Cloud) é o\nserviço de servidores virtuais da AWS — basicamente, um computador que você\naluga na nuvem.",[283,21451,17108],{"id":17107},[17,21453,21454,21460,21472],{},[20,21455,21456,21459],{},[89,21457,21458],{},"Terraform instalado"," (versão 1.0+)",[20,21461,21462,21465,21466,3022,21469,13125],{},[89,21463,21464],{},"Conta na AWS"," com credenciais configuradas (variáveis de ambiente\n",[513,21467,21468],{},"AWS_ACCESS_KEY_ID",[513,21470,21471],{},"AWS_SECRET_ACCESS_KEY",[20,21473,21474],{},"Um editor de texto",[283,21476,21478,21479],{"id":21477},"passo-1-criar-o-arquivo-maintf","Passo 1: criar o arquivo ",[513,21480,21481],{},"main.tf",[506,21483,21485],{"className":19114,"code":21484,"language":19116,"meta":515,"style":515},"# main.tf — arquivo principal do Terraform\n\n# Diz ao Terraform que vamos usar a AWS como provedor\nterraform {\n  required_providers {\n    aws = {\n      source  = \"hashicorp\u002Faws\"  # Onde baixar o provider\n      version = \"~> 5.0\"         # Versão 5.x (qualquer 5.algo)\n    }\n  }\n}\n\n# Configura o provider da AWS\nprovider \"aws\" {\n  region = \"us-east-1\"  # Norte da Virgínia — a região mais barata da AWS\n}\n\n# Cria um servidor EC2\n# \"aws_instance\" é o tipo do resource (uma instância EC2)\n# \"web\" é o nome lógico que damos (para referenciar depois)\nresource \"aws_instance\" \"web\" {\n  ami           = \"ami-0c7217cdde317cfec\"  # ID da imagem (Amazon Linux 2023)\n  instance_type = \"t3.micro\"               # Tamanho: 2 vCPUs, 1GB RAM\n\n  tags = {\n    Name        = \"meu-primeiro-servidor\"   # Nome que aparece no painel AWS\n    Environment = \"dev\"                    # Tag para organizar recursos\n    Owner       = \"inicialize\"\n  }\n}\n",[513,21486,21487,21492,21496,21501,21506,21511,21516,21521,21526,21530,21534,21538,21542,21547,21552,21557,21561,21565,21570,21575,21580,21584,21589,21594,21598,21603,21608,21613,21618,21622],{"__ignoreMap":515},[1376,21488,21489],{"class":1378,"line":1379},[1376,21490,21491],{},"# main.tf — arquivo principal do Terraform\n",[1376,21493,21494],{"class":1378,"line":984},[1376,21495,1398],{"emptyLinePlaceholder":898},[1376,21497,21498],{"class":1378,"line":981},[1376,21499,21500],{},"# Diz ao Terraform que vamos usar a AWS como provedor\n",[1376,21502,21503],{"class":1378,"line":1395},[1376,21504,21505],{},"terraform {\n",[1376,21507,21508],{"class":1378,"line":1401},[1376,21509,21510],{},"  required_providers {\n",[1376,21512,21513],{"class":1378,"line":1407},[1376,21514,21515],{},"    aws = {\n",[1376,21517,21518],{"class":1378,"line":1413},[1376,21519,21520],{},"      source  = \"hashicorp\u002Faws\"  # Onde baixar o provider\n",[1376,21522,21523],{"class":1378,"line":1419},[1376,21524,21525],{},"      version = \"~> 5.0\"         # Versão 5.x (qualquer 5.algo)\n",[1376,21527,21528],{"class":1378,"line":1424},[1376,21529,5626],{},[1376,21531,21532],{"class":1378,"line":1430},[1376,21533,19253],{},[1376,21535,21536],{"class":1378,"line":1436},[1376,21537,5170],{},[1376,21539,21540],{"class":1378,"line":1442},[1376,21541,1398],{"emptyLinePlaceholder":898},[1376,21543,21544],{"class":1378,"line":1447},[1376,21545,21546],{},"# Configura o provider da AWS\n",[1376,21548,21549],{"class":1378,"line":1453},[1376,21550,21551],{},"provider \"aws\" {\n",[1376,21553,21554],{"class":1378,"line":1459},[1376,21555,21556],{},"  region = \"us-east-1\"  # Norte da Virgínia — a região mais barata da AWS\n",[1376,21558,21559],{"class":1378,"line":2200},[1376,21560,5170],{},[1376,21562,21563],{"class":1378,"line":2208},[1376,21564,1398],{"emptyLinePlaceholder":898},[1376,21566,21567],{"class":1378,"line":2219},[1376,21568,21569],{},"# Cria um servidor EC2\n",[1376,21571,21572],{"class":1378,"line":2227},[1376,21573,21574],{},"# \"aws_instance\" é o tipo do resource (uma instância EC2)\n",[1376,21576,21577],{"class":1378,"line":2241},[1376,21578,21579],{},"# \"web\" é o nome lógico que damos (para referenciar depois)\n",[1376,21581,21582],{"class":1378,"line":2253},[1376,21583,21244],{},[1376,21585,21586],{"class":1378,"line":2283},[1376,21587,21588],{},"  ami           = \"ami-0c7217cdde317cfec\"  # ID da imagem (Amazon Linux 2023)\n",[1376,21590,21591],{"class":1378,"line":2296},[1376,21592,21593],{},"  instance_type = \"t3.micro\"               # Tamanho: 2 vCPUs, 1GB RAM\n",[1376,21595,21596],{"class":1378,"line":2311},[1376,21597,1398],{"emptyLinePlaceholder":898},[1376,21599,21600],{"class":1378,"line":2316},[1376,21601,21602],{},"  tags = {\n",[1376,21604,21605],{"class":1378,"line":2324},[1376,21606,21607],{},"    Name        = \"meu-primeiro-servidor\"   # Nome que aparece no painel AWS\n",[1376,21609,21610],{"class":1378,"line":2333},[1376,21611,21612],{},"    Environment = \"dev\"                    # Tag para organizar recursos\n",[1376,21614,21615],{"class":1378,"line":2340},[1376,21616,21617],{},"    Owner       = \"inicialize\"\n",[1376,21619,21620],{"class":1378,"line":2351},[1376,21621,19253],{},[1376,21623,21624],{"class":1378,"line":2362},[1376,21625,5170],{},[283,21627,21629],{"id":21628},"passo-2-inicializar-o-terraform","Passo 2: inicializar o Terraform",[506,21631,21633],{"className":11827,"code":21632,"language":11829,"meta":515,"style":515},"terraform init\n",[513,21634,21635],{"__ignoreMap":515},[1376,21636,21637,21640],{"class":1378,"line":1379},[1376,21638,21639],{"class":11841},"terraform",[1376,21641,21642],{"class":1682}," init\n",[86,21644,21645],{},"Este comando baixa o provider da AWS e prepara o diretório. Você verá algo\ncomo \"Terraform has been successfully initialized!\".",[283,21647,21649],{"id":21648},"passo-3-ver-o-plano-simulação","Passo 3: ver o plano (simulação)",[506,21651,21653],{"className":11827,"code":21652,"language":11829,"meta":515,"style":515},"terraform plan\n",[513,21654,21655],{"__ignoreMap":515},[1376,21656,21657,21659],{"class":1378,"line":1379},[1376,21658,21639],{"class":11841},[1376,21660,21661],{"class":1682}," plan\n",[86,21663,21664],{},"O Terraform vai mostrar:",[506,21666,21669],{"className":21667,"code":21668,"language":511},[509],"Terraform will perform the following actions:\n\n  # aws_instance.web will be created\n  + resource \"aws_instance\" \"web\" {\n      + ami           = \"ami-0c7217cdde317cfec\"\n      + instance_type = \"t3.micro\"\n      + tags          = {\n          + Environment = \"dev\"\n          + Name        = \"meu-primeiro-servidor\"\n          + Owner       = \"inicialize\"\n        }\n    }\n\nPlan: 1 to add, 0 to change, 0 to destroy.\n",[513,21670,21668],{"__ignoreMap":515},[86,21672,21673,21674,21677,21678,21681,21682,21685],{},"O símbolo ",[513,21675,21676],{},"+"," significa \"criar\". Se fosse ",[513,21679,21680],{},"~"," seria \"modificar\", e ",[513,21683,21684],{},"-"," seria\n\"destruir\". Revise com calma — especialmente o número de \"destroy\".",[283,21687,21689],{"id":21688},"passo-4-aplicar-executar-de-verdade","Passo 4: aplicar (executar de verdade)",[506,21691,21693],{"className":11827,"code":21692,"language":11829,"meta":515,"style":515},"terraform apply\n",[513,21694,21695],{"__ignoreMap":515},[1376,21696,21697,21699],{"class":1378,"line":1379},[1376,21698,21639],{"class":11841},[1376,21700,21701],{"class":1682}," apply\n",[86,21703,21704,21705,21708],{},"O Terraform pede confirmação (\"Do you want to perform these actions?\"). Digite\n",[513,21706,21707],{},"yes",". Em segundos, seu servidor está rodando na AWS.",[283,21710,21712],{"id":21711},"passo-5-destruir-para-não-pagar","Passo 5: destruir (para não pagar)",[506,21714,21716],{"className":11827,"code":21715,"language":11829,"meta":515,"style":515},"terraform destroy\n",[513,21717,21718],{"__ignoreMap":515},[1376,21719,21720,21722],{"class":1378,"line":1379},[1376,21721,21639],{"class":11841},[1376,21723,21724],{"class":1682}," destroy\n",[86,21726,21727,21729,21730,21732],{},[89,21728,15630],{},": este comando remove tudo o que o Terraform criou. Use-o quando\nterminar de testar para não gerar custos desnecessários. Digite ",[513,21731,21707],{}," para\nconfirmar.",[12,21734,21736],{"id":21735},"variáveis-reutilização-e-flexibilidade-variaveis","Variáveis: reutilização e flexibilidade {#variaveis}",[86,21738,21739,21740,522],{},"No exemplo anterior, a região e o tipo da instância estavam \"fixos\" no código.\nPara reutilizar o mesmo código em diferentes ambientes (dev, produção), usamos\n",[89,21741,21742],{},"variáveis",[506,21744,21746],{"className":19114,"code":21745,"language":19116,"meta":515,"style":515},"# variables.tf — declara as variáveis\n\nvariable \"aws_region\" {\n  description = \"Região da AWS onde os recursos serão criados\"\n  type        = string\n  default     = \"us-east-1\"  # Valor padrão, se não for passado\n}\n\nvariable \"instance_type\" {\n  description = \"Tipo da instância EC2\"\n  type        = string\n  default     = \"t3.micro\"\n}\n\nvariable \"environment\" {\n  description = \"Nome do ambiente (dev, staging, prod)\"\n  type        = string\n  default     = \"dev\"\n}\n",[513,21747,21748,21753,21757,21762,21767,21772,21777,21781,21785,21790,21795,21799,21804,21808,21812,21817,21822,21826,21831],{"__ignoreMap":515},[1376,21749,21750],{"class":1378,"line":1379},[1376,21751,21752],{},"# variables.tf — declara as variáveis\n",[1376,21754,21755],{"class":1378,"line":984},[1376,21756,1398],{"emptyLinePlaceholder":898},[1376,21758,21759],{"class":1378,"line":981},[1376,21760,21761],{},"variable \"aws_region\" {\n",[1376,21763,21764],{"class":1378,"line":1395},[1376,21765,21766],{},"  description = \"Região da AWS onde os recursos serão criados\"\n",[1376,21768,21769],{"class":1378,"line":1401},[1376,21770,21771],{},"  type        = string\n",[1376,21773,21774],{"class":1378,"line":1407},[1376,21775,21776],{},"  default     = \"us-east-1\"  # Valor padrão, se não for passado\n",[1376,21778,21779],{"class":1378,"line":1413},[1376,21780,5170],{},[1376,21782,21783],{"class":1378,"line":1419},[1376,21784,1398],{"emptyLinePlaceholder":898},[1376,21786,21787],{"class":1378,"line":1424},[1376,21788,21789],{},"variable \"instance_type\" {\n",[1376,21791,21792],{"class":1378,"line":1430},[1376,21793,21794],{},"  description = \"Tipo da instância EC2\"\n",[1376,21796,21797],{"class":1378,"line":1436},[1376,21798,21771],{},[1376,21800,21801],{"class":1378,"line":1442},[1376,21802,21803],{},"  default     = \"t3.micro\"\n",[1376,21805,21806],{"class":1378,"line":1447},[1376,21807,5170],{},[1376,21809,21810],{"class":1378,"line":1453},[1376,21811,1398],{"emptyLinePlaceholder":898},[1376,21813,21814],{"class":1378,"line":1459},[1376,21815,21816],{},"variable \"environment\" {\n",[1376,21818,21819],{"class":1378,"line":2200},[1376,21820,21821],{},"  description = \"Nome do ambiente (dev, staging, prod)\"\n",[1376,21823,21824],{"class":1378,"line":2208},[1376,21825,21771],{},[1376,21827,21828],{"class":1378,"line":2219},[1376,21829,21830],{},"  default     = \"dev\"\n",[1376,21832,21833],{"class":1378,"line":2227},[1376,21834,5170],{},[506,21836,21838],{"className":19114,"code":21837,"language":19116,"meta":515,"style":515},"# main.tf — usando as variáveis\n\nprovider \"aws\" {\n  region = var.aws_region  # Referência à variável com var.NOME\n}\n\nresource \"aws_instance\" \"web\" {\n  ami           = \"ami-0c7217cdde317cfec\"\n  instance_type = var.instance_type\n\n  tags = {\n    Name        = \"web-${var.environment}\"  # Interpolação: web-dev, web-prod\n    Environment = var.environment\n  }\n}\n",[513,21839,21840,21845,21849,21853,21858,21862,21866,21870,21875,21880,21884,21888,21893,21898,21902],{"__ignoreMap":515},[1376,21841,21842],{"class":1378,"line":1379},[1376,21843,21844],{},"# main.tf — usando as variáveis\n",[1376,21846,21847],{"class":1378,"line":984},[1376,21848,1398],{"emptyLinePlaceholder":898},[1376,21850,21851],{"class":1378,"line":981},[1376,21852,21551],{},[1376,21854,21855],{"class":1378,"line":1395},[1376,21856,21857],{},"  region = var.aws_region  # Referência à variável com var.NOME\n",[1376,21859,21860],{"class":1378,"line":1401},[1376,21861,5170],{},[1376,21863,21864],{"class":1378,"line":1407},[1376,21865,1398],{"emptyLinePlaceholder":898},[1376,21867,21868],{"class":1378,"line":1413},[1376,21869,21244],{},[1376,21871,21872],{"class":1378,"line":1419},[1376,21873,21874],{},"  ami           = \"ami-0c7217cdde317cfec\"\n",[1376,21876,21877],{"class":1378,"line":1424},[1376,21878,21879],{},"  instance_type = var.instance_type\n",[1376,21881,21882],{"class":1378,"line":1430},[1376,21883,1398],{"emptyLinePlaceholder":898},[1376,21885,21886],{"class":1378,"line":1436},[1376,21887,21602],{},[1376,21889,21890],{"class":1378,"line":1442},[1376,21891,21892],{},"    Name        = \"web-${var.environment}\"  # Interpolação: web-dev, web-prod\n",[1376,21894,21895],{"class":1378,"line":1447},[1376,21896,21897],{},"    Environment = var.environment\n",[1376,21899,21900],{"class":1378,"line":1453},[1376,21901,19253],{},[1376,21903,21904],{"class":1378,"line":1459},[1376,21905,5170],{},[86,21907,21908],{},"Para passar valores diferentes na linha de comando:",[506,21910,21912],{"className":11827,"code":21911,"language":11829,"meta":515,"style":515},"# Cria um servidor maior em produção\nterraform apply -var=\"instance_type=t3.large\" -var=\"environment=prod\"\n",[513,21913,21914,21919],{"__ignoreMap":515},[1376,21915,21916],{"class":1378,"line":1379},[1376,21917,21918],{"class":1665},"# Cria um servidor maior em produção\n",[1376,21920,21921,21923,21925,21928,21931,21933],{"class":1378,"line":984},[1376,21922,21639],{"class":11841},[1376,21924,15193],{"class":1682},[1376,21926,21927],{"class":1770}," -var=",[1376,21929,21930],{"class":1682},"\"instance_type=t3.large\"",[1376,21932,21927],{"class":1770},[1376,21934,21935],{"class":1682},"\"environment=prod\"\n",[86,21937,21938,21939,4171],{},"Ou crie um arquivo ",[513,21940,21941],{},"terraform.tfvars",[506,21943,21945],{"className":19114,"code":21944,"language":19116,"meta":515,"style":515},"# terraform.tfvars — valores específicos por ambiente\ninstance_type = \"t3.large\"\nenvironment   = \"prod\"\naws_region    = \"sa-east-1\"  # São Paulo (mais caro, mas menor latência no Brasil)\n",[513,21946,21947,21952,21957,21962],{"__ignoreMap":515},[1376,21948,21949],{"class":1378,"line":1379},[1376,21950,21951],{},"# terraform.tfvars — valores específicos por ambiente\n",[1376,21953,21954],{"class":1378,"line":984},[1376,21955,21956],{},"instance_type = \"t3.large\"\n",[1376,21958,21959],{"class":1378,"line":981},[1376,21960,21961],{},"environment   = \"prod\"\n",[1376,21963,21964],{"class":1378,"line":1395},[1376,21965,21966],{},"aws_region    = \"sa-east-1\"  # São Paulo (mais caro, mas menor latência no Brasil)\n",[12,21968,21970],{"id":21969},"state-a-memória-do-terraform-state","State: a memória do Terraform {#state}",[86,21972,21973,21974,21977],{},"O arquivo de state (",[513,21975,21976],{},"terraform.tfstate",") é crítico. Se você o perder, o\nTerraform perde a noção do que criou — e pode duplicar recursos ou falhar ao\ndestruir.",[283,21979,21981],{"id":21980},"regras-de-ouro-do-state","Regras de ouro do state",[17,21983,21984,21993,21999],{},[20,21985,21986,21992],{},[89,21987,21988,21989,21991],{},"Nunca edite o ",[513,21990,21379],{}," manualmente",". Sempre use comandos do Terraform.",[20,21994,21995,21998],{},[89,21996,21997],{},"Sempre armazene"," o state remotamente, não localmente.",[20,22000,22001,22004],{},[89,22002,22003],{},"Sempre trave"," o state durante uso (locking) para evitar corrupção.",[283,22006,22008],{"id":22007},"remote-state-state-remoto","Remote state (state remoto)",[86,22010,22011,22012,22015,22016,22019],{},"Para times, o state deve ficar em um local compartilhado com bloqueio (lock).\nNa AWS, usamos um ",[89,22013,22014],{},"bucket S3"," com uma tabela ",[89,22017,22018],{},"DynamoDB"," para lock.",[86,22021,22022],{},"S3 é o serviço de armazenamento de arquivos da AWS. DynamoDB é um banco de\ndados NoSQL da AWS — neste caso, usado só para gerenciar locks.",[506,22024,22026],{"className":19114,"code":22025,"language":19116,"meta":515,"style":515},"# backend.tf — configura onde o state é guardado\n\nterraform {\n  backend \"s3\" {\n    bucket         = \"meu-terraform-state-dev\"  # Bucket S3 que guarda o state\n    key            = \"infra\u002Fterraform.tfstate\"  # Caminho do arquivo dentro do bucket\n    region         = \"us-east-1\"                # Região do bucket\n    dynamodb_table = \"terraform-locks\"          # Tabela para lock (evita edição simultânea)\n    encrypt        = true                       # Criptografa o state em repouso\n  }\n}\n",[513,22027,22028,22033,22037,22041,22046,22051,22056,22061,22066,22071,22075],{"__ignoreMap":515},[1376,22029,22030],{"class":1378,"line":1379},[1376,22031,22032],{},"# backend.tf — configura onde o state é guardado\n",[1376,22034,22035],{"class":1378,"line":984},[1376,22036,1398],{"emptyLinePlaceholder":898},[1376,22038,22039],{"class":1378,"line":981},[1376,22040,21505],{},[1376,22042,22043],{"class":1378,"line":1395},[1376,22044,22045],{},"  backend \"s3\" {\n",[1376,22047,22048],{"class":1378,"line":1401},[1376,22049,22050],{},"    bucket         = \"meu-terraform-state-dev\"  # Bucket S3 que guarda o state\n",[1376,22052,22053],{"class":1378,"line":1407},[1376,22054,22055],{},"    key            = \"infra\u002Fterraform.tfstate\"  # Caminho do arquivo dentro do bucket\n",[1376,22057,22058],{"class":1378,"line":1413},[1376,22059,22060],{},"    region         = \"us-east-1\"                # Região do bucket\n",[1376,22062,22063],{"class":1378,"line":1419},[1376,22064,22065],{},"    dynamodb_table = \"terraform-locks\"          # Tabela para lock (evita edição simultânea)\n",[1376,22067,22068],{"class":1378,"line":1424},[1376,22069,22070],{},"    encrypt        = true                       # Criptografa o state em repouso\n",[1376,22072,22073],{"class":1378,"line":1430},[1376,22074,19253],{},[1376,22076,22077],{"class":1378,"line":1436},[1376,22078,5170],{},[710,22080,22081],{},[86,22082,22083,22085,22086,22088,22089,22092],{},[89,22084,4008],{},": o bucket S3 e a tabela DynamoDB precisam existir ",[89,22087,13459],{}," de\nrodar ",[513,22090,22091],{},"terraform init"," com o backend remoto. Crie-os manualmente uma única\nvez (ou use um projeto Terraform separado para o backend).",[283,22094,22096],{"id":22095},"por-que-lock-é-importante","Por que lock é importante?",[86,22098,22099,22100,22102],{},"Se dois desenvolvedores rodarem ",[513,22101,21425],{}," ao mesmo tempo, ambos leem o\nstate, fazem mudanças, e o segundo sobrescreve o primeiro — corrompendo o\nstate. O lock (via DynamoDB) impede isso: o primeiro trava, o segundo espera.",[12,22104,22106],{"id":22105},"módulos-organização-em-larga-escala-modulos","Módulos: organização em larga escala {#modulos}",[86,22108,22109,22110,22112,22113,22116],{},"Quando sua infraestrutura cresce (10, 50, 100 resources), um único ",[513,22111,21481],{},"\nfica impossível de manter. ",[89,22114,22115],{},"Módulos"," são a solução: pacotes reutilizáveis de\nconfiguração Terraform.",[86,22118,22119,22121],{},[89,22120,3011],{},": um módulo é como uma função em programação. Em vez de repetir o\ncódigo do servidor em cada ambiente, você cria um módulo \"servidor-web\" e o\nchama com parâmetros diferentes.",[283,22123,22125],{"id":22124},"estrutura-de-um-módulo","Estrutura de um módulo",[506,22127,22130],{"className":22128,"code":22129,"language":511},[509],"projeto\u002F\n├── main.tf          # Chama os módulos\n├── variables.tf     # Variáveis do projeto\n├── modules\u002F\n│   └── web-server\u002F  # Módulo reutilizável\n│       ├── main.tf       # Define os resources do módulo\n│       ├── variables.tf  # Inputs do módulo\n│       └── outputs.tf    # Outputs do módulo (valores que retorna)\n",[513,22131,22129],{"__ignoreMap":515},[506,22133,22135],{"className":19114,"code":22134,"language":19116,"meta":515,"style":515},"# modules\u002Fweb-server\u002Fmain.tf — conteúdo do módulo\n\nvariable \"instance_type\" {\n  type    = string\n  default = \"t3.micro\"\n}\n\nvariable \"environment\" {\n  type = string\n}\n\nvariable \"app_name\" {\n  type = string\n}\n\nresource \"aws_instance\" \"web\" {\n  ami           = \"ami-0c7217cdde317cfec\"\n  instance_type = var.instance_type\n\n  tags = {\n    Name        = \"${var.app_name}-${var.environment}\"\n    Environment = var.environment\n  }\n}\n\n# Outputs: valores que o módulo retorna para quem o chamou\noutput \"instance_id\" {\n  value       = aws_instance.web.id\n  description = \"ID da instância criada\"\n}\n\noutput \"public_ip\" {\n  value       = aws_instance.web.public_ip\n  description = \"IP público do servidor\"\n}\n",[513,22136,22137,22142,22146,22150,22155,22160,22164,22168,22172,22177,22181,22185,22190,22194,22198,22202,22206,22210,22214,22218,22222,22227,22231,22235,22239,22243,22248,22253,22258,22263,22267,22271,22276,22281,22286],{"__ignoreMap":515},[1376,22138,22139],{"class":1378,"line":1379},[1376,22140,22141],{},"# modules\u002Fweb-server\u002Fmain.tf — conteúdo do módulo\n",[1376,22143,22144],{"class":1378,"line":984},[1376,22145,1398],{"emptyLinePlaceholder":898},[1376,22147,22148],{"class":1378,"line":981},[1376,22149,21789],{},[1376,22151,22152],{"class":1378,"line":1395},[1376,22153,22154],{},"  type    = string\n",[1376,22156,22157],{"class":1378,"line":1401},[1376,22158,22159],{},"  default = \"t3.micro\"\n",[1376,22161,22162],{"class":1378,"line":1407},[1376,22163,5170],{},[1376,22165,22166],{"class":1378,"line":1413},[1376,22167,1398],{"emptyLinePlaceholder":898},[1376,22169,22170],{"class":1378,"line":1419},[1376,22171,21816],{},[1376,22173,22174],{"class":1378,"line":1424},[1376,22175,22176],{},"  type = string\n",[1376,22178,22179],{"class":1378,"line":1430},[1376,22180,5170],{},[1376,22182,22183],{"class":1378,"line":1436},[1376,22184,1398],{"emptyLinePlaceholder":898},[1376,22186,22187],{"class":1378,"line":1442},[1376,22188,22189],{},"variable \"app_name\" {\n",[1376,22191,22192],{"class":1378,"line":1447},[1376,22193,22176],{},[1376,22195,22196],{"class":1378,"line":1453},[1376,22197,5170],{},[1376,22199,22200],{"class":1378,"line":1459},[1376,22201,1398],{"emptyLinePlaceholder":898},[1376,22203,22204],{"class":1378,"line":2200},[1376,22205,21244],{},[1376,22207,22208],{"class":1378,"line":2208},[1376,22209,21874],{},[1376,22211,22212],{"class":1378,"line":2219},[1376,22213,21879],{},[1376,22215,22216],{"class":1378,"line":2227},[1376,22217,1398],{"emptyLinePlaceholder":898},[1376,22219,22220],{"class":1378,"line":2241},[1376,22221,21602],{},[1376,22223,22224],{"class":1378,"line":2253},[1376,22225,22226],{},"    Name        = \"${var.app_name}-${var.environment}\"\n",[1376,22228,22229],{"class":1378,"line":2283},[1376,22230,21897],{},[1376,22232,22233],{"class":1378,"line":2296},[1376,22234,19253],{},[1376,22236,22237],{"class":1378,"line":2311},[1376,22238,5170],{},[1376,22240,22241],{"class":1378,"line":2316},[1376,22242,1398],{"emptyLinePlaceholder":898},[1376,22244,22245],{"class":1378,"line":2324},[1376,22246,22247],{},"# Outputs: valores que o módulo retorna para quem o chamou\n",[1376,22249,22250],{"class":1378,"line":2333},[1376,22251,22252],{},"output \"instance_id\" {\n",[1376,22254,22255],{"class":1378,"line":2340},[1376,22256,22257],{},"  value       = aws_instance.web.id\n",[1376,22259,22260],{"class":1378,"line":2351},[1376,22261,22262],{},"  description = \"ID da instância criada\"\n",[1376,22264,22265],{"class":1378,"line":2362},[1376,22266,5170],{},[1376,22268,22269],{"class":1378,"line":2385},[1376,22270,1398],{"emptyLinePlaceholder":898},[1376,22272,22273],{"class":1378,"line":2396},[1376,22274,22275],{},"output \"public_ip\" {\n",[1376,22277,22278],{"class":1378,"line":2411},[1376,22279,22280],{},"  value       = aws_instance.web.public_ip\n",[1376,22282,22283],{"class":1378,"line":2416},[1376,22284,22285],{},"  description = \"IP público do servidor\"\n",[1376,22287,22288],{"class":1378,"line":2422},[1376,22289,5170],{},[506,22291,22293],{"className":19114,"code":22292,"language":19116,"meta":515,"style":515},"# main.tf — usando o módulo no projeto principal\n\nmodule \"web_dev\" {\n  source        = \".\u002Fmodules\u002Fweb-server\"  # Caminho para o módulo\n  app_name      = \"blog\"\n  environment   = \"dev\"\n  instance_type = \"t3.micro\"\n}\n\nmodule \"web_prod\" {\n  source        = \".\u002Fmodules\u002Fweb-server\"  # Mesmo módulo, parâmetros diferentes\n  app_name      = \"blog\"\n  environment   = \"prod\"\n  instance_type = \"t3.large\"              # Produção merece mais recursos\n}\n\n# Acessando os outputs do módulo\noutput \"ip_producao\" {\n  value = module.web_prod.public_ip  # Pega o IP público do módulo de prod\n}\n",[513,22294,22295,22300,22304,22309,22314,22319,22324,22328,22332,22336,22341,22346,22350,22355,22360,22364,22368,22373,22378,22383],{"__ignoreMap":515},[1376,22296,22297],{"class":1378,"line":1379},[1376,22298,22299],{},"# main.tf — usando o módulo no projeto principal\n",[1376,22301,22302],{"class":1378,"line":984},[1376,22303,1398],{"emptyLinePlaceholder":898},[1376,22305,22306],{"class":1378,"line":981},[1376,22307,22308],{},"module \"web_dev\" {\n",[1376,22310,22311],{"class":1378,"line":1395},[1376,22312,22313],{},"  source        = \".\u002Fmodules\u002Fweb-server\"  # Caminho para o módulo\n",[1376,22315,22316],{"class":1378,"line":1401},[1376,22317,22318],{},"  app_name      = \"blog\"\n",[1376,22320,22321],{"class":1378,"line":1407},[1376,22322,22323],{},"  environment   = \"dev\"\n",[1376,22325,22326],{"class":1378,"line":1413},[1376,22327,19147],{},[1376,22329,22330],{"class":1378,"line":1419},[1376,22331,5170],{},[1376,22333,22334],{"class":1378,"line":1424},[1376,22335,1398],{"emptyLinePlaceholder":898},[1376,22337,22338],{"class":1378,"line":1430},[1376,22339,22340],{},"module \"web_prod\" {\n",[1376,22342,22343],{"class":1378,"line":1436},[1376,22344,22345],{},"  source        = \".\u002Fmodules\u002Fweb-server\"  # Mesmo módulo, parâmetros diferentes\n",[1376,22347,22348],{"class":1378,"line":1442},[1376,22349,22318],{},[1376,22351,22352],{"class":1378,"line":1447},[1376,22353,22354],{},"  environment   = \"prod\"\n",[1376,22356,22357],{"class":1378,"line":1453},[1376,22358,22359],{},"  instance_type = \"t3.large\"              # Produção merece mais recursos\n",[1376,22361,22362],{"class":1378,"line":1459},[1376,22363,5170],{},[1376,22365,22366],{"class":1378,"line":2200},[1376,22367,1398],{"emptyLinePlaceholder":898},[1376,22369,22370],{"class":1378,"line":2208},[1376,22371,22372],{},"# Acessando os outputs do módulo\n",[1376,22374,22375],{"class":1378,"line":2219},[1376,22376,22377],{},"output \"ip_producao\" {\n",[1376,22379,22380],{"class":1378,"line":2227},[1376,22381,22382],{},"  value = module.web_prod.public_ip  # Pega o IP público do módulo de prod\n",[1376,22384,22385],{"class":1378,"line":2241},[1376,22386,5170],{},[86,22388,22389,22390,22392],{},"Ao usar módulos, você roda ",[513,22391,22091],{}," novamente para baixar o código do\nmódulo local:",[506,22394,22396],{"className":11827,"code":22395,"language":11829,"meta":515,"style":515},"terraform init  # Detecta e carrega o novo módulo\nterraform plan\nterraform apply\n",[513,22397,22398,22408,22414],{"__ignoreMap":515},[1376,22399,22400,22402,22405],{"class":1378,"line":1379},[1376,22401,21639],{"class":11841},[1376,22403,22404],{"class":1682}," init",[1376,22406,22407],{"class":1665},"  # Detecta e carrega o novo módulo\n",[1376,22409,22410,22412],{"class":1378,"line":984},[1376,22411,21639],{"class":11841},[1376,22413,21661],{"class":1682},[1376,22415,22416,22418],{"class":1378,"line":981},[1376,22417,21639],{"class":11841},[1376,22419,21701],{"class":1682},[283,22421,22423],{"id":22422},"módulos-públicos-terraform-registry","Módulos públicos (Terraform Registry)",[86,22425,15435,22426,22429],{},[89,22427,22428],{},"Terraform Registry"," é como um \"NPM para Terraform\" — um repositório de\nmódulos que a comunidade compartilha. Em vez de criar do zero, você pode usar\nmódulos prontos:",[506,22431,22433],{"className":19114,"code":22432,"language":19116,"meta":515,"style":515},"# Usa um módulo oficial da AWS para criar um VPC\n# VPC (Virtual Private Cloud) = sua rede privada isolada dentro da AWS\nmodule \"vpc\" {\n  source  = \"terraform-aws-modules\u002Fvpc\u002Faws\"  # Módulo do Registry\n  version = \"5.0.0\"                          # Sempre fixe a versão!\n\n  name = \"meu-vpc\"\n  cidr = \"10.0.0.0\u002F16\"  # Bloco de IPs da rede\n\n  azs             = [\"us-east-1a\", \"us-east-1b\"]  # Zonas de disponibilidade\n  public_subnets  = [\"10.0.1.0\u002F24\", \"10.0.2.0\u002F24\"]\n  private_subnets = [\"10.0.3.0\u002F24\", \"10.0.4.0\u002F24\"]\n\n  enable_nat_gateway = true  # Permite que subnets privadas acessem a internet\n}\n",[513,22434,22435,22440,22445,22450,22455,22460,22464,22469,22474,22478,22483,22488,22493,22497,22502],{"__ignoreMap":515},[1376,22436,22437],{"class":1378,"line":1379},[1376,22438,22439],{},"# Usa um módulo oficial da AWS para criar um VPC\n",[1376,22441,22442],{"class":1378,"line":984},[1376,22443,22444],{},"# VPC (Virtual Private Cloud) = sua rede privada isolada dentro da AWS\n",[1376,22446,22447],{"class":1378,"line":981},[1376,22448,22449],{},"module \"vpc\" {\n",[1376,22451,22452],{"class":1378,"line":1395},[1376,22453,22454],{},"  source  = \"terraform-aws-modules\u002Fvpc\u002Faws\"  # Módulo do Registry\n",[1376,22456,22457],{"class":1378,"line":1401},[1376,22458,22459],{},"  version = \"5.0.0\"                          # Sempre fixe a versão!\n",[1376,22461,22462],{"class":1378,"line":1407},[1376,22463,1398],{"emptyLinePlaceholder":898},[1376,22465,22466],{"class":1378,"line":1413},[1376,22467,22468],{},"  name = \"meu-vpc\"\n",[1376,22470,22471],{"class":1378,"line":1419},[1376,22472,22473],{},"  cidr = \"10.0.0.0\u002F16\"  # Bloco de IPs da rede\n",[1376,22475,22476],{"class":1378,"line":1424},[1376,22477,1398],{"emptyLinePlaceholder":898},[1376,22479,22480],{"class":1378,"line":1430},[1376,22481,22482],{},"  azs             = [\"us-east-1a\", \"us-east-1b\"]  # Zonas de disponibilidade\n",[1376,22484,22485],{"class":1378,"line":1436},[1376,22486,22487],{},"  public_subnets  = [\"10.0.1.0\u002F24\", \"10.0.2.0\u002F24\"]\n",[1376,22489,22490],{"class":1378,"line":1442},[1376,22491,22492],{},"  private_subnets = [\"10.0.3.0\u002F24\", \"10.0.4.0\u002F24\"]\n",[1376,22494,22495],{"class":1378,"line":1447},[1376,22496,1398],{"emptyLinePlaceholder":898},[1376,22498,22499],{"class":1378,"line":1453},[1376,22500,22501],{},"  enable_nat_gateway = true  # Permite que subnets privadas acessem a internet\n",[1376,22503,22504],{"class":1378,"line":1459},[1376,22505,5170],{},[710,22507,22508],{},[86,22509,22510,22512],{},[89,22511,6471],{},": módulos prontos economizam muito tempo, mas sempre leia o código\nantes de usar. Um módulo mal feito pode criar recursos caros ou inseguros\nque você não esperava.",[12,22514,15701],{"id":15700},[283,22516,22518,22519,22521],{"id":22517},"_1-commitar-o-arquivo-tfstate-no-git","1. Commitar o arquivo ",[513,22520,21379],{}," no Git",[86,22523,22524,22526],{},[89,22525,4750],{},": o state contém informações sensíveis (IPs, IDs internos) e é\nbinário. Se duas pessoas editarem ao mesmo tempo, o Git não consegue mesclar —\nvira conflito impossível de resolver.",[86,22528,22529,15881,22531,22534,22535,22538],{},[89,22530,15721],{},[513,22532,22533],{},"*.tfstate"," ao ",[513,22536,22537],{},".gitignore"," e use remote state (S3,\nTerraform Cloud, etc).",[283,22540,22542],{"id":22541},"_2-commitar-credenciais-no-código","2. Commitar credenciais no código",[86,22544,22545,22547,22548,3022,22551,22554,22555,22558],{},[89,22546,4750],{},": colocar ",[513,22549,22550],{},"access_key",[513,22552,22553],{},"secret_key"," diretamente no ",[513,22556,22557],{},"provider","\n\"faz funcionar\", mas vaza suas chaves da AWS para o mundo (se o repositório for\npúblico).",[86,22560,22561,22563,22564,22567,22568,22571,22572,22575],{},[89,22562,15721],{},": nunca coloque segredos no código. Use variáveis de ambiente, AWS\nCLI (",[513,22565,22566],{},"aws configure","), ou um gerenciador de segredos (AWS Secrets Manager,\nVault). Para senhas de banco, use o resource ",[513,22569,22570],{},"aws_secretsmanager_secret"," e\nreferencie via ",[513,22573,22574],{},"data"," block.",[283,22577,22579],{"id":22578},"_3-não-fixar-versões-de-provider-e-módulos","3. Não fixar versões de provider e módulos",[86,22581,22582,22584,22585,22587,22588,22591],{},[89,22583,4750],{},": sem versão fixada, ",[513,22586,22091],{}," pode baixar uma versão nova\ndo provider com ",[501,22589,22590],{},"breaking changes"," (mudanças que quebram compatibilidade). Seu\ncódigo para de funcionar do nada.",[86,22593,22594,22596],{},[89,22595,15721],{},": sempre fixe versões:",[506,22598,22600],{"className":19114,"code":22599,"language":19116,"meta":515,"style":515},"# Bom — fixa na major version\nversion = \"~> 5.0\"\n\n# Melhor — fixa exata\nversion = \"5.32.0\"\n\n# Ruim — pega qualquer versão (até betas)\nversion = \">= 0\"\n",[513,22601,22602,22607,22612,22616,22621,22626,22630,22635],{"__ignoreMap":515},[1376,22603,22604],{"class":1378,"line":1379},[1376,22605,22606],{},"# Bom — fixa na major version\n",[1376,22608,22609],{"class":1378,"line":984},[1376,22610,22611],{},"version = \"~> 5.0\"\n",[1376,22613,22614],{"class":1378,"line":981},[1376,22615,1398],{"emptyLinePlaceholder":898},[1376,22617,22618],{"class":1378,"line":1395},[1376,22619,22620],{},"# Melhor — fixa exata\n",[1376,22622,22623],{"class":1378,"line":1401},[1376,22624,22625],{},"version = \"5.32.0\"\n",[1376,22627,22628],{"class":1378,"line":1407},[1376,22629,1398],{"emptyLinePlaceholder":898},[1376,22631,22632],{"class":1378,"line":1413},[1376,22633,22634],{},"# Ruim — pega qualquer versão (até betas)\n",[1376,22636,22637],{"class":1378,"line":1419},[1376,22638,22639],{},"version = \">= 0\"\n",[283,22641,22643,22644,22646],{"id":22642},"_4-rodar-terraform-apply-sem-revisar-o-plan","4. Rodar ",[513,22645,21425],{}," sem revisar o plan",[86,22648,22649,22651,22652,22654],{},[89,22650,4750],{},": em infraestrutura grande, um ",[513,22653,21437],{}," cego pode destruir recursos\ncríticos que você não percebeu no plan.",[86,22656,22657,22659],{},[89,22658,15721],{},": sempre leia o plan. Preste atenção especial em:",[528,22661,22662,22670],{},[20,22663,22664,22669],{},[89,22665,22666],{},[513,22667,22668],{},"- destroy",": recursos que serão apagados — é isso que você quer?",[20,22671,22672,22677],{},[89,22673,22674],{},[513,22675,22676],{},"~ in-place",": recursos que serão modificados — pode causar downtime?",[86,22679,22680],{},"Em produção, salve o plan e aplique-o depois:",[506,22682,22684],{"className":11827,"code":22683,"language":11829,"meta":515,"style":515},"terraform plan -out=plan.tfplan    # Salva o plano\nterraform apply plan.tfplan        # Aplica exatamente o que foi salvo\n",[513,22685,22686,22699],{"__ignoreMap":515},[1376,22687,22688,22690,22693,22696],{"class":1378,"line":1379},[1376,22689,21639],{"class":11841},[1376,22691,22692],{"class":1682}," plan",[1376,22694,22695],{"class":1770}," -out=plan.tfplan",[1376,22697,22698],{"class":1665},"    # Salva o plano\n",[1376,22700,22701,22703,22705,22708],{"class":1378,"line":984},[1376,22702,21639],{"class":11841},[1376,22704,15193],{"class":1682},[1376,22706,22707],{"class":1682}," plan.tfplan",[1376,22709,22710],{"class":1665},"        # Aplica exatamente o que foi salvo\n",[283,22712,22714],{"id":22713},"_5-um-state-para-tudo","5. Um state para tudo",[86,22716,22717,22719],{},[89,22718,4750],{},": colocar dev, staging e produção no mesmo state. Uma mudança em\ndev pode acidentalmente afetar produção.",[86,22721,22722,1679,22724,22727],{},[89,22723,15721],{},[89,22725,22726],{},"um state por ambiente",". Use workspaces ou, melhor ainda,\ndiretórios\u002Fseparação de backend:",[506,22729,22732],{"className":22730,"code":22731,"language":511},[509],"projeto\u002F\n├── environments\u002F\n│   ├── dev\u002F\n│   │   ├── backend.tf    # backend S3 com key=\"dev\u002Fterraform.tfstate\"\n│   │   └── main.tf\n│   └── prod\u002F\n│       ├── backend.tf    # backend S3 com key=\"prod\u002Fterraform.tfstate\"\n│       └── main.tf\n",[513,22733,22731],{"__ignoreMap":515},[86,22735,22736,22737,22740],{},"Assim cada ambiente é totalmente isolado — um ",[513,22738,22739],{},"terraform destroy"," em dev nunca\ntocará produção.",[968,22742],{},[86,22744,22745,22748,22749,522],{},[89,22746,22747],{},"Precisa de ajuda com Terraform?"," A Inicialize Tec implementa infraestrutura\ncomo código, migra ambientes manuais para Terraform e estrutura pipelines de\nCI\u002FCD para IaC. ",[23,22750,2881],{"href":978},[2883,22752,13662],{},{"title":515,"searchDepth":981,"depth":981,"links":22754},[22755,22756,22759,22762,22768,22777,22778,22783,22787],{"id":14,"depth":984,"text":15},{"id":20988,"depth":984,"text":20989,"children":22757},[22758],{"id":21036,"depth":981,"text":21037},{"id":21100,"depth":984,"text":21101,"children":22760},[22761],{"id":21223,"depth":981,"text":21224},{"id":21340,"depth":984,"text":21341,"children":22763},[22764,22765,22766,22767],{"id":21347,"depth":981,"text":21348},{"id":21358,"depth":981,"text":21359},{"id":21369,"depth":981,"text":21370},{"id":21400,"depth":981,"text":21401},{"id":21441,"depth":984,"text":21442,"children":22769},[22770,22771,22773,22774,22775,22776],{"id":17107,"depth":981,"text":17108},{"id":21477,"depth":981,"text":22772},"Passo 1: criar o arquivo main.tf",{"id":21628,"depth":981,"text":21629},{"id":21648,"depth":981,"text":21649},{"id":21688,"depth":981,"text":21689},{"id":21711,"depth":981,"text":21712},{"id":21735,"depth":984,"text":21736},{"id":21969,"depth":984,"text":21970,"children":22779},[22780,22781,22782],{"id":21980,"depth":981,"text":21981},{"id":22007,"depth":981,"text":22008},{"id":22095,"depth":981,"text":22096},{"id":22105,"depth":984,"text":22106,"children":22784},[22785,22786],{"id":22124,"depth":981,"text":22125},{"id":22422,"depth":981,"text":22423},{"id":15700,"depth":984,"text":15701,"children":22788},[22789,22791,22792,22793,22795],{"id":22517,"depth":981,"text":22790},"1. Commitar o arquivo .tfstate no Git",{"id":22541,"depth":981,"text":22542},{"id":22578,"depth":981,"text":22579},{"id":22642,"depth":981,"text":22794},"4. Rodar terraform apply sem revisar o plan",{"id":22713,"depth":981,"text":22714},"Guia prático de Terraform para iniciantes: crie, modifique e versione sua infraestrutura na nuvem com código — sem precisar clicar em painéis.",[21639,21020,22798,20922,22799,22800],"infraestrutura como código","provisionamento","devops",{},"\u002Finfraestrutura\u002Fterraform-infraestrutura-como-codigo",{"title":20935,"description":22796},"terraform-infraestrutura-como-codigo","infraestrutura\u002Fterraform-infraestrutura-como-codigo","eX0WCcfSt_j_PuGllWVs4tG_qsY3xcG_k84kJ8-_U-w",{"id":22808,"title":22809,"author":7,"body":22810,"category":24241,"date":1028,"description":24242,"extension":1030,"keywords":24243,"meta":24249,"navigation":898,"path":24250,"seo":24251,"slug":24252,"stem":24253,"updated":1028,"__hash__":24254},"content\u002Fintegracoes\u002Fevent-driven-architecture-kafka.md","Event-driven architecture com Kafka: guia prático",{"type":9,"value":22811,"toc":24204},[22812,22814,22877,22881,22892,22899,22906,22909,22929,22933,22939,22950,22953,22979,23059,23063,23066,23070,23082,23086,23093,23111,23125,23129,23135,23141,23146,23150,23154,23157,23171,23175,23178,23182,23193,23207,23217,23223,23227,23234,23257,23264,23268,23271,23275,23278,23282,23289,23300,23304,23315,23322,23326,23333,23337,23526,23529,23549,23553,23713,23716,23748,23752,23756,23785,23789,23824,23885,23889,23893,23910,23913,23935,23939,23961,23965,23984,23987,24013,24015,24108,24112,24192,24194,24202],[12,22813,15],{"id":14},[17,22815,22816,22821,22827,22832,22838,22844,22850,22856,22862,22868,22872],{},[20,22817,22818],{},[23,22819,22820],{"href":25},"O que é event-driven architecture",[20,22822,22823],{},[23,22824,22826],{"href":22825},"#kafka","O que é o Apache Kafka",[20,22828,22829],{},[23,22830,22831],{"href":6914},"Conceitos essenciais: tópicos, partitions, offsets",[20,22833,22834],{},[23,22835,22837],{"href":22836},"#producers-consumers","Producers, consumers e consumer groups",[20,22839,22840],{},[23,22841,22843],{"href":22842},"#pubsub-vs-fila","Pub\u002FSub vs. fila tradicional",[20,22845,22846],{},[23,22847,22849],{"href":22848},"#garantias","Garantias de entrega: pelo menos uma vez, exatamente uma vez",[20,22851,22852],{},[23,22853,22855],{"href":22854},"#exemplo-python","Exemplo prático em Python",[20,22857,22858],{},[23,22859,22861],{"href":22860},"#quando-usar","Quando usar (e quando NÃO usar) Kafka",[20,22863,22864],{},[23,22865,22867],{"href":22866},"#particoes-chaves","Esquemas, partições e chaves na prática",[20,22869,22870],{},[23,22871,74],{"href":73},[20,22873,22874],{},[23,22875,22876],{"href":79},"Checklist de adoção",[12,22878,22880],{"id":22879},"o-que-é-event-driven-architecture-o-que-e","O que é event-driven architecture {#o-que-e}",[86,22882,22883,22884,22887,22888,22891],{},"Antes de entender ",[89,22885,22886],{},"event-driven architecture"," (arquitetura orientada a\neventos), vale explicar a palavra-chave: ",[89,22889,22890],{},"evento",". Um evento é \"algo que\naconteceu no passado e foi registrado\" — um pedido foi criado, um pagamento foi\nconfirmado, um sensor detectou temperatura acima do limite. O evento é um fato\nimutável: você não \"des-faz\" um evento, no máximo emite outro que o compensa.",[86,22893,22894,22895,22898],{},"Na arquitetura tradicional (síncrona, baseada em requests), o serviço A chama\no serviço B e ",[89,22896,22897],{},"fica esperando"," a resposta. Pense numa ligação telefônica:\nvocê liga para o colega, ele atende, você fala, ele responde, e ninguém desliga\nenquanto a conversa não termina. Se ele não atende, você fica preso.",[86,22900,22901,22902,22905],{},"Na arquitetura orientada a eventos, o serviço A simplesmente ",[89,22903,22904],{},"anuncia"," que\nalgo aconteceu e segue sua vida. Pense num canal de rádio: o locutor fala \"o\npedido 1234 foi pago\" e desliga o microfone. Quem estiver ouvindo e se importar\nreage; quem não estiver ouvindo, perdeu. Ninguém fica esperando ninguém.",[86,22907,22908],{},"Isso traz três propriedades poderosas:",[528,22910,22911,22917,22923],{},[20,22912,22913,22916],{},[89,22914,22915],{},"Desacoplamento temporal",": o produtor não precisa que o consumidor esteja\nonline no momento do evento.",[20,22918,22919,22922],{},[89,22920,22921],{},"Desacoplamento espacial",": o produtor não precisa saber quem consome —\nnovos consumidores podem surgir sem o produtor mudar uma linha.",[20,22924,22925,22928],{},[89,22926,22927],{},"Elasticidade",": dá para adicionar mais consumidores se o volume crescer,\nsem mexer em quem produz os eventos.",[12,22930,22932],{"id":22931},"o-que-é-o-apache-kafka-kafka","O que é o Apache Kafka {#kafka}",[86,22934,22935,22938],{},[89,22936,22937],{},"Apache Kafka"," é uma plataforma de streaming de eventos distribuída, criada\nno LinkedIn em 2011 e hoje mantida pela Confluent e pela comunidade. Em termos\nsimples, é um \"correio de alta velocidade\" para eventos.",[86,22940,22941,22942,22945,22946,22949],{},"A analogia mais útil: Kafka é como um ",[89,22943,22944],{},"correio que distribui cartas para todos\nos destinatários interessados, sem que o remetente precise saber quem são",".\nVocê entrega a carta no correio dizendo \"esta é sobre o assunto\n",[513,22947,22948],{},"pedidos-criados","\", e o correio garante que todo assinante daquele assunto\nreceba uma cópia. O remetente não conhece os destinatários, os destinatários\nnão se conhecem entre si, e o correio guarda as cartas por vários dias caso\nalguém esteja de folga.",[86,22951,22952],{},"O que torna o Kafka diferente de um banco de dados ou de uma fila comum:",[17,22954,22955,22961,22967,22973],{},[20,22956,22957,22960],{},[89,22958,22959],{},"É um log append-only",": eventos são só acrescentados, nunca alterados\nnem apagados no fluxo normal (apagamento só por retenção por tempo\u002Ftamanho).",[20,22962,22963,22966],{},[89,22964,22965],{},"Distribuído e replicado",": roda em cluster, com partições espalhadas e\nréplicas para tolerância a falhas.",[20,22968,22969,22972],{},[89,22970,22971],{},"Replay",": consumidores podem \"rebobinar\" e ler eventos antigos, como um\nDVR de TV. Isso é brutalmente útil para reprocessar dados, treinar modelos\nde ML, ou reconstruir views materializadas.",[20,22974,22975,22978],{},[89,22976,22977],{},"Altíssimo throughput",": centenas de milhares de eventos por segundo numa\nmáquina modesta, milhões por segundo num cluster bem ajustado.",[165,22980,22981,22994],{},[168,22982,22983],{},[171,22984,22985,22988,22991],{},[174,22986,22987],{},"Característica",[174,22989,22990],{},"Fila tradicional (RabbitMQ)",[174,22992,22993],{},"Kafka",[184,22995,22996,23006,23017,23027,23038,23049],{},[171,22997,22998,23000,23003],{},[189,22999,8899],{},[189,23001,23002],{},"Mensagem consumida e removida",[189,23004,23005],{},"Log persistente, múltiplas leituras",[171,23007,23008,23011,23014],{},[189,23009,23010],{},"Retenção",[189,23012,23013],{},"Até leitura",[189,23015,23016],{},"Por tempo\u002Ftamanho configurável",[171,23018,23019,23021,23024],{},[189,23020,22971],{},[189,23022,23023],{},"Não (consumida = sumiu)",[189,23025,23026],{},"Sim, rebobinando o offset",[171,23028,23029,23032,23035],{},[189,23030,23031],{},"Throughput",[189,23033,23034],{},"Milhares\u002Fs",[189,23036,23037],{},"Centenas de milhares a milhões\u002Fs",[171,23039,23040,23043,23046],{},[189,23041,23042],{},"Ordenação",[189,23044,23045],{},"Por fila",[189,23047,23048],{},"Por partition",[171,23050,23051,23053,23056],{},[189,23052,10680],{},[189,23054,23055],{},"Comando pontual",[189,23057,23058],{},"Stream de eventos, integração de sistemas",[12,23060,23062],{"id":23061},"conceitos-essenciais-tópicos-partitions-offsets-conceitos","Conceitos essenciais: tópicos, partitions, offsets {#conceitos}",[86,23064,23065],{},"Esses três conceitos são a base de tudo no Kafka. Vamos devagar.",[283,23067,23069],{"id":23068},"tópico-topic","Tópico (topic)",[86,23071,23072,23073,1327,23075,1327,23078,23081],{},"Um tópico é o \"assunto\" da carta. É um canal nomeado para um tipo de evento.\nExemplos: ",[513,23074,22948],{},[513,23076,23077],{},"pagamentos-confirmados",[513,23079,23080],{},"sensor-temperatura",".\nProdutores publicam em tópicos; consumidores assinam tópicos.",[283,23083,23085],{"id":23084},"partition-partição","Partition (partição)",[86,23087,23088,23089,23092],{},"Cada tópico é dividido em ",[89,23090,23091],{},"partitions"," — pense em várias esteiras paralelas\ndo mesmo correio. A partição é o que permite escala horizontal: múltiplas\npartições significam múltiplos consumidores em paralelo.",[528,23094,23095,23102,23108],{},[20,23096,23097,23098,23101],{},"Cada evento vai para ",[89,23099,23100],{},"uma"," partition, decidida por uma chave (key).",[20,23103,23104,23105,522],{},"Eventos com a mesma chave sempre vão para a mesma partition → garante\n",[89,23106,23107],{},"ordem por chave",[20,23109,23110],{},"Eventos em partitions diferentes não têm ordem garantida entre si.",[86,23112,23113,23114,23116,23117,23120,23121,23124],{},"Exemplo: num tópico ",[513,23115,22948],{}," com 6 partições, o evento com\n",[513,23118,23119],{},"key=cliente-123"," sempre cai na partition 2 (por hash). Assim, todos os pedidos\ndo ",[513,23122,23123],{},"cliente-123"," são processados em ordem pelo consumidor que pegou a partition 2.",[283,23126,23128],{"id":23127},"offset","Offset",[86,23130,23131,23132,23134],{},"Cada evento dentro de uma partition recebe um número sequencial chamado\n",[89,23133,23127],{},", começando em 0. É como a numeração das páginas de um livro. O\nconsumidor registra \"li até a página 47\" e, se reiniciar, volta da página 48.\nIsso permite parar e retomar sem perder nem duplicar (se o commit for feito\ncorreto).",[506,23136,23139],{"className":23137,"code":23138,"language":511},[509],"Tópico: pedidos-criados (3 partições)\n\nPartition 0:  [evt0] [evt3] [evt6] [evt9]   ← offsets 0,1,2,3\nPartition 1:  [evt1] [evt4] [evt7] [evt10]  ← offsets 0,1,2,3\nPartition 2:  [evt2] [evt5] [evt8] [evt11]  ← offsets 0,1,2,3\n",[513,23140,23138],{"__ignoreMap":515},[710,23142,23143],{},[86,23144,23145],{},"Nota importante: o offset é por partition, não global. O evento offset 3 da\npartition 0 não é \"o quarto evento do tópico todo\".",[12,23147,23149],{"id":23148},"producers-consumers-e-consumer-groups-producers-consumers","Producers, consumers e consumer groups {#producers-consumers}",[283,23151,23153],{"id":23152},"producer-produtor","Producer (produtor)",[86,23155,23156],{},"Quem publica eventos no tópico. Pode publicar:",[528,23158,23159,23165],{},[20,23160,23161,23164],{},[89,23162,23163],{},"Com chave",": Kafka decide a partition via hash da chave → ordem garantida\npor chave.",[20,23166,23167,23170],{},[89,23168,23169],{},"Sem chave",": Kafka escolhe a partition (round-robin ou sticky) → sem ordem.",[283,23172,23174],{"id":23173},"consumer-consumidor","Consumer (consumidor)",[86,23176,23177],{},"Quem lê eventos do tópico. Um consumer lê de uma ou mais partitions.",[283,23179,23181],{"id":23180},"consumer-group-grupo-de-consumidores","Consumer group (grupo de consumidores)",[86,23183,23184,23185,23188,23189,23192],{},"Aqui está a mágica da escala. Um ",[89,23186,23187],{},"consumer group"," é um conjunto de consumers\nque dividem as partitions de um tópico entre si: cada partition é lida por\n",[89,23190,23191],{},"exatamente um"," consumer dentro do grupo.",[528,23194,23195,23198,23201],{},[20,23196,23197],{},"Tópico com 6 partitions e consumer group com 6 consumers → cada consumer\npega uma partition, paralelismo máximo.",[20,23199,23200],{},"Consumer group com 3 consumers → cada um pega 2 partitions.",[20,23202,23203,23204],{},"Consumer group com 10 consumers → 4 ficam parados (não há partition para\neles). ",[89,23205,23206],{},"Mais consumers que partitions = desperdício.",[86,23208,23209,23210,23213,23214,23216],{},"Diferentes consumer groups ",[89,23211,23212],{},"não competem",": cada grupo recebe uma cópia\ncompleta dos eventos. É o que permite que o serviço de estoque, o de\nnotificação e o de analytics leiam o mesmo ",[513,23215,22948],{}," independentemente.",[506,23218,23221],{"className":23219,"code":23220,"language":511},[509],"              Tópico: pedidos-criados (6 partições)\n                        │\n        ┌───────────────┼───────────────┐\n        ▼               ▼               ▼\n   Group A           Group B         Group C\n   (estoque)        (notifica)       (analytics)\n   3 consumers      6 consumers      1 consumer\n   2 parts cada    1 part cada      6 parts\n",[513,23222,23220],{"__ignoreMap":515},[12,23224,23226],{"id":23225},"pubsub-vs-fila-tradicional-pubsub-vs-fila","Pub\u002FSub vs. fila tradicional {#pubsub-vs-fila}",[86,23228,23229,23230,23233],{},"O modelo ",[89,23231,23232],{},"pub\u002Fsub"," (publish\u002Fsubscribe) é o coração do Kafka. Diferente de\numa fila comum onde a mensagem é consumida e some, no Kafka:",[528,23235,23236,23243,23246],{},[20,23237,23238,23239,23242],{},"Cada consumer group mantém ",[89,23240,23241],{},"seu próprio offset"," — o Kafka não remove o\nevento quando alguém lê.",[20,23244,23245],{},"Múltiplos grupos independentes leem o mesmo stream.",[20,23247,23248,23249,23252,23253,23256],{},"A retenção é por ",[89,23250,23251],{},"tempo"," (ex.: 7 dias) ou ",[89,23254,23255],{},"tamanho"," (ex.: 10GB por\npartition), independente de leitura.",[86,23258,23259,23260,23263],{},"Isso explica por que Kafka é usado para ",[89,23261,23262],{},"integração entre sistemas",": o ERP,\no CRM e o data warehouse podem ler o mesmo evento de \"cliente cadastrado\" sem\nque um atrapalhe o outro. Cada um segue no seu ritmo.",[12,23265,23267],{"id":23266},"garantias-de-entrega-pelo-menos-uma-vez-exatamente-uma-vez-garantias","Garantias de entrega: pelo menos uma vez, exatamente uma vez {#garantias}",[86,23269,23270],{},"Esse é o tópico que mais confunde iniciantes. Vamos com calma.",[283,23272,23274],{"id":23273},"at-most-once-no-máximo-uma-vez","At-most-once (no máximo uma vez)",[86,23276,23277],{},"O produtor envia e esquece. Pode perder eventos. Raramente é o que você quer\nem negócio (perder um pagamento é grave).",[283,23279,23281],{"id":23280},"at-least-once-pelo-menos-uma-vez","At-least-once (pelo menos uma vez)",[86,23283,23284,23285,23288],{},"O produtor reenvia enquanto não recebe confirmação. Pode haver ",[89,23286,23287],{},"duplicação",":\no evento chegou, mas a confirmação se perdeu, então ele é reenviado. É o\npadrão mais comum no Kafka.",[86,23290,23291,23292,23295,23296,23299],{},"Para lidar com duplicação, seus consumers precisam ser ",[89,23293,23294],{},"idempotentes"," —\nprocessar o mesmo evento duas vezes deve dar o mesmo resultado que processar\numa vez. Exemplo: usar o ",[513,23297,23298],{},"event_id"," como chave de deduplicação no banco.",[283,23301,23303],{"id":23302},"exactly-once-exatamente-uma-vez","Exactly-once (exatamente uma vez)",[86,23305,23306,23307,23310,23311,23314],{},"Kafka suporta exactly-once com ",[89,23308,23309],{},"transactions API"," (produtor transacional +\n",[513,23312,23313],{},"read_process_write","), mas é mais complexo e tem custo de throughput. Em\nmuitos casos, at-least-once + idempotência no consumidor é mais simples e\nigualmente correto.",[710,23316,23317],{},[86,23318,23319,23321],{},[89,23320,8851],{},": prefira at-least-once + consumidor idempotente. Reserve\nexactly-once transacional para fluxos financeiros sensíveis onde a\nduplicação é cara de deduplicar.",[12,23323,23325],{"id":23324},"exemplo-prático-em-python-exemplo-python","Exemplo prático em Python {#exemplo-python}",[86,23327,23328,23329,23332],{},"Vamos usar ",[513,23330,23331],{},"confluent-kafka"," (cliente Python performático, baseado em librdkafka).",[283,23334,23336],{"id":23335},"produtor","Produtor",[506,23338,23340],{"className":3439,"code":23339,"language":3441,"meta":515,"style":515},"from confluent_kafka import Producer\nimport json\n\n# Configuração do produtor\nconf = {\n    \"bootstrap.servers\": \"kafka1:9092,kafka2:9092\",  # brokers do cluster\n    \"client.id\": \"pedido-service\",\n    \"acks\": \"all\",        # espera confirmação de todas as réplicas → seguro\n    \"retries\": 5,         # reenvia em caso de falha transitória\n    \"enable.idempotence\": True,  # produtor idempotente → sem duplicação no broker\n}\n\nproducer = Producer(conf)\n\ndef delivery_report(err, msg):\n    \"\"\"Callback chamado quando o broker confirma (ou falha) a entrega.\"\"\"\n    if err is not None:\n        print(f\"Falha ao entregar evento: {err}\")\n    else:\n        print(f\"Entregue em {msg.topic()}[{msg.partition()}]@{msg.offset()}\")\n\n# Publicando um evento com chave = customer_id (ordem garantida por cliente)\nevento = {\n    \"event_id\": \"a1b2c3\",\n    \"order_id\": 4567,\n    \"customer_id\": \"cliente-123\",\n    \"total\": 199.90,\n    \"timestamp\": \"2026-08-26T10:15:00Z\",\n}\n\nproducer.produce(\n    topic=\"pedidos-criados\",\n    key=evento[\"customer_id\"].encode(\"utf-8\"),  # chave → partição estável\n    value=json.dumps(evento).encode(\"utf-8\"),\n    callback=delivery_report,\n)\n\n# Garante que todas as mensagens pendentes sejam enviadas antes de sair\nproducer.flush()\n",[513,23341,23342,23347,23351,23355,23360,23365,23370,23375,23380,23385,23390,23394,23398,23403,23407,23412,23417,23422,23427,23431,23436,23440,23445,23450,23455,23460,23465,23470,23475,23479,23483,23488,23493,23498,23503,23508,23512,23516,23521],{"__ignoreMap":515},[1376,23343,23344],{"class":1378,"line":1379},[1376,23345,23346],{},"from confluent_kafka import Producer\n",[1376,23348,23349],{"class":1378,"line":984},[1376,23350,3637],{},[1376,23352,23353],{"class":1378,"line":981},[1376,23354,1398],{"emptyLinePlaceholder":898},[1376,23356,23357],{"class":1378,"line":1395},[1376,23358,23359],{},"# Configuração do produtor\n",[1376,23361,23362],{"class":1378,"line":1401},[1376,23363,23364],{},"conf = {\n",[1376,23366,23367],{"class":1378,"line":1407},[1376,23368,23369],{},"    \"bootstrap.servers\": \"kafka1:9092,kafka2:9092\",  # brokers do cluster\n",[1376,23371,23372],{"class":1378,"line":1413},[1376,23373,23374],{},"    \"client.id\": \"pedido-service\",\n",[1376,23376,23377],{"class":1378,"line":1419},[1376,23378,23379],{},"    \"acks\": \"all\",        # espera confirmação de todas as réplicas → seguro\n",[1376,23381,23382],{"class":1378,"line":1424},[1376,23383,23384],{},"    \"retries\": 5,         # reenvia em caso de falha transitória\n",[1376,23386,23387],{"class":1378,"line":1430},[1376,23388,23389],{},"    \"enable.idempotence\": True,  # produtor idempotente → sem duplicação no broker\n",[1376,23391,23392],{"class":1378,"line":1436},[1376,23393,5170],{},[1376,23395,23396],{"class":1378,"line":1442},[1376,23397,1398],{"emptyLinePlaceholder":898},[1376,23399,23400],{"class":1378,"line":1447},[1376,23401,23402],{},"producer = Producer(conf)\n",[1376,23404,23405],{"class":1378,"line":1453},[1376,23406,1398],{"emptyLinePlaceholder":898},[1376,23408,23409],{"class":1378,"line":1459},[1376,23410,23411],{},"def delivery_report(err, msg):\n",[1376,23413,23414],{"class":1378,"line":2200},[1376,23415,23416],{},"    \"\"\"Callback chamado quando o broker confirma (ou falha) a entrega.\"\"\"\n",[1376,23418,23419],{"class":1378,"line":2208},[1376,23420,23421],{},"    if err is not None:\n",[1376,23423,23424],{"class":1378,"line":2219},[1376,23425,23426],{},"        print(f\"Falha ao entregar evento: {err}\")\n",[1376,23428,23429],{"class":1378,"line":2227},[1376,23430,4576],{},[1376,23432,23433],{"class":1378,"line":2241},[1376,23434,23435],{},"        print(f\"Entregue em {msg.topic()}[{msg.partition()}]@{msg.offset()}\")\n",[1376,23437,23438],{"class":1378,"line":2253},[1376,23439,1398],{"emptyLinePlaceholder":898},[1376,23441,23442],{"class":1378,"line":2283},[1376,23443,23444],{},"# Publicando um evento com chave = customer_id (ordem garantida por cliente)\n",[1376,23446,23447],{"class":1378,"line":2296},[1376,23448,23449],{},"evento = {\n",[1376,23451,23452],{"class":1378,"line":2311},[1376,23453,23454],{},"    \"event_id\": \"a1b2c3\",\n",[1376,23456,23457],{"class":1378,"line":2316},[1376,23458,23459],{},"    \"order_id\": 4567,\n",[1376,23461,23462],{"class":1378,"line":2324},[1376,23463,23464],{},"    \"customer_id\": \"cliente-123\",\n",[1376,23466,23467],{"class":1378,"line":2333},[1376,23468,23469],{},"    \"total\": 199.90,\n",[1376,23471,23472],{"class":1378,"line":2340},[1376,23473,23474],{},"    \"timestamp\": \"2026-08-26T10:15:00Z\",\n",[1376,23476,23477],{"class":1378,"line":2351},[1376,23478,5170],{},[1376,23480,23481],{"class":1378,"line":2362},[1376,23482,1398],{"emptyLinePlaceholder":898},[1376,23484,23485],{"class":1378,"line":2385},[1376,23486,23487],{},"producer.produce(\n",[1376,23489,23490],{"class":1378,"line":2396},[1376,23491,23492],{},"    topic=\"pedidos-criados\",\n",[1376,23494,23495],{"class":1378,"line":2411},[1376,23496,23497],{},"    key=evento[\"customer_id\"].encode(\"utf-8\"),  # chave → partição estável\n",[1376,23499,23500],{"class":1378,"line":2416},[1376,23501,23502],{},"    value=json.dumps(evento).encode(\"utf-8\"),\n",[1376,23504,23505],{"class":1378,"line":2422},[1376,23506,23507],{},"    callback=delivery_report,\n",[1376,23509,23510],{"class":1378,"line":2430},[1376,23511,7575],{},[1376,23513,23514],{"class":1378,"line":2448},[1376,23515,1398],{"emptyLinePlaceholder":898},[1376,23517,23518],{"class":1378,"line":2457},[1376,23519,23520],{},"# Garante que todas as mensagens pendentes sejam enviadas antes de sair\n",[1376,23522,23523],{"class":1378,"line":2464},[1376,23524,23525],{},"producer.flush()\n",[86,23527,23528],{},"Pontos-chave do código acima:",[528,23530,23531,23537,23543],{},[20,23532,23533,23536],{},[513,23534,23535],{},"acks=all",": o evento só é confirmado quando todas as réplicas in-sync\ngravam — máxima durabilidade.",[20,23538,23539,23542],{},[513,23540,23541],{},"enable.idempotence=True",": mesmo se o produtor reenviar, o broker descarta\nduplicatas baseado em PID (Producer ID) e sequência.",[20,23544,23545,23548],{},[513,23546,23547],{},"key=customer_id",": garante que todos os eventos do mesmo cliente caiam na\nmesma partition, preservando ordem por cliente.",[283,23550,23552],{"id":23551},"consumidor","Consumidor",[506,23554,23556],{"className":3439,"code":23555,"language":3441,"meta":515,"style":515},"from confluent_kafka import Consumer\nimport json\n\nconf = {\n    \"bootstrap.servers\": \"kafka1:9092,kafka2:9092\",\n    \"group.id\": \"estoque-service\",        # identidade do consumer group\n    \"auto.offset.reset\": \"earliest\",      # começa do início se não houver offset salvo\n    \"enable.auto.commit\": False,          # commit manual → controle de \"processado\"\n}\n\nconsumer = Consumer(conf)\nconsumer.subscribe([\"pedidos-criados\"])\n\ntry:\n    while True:\n        msg = consumer.poll(1.0)          # espera até 1s por uma mensagem\n        if msg is None:\n            continue                      # sem mensagem neste instante\n        if msg.error():\n            print(f\"Erro: {msg.error()}\")\n            continue\n\n        evento = json.loads(msg.value())\n\n        # IDEMPOTÊNCIA: usa event_id para não duplicar o processamento\n        # Em produção: INSERT ... ON CONFLICT (event_id) DO NOTHING\n        reservar_estoque(evento[\"order_id\"], evento[\"customer_id\"])\n\n        # Só confirma o offset DEPOIS de processar com sucesso\n        # Se o processo cair antes disso, o evento será reprocessado ao reiniciar\n        consumer.commit(msg)\nfinally:\n    consumer.close()\n",[513,23557,23558,23563,23567,23571,23575,23580,23585,23590,23595,23599,23603,23608,23613,23617,23621,23626,23631,23636,23641,23646,23651,23656,23660,23665,23669,23674,23679,23684,23688,23693,23698,23703,23708],{"__ignoreMap":515},[1376,23559,23560],{"class":1378,"line":1379},[1376,23561,23562],{},"from confluent_kafka import Consumer\n",[1376,23564,23565],{"class":1378,"line":984},[1376,23566,3637],{},[1376,23568,23569],{"class":1378,"line":981},[1376,23570,1398],{"emptyLinePlaceholder":898},[1376,23572,23573],{"class":1378,"line":1395},[1376,23574,23364],{},[1376,23576,23577],{"class":1378,"line":1401},[1376,23578,23579],{},"    \"bootstrap.servers\": \"kafka1:9092,kafka2:9092\",\n",[1376,23581,23582],{"class":1378,"line":1407},[1376,23583,23584],{},"    \"group.id\": \"estoque-service\",        # identidade do consumer group\n",[1376,23586,23587],{"class":1378,"line":1413},[1376,23588,23589],{},"    \"auto.offset.reset\": \"earliest\",      # começa do início se não houver offset salvo\n",[1376,23591,23592],{"class":1378,"line":1419},[1376,23593,23594],{},"    \"enable.auto.commit\": False,          # commit manual → controle de \"processado\"\n",[1376,23596,23597],{"class":1378,"line":1424},[1376,23598,5170],{},[1376,23600,23601],{"class":1378,"line":1430},[1376,23602,1398],{"emptyLinePlaceholder":898},[1376,23604,23605],{"class":1378,"line":1436},[1376,23606,23607],{},"consumer = Consumer(conf)\n",[1376,23609,23610],{"class":1378,"line":1442},[1376,23611,23612],{},"consumer.subscribe([\"pedidos-criados\"])\n",[1376,23614,23615],{"class":1378,"line":1447},[1376,23616,1398],{"emptyLinePlaceholder":898},[1376,23618,23619],{"class":1378,"line":1453},[1376,23620,4532],{},[1376,23622,23623],{"class":1378,"line":1459},[1376,23624,23625],{},"    while True:\n",[1376,23627,23628],{"class":1378,"line":2200},[1376,23629,23630],{},"        msg = consumer.poll(1.0)          # espera até 1s por uma mensagem\n",[1376,23632,23633],{"class":1378,"line":2208},[1376,23634,23635],{},"        if msg is None:\n",[1376,23637,23638],{"class":1378,"line":2219},[1376,23639,23640],{},"            continue                      # sem mensagem neste instante\n",[1376,23642,23643],{"class":1378,"line":2227},[1376,23644,23645],{},"        if msg.error():\n",[1376,23647,23648],{"class":1378,"line":2241},[1376,23649,23650],{},"            print(f\"Erro: {msg.error()}\")\n",[1376,23652,23653],{"class":1378,"line":2253},[1376,23654,23655],{},"            continue\n",[1376,23657,23658],{"class":1378,"line":2283},[1376,23659,1398],{"emptyLinePlaceholder":898},[1376,23661,23662],{"class":1378,"line":2296},[1376,23663,23664],{},"        evento = json.loads(msg.value())\n",[1376,23666,23667],{"class":1378,"line":2311},[1376,23668,1398],{"emptyLinePlaceholder":898},[1376,23670,23671],{"class":1378,"line":2316},[1376,23672,23673],{},"        # IDEMPOTÊNCIA: usa event_id para não duplicar o processamento\n",[1376,23675,23676],{"class":1378,"line":2324},[1376,23677,23678],{},"        # Em produção: INSERT ... ON CONFLICT (event_id) DO NOTHING\n",[1376,23680,23681],{"class":1378,"line":2333},[1376,23682,23683],{},"        reservar_estoque(evento[\"order_id\"], evento[\"customer_id\"])\n",[1376,23685,23686],{"class":1378,"line":2340},[1376,23687,1398],{"emptyLinePlaceholder":898},[1376,23689,23690],{"class":1378,"line":2351},[1376,23691,23692],{},"        # Só confirma o offset DEPOIS de processar com sucesso\n",[1376,23694,23695],{"class":1378,"line":2362},[1376,23696,23697],{},"        # Se o processo cair antes disso, o evento será reprocessado ao reiniciar\n",[1376,23699,23700],{"class":1378,"line":2385},[1376,23701,23702],{},"        consumer.commit(msg)\n",[1376,23704,23705],{"class":1378,"line":2396},[1376,23706,23707],{},"finally:\n",[1376,23709,23710],{"class":1378,"line":2411},[1376,23711,23712],{},"    consumer.close()\n",[86,23714,23715],{},"Repare em três decisões críticas desse consumidor:",[17,23717,23718,23730,23736],{},[20,23719,23720,23725,23726,23729],{},[89,23721,23722],{},[513,23723,23724],{},"enable.auto.commit=False",": o offset só avança quando ",[89,23727,23728],{},"você"," decide.\nCom auto-commit, o Kafka pode marcar como lido antes do processamento\nterminar — se o processo cair, você perde o evento.",[20,23731,23732,23735],{},[89,23733,23734],{},"Commit manual após processar",": garante at-least-once. Se der crash entre\nprocessar e commitar, o evento é reprocessado (daí a idempotência).",[20,23737,23738,23743,23744,23747],{},[89,23739,23740,23741],{},"Idempotência com ",[513,23742,23298],{},": o ",[513,23745,23746],{},"INSERT ... ON CONFLICT DO NOTHING"," evita\nduplicar a reserva de estoque caso o evento seja reprocessado.",[12,23749,23751],{"id":23750},"quando-usar-e-quando-não-usar-kafka-quando-usar","Quando usar (e quando NÃO usar) Kafka {#quando-usar}",[283,23753,23755],{"id":23754},"use-kafka-quando","Use Kafka quando…",[528,23757,23758,23764,23770,23776,23782],{},[20,23759,23760,23763],{},[89,23761,23762],{},"Vários sistemas precisam reagir ao mesmo fato",": pedido criado dispara\nestoque, notificação, analytics, faturamento. Pub\u002FSub brilha aqui.",[20,23765,23766,23769],{},[89,23767,23768],{},"Volume muito alto",": milhões de eventos\u002Fdia, em que uma fila comum sente.",[20,23771,23772,23775],{},[89,23773,23774],{},"Replay é necessário",": reconstruir uma base, treinar modelo, reprocessar\napós bug.",[20,23777,23778,23781],{},[89,23779,23780],{},"Integração de dados",": alimentar data lake, warehouse e caches a partir\nde um único stream (CDC — change data capture).",[20,23783,23784],{},"** buffering entre produtores e consumidores**: produtores em picos, consumidores\nconstantes — Kafka absorve o pico no log.",[283,23786,23788],{"id":23787},"não-use-kafka-quando","NÃO use Kafka quando…",[528,23790,23791,23797,23806,23812,23818],{},[20,23792,23793,23796],{},[89,23794,23795],{},"Simples fila de tarefas",": worker pegar job, processar, pronto. RabbitMQ,\nSQS ou Redis Streams são mais simples e baratos.",[20,23798,23799,23802,23803,23805],{},[89,23800,23801],{},"Solicitação-resposta síncrona",": o cliente precisa da resposta ",[89,23804,16126],{},".\nKafka não foi feito para RPC. Use REST\u002FgRPC.",[20,23807,23808,23811],{},[89,23809,23810],{},"Time pequeno, volume baixo",": a complexidade operacional do Kafka (cluster,\nZooKeeper\u002FKRaft, monitoramento de lag) não se paga. Use um broker simples.",[20,23813,23814,23817],{},[89,23815,23816],{},"Mensagens grandes",": Kafka é otimizado para mensagens pequenas (KBs).\nArquivos grandes devem ir para object storage e o evento só carrega a URL.",[20,23819,23820,23823],{},[89,23821,23822],{},"Fila com prioridade",": Kafka não tem prioridade nativa entre mensagens\ndo mesmo tópico. Para isso, use RabbitMQ com filas prioritárias.",[165,23825,23826,23835],{},[168,23827,23828],{},[171,23829,23830,23832],{},[174,23831,8776],{},[174,23833,23834],{},"Ferramenta recomendada",[184,23836,23837,23845,23853,23861,23869,23877],{},[171,23838,23839,23842],{},[189,23840,23841],{},"Worker queue simples",[189,23843,23844],{},"RabbitMQ, SQS, Redis",[171,23846,23847,23850],{},[189,23848,23849],{},"Pub\u002FSub de alta escala",[189,23851,23852],{},"Kafka, Pulsar",[171,23854,23855,23858],{},[189,23856,23857],{},"RPC síncrono",[189,23859,23860],{},"REST, gRPC",[171,23862,23863,23866],{},[189,23864,23865],{},"Streaming + processamento",[189,23867,23868],{},"Kafka + Flink\u002FStreams",[171,23870,23871,23874],{},[189,23872,23873],{},"Notificação simples entre serviços",[189,23875,23876],{},"RabbitMQ, SNS",[171,23878,23879,23882],{},[189,23880,23881],{},"Event sourcing \u002F log de domínio",[189,23883,23884],{},"Kafka, EventStoreDB",[12,23886,23888],{"id":23887},"esquemas-partições-e-chaves-na-prática-particoes-chaves","Esquemas, partições e chaves na prática {#particoes-chaves}",[283,23890,23892],{"id":23891},"esquema-schema-dos-eventos","Esquema (schema) dos eventos",[86,23894,23895,23896,23899,23900,1327,23903,17697,23906,23909],{},"Eventos sem contrato quebram consumidores a cada mudança. Use um ",[89,23897,23898],{},"schema\nregistry"," (Confluent Schema Registry, Apicurio) com ",[89,23901,23902],{},"Avro",[89,23904,23905],{},"Protobuf",[89,23907,23908],{},"JSON Schema",". O registro valida que cada evento respeita o contrato e\npermite evolução compatível (backward\u002Fforward compatibility).",[86,23911,23912],{},"Boas práticas de schema:",[528,23914,23915,23929,23932],{},[20,23916,23917,23918,1327,23920,1327,23923,1327,23926,23928],{},"Inclua ",[513,23919,23298],{},[513,23921,23922],{},"event_type",[513,23924,23925],{},"timestamp",[513,23927,17727],{}," em todo evento.",[20,23930,23931],{},"Nunca remova campos — marque como deprecated e mantenha nullable.",[20,23933,23934],{},"Adicione campos novos como opcionais (default) para não quebrar consumidores\nantigos.",[283,23936,23938],{"id":23937},"quantas-partições","Quantas partições?",[528,23940,23941,23948,23958],{},[20,23942,23943,23944,23947],{},"Calcule pelo ",[89,23945,23946],{},"throughput desejado dividido pela capacidade de cada consumidor",".\nSe 1 consumidor processa 1.000 eventos\u002Fs e você precisa de 6.000\u002Fs, precisa\nde no mínimo 6 partições (e 6 consumers).",[20,23949,23950,23953,23954,23957],{},[89,23951,23952],{},"Arredonde para cima com folga",": partições são baratas, mas ",[89,23955,23956],{},"não dá para\ndiminuir"," depois de criadas (precisa recriar o tópico).",[20,23959,23960],{},"Considere paralelismo futuro: mais partições hoje = headroom para escalar\nconsumers amanhã.",[283,23962,23964],{"id":23963},"escolha-da-chave","Escolha da chave",[86,23966,23967,23968,23971,23972,23975,23976,23979,23980,23983],{},"A chave decide a partition e, portanto, a ",[89,23969,23970],{},"ordem garantida",". Pergunte:\n\"para qual entidade a ordem dos eventos importa?\". Se for por cliente, use\n",[513,23973,23974],{},"customer_id"," como chave. Se for por pedido, use ",[513,23977,23978],{},"order_id",". Cuidado com\nchaves de baixa cardinalidade (ex.: 10 regiões) que causam ",[89,23981,23982],{},"hot partitions",":\numa partition recebe quase todo o tráfego e vira gargalo.",[283,23985,23010],{"id":23986},"retenção",[528,23988,23989,23998,24007],{},[20,23990,23991,1679,23994,23997],{},[89,23992,23993],{},"Por tempo",[513,23995,23996],{},"retention.ms=604800000"," (7 dias). Padrão comum.",[20,23999,24000,1679,24003,24006],{},[89,24001,24002],{},"Por tamanho",[513,24004,24005],{},"retention.bytes=10737418240"," (10GB por partition).",[20,24008,24009,24012],{},[89,24010,24011],{},"Log compaction",": para tópicos de \"estado atual\" (ex.: saldos de conta),\nKafka mantém só o último valor por chave. Útil para materializar views.",[12,24014,820],{"id":819},[17,24016,24017,24025,24031,24037,24047,24053,24063,24079,24085,24091],{},[20,24018,24019,24022,24023,522],{},[89,24020,24021],{},"Consumer sem idempotência",": duplica reservas\u002Fpagamentos quando há\nreprocessamento. Sempre deduque por ",[513,24024,23298],{},[20,24026,24027,24030],{},[89,24028,24029],{},"Auto-commit ligado",": o offset avança antes do processamento terminar.\nCrash no meio = evento perdido. Desligue e faça commit manual.",[20,24032,24033,24036],{},[89,24034,24035],{},"Poucas partições para o volume",": consumers parados, lag crescente.\nCalcule partições pelo throughput e deixe folga.",[20,24038,24039,24042,24043,24046],{},[89,24040,24041],{},"Chave errada \u002F hot partition",": usar ",[513,24044,24045],{},"country=BR"," como chave quando 90%\ndos seus clientes são brasileiros concentra tudo numa partition. Use\nchaves de alta cardinalidade.",[20,24048,24049,24052],{},[89,24050,24051],{},"Mensagens gigantes",": mandar PDF\u002Fimagem dentro do evento. Kafka sofre\ncom mensagens grandes. Use object storage e passe a URL.",[20,24054,24055,24058,24059,24062],{},[89,24056,24057],{},"Ignorar lag",": não monitorar o ",[513,24060,24061],{},"consumer_lag"," é não saber que seu\npipeline está atrasado. Alertas em lag crescente são obrigatórios.",[20,24064,24065,24068,24069,17031,24072,1327,24075,24078],{},[89,24066,24067],{},"Tópico único para tudo event types",": misturar ",[513,24070,24071],{},"pedido.criado",[513,24073,24074],{},"pagamento.aprovado",[513,24076,24077],{},"usuario.atualizado"," num tópico só obriga todos os\nconsumers a filtrar. Separe por domínio\u002Fevent type.",[20,24080,24081,24084],{},[89,24082,24083],{},"Sem schema registry",": consumidores quebram a cada mudança de campo.\nVersione e valide os eventos.",[20,24086,24087,24090],{},[89,24088,24089],{},"Tratar Kafka como banco de dados",": consultar o mesmo evento de forma\naleatória é lento e caro. Kafka é log sequencial, não é store de lookup.",[20,24092,24093,24096,24097,24100,24101,3022,24104,24107],{},[89,24094,24095],{},"Cluster mal dimensionado",": poucos brokers = risco de perda de dados se\n",[513,24098,24099],{},"min.insync.replicas"," não for respeitado. No mínimo 3 brokers com\n",[513,24102,24103],{},"replication.factor=3",[513,24105,24106],{},"min.insync.replicas=2"," para produção.",[12,24109,24111],{"id":24110},"checklist-de-adoção-checklist","Checklist de adoção {#checklist}",[528,24113,24115,24121,24127,24133,24139,24148,24154,24160,24166,24174,24180,24186],{"className":24114},[890],[20,24116,24118,24120],{"className":24117},[894],[896,24119],{"disabled":898,"type":899}," Definiu se o problema é pub\u002Fsub de escala (Kafka) ou fila simples (RabbitMQ)",[20,24122,24124,24126],{"className":24123},[894],[896,24125],{"disabled":898,"type":899}," Esquema de eventos versionado em schema registry",[20,24128,24130,24132],{"className":24129},[894],[896,24131],{"disabled":898,"type":899}," Particionamento calculado pelo throughput + folga",[20,24134,24136,24138],{"className":24135},[894],[896,24137],{"disabled":898,"type":899}," Chave de partição escolhida para ordem por entidade correta",[20,24140,24142,24144,24145,24147],{"className":24141},[894],[896,24143],{"disabled":898,"type":899}," Produtores com ",[513,24146,23535],{}," e idempotência",[20,24149,24151,24153],{"className":24150},[894],[896,24152],{"disabled":898,"type":899}," Consumidores com commit manual e idempotência",[20,24155,24157,24159],{"className":24156},[894],[896,24158],{"disabled":898,"type":899}," Monitoramento de consumer lag com alertas",[20,24161,24163,24165],{"className":24162},[894],[896,24164],{"disabled":898,"type":899}," Retenção configurada (tempo e\u002Fou tamanho) por tópico",[20,24167,24169,24171,24172],{"className":24168},[894],[896,24170],{"disabled":898,"type":899}," Cluster com no mínimo 3 brokers, ",[513,24173,24103],{},[20,24175,24177,24179],{"className":24176},[894],[896,24178],{"disabled":898,"type":899}," Tópicos separados por domínio\u002Fevent type",[20,24181,24183,24185],{"className":24182},[894],[896,24184],{"disabled":898,"type":899}," Estratégia de DLQ (dead letter queue) para eventos que falham",[20,24187,24189,24191],{"className":24188},[894],[896,24190],{"disabled":898,"type":899}," Testes de caos: matar broker, reiniciar consumer, simular reprocessamento",[968,24193],{},[86,24195,24196,24199,24200,522],{},[89,24197,24198],{},"Construindo integrações orientadas a eventos?"," A Inicialize Tec projeta e\nimplementa pipelines com Kafka — da modelagem de tópicos e esquemas aos\nconsumidores idempotentes e monitoramento de lag. ",[23,24201,2881],{"href":978},[2883,24203,4828],{},{"title":515,"searchDepth":981,"depth":981,"links":24205},[24206,24207,24208,24209,24214,24219,24220,24225,24229,24233,24239,24240],{"id":14,"depth":984,"text":15},{"id":22879,"depth":984,"text":22880},{"id":22931,"depth":984,"text":22932},{"id":23061,"depth":984,"text":23062,"children":24210},[24211,24212,24213],{"id":23068,"depth":981,"text":23069},{"id":23084,"depth":981,"text":23085},{"id":23127,"depth":981,"text":23128},{"id":23148,"depth":984,"text":23149,"children":24215},[24216,24217,24218],{"id":23152,"depth":981,"text":23153},{"id":23173,"depth":981,"text":23174},{"id":23180,"depth":981,"text":23181},{"id":23225,"depth":984,"text":23226},{"id":23266,"depth":984,"text":23267,"children":24221},[24222,24223,24224],{"id":23273,"depth":981,"text":23274},{"id":23280,"depth":981,"text":23281},{"id":23302,"depth":981,"text":23303},{"id":23324,"depth":984,"text":23325,"children":24226},[24227,24228],{"id":23335,"depth":981,"text":23336},{"id":23551,"depth":981,"text":23552},{"id":23750,"depth":984,"text":23751,"children":24230},[24231,24232],{"id":23754,"depth":981,"text":23755},{"id":23787,"depth":981,"text":23788},{"id":23887,"depth":984,"text":23888,"children":24234},[24235,24236,24237,24238],{"id":23891,"depth":981,"text":23892},{"id":23937,"depth":981,"text":23938},{"id":23963,"depth":981,"text":23964},{"id":23986,"depth":981,"text":23010},{"id":819,"depth":984,"text":820},{"id":24110,"depth":984,"text":24111},"integracoes","Aprenda arquitetura orientada a eventos com Apache Kafka: producers, consumers, tópicos e quando usar — com exemplos em Python e analogias do dia-a-dia.",[24244,24245,24246,23232,24247,22937,24248],"kafka","event-driven","mensageria","eventos","microserviços comunicação",{},"\u002Fintegracoes\u002Fevent-driven-architecture-kafka",{"title":22809,"description":24242},"event-driven-architecture-kafka","integracoes\u002Fevent-driven-architecture-kafka","DstY79jPycoaD0CYBvFn7pLhzps7hIhiDSYVYl7SQW0",{"id":24256,"title":24257,"author":7,"body":24258,"category":24241,"date":1028,"description":26796,"extension":1030,"keywords":26797,"meta":26801,"navigation":898,"path":26802,"seo":26803,"slug":26804,"stem":26805,"updated":1028,"__hash__":26806},"content\u002Fintegracoes\u002Fintegrar-erp-com-api-passo-a-passo.md","Integrar ERP com API: passo a passo técnico",{"type":9,"value":24259,"toc":26759},[24260,24262,24318,24322,24328,24335,24361,24379,24393,24400,24404,24407,24411,24418,24424,24496,24499,24506,24509,24513,24520,24523,24534,24538,24545,24548,24552,24559,24562,24566,24573,24579,24581,24587,24594,24599,24624,24628,24638,24645,24857,24870,24880,24884,24887,24913,24920,24924,25118,25332,25340,25344,25355,25359,25366,25410,25528,25532,25535,25546,25632,25644,25651,25655,25662,25760,25768,25773,25798,25805,25809,25814,25821,25825,25832,25952,25960,25964,25967,25993,25997,26000,26059,26063,26070,26078,26080,26084,26089,26093,26128,26132,26166,26170,26179,26183,26221,26225,26283,26287,26292,26296,26326,26330,26376,26380,26388,26392,26434,26438,26496,26500,26509,26513,26561,26565,26628,26632,26637,26642,26656,26660,26663,26668,26682,26687,26719,26724,26741,26746,26748,26756],[12,24261,15],{"id":14},[17,24263,24264,24270,24275,24279,24285,24291,24297,24303,24309,24313],{},[20,24265,24266],{},[23,24267,24269],{"href":24268},"#problema","O problema da integração",[20,24271,24272],{},[23,24273,24274],{"href":6914},"Conceitos que você precisa entender",[20,24276,24277],{},[23,24278,4913],{"href":4912},[20,24280,24281],{},[23,24282,24284],{"href":24283},"#passo-1","Passo 1: Mapear dados do ERP",[20,24286,24287],{},[23,24288,24290],{"href":24289},"#passo-2","Passo 2: Criar camada de middleware",[20,24292,24293],{},[23,24294,24296],{"href":24295},"#passo-3","Passo 3: Implementar sincronização",[20,24298,24299],{},[23,24300,24302],{"href":24301},"#passo-4","Passo 4: Webhooks para tempo real",[20,24304,24305],{},[23,24306,24308],{"href":24307},"#passo-5","Passo 5: Observabilidade",[20,24310,24311],{},[23,24312,74],{"href":73},[20,24314,24315],{},[23,24316,24317],{"href":2990},"Caso real: e-commerce que integrou ERP em 6 semanas",[12,24319,24321],{"id":24320},"o-problema-da-integração-problema","O problema da integração {#problema}",[86,24323,24324,24327],{},[89,24325,24326],{},"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.",[86,24329,24330,24331,24334],{},"O problema é que ERPs legados (sistemas antigos, instalados há 10-20 anos)\nraramente expõem ",[89,24332,24333],{},"APIs REST"," modernas. Eles foram desenhados para operar\nisoladamente, como uma ilha. Suas integrações costumam ser via:",[528,24336,24337,24343,24349,24355],{},[20,24338,24339,24342],{},[89,24340,24341],{},"Arquivos em disco",": o ERP gera um CSV\u002FTXT\u002FXML em uma pasta, outro sistema\nlê essa pasta periodicamente",[20,24344,24345,24348],{},[89,24346,24347],{},"Tabelas compartilhadas no banco",": dois sistemas leem\u002Fescrevem na mesma\ntabela do banco de dados",[20,24350,24351,24354],{},[89,24352,24353],{},"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)",[20,24356,24357,24360],{},[89,24358,24359],{},"Conexões diretas ao banco de dados",": um sistema acessa o banco do outro\ndiretamente (arriscado e frágil)",[86,24362,24363,24364,24366,24367,24370,24371,24374,24375,24378],{},"Sistemas modernos esperam ",[89,24365,24333],{}," (um padrão de comunicação pela web,\nusando HTTP — o mesmo protocolo dos sites) com autenticação ",[89,24368,24369],{},"OAuth","\n(um sistema de \"carteirinha\" que prova quem você é), respostas em ",[89,24372,24373],{},"JSON","\n(formato de dados leve e legível) e ",[89,24376,24377],{},"webhooks"," (explico adiante).",[710,24380,24381],{},[86,24382,24383,24385,24386,24389,24390,522],{},[89,24384,3011],{},": 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 ",[89,24387,24388],{},"tradutor"," que entende ambos os idiomas e formatos. Esse\ntradutor é o que chamamos de ",[89,24391,24392],{},"middleware",[86,24394,24395,24396,24399],{},"A lacuna entre os dois mundos é a fonte de ",[89,24397,24398],{},"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.",[12,24401,24403],{"id":24402},"conceitos-que-você-precisa-entender-conceitos","Conceitos que você precisa entender {#conceitos}",[86,24405,24406],{},"Antes de mergulhar na implementação, vamos alinhar 6 conceitos-chave:",[283,24408,24410],{"id":24409},"api-rest","API REST",[710,24412,24413],{},[86,24414,24415,24417],{},[89,24416,3011],{},": 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.",[86,24419,24420,24423],{},[89,24421,24422],{},"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:",[165,24425,24426,24438],{},[168,24427,24428],{},[171,24429,24430,24433,24436],{},[174,24431,24432],{},"Operação",[174,24434,24435],{},"Verbo HTTP",[174,24437,5731],{},[184,24439,24440,24454,24468,24482],{},[171,24441,24442,24445,24448],{},[189,24443,24444],{},"Criar",[189,24446,24447],{},"POST",[189,24449,24450,24453],{},[513,24451,24452],{},"POST \u002Fpedidos"," — cria um pedido novo",[171,24455,24456,24459,24462],{},[189,24457,24458],{},"Ler",[189,24460,24461],{},"GET",[189,24463,24464,24467],{},[513,24465,24466],{},"GET \u002Fpedidos\u002F123"," — busca o pedido #123",[171,24469,24470,24473,24476],{},[189,24471,24472],{},"Atualizar",[189,24474,24475],{},"PUT\u002FPATCH",[189,24477,24478,24481],{},[513,24479,24480],{},"PUT \u002Fpedidos\u002F123"," — atualiza o pedido #123",[171,24483,24484,24487,24490],{},[189,24485,24486],{},"Deletar",[189,24488,24489],{},"DELETE",[189,24491,24492,24495],{},[513,24493,24494],{},"DELETE \u002Fpedidos\u002F123"," — remove o pedido #123",[283,24497,24498],{"id":24392},"Middleware",[710,24500,24501],{},[86,24502,24503,24505],{},[89,24504,3011],{},": 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.",[86,24507,24508],{},"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.",[283,24510,24512],{"id":24511},"fila-de-mensagens-message-queue","Fila de mensagens (Message Queue)",[710,24514,24515],{},[86,24516,24517,24519],{},[89,24518,3011],{},": 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.",[86,24521,24522],{},"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.",[86,24524,24525,24526,1327,24529,1327,24531,522],{},"Ferramentas comuns: ",[89,24527,24528],{},"RabbitMQ",[89,24530,22993],{},[89,24532,24533],{},"Amazon SQS",[283,24535,24537],{"id":24536},"webhook","Webhook",[710,24539,24540],{},[86,24541,24542,24544],{},[89,24543,3011],{},": 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.",[86,24546,24547],{},"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.",[283,24549,24551],{"id":24550},"idempotência","Idempotência",[710,24553,24554],{},[86,24555,24556,24558],{},[89,24557,3011],{},": 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.",[86,24560,24561],{},"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.",[283,24563,24565],{"id":24564},"cdc-change-data-capture","CDC (Change Data Capture)",[710,24567,24568],{},[86,24569,24570,24572],{},[89,24571,3011],{},": 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.",[86,24574,24575,24576,522],{},"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: ",[89,24577,24578],{},"Debezium",[12,24580,6284],{"id":6283},[506,24582,24585],{"className":24583,"code":24584,"language":511},[509],"[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",[513,24586,24584],{"__ignoreMap":515},[710,24588,24589],{},[86,24590,24591,24593],{},[89,24592,7506],{},": 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.",[86,24595,24596],{},[89,24597,24598],{},"Princípios da arquitetura:",[17,24600,24601,24607,24613,24618],{},[20,24602,24603,24606],{},[89,24604,24605],{},"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.",[20,24608,24609,24612],{},[89,24610,24611],{},"Assíncrono",": comunicação via fila, não síncrona. O ERP não espera o\nsistema moderno responder.",[20,24614,24615,24617],{},[89,24616,21096],{},": reprocessar uma mensagem não causa duplicação.",[20,24619,24620,24623],{},[89,24621,24622],{},"Observável",": cada mensagem tem um ID único e é rastreável do início ao\nfim.",[12,24625,24627],{"id":24626},"passo-1-mapear-dados-do-erp-passo-1","Passo 1: Mapear dados do ERP {#passo-1}",[86,24629,24630,24631,24634,24635,24637],{},"Antes de escrever qualquer código, documente ",[89,24632,24633],{},"quais dados"," do ERP serão\nintegrados e ",[89,24636,3131],{}," eles estão estruturados.",[710,24639,24640],{},[86,24641,24642,24644],{},[89,24643,3011],{},": 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.",[506,24646,24648],{"className":1656,"code":24647,"language":1658,"meta":515,"style":515},"# 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",[513,24649,24650,24655,24662,24669,24679,24686,24698,24710,24722,24734,24746,24756,24766,24770,24777,24786,24792,24803,24815,24827,24839,24848],{"__ignoreMap":515},[1376,24651,24652],{"class":1378,"line":1379},[1376,24653,24654],{"class":1665},"# mapeamento-erp.yaml — Documentação dos dados a integrar\n",[1376,24656,24657,24660],{"class":1378,"line":984},[1376,24658,24659],{"class":1675},"entidades",[1376,24661,1750],{"class":1671},[1376,24663,24664,24667],{"class":1378,"line":981},[1376,24665,24666],{"class":1675},"  pedido",[1376,24668,1750],{"class":1671},[1376,24670,24671,24674,24676],{"class":1378,"line":1395},[1376,24672,24673],{"class":1675},"    origem",[1376,24675,1679],{"class":1671},[1376,24677,24678],{"class":1682},"tabela TBL_PEDIDO (SQL Server)\n",[1376,24680,24681,24684],{"class":1378,"line":1401},[1376,24682,24683],{"class":1675},"    campos",[1376,24685,1750],{"class":1671},[1376,24687,24688,24690,24693,24695],{"class":1378,"line":1407},[1376,24689,2230],{"class":1671},[1376,24691,24692],{"class":1675},"nr_pedido",[1376,24694,1679],{"class":1671},[1376,24696,24697],{"class":1682},"\"ID único do pedido (int, chave primária)\"\n",[1376,24699,24700,24702,24705,24707],{"class":1378,"line":1413},[1376,24701,2230],{"class":1671},[1376,24703,24704],{"class":1675},"dt_emissao",[1376,24706,1679],{"class":1671},[1376,24708,24709],{"class":1682},"\"Data de emissão (datetime)\"\n",[1376,24711,24712,24714,24717,24719],{"class":1378,"line":1419},[1376,24713,2230],{"class":1671},[1376,24715,24716],{"class":1675},"cd_cliente",[1376,24718,1679],{"class":1671},[1376,24720,24721],{"class":1682},"\"Código do cliente — FK para TBL_CLIENTE\"\n",[1376,24723,24724,24726,24729,24731],{"class":1378,"line":1424},[1376,24725,2230],{"class":1671},[1376,24727,24728],{"class":1675},"vl_total",[1376,24730,1679],{"class":1671},[1376,24732,24733],{"class":1682},"\"Valor total (Decimal 12,2 — até 99.999.999,99)\"\n",[1376,24735,24736,24738,24741,24743],{"class":1378,"line":1430},[1376,24737,2230],{"class":1671},[1376,24739,24740],{"class":1675},"st_pedido",[1376,24742,1679],{"class":1671},[1376,24744,24745],{"class":1682},"\"Status: A=Aberto, F=Faturado, C=Cancelado\"\n",[1376,24747,24748,24751,24753],{"class":1378,"line":1436},[1376,24749,24750],{"class":1675},"    trigger",[1376,24752,1679],{"class":1671},[1376,24754,24755],{"class":1682},"\"INSERT ou UPDATE em TBL_PEDIDO\"\n",[1376,24757,24758,24761,24763],{"class":1378,"line":1442},[1376,24759,24760],{"class":1675},"    frequencia",[1376,24762,1679],{"class":1671},[1376,24764,24765],{"class":1682},"\"Aproximadamente 200 pedidos\u002Fdia\"\n",[1376,24767,24768],{"class":1378,"line":1447},[1376,24769,1398],{"emptyLinePlaceholder":898},[1376,24771,24772,24775],{"class":1378,"line":1453},[1376,24773,24774],{"class":1675},"  cliente",[1376,24776,1750],{"class":1671},[1376,24778,24779,24781,24783],{"class":1378,"line":1459},[1376,24780,24673],{"class":1675},[1376,24782,1679],{"class":1671},[1376,24784,24785],{"class":1682},"tabela TBL_CLIENTE (SQL Server)\n",[1376,24787,24788,24790],{"class":1378,"line":2200},[1376,24789,24683],{"class":1675},[1376,24791,1750],{"class":1671},[1376,24793,24794,24796,24798,24800],{"class":1378,"line":2208},[1376,24795,2230],{"class":1671},[1376,24797,24716],{"class":1675},[1376,24799,1679],{"class":1671},[1376,24801,24802],{"class":1682},"\"ID único do cliente (int)\"\n",[1376,24804,24805,24807,24810,24812],{"class":1378,"line":2219},[1376,24806,2230],{"class":1671},[1376,24808,24809],{"class":1675},"nm_cliente",[1376,24811,1679],{"class":1671},[1376,24813,24814],{"class":1682},"\"Nome ou razão social (varchar 100)\"\n",[1376,24816,24817,24819,24822,24824],{"class":1378,"line":2227},[1376,24818,2230],{"class":1671},[1376,24820,24821],{"class":1675},"nr_cnpj",[1376,24823,1679],{"class":1671},[1376,24825,24826],{"class":1682},"\"CNPJ sem máscara (varchar 14)\"\n",[1376,24828,24829,24831,24834,24836],{"class":1378,"line":2241},[1376,24830,2230],{"class":1671},[1376,24832,24833],{"class":1675},"ds_email",[1376,24835,1679],{"class":1671},[1376,24837,24838],{"class":1682},"\"E-mail de contato (varchar 200)\"\n",[1376,24840,24841,24843,24845],{"class":1378,"line":2253},[1376,24842,24750],{"class":1675},[1376,24844,1679],{"class":1671},[1376,24846,24847],{"class":1682},"\"INSERT ou UPDATE em TBL_CLIENTE\"\n",[1376,24849,24850,24852,24854],{"class":1378,"line":2283},[1376,24851,24760],{"class":1675},[1376,24853,1679],{"class":1671},[1376,24855,24856],{"class":1682},"\"Aproximadamente 20 clientes novos\u002Fdia\"\n",[86,24858,24859,24861,24862,24865,24866,24869],{},[89,24860,6471],{},": Use ",[89,24863,24864],{},"database triggers"," (gatilhos no banco que disparam quando\nalgo muda) ou ",[89,24867,24868],{},"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.",[710,24871,24872],{},[86,24873,24874,24876,24877,24879],{},[89,24875,7223],{},": Pular o mapeamento e ir direto ao código. Resultado: 3\nsemanas depois, descobre que o campo ",[513,24878,24740],{}," pode ter 6 valores\ndiferentes, não 3 como você assumiu. O mapeamento é a parte mais importante\n— invista tempo nele.",[12,24881,24883],{"id":24882},"passo-2-criar-camada-de-middleware-passo-2","Passo 2: Criar camada de middleware {#passo-2}",[86,24885,24886],{},"O middleware é o coração da integração. Ele faz 4 coisas:",[17,24888,24889,24895,24901,24907],{},[20,24890,24891,24894],{},[89,24892,24893],{},"Lê"," mudanças do ERP (via trigger, CDC ou polling)",[20,24896,24897,24900],{},[89,24898,24899],{},"Transforma"," para um formato canônico (JSON padronizado que o sistema\nmoderno entende)",[20,24902,24903,24906],{},[89,24904,24905],{},"Publica"," em fila (RabbitMQ\u002FKafka)",[20,24908,24909,24912],{},[89,24910,24911],{},"Consome"," da fila e envia para a API moderna",[710,24914,24915],{},[86,24916,24917,24919],{},[89,24918,3011],{},": 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.",[283,24921,24923],{"id":24922},"exemplo-em-python-fastapi-rabbitmq","Exemplo em Python (FastAPI + RabbitMQ)",[506,24925,24927],{"className":3439,"code":24926,"language":3441,"meta":515,"style":515},"# 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",[513,24928,24929,24934,24938,24942,24947,24951,24956,24960,24965,24969,24974,24979,24984,24989,24993,24998,25003,25008,25013,25018,25023,25028,25033,25037,25041,25046,25051,25056,25060,25065,25070,25074,25079,25084,25089,25094,25099,25104,25109,25113],{"__ignoreMap":515},[1376,24930,24931],{"class":1378,"line":1379},[1376,24932,24933],{},"# adapter_erp.py — Adapter que lê mudanças do ERP e publica na fila\n",[1376,24935,24936],{"class":1378,"line":984},[1376,24937,3637],{},[1376,24939,24940],{"class":1378,"line":981},[1376,24941,4384],{},[1376,24943,24944],{"class":1378,"line":1395},[1376,24945,24946],{},"from pika import BlockingConnection, ConnectionParameters, BasicProperties\n",[1376,24948,24949],{"class":1378,"line":1401},[1376,24950,1398],{"emptyLinePlaceholder":898},[1376,24952,24953],{"class":1378,"line":1407},[1376,24954,24955],{},"def on_erp_change(entity: str, action: str, data: dict):\n",[1376,24957,24958],{"class":1378,"line":1413},[1376,24959,3714],{},[1376,24961,24962],{"class":1378,"line":1419},[1376,24963,24964],{},"    Chamado quando o ERP notifica uma mudança (via trigger ou CDC).\n",[1376,24966,24967],{"class":1378,"line":1424},[1376,24968,1398],{"emptyLinePlaceholder":898},[1376,24970,24971],{"class":1378,"line":1430},[1376,24972,24973],{},"    Args:\n",[1376,24975,24976],{"class":1378,"line":1436},[1376,24977,24978],{},"        entity: nome da entidade que mudou ('pedido', 'cliente', etc.)\n",[1376,24980,24981],{"class":1378,"line":1442},[1376,24982,24983],{},"        action: o que aconteceu ('created', 'updated', 'deleted')\n",[1376,24985,24986],{"class":1378,"line":1447},[1376,24987,24988],{},"        data: os dados do registro que mudou\n",[1376,24990,24991],{"class":1378,"line":1453},[1376,24992,3714],{},[1376,24994,24995],{"class":1378,"line":1459},[1376,24996,24997],{},"    # Constrói mensagem no formato canônico (padrão para todos os sistemas)\n",[1376,24999,25000],{"class":1378,"line":2200},[1376,25001,25002],{},"    message = {\n",[1376,25004,25005],{"class":1378,"line":2208},[1376,25006,25007],{},"        # ID único da mensagem — usado para idempotência (evitar duplicação)\n",[1376,25009,25010],{"class":1378,"line":2219},[1376,25011,25012],{},"        \"id\": f\"{entity}-{data['id']}-{action}-{datetime.utcnow().isoformat()}\",\n",[1376,25014,25015],{"class":1378,"line":2227},[1376,25016,25017],{},"        \"entity\": entity,        # qual entidade mudou\n",[1376,25019,25020],{"class":1378,"line":2241},[1376,25021,25022],{},"        \"action\": action,        # o que aconteceu: created | updated | deleted\n",[1376,25024,25025],{"class":1378,"line":2253},[1376,25026,25027],{},"        \"data\": data,            # os dados em si\n",[1376,25029,25030],{"class":1378,"line":2283},[1376,25031,25032],{},"        \"timestamp\": datetime.utcnow().isoformat(),  # quando ocorreu\n",[1376,25034,25035],{"class":1378,"line":2296},[1376,25036,5626],{},[1376,25038,25039],{"class":1378,"line":2311},[1376,25040,1398],{"emptyLinePlaceholder":898},[1376,25042,25043],{"class":1378,"line":2316},[1376,25044,25045],{},"    # Conecta ao RabbitMQ e publica a mensagem na fila\n",[1376,25047,25048],{"class":1378,"line":2324},[1376,25049,25050],{},"    connection = BlockingConnection(ConnectionParameters(\"rabbitmq\"))\n",[1376,25052,25053],{"class":1378,"line":2333},[1376,25054,25055],{},"    channel = connection.channel()\n",[1376,25057,25058],{"class":1378,"line":2340},[1376,25059,1398],{"emptyLinePlaceholder":898},[1376,25061,25062],{"class":1378,"line":2351},[1376,25063,25064],{},"    # Declara a fila como 'durable' — sobrevive a reinícios do RabbitMQ\n",[1376,25066,25067],{"class":1378,"line":2362},[1376,25068,25069],{},"    channel.queue_declare(queue=\"erp_changes\", durable=True)\n",[1376,25071,25072],{"class":1378,"line":2385},[1376,25073,1398],{"emptyLinePlaceholder":898},[1376,25075,25076],{"class":1378,"line":2396},[1376,25077,25078],{},"    # Publica a mensagem\n",[1376,25080,25081],{"class":1378,"line":2411},[1376,25082,25083],{},"    channel.basic_publish(\n",[1376,25085,25086],{"class":1378,"line":2416},[1376,25087,25088],{},"        exchange=\"\",\n",[1376,25090,25091],{"class":1378,"line":2422},[1376,25092,25093],{},"        routing_key=\"erp_changes\",\n",[1376,25095,25096],{"class":1378,"line":2430},[1376,25097,25098],{},"        body=json.dumps(message),\n",[1376,25100,25101],{"class":1378,"line":2448},[1376,25102,25103],{},"        # delivery_mode=2 = mensagem persistente (não se perde em restart)\n",[1376,25105,25106],{"class":1378,"line":2457},[1376,25107,25108],{},"        properties=BasicProperties(delivery_mode=2),\n",[1376,25110,25111],{"class":1378,"line":2464},[1376,25112,3748],{},[1376,25114,25115],{"class":1378,"line":2475},[1376,25116,25117],{},"    connection.close()\n",[506,25119,25121],{"className":3439,"code":25120,"language":3441,"meta":515,"style":515},"# 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",[513,25122,25123,25128,25132,25137,25141,25146,25150,25155,25159,25164,25168,25173,25177,25182,25187,25192,25197,25202,25207,25212,25216,25221,25226,25231,25236,25241,25246,25251,25256,25261,25266,25270,25275,25280,25285,25289,25294,25299,25304,25309,25313,25317,25322,25327],{"__ignoreMap":515},[1376,25124,25125],{"class":1378,"line":1379},[1376,25126,25127],{},"# consumer_api.py — Consumer que lê da fila e envia para a API moderna\n",[1376,25129,25130],{"class":1378,"line":984},[1376,25131,3637],{},[1376,25133,25134],{"class":1378,"line":981},[1376,25135,25136],{},"from pika import BlockingConnection, ConnectionParameters\n",[1376,25138,25139],{"class":1378,"line":1395},[1376,25140,1398],{"emptyLinePlaceholder":898},[1376,25142,25143],{"class":1378,"line":1401},[1376,25144,25145],{},"def on_message(ch, method, properties, body):\n",[1376,25147,25148],{"class":1378,"line":1407},[1376,25149,3714],{},[1376,25151,25152],{"class":1378,"line":1413},[1376,25153,25154],{},"    Chamado quando uma mensagem chega na fila.\n",[1376,25156,25157],{"class":1378,"line":1419},[1376,25158,1398],{"emptyLinePlaceholder":898},[1376,25160,25161],{"class":1378,"line":1424},[1376,25162,25163],{},"    O RabbitMQ chama esta função automaticamente para cada mensagem.\n",[1376,25165,25166],{"class":1378,"line":1430},[1376,25167,3714],{},[1376,25169,25170],{"class":1378,"line":1436},[1376,25171,25172],{},"    message = json.loads(body)\n",[1376,25174,25175],{"class":1378,"line":1442},[1376,25176,1398],{"emptyLinePlaceholder":898},[1376,25178,25179],{"class":1378,"line":1447},[1376,25180,25181],{},"    # === IDEMPOTÊNCIA ===\n",[1376,25183,25184],{"class":1378,"line":1453},[1376,25185,25186],{},"    # Verifica se esta mensagem já foi processada (pode chegar duplicada\n",[1376,25188,25189],{"class":1378,"line":1459},[1376,25190,25191],{},"    # devido a retries). Usa o ID único da mensagem.\n",[1376,25193,25194],{"class":1378,"line":2200},[1376,25195,25196],{},"    if already_processed(message[\"id\"]):\n",[1376,25198,25199],{"class":1378,"line":2208},[1376,25200,25201],{},"        # Já processou? Confirma o recebimento e ignora\n",[1376,25203,25204],{"class":1378,"line":2219},[1376,25205,25206],{},"        ch.basic_ack(method.delivery_tag)\n",[1376,25208,25209],{"class":1378,"line":2227},[1376,25210,25211],{},"        return\n",[1376,25213,25214],{"class":1378,"line":2241},[1376,25215,1398],{"emptyLinePlaceholder":898},[1376,25217,25218],{"class":1378,"line":2253},[1376,25219,25220],{},"    # === ENVIO PARA API MODERNA ===\n",[1376,25222,25223],{"class":1378,"line":2283},[1376,25224,25225],{},"    # Conforme a ação, chama o endpoint correto da API REST\n",[1376,25227,25228],{"class":1378,"line":2296},[1376,25229,25230],{},"    if message[\"action\"] == \"created\":\n",[1376,25232,25233],{"class":1378,"line":2311},[1376,25234,25235],{},"        # POST: cria um novo registro no sistema moderno\n",[1376,25237,25238],{"class":1378,"line":2316},[1376,25239,25240],{},"        response = api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n",[1376,25242,25243],{"class":1378,"line":2324},[1376,25244,25245],{},"    elif message[\"action\"] == \"updated\":\n",[1376,25247,25248],{"class":1378,"line":2333},[1376,25249,25250],{},"        # PUT: atualiza o registro existente\n",[1376,25252,25253],{"class":1378,"line":2340},[1376,25254,25255],{},"        response = api.put(\n",[1376,25257,25258],{"class":1378,"line":2351},[1376,25259,25260],{},"            f\"\u002F{message['entity']}\u002F{message['data']['id']}\",\n",[1376,25262,25263],{"class":1378,"line":2362},[1376,25264,25265],{},"            json=message[\"data\"]\n",[1376,25267,25268],{"class":1378,"line":2385},[1376,25269,6126],{},[1376,25271,25272],{"class":1378,"line":2396},[1376,25273,25274],{},"    elif message[\"action\"] == \"deleted\":\n",[1376,25276,25277],{"class":1378,"line":2411},[1376,25278,25279],{},"        # DELETE: remove o registro\n",[1376,25281,25282],{"class":1378,"line":2416},[1376,25283,25284],{},"        response = api.delete(f\"\u002F{message['entity']}\u002F{message['data']['id']}\")\n",[1376,25286,25287],{"class":1378,"line":2422},[1376,25288,1398],{"emptyLinePlaceholder":898},[1376,25290,25291],{"class":1378,"line":2430},[1376,25292,25293],{},"    # === TRATAMENTO DE RESPOSTA ===\n",[1376,25295,25296],{"class":1378,"line":2448},[1376,25297,25298],{},"    if response.ok:\n",[1376,25300,25301],{"class":1378,"line":2457},[1376,25302,25303],{},"        # Sucesso: marca como processado (para idempotência) e confirma\n",[1376,25305,25306],{"class":1378,"line":2464},[1376,25307,25308],{},"        mark_processed(message[\"id\"])\n",[1376,25310,25311],{"class":1378,"line":2475},[1376,25312,25206],{},[1376,25314,25315],{"class":1378,"line":2487},[1376,25316,4576],{},[1376,25318,25319],{"class":1378,"line":2499},[1376,25320,25321],{},"        # Falha: rejeita e coloca de volta na fila (com retry)\n",[1376,25323,25324],{"class":1378,"line":2506},[1376,25325,25326],{},"        # Em produção: usar dead letter queue após N tentativas\n",[1376,25328,25329],{"class":1378,"line":2517},[1376,25330,25331],{},"        ch.basic_nack(method.delivery_tag, requeue=True)\n",[710,25333,25334],{},[86,25335,25336,25339],{},[89,25337,25338],{},"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.",[12,25341,25343],{"id":25342},"passo-3-implementar-sincronização-passo-3","Passo 3: Implementar sincronização {#passo-3}",[86,25345,25346,25347,25350,25351,25354],{},"Há dois tipos de sincronização: ",[89,25348,25349],{},"carga completa"," (one-time, no início) e\n",[89,25352,25353],{},"incremental"," (contínua, após a carga inicial).",[283,25356,25358],{"id":25357},"carga-completa-one-time","Carga completa (one-time)",[86,25360,25361,25362,25365],{},"A primeira vez que você liga a integração, precisa sincronizar todos os dados\nhistóricos. Faça em ",[89,25363,25364],{},"batches"," (lotes) para não sobrecarregar o ERP.",[506,25367,25369],{"className":11777,"code":25368,"language":11779,"meta":515,"style":515},"-- 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",[513,25370,25371,25376,25381,25386,25391,25396,25401,25405],{"__ignoreMap":515},[1376,25372,25373],{"class":1378,"line":1379},[1376,25374,25375],{},"-- Carga inicial: busca pedidos em lotes de 10.000\n",[1376,25377,25378],{"class":1378,"line":984},[1376,25379,25380],{},"-- OFFSET\u002FFETCH NEXT garante que pegamos os dados em páginas, sem travar o ERP\n",[1376,25382,25383],{"class":1378,"line":981},[1376,25384,25385],{},"SELECT * FROM TBL_PEDIDO\n",[1376,25387,25388],{"class":1378,"line":1395},[1376,25389,25390],{},"WHERE dt_emissao >= '2024-01-01'\n",[1376,25392,25393],{"class":1378,"line":1401},[1376,25394,25395],{},"ORDER BY nr_pedido\n",[1376,25397,25398],{"class":1378,"line":1407},[1376,25399,25400],{},"OFFSET 0 ROWS FETCH NEXT 10000 ROWS ONLY;\n",[1376,25402,25403],{"class":1378,"line":1413},[1376,25404,1398],{"emptyLinePlaceholder":898},[1376,25406,25407],{"class":1378,"line":1419},[1376,25408,25409],{},"-- Depois: OFFSET 10000, 20000, 30000... até terminar\n",[506,25411,25413],{"className":3439,"code":25412,"language":3441,"meta":515,"style":515},"# 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",[513,25414,25415,25420,25425,25430,25435,25439,25443,25448,25453,25458,25463,25467,25471,25476,25481,25485,25490,25495,25500,25504,25509,25514,25518,25523],{"__ignoreMap":515},[1376,25416,25417],{"class":1378,"line":1379},[1376,25418,25419],{},"# sincronizacao_inicial.py — Carga completa em batches\n",[1376,25421,25422],{"class":1378,"line":984},[1376,25423,25424],{},"def sincronizar_pedidos_completos():\n",[1376,25426,25427],{"class":1378,"line":981},[1376,25428,25429],{},"    offset = 0\n",[1376,25431,25432],{"class":1378,"line":1395},[1376,25433,25434],{},"    batch_size = 10000\n",[1376,25436,25437],{"class":1378,"line":1401},[1376,25438,1398],{"emptyLinePlaceholder":898},[1376,25440,25441],{"class":1378,"line":1407},[1376,25442,23625],{},[1376,25444,25445],{"class":1378,"line":1413},[1376,25446,25447],{},"        # Busca um lote de pedidos do ERP\n",[1376,25449,25450],{"class":1378,"line":1419},[1376,25451,25452],{},"        pedidos = erp_db.execute(\n",[1376,25454,25455],{"class":1378,"line":1424},[1376,25456,25457],{},"            f\"SELECT * FROM TBL_PEDIDO ORDER BY nr_pedido \"\n",[1376,25459,25460],{"class":1378,"line":1430},[1376,25461,25462],{},"            f\"OFFSET {offset} ROWS FETCH NEXT {batch_size} ROWS ONLY\"\n",[1376,25464,25465],{"class":1378,"line":1436},[1376,25466,6126],{},[1376,25468,25469],{"class":1378,"line":1442},[1376,25470,1398],{"emptyLinePlaceholder":898},[1376,25472,25473],{"class":1378,"line":1447},[1376,25474,25475],{},"        if not pedidos:\n",[1376,25477,25478],{"class":1378,"line":1453},[1376,25479,25480],{},"            break  # não há mais dados\n",[1376,25482,25483],{"class":1378,"line":1459},[1376,25484,1398],{"emptyLinePlaceholder":898},[1376,25486,25487],{"class":1378,"line":2200},[1376,25488,25489],{},"        # Envia cada pedido para a fila (mesmo fluxo do adapter)\n",[1376,25491,25492],{"class":1378,"line":2208},[1376,25493,25494],{},"        for pedido in pedidos:\n",[1376,25496,25497],{"class":1378,"line":2219},[1376,25498,25499],{},"            on_erp_change(\"pedido\", \"created\", pedido)\n",[1376,25501,25502],{"class":1378,"line":2227},[1376,25503,1398],{"emptyLinePlaceholder":898},[1376,25505,25506],{"class":1378,"line":2241},[1376,25507,25508],{},"        offset += batch_size\n",[1376,25510,25511],{"class":1378,"line":2253},[1376,25512,25513],{},"        print(f\"Sincronizados {offset} pedidos...\")\n",[1376,25515,25516],{"class":1378,"line":2283},[1376,25517,1398],{"emptyLinePlaceholder":898},[1376,25519,25520],{"class":1378,"line":2296},[1376,25521,25522],{},"        # Pausa entre batches para não saturar o ERP\n",[1376,25524,25525],{"class":1378,"line":2311},[1376,25526,25527],{},"        time.sleep(2)\n",[283,25529,25531],{"id":25530},"incremental-contínuo","Incremental (contínuo)",[86,25533,25534],{},"Após a carga inicial, você só precisa sincronizar as mudanças novas:",[528,25536,25537],{},[20,25538,25539,25542,25543,25545],{},[89,25540,25541],{},"Abordagem 1 — Watermark com timestamp",": use o campo ",[513,25544,13635],{}," como\n\"marca d'água\". A cada 1-5 minutos, busque registros atualizados desde a\núltima marca.",[506,25547,25549],{"className":3439,"code":25548,"language":3441,"meta":515,"style":515},"# 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",[513,25550,25551,25556,25561,25566,25571,25575,25580,25585,25590,25595,25599,25603,25608,25613,25617,25622,25627],{"__ignoreMap":515},[1376,25552,25553],{"class":1378,"line":1379},[1376,25554,25555],{},"# sincronizacao_incremental.py — Polling com watermark\n",[1376,25557,25558],{"class":1378,"line":984},[1376,25559,25560],{},"def sincronizar_incremental():\n",[1376,25562,25563],{"class":1378,"line":981},[1376,25564,25565],{},"    # last_watermark = timestamp da última sincronização (salvo em arquivo\u002Fbanco)\n",[1376,25567,25568],{"class":1378,"line":1395},[1376,25569,25570],{},"    last_watermark = carregar_ultimo_watermark()\n",[1376,25572,25573],{"class":1378,"line":1401},[1376,25574,1398],{"emptyLinePlaceholder":898},[1376,25576,25577],{"class":1378,"line":1407},[1376,25578,25579],{},"    # Busca apenas registros modificados após o último watermark\n",[1376,25581,25582],{"class":1378,"line":1413},[1376,25583,25584],{},"    novos_pedidos = erp_db.execute(\n",[1376,25586,25587],{"class":1378,"line":1419},[1376,25588,25589],{},"        \"SELECT * FROM TBL_PEDIDO WHERE updated_at > ? ORDER BY updated_at\",\n",[1376,25591,25592],{"class":1378,"line":1424},[1376,25593,25594],{},"        [last_watermark]\n",[1376,25596,25597],{"class":1378,"line":1430},[1376,25598,3748],{},[1376,25600,25601],{"class":1378,"line":1436},[1376,25602,1398],{"emptyLinePlaceholder":898},[1376,25604,25605],{"class":1378,"line":1442},[1376,25606,25607],{},"    for pedido in novos_pedidos:\n",[1376,25609,25610],{"class":1378,"line":1447},[1376,25611,25612],{},"        on_erp_change(\"pedido\", \"updated\", pedido)\n",[1376,25614,25615],{"class":1378,"line":1453},[1376,25616,1398],{"emptyLinePlaceholder":898},[1376,25618,25619],{"class":1378,"line":1459},[1376,25620,25621],{},"    # Atualiza o watermark para o timestamp do último registro processado\n",[1376,25623,25624],{"class":1378,"line":2200},[1376,25625,25626],{},"    if novos_pedidos:\n",[1376,25628,25629],{"class":1378,"line":2208},[1376,25630,25631],{},"        salvar_watermark(novos_pedidos[-1][\"updated_at\"])\n",[528,25633,25634],{},[20,25635,25636,25639,25640,25643],{},[89,25637,25638],{},"Abordagem 2 — CDC (Change Data Capture)",": usa ",[89,25641,25642],{},"Debezium + Kafka Connect","\npara capturar mudanças em tempo real, sem polling. Mais eficiente, mas mais\ncomplexo de configurar.",[710,25645,25646],{},[86,25647,25648,25650],{},[89,25649,7223],{},": 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.",[12,25652,25654],{"id":25653},"passo-4-webhooks-para-tempo-real-passo-4","Passo 4: Webhooks para tempo real {#passo-4}",[86,25656,25657,25658,25661],{},"Para notificações em ",[89,25659,25660],{},"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.",[506,25663,25667],{"className":25664,"code":25665,"language":25666,"meta":515,"style":515},"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",[513,25668,25669,25674,25679,25684,25688,25693,25698,25703,25708,25713,25717,25721,25726,25731,25736,25740,25745,25750,25755],{"__ignoreMap":515},[1376,25670,25671],{"class":1378,"line":1379},[1376,25672,25673],{},"\u002F\u002F Node.js — endpoint que recebe webhook do sistema moderno\n",[1376,25675,25676],{"class":1378,"line":984},[1376,25677,25678],{},"app.post('\u002Fwebhooks\u002Ferp', async (req, res) => {\n",[1376,25680,25681],{"class":1378,"line":981},[1376,25682,25683],{},"  const { event, entity, id, data } = req.body;\n",[1376,25685,25686],{"class":1378,"line":1395},[1376,25687,1398],{"emptyLinePlaceholder":898},[1376,25689,25690],{"class":1378,"line":1401},[1376,25691,25692],{},"  \u002F\u002F 1. VALIDAR ASSINATURA (segurança obrigatória!)\n",[1376,25694,25695],{"class":1378,"line":1407},[1376,25696,25697],{},"  \u002F\u002F Verifica que o webhook realmente veio de quem diz ter vindo\n",[1376,25699,25700],{"class":1378,"line":1413},[1376,25701,25702],{},"  \u002F\u002F usando HMAC-SHA256 (uma assinatura criptográfica)\n",[1376,25704,25705],{"class":1378,"line":1419},[1376,25706,25707],{},"  if (!verifySignature(req.headers['x-signature'], req.body)) {\n",[1376,25709,25710],{"class":1378,"line":1424},[1376,25711,25712],{},"    return res.status(401).send('Invalid signature');\n",[1376,25714,25715],{"class":1378,"line":1430},[1376,25716,19253],{},[1376,25718,25719],{"class":1378,"line":1436},[1376,25720,1398],{"emptyLinePlaceholder":898},[1376,25722,25723],{"class":1378,"line":1442},[1376,25724,25725],{},"  \u002F\u002F 2. COLOCAR NA FILA PARA PROCESSAMENTO ASSÍNCRRONO\n",[1376,25727,25728],{"class":1378,"line":1447},[1376,25729,25730],{},"  \u002F\u002F Não processa aqui — responde 200 rápido e processa depois\n",[1376,25732,25733],{"class":1378,"line":1453},[1376,25734,25735],{},"  await queue.add('erp_event', { event, entity, id, data });\n",[1376,25737,25738],{"class":1378,"line":1459},[1376,25739,1398],{"emptyLinePlaceholder":898},[1376,25741,25742],{"class":1378,"line":2200},[1376,25743,25744],{},"  \u002F\u002F 3. RESPONDER 200 IMEDIATAMENTE\n",[1376,25746,25747],{"class":1378,"line":2208},[1376,25748,25749],{},"  \u002F\u002F Não faz o ERP esperar o processamento terminar\n",[1376,25751,25752],{"class":1378,"line":2219},[1376,25753,25754],{},"  res.status(200).send('OK');\n",[1376,25756,25757],{"class":1378,"line":2227},[1376,25758,25759],{},"});\n",[710,25761,25762],{},[86,25763,25764,25767],{},[89,25765,25766],{},"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.",[86,25769,25770],{},[89,25771,25772],{},"Padrões essenciais de webhook:",[528,25774,25775,25781,25787,25793],{},[20,25776,25777,25780],{},[89,25778,25779],{},"Sempre responder 200 em menos de 5 segundos",": não bloquear quem chama.\nSe precisar processar algo demorado, coloque em fila e responda 200.",[20,25782,25783,25786],{},[89,25784,25785],{},"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)",[20,25788,25789,25792],{},[89,25790,25791],{},"Implementar retry exponencial no envio",": se o receptor não responder\n200, tentar novamente em 1min, 5min, 30min, 2h...",[20,25794,25795,25797],{},[89,25796,24551],{},": mesmo evento enviado 2x não deve criar duplicata",[710,25799,25800],{},[86,25801,25802,25804],{},[89,25803,7223],{},": 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.",[12,25806,25808],{"id":25807},"passo-5-observabilidade-passo-5","Passo 5: Observabilidade {#passo-5}",[86,25810,25811,25813],{},[89,25812,6438],{}," é 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.",[710,25815,25816],{},[86,25817,25818,25820],{},[89,25819,3011],{},": 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.",[283,25822,25824],{"id":25823},"_1-logging-estruturado","1. Logging estruturado",[86,25826,25827,25828,25831],{},"Cada mensagem deve ser logada com um ",[89,25829,25830],{},"correlation ID"," (ID de correlação)\nque permite rastrear uma mensagem do início ao fim:",[506,25833,25835],{"className":16949,"code":25834,"language":16951,"meta":515,"style":515},"{\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",[513,25836,25837,25842,25854,25866,25878,25890,25902,25914,25926,25938,25948],{"__ignoreMap":515},[1376,25838,25839],{"class":1378,"line":1379},[1376,25840,25841],{"class":1671},"{\n",[1376,25843,25844,25847,25849,25852],{"class":1378,"line":984},[1376,25845,25846],{"class":1770},"  \"correlation_id\"",[1376,25848,1679],{"class":1671},[1376,25850,25851],{"class":1682},"\"pedido-12345-created\"",[1376,25853,17031],{"class":1671},[1376,25855,25856,25859,25861,25864],{"class":1378,"line":981},[1376,25857,25858],{"class":1770},"  \"entity\"",[1376,25860,1679],{"class":1671},[1376,25862,25863],{"class":1682},"\"pedido\"",[1376,25865,17031],{"class":1671},[1376,25867,25868,25871,25873,25876],{"class":1378,"line":1395},[1376,25869,25870],{"class":1770},"  \"action\"",[1376,25872,1679],{"class":1671},[1376,25874,25875],{"class":1682},"\"created\"",[1376,25877,17031],{"class":1671},[1376,25879,25880,25883,25885,25888],{"class":1378,"line":1401},[1376,25881,25882],{"class":1770},"  \"status\"",[1376,25884,1679],{"class":1671},[1376,25886,25887],{"class":1682},"\"success\"",[1376,25889,17031],{"class":1671},[1376,25891,25892,25895,25897,25900],{"class":1378,"line":1407},[1376,25893,25894],{"class":1770},"  \"latency_ms\"",[1376,25896,1679],{"class":1671},[1376,25898,25899],{"class":1770},"142",[1376,25901,17031],{"class":1671},[1376,25903,25904,25907,25909,25912],{"class":1378,"line":1413},[1376,25905,25906],{"class":1770},"  \"timestamp\"",[1376,25908,1679],{"class":1671},[1376,25910,25911],{"class":1682},"\"2026-08-26T10:30:00Z\"",[1376,25913,17031],{"class":1671},[1376,25915,25916,25919,25921,25924],{"class":1378,"line":1419},[1376,25917,25918],{"class":1770},"  \"source\"",[1376,25920,1679],{"class":1671},[1376,25922,25923],{"class":1682},"\"erp_adapter\"",[1376,25925,17031],{"class":1671},[1376,25927,25928,25931,25933,25936],{"class":1378,"line":1424},[1376,25929,25930],{"class":1770},"  \"target\"",[1376,25932,1679],{"class":1671},[1376,25934,25935],{"class":1682},"\"api_moderna\"",[1376,25937,17031],{"class":1671},[1376,25939,25940,25943,25945],{"class":1378,"line":1430},[1376,25941,25942],{"class":1770},"  \"message_size_bytes\"",[1376,25944,1679],{"class":1671},[1376,25946,25947],{"class":1770},"1240\n",[1376,25949,25950],{"class":1378,"line":1436},[1376,25951,5170],{"class":1671},[710,25953,25954],{},[86,25955,25956,25959],{},[89,25957,25958],{},"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.",[283,25961,25963],{"id":25962},"_2-métricas-prometheus-grafana","2. Métricas (Prometheus + Grafana)",[86,25965,25966],{},"Métricas agregadas que permitem ver tendências e anomalias:",[528,25968,25969,25975,25981,25987],{},[20,25970,25971,25974],{},[513,25972,25973],{},"integration_messages_total{entity, action, status}"," — total de mensagens\nprocessadas, por entidade\u002Fação\u002Fstatus",[20,25976,25977,25980],{},[513,25978,25979],{},"integration_latency_seconds{entity}"," — tempo de processamento por entidade",[20,25982,25983,25986],{},[513,25984,25985],{},"integration_queue_depth"," — quantas mensagens estão na fila esperando",[20,25988,25989,25992],{},[513,25990,25991],{},"integration_error_rate"," — percentual de mensagens que falharam",[283,25994,25996],{"id":25995},"_3-alertas","3. Alertas",[86,25998,25999],{},"Falhas silenciosas são o pior cenário em integração. Configure alertas\nautomáticos:",[165,26001,26002,26015],{},[168,26003,26004],{},[171,26005,26006,26009,26012],{},[174,26007,26008],{},"Condição",[174,26010,26011],{},"Ação",[174,26013,26014],{},"Urgência",[184,26016,26017,26028,26039,26049],{},[171,26018,26019,26022,26025],{},[189,26020,26021],{},"Erro rate > 5%",[189,26023,26024],{},"Slack\u002FPagerDuty",[189,26026,26027],{},"Alta",[171,26029,26030,26033,26036],{},[189,26031,26032],{},"Queue depth > 1000 mensagens",[189,26034,26035],{},"Slack",[189,26037,26038],{},"Média",[171,26040,26041,26044,26047],{},[189,26042,26043],{},"Latência > 30s",[189,26045,26046],{},"Investigar",[189,26048,26038],{},[171,26050,26051,26054,26057],{},[189,26052,26053],{},"Nenhuma mensagem em 1h",[189,26055,26056],{},"Verificar ERP",[189,26058,26027],{},[283,26060,26062],{"id":26061},"_4-dead-letter-queue-dlq","4. Dead Letter Queue (DLQ)",[86,26064,26065,26066,26069],{},"Mensagens que falham N vezes (ex: 5 tentativas) vão para uma fila separada\nchamada ",[89,26067,26068],{},"DLQ"," (Dead Letter Queue, ou \"Fila de Cartas Mortas\"). Lá, elas\nnão bloqueiam a fila principal e podem ser analisadas manualmente.",[710,26071,26072],{},[86,26073,26074,26077],{},[89,26075,26076],{},"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.",[12,26079,820],{"id":819},[283,26081,26083],{"id":26082},"erro-1-síncrono-em-vez-de-assíncrono","Erro 1: Síncrono em vez de assíncrono",[86,26085,26086,26088],{},[89,26087,4164],{},": 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.",[86,26090,26091,4171],{},[89,26092,4170],{},[506,26094,26096],{"className":3439,"code":26095,"language":3441,"meta":515,"style":515},"# ❌ 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",[513,26097,26098,26103,26108,26113,26118,26123],{"__ignoreMap":515},[1376,26099,26100],{"class":1378,"line":1379},[1376,26101,26102],{},"# ❌ ERRADO: chamada síncrona no trigger do ERP\n",[1376,26104,26105],{"class":1378,"line":984},[1376,26106,26107],{},"def on_pedido_created(pedido):\n",[1376,26109,26110],{"class":1378,"line":981},[1376,26111,26112],{},"    # Chama a API diretamente e espera a resposta\n",[1376,26114,26115],{"class":1378,"line":1395},[1376,26116,26117],{},"    response = requests.post(\"https:\u002F\u002Fapi.moderna.com\u002Fpedidos\", json=pedido)\n",[1376,26119,26120],{"class":1378,"line":1401},[1376,26121,26122],{},"    response.raise_for_status()  # se falhar, o ERP trava\n",[1376,26124,26125],{"class":1378,"line":1407},[1376,26126,26127],{},"    # O usuário do ERP fica esperando isso terminar!\n",[86,26129,26130,4171],{},[89,26131,4182],{},[506,26133,26135],{"className":3439,"code":26134,"language":3441,"meta":515,"style":515},"# ✅ 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",[513,26136,26137,26142,26146,26151,26156,26161],{"__ignoreMap":515},[1376,26138,26139],{"class":1378,"line":1379},[1376,26140,26141],{},"# ✅ CERTO: publica na fila e retorna imediatamente\n",[1376,26143,26144],{"class":1378,"line":984},[1376,26145,26107],{},[1376,26147,26148],{"class":1378,"line":981},[1376,26149,26150],{},"    # Coloca na fila (microsegundos) e retorna\n",[1376,26152,26153],{"class":1378,"line":1395},[1376,26154,26155],{},"    on_erp_change(\"pedido\", \"created\", pedido)\n",[1376,26157,26158],{"class":1378,"line":1401},[1376,26159,26160],{},"    # O ERP continua funcionando normalmente\n",[1376,26162,26163],{"class":1378,"line":1407},[1376,26164,26165],{},"    # A API moderna será chamada assíncronamente pelo consumer\n",[283,26167,26169],{"id":26168},"erro-2-sem-idempotência","Erro 2: Sem idempotência",[86,26171,26172,26174,26175,26178],{},[89,26173,4164],{},": Uma mensagem é processada, mas o ",[513,26176,26177],{},"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.",[86,26180,26181,4171],{},[89,26182,4170],{},[506,26184,26186],{"className":3439,"code":26185,"language":3441,"meta":515,"style":515},"# ❌ 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",[513,26187,26188,26193,26197,26201,26206,26211,26216],{"__ignoreMap":515},[1376,26189,26190],{"class":1378,"line":1379},[1376,26191,26192],{},"# ❌ ERRADO: sem verificação de idempotência\n",[1376,26194,26195],{"class":1378,"line":984},[1376,26196,25145],{},[1376,26198,26199],{"class":1378,"line":981},[1376,26200,25172],{},[1376,26202,26203],{"class":1378,"line":1395},[1376,26204,26205],{},"    # Processa direto, sem verificar se já processou\n",[1376,26207,26208],{"class":1378,"line":1401},[1376,26209,26210],{},"    api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n",[1376,26212,26213],{"class":1378,"line":1407},[1376,26214,26215],{},"    ch.basic_ack(method.delivery_tag)\n",[1376,26217,26218],{"class":1378,"line":1413},[1376,26219,26220],{},"    # Se a mensagem chegar 2x, cria 2 registros!\n",[86,26222,26223,4171],{},[89,26224,4182],{},[506,26226,26228],{"className":3439,"code":26227,"language":3441,"meta":515,"style":515},"# ✅ 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",[513,26229,26230,26235,26239,26243,26247,26252,26257,26262,26266,26270,26274,26279],{"__ignoreMap":515},[1376,26231,26232],{"class":1378,"line":1379},[1376,26233,26234],{},"# ✅ CERTO: verificar idempotência antes de processar\n",[1376,26236,26237],{"class":1378,"line":984},[1376,26238,25145],{},[1376,26240,26241],{"class":1378,"line":981},[1376,26242,25172],{},[1376,26244,26245],{"class":1378,"line":1395},[1376,26246,1398],{"emptyLinePlaceholder":898},[1376,26248,26249],{"class":1378,"line":1401},[1376,26250,26251],{},"    # Verifica no banco se este ID já foi processado\n",[1376,26253,26254],{"class":1378,"line":1407},[1376,26255,26256],{},"    if ja_processado(message[\"id\"]):\n",[1376,26258,26259],{"class":1378,"line":1413},[1376,26260,26261],{},"        ch.basic_ack(method.delivery_tag)  # confirma e ignora\n",[1376,26263,26264],{"class":1378,"line":1419},[1376,26265,25211],{},[1376,26267,26268],{"class":1378,"line":1424},[1376,26269,1398],{"emptyLinePlaceholder":898},[1376,26271,26272],{"class":1378,"line":1430},[1376,26273,26210],{},[1376,26275,26276],{"class":1378,"line":1436},[1376,26277,26278],{},"    marcar_como_processado(message[\"id\"])  # registra no banco\n",[1376,26280,26281],{"class":1378,"line":1442},[1376,26282,26215],{},[283,26284,26286],{"id":26285},"erro-3-polling-agressivo","Erro 3: Polling agressivo",[86,26288,26289,26291],{},[89,26290,4164],{},": 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.",[86,26293,26294,4171],{},[89,26295,4170],{},[506,26297,26299],{"className":3439,"code":26298,"language":3441,"meta":515,"style":515},"# ❌ 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",[513,26300,26301,26306,26311,26316,26321],{"__ignoreMap":515},[1376,26302,26303],{"class":1378,"line":1379},[1376,26304,26305],{},"# ❌ ERRADO: polling a cada 1 segundo\n",[1376,26307,26308],{"class":1378,"line":984},[1376,26309,26310],{},"while True:\n",[1376,26312,26313],{"class":1378,"line":981},[1376,26314,26315],{},"    novos = erp_db.execute(\"SELECT * FROM TBL_PEDIDO WHERE updated_at > ?\", [last_check])\n",[1376,26317,26318],{"class":1378,"line":1395},[1376,26319,26320],{},"    processar(novos)\n",[1376,26322,26323],{"class":1378,"line":1401},[1376,26324,26325],{},"    time.sleep(1)  # muito agressivo!\n",[86,26327,26328,4171],{},[89,26329,4182],{},[506,26331,26333],{"className":3439,"code":26332,"language":3441,"meta":515,"style":515},"# ✅ 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",[513,26334,26335,26340,26345,26349,26353,26357,26362,26366,26371],{"__ignoreMap":515},[1376,26336,26337],{"class":1378,"line":1379},[1376,26338,26339],{},"# ✅ CERTO: intervalo razoável (1-5 min) ou use CDC\n",[1376,26341,26342],{"class":1378,"line":984},[1376,26343,26344],{},"# Opção 1: polling a cada 2 minutos (aceitável para a maioria dos casos)\n",[1376,26346,26347],{"class":1378,"line":981},[1376,26348,26310],{},[1376,26350,26351],{"class":1378,"line":1395},[1376,26352,26315],{},[1376,26354,26355],{"class":1378,"line":1401},[1376,26356,26320],{},[1376,26358,26359],{"class":1378,"line":1407},[1376,26360,26361],{},"    time.sleep(120)  # 2 minutos\n",[1376,26363,26364],{"class":1378,"line":1413},[1376,26365,1398],{"emptyLinePlaceholder":898},[1376,26367,26368],{"class":1378,"line":1419},[1376,26369,26370],{},"# Opção 2 (melhor): CDC com Debezium — captura mudanças em tempo real\n",[1376,26372,26373],{"class":1378,"line":1424},[1376,26374,26375],{},"# sem polling. Configura uma vez e esquece.\n",[283,26377,26379],{"id":26378},"erro-4-sem-tratamento-de-erro-mensagens-se-perdem","Erro 4: Sem tratamento de erro — mensagens se perdem",[86,26381,26382,26384,26385,26387],{},[89,26383,4164],{},": A API moderna retorna erro 500 (servidor indisponível).\nO código não trata o erro, a mensagem é confirmada (",[513,26386,26177],{},") e perdida.\nO pedido existe no ERP mas não no sistema moderno — inconsistência silenciosa.",[86,26389,26390,4171],{},[89,26391,4170],{},[506,26393,26395],{"className":3439,"code":26394,"language":3441,"meta":515,"style":515},"# ❌ 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",[513,26396,26397,26402,26406,26410,26414,26419,26424,26429],{"__ignoreMap":515},[1376,26398,26399],{"class":1378,"line":1379},[1376,26400,26401],{},"# ❌ ERRADO: confirma a mensagem mesmo se a API falhar\n",[1376,26403,26404],{"class":1378,"line":984},[1376,26405,25145],{},[1376,26407,26408],{"class":1378,"line":981},[1376,26409,25172],{},[1376,26411,26412],{"class":1378,"line":1395},[1376,26413,4398],{},[1376,26415,26416],{"class":1378,"line":1401},[1376,26417,26418],{},"        api.post(f\"\u002F{message['entity']}\", json=message[\"data\"])\n",[1376,26420,26421],{"class":1378,"line":1407},[1376,26422,26423],{},"    except Exception:\n",[1376,26425,26426],{"class":1378,"line":1413},[1376,26427,26428],{},"        pass  # ignora o erro!\n",[1376,26430,26431],{"class":1378,"line":1419},[1376,26432,26433],{},"    ch.basic_ack(method.delivery_tag)  # confirma mesmo assim — mensagem perdida!\n",[86,26435,26436,4171],{},[89,26437,4182],{},[506,26439,26441],{"className":3439,"code":26440,"language":3441,"meta":515,"style":515},"# ✅ 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",[513,26442,26443,26448,26452,26456,26460,26464,26469,26473,26477,26482,26487,26492],{"__ignoreMap":515},[1376,26444,26445],{"class":1378,"line":1379},[1376,26446,26447],{},"# ✅ CERTO: rejeita a mensagem em caso de erro (volta para a fila)\n",[1376,26449,26450],{"class":1378,"line":984},[1376,26451,25145],{},[1376,26453,26454],{"class":1378,"line":981},[1376,26455,25172],{},[1376,26457,26458],{"class":1378,"line":1395},[1376,26459,4398],{},[1376,26461,26462],{"class":1378,"line":1401},[1376,26463,25240],{},[1376,26465,26466],{"class":1378,"line":1407},[1376,26467,26468],{},"        response.raise_for_status()  # lança exceção se status >= 400\n",[1376,26470,26471],{"class":1378,"line":1413},[1376,26472,25206],{},[1376,26474,26475],{"class":1378,"line":1419},[1376,26476,4438],{},[1376,26478,26479],{"class":1378,"line":1424},[1376,26480,26481],{},"        logger.error(f\"Falha ao processar {message['id']}: {e}\")\n",[1376,26483,26484],{"class":1378,"line":1430},[1376,26485,26486],{},"        # Rejeita e coloca de volta na fila para retry\n",[1376,26488,26489],{"class":1378,"line":1436},[1376,26490,26491],{},"        # Após N falhas, vai automaticamente para DLQ\n",[1376,26493,26494],{"class":1378,"line":1442},[1376,26495,25331],{},[283,26497,26499],{"id":26498},"erro-5-mapeamento-rígido-de-campos","Erro 5: Mapeamento rígido de campos",[86,26501,26502,26504,26505,26508],{},[89,26503,4164],{},": O ERP adiciona um campo novo na tabela de pedidos\n(ex: ",[513,26506,26507],{},"vl_desconto","). A integração espera um schema fixo e quebra ao\nencontrar o campo novo.",[86,26510,26511,4171],{},[89,26512,4170],{},[506,26514,26516],{"className":3439,"code":26515,"language":3441,"meta":515,"style":515},"# ❌ 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",[513,26517,26518,26523,26528,26532,26537,26542,26547,26552,26557],{"__ignoreMap":515},[1376,26519,26520],{"class":1378,"line":1379},[1376,26521,26522],{},"# ❌ ERRADO: schema rígido que quebra com qualquer mudança\n",[1376,26524,26525],{"class":1378,"line":984},[1376,26526,26527],{},"def transformar_pedido(erp_row):\n",[1376,26529,26530],{"class":1378,"line":981},[1376,26531,5611],{},[1376,26533,26534],{"class":1378,"line":1395},[1376,26535,26536],{},"        \"id\": erp_row[\"nr_pedido\"],          # se esse campo sumir, quebra\n",[1376,26538,26539],{"class":1378,"line":1401},[1376,26540,26541],{},"        \"data\": erp_row[\"dt_emissao\"],        # se renomear, quebra\n",[1376,26543,26544],{"class":1378,"line":1407},[1376,26545,26546],{},"        \"cliente\": erp_row[\"cd_cliente\"],     # se mudar tipo, quebra\n",[1376,26548,26549],{"class":1378,"line":1413},[1376,26550,26551],{},"        \"total\": erp_row[\"vl_total\"],\n",[1376,26553,26554],{"class":1378,"line":1419},[1376,26555,26556],{},"        # não há como receber campos novos sem mudar o código\n",[1376,26558,26559],{"class":1378,"line":1424},[1376,26560,5626],{},[86,26562,26563,4171],{},[89,26564,4182],{},[506,26566,26568],{"className":3439,"code":26567,"language":3441,"meta":515,"style":515},"# ✅ 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",[513,26569,26570,26575,26579,26584,26589,26594,26599,26604,26608,26613,26618,26623],{"__ignoreMap":515},[1376,26571,26572],{"class":1378,"line":1379},[1376,26573,26574],{},"# ✅ CERTO: schema flexível com campos opcionais\n",[1376,26576,26577],{"class":1378,"line":984},[1376,26578,26527],{},[1376,26580,26581],{"class":1378,"line":981},[1376,26582,26583],{},"    result = {\n",[1376,26585,26586],{"class":1378,"line":1395},[1376,26587,26588],{},"        \"id\": erp_row.get(\"nr_pedido\"),\n",[1376,26590,26591],{"class":1378,"line":1401},[1376,26592,26593],{},"        \"data\": erp_row.get(\"dt_emissao\"),\n",[1376,26595,26596],{"class":1378,"line":1407},[1376,26597,26598],{},"        \"cliente\": erp_row.get(\"cd_cliente\"),\n",[1376,26600,26601],{"class":1378,"line":1413},[1376,26602,26603],{},"        \"total\": erp_row.get(\"vl_total\"),\n",[1376,26605,26606],{"class":1378,"line":1419},[1376,26607,5626],{},[1376,26609,26610],{"class":1378,"line":1424},[1376,26611,26612],{},"    # Campos extras do ERP são preservados (não quebra se ERP adicionar campos)\n",[1376,26614,26615],{"class":1378,"line":1430},[1376,26616,26617],{},"    extras = {k: v for k, v in erp_row.items() if k not in result}\n",[1376,26619,26620],{"class":1378,"line":1436},[1376,26621,26622],{},"    result[\"_extras\"] = extras  # campos não mapeados ficam aqui\n",[1376,26624,26625],{"class":1378,"line":1442},[1376,26626,26627],{},"    return result\n",[283,26629,26631],{"id":26630},"erro-6-sem-monitoramento","Erro 6: Sem monitoramento",[86,26633,26634,26636],{},[89,26635,4164],{},": 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.",[86,26638,26639,26641],{},[89,26640,4182],{},": Monitoramento proativo é obrigatório. Configure alertas\npara:",[528,26643,26644,26647,26650,26653],{},[20,26645,26646],{},"Erro rate > 5% → alerta imediato",[20,26648,26649],{},"Nenhuma mensagem processada em 1h → verificar se ERP\u002Fmiddleware está vivo",[20,26651,26652],{},"Queue depth crescendo → consumer pode estar travado",[20,26654,26655],{},"Latência aumentando → possível degradação",[12,26657,26659],{"id":26658},"caso-real-e-commerce-que-integrou-erp-em-6-semanas-caso-real","Caso real: e-commerce que integrou ERP em 6 semanas {#caso-real}",[86,26661,26662],{},"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.",[86,26664,26665,4171],{},[89,26666,26667],{},"Problemas",[528,26669,26670,26673,26676,26679],{},[20,26671,26672],{},"Estoque desatualizado: clientes compravam produtos sem estoque",[20,26674,26675],{},"Preços desincronizados: preço promocional na VTEX não refletia no ERP",[20,26677,26678],{},"1 funcionário full-time só fazendo export\u002Fimport manual",[20,26680,26681],{},"Erros frequentes: planilha errada, importação duplicada",[86,26683,26684,26686],{},[89,26685,8493],{}," (6 semanas):",[17,26688,26689,26695,26701,26707,26713],{},[20,26690,26691,26694],{},[89,26692,26693],{},"Semana 1-2",": Mapeamento de entidades (produto, estoque, preço, pedido)",[20,26696,26697,26700],{},[89,26698,26699],{},"Semana 2-3",": Adapter ERP lendo mudanças via trigger no SQL Server",[20,26702,26703,26706],{},[89,26704,26705],{},"Semana 3-4",": Middleware + RabbitMQ + consumer enviando para VTEX API",[20,26708,26709,26712],{},[89,26710,26711],{},"Semana 4-5",": Webhook da VTEX para enviar pedidos de volta ao ERP",[20,26714,26715,26718],{},[89,26716,26717],{},"Semana 5-6",": Observabilidade (logs, métricas, alertas) + testes",[86,26720,26721,4171],{},[89,26722,26723],{},"Resultados",[528,26725,26726,26729,26732,26735,26738],{},[20,26727,26728],{},"Sincronização em tempo real (\u003C 30 segundos do ERP → e-commerce)",[20,26730,26731],{},"Estoque sempre atualizado: vendas de produtos sem estoque: 0",[20,26733,26734],{},"1 funcionário realocado para tarefas de maior valor",[20,26736,26737],{},"Erros de sincronização: reduzidos em 98%",[20,26739,26740],{},"ROI em 2 meses (economia de salário + redução de chargebacks)",[86,26742,26743,26745],{},[89,26744,4814],{},": A integração bem feita não é apenas técnica — ela resolve\nproblemas de negócio reais (estoque desatualizado = cliente frustrado =\nchargeback = prejuízo).",[968,26747],{},[86,26749,26750,26753,26754,522],{},[89,26751,26752],{},"Precisa integrar seu ERP com sistemas modernos?"," A Inicialize Tec\nimplementa integrações robustas com middleware, filas e observabilidade.\n",[23,26755,2881],{"href":978},[2883,26757,26758],{},"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":515,"searchDepth":981,"depth":981,"links":26760},[26761,26762,26763,26771,26772,26773,26776,26780,26781,26787,26795],{"id":14,"depth":984,"text":15},{"id":24320,"depth":984,"text":24321},{"id":24402,"depth":984,"text":24403,"children":26764},[26765,26766,26767,26768,26769,26770],{"id":24409,"depth":981,"text":24410},{"id":24392,"depth":981,"text":24498},{"id":24511,"depth":981,"text":24512},{"id":24536,"depth":981,"text":24537},{"id":24550,"depth":981,"text":24551},{"id":24564,"depth":981,"text":24565},{"id":6283,"depth":984,"text":6284},{"id":24626,"depth":984,"text":24627},{"id":24882,"depth":984,"text":24883,"children":26774},[26775],{"id":24922,"depth":981,"text":24923},{"id":25342,"depth":984,"text":25343,"children":26777},[26778,26779],{"id":25357,"depth":981,"text":25358},{"id":25530,"depth":981,"text":25531},{"id":25653,"depth":984,"text":25654},{"id":25807,"depth":984,"text":25808,"children":26782},[26783,26784,26785,26786],{"id":25823,"depth":981,"text":25824},{"id":25962,"depth":981,"text":25963},{"id":25995,"depth":981,"text":25996},{"id":26061,"depth":981,"text":26062},{"id":819,"depth":984,"text":820,"children":26788},[26789,26790,26791,26792,26793,26794],{"id":26082,"depth":981,"text":26083},{"id":26168,"depth":981,"text":26169},{"id":26285,"depth":981,"text":26286},{"id":26378,"depth":981,"text":26379},{"id":26498,"depth":981,"text":26499},{"id":26630,"depth":981,"text":26631},{"id":26658,"depth":984,"text":26659},"Como integrar sistemas legados (ERP) com APIs modernas. Middleware, filas, webhooks e observabilidade — com exemplos em Python e Node.js.",[26798,24410,26799,24392,26800,24377,24528],"integrar ERP","sistema legado","integração de sistemas",{},"\u002Fintegracoes\u002Fintegrar-erp-com-api-passo-a-passo",{"title":24257,"description":26796},"integrar-erp-com-api-passo-a-passo","integracoes\u002Fintegrar-erp-com-api-passo-a-passo","9lodk1bqeQWoPPamyKdbr-BrPSQGh0sFoYukOlSYYfo",{"id":26808,"title":26809,"author":7,"body":26810,"category":28085,"date":1028,"description":28086,"extension":1030,"keywords":28087,"meta":28091,"navigation":898,"path":28092,"seo":28093,"slug":28094,"stem":28095,"updated":1028,"__hash__":28096},"content\u002Fsistemas\u002Farquitetura-de-dados-tempo-real.md","Arquitetura de dados em tempo real: streaming e CDC",{"type":9,"value":26811,"toc":28052},[26812,26814,26889,26893,26906,26913,26916,26920,26923,26937,26944,27022,27030,27034,27047,27054,27080,27084,27089,27233,27244,27248,27265,27269,27272,27278,27282,27285,27289,27295,27299,27310,27314,27317,27321,27326,27329,27393,27396,27407,27411,27420,27423,27449,27498,27501,27505,27613,27620,27624,27631,27635,27655,27659,27680,27693,27700,27704,27707,27711,27726,27732,27736,27743,27746,27750,27753,27758,27764,27770,27776,27781,27785,27789,27803,27807,27821,27886,27888,27960,27964,28039,28041,28049],[12,26813,15],{"id":14},[17,26815,26816,26821,26827,26833,26839,26845,26851,26857,26863,26869,26875,26880,26884],{},[20,26817,26818],{},[23,26819,26820],{"href":25},"O que é streaming de dados",[20,26822,26823],{},[23,26824,26826],{"href":26825},"#batch-vs-streaming","Batch vs streaming: a analogia do correio",[20,26828,26829],{},[23,26830,26832],{"href":26831},"#cdc","CDC — Change Data Capture explicado",[20,26834,26835],{},[23,26836,26838],{"href":26837},"#componentes","Componentes de uma arquitetura de streaming",[20,26840,26841],{},[23,26842,26844],{"href":26843},"#kafka-streams","Kafka Streams: processamento dentro do Kafka",[20,26846,26847],{},[23,26848,26850],{"href":26849},"#flink","Apache Flink: processamento externo e stateful",[20,26852,26853],{},[23,26854,26856],{"href":26855},"#streams-vs-flink","Kafka Streams vs Flink: quando usar cada um",[20,26858,26859],{},[23,26860,26862],{"href":26861},"#windowing","Janelas (windowing) e tempo",[20,26864,26865],{},[23,26866,26868],{"href":26867},"#cqrs","Padrão CQRS + Event Sourcing",[20,26870,26871],{},[23,26872,26874],{"href":26873},"#exemplo","Exemplo prático: agregação em tempo real",[20,26876,26877],{},[23,26878,26879],{"href":22860},"Quando usar (e quando não usar) streaming",[20,26881,26882],{},[23,26883,74],{"href":73},[20,26885,26886],{},[23,26887,26888],{"href":79},"Checklist de arquitetura",[12,26890,26892],{"id":26891},"o-que-é-streaming-de-dados-o-que-e","O que é streaming de dados {#o-que-e}",[86,26894,26895,26898,26899,26902,26903,26905],{},[89,26896,26897],{},"Streaming de dados"," é o processamento de dados ",[89,26900,26901],{},"à medida que eles\nacontecem",", em vez de esperar acumular um lote e processar tudo de uma vez.\nPense numa transmissão de rádio: a música sai ao vivo, segundo a segundo,\ne você escuta conforme ela toca. O modelo tradicional, chamado ",[89,26904,18159],{},"\n(lote), seria mais como um CD: você grava tudo, espera terminar, e só depois\nouve a faixa completa.",[86,26907,26908,26909,26912],{},"Em TI, \"tempo real\" costuma ser um termo elástico. Para um sistema de\npagamento, tempo real pode significar \"menos de 100 milissegundos\". Para um\ndashboard de vendas, \"menos de 5 segundos\" já é tempo real o bastante. O\nponto não é ser instantâneo em termos físicos, e sim ",[89,26910,26911],{},"não esperar o próximo\nbatch agendado"," (que tipicamente roda a cada hora, dia ou noite).",[86,26914,26915],{},"Uma arquitetura de dados em tempo real troca a pergunta clássica do mundo\nbatch — \"qual job roda às 2h da manhã para atualizar a base?\" — por uma\noutra: \"quando o evento chega, qual é o caminho até quem precisa dele?\".\nCada mudança relevante (pedido, pagamento, clique, leitura de sensor) vira um\nevento que flui por um pipeline até ser consumido.",[12,26917,26919],{"id":26918},"batch-vs-streaming-a-analogia-do-correio-batch-vs-streaming","Batch vs streaming: a analogia do correio {#batch-vs-streaming}",[86,26921,26922],{},"Imagine uma empresa que precisa enviar 1.000 cartas por dia:",[528,26924,26925,26931],{},[20,26926,26927,26930],{},[89,26928,26929],{},"Batch"," é juntar todas as 1.000 cartas no fim do dia, amarrar com um\nelástico e levar de uma vez à agência dos correios. Simples, barato, mas\no destinatário só recebe no dia seguinte.",[20,26932,26933,26936],{},[89,26934,26935],{},"Streaming"," é levar cada carta à agência assim que ela fica pronta. O\ndestinatário recebe em horas (ou minutos). Mais trabalho logístico, mas\nmuito mais rápido.",[86,26938,26939,26940,26943],{},"No software, o batch ainda é extremamente comum — e muitas vezes ",[89,26941,26942],{},"é a escolha\ncerta",". Nem tudo precisa de streaming. Mas quando o negócio depende de\ndecisões rápidas (detecção de fraude, alertas de máquinas, dashboards ao vivo,\nrecomendações em tempo real), o batch passa a ser um gargalo.",[165,26945,26946,26956],{},[168,26947,26948],{},[171,26949,26950,26952,26954],{},[174,26951,3229],{},[174,26953,26929],{},[174,26955,26935],{},[184,26957,26958,26969,26980,26990,27001,27011],{},[171,26959,26960,26963,26966],{},[189,26961,26962],{},"Latência típica",[189,26964,26965],{},"Minutos a horas",[189,26967,26968],{},"Milissegundos a segundos",[171,26970,26971,26974,26977],{},[189,26972,26973],{},"Complexidade",[189,26975,26976],{},"Baixa (job agendado)",[189,26978,26979],{},"Alta (infra, estado, ordenação)",[171,26981,26982,26985,26987],{},[189,26983,26984],{},"Custo de infra",[189,26986,20528],{},[189,26988,26989],{},"Médio a alto",[171,26991,26992,26995,26998],{},[189,26993,26994],{},"Tolerância a falhas",[189,26996,26997],{},"Re-roda o job",[189,26999,27000],{},"Checkpoints, replay, idempotência",[171,27002,27003,27005,27008],{},[189,27004,10680],{},[189,27006,27007],{},"ETL noturno, relatório diário",[189,27009,27010],{},"Fraude, alertas, dashboards live",[171,27012,27013,27016,27019],{},[189,27014,27015],{},"Janela de dados",[189,27017,27018],{},"Conjunto fechado e finito",[189,27020,27021],{},"Fluxo contínuo e infinito",[710,27023,27024],{},[86,27025,27026,27029],{},[89,27027,27028],{},"Boa prática",": comece com batch. Só vá para streaming quando o batch\nvisivelmente limitar o negócio. Streaming é poderoso, mas caro em\ncomplexidade operacional.",[12,27031,27033],{"id":27032},"cdc-change-data-capture-explicado-cdc","CDC — Change Data Capture explicado {#cdc}",[86,27035,27036,27038,27039,27042,27043,27046],{},[89,27037,24868],{}," significa ",[501,27040,27041],{},"Change Data Capture",", ou \"captura de mudanças\". É a técnica\nde detectar toda alteração que acontece num banco de dados (insert, update,\ndelete) e publicá-la como um stream de eventos, ",[89,27044,27045],{},"sem precisar mudar a\naplicação"," que usa o banco.",[86,27048,27049,27050,27053],{},"A analogia: imagine um carteiro que, em vez de esperar você escrever uma\ncarta e levá-la até ele, ",[89,27051,27052],{},"espiona"," a sua mesa e, sempre que você escreve\nqualquer coisa nova num caderno, ele copia e entrega a quem quiser ler. Você\nnão precisa se preocupar em \"avisar\" ninguém — ele captura a mudança sozinho.",[86,27055,27056,27057,27060,27061,21017,27064,27067,27068,27071,27072,27075,27076,27079],{},"Como o CDC faz isso na prática? Lendo o ",[89,27058,27059],{},"log de transações"," do banco\n(o ",[501,27062,27063],{},"transaction log",[501,27065,27066],{},"WAL"," no Postgres, ",[501,27069,27070],{},"binlog"," no MySQL, ",[501,27073,27074],{},"redo log"," no\nOracle). Todo banco sério grava cada mudança num log antes de aplicar — é o\nque garante durabilidade. O CDC só lê esse log e transforma cada entrada em\nevento. Por isso é ",[89,27077,27078],{},"baixo impacto"," na aplicação: o log já existe, ele só\nlê.",[283,27081,27083],{"id":27082},"a-ferramenta-padrão-debezium","A ferramenta padrão: Debezium",[86,27085,27086,27088],{},[89,27087,24578],{}," é o conector CDC open source mais usado. Roda sobre Kafka Connect\ne tem conectores para Postgres, MySQL, SQL Server, Oracle, MongoDB e outros.\nCada mudança vira um evento num tópico Kafka, com o \"antes\" e o \"depois\" do\nregistro:",[506,27090,27092],{"className":16949,"code":27091,"language":16951,"meta":515,"style":515},"{\n  \"before\": { \"id\": 7, \"status\": \"PENDENTE\", \"total\": 199.90 },\n  \"after\":  { \"id\": 7, \"status\": \"PAGO\",     \"total\": 199.90 },\n  \"op\": \"u\",\n  \"ts_ms\": 1724666400000,\n  \"source\": { \"db\": \"loja\", \"table\": \"pedidos\", \"lsn\": 12345 }\n}\n",[513,27093,27094,27098,27135,27169,27181,27193,27229],{"__ignoreMap":515},[1376,27095,27096],{"class":1378,"line":1379},[1376,27097,25841],{"class":1671},[1376,27099,27100,27103,27105,27108,27110,27113,27115,27118,27120,27123,27125,27128,27130,27132],{"class":1378,"line":984},[1376,27101,27102],{"class":1770},"  \"before\"",[1376,27104,2259],{"class":1671},[1376,27106,27107],{"class":1770},"\"id\"",[1376,27109,1679],{"class":1671},[1376,27111,27112],{"class":1770},"7",[1376,27114,1327],{"class":1671},[1376,27116,27117],{"class":1770},"\"status\"",[1376,27119,1679],{"class":1671},[1376,27121,27122],{"class":1682},"\"PENDENTE\"",[1376,27124,1327],{"class":1671},[1376,27126,27127],{"class":1770},"\"total\"",[1376,27129,1679],{"class":1671},[1376,27131,17071],{"class":1770},[1376,27133,27134],{"class":1671}," },\n",[1376,27136,27137,27140,27143,27145,27147,27149,27151,27153,27155,27158,27161,27163,27165,27167],{"class":1378,"line":981},[1376,27138,27139],{"class":1770},"  \"after\"",[1376,27141,27142],{"class":1671},":  { ",[1376,27144,27107],{"class":1770},[1376,27146,1679],{"class":1671},[1376,27148,27112],{"class":1770},[1376,27150,1327],{"class":1671},[1376,27152,27117],{"class":1770},[1376,27154,1679],{"class":1671},[1376,27156,27157],{"class":1682},"\"PAGO\"",[1376,27159,27160],{"class":1671},",     ",[1376,27162,27127],{"class":1770},[1376,27164,1679],{"class":1671},[1376,27166,17071],{"class":1770},[1376,27168,27134],{"class":1671},[1376,27170,27171,27174,27176,27179],{"class":1378,"line":1395},[1376,27172,27173],{"class":1770},"  \"op\"",[1376,27175,1679],{"class":1671},[1376,27177,27178],{"class":1682},"\"u\"",[1376,27180,17031],{"class":1671},[1376,27182,27183,27186,27188,27191],{"class":1378,"line":1401},[1376,27184,27185],{"class":1770},"  \"ts_ms\"",[1376,27187,1679],{"class":1671},[1376,27189,27190],{"class":1770},"1724666400000",[1376,27192,17031],{"class":1671},[1376,27194,27195,27197,27199,27202,27204,27207,27209,27212,27214,27217,27219,27222,27224,27227],{"class":1378,"line":1407},[1376,27196,25918],{"class":1770},[1376,27198,2259],{"class":1671},[1376,27200,27201],{"class":1770},"\"db\"",[1376,27203,1679],{"class":1671},[1376,27205,27206],{"class":1682},"\"loja\"",[1376,27208,1327],{"class":1671},[1376,27210,27211],{"class":1770},"\"table\"",[1376,27213,1679],{"class":1671},[1376,27215,27216],{"class":1682},"\"pedidos\"",[1376,27218,1327],{"class":1671},[1376,27220,27221],{"class":1770},"\"lsn\"",[1376,27223,1679],{"class":1671},[1376,27225,27226],{"class":1770},"12345",[1376,27228,2280],{"class":1671},[1376,27230,27231],{"class":1378,"line":1413},[1376,27232,5170],{"class":1671},[86,27234,27235,27236,27239,27240,27243],{},"Com isso, qualquer sistema pode reagir à mudança (",[513,27237,27238],{},"PENDENTE → PAGO",") em\nsegundos, sem a aplicação original publicar nada. Isso é o que torna o CDC\na ",[89,27241,27242],{},"ponte perfeita"," entre o mundo dos bancos relacionais e o mundo do\nstreaming.",[283,27245,27247],{"id":27246},"quando-cdc-brilha","Quando CDC brilha",[528,27249,27250,27253,27256,27259,27262],{},[20,27251,27252],{},"Sincronizar banco operacional com data warehouse \u002F lake (sem ETL noturno).",[20,27254,27255],{},"Manter caches e views materializadas atualizadas (Redis, Elasticsearch).",[20,27257,27258],{},"Replicar dados entre serviços numa migração Strangler Fig.",[20,27260,27261],{},"Auditar mudanças (quem alterou o quê, quando).",[20,27263,27264],{},"Disparar reações (webhook, notificação) a partir de mudanças no banco.",[12,27266,27268],{"id":27267},"componentes-de-uma-arquitetura-de-streaming-componentes","Componentes de uma arquitetura de streaming {#componentes}",[86,27270,27271],{},"Uma arquitetura de streaming típica tem quatro camadas:",[506,27273,27276],{"className":27274,"code":27275,"language":511},[509],"[Fontes]            [Transporte]      [Processamento]      [Destinos]\n Bancos     ──CDC──▶  Kafka   ──▶   Flink \u002F Streams  ──▶  Data lake\n Apps       ──pub──▶           ──▶                    ──▶  Warehouse\n Sensores   ──pub──▶           ──▶                    ──▶  Cache \u002F Busca\n IoT                                    Estado + janelas      Dashboards\n",[513,27277,27275],{"__ignoreMap":515},[283,27279,27281],{"id":27280},"fontes","Fontes",[86,27283,27284],{},"Sistemas que produzem eventos: bancos relacionais (via CDC), aplicações que\npublicam diretamente, sensores\u002FIoT, logs, filas externas.",[283,27286,27288],{"id":27287},"transporte","Transporte",[86,27290,27291,27292,27294],{},"O broker de eventos que recebe, armazena e entrega. ",[89,27293,22993],{}," domina o\nmercado, com Pulsar e Redpanda como alternativas compatíveis. O transporte é\no \"log central\" onde tudo passa.",[283,27296,27298],{"id":27297},"processamento","Processamento",[86,27300,27301,27302,27305,27306,27309],{},"Onde os eventos são transformados, filtrados, agregados, enriquecidos e\njuntados. Aqui entram ",[89,27303,27304],{},"Kafka Streams"," (biblioteca dentro da app) e\n",[89,27307,27308],{},"Apache Flink"," (engine externo stateful). Falaremos deles a seguir.",[283,27311,27313],{"id":27312},"destinos","Destinos",[86,27315,27316],{},"Onde o resultado é consumido: data lake (S3 + Iceberg\u002FHudi), warehouse\n(Snowflake, BigQuery), cache (Redis), índice de busca (Elasticsearch),\ndashboards (Grafana, Superset), ou outra aplicação.",[12,27318,27320],{"id":27319},"kafka-streams-processamento-dentro-do-kafka-kafka-streams","Kafka Streams: processamento dentro do Kafka {#kafka-streams}",[86,27322,27323,27325],{},[89,27324,27304],{}," é uma biblioteca Java que faz processamento de streams\ndireto dentro da sua aplicação, sem precisar de um cluster separado de\nprocessamento. Você escreve código Java\u002FKotlin que lê de um tópico, transforma\ne escreve em outro tópico — e a biblioteca cuida de paralelismo, estado local\n(RocksDB), rebalanceamento e tolerância a falhas.",[86,27327,27328],{},"Pense numa cozinha onde o chef prepara o prato na mesma bancada onde recebe\nos ingredientes — sem precisar levar nada para outro andar. Rápido, simples,\npouca infraestrutura. Mas o chef só faz um prato por vez, e se a cozinha\nqueimar, perde-se o que estava em andamento (a menos que haja checkpoint).",[506,27330,27334],{"className":27331,"code":27332,"language":27333,"meta":515,"style":515},"language-java shiki shiki-themes github-light github-dark","\u002F\u002F Kafka Streams: conta pedidos por cliente em janela de 5 minutos\nStreamsBuilder builder = new StreamsBuilder();\n\nKStream\u003CString, OrderEvent> pedidos = builder.stream(\"pedidos-criados\");\n\nKTable\u003CWindowed\u003CString>, Long> contagem = pedidos\n    .groupByKey()                                          \u002F\u002F agrupa por customer_id\n    .windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))\n    .count();                                              \u002F\u002F conta na janela\n\ncontagem.toStream().to(\"pedidos-por-cliente-5min\",\n    Produced.with(WindowedSerdes.timeWindowedSerdeFrom(String.class), Serdes.Long()));\n","java",[513,27335,27336,27341,27346,27350,27355,27359,27364,27369,27374,27379,27383,27388],{"__ignoreMap":515},[1376,27337,27338],{"class":1378,"line":1379},[1376,27339,27340],{},"\u002F\u002F Kafka Streams: conta pedidos por cliente em janela de 5 minutos\n",[1376,27342,27343],{"class":1378,"line":984},[1376,27344,27345],{},"StreamsBuilder builder = new StreamsBuilder();\n",[1376,27347,27348],{"class":1378,"line":981},[1376,27349,1398],{"emptyLinePlaceholder":898},[1376,27351,27352],{"class":1378,"line":1395},[1376,27353,27354],{},"KStream\u003CString, OrderEvent> pedidos = builder.stream(\"pedidos-criados\");\n",[1376,27356,27357],{"class":1378,"line":1401},[1376,27358,1398],{"emptyLinePlaceholder":898},[1376,27360,27361],{"class":1378,"line":1407},[1376,27362,27363],{},"KTable\u003CWindowed\u003CString>, Long> contagem = pedidos\n",[1376,27365,27366],{"class":1378,"line":1413},[1376,27367,27368],{},"    .groupByKey()                                          \u002F\u002F agrupa por customer_id\n",[1376,27370,27371],{"class":1378,"line":1419},[1376,27372,27373],{},"    .windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))\n",[1376,27375,27376],{"class":1378,"line":1424},[1376,27377,27378],{},"    .count();                                              \u002F\u002F conta na janela\n",[1376,27380,27381],{"class":1378,"line":1430},[1376,27382,1398],{"emptyLinePlaceholder":898},[1376,27384,27385],{"class":1378,"line":1436},[1376,27386,27387],{},"contagem.toStream().to(\"pedidos-por-cliente-5min\",\n",[1376,27389,27390],{"class":1378,"line":1442},[1376,27391,27392],{},"    Produced.with(WindowedSerdes.timeWindowedSerdeFrom(String.class), Serdes.Long()));\n",[86,27394,27395],{},"Quando usar Kafka Streams:",[528,27397,27398,27401,27404],{},[20,27399,27400],{},"Aplicação já é Java\u002FJVM.",[20,27402,27403],{},"Processamento de médio porte, sem precisar de joins complexos entre\nmuitos streams.",[20,27405,27406],{},"Time pequeno\u002Fmédio: não quer administrar cluster separado.",[12,27408,27410],{"id":27409},"apache-flink-processamento-externo-e-stateful-flink","Apache Flink: processamento externo e stateful {#flink}",[86,27412,27413,27415,27416,27419],{},[89,27414,27308],{}," é um motor de processamento de streams stateful (com estado)\nde altíssimo desempenho, rodando num cluster próprio. Diferente do Kafka\nStreams (biblioteca embutida), Flink é um ",[89,27417,27418],{},"serviço separado"," que você\nsubmete jobs, como um \"Spark da vida em tempo real\".",[86,27421,27422],{},"Flink brilha em:",[528,27424,27425,27431,27437,27443],{},[20,27426,27427,27430],{},[89,27428,27429],{},"Estado grande"," que não cabe numa máquina só (RocksDB distribuído).",[20,27432,27433,27436],{},[89,27434,27435],{},"Joins de streams"," complexos (stream-stream, stream-table).",[20,27438,27439,27442],{},[89,27440,27441],{},"Processamento baseado em tempo de evento"," (event time) com janelas\nsofisticadas e watermarking.",[20,27444,27445,27448],{},[89,27446,27447],{},"Exactly-once"," end-to-end com checkpoints baratos.",[506,27450,27452],{"className":27331,"code":27451,"language":27333,"meta":515,"style":515},"\u002F\u002F Flink: detecta clientes com mais de 10 pedidos em 1 hora\nDataStream\u003COrderEvent> stream = env\n    .addSource(new FlinkKafkaConsumer\u003C>(\"pedidos-criados\", new OrderDeserializer(), props));\n\nstream\n    .keyBy(OrderEvent::getCustomerId)\n    .timeWindow(Time.hours(1))\n    .process(new CountAndAlertFunction(10))   \u002F\u002F emite alerta se > 10\n    .addSink(new AlertSink());\n",[513,27453,27454,27459,27464,27469,27473,27478,27483,27488,27493],{"__ignoreMap":515},[1376,27455,27456],{"class":1378,"line":1379},[1376,27457,27458],{},"\u002F\u002F Flink: detecta clientes com mais de 10 pedidos em 1 hora\n",[1376,27460,27461],{"class":1378,"line":984},[1376,27462,27463],{},"DataStream\u003COrderEvent> stream = env\n",[1376,27465,27466],{"class":1378,"line":981},[1376,27467,27468],{},"    .addSource(new FlinkKafkaConsumer\u003C>(\"pedidos-criados\", new OrderDeserializer(), props));\n",[1376,27470,27471],{"class":1378,"line":1395},[1376,27472,1398],{"emptyLinePlaceholder":898},[1376,27474,27475],{"class":1378,"line":1401},[1376,27476,27477],{},"stream\n",[1376,27479,27480],{"class":1378,"line":1407},[1376,27481,27482],{},"    .keyBy(OrderEvent::getCustomerId)\n",[1376,27484,27485],{"class":1378,"line":1413},[1376,27486,27487],{},"    .timeWindow(Time.hours(1))\n",[1376,27489,27490],{"class":1378,"line":1419},[1376,27491,27492],{},"    .process(new CountAndAlertFunction(10))   \u002F\u002F emite alerta se > 10\n",[1376,27494,27495],{"class":1378,"line":1424},[1376,27496,27497],{},"    .addSink(new AlertSink());\n",[86,27499,27500],{},"Flink exige mais operação (cluster, JobManager, TaskManagers, checkpoints,\nmonitoramento de backpressure), mas entrega capacidade que o Kafka Streams\nnão atinge em escala e complexidade de estado.",[12,27502,27504],{"id":27503},"kafka-streams-vs-flink-quando-usar-cada-um-streams-vs-flink","Kafka Streams vs Flink: quando usar cada um {#streams-vs-flink}",[165,27506,27507,27517],{},[168,27508,27509],{},[171,27510,27511,27513,27515],{},[174,27512,3229],{},[174,27514,27304],{},[174,27516,27308],{},[184,27518,27519,27529,27540,27550,27560,27571,27582,27592,27603],{},[171,27520,27521,27523,27526],{},[189,27522,8899],{},[189,27524,27525],{},"Biblioteca na app",[189,27527,27528],{},"Cluster separado",[171,27530,27531,27534,27537],{},[189,27532,27533],{},"Linguagem",[189,27535,27536],{},"Java\u002FScala (JVM)",[189,27538,27539],{},"Java\u002FScala\u002FPython (PyFlink)",[171,27541,27542,27544,27547],{},[189,27543,5792],{},[189,27545,27546],{},"Local (RocksDB por instância)",[189,27548,27549],{},"Distribuído (RocksDB + checkpoint)",[171,27551,27552,27554,27557],{},[189,27553,24432],{},[189,27555,27556],{},"Baixa (só sua app)",[189,27558,27559],{},"Alta (cluster próprio)",[171,27561,27562,27565,27568],{},[189,27563,27564],{},"Joins complexos",[189,27566,27567],{},"Limitados",[189,27569,27570],{},"Robustos (stream-stream, stream-table)",[171,27572,27573,27576,27579],{},[189,27574,27575],{},"Event time \u002F watermarking",[189,27577,27578],{},"Básico",[189,27580,27581],{},"Avançado",[171,27583,27584,27587,27589],{},[189,27585,27586],{},"Escala de estado",[189,27588,23048],{},[189,27590,27591],{},"Horizontal, muito grande",[171,27593,27594,27597,27600],{},[189,27595,27596],{},"Latência",[189,27598,27599],{},"Muito baixa (ms)",[189,27601,27602],{},"Baixa (dezenas de ms)",[171,27604,27605,27607,27610],{},[189,27606,1588],{},[189,27608,27609],{},"Agregações e filtros simples em app JVM",[189,27611,27612],{},"Pipelines complexos, estado grande, joins",[86,27614,27615,27616,27619],{},"Regra prática: ",[89,27617,27618],{},"comece com Kafka Streams"," se sua app é JVM e o caso é\nagregação\u002Ffiltro simples. Vá para Flink quando o estado crescer além de uma\nmáquina, quando precisar de joins de múltiplos streams, ou quando o\nprocessamento por tempo de evento ficar sofisticado.",[12,27621,27623],{"id":27622},"janelas-windowing-e-tempo-windowing","Janelas (windowing) e tempo {#windowing}",[86,27625,27626,27627,27630],{},"Streaming traz um problema que o batch não tem: ",[89,27628,27629],{},"quando fechar uma janela de\ncálculo?"," No batch, \"o dia fechou às 23h59\". No streaming, os eventos chegam\no tempo todo, e alguns chegam atrasados (rede lenta, retry, relógio descalibrado).",[283,27632,27634],{"id":27633},"tipos-de-janela","Tipos de janela",[528,27636,27637,27643,27649],{},[20,27638,27639,27642],{},[89,27640,27641],{},"Tumbling",": janelas fixas e não sobrepostas (0-5min, 5-10min, 10-15min).",[20,27644,27645,27648],{},[89,27646,27647],{},"Hopping\u002FSliding",": janelas fixas que avançam de um \"salto\" menor que o\ntamanho (janela de 5min a cada 1min → sobrepostas).",[20,27650,27651,27654],{},[89,27652,27653],{},"Session",": janelas definidas por gaps de inatividade — fecha quando passa\nX minutos sem evento.",[283,27656,27658],{"id":27657},"processing-time-vs-event-time","Processing time vs event time",[528,27660,27661,27667],{},[20,27662,27663,27666],{},[89,27664,27665],{},"Processing time",": a hora em que o sistema processa o evento. Simples,\nmas distorcido por atrasos e backpressure.",[20,27668,27669,27672,27673,27676,27677,27679],{},[89,27670,27671],{},"Event time",": a hora em que o evento ",[89,27674,27675],{},"ocorreu"," na origem (campo\n",[513,27678,23925],{}," do evento). Correta para negócio, mas exige lidar com eventos\natrasados e fora de ordem.",[86,27681,15435,27682,27685,27686,27689,27690,1157],{},[89,27683,27684],{},"watermark"," é o mecanismo do Flink (e de outros) que diz \"até agora\nachamos que já recebemos todos os eventos com timestamp ≤ X\". Eventos que\nchegam depois do watermark são ",[89,27687,27688],{},"late events"," — você decide: descartar,\nredirecionar para um stream de \"side output\", ou aceitar dentro de uma\njanela de tolerância (",[501,27691,27692],{},"allowed lateness",[710,27694,27695],{},[86,27696,27697,27699],{},[89,27698,6471],{},": para dashboards de negócio, event time costuma ser a escolha\ncerta. Para monitoramento de infra, processing time basta.",[12,27701,27703],{"id":27702},"padrão-cqrs-event-sourcing-cqrs","Padrão CQRS + Event Sourcing {#cqrs}",[86,27705,27706],{},"Duas ideias que combinam naturalmente com streaming:",[283,27708,27710],{"id":27709},"cqrs-command-query-responsibility-segregation","CQRS (Command Query Responsibility Segregation)",[86,27712,27713,27714,27717,27718,27721,27722,27725],{},"Separar o modelo de ",[89,27715,27716],{},"escrita"," (commands, otimizado para regras de negócio\ne consistência) do modelo de ",[89,27719,27720],{},"leitura"," (queries, otimizado para acesso\nrápido e shaped para a tela). Em vez de uma única tabela que serve mal para\nos dois lados, você tem tabelas de escrita e ",[89,27723,27724],{},"views materializadas"," para\nleitura, mantidas via stream de eventos.",[506,27727,27730],{"className":27728,"code":27729,"language":511},[509],"[Comando] → [Modelo escrita] → publica evento → [Projeção] → [View leitura]\n[Query]   → [View leitura] (rápida, desnormalizada)\n",[513,27731,27729],{"__ignoreMap":515},[283,27733,27735],{"id":27734},"event-sourcing","Event Sourcing",[86,27737,27738,27739,27742],{},"Guardar ",[89,27740,27741],{},"todos os eventos"," que levaram ao estado atual, em vez de só o\nestado final. O estado é uma projeção do log de eventos. Vantagens: auditoria\ntotal, replay para reconstruir qualquer versão do passado, e novos modelos\nde leitura podem ser criados retroativamente rebobinando o log.",[86,27744,27745],{},"Custo: o log cresce indefinidamente (precisa de snapshots), e a modelagem é\ndiferente do CRUD tradicional. Use quando auditoria\u002Fevolução de modelo\njustificarem — não em todo sistema.",[12,27747,27749],{"id":27748},"exemplo-prático-agregação-em-tempo-real-exemplo","Exemplo prático: agregação em tempo real {#exemplo}",[86,27751,27752],{},"Cenário: um e-commerce quer um dashboard ao vivo de vendas por categoria,\natualizado a cada minuto, sem esperar um job noturno.",[86,27754,27755],{},[89,27756,27757],{},"Arquitetura:",[506,27759,27762],{"className":27760,"code":27761,"language":511},[509],"[Postgres pedidos] ─CDC (Debezium)─▶ [Kafka tópico: pedidos]\n                                            │\n                                            ▼\n                                   [Kafka Streams app]\n                                   agrega por categoria\n                                   em janela tumbling de 1min\n                                            │\n                                            ▼\n                                  [Kafka tópico: vendas-por-cat-1min]\n                                            │\n                              ┌─────────────┴──────────────┐\n                              ▼                            ▼\n                        [Redis] (cache p\u002F dashboard)   [S3\u002FIceberg] (histórico)\n",[513,27763,27761],{"__ignoreMap":515},[86,27765,27766,27769],{},[89,27767,27768],{},"Por que CDC e não a app publicar?"," Porque a app de pedidos é um monolito\nlegado que não sabemos (ou não queremos) modificar. O CDC captura as mudanças\nno banco sem tocar na app — zero risco para o legado.",[86,27771,27772,27775],{},[89,27773,27774],{},"Por que Kafka Streams e não Flink?"," A agregação é simples (group by\ncategoria + sum em janela), a app é JVM, o estado cabe numa máquina. Flink\nseria overengineering aqui.",[86,27777,27778,27780],{},[89,27779,3940],{},": o dashboard mostra vendas por categoria com ~30 segundos de\natraso em vez de \"dados de ontem\". Decisões de marketing podem reagir a uma\npromoção que bomba em tempo real.",[12,27782,27784],{"id":27783},"quando-usar-e-quando-não-usar-streaming-quando-usar","Quando usar (e quando não usar) streaming {#quando-usar}",[283,27786,27788],{"id":27787},"use-streaming-quando","Use streaming quando",[528,27790,27791,27794,27797,27800],{},[20,27792,27793],{},"Decisões dependem de dados recentes (fraude, alertas, pricing dinâmico).",[20,27795,27796],{},"Dashboards precisam ser ao vivo (operações, trading, monitoramento).",[20,27798,27799],{},"Sincronização entre sistemas precisa ser em segundos (não horas).",[20,27801,27802],{},"Event sourcing \u002F CQRS fazem sentido para o domínio.",[283,27804,27806],{"id":27805},"não-use-streaming-quando","Não use streaming quando",[528,27808,27809,27812,27815,27818],{},[20,27810,27811],{},"Relatórios diários\u002Fsemanais bastam — batch é mais barato e simples.",[20,27813,27814],{},"Volume de eventos é baixo — uma fila + cron resolve.",[20,27816,27817],{},"Time não tem maturidade de operação (streaming exige monitoramento de lag,\ncheckpoints, backpressure, rebalanceamento).",[20,27819,27820],{},"Latência de minutos é aceitável — micro-batch (Spark Structured Streaming,\nAirflow com frequência alta) pode ser o meio-termo.",[165,27822,27823,27831],{},[168,27824,27825],{},[171,27826,27827,27829],{},[174,27828,8776],{},[174,27830,7262],{},[184,27832,27833,27841,27849,27857,27864,27871,27878],{},[171,27834,27835,27838],{},[189,27836,27837],{},"ETL noturno para warehouse",[189,27839,27840],{},"Batch (Airflow + dbt)",[171,27842,27843,27846],{},[189,27844,27845],{},"Sincronizar cache a partir do banco",[189,27847,27848],{},"CDC + Kafka",[171,27850,27851,27854],{},[189,27852,27853],{},"Detecção de fraude em pagamentos",[189,27855,27856],{},"Streaming (Flink)",[171,27858,27859,27862],{},[189,27860,27861],{},"Dashboard de vendas ao vivo",[189,27863,27304],{},[171,27865,27866,27869],{},[189,27867,27868],{},"Relatório mensal de faturamento",[189,27870,26929],{},[171,27872,27873,27876],{},[189,27874,27875],{},"Replicação entre serviços (migração)",[189,27877,24868],{},[171,27879,27880,27883],{},[189,27881,27882],{},"Recomendação em tempo real",[189,27884,27885],{},"Streaming + modelo servido",[12,27887,820],{"id":819},[17,27889,27890,27896,27902,27908,27914,27920,27934,27942,27948,27954],{},[20,27891,27892,27895],{},[89,27893,27894],{},"Adotar streaming para tudo",": nem todo dado precisa ser ao vivo. O\ncusto de operar streaming em pipelines que poderiam ser batch é real.",[20,27897,27898,27901],{},[89,27899,27900],{},"Ignorar eventos atrasados",": assumir que eventos chegam em ordem e na\nhora. Em produção, sempre há atraso e desordem — use event time e\nwatermarks.",[20,27903,27904,27907],{},[89,27905,27906],{},"Sem checkpoint\u002Festado persistente",": o job reinicia e perde tudo o que\nestava em memória. Configure checkpoints periodicamente.",[20,27909,27910,27913],{},[89,27911,27912],{},"Backpressure sem monitoramento",": o produtor enche o broker, o\nconsumidor não acompanha, lag explode e ninguém percebe. Alerta de lag é\nobrigatório.",[20,27915,27916,27919],{},[89,27917,27918],{},"CDC sem cuidado com schemas",": mudança de coluna no banco quebra o\nconector e os consumidores. Versionar schema e coordenar mudanças.",[20,27921,27922,27925,27926,27929,27930,27933],{},[89,27923,27924],{},"CDC como substituto de eventos de domínio",": CDC captura mudanças de\n",[89,27927,27928],{},"tabela",", não de ",[89,27931,27932],{},"negócio",". Para lógica de domínio, prefira eventos\nexplícitos publicados pela app.",[20,27935,27936,27939,27940,522],{},[89,27937,27938],{},"Janela de cálculo sem tolerância a late events",": janela fecha cedo\ndemais e perde eventos legítimos. Defina ",[513,27941,27692],{},[20,27943,27944,27947],{},[89,27945,27946],{},"Sem idempotência no destino",": reprocessamento duplica dados no\nwarehouse\u002Flake. Use upserts ou deduplicação por chave de evento.",[20,27949,27950,27953],{},[89,27951,27952],{},"Estado crescente sem TTL\u002Fcompactação",": o RocksDB do processador incha\naté estourar disco. Defina retenção de estado (TTL) ou use janelas.",[20,27955,27956,27959],{},[89,27957,27958],{},"Achar que streaming dispensa batch",": muitos pipelines têm um híbrido\n— streaming para o ao vivo, batch para reconciliação e fechamentos.",[12,27961,27963],{"id":27962},"checklist-de-arquitetura-checklist","Checklist de arquitetura {#checklist}",[528,27965,27967,27973,27979,27985,27991,27997,28003,28009,28015,28021,28027,28033],{"className":27966},[890],[20,27968,27970,27972],{"className":27969},[894],[896,27971],{"disabled":898,"type":899}," Confirmou que o caso precisa de streaming (não batch)",[20,27974,27976,27978],{"className":27975},[894],[896,27977],{"disabled":898,"type":899}," Fontes conectadas via CDC ou publicação direta",[20,27980,27982,27984],{"className":27981},[894],[896,27983],{"disabled":898,"type":899}," Broker com retenção e replica configuradas (3 brokers, RF=3)",[20,27986,27988,27990],{"className":27987},[894],[896,27989],{"disabled":898,"type":899}," Schema registry versionando os eventos",[20,27992,27994,27996],{"className":27993},[894],[896,27995],{"disabled":898,"type":899}," Processador com estado persistente e checkpoints",[20,27998,28000,28002],{"className":27999},[894],[896,28001],{"disabled":898,"type":899}," Event time + watermark + allowed lateness definidos",[20,28004,28006,28008],{"className":28005},[894],[896,28007],{"disabled":898,"type":899}," Janelas adequadas ao problema (tumbling\u002Fhopping\u002Fsession)",[20,28010,28012,28014],{"className":28011},[894],[896,28013],{"disabled":898,"type":899}," Destinos idempotentes (upsert ou dedup por event_id)",[20,28016,28018,28020],{"className":28017},[894],[896,28019],{"disabled":898,"type":899}," Monitoramento de lag, backpressure e taxa de erro",[20,28022,28024,28026],{"className":28023},[894],[896,28025],{"disabled":898,"type":899}," Plano de replay: sabe reprocessar do offset X em caso de bug",[20,28028,28030,28032],{"className":28029},[894],[896,28031],{"disabled":898,"type":899}," Retenção de estado configurada (TTL ou compactação)",[20,28034,28036,28038],{"className":28035},[894],[896,28037],{"disabled":898,"type":899}," Híbrido com batch para reconciliação\u002Ffechamento, se aplicável",[968,28040],{},[86,28042,28043,28046,28047,522],{},[89,28044,28045],{},"Construindo uma arquitetura de dados em tempo real?"," A Inicialize Tec\ndesenha pipelines de streaming com CDC, Kafka e Flink — da captura de\nmudanças no banco até dashboards ao vivo e data lake atualizado em segundos.\n",[23,28048,2881],{"href":978},[2883,28050,28051],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}",{"title":515,"searchDepth":981,"depth":981,"links":28053},[28054,28055,28056,28057,28061,28067,28068,28069,28070,28074,28078,28079,28083,28084],{"id":14,"depth":984,"text":15},{"id":26891,"depth":984,"text":26892},{"id":26918,"depth":984,"text":26919},{"id":27032,"depth":984,"text":27033,"children":28058},[28059,28060],{"id":27082,"depth":981,"text":27083},{"id":27246,"depth":981,"text":27247},{"id":27267,"depth":984,"text":27268,"children":28062},[28063,28064,28065,28066],{"id":27280,"depth":981,"text":27281},{"id":27287,"depth":981,"text":27288},{"id":27297,"depth":981,"text":27298},{"id":27312,"depth":981,"text":27313},{"id":27319,"depth":984,"text":27320},{"id":27409,"depth":984,"text":27410},{"id":27503,"depth":984,"text":27504},{"id":27622,"depth":984,"text":27623,"children":28071},[28072,28073],{"id":27633,"depth":981,"text":27634},{"id":27657,"depth":981,"text":27658},{"id":27702,"depth":984,"text":27703,"children":28075},[28076,28077],{"id":27709,"depth":981,"text":27710},{"id":27734,"depth":981,"text":27735},{"id":27748,"depth":984,"text":27749},{"id":27783,"depth":984,"text":27784,"children":28080},[28081,28082],{"id":27787,"depth":981,"text":27788},{"id":27805,"depth":981,"text":27806},{"id":819,"depth":984,"text":820},{"id":27962,"depth":984,"text":27963},"sistemas-escalaveis","Como construir uma arquitetura de dados que processa eventos em tempo real: streaming, CDC, Kafka Streams e Flink — explicado para iniciantes.",[28088,25660,24868,28089,27304,27308,28090],"streaming de dados","change data capture","arquitetura de dados",{},"\u002Fsistemas\u002Farquitetura-de-dados-tempo-real",{"title":26809,"description":28086},"arquitetura-de-dados-tempo-real","sistemas\u002Farquitetura-de-dados-tempo-real","lbqlbnGYpXbMBEAxQnshd4ETOjwxW4NLf7Mc8w-ceO8",{"id":28098,"title":28099,"author":7,"body":28100,"category":28085,"date":1028,"description":30739,"extension":1030,"keywords":30740,"meta":30746,"navigation":898,"path":30747,"seo":30748,"slug":30749,"stem":30750,"updated":1028,"__hash__":30751},"content\u002Fsistemas\u002Farquitetura-de-microservicos-escalaveis.md","Arquitetura de microserviços escaláveis: guia 2026",{"type":9,"value":28101,"toc":30692},[28102,28104,28160,28164,28174,28189,28192,28229,28233,28323,28327,28334,28338,28370,28374,28394,28405,28412,28416,28419,28430,28437,28477,28483,28489,28493,28504,28511,28540,28545,28549,28560,28566,28586,28590,28601,28611,28619,28622,28636,28642,28650,28654,28658,28669,28694,28702,28706,28717,28738,28844,28848,28859,28865,28885,28999,29007,29011,29021,29028,29036,29040,29120,29134,29145,29149,29152,29175,29179,29183,29200,29203,29476,29484,29486,29548,29556,29564,29566,29713,29720,29724,29729,29740,29743,29747,29910,29916,29924,29926,29944,29949,29986,29990,30001,30010,30016,30024,30028,30097,30104,30108,30161,30187,30189,30193,30198,30202,30208,30212,30218,30222,30227,30231,30266,30270,30302,30306,30311,30315,30321,30325,30331,30335,30340,30345,30430,30434,30439,30443,30478,30482,30558,30562,30566,30580,30590,30594,30597,30617,30623,30649,30654,30674,30679,30681,30689],[12,28103,15],{"id":14},[17,28105,28106,28111,28117,28123,28129,28135,28140,28145,28151,28155],{},[20,28107,28108],{},[23,28109,28110],{"href":10085},"O que são microserviços?",[20,28112,28113],{},[23,28114,28116],{"href":28115},"#quando","Quando (não) usar microserviços",[20,28118,28119],{},[23,28120,28122],{"href":28121},"#padroes","Padrões arquiteturais",[20,28124,28125],{},[23,28126,28128],{"href":28127},"#comunicacao","Comunicação entre serviços",[20,28130,28131],{},[23,28132,28134],{"href":28133},"#dados","Dados em microserviços",[20,28136,28137],{},[23,28138,28139],{"href":1085},"Deploy e orquestração",[20,28141,28142],{},[23,28143,6438],{"href":28144},"#observabilidade",[20,28146,28147],{},[23,28148,28150],{"href":28149},"#numeros","Escala real: números",[20,28152,28153],{},[23,28154,74],{"href":73},[20,28156,28157],{},[23,28158,28159],{"href":2990},"Caso real: fintech que escalou de 5K a 2M usuários",[12,28161,28163],{"id":28162},"o-que-são-microserviços-o-que-sao","O que são microserviços? {#o-que-sao}",[86,28165,28166,28169,28170,28173],{},[89,28167,28168],{},"Microserviços"," são uma forma de organizar um sistema de software em que,\nem vez de construir uma única aplicação grande (chamada ",[89,28171,28172],{},"monolito","), você\ndivide em vários serviços pequenos e independentes, cada um responsável por\numa área específica do negócio.",[710,28175,28176,28184],{},[86,28177,28178,28180,28181,28183],{},[89,28179,3011],{},": Imagine uma cozinha de restaurante. Um ",[89,28182,28172],{}," é como\num cozinheiro que faz tudo: prepara a entrada, o prato principal, a sobremesa,\nlava a louça, atende o garçom. Se ele fica doente, o restaurante inteiro para.",[86,28185,28186,28188],{},[89,28187,28168],{}," são como uma cozinha com estações separadas: uma para\nentradas, uma para grelhados, uma para sobremesas, uma para lavar louça.\nCada estação trabalha independentemente. Se a estação de sobremesas quebra,\no restaurante ainda serve entradas e pratos principais.",[86,28190,28191],{},"Cada microserviço:",[528,28193,28194,28201,28208,28215,28222],{},[20,28195,28196,28197,28200],{},"Tem seu próprio ",[89,28198,28199],{},"banco de dados"," (não compartilha com outros)",[20,28202,28203,28204,28207],{},"Pode ser ",[89,28205,28206],{},"deployado"," (colocado em produção) independentemente",[20,28209,28210,28211,28214],{},"Pode ser escrito em ",[89,28212,28213],{},"linguagens diferentes"," (um em Python, outro em Go)",[20,28216,28217,28218,28221],{},"Pode ",[89,28219,28220],{},"escalar"," (crescer para atender mais usuários) independentemente",[20,28223,28224,28225,28228],{},"Comunica-se com outros serviços via ",[89,28226,28227],{},"rede"," (API, eventos)",[283,28230,28232],{"id":28231},"monolito-vs-microserviços-visão-geral","Monolito vs Microserviços — visão geral",[165,28234,28235,28246],{},[168,28236,28237],{},[171,28238,28239,28241,28244],{},[174,28240,13898],{},[174,28242,28243],{},"Monolito",[174,28245,28168],{},[184,28247,28248,28259,28270,28281,28292,28302,28313],{},[171,28249,28250,28253,28256],{},[189,28251,28252],{},"Estrutura",[189,28254,28255],{},"Uma aplicação, um código",[189,28257,28258],{},"Vários serviços independentes",[171,28260,28261,28264,28267],{},[189,28262,28263],{},"Banco de dados",[189,28265,28266],{},"Um banco compartilhado",[189,28268,28269],{},"Cada serviço tem o seu",[171,28271,28272,28275,28278],{},[189,28273,28274],{},"Deploy",[189,28276,28277],{},"Todo o sistema de uma vez",[189,28279,28280],{},"Cada serviço separadamente",[171,28282,28283,28286,28289],{},[189,28284,28285],{},"Escala",[189,28287,28288],{},"Tudo escala junto",[189,28290,28291],{},"Cada serviço escala na medida",[171,28293,28294,28296,28299],{},[189,28295,26973],{},[189,28297,28298],{},"Baixa no início, alta depois",[189,28300,28301],{},"Alta desde o início",[171,28303,28304,28307,28310],{},[189,28305,28306],{},"Time",[189,28308,28309],{},"Funciona com time pequeno",[189,28311,28312],{},"Precisa de times maiores",[171,28314,28315,28317,28320],{},[189,28316,3281],{},[189,28318,28319],{},"Mudanças afetam tudo",[189,28321,28322],{},"Mudanças isoladas por serviço",[12,28324,28326],{"id":28325},"quando-não-usar-microserviços-quando","Quando (não) usar microserviços {#quando}",[86,28328,28329,28330,28333],{},"Microserviços ",[89,28331,28332],{},"não são bala de prata"," (não resolvem todos os problemas).\nNa verdade, para muitos projetos, um monolito é a escolha certa.",[283,28335,28337],{"id":28336},"use-microserviços-quando","Use microserviços quando:",[528,28339,28340,28346,28352,28358,28364],{},[20,28341,28342,28345],{},[89,28343,28344],{},"Time > 8 pessoas",": a complexidade operacional só se justifica com um\ntime grande o suficiente para manter múltiplos serviços",[20,28347,28348,28351],{},[89,28349,28350],{},"Domínios distintos",": e-commerce, pagamentos e logística têm ciclos de\ndesenvolvimento e necessidades diferentes. Faz sentido separá-los.",[20,28353,28354,28357],{},[89,28355,28356],{},"Escala diferenciada",": o catálogo de produtos recebe 100x mais tráfego\nque o checkout. Separando, você escala só o catálogo.",[20,28359,28360,28363],{},[89,28361,28362],{},"Deploy independente",": times precisam liberar atualizações sem coordenar\ncom outros times",[20,28365,28366,28369],{},[89,28367,28368],{},"Requisitos técnicos diferentes",": o serviço de busca precisa de\nElasticsearch, o de pagamentos precisa de PostgreSQL transacional",[283,28371,28373],{"id":28372},"não-use-microserviços-quando","NÃO use microserviços quando:",[528,28375,28376,28382,28388],{},[20,28377,28378,28381],{},[89,28379,28380],{},"Time \u003C 5 pessoas",": monolito modular (um código organizado em módulos\nbem separados) é mais produtivo e simples de manter",[20,28383,28384,28387],{},[89,28385,28386],{},"MVP \u002F fase inicial",": valide o produto antes de otimizar a arquitetura.\n\"Primeiro faça funcionar, depois faça rápido, depois faça bonito.\"",[20,28389,28390,28393],{},[89,28391,28392],{},"\"Porque o Netflix usa\"",": você não é o Netflix (ainda). O Netflix tem\nmilhares de engenheiros e bilhões de requisições. Sua startup com 500\nusuários não precisa da mesma arquitetura.",[710,28395,28396],{},[86,28397,28398,28400,28401,28404],{},[89,28399,8214],{},": Comece com ",[89,28402,28403],{},"monolito modular"," (um código só, mas\norganizado em módulos bem separados). Extraia microserviços apenas quando\num domínio específico tiver requisitos de escala, deploy ou time que\njustifiquem a separação.",[710,28406,28407],{},[86,28408,28409,28411],{},[89,28410,7223],{},": O CTO lê um artigo sobre \"Netflix migrou para\nmicroserviços\" e decide migrar a startup com 3 desenvolvedores e 200\nusuários. Resultado: 6 meses migrando, nenhuma feature nova lançada,\ne o sistema ficou mais lento e mais difícil de manter.",[12,28413,28415],{"id":28414},"padrões-arquiteturais-padroes","Padrões arquiteturais {#padroes}",[283,28417,6300],{"id":28418},"api-gateway",[710,28420,28421],{},[86,28422,28423,28425,28426,28429],{},[89,28424,3011],{},": O API Gateway é como a ",[89,28427,28428],{},"recepção de um prédio comercial",".\nTodos os visitantes entram pela recepção. A recepcionista verifica a\nidentidade (autenticação), diz para qual sala ir (roteamento), limita\nquantas pessoas entram por hora (rate limiting) e pode dar informações\nde múltiplos andares em uma única visita (agregação).",[86,28431,28432,28433,28436],{},"O API Gateway é o ",[89,28434,28435],{},"ponto único de entrada"," para todos os clientes. Ele é\nresponsável por:",[528,28438,28439,28453,28459,28465,28471],{},[20,28440,28441,28444,28445,28448,28449,28452],{},[89,28442,28443],{},"Roteamento",": cada request vai para o serviço certo\n(",[513,28446,28447],{},"GET \u002Fpedidos"," → serviço de pedidos, ",[513,28450,28451],{},"GET \u002Fprodutos"," → serviço de catálogo)",[20,28454,28455,28458],{},[89,28456,28457],{},"Autenticação\u002FAutorização",": verifica quem é o usuário e o que pode fazer",[20,28460,28461,28464],{},[89,28462,28463],{},"Rate limiting",": limita quantas requisições um cliente pode fazer por minuto",[20,28466,28467,28470],{},[89,28468,28469],{},"Transformação de request\u002Fresponse",": converte formatos se necessário",[20,28472,28473,28476],{},[89,28474,28475],{},"Agregação de múltiplos serviços",": combina respostas de vários serviços\nem uma única resposta para o cliente",[506,28478,28481],{"className":28479,"code":28480,"language":511},[509],"[Cliente] → [API Gateway] → [Serviço de Pedidos]\n                          → [Serviço de Catálogo]\n                          → [Serviço de Pagamentos]\n",[513,28482,28480],{"__ignoreMap":515},[86,28484,28485,28488],{},[89,28486,28487],{},"Ferramentas populares",": Kong, AWS API Gateway, Traefik, Envoy",[283,28490,28492],{"id":28491},"service-mesh","Service Mesh",[710,28494,28495],{},[86,28496,28497,28499,28500,28503],{},[89,28498,3011],{},": Service Mesh é como um ",[89,28501,28502],{},"sistema de rádio interno"," do\nprédio. Cada andar (serviço) se comunica com outros andares pelo rádio,\nsem precisar saber o número da sala. O sistema de rádio garante que a\ncomunicação é segura (criptografada), rastreável (gravada) e resiliente\n(se uma sala não responde, tenta de novo automaticamente).",[86,28505,28506,28507,28510],{},"Service Mesh lida com a comunicação ",[89,28508,28509],{},"service-to-service"," (entre serviços,\nnão entre cliente e serviço):",[528,28512,28513,28519,28525,28531],{},[20,28514,28515,28518],{},[89,28516,28517],{},"mTLS"," (mutual TLS): criptografia em trânsito entre serviços",[20,28520,28521,28524],{},[89,28522,28523],{},"Retry\u002Fcircuit breaker",": se um serviço falha, tenta de novo; se continua\nfalhando, \"desarma o disjuntor\" e para de tentar (explico abaixo)",[20,28526,28527,28530],{},[89,28528,28529],{},"Distributed tracing",": rastreia uma requisição através de múltiplos serviços",[20,28532,28533,28536,28537,13125],{},[89,28534,28535],{},"Traffic splitting",": divide tráfego entre versões (ex: 95% v1, 5% v2\npara testar nova versão — chamado ",[89,28538,28539],{},"canary deploy",[86,28541,28542,28544],{},[89,28543,5786],{},": Istio, Linkerd, Consul Connect",[283,28546,28548],{"id":28547},"backend-for-frontend-bff","Backend for Frontend (BFF)",[710,28550,28551],{},[86,28552,28553,28555,28556,28559],{},[89,28554,3011],{},": Em vez de uma recepção única para todos (API Gateway padrão),\no BFF é como ter ",[89,28557,28558],{},"recepções especializadas",": uma para visitantes, uma\npara entregadores, uma para fornecedores. Cada uma atende as necessidades\nespecíficas de cada tipo de visitante.",[86,28561,1160,28562,28565],{},[89,28563,28564],{},"BFF"," (Backend for Frontend) é um gateway especializado por tipo de\ncliente:",[528,28567,28568,28574,28580],{},[20,28569,28570,28573],{},[89,28571,28572],{},"Web BFF",": agrega dados para o dashboard completo do navegador",[20,28575,28576,28579],{},[89,28577,28578],{},"Mobile BFF",": responde só o essencial (menos dados = menos consumo de\nbateria e dados móveis)",[20,28581,28582,28585],{},[89,28583,28584],{},"Partner BFF",": expõe apenas endpoints públicos para parceiros\u002Fintegradores",[283,28587,28589],{"id":28588},"saga-pattern","Saga Pattern",[710,28591,28592],{},[86,28593,28594,28596,28597,28600],{},[89,28595,3011],{},": Saga é como uma ",[89,28598,28599],{},"corrida de revezamento",". Em vez de uma\núnica transação que faz tudo (o que seria impossível entre serviços\ndiferentes), cada serviço faz sua parte e passa o bastão para o próximo.\nSe um corredor tropeça, os anteriores precisam \"desfazer\" o que fizeram\n(compensação).",[86,28602,28603,28606,28607,28610],{},[89,28604,28605],{},"Saga"," é o padrão para ",[89,28608,28609],{},"transações distribuídas"," — quando uma operação\nprecisa mudar dados em múltiplos serviços e não há como fazer tudo em uma\ntransação única (ACID).",[710,28612,28613],{},[86,28614,28615,28618],{},[89,28616,28617],{},"Por que não transação ACID?",": ACID (Atomicidade, Consistência, Isolamento,\nDurabilidade) garante que tudo acontece ou nada acontece. Mas quando os dados\nestão em bancos diferentes (cada microserviço tem o seu), não dá para fazer\numa transação que englobe todos. Saga resolve isso com compensação.",[86,28620,28621],{},"Dois estilos de Saga:",[528,28623,28624,28630],{},[20,28625,28626,28629],{},[89,28627,28628],{},"Choreography"," (coreografia): cada serviço publica um evento, o próximo\nserviço reage automaticamente. Sem orquestrador central.",[20,28631,28632,28635],{},[89,28633,28634],{},"Orchestration"," (orquestração): um orquestrador coordena a sequência,\nchamando cada serviço na ordem certa.",[506,28637,28640],{"className":28638,"code":28639,"language":511},[509],"[Pedido criado] → [Estoque reservado] → [Pagamento processado] → [Pedido confirmado]\n      ↓ falha no pagamento\n[Estoque liberado] ← (compensação: desfaz a reserva)\n",[513,28641,28639],{"__ignoreMap":515},[710,28643,28644],{},[86,28645,28646,28649],{},[89,28647,28648],{},"Analogia da compensação",": Você comprou um produto online. O sistema\nreservou o estoque, processou o pagamento, mas o pagamento foi recusado.\nA Saga desfaz: libera o estoque que estava reservado. É como devolver um\nitem à prateleira quando o cartão é recusado no caixa.",[12,28651,28653],{"id":28652},"comunicação-entre-serviços-comunicacao","Comunicação entre serviços {#comunicacao}",[283,28655,28657],{"id":28656},"síncrona-restgrpc","Síncrona (REST\u002FgRPC)",[710,28659,28660],{},[86,28661,28662,28664,28665,28668],{},[89,28663,3011],{},": Comunicação síncrona é como uma ",[89,28666,28667],{},"ligação telefônica",". Você\nliga, espera a pessoa atender, pergunta, espera a resposta, e só depois\ndesliga. Se a pessoa não atende, você fica esperando.",[528,28670,28671,28677,28685],{},[20,28672,28673,28676],{},[89,28674,28675],{},"Use para",": consultas que precisam de resposta imediata (ex: \"este produto\nestá em estoque?\")",[20,28678,28679,1679,28681,28684],{},[89,28680,4750],{},[89,28682,28683],{},"acoplamento temporal"," — se o serviço B cai, o serviço A\ntambém \"cai\" (fica esperando resposta que nunca vem)",[20,28686,28687,1679,28690,28693],{},[89,28688,28689],{},"Mitigação",[89,28691,28692],{},"circuit breaker"," (disjuntor) — se B falha N vezes seguidas,\nA para de tentar e responde com um fallback (resposta degradada) em vez de\nficar esperando",[710,28695,28696],{},[86,28697,28698,28701],{},[89,28699,28700],{},"O que é Circuit Breaker?",": Como o disjuntor da sua casa. Se há um\ncurto-circuito (serviço B falhando), o disjuntor \"desarma\" e corta a energia\n(para de chamar B). Após um tempo, tenta \"religar\" (half-open) para ver se\nB voltou. Se sim, volta ao normal. Se não, desarma de novo. Ferramentas:\nResilience4j (Java), Polly (.NET), opossum (Node.js).",[283,28703,28705],{"id":28704},"assíncrona-eventosmensagens","Assíncrona (Eventos\u002FMensagens)",[710,28707,28708],{},[86,28709,28710,28712,28713,28716],{},[89,28711,3011],{},": Comunicação assíncrona é como ",[89,28714,28715],{},"mandar um e-mail",". Você\nescreve, envia e continua sua vida. Não espera a pessoa responder para\ncontinuar trabalhando. Quando ela responder, você lê.",[528,28718,28719,28724,28729],{},[20,28720,28721,28723],{},[89,28722,28675],{},": comandos que podem ser processados depois (ex: \"envie e-mail\nde confirmação\", \"recalcule o score do cliente\")",[20,28725,28726,28728],{},[89,28727,5786],{},": Kafka, RabbitMQ, NATS, Amazon SQS",[20,28730,28731,1679,28734,28737],{},[89,28732,28733],{},"Vantagem",[89,28735,28736],{},"desacoplamento temporal"," — o produtor não depende do\nconsumidor estar online",[506,28739,28741],{"className":3439,"code":28740,"language":3441,"meta":515,"style":515},"# Padrão: evento publicado, múltiplos serviços consomem independentemente\norder_created = {\n    \"event_id\": \"uuid-123\",                    # ID único do evento\n    \"event_type\": \"order.created\",             # tipo do evento\n    \"data\": {\n        \"order_id\": 456,\n        \"customer_id\": 789,\n        \"total\": 199.90,\n        \"items\": [{\"product_id\": 1, \"qty\": 2}]\n    },\n    \"timestamp\": \"2026-08-26T10:00:00Z\"\n}\n\n# Publica no Kafka\u002FRabbitMQ — não espera ninguém processar\nproducer.send(\"order-events\", order_created)\n\n# Múltiplos serviços consomem independentemente:\n# - Serviço de estoque consome → reserva os itens\n# - Serviço de notificação consome → envia e-mail de confirmação\n# - Serviço de analytics consome → atualiza dashboard de vendas\n# Cada um faz sua parte sem depender dos outros\n",[513,28742,28743,28748,28753,28758,28763,28768,28773,28778,28783,28788,28792,28797,28801,28805,28810,28815,28819,28824,28829,28834,28839],{"__ignoreMap":515},[1376,28744,28745],{"class":1378,"line":1379},[1376,28746,28747],{},"# Padrão: evento publicado, múltiplos serviços consomem independentemente\n",[1376,28749,28750],{"class":1378,"line":984},[1376,28751,28752],{},"order_created = {\n",[1376,28754,28755],{"class":1378,"line":981},[1376,28756,28757],{},"    \"event_id\": \"uuid-123\",                    # ID único do evento\n",[1376,28759,28760],{"class":1378,"line":1395},[1376,28761,28762],{},"    \"event_type\": \"order.created\",             # tipo do evento\n",[1376,28764,28765],{"class":1378,"line":1401},[1376,28766,28767],{},"    \"data\": {\n",[1376,28769,28770],{"class":1378,"line":1407},[1376,28771,28772],{},"        \"order_id\": 456,\n",[1376,28774,28775],{"class":1378,"line":1413},[1376,28776,28777],{},"        \"customer_id\": 789,\n",[1376,28779,28780],{"class":1378,"line":1419},[1376,28781,28782],{},"        \"total\": 199.90,\n",[1376,28784,28785],{"class":1378,"line":1424},[1376,28786,28787],{},"        \"items\": [{\"product_id\": 1, \"qty\": 2}]\n",[1376,28789,28790],{"class":1378,"line":1430},[1376,28791,5145],{},[1376,28793,28794],{"class":1378,"line":1436},[1376,28795,28796],{},"    \"timestamp\": \"2026-08-26T10:00:00Z\"\n",[1376,28798,28799],{"class":1378,"line":1442},[1376,28800,5170],{},[1376,28802,28803],{"class":1378,"line":1447},[1376,28804,1398],{"emptyLinePlaceholder":898},[1376,28806,28807],{"class":1378,"line":1453},[1376,28808,28809],{},"# Publica no Kafka\u002FRabbitMQ — não espera ninguém processar\n",[1376,28811,28812],{"class":1378,"line":1459},[1376,28813,28814],{},"producer.send(\"order-events\", order_created)\n",[1376,28816,28817],{"class":1378,"line":2200},[1376,28818,1398],{"emptyLinePlaceholder":898},[1376,28820,28821],{"class":1378,"line":2208},[1376,28822,28823],{},"# Múltiplos serviços consomem independentemente:\n",[1376,28825,28826],{"class":1378,"line":2219},[1376,28827,28828],{},"# - Serviço de estoque consome → reserva os itens\n",[1376,28830,28831],{"class":1378,"line":2227},[1376,28832,28833],{},"# - Serviço de notificação consome → envia e-mail de confirmação\n",[1376,28835,28836],{"class":1378,"line":2241},[1376,28837,28838],{},"# - Serviço de analytics consome → atualiza dashboard de vendas\n",[1376,28840,28841],{"class":1378,"line":2253},[1376,28842,28843],{},"# Cada um faz sua parte sem depender dos outros\n",[283,28845,28847],{"id":28846},"grpc-para-service-to-service","gRPC para service-to-service",[710,28849,28850],{},[86,28851,28852,28854,28855,28858],{},[89,28853,3011],{},": gRPC é como dois cozinheiros se comunicando por um ",[89,28856,28857],{},"sistema\nde pedidos digital"," em vez de gritar pela cozinha. Mais rápido, mais\norganizado, e com um \"menu\" definido (contrato) que ambos seguem.",[86,28860,28861,28864],{},[89,28862,28863],{},"gRPC"," é uma alternativa ao REST para comunicação entre serviços:",[528,28866,28867,28873,28879],{},[20,28868,28869,28872],{},[89,28870,28871],{},"Binário (Protocol Buffers)",": mensagens 3-10x menores que JSON → menos\nbanda, mais rápido",[20,28874,28875,28878],{},[89,28876,28877],{},"HTTP\u002F2",": suporta multiplexação (múltiplas mensagens em uma conexão) e\nstreaming",[20,28880,28881,28884],{},[89,28882,28883],{},"Contrato tipado (.proto)",": um arquivo define exatamente os tipos de\ndados → geração automática de código para cliente e servidor",[506,28886,28890],{"className":28887,"code":28888,"language":28889,"meta":515,"style":515},"language-protobuf shiki shiki-themes github-light github-dark","\u002F\u002F pedido.proto — contrato que define a comunicação\nsyntax = \"proto3\";\n\nservice PedidoService {\n  rpc CriarPedido (CriarPedidoRequest) returns (PedidoResponse);\n  rpc GetPedido (GetPedidoRequest) returns (PedidoResponse);\n}\n\nmessage CriarPedidoRequest {\n  int32 cliente_id = 1;\n  repeated ItemPedido itens = 2;\n}\n\nmessage ItemPedido {\n  int32 produto_id = 1;\n  int32 quantidade = 2;\n}\n\nmessage PedidoResponse {\n  int32 id = 1;\n  string status = 2;\n  double total = 3;\n}\n","protobuf",[513,28891,28892,28897,28902,28906,28911,28916,28921,28925,28929,28934,28939,28944,28948,28952,28957,28962,28967,28971,28975,28980,28985,28990,28995],{"__ignoreMap":515},[1376,28893,28894],{"class":1378,"line":1379},[1376,28895,28896],{},"\u002F\u002F pedido.proto — contrato que define a comunicação\n",[1376,28898,28899],{"class":1378,"line":984},[1376,28900,28901],{},"syntax = \"proto3\";\n",[1376,28903,28904],{"class":1378,"line":981},[1376,28905,1398],{"emptyLinePlaceholder":898},[1376,28907,28908],{"class":1378,"line":1395},[1376,28909,28910],{},"service PedidoService {\n",[1376,28912,28913],{"class":1378,"line":1401},[1376,28914,28915],{},"  rpc CriarPedido (CriarPedidoRequest) returns (PedidoResponse);\n",[1376,28917,28918],{"class":1378,"line":1407},[1376,28919,28920],{},"  rpc GetPedido (GetPedidoRequest) returns (PedidoResponse);\n",[1376,28922,28923],{"class":1378,"line":1413},[1376,28924,5170],{},[1376,28926,28927],{"class":1378,"line":1419},[1376,28928,1398],{"emptyLinePlaceholder":898},[1376,28930,28931],{"class":1378,"line":1424},[1376,28932,28933],{},"message CriarPedidoRequest {\n",[1376,28935,28936],{"class":1378,"line":1430},[1376,28937,28938],{},"  int32 cliente_id = 1;\n",[1376,28940,28941],{"class":1378,"line":1436},[1376,28942,28943],{},"  repeated ItemPedido itens = 2;\n",[1376,28945,28946],{"class":1378,"line":1442},[1376,28947,5170],{},[1376,28949,28950],{"class":1378,"line":1447},[1376,28951,1398],{"emptyLinePlaceholder":898},[1376,28953,28954],{"class":1378,"line":1453},[1376,28955,28956],{},"message ItemPedido {\n",[1376,28958,28959],{"class":1378,"line":1459},[1376,28960,28961],{},"  int32 produto_id = 1;\n",[1376,28963,28964],{"class":1378,"line":2200},[1376,28965,28966],{},"  int32 quantidade = 2;\n",[1376,28968,28969],{"class":1378,"line":2208},[1376,28970,5170],{},[1376,28972,28973],{"class":1378,"line":2219},[1376,28974,1398],{"emptyLinePlaceholder":898},[1376,28976,28977],{"class":1378,"line":2227},[1376,28978,28979],{},"message PedidoResponse {\n",[1376,28981,28982],{"class":1378,"line":2241},[1376,28983,28984],{},"  int32 id = 1;\n",[1376,28986,28987],{"class":1378,"line":2253},[1376,28988,28989],{},"  string status = 2;\n",[1376,28991,28992],{"class":1378,"line":2283},[1376,28993,28994],{},"  double total = 3;\n",[1376,28996,28997],{"class":1378,"line":2296},[1376,28998,5170],{},[710,29000,29001],{},[86,29002,29003,29006],{},[89,29004,29005],{},"Quando usar gRPC vs REST",": REST para APIs externas (clientes, parceiros),\ngRPC para comunicação interna entre serviços (onde performance importa e\nvocê controla ambos os lados).",[12,29008,29010],{"id":29009},"dados-em-microserviços-dados","Dados em microserviços {#dados}",[86,29012,29013,29016,29017,29020],{},[89,29014,29015],{},"Princípio fundamental",": cada microserviço é ",[89,29018,29019],{},"dono do seu banco de dados",".\nNenhum serviço acessa o banco de outro diretamente.",[710,29022,29023],{},[86,29024,29025,29027],{},[89,29026,3011],{},": Em uma empresa, o setor financeiro tem seus arquivos, o RH tem\nos seus. O financeiro não vai mexer nos arquivos do RH diretamente — se\nprecisa de informação, pede ao RH. Em microserviços é igual: se o serviço\nA precisa de dados do serviço B, ele pede via API\u002Feventos, não acessa o\nbanco do B diretamente.",[710,29029,29030],{},[86,29031,29032,29035],{},[89,29033,29034],{},"Por que?",": Se dois serviços compartilham o banco, uma mudança na tabela\nde um afeta o outro. Você perde o isolamento — a principal vantagem dos\nmicroserviços.",[283,29037,29039],{"id":29038},"padrões-de-dados","Padrões de dados",[165,29041,29042,29054],{},[168,29043,29044],{},[171,29045,29046,29049,29051],{},[174,29047,29048],{},"Padrão",[174,29050,1588],{},[174,29052,29053],{},"Trade-off",[184,29055,29056,29069,29082,29096,29108],{},[171,29057,29058,29063,29066],{},[189,29059,29060],{},[89,29061,29062],{},"Database per service",[189,29064,29065],{},"Padrão default (cada serviço tem seu banco)",[189,29067,29068],{},"Isolamento total, mas consultas que cruzam serviços são complexas",[171,29070,29071,29076,29079],{},[189,29072,29073],{},[89,29074,29075],{},"Shared database",[189,29077,29078],{},"Transição gradual de monolito",[189,29080,29081],{},"Rápido para migrar, mas quebra o isolamento",[171,29083,29084,29090,29093],{},[189,29085,29086,29089],{},[89,29087,29088],{},"CQRS"," (Command Query Responsibility Segregation)",[189,29091,29092],{},"Leitura e escrita têm necessidades diferentes",[189,29094,29095],{},"Otimiza cada lado, mas adiciona complexidade",[171,29097,29098,29102,29105],{},[189,29099,29100],{},[89,29101,27735],{},[189,29103,29104],{},"Precisa de auditoria completa de todas as mudanças",[189,29106,29107],{},"Permite \"replay\" da história, mas o storage cresce indefinidamente",[171,29109,29110,29114,29117],{},[189,29111,29112],{},[89,29113,28605],{},[189,29115,29116],{},"Transação que envolve múltiplos serviços",[189,29118,29119],{},"Consistência eventual (não imediata), exige compensação",[710,29121,29122],{},[86,29123,29124,29127,29128,29130,29131,29133],{},[89,29125,29126],{},"O que é CQRS?",": Separa operações de ",[89,29129,27716],{}," (Command — criar,\natualizar, deletar) de ",[89,29132,27720],{}," (Query — buscar, listar). Em vez de um\núnico modelo para ambos, você tem um modelo otimizado para escrita (normalizado,\ntransacional) e outro otimizado para leitura (desnormalizado, rápido para\nconsultas).",[710,29135,29136],{},[86,29137,29138,29141,29142,29144],{},[89,29139,29140],{},"O que é Event Sourcing?",": Em vez de guardar apenas o estado atual\n(\"saldo = R$ 1.000\"), guarda ",[89,29143,27741],{}," que levaram a esse estado\n(\"+500 depósito, -200 saque, +700 depósito\"). Permite reconstruir o estado\na qualquer momento e auditar toda a história. Mas o armazenamento cresce\npara sempre.",[283,29146,29148],{"id":29147},"sincronização-de-dados","Sincronização de dados",[86,29150,29151],{},"Quando um serviço precisa de dados de outro, há 3 abordagens:",[17,29153,29154,29160,29169],{},[20,29155,29156,29159],{},[89,29157,29158],{},"API call"," (chamada direta): simples, mas cria acoplamento temporal\n(se o serviço B cai, A não consegue obter os dados)",[20,29161,29162,29165,29166,29168],{},[89,29163,29164],{},"Réplica de leitura",": copia dados via ",[89,29167,24868],{}," (Change Data Capture —\ncaptura mudanças do banco de B e replica para uma cópia de leitura de A)\n→ rápido, mas os dados são eventualmente consistentes (podem estar\nalguns segundos desatualizados)",[20,29170,29171,29174],{},[89,29172,29173],{},"Materialized view"," (visão materializada): agregação pré-computada\n→ rápido para consultas complexas, mas precisa ser atualizada periodicamente",[12,29176,29178],{"id":29177},"deploy-e-orquestração-deploy","Deploy e orquestração {#deploy}",[283,29180,29182],{"id":29181},"kubernetes-o-padrão-de-facto","Kubernetes: o padrão de facto",[710,29184,29185],{},[86,29186,29187,1679,29189,16468,29192,29195,29196,29199],{},[89,29188,3011],{},[89,29190,29191],{},"Kubernetes",[89,29193,29194],{},"K8s",") é como\num ",[89,29197,29198],{},"gerente de fábrica",". Você diz \"quero 3 instâncias do serviço de\npedidos rodando, cada uma usando no máximo 512MB de memória\". O Kubernetes\ngarante que isso aconteça: se uma instância cai, ele cria outra. Se o\ntráfego aumenta, ele cria mais instâncias. Se diminui, ele remove. Tudo\nautomaticamente.",[86,29201,29202],{},"Kubernetes é o padrão da indústria para orquestrar contêineres (aplicações\nempacotadas com tudo que precisam para rodar). Veja um exemplo de configuração:",[506,29204,29206],{"className":1656,"code":29205,"language":1658,"meta":515,"style":515},"# deployment.yaml — Define como o serviço de pedidos deve rodar\napiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: order-service\nspec:\n  replicas: 3                    # sempre 3 instâncias rodando (alta disponibilidade)\n  selector:\n    matchLabels:\n      app: order-service\n  template:\n    spec:\n      containers:\n        - name: order-service\n          image: registry.com\u002Forder-service:v1.4.2  # versão da imagem\n          resources:\n            # 'requests' = mínimo garantido (K8s reserva isto)\n            requests:\n              memory: \"256Mi\"     # 256 MB de memória\n              cpu: \"250m\"         # 0.25 CPU (1 CPU = 1000m)\n            # 'limits' = máximo permitido (K8s limita a isto)\n            limits:\n              memory: \"512Mi\"     # até 512 MB\n              cpu: \"500m\"         # até 0.5 CPU\n          # Liveness: \"este serviço está vivo?\" — se falhar, K8s reinicia\n          livenessProbe:\n            httpGet:\n              path: \u002Fhealth       # endpoint que retorna 200 se saudável\n              port: 8080\n          # Readiness: \"este serviço está pronto para receber tráfego?\"\n          # — se falhar, K8s tira do load balancer mas não reinicia\n          readinessProbe:\n            httpGet:\n              path: \u002Fready        # endpoint que retorna 200 se pronto\n              port: 8080\n",[513,29207,29208,29213,29221,29229,29235,29244,29250,29261,29267,29273,29281,29287,29293,29299,29309,29321,29327,29332,29338,29350,29361,29366,29372,29384,29396,29401,29407,29413,29425,29434,29439,29444,29450,29456,29468],{"__ignoreMap":515},[1376,29209,29210],{"class":1378,"line":1379},[1376,29211,29212],{"class":1665},"# deployment.yaml — Define como o serviço de pedidos deve rodar\n",[1376,29214,29215,29217,29219],{"class":1378,"line":984},[1376,29216,14344],{"class":1675},[1376,29218,1679],{"class":1671},[1376,29220,14507],{"class":1682},[1376,29222,29223,29225,29227],{"class":1378,"line":981},[1376,29224,14354],{"class":1675},[1376,29226,1679],{"class":1671},[1376,29228,14516],{"class":1682},[1376,29230,29231,29233],{"class":1378,"line":1395},[1376,29232,14364],{"class":1675},[1376,29234,1750],{"class":1671},[1376,29236,29237,29239,29241],{"class":1378,"line":1401},[1376,29238,14371],{"class":1675},[1376,29240,1679],{"class":1671},[1376,29242,29243],{"class":1682},"order-service\n",[1376,29245,29246,29248],{"class":1378,"line":1407},[1376,29247,14403],{"class":1675},[1376,29249,1750],{"class":1671},[1376,29251,29252,29254,29256,29258],{"class":1378,"line":1413},[1376,29253,14542],{"class":1675},[1376,29255,1679],{"class":1671},[1376,29257,5054],{"class":1770},[1376,29259,29260],{"class":1665},"                    # sempre 3 instâncias rodando (alta disponibilidade)\n",[1376,29262,29263,29265],{"class":1378,"line":1419},[1376,29264,14554],{"class":1675},[1376,29266,1750],{"class":1671},[1376,29268,29269,29271],{"class":1378,"line":1424},[1376,29270,14561],{"class":1675},[1376,29272,1750],{"class":1671},[1376,29274,29275,29277,29279],{"class":1378,"line":1430},[1376,29276,14568],{"class":1675},[1376,29278,1679],{"class":1671},[1376,29280,29243],{"class":1682},[1376,29282,29283,29285],{"class":1378,"line":1436},[1376,29284,14580],{"class":1675},[1376,29286,1750],{"class":1671},[1376,29288,29289,29291],{"class":1378,"line":1442},[1376,29290,14617],{"class":1675},[1376,29292,1750],{"class":1671},[1376,29294,29295,29297],{"class":1378,"line":1447},[1376,29296,14624],{"class":1675},[1376,29298,1750],{"class":1671},[1376,29300,29301,29303,29305,29307],{"class":1378,"line":1453},[1376,29302,14450],{"class":1671},[1376,29304,1676],{"class":1675},[1376,29306,1679],{"class":1671},[1376,29308,29243],{"class":1682},[1376,29310,29311,29313,29315,29318],{"class":1378,"line":1459},[1376,29312,14641],{"class":1675},[1376,29314,1679],{"class":1671},[1376,29316,29317],{"class":1682},"registry.com\u002Forder-service:v1.4.2",[1376,29319,29320],{"class":1665},"  # versão da imagem\n",[1376,29322,29323,29325],{"class":1378,"line":2200},[1376,29324,14673],{"class":1675},[1376,29326,1750],{"class":1671},[1376,29328,29329],{"class":1378,"line":2208},[1376,29330,29331],{"class":1665},"            # 'requests' = mínimo garantido (K8s reserva isto)\n",[1376,29333,29334,29336],{"class":1378,"line":2219},[1376,29335,14684],{"class":1675},[1376,29337,1750],{"class":1671},[1376,29339,29340,29342,29344,29347],{"class":1378,"line":2227},[1376,29341,14695],{"class":1675},[1376,29343,1679],{"class":1671},[1376,29345,29346],{"class":1682},"\"256Mi\"",[1376,29348,29349],{"class":1665},"     # 256 MB de memória\n",[1376,29351,29352,29354,29356,29358],{"class":1378,"line":2241},[1376,29353,14705],{"class":1675},[1376,29355,1679],{"class":1671},[1376,29357,14710],{"class":1682},[1376,29359,29360],{"class":1665},"         # 0.25 CPU (1 CPU = 1000m)\n",[1376,29362,29363],{"class":1378,"line":2253},[1376,29364,29365],{"class":1665},"            # 'limits' = máximo permitido (K8s limita a isto)\n",[1376,29367,29368,29370],{"class":1378,"line":2283},[1376,29369,14718],{"class":1675},[1376,29371,1750],{"class":1671},[1376,29373,29374,29376,29378,29381],{"class":1378,"line":2296},[1376,29375,14695],{"class":1675},[1376,29377,1679],{"class":1671},[1376,29379,29380],{"class":1682},"\"512Mi\"",[1376,29382,29383],{"class":1665},"     # até 512 MB\n",[1376,29385,29386,29388,29390,29393],{"class":1378,"line":2311},[1376,29387,14705],{"class":1675},[1376,29389,1679],{"class":1671},[1376,29391,29392],{"class":1682},"\"500m\"",[1376,29394,29395],{"class":1665},"         # até 0.5 CPU\n",[1376,29397,29398],{"class":1378,"line":2316},[1376,29399,29400],{"class":1665},"          # Liveness: \"este serviço está vivo?\" — se falhar, K8s reinicia\n",[1376,29402,29403,29405],{"class":1378,"line":2324},[1376,29404,14747],{"class":1675},[1376,29406,1750],{"class":1671},[1376,29408,29409,29411],{"class":1378,"line":2333},[1376,29410,14758],{"class":1675},[1376,29412,1750],{"class":1671},[1376,29414,29415,29417,29419,29422],{"class":1378,"line":2340},[1376,29416,14765],{"class":1675},[1376,29418,1679],{"class":1671},[1376,29420,29421],{"class":1682},"\u002Fhealth",[1376,29423,29424],{"class":1665},"       # endpoint que retorna 200 se saudável\n",[1376,29426,29427,29429,29431],{"class":1378,"line":2351},[1376,29428,14775],{"class":1675},[1376,29430,1679],{"class":1671},[1376,29432,29433],{"class":1770},"8080\n",[1376,29435,29436],{"class":1378,"line":2362},[1376,29437,29438],{"class":1665},"          # Readiness: \"este serviço está pronto para receber tráfego?\"\n",[1376,29440,29441],{"class":1378,"line":2385},[1376,29442,29443],{"class":1665},"          # — se falhar, K8s tira do load balancer mas não reinicia\n",[1376,29445,29446,29448],{"class":1378,"line":2396},[1376,29447,14797],{"class":1675},[1376,29449,1750],{"class":1671},[1376,29451,29452,29454],{"class":1378,"line":2411},[1376,29453,14758],{"class":1675},[1376,29455,1750],{"class":1671},[1376,29457,29458,29460,29462,29465],{"class":1378,"line":2416},[1376,29459,14765],{"class":1675},[1376,29461,1679],{"class":1671},[1376,29463,29464],{"class":1682},"\u002Fready",[1376,29466,29467],{"class":1665},"        # endpoint que retorna 200 se pronto\n",[1376,29469,29470,29472,29474],{"class":1378,"line":2422},[1376,29471,14775],{"class":1675},[1376,29473,1679],{"class":1671},[1376,29475,29433],{"class":1770},[710,29477,29478],{},[86,29479,29480,29483],{},[89,29481,29482],{},"Diferença entre Liveness e Readiness",": Liveness verifica se a aplicação\nestá rodando (se falhar, reinicia). Readiness verifica se está pronta para\nreceber requisições (ex: terminou de carregar configurações, conectou ao\nbanco). Se readiness falhar, o serviço fica fora do balanceador de carga\nmas não é reiniciado.",[283,29485,1573],{"id":1572},[165,29487,29488,29498],{},[168,29489,29490],{},[171,29491,29492,29494,29496],{},[174,29493,1582],{},[174,29495,1585],{},[174,29497,1588],{},[184,29499,29500,29511,29523,29535],{},[171,29501,29502,29506,29509],{},[189,29503,29504],{},[89,29505,1597],{},[189,29507,29508],{},"Substitui instâncias gradualmente (1 por vez)",[189,29510,1603],{},[171,29512,29513,29517,29520],{},[189,29514,29515],{},[89,29516,1610],{},[189,29518,29519],{},"Dois ambientes idênticos; switch de tráfego instantâneo",[189,29521,29522],{},"Validação rápida com rollback fácil",[171,29524,29525,29529,29532],{},[189,29526,29527],{},[89,29528,1623],{},[189,29530,29531],{},"5% → 25% → 50% → 100% do tráfego na nova versão",[189,29533,29534],{},"Risco de regressão alto",[171,29536,29537,29542,29545],{},[189,29538,29539],{},[89,29540,29541],{},"Feature flags",[189,29543,29544],{},"Liga\u002Fdesliga funcionalidade sem novo deploy",[189,29546,29547],{},"Experimentação A\u002FB, rollout controlado",[710,29549,29550],{},[86,29551,29552,29555],{},[89,29553,29554],{},"Analogia Blue\u002FGreen",": É como ter dois palcos de teatro idênticos. A peça\nrola no palco Azul. Você prepara a nova versão no palco Verde. Quando tudo\nestá pronto, muda a plateia do Azul para o Verde instantaneamente. Se algo\nder errado, muda de volta num segundo.",[710,29557,29558],{},[86,29559,29560,29563],{},[89,29561,29562],{},"Analogia Canary",": É como enviar uma nova receita para 5% dos clientes\nprimeiro. Se ninguém reclama, envia para 25%. Se alguém reclama, para. Se\nestá tudo bem, continua até 100%. O nome vem dos canários que mineiros\nlevavam para dentro das minas — se o canário morria, era sinal de gás\ntóxico e todos evacuavam.",[283,29565,19071],{"id":20923},[506,29567,29569],{"className":1656,"code":29568,"language":1658,"meta":515,"style":515},"# hpa.yaml — Horizontal Pod Autoscaler: escala automaticamente\napiVersion: autoscaling\u002Fv2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: order-service-hpa\nspec:\n  scaleTargetRef:\n    kind: Deployment\n    name: order-service\n  minReplicas: 3               # mínimo de 3 instâncias (sempre)\n  maxReplicas: 50              # máximo de 50 instâncias\n  metrics:\n    - type: Resource\n      resource:\n        name: cpu\n        target:\n          type: Utilization\n          averageUtilization: 70  # se CPU médio > 70%, cria mais instâncias\n",[513,29570,29571,29576,29584,29592,29598,29607,29613,29619,29627,29635,29646,29658,29664,29674,29680,29688,29694,29702],{"__ignoreMap":515},[1376,29572,29573],{"class":1378,"line":1379},[1376,29574,29575],{"class":1665},"# hpa.yaml — Horizontal Pod Autoscaler: escala automaticamente\n",[1376,29577,29578,29580,29582],{"class":1378,"line":984},[1376,29579,14344],{"class":1675},[1376,29581,1679],{"class":1671},[1376,29583,15463],{"class":1682},[1376,29585,29586,29588,29590],{"class":1378,"line":981},[1376,29587,14354],{"class":1675},[1376,29589,1679],{"class":1671},[1376,29591,15472],{"class":1682},[1376,29593,29594,29596],{"class":1378,"line":1395},[1376,29595,14364],{"class":1675},[1376,29597,1750],{"class":1671},[1376,29599,29600,29602,29604],{"class":1378,"line":1401},[1376,29601,14371],{"class":1675},[1376,29603,1679],{"class":1671},[1376,29605,29606],{"class":1682},"order-service-hpa\n",[1376,29608,29609,29611],{"class":1378,"line":1407},[1376,29610,14403],{"class":1675},[1376,29612,1750],{"class":1671},[1376,29614,29615,29617],{"class":1378,"line":1413},[1376,29616,15498],{"class":1675},[1376,29618,1750],{"class":1671},[1376,29620,29621,29623,29625],{"class":1378,"line":1419},[1376,29622,15514],{"class":1675},[1376,29624,1679],{"class":1671},[1376,29626,14516],{"class":1682},[1376,29628,29629,29631,29633],{"class":1378,"line":1424},[1376,29630,15523],{"class":1675},[1376,29632,1679],{"class":1671},[1376,29634,29243],{"class":1682},[1376,29636,29637,29639,29641,29643],{"class":1378,"line":1430},[1376,29638,15535],{"class":1675},[1376,29640,1679],{"class":1671},[1376,29642,5054],{"class":1770},[1376,29644,29645],{"class":1665},"               # mínimo de 3 instâncias (sempre)\n",[1376,29647,29648,29650,29652,29655],{"class":1378,"line":1436},[1376,29649,15547],{"class":1675},[1376,29651,1679],{"class":1671},[1376,29653,29654],{"class":1770},"50",[1376,29656,29657],{"class":1665},"              # máximo de 50 instâncias\n",[1376,29659,29660,29662],{"class":1378,"line":1442},[1376,29661,15559],{"class":1675},[1376,29663,1750],{"class":1671},[1376,29665,29666,29668,29670,29672],{"class":1378,"line":1447},[1376,29667,1873],{"class":1671},[1376,29669,15568],{"class":1675},[1376,29671,1679],{"class":1671},[1376,29673,15573],{"class":1682},[1376,29675,29676,29678],{"class":1378,"line":1453},[1376,29677,15578],{"class":1675},[1376,29679,1750],{"class":1671},[1376,29681,29682,29684,29686],{"class":1378,"line":1459},[1376,29683,15585],{"class":1675},[1376,29685,1679],{"class":1671},[1376,29687,15590],{"class":1682},[1376,29689,29690,29692],{"class":1378,"line":2200},[1376,29691,15595],{"class":1675},[1376,29693,1750],{"class":1671},[1376,29695,29696,29698,29700],{"class":1378,"line":2208},[1376,29697,15602],{"class":1675},[1376,29699,1679],{"class":1671},[1376,29701,15607],{"class":1682},[1376,29703,29704,29706,29708,29710],{"class":1378,"line":2219},[1376,29705,15612],{"class":1675},[1376,29707,1679],{"class":1671},[1376,29709,15617],{"class":1770},[1376,29711,29712],{"class":1665},"  # se CPU médio > 70%, cria mais instâncias\n",[710,29714,29715],{},[86,29716,29717,29719],{},[89,29718,1585],{},": O Kubernetes monitora o uso de CPU. Se a média passa de\n70%, ele cria mais instâncias para dividir a carga. Se cai, ele remove\ninstâncias para economizar. Tudo automático, 24\u002F7.",[12,29721,29723],{"id":29722},"observabilidade-observabilidade","Observabilidade {#observabilidade}",[86,29725,29726,29728],{},[89,29727,6438],{}," é a capacidade de entender o estado interno do sistema\nobservando suas saídas (logs, métricas, traces). Sem ela, gerenciar\nmicroserviços é como dirigir de olhos vendados.",[710,29730,29731],{},[86,29732,29733,29735,29736,29739],{},[89,29734,3011],{},": Observabilidade é o ",[89,29737,29738],{},"painel do carro + GPS + câmera de ré",".\nLogs são o histórico de manutenção. Métricas são o painel (velocímetro,\ntemperatura, combustível). Traces são o GPS que mostra o caminho completo\nde cada viagem.",[86,29741,29742],{},"Os três pilares:",[283,29744,29746],{"id":29745},"_1-logs-estruturados","1. Logs estruturados",[506,29748,29750],{"className":16949,"code":29749,"language":16951,"meta":515,"style":515},"{\n  \"timestamp\": \"2026-08-26T10:30:00.123Z\",\n  \"level\": \"info\",\n  \"service\": \"order-service\",\n  \"trace_id\": \"abc123\",         # ID que rastreia a requisição em todos os serviços\n  \"span_id\": \"def456\",          # ID deste trecho específico\n  \"message\": \"Order created\",\n  \"order_id\": 456,\n  \"customer_id\": 789,\n  \"latency_ms\": 142\n}\n",[513,29751,29752,29756,29767,29779,29791,29835,29861,29873,29885,29897,29906],{"__ignoreMap":515},[1376,29753,29754],{"class":1378,"line":1379},[1376,29755,25841],{"class":1671},[1376,29757,29758,29760,29762,29765],{"class":1378,"line":984},[1376,29759,25906],{"class":1770},[1376,29761,1679],{"class":1671},[1376,29763,29764],{"class":1682},"\"2026-08-26T10:30:00.123Z\"",[1376,29766,17031],{"class":1671},[1376,29768,29769,29772,29774,29777],{"class":1378,"line":981},[1376,29770,29771],{"class":1770},"  \"level\"",[1376,29773,1679],{"class":1671},[1376,29775,29776],{"class":1682},"\"info\"",[1376,29778,17031],{"class":1671},[1376,29780,29781,29784,29786,29789],{"class":1378,"line":1395},[1376,29782,29783],{"class":1770},"  \"service\"",[1376,29785,1679],{"class":1671},[1376,29787,29788],{"class":1682},"\"order-service\"",[1376,29790,17031],{"class":1671},[1376,29792,29793,29796,29798,29801,29804,29808,29811,29814,29817,29820,29823,29826,29829,29832],{"class":1378,"line":1401},[1376,29794,29795],{"class":1770},"  \"trace_id\"",[1376,29797,1679],{"class":1671},[1376,29799,29800],{"class":1682},"\"abc123\"",[1376,29802,29803],{"class":1671},",         ",[1376,29805,29807],{"class":29806},"s7hpK","#",[1376,29809,29810],{"class":29806}," ID",[1376,29812,29813],{"class":29806}," que",[1376,29815,29816],{"class":29806}," rastreia",[1376,29818,29819],{"class":29806}," a",[1376,29821,29822],{"class":29806}," requisição",[1376,29824,29825],{"class":29806}," em",[1376,29827,29828],{"class":29806}," todos",[1376,29830,29831],{"class":29806}," os",[1376,29833,29834],{"class":29806}," serviços\n",[1376,29836,29837,29840,29842,29845,29848,29850,29852,29855,29858],{"class":1378,"line":1407},[1376,29838,29839],{"class":1770},"  \"span_id\"",[1376,29841,1679],{"class":1671},[1376,29843,29844],{"class":1682},"\"def456\"",[1376,29846,29847],{"class":1671},",          ",[1376,29849,29807],{"class":29806},[1376,29851,29810],{"class":29806},[1376,29853,29854],{"class":29806}," deste",[1376,29856,29857],{"class":29806}," trecho",[1376,29859,29860],{"class":29806}," específico\n",[1376,29862,29863,29866,29868,29871],{"class":1378,"line":1413},[1376,29864,29865],{"class":1770},"  \"message\"",[1376,29867,1679],{"class":1671},[1376,29869,29870],{"class":1682},"\"Order created\"",[1376,29872,17031],{"class":1671},[1376,29874,29875,29878,29880,29883],{"class":1378,"line":1419},[1376,29876,29877],{"class":1770},"  \"order_id\"",[1376,29879,1679],{"class":1671},[1376,29881,29882],{"class":1770},"456",[1376,29884,17031],{"class":1671},[1376,29886,29887,29890,29892,29895],{"class":1378,"line":1424},[1376,29888,29889],{"class":1770},"  \"customer_id\"",[1376,29891,1679],{"class":1671},[1376,29893,29894],{"class":1770},"789",[1376,29896,17031],{"class":1671},[1376,29898,29899,29901,29903],{"class":1378,"line":1430},[1376,29900,25894],{"class":1770},[1376,29902,1679],{"class":1671},[1376,29904,29905],{"class":1770},"142\n",[1376,29907,29908],{"class":1378,"line":1436},[1376,29909,5170],{"class":1671},[86,29911,29912,29915],{},[89,29913,29914],{},"Stack",": Fluentd\u002FFluent Bit (coleta) → Elasticsearch\u002FLoki (armazenamento)\n→ Kibana\u002FGrafana (visualização)",[710,29917,29918],{},[86,29919,29920,29923],{},[89,29921,29922],{},"Por que estruturado?",": Logs em texto livre (\"Order 456 created in 142ms\")\nsão difíceis de buscar e filtrar. Logs em JSON permitem queries como\n\"mostre todos os logs onde service=order-service AND latency_ms > 500\".",[283,29925,25963],{"id":25962},[528,29927,29928,29933,29938],{},[20,29929,29930,29932],{},[89,29931,18375],{}," coleta métricas de cada serviço periodicamente",[20,29934,29935,29937],{},[89,29936,18560],{}," visualiza dashboards em tempo real",[20,29939,29940,29943],{},[89,29941,29942],{},"Alertas",": erro rate, latência P99, saturação de recursos",[86,29945,29946],{},[89,29947,29948],{},"Métricas essenciais (frameworks USE e RED):",[528,29950,29951,29969],{},[20,29952,29953,29956,29957,29960,29961,29964,29965,29968],{},[89,29954,29955],{},"RED"," (por serviço): ",[89,29958,29959],{},"R","ate (taxa de requisições), ",[89,29962,29963],{},"E","rrors (taxa de\nerro), ",[89,29966,29967],{},"D","uration (latência)",[20,29970,29971,29974,29975,29978,29979,29982,29983,29985],{},[89,29972,29973],{},"USE"," (por recurso): ",[89,29976,29977],{},"U","tilization (utilização), ",[89,29980,29981],{},"S","aturation\n(saturação), ",[89,29984,29963],{},"rrors (erros)",[283,29987,29989],{"id":29988},"_3-distributed-tracing","3. Distributed tracing",[710,29991,29992],{},[86,29993,29994,29996,29997,30000],{},[89,29995,3011],{},": Distributed tracing é como o ",[89,29998,29999],{},"rastreamento de encomenda"," dos\ncorreios. Você vê cada passo: saiu do centro de distribuição → chegou na\nunidade de triagem → saiu para entrega → entregue. Com timestamps em cada\netapa, você vê onde demorou mais.",[86,30002,30003,30005,30006,30009],{},[89,30004,16467],{}," instrumenta cada serviço. ",[89,30007,30008],{},"Jaeger\u002FTempo"," visualiza o\ntrace completo de uma requisição:",[506,30011,30014],{"className":30012,"code":30013,"language":511},[509],"[Gateway] → [Auth] → [Order] → [Inventory] → [Payment]\n  2ms        15ms     45ms       30ms         50ms\n                                        Total: 142ms\n",[513,30015,30013],{"__ignoreMap":515},[710,30017,30018],{},[86,30019,30020,30023],{},[89,30021,30022],{},"Por que isso importa?",": Um cliente reclama \"a compra demorou 10 segundos\".\nSem distributed tracing, você não sabe qual dos 5 serviços é o culpado. Com\ntracing, você vê imediatamente: o serviço de Inventory demorou 8 segundos.",[12,30025,30027],{"id":30026},"escala-real-números-numeros","Escala real: números {#numeros}",[165,30029,30030,30041],{},[168,30031,30032],{},[171,30033,30034,30036,30039],{},[174,30035,7988],{},[174,30037,30038],{},"Arquitetura recomendada",[174,30040,9733],{},[184,30042,30043,30053,30064,30075,30086],{},[171,30044,30045,30048,30050],{},[189,30046,30047],{},"1K req\u002Fdia",[189,30049,28243],{},[189,30051,30052],{},"1 instância (t3.small)",[171,30054,30055,30058,30061],{},[189,30056,30057],{},"10K req\u002Fdia",[189,30059,30060],{},"Monolito modular",[189,30062,30063],{},"2 instâncias + Load Balancer",[171,30065,30066,30069,30072],{},[189,30067,30068],{},"100K req\u002Fdia",[189,30070,30071],{},"2-3 serviços",[189,30073,30074],{},"3-5 pods + Redis cache",[171,30076,30077,30080,30083],{},[189,30078,30079],{},"1M req\u002Fdia",[189,30081,30082],{},"5-10 microserviços",[189,30084,30085],{},"K8s + auto-scaling + Kafka",[171,30087,30088,30091,30094],{},[189,30089,30090],{},"10M req\u002Fdia",[189,30092,30093],{},"15-30 microserviços",[189,30095,30096],{},"K8s multi-AZ + CDN + event-driven",[710,30098,30099],{},[86,30100,30101,30103],{},[89,30102,3011],{},": Você não constrói um shopping center para uma banca de jornal.\nCada volume de tráfego pede uma arquitetura diferente. Comece pequeno e\nevolua conforme o negócio cresce.",[283,30105,30107],{"id":30106},"custos-aproximados-aws-2026","Custos aproximados (AWS, 2026)",[165,30109,30110,30120],{},[168,30111,30112],{},[171,30113,30114,30116,30118],{},[174,30115,4648],{},[174,30117,19416],{},[174,30119,9234],{},[184,30121,30122,30132,30142,30151],{},[171,30123,30124,30127,30129],{},[189,30125,30126],{},"MVP",[189,30128,30047],{},[189,30130,30131],{},"$20-50",[171,30133,30134,30137,30139],{},[189,30135,30136],{},"Crescimento",[189,30138,30068],{},[189,30140,30141],{},"$200-500",[171,30143,30144,30146,30148],{},[189,30145,28285],{},[189,30147,30079],{},[189,30149,30150],{},"$1.000-3.000",[171,30152,30153,30156,30158],{},[189,30154,30155],{},"Alta escala",[189,30157,30090],{},[189,30159,30160],{},"$5.000-15.000",[710,30162,30163,30168],{},[86,30164,30165,30167],{},[89,30166,6471],{},": Otimize custos com:",[528,30169,30170,30176,30181],{},[20,30171,30172,30175],{},[89,30173,30174],{},"Auto-scaling agressivo",": reduz instâncias quando o tráfego cai (à noite,\nfins de semana)",[20,30177,30178,30180],{},[89,30179,19487],{},": instâncias mais baratas (até 90% de desconto) para\nworkloads que toleram interrupção (processamento em batch, por exemplo)",[20,30182,30183,30186],{},[89,30184,30185],{},"CDN"," (CloudFront): reduz carga na origem servindo conteúdo estático\npela borda (mais próximo do usuário)",[12,30188,820],{"id":819},[283,30190,30192],{"id":30191},"erro-1-começar-com-microserviços-no-mvp","Erro 1: Começar com microserviços no MVP",[86,30194,30195,30197],{},[89,30196,4164],{},": A startup decide começar já com microserviços \"para não\nprecisar migrar depois\". 3 desenvolvedores gastam 4 meses configurando\nKubernetes, service mesh e CI\u002FCD antes de ter um único usuário.",[86,30199,30200,4171],{},[89,30201,4170],{},[506,30203,30206],{"className":30204,"code":30205,"language":511},[509],"❌ Dia 1 da startup, 0 usuários, 0 receita:\n- 8 microserviços\n- Kubernetes com Istio\n- Kafka para eventos\n- CI\u002FCD com ArgoCD\n- Observabilidade com 5 ferramentas\n\nResultado: 4 meses de setup, nenhuma feature para o usuário, dinheiro acabando.\n",[513,30207,30205],{"__ignoreMap":515},[86,30209,30210,4171],{},[89,30211,4182],{},[506,30213,30216],{"className":30214,"code":30215,"language":511},[509],"✅ Dia 1 da startup:\n- 1 monolito modular (Node.js ou Python)\n- 1 banco de dados (PostgreSQL)\n- 1 instância (Railway ou Fly.io)\n- Deploy com git push\n\nQuando tiver 100K+ req\u002Fdia e 8+ desenvolvedores:\n- Aí sim, extraia microserviços conforme a necessidade\n",[513,30217,30215],{"__ignoreMap":515},[283,30219,30221],{"id":30220},"erro-2-compartilhar-banco-de-dados-entre-serviços","Erro 2: Compartilhar banco de dados entre serviços",[86,30223,30224,30226],{},[89,30225,4164],{},": Dois microserviços acessam o mesmo banco \"para facilitar\njoins\". Uma mudança na tabela de um serviço quebra o outro. Os serviços não\nsão realmente independentes.",[86,30228,30229,4171],{},[89,30230,4170],{},[506,30232,30234],{"className":3439,"code":30233,"language":3441,"meta":515,"style":515},"# ❌ ERRADO: serviço de pedidos acessa o banco de clientes diretamente\ndef get_pedido_com_cliente(pedido_id):\n    pedido = db_pedidos.query(\"SELECT * FROM pedidos WHERE id = ?\", pedido_id)\n    # Acessando diretamente a tabela de clientes — quebra o isolamento!\n    cliente = db_pedidos.query(\"SELECT * FROM clientes WHERE id = ?\", pedido.cliente_id)\n    return {**pedido, \"cliente\": cliente}\n",[513,30235,30236,30241,30246,30251,30256,30261],{"__ignoreMap":515},[1376,30237,30238],{"class":1378,"line":1379},[1376,30239,30240],{},"# ❌ ERRADO: serviço de pedidos acessa o banco de clientes diretamente\n",[1376,30242,30243],{"class":1378,"line":984},[1376,30244,30245],{},"def get_pedido_com_cliente(pedido_id):\n",[1376,30247,30248],{"class":1378,"line":981},[1376,30249,30250],{},"    pedido = db_pedidos.query(\"SELECT * FROM pedidos WHERE id = ?\", pedido_id)\n",[1376,30252,30253],{"class":1378,"line":1395},[1376,30254,30255],{},"    # Acessando diretamente a tabela de clientes — quebra o isolamento!\n",[1376,30257,30258],{"class":1378,"line":1401},[1376,30259,30260],{},"    cliente = db_pedidos.query(\"SELECT * FROM clientes WHERE id = ?\", pedido.cliente_id)\n",[1376,30262,30263],{"class":1378,"line":1407},[1376,30264,30265],{},"    return {**pedido, \"cliente\": cliente}\n",[86,30267,30268,4171],{},[89,30269,4182],{},[506,30271,30273],{"className":3439,"code":30272,"language":3441,"meta":515,"style":515},"# ✅ CERTO: serviço de pedidos pede dados de cliente via API do serviço de clientes\ndef get_pedido_com_cliente(pedido_id):\n    pedido = db_pedidos.query(\"SELECT * FROM pedidos WHERE id = ?\", pedido_id)\n    # Pede ao serviço de clientes via API — mantém o isolamento\n    cliente = cliente_service_api.get(pedido.cliente_id)\n    return {**pedido, \"cliente\": cliente}\n",[513,30274,30275,30280,30284,30288,30293,30298],{"__ignoreMap":515},[1376,30276,30277],{"class":1378,"line":1379},[1376,30278,30279],{},"# ✅ CERTO: serviço de pedidos pede dados de cliente via API do serviço de clientes\n",[1376,30281,30282],{"class":1378,"line":984},[1376,30283,30245],{},[1376,30285,30286],{"class":1378,"line":981},[1376,30287,30250],{},[1376,30289,30290],{"class":1378,"line":1395},[1376,30291,30292],{},"    # Pede ao serviço de clientes via API — mantém o isolamento\n",[1376,30294,30295],{"class":1378,"line":1401},[1376,30296,30297],{},"    cliente = cliente_service_api.get(pedido.cliente_id)\n",[1376,30299,30300],{"class":1378,"line":1407},[1376,30301,30265],{},[283,30303,30305],{"id":30304},"erro-3-comunicação-síncrona-em-cascata","Erro 3: Comunicação síncrona em cascata",[86,30307,30308,30310],{},[89,30309,4164],{},": O serviço A chama B, que chama C, que chama D. Se D cai,\nC fica esperando, B fica esperando, A fica esperando, e o cliente recebe\ntimeout. Um único serviço derruba toda a cadeia.",[86,30312,30313,4171],{},[89,30314,4170],{},[506,30316,30319],{"className":30317,"code":30318,"language":511},[509],"❌ Cadeia síncrona:\n[Cliente] → [A] → [B] → [C] → [D (caiu)]\n     ← timeout ← timeout ← timeout ← timeout\nTodos os serviços ficam presos esperando D.\n",[513,30320,30318],{"__ignoreMap":515},[86,30322,30323,4171],{},[89,30324,4182],{},[506,30326,30329],{"className":30327,"code":30328,"language":511},[509],"✅ Usar circuit breaker + comunicação assíncrona onde possível:\n[Cliente] → [A] → [B] → [C] → [D (caiu)]\n                    ← circuit breaker \"desarma\" ← fallback imediato\nA responde com dados parciais ou mensagem de \"tente mais tarde\".\n\nOu melhor: [A] publica evento, [B], [C], [D] consomem independentemente.\nNenhum depende do outro estar online.\n",[513,30330,30328],{"__ignoreMap":515},[283,30332,30334],{"id":30333},"erro-4-sem-distributed-tracing","Erro 4: Sem distributed tracing",[86,30336,30337,30339],{},[89,30338,4164],{},": Em produção, um cliente reclama que \"às vezes o checkout\ndemora 15 segundos\". Você tem 6 serviços. Sem distributed tracing, você não\nfaz ideia de qual serviço está lento. Fica testando um por um, por dias.",[86,30341,30342,30344],{},[89,30343,4182],{},": Implemente OpenTelemetry desde o início. O custo de\nimplementar é baixo (algumas linhas de configuração por serviço) e o\nbenefício é enorme quando há problemas em produção.",[506,30346,30348],{"className":3439,"code":30347,"language":3441,"meta":515,"style":515},"# Exemplo: instrumentação com OpenTelemetry em Python\nfrom opentelemetry import trace\nfrom opentelemetry.instrumentation.fastapi import FastAPIInstrumentor\n\ntracer = trace.get_tracer(__name__)\n\n@app.get(\"\u002Fpedidos\u002F{pedido_id}\")\nasync def get_pedido(pedido_id: int):\n    # Cria um span (trecho do trace) automaticamente\n    with tracer.start_as_current_span(\"get_pedido\") as span:\n        span.set_attribute(\"pedido_id\", pedido_id)\n        \n        pedido = buscar_pedido(pedido_id)  # sub-span automático\n        cliente = buscar_cliente(pedido.cliente_id)  # sub-span automático\n        \n        span.set_attribute(\"cliente_id\", pedido.cliente_id)\n        return {**pedido, \"cliente\": cliente}\n",[513,30349,30350,30355,30359,30363,30367,30372,30376,30381,30386,30391,30396,30401,30406,30411,30416,30420,30425],{"__ignoreMap":515},[1376,30351,30352],{"class":1378,"line":1379},[1376,30353,30354],{},"# Exemplo: instrumentação com OpenTelemetry em Python\n",[1376,30356,30357],{"class":1378,"line":984},[1376,30358,17346],{},[1376,30360,30361],{"class":1378,"line":981},[1376,30362,17313],{},[1376,30364,30365],{"class":1378,"line":1395},[1376,30366,1398],{"emptyLinePlaceholder":898},[1376,30368,30369],{"class":1378,"line":1401},[1376,30370,30371],{},"tracer = trace.get_tracer(__name__)\n",[1376,30373,30374],{"class":1378,"line":1407},[1376,30375,1398],{"emptyLinePlaceholder":898},[1376,30377,30378],{"class":1378,"line":1413},[1376,30379,30380],{},"@app.get(\"\u002Fpedidos\u002F{pedido_id}\")\n",[1376,30382,30383],{"class":1378,"line":1419},[1376,30384,30385],{},"async def get_pedido(pedido_id: int):\n",[1376,30387,30388],{"class":1378,"line":1424},[1376,30389,30390],{},"    # Cria um span (trecho do trace) automaticamente\n",[1376,30392,30393],{"class":1378,"line":1430},[1376,30394,30395],{},"    with tracer.start_as_current_span(\"get_pedido\") as span:\n",[1376,30397,30398],{"class":1378,"line":1436},[1376,30399,30400],{},"        span.set_attribute(\"pedido_id\", pedido_id)\n",[1376,30402,30403],{"class":1378,"line":1442},[1376,30404,30405],{},"        \n",[1376,30407,30408],{"class":1378,"line":1447},[1376,30409,30410],{},"        pedido = buscar_pedido(pedido_id)  # sub-span automático\n",[1376,30412,30413],{"class":1378,"line":1453},[1376,30414,30415],{},"        cliente = buscar_cliente(pedido.cliente_id)  # sub-span automático\n",[1376,30417,30418],{"class":1378,"line":1459},[1376,30419,30405],{},[1376,30421,30422],{"class":1378,"line":2200},[1376,30423,30424],{},"        span.set_attribute(\"cliente_id\", pedido.cliente_id)\n",[1376,30426,30427],{"class":1378,"line":2208},[1376,30428,30429],{},"        return {**pedido, \"cliente\": cliente}\n",[283,30431,30433],{"id":30432},"erro-5-ignorar-consistência-eventual","Erro 5: Ignorar consistência eventual",[86,30435,30436,30438],{},[89,30437,4164],{},": O serviço de pedidos confirma um pedido, mas o serviço\nde estoque ainda não processou a reserva (fila com atraso). O cliente vê\n\"pedido confirmado\" mas o estoque não foi reservado. Resultado: venda sem\nestoque.",[86,30440,30441,4171],{},[89,30442,4170],{},[506,30444,30446],{"className":3439,"code":30445,"language":3441,"meta":515,"style":515},"# ❌ ERRADO: assumir que tudo é imediato\ndef criar_pedido(dados):\n    pedido = salvar_pedido(dados)\n    publicar_evento_pedido_criado(pedido)\n    return {\"status\": \"confirmado\"}  # afirma confirmação imediata\n    # Mas estoque ainda pode não ter reservado!\n",[513,30447,30448,30453,30458,30463,30468,30473],{"__ignoreMap":515},[1376,30449,30450],{"class":1378,"line":1379},[1376,30451,30452],{},"# ❌ ERRADO: assumir que tudo é imediato\n",[1376,30454,30455],{"class":1378,"line":984},[1376,30456,30457],{},"def criar_pedido(dados):\n",[1376,30459,30460],{"class":1378,"line":981},[1376,30461,30462],{},"    pedido = salvar_pedido(dados)\n",[1376,30464,30465],{"class":1378,"line":1395},[1376,30466,30467],{},"    publicar_evento_pedido_criado(pedido)\n",[1376,30469,30470],{"class":1378,"line":1401},[1376,30471,30472],{},"    return {\"status\": \"confirmado\"}  # afirma confirmação imediata\n",[1376,30474,30475],{"class":1378,"line":1407},[1376,30476,30477],{},"    # Mas estoque ainda pode não ter reservado!\n",[86,30479,30480,4171],{},[89,30481,4182],{},[506,30483,30485],{"className":3439,"code":30484,"language":3441,"meta":515,"style":515},"# ✅ CERTO: usar Saga e informar status correto\ndef criar_pedido(dados):\n    pedido = salvar_pedido(dados, status=\"processando\")  # não confirmado ainda\n    publicar_evento_pedido_criado(pedido)\n    return {\"status\": \"processando\", \"pedido_id\": pedido.id}\n    # Cliente sabe que está em processamento\n\n# Quando estoque reserva e pagamento confirma:\ndef on_estoque_reservado(evento):\n    if evento.sucesso:\n        atualizar_pedido_status(evento.pedido_id, \"confirmado\")\n        notificar_cliente(evento.pedido_id, \"Pedido confirmado!\")\n    else:\n        atualizar_pedido_status(evento.pedido_id, \"cancelado_sem_estoque\")\n        notificar_cliente(evento.pedido_id, \"Produto esgotado, pedido cancelado\")\n",[513,30486,30487,30492,30496,30501,30505,30510,30515,30519,30524,30529,30534,30539,30544,30548,30553],{"__ignoreMap":515},[1376,30488,30489],{"class":1378,"line":1379},[1376,30490,30491],{},"# ✅ CERTO: usar Saga e informar status correto\n",[1376,30493,30494],{"class":1378,"line":984},[1376,30495,30457],{},[1376,30497,30498],{"class":1378,"line":981},[1376,30499,30500],{},"    pedido = salvar_pedido(dados, status=\"processando\")  # não confirmado ainda\n",[1376,30502,30503],{"class":1378,"line":1395},[1376,30504,30467],{},[1376,30506,30507],{"class":1378,"line":1401},[1376,30508,30509],{},"    return {\"status\": \"processando\", \"pedido_id\": pedido.id}\n",[1376,30511,30512],{"class":1378,"line":1407},[1376,30513,30514],{},"    # Cliente sabe que está em processamento\n",[1376,30516,30517],{"class":1378,"line":1413},[1376,30518,1398],{"emptyLinePlaceholder":898},[1376,30520,30521],{"class":1378,"line":1419},[1376,30522,30523],{},"# Quando estoque reserva e pagamento confirma:\n",[1376,30525,30526],{"class":1378,"line":1424},[1376,30527,30528],{},"def on_estoque_reservado(evento):\n",[1376,30530,30531],{"class":1378,"line":1430},[1376,30532,30533],{},"    if evento.sucesso:\n",[1376,30535,30536],{"class":1378,"line":1436},[1376,30537,30538],{},"        atualizar_pedido_status(evento.pedido_id, \"confirmado\")\n",[1376,30540,30541],{"class":1378,"line":1442},[1376,30542,30543],{},"        notificar_cliente(evento.pedido_id, \"Pedido confirmado!\")\n",[1376,30545,30546],{"class":1378,"line":1447},[1376,30547,4576],{},[1376,30549,30550],{"class":1378,"line":1453},[1376,30551,30552],{},"        atualizar_pedido_status(evento.pedido_id, \"cancelado_sem_estoque\")\n",[1376,30554,30555],{"class":1378,"line":1459},[1376,30556,30557],{},"        notificar_cliente(evento.pedido_id, \"Produto esgotado, pedido cancelado\")\n",[283,30559,30561],{"id":30560},"erro-6-granularidade-errada-serviços-pequenos-demais-ou-grandes-demais","Erro 6: Granularidade errada (serviços pequenos demais ou grandes demais)",[86,30563,30564,4171],{},[89,30565,4164],{},[528,30567,30568,30574],{},[20,30569,30570,30573],{},[89,30571,30572],{},"Pequenos demais",": cada endpoint vira um microserviço (\"serviço de\ncriar pedido\", \"serviço de listar pedido\", \"serviço de cancelar pedido\").\n50 serviços para o que deveria ser 1. A comunicação entre eles é mais\ncomplexa que a lógica de negócio.",[20,30575,30576,30579],{},[89,30577,30578],{},"Grandes demais",": um \"microserviço\" que faz tudo do e-commerce (catálogo,\ncarrinho, checkout, pagamentos, envio). É um monolito disfarçado.",[86,30581,30582,30585,30586,30589],{},[89,30583,30584],{},"Como acertar a granularidade",": Agrupe por ",[89,30587,30588],{},"domínio de negócio"," (conceito\nde Domain-Driven Design). \"Pedidos\" é um domínio. \"Catálogo\" é outro.\n\"Pagamentos\" é outro. Dentro de cada, pode haver múltiplas operações, mas\npertencem ao mesmo serviço.",[12,30591,30593],{"id":30592},"caso-real-fintech-que-escalou-de-5k-a-2m-usuários-caso-real","Caso real: fintech que escalou de 5K a 2M usuários {#caso-real}",[86,30595,30596],{},"Uma fintech de pagamentos começou como monolito em Ruby on Rails com 5K\nusuários. Conforme cresceu, sentiu a dor:",[528,30598,30599,30605,30611],{},[20,30600,30601,30604],{},[89,30602,30603],{},"50K usuários",": deploys de 30 min (o monolito era grande), times\nbloqueando uns aos outros",[20,30606,30607,30610],{},[89,30608,30609],{},"200K usuários",": o serviço de consulta de saldo sobrecarregava todo o\nsistema (era a operação mais frequente)",[20,30612,30613,30616],{},[89,30614,30615],{},"500K usuários",": time de 20 desenvolvedores, mudanças em uma área quebravam\noutras",[86,30618,30619,30622],{},[89,30620,30621],{},"Migração gradual"," (8 meses):",[17,30624,30625,30631,30637,30643],{},[20,30626,30627,30630],{},[89,30628,30629],{},"Extraíram primeiro o serviço de consulta de saldo"," (mais trafegado)\n→ reduziu 60% da carga do monolito",[20,30632,30633,30636],{},[89,30634,30635],{},"Extraíram o serviço de notificações"," → parou de travar o processamento\nprincipal quando o SES (e-mail) tinha lentidão",[20,30638,30639,30642],{},[89,30640,30641],{},"Extraíram o serviço de anti-fraude"," → time dedicado, deploy independente,\niteração rápida em regras de risco",[20,30644,30645,30648],{},[89,30646,30647],{},"Migraram para Kubernetes"," com auto-scaling → sobreviveram a picos de\nBlack Friday sem intervenção manual",[86,30650,30651,4171],{},[89,30652,30653],{},"Resultado aos 2M usuários",[528,30655,30656,30659,30662,30665,30668,30671],{},[20,30657,30658],{},"12 microserviços (não 50 — granularidade por domínio)",[20,30660,30661],{},"Deploy de cada serviço: 3-5 minutos",[20,30663,30664],{},"Times independentes (4 squads de 5 pessoas)",[20,30666,30667],{},"Latência P99: 200ms (antes: 2-5s nos picos)",[20,30669,30670],{},"Custo de infra: $8.000\u002Fmês (antes: $12.000\u002Fmês com monolito superdimensionado)",[20,30672,30673],{},"Zero downtime em deploys",[86,30675,30676,30678],{},[89,30677,4814],{},": Não migre tudo de uma vez. Extraia o serviço que mais dói primeiro\n(o de maior tráfego ou maior conflito entre times). Valide. Depois extraia o\npróximo. Migração incremental reduz risco e entrega valor desde o primeiro\nserviço extraído.",[968,30680],{},[86,30682,30683,30686,30687,522],{},[89,30684,30685],{},"Desenhando sistemas escaláveis?"," A Inicialize Tec arquiteta e implementa\nmicroserviços, desde a extração do monolito até o deploy em Kubernetes com\nobservabilidade completa. ",[23,30688,2881],{"href":978},[2883,30690,30691],{},"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 .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 pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":515,"searchDepth":981,"depth":981,"links":30693},[30694,30695,30698,30702,30708,30713,30717,30722,30727,30730,30738],{"id":14,"depth":984,"text":15},{"id":28162,"depth":984,"text":28163,"children":30696},[30697],{"id":28231,"depth":981,"text":28232},{"id":28325,"depth":984,"text":28326,"children":30699},[30700,30701],{"id":28336,"depth":981,"text":28337},{"id":28372,"depth":981,"text":28373},{"id":28414,"depth":984,"text":28415,"children":30703},[30704,30705,30706,30707],{"id":28418,"depth":981,"text":6300},{"id":28491,"depth":981,"text":28492},{"id":28547,"depth":981,"text":28548},{"id":28588,"depth":981,"text":28589},{"id":28652,"depth":984,"text":28653,"children":30709},[30710,30711,30712],{"id":28656,"depth":981,"text":28657},{"id":28704,"depth":981,"text":28705},{"id":28846,"depth":981,"text":28847},{"id":29009,"depth":984,"text":29010,"children":30714},[30715,30716],{"id":29038,"depth":981,"text":29039},{"id":29147,"depth":981,"text":29148},{"id":29177,"depth":984,"text":29178,"children":30718},[30719,30720,30721],{"id":29181,"depth":981,"text":29182},{"id":1572,"depth":981,"text":1573},{"id":20923,"depth":981,"text":19071},{"id":29722,"depth":984,"text":29723,"children":30723},[30724,30725,30726],{"id":29745,"depth":981,"text":29746},{"id":25962,"depth":981,"text":25963},{"id":29988,"depth":981,"text":29989},{"id":30026,"depth":984,"text":30027,"children":30728},[30729],{"id":30106,"depth":981,"text":30107},{"id":819,"depth":984,"text":820,"children":30731},[30732,30733,30734,30735,30736,30737],{"id":30191,"depth":981,"text":30192},{"id":30220,"depth":981,"text":30221},{"id":30304,"depth":981,"text":30305},{"id":30333,"depth":981,"text":30334},{"id":30432,"depth":981,"text":30433},{"id":30560,"depth":981,"text":30561},{"id":30592,"depth":984,"text":30593},"Como desenhar microserviços que escalam de 1K a 10M usuários. Padrões, K8s, service mesh, saga, observabilidade e custos reais de infraestrutura.",[16179,30741,30742,30743,29191,30744,30745],"arquitetura escalável","sistemas escaláveis","arquitetura de software","service mesh","API gateway",{},"\u002Fsistemas\u002Farquitetura-de-microservicos-escalaveis",{"title":28099,"description":30739},"arquitetura-de-microservicos-escalaveis","sistemas\u002Farquitetura-de-microservicos-escalaveis","wcyQuvmuUWtZ18jqvq2fwBy3TKWSLFS5F4ul09_RbSI",{"id":30753,"title":30754,"author":7,"body":30755,"category":28085,"date":1028,"description":31918,"extension":1030,"keywords":31919,"meta":31923,"navigation":898,"path":31924,"seo":31925,"slug":31926,"stem":31927,"updated":1028,"__hash__":31928},"content\u002Fsistemas\u002Fmigrar-monolito-para-microservicos.md","Migrar monolito para microserviços: o padrão Strangler Fig",{"type":9,"value":30756,"toc":31883},[30757,30759,30821,30825,30839,30842,30849,30856,30860,30863,30889,30969,30973,30976,30980,30987,31001,31005,31012,31019,31023,31030,31034,31037,31041,31048,31052,31055,31061,31065,31068,31074,31077,31100,31206,31217,31221,31224,31226,31229,31293,31296,31300,31303,31328,31331,31335,31338,31342,31349,31353,31358,31362,31365,31369,31372,31441,31453,31457,31464,31467,31493,31496,31500,31503,31509,31534,31544,31553,31663,31669,31678,31681,31685,31689,31696,31700,31703,31707,31713,31717,31720,31724,31727,31729,31797,31801,31870,31872,31880],[12,30758,15],{"id":14},[17,30760,30761,30766,30772,30778,30784,30790,30796,30802,30807,30812,30816],{},[20,30762,30763],{},[23,30764,30765],{"href":25},"O que é o padrão Strangler Fig",[20,30767,30768],{},[23,30769,30771],{"href":30770},"#nao-reescreva","Por que \"reescrever tudo de uma vez\" dá errado",[20,30773,30774],{},[23,30775,30777],{"href":30776},"#passo-a-passo","Como funciona, passo a passo",[20,30779,30780],{},[23,30781,30783],{"href":30782},"#acl","O Anti-Corruption Layer (ACL)",[20,30785,30786],{},[23,30787,30789],{"href":30788},"#roteamento","Roteamento: API Gateway e feature flags",[20,30791,30792],{},[23,30793,30795],{"href":30794},"#banco","Migração de banco de dados",[20,30797,30798],{},[23,30799,30801],{"href":30800},"#quando-parar","Quando parar de migrar",[20,30803,30804],{},[23,30805,30806],{"href":26873},"Exemplo prático: extraindo o checkout",[20,30808,30809],{},[23,30810,30811],{"href":67},"Anti-padrões e armadilhas reais",[20,30813,30814],{},[23,30815,74],{"href":73},[20,30817,30818],{},[23,30819,30820],{"href":79},"Checklist de migração",[12,30822,30824],{"id":30823},"o-que-é-o-padrão-strangler-fig-o-que-e","O que é o padrão Strangler Fig {#o-que-e}",[86,30826,15435,30827,30830,30831,30834,30835,30838],{},[89,30828,30829],{},"Strangler Fig"," é um padrão de migração incremental descrito por Martin\nFowler em 2004. O nome vem de uma árvore real, a ",[89,30832,30833],{},"figueira estranguladora","\n(",[501,30836,30837],{},"strangler fig","), uma planta que cresce ao redor de outra árvore hospedeira.\nEla vai envolvendo o tronco aos poucos, ganhando espaço, até que um dia a\nárvore original morre e a figueira ocupa seu lugar — sem que em nenhum\nmomento a floresta ficou sem árvore.",[86,30840,30841],{},"A analogia no software é direta: em vez de derrubar o monolito (a árvore\nvelha) e plantar um sistema novo do zero, você vai envolvendo o monolito com\nserviços novos. Cada pedço de funcionalidade que sai do monolito vira um\nmicroserviço ao redor dele. O tráfego é desviado pouco a pouco para os\nserviços novos. Quando o monolito não tem mais nada útil dentro dele, você o\n\"desliga\" — e os microserviços já estão sustentando tudo sozinhos.",[86,30843,30844,30845,30848],{},"A ideia central é ",[89,30846,30847],{},"nunca parar de entregar valor"," durante a migração. O\nsistema continua no ar, os clientes continuam usando, o time continua\nlançando features. A migração acontece nas bordas, em pedaços pequenos e\nreversíveis.",[710,30850,30851],{},[86,30852,30853,30855],{},[89,30854,8214],{},": cada extração deve ser pequena o suficiente para ser\nrevertida em minutos se algo der errado. Se uma extração não pode ser\nrevertida, ela é grande demais.",[12,30857,30859],{"id":30858},"por-que-reescrever-tudo-de-uma-vez-dá-errado-nao-reescreva","Por que \"reescrever tudo de uma vez\" dá errado {#nao-reescreva}",[86,30861,30862],{},"A tentação clássica é: \"nosso monolito está uma bagunça, vamos congelar as\nfeatures por 6 meses e reescrever tudo limpo\". Isso quase sempre termina\nmal. A história se repete:",[17,30864,30865,30871,30877,30883],{},[20,30866,30867,30870],{},[89,30868,30869],{},"O monolito continua evoluindo",": negócio não para. Enquanto o time novo\nreescreve, o monolito recebe features que a reescrita não conhece. No fim,\na reescrita está sempre atrás.",[20,30872,30873,30876],{},[89,30874,30875],{},"Big bang deployment",": no dia de trocar, você descobre mil edge cases que\nsó existiam em produção. Sem rollback fácil, vira madrugada de incidente.",[20,30878,30879,30882],{},[89,30880,30881],{},"Ninguém aprende com o monolito",": a reescrita repete erros que o monolito\njá tinha corrigido em produção há anos.",[20,30884,30885,30888],{},[89,30886,30887],{},"ROI só no fim",": 6 meses sem entrega de valor, e só no dia D você sabe\nse funcionou. Risco enorme de ter gastado muito em algo que não atende.",[165,30890,30891,30903],{},[168,30892,30893],{},[171,30894,30895,30898,30901],{},[174,30896,30897],{},"Abordagem",[174,30899,30900],{},"Reescrita big-bang",[174,30902,30829],{},[184,30904,30905,30916,30927,30938,30947,30958],{},[171,30906,30907,30910,30913],{},[189,30908,30909],{},"Entrega de valor durante migração",[189,30911,30912],{},"Congelada",[189,30914,30915],{},"Contínua",[171,30917,30918,30921,30924],{},[189,30919,30920],{},"Risco por etapa",[189,30922,30923],{},"Muito alto",[189,30925,30926],{},"Baixo (reversível)",[171,30928,30929,30932,30935],{},[189,30930,30931],{},"Feedback do usuário",[189,30933,30934],{},"Só no fim",[189,30936,30937],{},"A cada extração",[171,30939,30940,30943,30945],{},[189,30941,30942],{},"Acompanha evolução do negócio",[189,30944,19550],{},[189,30946,10629],{},[171,30948,30949,30952,30955],{},[189,30950,30951],{},"Tempo para primeiro microserviço em prod",[189,30953,30954],{},"Meses",[189,30956,30957],{},"Semanas",[171,30959,30960,30963,30966],{},[189,30961,30962],{},"Rollback",[189,30964,30965],{},"Quase impossível",[189,30967,30968],{},"Por extração",[12,30970,30972],{"id":30971},"como-funciona-passo-a-passo-passo-a-passo","Como funciona, passo a passo {#passo-a-passo}",[86,30974,30975],{},"O fluxo do Strangler Fig é um ciclo que se repete para cada funcionalidade\nextraída:",[283,30977,30979],{"id":30978},"passo-1-identificar-a-funcionalidade-candidata","Passo 1 — Identificar a funcionalidade candidata",[86,30981,30982,30983,30986],{},"Procure uma ",[89,30984,30985],{},"costura"," (seam) no monolito: uma parte com fronteiras razoavelmente\nclaras, baixo acoplamento com o resto e que traria valor sendo independente.\nBons candidatos:",[528,30988,30989,30992,30995,30998],{},[20,30990,30991],{},"Funcionalidade com escala diferente do resto (ex.: busca vs. checkout).",[20,30993,30994],{},"Domínio que muda com frequência e trava deploys do monolito todo.",[20,30996,30997],{},"Integração com sistema externo que merece isolar.",[20,30999,31000],{},"Funcionalidade que um novo time vai assumir.",[283,31002,31004],{"id":31003},"passo-2-colocar-um-interceptador-na-frente","Passo 2 — Colocar um \"interceptador\" na frente",[86,31006,31007,31008,31011],{},"Antes de mexer no monolito, coloque um ",[89,31009,31010],{},"ponto de roteamento"," entre clientes\ne o monolito. Pode ser um API Gateway, um proxy, ou até um pequeno\n\"facade\" dentro do próprio monolito. A função: redirecionar tráfego daquela\nfuncionalidade para onde você quiser, sem que o cliente perceba.",[86,31013,31014,31015,31018],{},"Neste momento, ",[89,31016,31017],{},"100% do tráfego ainda vai para o monolito",". Você só criou a\nalavanca de roteamento.",[283,31020,31022],{"id":31021},"passo-3-construir-o-novo-microserviço","Passo 3 — Construir o novo microserviço",[86,31024,31025,31026,31029],{},"Construa o serviço novo, com seu próprio banco (ou réplica de leitura), sua\nprópria pipeline, seus próprios testes. Ele implementa ",[89,31027,31028],{},"só"," a funcionalidade\nescolhida.",[283,31031,31033],{"id":31032},"passo-4-desviar-uma-fração-do-tráfego","Passo 4 — Desviar uma fração do tráfego",[86,31035,31036],{},"Roteie 1% (ou só um grupo interno, ou só um cliente piloto) para o novo serviço.\nCompare com o monolito: métricas, erros, latência, resultado de negócio. Se\nestiver igual ou melhor, aumente: 5% → 25% → 50% → 100%.",[283,31038,31040],{"id":31039},"passo-5-remover-a-funcionalidade-do-monolito","Passo 5 — Remover a funcionalidade do monolito",[86,31042,31043,31044,31047],{},"Quando 100% do tráfego está no novo serviço, ",[89,31045,31046],{},"apague"," o código antigo do\nmonolito. Esse é o momento em que a figueira \"estrangulou\" aquele pedaço.\nA árvore velha ficou menor, o novo serviço ocupa aquele espaço.",[283,31049,31051],{"id":31050},"passo-6-repetir","Passo 6 — Repetir",[86,31053,31054],{},"Volte ao Passo 1 com outra funcionalidade. O ciclo se repete até o monolito\nvirar uma casca vazia — ou até parar antes (veremos quando).",[506,31056,31059],{"className":31057,"code":31058,"language":511},[509],"           Cliente\n             │\n         [Gateway]\n        ╱   │   ╲       ← roteamento por funcionalidade\n       ╱    │    ╲\n  [MS-novo] [Monolito cada vez menor]\n",[513,31060,31058],{"__ignoreMap":515},[12,31062,31064],{"id":31063},"o-anti-corruption-layer-acl-acl","O Anti-Corruption Layer (ACL) {#acl}",[86,31066,31067],{},"Quando o novo microserviço precisa falar com o monolito (ou vice-versa), surge\num perigo silencioso: o modelo bagunçado do monolito \"vaza\" para o serviço\nnovo e o contamina. Você sai de um monolito sujo para microserviços sujos.",[86,31069,15435,31070,31073],{},[89,31071,31072],{},"Anti-Corruption Layer"," (camada anticorrupção) é uma tradução entre os\ndois mundos. Pense numa embaixada: você fala com o embaixador no seu idioma,\nele fala com o país estrangeiro no idioma deles. Você nunca precisa aprender\no idioma bagunçado do monolito, e o monolito nunca aprende o seu modelo limpo.",[86,31075,31076],{},"Na prática, o ACL é um pequeno componente (pode ser um serviço separado, um\nadapter, ou parte do gateway) que:",[528,31078,31079,31086,31097],{},[20,31080,31081,31082,31085],{},"Recebe chamadas no ",[89,31083,31084],{},"modelo novo"," (limpo, alinhado ao domínio).",[20,31087,31088,31089,31092,31093,31096],{},"Traduz para o ",[89,31090,31091],{},"modelo legado"," (tabelas espalhadas, campos ",[513,31094,31095],{},"campo_aux_2",",\nlógica escondida em triggers).",[20,31098,31099],{},"Esconde as idiossincrasias do monolito do resto do mundo novo.",[506,31101,31103],{"className":3439,"code":31102,"language":3441,"meta":515,"style":515},"# ACL: traduz Pedido (modelo novo) para o formato que o monolito espera\n\nclass PedidoAcl:\n    def __init__(self, monolito_client):\n        self.monolito = monolito_client\n\n    def criar_pedido(self, pedido: Pedido) -> str:\n        # Modelo novo: limpo, legível\n        payload_legado = {\n            \"TBL_PEDIDO\": {\n                \"PED_ID_CLI\": pedido.cliente_id,         # campo renomeado\n                \"PED_VLR_TOT\": pedido.total,             # tipo ajustado\n                \"PED_DT_INC\": pedido.criado_em.strftime(\"%Y%m%d\"),  # formato antigo\n                \"PED_STA\": \"I\",                          # status inventado pelo legado\n            },\n            \"TBL_PEDIDO_AUX\": {\n                \"AUX_CAMPO2\": pedido.cupom or \"\",        # campo genérico reutilizado\n            },\n        }\n        resposta = self.monolito.gravar(payload_legado)\n        return resposta[\"PED_ID\"]\n",[513,31104,31105,31110,31114,31119,31124,31129,31133,31138,31143,31148,31153,31158,31163,31168,31173,31177,31182,31187,31191,31196,31201],{"__ignoreMap":515},[1376,31106,31107],{"class":1378,"line":1379},[1376,31108,31109],{},"# ACL: traduz Pedido (modelo novo) para o formato que o monolito espera\n",[1376,31111,31112],{"class":1378,"line":984},[1376,31113,1398],{"emptyLinePlaceholder":898},[1376,31115,31116],{"class":1378,"line":981},[1376,31117,31118],{},"class PedidoAcl:\n",[1376,31120,31121],{"class":1378,"line":1395},[1376,31122,31123],{},"    def __init__(self, monolito_client):\n",[1376,31125,31126],{"class":1378,"line":1401},[1376,31127,31128],{},"        self.monolito = monolito_client\n",[1376,31130,31131],{"class":1378,"line":1407},[1376,31132,1398],{"emptyLinePlaceholder":898},[1376,31134,31135],{"class":1378,"line":1413},[1376,31136,31137],{},"    def criar_pedido(self, pedido: Pedido) -> str:\n",[1376,31139,31140],{"class":1378,"line":1419},[1376,31141,31142],{},"        # Modelo novo: limpo, legível\n",[1376,31144,31145],{"class":1378,"line":1424},[1376,31146,31147],{},"        payload_legado = {\n",[1376,31149,31150],{"class":1378,"line":1430},[1376,31151,31152],{},"            \"TBL_PEDIDO\": {\n",[1376,31154,31155],{"class":1378,"line":1436},[1376,31156,31157],{},"                \"PED_ID_CLI\": pedido.cliente_id,         # campo renomeado\n",[1376,31159,31160],{"class":1378,"line":1442},[1376,31161,31162],{},"                \"PED_VLR_TOT\": pedido.total,             # tipo ajustado\n",[1376,31164,31165],{"class":1378,"line":1447},[1376,31166,31167],{},"                \"PED_DT_INC\": pedido.criado_em.strftime(\"%Y%m%d\"),  # formato antigo\n",[1376,31169,31170],{"class":1378,"line":1453},[1376,31171,31172],{},"                \"PED_STA\": \"I\",                          # status inventado pelo legado\n",[1376,31174,31175],{"class":1378,"line":1459},[1376,31176,5891],{},[1376,31178,31179],{"class":1378,"line":2200},[1376,31180,31181],{},"            \"TBL_PEDIDO_AUX\": {\n",[1376,31183,31184],{"class":1378,"line":2208},[1376,31185,31186],{},"                \"AUX_CAMPO2\": pedido.cupom or \"\",        # campo genérico reutilizado\n",[1376,31188,31189],{"class":1378,"line":2219},[1376,31190,5891],{},[1376,31192,31193],{"class":1378,"line":2227},[1376,31194,31195],{},"        }\n",[1376,31197,31198],{"class":1378,"line":2241},[1376,31199,31200],{},"        resposta = self.monolito.gravar(payload_legado)\n",[1376,31202,31203],{"class":1378,"line":2253},[1376,31204,31205],{},"        return resposta[\"PED_ID\"]\n",[86,31207,31208,31209,31212,31213,31216],{},"Sem o ACL, o ",[513,31210,31211],{},"cupom"," viraria ",[513,31214,31215],{},"AUX_CAMPO2"," espalhado por todo o serviço novo —\na bagunça teria migrado. Com o ACL, a sujeira fica num lugar só, fácil de\njogar fora quando o monolito morrer.",[12,31218,31220],{"id":31219},"roteamento-api-gateway-e-feature-flags-roteamento","Roteamento: API Gateway e feature flags {#roteamento}",[86,31222,31223],{},"O roteamento é o coração operacional do Strangler Fig. As duas ferramentas\nmais usadas:",[283,31225,6300],{"id":28418},[86,31227,31228],{},"Um gateway (Kong, Traefik, AWS API Gateway, Envoy) recebe todas as requisições\ne decide, por path, para onde mandar:",[506,31230,31232],{"className":1656,"code":31231,"language":1658,"meta":515,"style":515},"# Exemplo conceitual de rotas no gateway\nroutes:\n  - path: \u002Fapi\u002Fcheckout\u002F*        # extraído → vai para o novo serviço\n    upstream: checkout-service\n  - path: \u002Fapi\u002F*                 # resto → continua no monolito\n    upstream: monolito\n",[513,31233,31234,31239,31246,31260,31270,31284],{"__ignoreMap":515},[1376,31235,31236],{"class":1378,"line":1379},[1376,31237,31238],{"class":1665},"# Exemplo conceitual de rotas no gateway\n",[1376,31240,31241,31244],{"class":1378,"line":984},[1376,31242,31243],{"class":1675},"routes",[1376,31245,1750],{"class":1671},[1376,31247,31248,31250,31252,31254,31257],{"class":1378,"line":981},[1376,31249,1906],{"class":1671},[1376,31251,2592],{"class":1675},[1376,31253,1679],{"class":1671},[1376,31255,31256],{"class":1682},"\u002Fapi\u002Fcheckout\u002F*",[1376,31258,31259],{"class":1665},"        # extraído → vai para o novo serviço\n",[1376,31261,31262,31265,31267],{"class":1378,"line":1395},[1376,31263,31264],{"class":1675},"    upstream",[1376,31266,1679],{"class":1671},[1376,31268,31269],{"class":1682},"checkout-service\n",[1376,31271,31272,31274,31276,31278,31281],{"class":1378,"line":1401},[1376,31273,1906],{"class":1671},[1376,31275,2592],{"class":1675},[1376,31277,1679],{"class":1671},[1376,31279,31280],{"class":1682},"\u002Fapi\u002F*",[1376,31282,31283],{"class":1665},"                 # resto → continua no monolito\n",[1376,31285,31286,31288,31290],{"class":1378,"line":1407},[1376,31287,31264],{"class":1675},[1376,31289,1679],{"class":1671},[1376,31291,31292],{"class":1682},"monolito\n",[86,31294,31295],{},"Vantagem: roteamento centralizado, fácil de mudar, sem mexer no código do\nmonolito. Quando a extração terminar, só remove a rota legada.",[283,31297,31299],{"id":31298},"feature-flags-para-migração-interna","Feature flags para migração interna",[86,31301,31302],{},"Para funcionalidades dentro do mesmo backend, use feature flags (LaunchDarkly,\nUnleash, Flagsmith) para decidir por usuário\u002Ftenant se a chamada usa o código\nantigo ou o novo serviço:",[506,31304,31306],{"className":3439,"code":31305,"language":3441,"meta":515,"style":515},"def processar_pagamento(pedido):\n    if feature_flag.usar_servico_novo(pedido.cliente_id):\n        return pagamento_service_novo.cobrar(pedido)   # microserviço\n    return gateway_monolito.cobrar(pedido)              # legado\n",[513,31307,31308,31313,31318,31323],{"__ignoreMap":515},[1376,31309,31310],{"class":1378,"line":1379},[1376,31311,31312],{},"def processar_pagamento(pedido):\n",[1376,31314,31315],{"class":1378,"line":984},[1376,31316,31317],{},"    if feature_flag.usar_servico_novo(pedido.cliente_id):\n",[1376,31319,31320],{"class":1378,"line":981},[1376,31321,31322],{},"        return pagamento_service_novo.cobrar(pedido)   # microserviço\n",[1376,31324,31325],{"class":1378,"line":1395},[1376,31326,31327],{},"    return gateway_monolito.cobrar(pedido)              # legado\n",[86,31329,31330],{},"Assim você libera o novo serviço primeiro para clientes beta, compara\ncomportamento e expande sem deploys arriscados.",[12,31332,31334],{"id":31333},"migração-de-banco-de-dados-banco","Migração de banco de dados {#banco}",[86,31336,31337],{},"O banco é geralmente a parte mais difícil da migração. O monolito costuma ter\num banco gigante e compartilhado, e os dados não se separam em domínios tão\nfacilmente quanto o código. Estratégias, do mais simples ao mais avançado:",[283,31339,31341],{"id":31340},"_1-banco-compartilhado-não-recomendado-mas-realista","1. Banco compartilhado (não recomendado, mas realista)",[86,31343,31344,31345,31348],{},"O novo serviço acessa o mesmo banco do monolito, com permissão só de leitura\nem tabelas que precisa. Rápido para começar, mas ",[89,31346,31347],{},"quebra o isolamento"," —\nmudança de schema do monolito quebra o serviço novo. Use só como passo\ntemporário.",[283,31350,31352],{"id":31351},"_2-réplica-de-leitura-via-cdc","2. Réplica de leitura via CDC",[86,31354,20776,31355,31357],{},[89,31356,24868],{}," (Change Data Capture — captura de mudanças de dados) com\nDebezium para replicar as tabelas relevantes do monolito para o banco do\nnovo serviço. O serviço lê do seu próprio banco (rápido), e os dados se\natualizam em segundos via stream de mudanças. Mantém isolamento sem mudar o\nmonolito.",[283,31359,31361],{"id":31360},"_3-sincronização-por-eventos","3. Sincronização por eventos",[86,31363,31364],{},"O monolito publica eventos (\"pedido criado\", \"cliente atualizado\") num broker\n(Kafka) e o novo serviço consome e monta sua própria visão dos dados. É o\nmodelo mais limpo, mas exige que o monolito aprenda a publicar eventos —\nmodificação necessária.",[283,31366,31368],{"id":31367},"_4-separação-completa-estado-final","4. Separação completa (estado final)",[86,31370,31371],{},"O novo serviço é dono dos seus dados. O monolito deixa de escrever naquelas\ntabelas. Migração dos registros existentes feita em batch. Esta é a chegada\ndo \"serviço realmente independente\".",[165,31373,31374,31387],{},[168,31375,31376],{},[171,31377,31378,31380,31383,31385],{},[174,31379,1582],{},[174,31381,31382],{},"Esforço inicial",[174,31384,13951],{},[174,31386,1588],{},[184,31388,31389,31402,31415,31429],{},[171,31390,31391,31394,31396,31399],{},[189,31392,31393],{},"Banco compartilhado",[189,31395,20528],{},[189,31397,31398],{},"Nenhum",[189,31400,31401],{},"Primeira extração, ponte rápida",[171,31403,31404,31407,31409,31412],{},[189,31405,31406],{},"Réplica via CDC",[189,31408,16445],{},[189,31410,31411],{},"Alto (leitura)",[189,31413,31414],{},"Serviço só lê dados do monolito",[171,31416,31417,31420,31423,31426],{},[189,31418,31419],{},"Sincronização por eventos",[189,31421,31422],{},"Médio-alto",[189,31424,31425],{},"Alto",[189,31427,31428],{},"Serviço precisa reagir a mudanças",[171,31430,31431,31434,31436,31438],{},[189,31432,31433],{},"Separação completa",[189,31435,31425],{},[189,31437,7964],{},[189,31439,31440],{},"Estado final, dono dos dados",[710,31442,31443],{},[86,31444,31445,31448,31449,31452],{},[89,31446,31447],{},"Cuidado com joins cross-serviço",": quando os dados estão em bancos\ndiferentes, um ",[513,31450,31451],{},"JOIN"," trivial vira uma dor. Planeje agregações via\nmaterialized views, desnormalização ou chamadas de API com cache.",[12,31454,31456],{"id":31455},"quando-parar-de-migrar-quando-parar","Quando parar de migrar {#quando-parar}",[86,31458,31459,31460,31463],{},"Um erro comum é achar que o objetivo é \"extrair tudo\". Não é. O objetivo é\n",[89,31461,31462],{},"entregar valor",", e às vezes parar no meio é a decisão certa.",[86,31465,31466],{},"Pare de extrair quando:",[528,31468,31469,31475,31481,31487],{},[20,31470,31471,31474],{},[89,31472,31473],{},"O que sobrou no monolito é estável e baixo risco",": funcionalidades que\nnão mudam há anos, com escala sem pressão, podem ficar no monolito para\nsempre. Um monolito pequeno e estável é ótimo.",[20,31476,31477,31480],{},[89,31478,31479],{},"O custo da próxima extração supera o benefício",": as últimas costuras são\nas mais difíceis (alto acoplamento). Se a extração custa 3 meses e libera\npouco valor, mantenha no monolito.",[20,31482,31483,31486],{},[89,31484,31485],{},"A equipe precisa entregar features",": prioridade de negócio mudou. Pausar\na migração não é fracasso, é pragmatismo.",[20,31488,31489,31492],{},[89,31490,31491],{},"O monolito virou \"serviço CRUD\":"," quando só sobram telas simples e\nCRUDs, ele é praticamente um microserviço grande. Deixe quieto.",[86,31494,31495],{},"O Strangler Fig não é uma religião. É uma ferramenta. Use enquanto pagar.",[12,31497,31499],{"id":31498},"exemplo-prático-extraindo-o-checkout-exemplo","Exemplo prático: extraindo o checkout {#exemplo}",[86,31501,31502],{},"Cenário: e-commerce com monolito em Rails. Checkout é o gargalo de escala\n(black friday) e trava deploys. Vamos extraí-lo.",[86,31504,31505,31508],{},[89,31506,31507],{},"Semana 1 — Roteamento","\nColocamos um API Gateway na frente do monolito. Tudo ainda aponta para o\nmonolito. Métricas de baseline coletadas (latência, erro rate por path).",[86,31510,31511,31514,31515,1327,31518,1327,31520,31523,31524,1327,31527,1327,31530,31533],{},[89,31512,31513],{},"Semana 2-3 — Modelo e ACL","\nDefinimos o modelo ",[513,31516,31517],{},"Pedido",[513,31519,596],{},[513,31521,31522],{},"Pagamento"," do novo serviço. Criamos a\nACL que traduz para as tabelas ",[513,31525,31526],{},"pedidos",[513,31528,31529],{},"itens_ped",[513,31531,31532],{},"pgto"," do monolito.",[86,31535,31536,31539,31540,31543],{},[89,31537,31538],{},"Semana 4-6 — Novo serviço","\nConstruímos ",[513,31541,31542],{},"checkout-service"," em Go, com seu próprio banco (Postgres). Usa\nCDC do monolito para ter o catálogo de produtos e preços (réplica de leitura).",[86,31545,31546,31549,31550,31552],{},[89,31547,31548],{},"Semana 7 — Canary","\nGateway roteia ",[513,31551,31256],{}," para o novo serviço em 1% do tráfego\n(clientes marcados como beta). Comparação lado a lado:",[506,31554,31556],{"className":1656,"code":31555,"language":1658,"meta":515,"style":515},"# Config de canary no gateway\nroutes:\n  - path: \u002Fapi\u002Fcheckout\u002F*\n    strategy: canary\n    canary:\n      upstream: checkout-service\n      weight: 1        # 1% para o novo\n      fallback: monolito\n    monitor:\n      error_rate_threshold: 0.5%\n      latency_p99_threshold: 800ms\n      auto_rollback: true\n",[513,31557,31558,31563,31569,31580,31590,31597,31606,31618,31627,31634,31644,31654],{"__ignoreMap":515},[1376,31559,31560],{"class":1378,"line":1379},[1376,31561,31562],{"class":1665},"# Config de canary no gateway\n",[1376,31564,31565,31567],{"class":1378,"line":984},[1376,31566,31243],{"class":1675},[1376,31568,1750],{"class":1671},[1376,31570,31571,31573,31575,31577],{"class":1378,"line":981},[1376,31572,1906],{"class":1671},[1376,31574,2592],{"class":1675},[1376,31576,1679],{"class":1671},[1376,31578,31579],{"class":1682},"\u002Fapi\u002Fcheckout\u002F*\n",[1376,31581,31582,31585,31587],{"class":1378,"line":1395},[1376,31583,31584],{"class":1675},"    strategy",[1376,31586,1679],{"class":1671},[1376,31588,31589],{"class":1682},"canary\n",[1376,31591,31592,31595],{"class":1378,"line":1401},[1376,31593,31594],{"class":1675},"    canary",[1376,31596,1750],{"class":1671},[1376,31598,31599,31602,31604],{"class":1378,"line":1407},[1376,31600,31601],{"class":1675},"      upstream",[1376,31603,1679],{"class":1671},[1376,31605,31269],{"class":1682},[1376,31607,31608,31611,31613,31615],{"class":1378,"line":1413},[1376,31609,31610],{"class":1675},"      weight",[1376,31612,1679],{"class":1671},[1376,31614,5027],{"class":1770},[1376,31616,31617],{"class":1665},"        # 1% para o novo\n",[1376,31619,31620,31623,31625],{"class":1378,"line":1419},[1376,31621,31622],{"class":1675},"      fallback",[1376,31624,1679],{"class":1671},[1376,31626,31292],{"class":1682},[1376,31628,31629,31632],{"class":1378,"line":1424},[1376,31630,31631],{"class":1675},"    monitor",[1376,31633,1750],{"class":1671},[1376,31635,31636,31639,31641],{"class":1378,"line":1430},[1376,31637,31638],{"class":1675},"      error_rate_threshold",[1376,31640,1679],{"class":1671},[1376,31642,31643],{"class":1682},"0.5%\n",[1376,31645,31646,31649,31651],{"class":1378,"line":1436},[1376,31647,31648],{"class":1675},"      latency_p99_threshold",[1376,31650,1679],{"class":1671},[1376,31652,31653],{"class":1682},"800ms\n",[1376,31655,31656,31659,31661],{"class":1378,"line":1442},[1376,31657,31658],{"class":1675},"      auto_rollback",[1376,31660,1679],{"class":1671},[1376,31662,2182],{"class":1770},[86,31664,31665,31668],{},[89,31666,31667],{},"Semana 8-10 — Expansão","\nSem incidentes, subimos para 10% → 50% → 100%. Em 100%, o monolito para de\nreceber checkout.",[86,31670,31671,31674,31675,31677],{},[89,31672,31673],{},"Semana 11 — Limpeza","\nRemovemos o código de checkout do monolito. Apagamos as rotas legadas. O\nmonolito encolheu. O ",[513,31676,31542],{}," deploya sozinho, escala sozinho, e\nestá pronto para a próxima black friday.",[86,31679,31680],{},"Total: ~11 semanas, com valor entregue em produção desde a semana 7, e\nreversível a qualquer momento até a semana 10.",[12,31682,31684],{"id":31683},"anti-padrões-e-armadilhas-reais-armadilhas","Anti-padrões e armadilhas reais {#armadilhas}",[283,31686,31688],{"id":31687},"distribuído-demais","\"Distribuído demais\"",[86,31690,31691,31692,31695],{},"Cada extração cria um novo serviço, novo banco, nova pipeline. Extrair 40\nserviços de um monolito de 5 pessoas gera mais dor do que valor. ",[89,31693,31694],{},"A extração\ndeve seguir fronteiras de domínio e de time",", não fronteiras de classe.",[283,31697,31699],{"id":31698},"banho-de-dados-compartilhado-disfarçado","\"Banho de dados compartilhado disfarçado\"",[86,31701,31702],{},"Serviços \"separados\" que compartilham o mesmo banco não são microserviços —\nsão um monolito distribuído com latência de rede. O isolamento de dados é o\nque define o limite real.",[283,31704,31706],{"id":31705},"síndrome-do-bolso-cheio-de-features","\"Síndrome do bolso-cheio de features\"",[86,31708,31709,31710],{},"Aproveitar a migração para \"reformar\" a funcionalidade — adicionar features\nnovas, mudar regras de negócio, redesenhar UX. Isso quebra a comparabilidade\nentre o novo e o velho, dificultando o canary e o rollback. ",[89,31711,31712],{},"Primeiro migre\ncomportamento igual. Só depois evolua.",[283,31714,31716],{"id":31715},"sem-observabilidade-no-novo-serviço","\"Sem observabilidade no novo serviço\"",[86,31718,31719],{},"Colocar um serviço novo em produção sem tracing distribuído, métricas RED e\nlogs estruturados é dirigir no escuro. Você não consegue comparar com o\nmonolito, não detecta regressões, e o canary vira loteria.",[283,31721,31723],{"id":31722},"dependência-circular-nova-velho-nova","\"Dependência circular nova → velho → nova\"",[86,31725,31726],{},"O serviço novo chama o monolito, que chama o serviço novo. Latência explode\ne debugging vira pesadelo. Quebre o ciclo: evento assíncrono, ou consolide\na responsabilidade num único lado.",[12,31728,820],{"id":819},[17,31730,31731,31737,31746,31752,31758,31764,31773,31779,31785,31791],{},[20,31732,31733,31736],{},[89,31734,31735],{},"Reescrever em vez de estrangular",": parar o time por meses. Estrangule\nincrementalmente e entregue valor desde cedo.",[20,31738,31739,31742,31743,31745],{},[89,31740,31741],{},"Sem gateway\u002Fproxy antes de começar",": quando você precisa desviar\ntráfego, descobre que o cliente chama o monolito direto por IP. Coloque o\nroteamento ",[89,31744,13459],{}," de extrair qualquer coisa.",[20,31747,31748,31751],{},[89,31749,31750],{},"Extração grande e irreversível",": a primeira extração deve ser pequena e\nreversível para treinar o processo. Pegue uma funcionalidade de baixo risco\nprimeiro.",[20,31753,31754,31757],{},[89,31755,31756],{},"Ignorar o ACL",": o modelo sujo do monolito contamina o serviço novo.\nTraduça sempre numa camada isolada.",[20,31759,31760,31763],{},[89,31761,31762],{},"Misturar migração com evolução",": mudar regras durante a migração\nquebra o canary. Mantenha o comportamento idêntico até 100%.",[20,31765,31766,31769,31770,31772],{},[89,31767,31768],{},"Sem estratégia de dados",": começar o serviço novo sem definir se vai\ncompartilhar, replicar ou sincronizar dados. Decida o modelo de dados\n",[89,31771,13459],{}," de escrever a primeira linha do serviço.",[20,31774,31775,31778],{},[89,31776,31777],{},"Não comparar métricas novo vs. velho",": rodar canary sem dashboard lado\na lado. Você só descobre o problema quando o cliente reclama.",[20,31780,31781,31784],{},[89,31782,31783],{},"Não remover o código velho",": depois de 100% no novo, o código legado\nfica \"por via das dúvidas\". Vira dívida que ninguém entende mais. Remova.",[20,31786,31787,31790],{},[89,31788,31789],{},"Extrair sem fronteira de time",": um microserviço que ninguém é dono vira\nórfão. Só extraia se houver um time para assumi-lo.",[20,31792,31793,31796],{},[89,31794,31795],{},"Achar que Strangler Fig é para tudo",": algumas partes do monolito podem\ne devem ficar. Avalie custo\u002Fbenefício de cada extração.",[12,31798,31800],{"id":31799},"checklist-de-migração-checklist","Checklist de migração {#checklist}",[528,31802,31804,31810,31816,31822,31828,31834,31840,31846,31852,31858,31864],{"className":31803},[890],[20,31805,31807,31809],{"className":31806},[894],[896,31808],{"disabled":898,"type":899}," Gateway\u002Fproxy na frente do monolito antes de qualquer extração",[20,31811,31813,31815],{"className":31812},[894],[896,31814],{"disabled":898,"type":899}," Funcionalidade candidata tem fronteira de domínio e de time clara",[20,31817,31819,31821],{"className":31818},[894],[896,31820],{"disabled":898,"type":899}," ACL definida para traduzir modelo novo ↔ legado",[20,31823,31825,31827],{"className":31824},[894],[896,31826],{"disabled":898,"type":899}," Estratégia de dados escolhida (compartilhado, CDC, eventos, separação)",[20,31829,31831,31833],{"className":31830},[894],[896,31832],{"disabled":898,"type":899}," Novo serviço com observabilidade (logs, métricas RED, tracing)",[20,31835,31837,31839],{"className":31836},[894],[896,31838],{"disabled":898,"type":899}," Pipeline de CI\u002FCD independente para o novo serviço",[20,31841,31843,31845],{"className":31842},[894],[896,31844],{"disabled":898,"type":899}," Canary com comparação de métricas lado a lado e auto-rollback",[20,31847,31849,31851],{"className":31848},[894],[896,31850],{"disabled":898,"type":899}," Comportamento idêntico ao legado durante a migração (sem features novas)",[20,31853,31855,31857],{"className":31854},[894],[896,31856],{"disabled":898,"type":899}," Plano de remoção do código velho ao atingir 100%",[20,31859,31861,31863],{"className":31860},[894],[896,31862],{"disabled":898,"type":899}," Documentação da decisão de parar (se parar antes do fim)",[20,31865,31867,31869],{"className":31866},[894],[896,31868],{"disabled":898,"type":899}," Reversibilidade testada: simular rollback e medir tempo de recuperação",[968,31871],{},[86,31873,31874,31877,31878,522],{},[89,31875,31876],{},"Migrando do monolito para microserviços?"," A Inicialize Tec conduz migrações\nincrementais com Strangler Fig — da escolha das primeiras costuras ao\nroteamento, ACL e estratégia de dados, sem parar suas entregas.\n",[23,31879,2881],{"href":978},[2883,31881,31882],{},"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 .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 pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":515,"searchDepth":981,"depth":981,"links":31884},[31885,31886,31887,31888,31896,31897,31901,31907,31908,31909,31916,31917],{"id":14,"depth":984,"text":15},{"id":30823,"depth":984,"text":30824},{"id":30858,"depth":984,"text":30859},{"id":30971,"depth":984,"text":30972,"children":31889},[31890,31891,31892,31893,31894,31895],{"id":30978,"depth":981,"text":30979},{"id":31003,"depth":981,"text":31004},{"id":31021,"depth":981,"text":31022},{"id":31032,"depth":981,"text":31033},{"id":31039,"depth":981,"text":31040},{"id":31050,"depth":981,"text":31051},{"id":31063,"depth":984,"text":31064},{"id":31219,"depth":984,"text":31220,"children":31898},[31899,31900],{"id":28418,"depth":981,"text":6300},{"id":31298,"depth":981,"text":31299},{"id":31333,"depth":984,"text":31334,"children":31902},[31903,31904,31905,31906],{"id":31340,"depth":981,"text":31341},{"id":31351,"depth":981,"text":31352},{"id":31360,"depth":981,"text":31361},{"id":31367,"depth":981,"text":31368},{"id":31455,"depth":984,"text":31456},{"id":31498,"depth":984,"text":31499},{"id":31683,"depth":984,"text":31684,"children":31910},[31911,31912,31913,31914,31915],{"id":31687,"depth":981,"text":31688},{"id":31698,"depth":981,"text":31699},{"id":31705,"depth":981,"text":31706},{"id":31715,"depth":981,"text":31716},{"id":31722,"depth":981,"text":31723},{"id":819,"depth":984,"text":820},{"id":31799,"depth":984,"text":31800},"Como migrar uma aplicação monolítica para microserviços sem parar o time: o padrão Strangler Fig, anti-corruption layer e armadilhas reais.",[28172,16179,31920,30837,31921,31922],"migração","refatoração","arquitetura",{},"\u002Fsistemas\u002Fmigrar-monolito-para-microservicos",{"title":30754,"description":31918},"migrar-monolito-para-microservicos","sistemas\u002Fmigrar-monolito-para-microservicos","GfahoUqOsk91r2Kqw-8HX5fMb3sPEBDjEaJBycm4MHI",1787796308836]