<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: He4rt Developers</title>
    <description>The latest articles on DEV Community by He4rt Developers (he4rt).</description>
    <link>https://dev.to/he4rt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5490%2Fef471ad5-dbd9-40be-9951-743a6026d59c.png</url>
      <title>DEV Community: He4rt Developers</title>
      <link>https://dev.to/he4rt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/he4rt"/>
    <language>en</language>
    <item>
      <title>Produto 4 Devs</title>
      <dc:creator>Renan Vidal Rodrigues</dc:creator>
      <pubDate>Thu, 16 Jul 2026 13:43:06 +0000</pubDate>
      <link>https://dev.to/he4rt/produto-4-devs-pbp</link>
      <guid>https://dev.to/he4rt/produto-4-devs-pbp</guid>
      <description>&lt;p&gt;Todo Dev tem que entender que "ou tu aprende sobre Produto e Regras de Negócio ou tu vai ficar pra trás", o código é só 30% da resolução do problema!&lt;/p&gt;

&lt;h2&gt;
  
  
  Tabela de Conteúdo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. Prólogo &lt;/li&gt;
&lt;li&gt;2. O que é um Produto? (Sem enrolação) &lt;/li&gt;
&lt;li&gt;3. Por que um dev deveria se importar? &lt;/li&gt;
&lt;li&gt;4. Diferença entre Projeto e Produto &lt;/li&gt;
&lt;li&gt;5. Ciclo de vida do Produto &lt;/li&gt;
&lt;li&gt;6. O que importa o Dev entender do ciclo de vida do produto? &lt;/li&gt;
&lt;li&gt;7. Conclusão &lt;/li&gt;
&lt;li&gt;8. Se interessou?
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. Prólogo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cada vez mais vejo que é essencial para um Dev ou para um time de desenvolvedores entenderem sobre Produtos e Regras de Negócio, pois é isso que mercado espera.&lt;/p&gt;

&lt;p&gt;É como o Daniel &lt;strong&gt;(He4rt)&lt;/strong&gt; Reis sempre fala o mercado espera Devs &lt;strong&gt;desenrolados&lt;/strong&gt; que buscam entender as regras de negocio, do produto em si! Pois código é só 30% do trabalho, os outros 70% são a capacidade de entender o produto e debater sobre o mesmo.&lt;/p&gt;

&lt;p&gt;Pensando nisso decidi escrever uma série de artigos focados em Produtos para Devs, buscando preencher uma lacuna comum nas equipes de desenvolvimento, buscando aproximar desenvolvimento e produto sem jargões ou excesso de teoria. Para ajudar aqueles que querem aprender sobre e também uma forma de sintetizar meus conhecimentos.&lt;/p&gt;

&lt;p&gt;Mas você deve estar ai pensando: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;"Mas Renan esse conteúdo faz sentido pra mim que estou começando a estudar programação agora?&lt;/em&gt;&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;E minha resposta é &lt;strong&gt;Sim&lt;/strong&gt;, esse conhecimento é fundamental pra você, seja você uma pessoa que começou a estudar programação agora, um júnior, um pleno, um sênior, ou simplesmente alguém que está pensando em migrar de área. E novamente &lt;strong&gt;Sim&lt;/strong&gt; esse conteúdo é para &lt;strong&gt;você ai&lt;/strong&gt;, que quer se destacar e sair na frente de muita gente que pensa que hoje é só &lt;em&gt;vibecodar e tudo vai dar bom!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fna4hef9x9n65j24cvcss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fna4hef9x9n65j24cvcss.png" alt="Daniel He4rt apontando para tela dizendo " width="274" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Portanto vamos começar abordando o tópico que acho que é o que faz mais sentido para iniciarmos essa jornada que é:   &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. O que é um Produto? &lt;em&gt;(Sem enrolação)&lt;/em&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Mas e ai &lt;strong&gt;o que é um produto de verdade?&lt;/strong&gt; Um produto não é um software, pois um software pode ser apenas uma ferramenta, um &lt;strong&gt;produto é o valor real entregue ao usuário!&lt;/strong&gt; Um produto é resolver um problema de negocio de forma sustentável!&lt;/p&gt;

&lt;p&gt;Um Produto sempre, mais &lt;strong&gt;sempre&lt;/strong&gt; mesmo só ira surgir para resolver um problema, uma dor, uma necessidade ou simplesmente satisfazer um desejo de um usuário ou um cliente. Seja esse usuário uma empresa ou um simples cliente.  &lt;/p&gt;

&lt;p&gt;Mas vale ressaltar que nem todo produto resolve uma &lt;strong&gt;dor&lt;/strong&gt; intensa, muitas vezes alguns apenas amenizam essa necessidade, alguns ganham tempo, outros geram status ou entretenimento.  Mas o mais importante é que sempre um produto entregam algum &lt;strong&gt;valor&lt;/strong&gt; ao usuário.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mas e o produto digital?&lt;/strong&gt;&lt;br&gt;
Agora que já entendemos o conceito básico de produto, podemos dizer que um &lt;strong&gt;Produto digital&lt;/strong&gt; é qualquer software que tenha usuários e entregam algum tipo de valor a esse.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. Por que um dev deveria se importar?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Essa é a pergunta de milhões que faz toda diferença e sua resposta é bem simples quando paramos para pensar que decisões técnicas que são tomadas pelo time tech impactam no negócio, que toda divida técnica também é uma decisão de de produto e que a performance, segurança e escalabilidade também são uma entrega de &lt;strong&gt;valor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tendo isso em vista podemos entender que um dev que se importa em entender as &lt;strong&gt;Regras do Negocio&lt;/strong&gt; e entender o &lt;strong&gt;Valor que o Produto busca entregar&lt;/strong&gt;, consegue enxergar qual caminho deve seguir, quais decisões devem ser levadas em consideração na construção da arquitetura de um produto, quais funcionalidades agregaram mais valor a experiencia do usuário, como um banco de dados deve ser modelado para poder atender a necessidade deste produto.&lt;/p&gt;

&lt;p&gt;Além disso essa compreensão permite o dev questionar, debater e a elaborar planos de ação muito mais eficientes e que muitas vezes evitam muito retrabalho e refatoração de código, que muitas vezes aparecem pelo fato de você ter simplesmente ter "codado" uma funcionalidade sem entender todo o escopo do que o produto se propõem através da mesma.&lt;/p&gt;

&lt;p&gt;Então jovens lembre-se entender o Produto evita retrabalho, e evitar retrabalho significa menos esforço e mais tempo para outras coisas que realmente valem a pena!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34i9bdwl9l337l23ymfr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34i9bdwl9l337l23ymfr.png" alt="fluxo de produto" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. Diferença entre Projeto e Produto:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Agora que acredito que já desenvolvemos uma visão legal do que é Produto, agora podemos debater um pouco sobre a diferença entre um &lt;strong&gt;Projeto&lt;/strong&gt; e um &lt;strong&gt;Produto&lt;/strong&gt;. Claro que neste momento você deve estar se questionando: &lt;strong&gt;"Mais não é tudo a mesma coisa no final?"&lt;/strong&gt; e foi esse mesmo pensamento que tive até entender que:&lt;/p&gt;

&lt;h3&gt;
  
  
  Projeto:
&lt;/h3&gt;

&lt;p&gt;Tem seu escopo é fechado, muito bem delimitado e definido no planejamento, tendo um começo, meio e fim claramente definidos e desenhados, tem um foco na entrega com um caminho bem documentado e claro pode funcionar em cenários previsíveis. &lt;/p&gt;

&lt;h3&gt;
  
  
  Produto:
&lt;/h3&gt;

&lt;p&gt;Já um produto tem um escopo mais aberto pois seu foco inicial é resolver uma &lt;strong&gt;problema&lt;/strong&gt; e não o como, geralmente ele tem um começo e meio que podem ser identificáveis, mas seu fim não. Seu foco esta no resultado, tendo um objetivo bem definido, e para isso faz uso de experimentação, testes e validação de ideias (Discovery), que servem de guia para o caminho a ser seguido. E é indicado para cenários e contextos voláteis.&lt;/p&gt;

&lt;p&gt;Então &lt;strong&gt;"não"&lt;/strong&gt;, uma nova funcionalidade não é um novo produto e sim um projeto, um produto é algo maior que vai possuir durante seu &lt;strong&gt;Ciclo de Vida&lt;/strong&gt; alguns projetos &lt;em&gt;(novas funcionalidades)&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;5. Ciclo de vida de Produto:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Por fim quero aborda um ponto que acho relevante de se ter pelo menos o conhecimento básico sobre. O &lt;strong&gt;Ciclo de vida do produto&lt;/strong&gt; descreve todas as etapas pelas quais um produto passa dentro do mercado, desde seu lançamento até o seu fim por assim dizer.&lt;/p&gt;

&lt;p&gt;Basicamente temos a fase &lt;strong&gt;Inovação&lt;/strong&gt; que engloba desde o desenvolvimento do produto até sua entrada no mercado, onde seu foco inicial no mercado é buscar o &lt;strong&gt;Product Market Fit&lt;/strong&gt; &lt;em&gt;(Bem resumidamente são os clientes que se identificam com o produto)&lt;/em&gt;, nessa etapa o perfil do usuário é composto por aqueles que são os &lt;strong&gt;Inovadores&lt;/strong&gt; &lt;em&gt;(Pessoas que estão usando o produto apenas por ele ser novo ou por apenas querer somente testa-lo)&lt;/em&gt;  e pelo &lt;strong&gt;Primeiros Adeptos&lt;/strong&gt; &lt;em&gt;(São aqueles que realmente se identificam com o problema que o produto resolve logo de cara e servem muitas vezes como propagadores)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Temos também a fase &lt;strong&gt;Crescimento&lt;/strong&gt; onde o foco é &lt;strong&gt;Growth&lt;/strong&gt; &lt;em&gt;(Processo contínuo de crescimento do produto no mercado por meio de melhorias no produto, novas funcionalidades e otimização da experiência do usuário).&lt;/em&gt; Aqui temos dos perfis de usuários a o que chamamos de &lt;strong&gt;Maioria Inicial&lt;/strong&gt; que são aqueles que vem junto logo quando o produto começa a se popularizar no mercado e temos a &lt;strong&gt;Maioria Tardia&lt;/strong&gt; que são aqueles que chegam depois que o produto já se estabilizou e começou a se consolidar. &lt;/p&gt;

&lt;p&gt;E por fim temos a fase de &lt;strong&gt;Maturidade&lt;/strong&gt; aqui nosso produto já se consolidou e mantem uma boa base de clientes fieis e continua entregando valor, daqui ou o produto se matem no mercado ou entra em &lt;strong&gt;Declínio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No processo inicial de um produto logo após os Primeiros Adeptos antes da Maioria Inicial, temos um &lt;strong&gt;Abismo&lt;/strong&gt; onde muitos produtos morrem, por não conseguirem cruzar esse abismo. E temos também um abismo após a maturidade uma &lt;strong&gt;quarta fase&lt;/strong&gt; que que quando não conseguimos seguir inovando que é &lt;em&gt;Descontinuar um produto&lt;/em&gt; ou &lt;em&gt;Morrer, matar o produto, o sunset&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw27opqop5be2yqprfnga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw27opqop5be2yqprfnga.png" alt="ciclo de vida do produto" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;6. Mas o que importa o Dev entender do ciclo de vida do produto?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Saber o ciclo de vida do produto pode te ajudar a entender em que fase o produto em que você vai estar trabalhando pode estar e o quanto de esforço e investimento será empregado em cada fase. O quanto o processo de Discovery será necessário para mapear novas funcionalidades e melhorias de fluxos. E o que pra mim faz bastante sentido quando você pode propor alguma melhoria, nova funcionalidade ou até mesmo a ideia de um novo produto! &lt;/p&gt;

&lt;p&gt;Pois um Dev que tem capacidade de ajudar um produto crescer ou até mesmo a capacidade de propor um novo produto, sempre será um Dev diferenciado no mercado! &lt;strong&gt;&lt;em&gt;(Claro quando esse tipo de atitude é valorizada)!!!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;7. Conclusão:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Nesse artigo entendemos um pouco mais sobre produtos, o que impacta na vida do Dev entender sobre o assunto, a diferença entre um projeto e um produto e compreendemos um pouco sobre o ciclo de vida do produto.&lt;/p&gt;

&lt;p&gt;Tudo isso pra tentar ajudar você a ser um pouco mais um Dev &lt;strong&gt;Desenrolado&lt;/strong&gt;, que tem uma visão mais macro do processo que vai muito além do Código.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;8. Se interessou?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Eu sou o Renan Vidal, trabalho como Product Owner já a algum tempo e agora estou estudando para migrar mais ainda para a área de produto, e com tudo que venho estudando e com toda experiencia que já tenho de mercado, e posso te dizer com alguma certeza que um Dev que se preocupa entender sobre Produtos e Regras de Negocio está na frente de muita, mas muita gente mesmo.&lt;/p&gt;

&lt;p&gt;Espero que tudo que escrevi aqui tenha algum sentido para pelo menos uma pessoa, e no caso de duvidas ou se deseja que eu aborde algum tópico de produto mais aprofundada é só deixar seu comentário!&lt;/p&gt;

</description>
      <category>product</category>
      <category>productivity</category>
      <category>career</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Métricas de qualidade de software na era da IA</title>
      <dc:creator>Alicia Marianne Gonçalves</dc:creator>
      <pubDate>Thu, 16 Jul 2026 00:12:21 +0000</pubDate>
      <link>https://dev.to/he4rt/metricas-de-qualidade-de-software-na-era-da-ia-334o</link>
      <guid>https://dev.to/he4rt/metricas-de-qualidade-de-software-na-era-da-ia-334o</guid>
      <description>&lt;p&gt;Não é novidade para ninguém que estamos passando por uma transformação na área de desenvolvimento de software, em que a IA está assumindo diversas atividades. E isso me faz pensar: o que vamos medir, ou o que teremos como parâmetro para qualidade de software daqui pra frente? É sobre isso que vou falar neste texto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes das métricas: entenda o momento do seu time
&lt;/h2&gt;

&lt;p&gt;Antes de entrarmos nas métricas em si, precisamos entender o momento em que o nosso time está. É muito fácil eu simplesmente jogar métricas aqui e você aplicá-las ao seu time de maneira automática — mas será que elas fazem sentido para o seu contexto?&lt;/p&gt;

&lt;p&gt;Uma coisa que eu falo bastante aos meus alunos da mentoria que dou na He4rt Developers é: &lt;strong&gt;pra que eu quero isso?&lt;/strong&gt; Softwares representam necessidades do mundo real, logo, medir o sucesso e a qualidade deles vai depender muito das necessidades que eles buscam suprir.&lt;/p&gt;

&lt;p&gt;Partindo agora para as métricas, eu gosto de dividi-las em dois grupos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Métricas para stakeholders&lt;/li&gt;
&lt;li&gt;Métricas para o time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Qualidade de software não se resume a número de bugs — ela se aplica tanto em como o software é recebido pelo cliente final, quanto em como ele é desenvolvido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Métricas para stakeholders
&lt;/h2&gt;

&lt;p&gt;Uma coisa que eu aprendi neste tempo na empresa em que tenho atuado, principalmente com a transformação digital, é que mostrar número de bugs abertos ou resolvidos não mostra para o público o que realmente importa: como está a qualidade do produto. E, para me ajudar nisso, eu sempre tento me colocar no lugar de um cliente que não tem conhecimento profundo sobre o ciclo de desenvolvimento de software.&lt;/p&gt;

&lt;p&gt;A primeira coisa que eu gostaria de ver quando um QA, ou o time, vier me mostrar os resultados de uma sprint ou de um quarter é: quantos problemas eu tenho em produção — mas não só isso, quanto tempo tenho levado para resolvê-los.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mean Time to Resolve/Repair (MTTR)
&lt;/h3&gt;

&lt;p&gt;Essa é a famosa métrica que vai mostrar o tempo que leva desde que o problema é identificado até ele ser resolvido em produção. Dependendo de como é o seu processo de desenvolvimento, isso pode ser medido de formas diferentes. Se você tem um processo em que o cliente primeiro reporta para o suporte, o suporte faz uma pré-avaliação e só depois disso o problema vai para o time resolver, você pode medir em dois momentos: o tempo que o suporte leva para avaliar o problema e dar um primeiro parecer, e o tempo em que esse ticket de suporte de fato se torna um bug e entra na esteira de resolução.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cobertura de testes automatizados
&lt;/h3&gt;

&lt;p&gt;Outra métrica interessante de se mostrar para um time de stakeholders é a quantidade de testes automatizados — porém, não isolada. Acho que você já deve estar cansado de ouvir que ter 99% de cobertura de testes não significa qualidade, mas como posso apresentar essa métrica ao stakeholder de forma que ele veja o valor do investimento em automação que o seu time está fazendo?&lt;/p&gt;

&lt;p&gt;Uma coisa importante a analisar nesse contexto é se a cobertura de testes está realmente pegando os cenários importantes do sistema. Exemplo: se, no seu contexto, o seu MTTR está alto mas a sua cobertura de testes está alta, isso significa que a sua cobertura não está cobrindo o que realmente precisa ser coberto.&lt;/p&gt;

&lt;h3&gt;
  
  
  DORA Metrics
&lt;/h3&gt;

&lt;p&gt;Além dessas, as &lt;strong&gt;DORA Metrics&lt;/strong&gt; são grandes aliadas para auxiliar nesse diagnóstico.&lt;/p&gt;

&lt;p&gt;As DORA Metrics (DevOps Research and Assessment, hoje parte do Google Cloud) nasceram para medir a performance de entrega de software, mas, na prática, funcionam como um termômetro da qualidade do processo como um todo — porque times que entregam rápido &lt;em&gt;e&lt;/em&gt; com poucos incidentes são times com um pipeline de qualidade (testes, revisão, observabilidade) bem construído.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fonte oficial:&lt;/strong&gt; &lt;a href="https://dora.dev/" rel="noopener noreferrer"&gt;dora.dev&lt;/a&gt; — pesquisa contínua do DORA (Google Cloud) sobre as métricas e capacidades que diferenciam times de alta performance. Vale a leitura direta da fonte para acompanhar atualizações do modelo, como a inclusão de Reliability como quinta métrica.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Deployment Frequency (Frequência de Deploy)&lt;/strong&gt;&lt;br&gt;
Com que frequência o time coloca código em produção. Deploys menores e mais frequentes são mais fáceis de testar e, se algo der errado, mais fáceis de isolar a causa — menos mudanças acumuladas por deploy significa menos variáveis para investigar. Uma frequência de deploy caindo pode ser sintoma de um time com medo de quebrar produção, geralmente por falta de confiança nos testes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lead Time for Changes (Tempo entre o commit e a produção)&lt;/strong&gt;&lt;br&gt;
Tempo desde que uma mudança é commitada até ela estar rodando em produção. Um lead time alto muitas vezes esconde um gargalo em etapas de validação manual demoradas, ou retrabalho por bugs encontrados tarde no ciclo. É uma métrica que expõe se o teste está acontecendo cedo (shift-left) ou só no fim, represando a entrega.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change Failure Rate (Taxa de Falha em Mudanças)&lt;/strong&gt;&lt;br&gt;
Percentual de deploys que causam falha em produção (incidente, rollback, hotfix). É, de longe, a métrica DORA mais diretamente ligada à qualidade do processo de teste. Está diretamente relacionada à cobertura de testes: se essa métrica está alta, significa que a sua cobertura de testes está baixa ou, como dito anteriormente, não está cobrindo o que realmente precisa ser coberto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time to Restore Service (Tempo de Restauração)&lt;/strong&gt;&lt;br&gt;
Tempo médio para restaurar o serviço depois de um incidente em produção (rollback, hotfix, correção de configuração). Mede a capacidade do time de reagir quando — não &lt;em&gt;se&lt;/em&gt; — algo dá errado. QA pode influenciar diretamente essa métrica ajudando a desenhar testes de rollback, feature flags e smoke tests pós-deploy que aceleram a detecção e a decisão de reverter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliability (Confiabilidade — métrica mais recente do modelo DORA)&lt;/strong&gt;&lt;br&gt;
O quanto o sistema atende às expectativas de disponibilidade e performance do usuário no dia a dia, não só durante incidentes. Conecta a qualidade percebida pelo usuário com o trabalho de teste não funcional (performance, disponibilidade) — é o lembrete de que qualidade não é só "não ter bug", é o sistema se comportar bem sob uso real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Métricas para o time
&lt;/h2&gt;

&lt;p&gt;As métricas para stakeholders ajudam bastante o time a ver o estado daquilo que estão produzindo, mas, às vezes, você quer enxergar a qualidade no próprio processo de desenvolvimento do software. Aqui podemos ter métricas mais pontuais.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root Cause Analysis
&lt;/h3&gt;

&lt;p&gt;São métricas que ajudam a ver os padrões dos problemas que acontecem no software ou durante o desenvolvimento e, a partir delas, pensar em planos de ação. Exemplo: se eu tenho muitos problemas relacionados a falta de requisitos, posso deixar os requisitos mais explícitos nas tarefas e, daqui a 2 ou 3 sprints, validar se esse tipo de root cause se tornou menos frequente ou não.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugs identificados antes do lançamento
&lt;/h3&gt;

&lt;p&gt;Existem algumas métricas que você também pode extrair relacionadas a bugs, como: quantos bugs foram identificados antes mesmo de lançarmos a feature em produção? Isso pode ser alimentado tanto pela execução de testes manuais quanto por testes automatizados que falharam ao adicionar uma nova feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrabalho
&lt;/h3&gt;

&lt;p&gt;É possível também calcular o retrabalho, uma métrica que vai te ajudar tanto a ver se o número de bugs está aumentando ou não quanto no planejamento. Quando o seu time costuma ter uma taxa de retrabalho de 10%, é possível alinhar com os Product Owners 10% de margem dentro de uma sprint para possíveis bugs. Assim, o planejamento se torna mais realista, as expectativas ficam alinhadas, e o cliente e o time ficam mais felizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Com essa transformação digital, é importante entendermos que temos métricas que podem nos auxiliar a medir o ciclo de desenvolvimento e a qualidade do produto — e o principal é que nenhuma delas, isoladamente, traz valor. Elas precisam ser lidas em conjunto, cruzadas entre si, e sempre voltando para aquela pergunta inicial: pra que eu quero isso?&lt;/p&gt;

&lt;p&gt;Com a IA aumentando a produção de código, a tendência é termos mais código sendo escrito, mais features sendo entregues e mais decisões sendo tomadas em menos tempo. E é justamente por isso que medir vai se tornar ainda mais importante do que já é hoje: se antes um time levava dias para gerar uma quantidade de código que hoje pode ser gerada em horas, os erros também podem se multiplicar na mesma velocidade — só que muitas vezes invisíveis até chegarem em produção.&lt;/p&gt;

&lt;p&gt;Por isso, acho que o papel do QA nesse novo cenário não é só testar mais rápido para acompanhar o ritmo da IA, mas também ser a pessoa que garante que estamos olhando para os números certos. Não adianta ganhar velocidade e perder visibilidade sobre o que está sendo entregue. Então, o convite que eu deixo é: olhe para essas métricas não como números para preencher um dashboard, mas como perguntas que elas estão te fazendo sobre o seu processo. E use a IA para te ajudar a responder essas perguntas mais rápido — não para substituir o hábito de perguntar.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>qa</category>
      <category>testing</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Como escolher eventos de tecnologia para participar</title>
      <dc:creator>vitoriazzp</dc:creator>
      <pubDate>Mon, 13 Jul 2026 17:45:47 +0000</pubDate>
      <link>https://dev.to/he4rt/como-escolher-eventos-de-tecnologia-para-participar-55kf</link>
      <guid>https://dev.to/he4rt/como-escolher-eventos-de-tecnologia-para-participar-55kf</guid>
      <description>&lt;p&gt;Quando comecei a ir a eventos de tecnologia, estava na faculdade e ia a todos os gratuitos ou que custavam, no máximo, 100 reais. Porém, eu estava começando em TI e sabia tão pouco sobre a área em que eu queria atuar, logo qualquer evento despertava minha curiosidade.&lt;/p&gt;

&lt;p&gt;Os anos passaram e eu me tornei frontend e passei a participar de eventos como front in (sampa, floripa, poa), TDCs, Roadsec (the mind sec) entre outros aí.&lt;/p&gt;




&lt;h2&gt;
  
  
  Por que ir a esses eventos?
&lt;/h2&gt;

&lt;p&gt;Desde que comecei a fazer freelance e me formei, percebo que é onde eu descubro as tecnologias e os problemas que as pessoas têm percebido. Também é uma forma prática de eu sair da minha zona de conforto e conhecer pessoas, visões de mundo diferentes e voltar com ideias pros meus serviços.&lt;/p&gt;

&lt;p&gt;Quando eu estava na faculdade, era muito mais simples ter essas duas "pontes" de &lt;em&gt;comunicação&lt;/em&gt; e também de &lt;em&gt;venda de solução&lt;/em&gt;. Após me formar e começar a trabalhar, percebi que a prospecção de clientes seria muito mais difícil se eu não estivesse nesses eventos.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mas Vih, eu estou estudando, não estou mirando em freelance, do que me adianta ir?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Adianta que você conhece pessoas, faz amigos ou pessoas que vão te contratar ou indicar em trabalhos futuros. Além das palestras que podem te mostrar uma área dentro de TI que você ainda não conhecia e pode acabar gostando.&lt;/p&gt;




&lt;h2&gt;
  
  
  Como eu escolhia os eventos que iam fazer diferença pra mim?
&lt;/h2&gt;

&lt;p&gt;1) Eu tinha uma lista de assuntos e áreas de que eu gostava ou que acompanhava já nas redes sociais, exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- frontend: flutter, react, html/css, boas práticas, realidade aumentada
- segurança da informação: hacks, boas práticas, curiosidades etc
- backend: qualquer stack pra se atualizar
- design ihc, ux, produto, estratégia, marketing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;2) Seguia no Instagram as pessoas dessas áreas, que às vezes eram as mesmas que participavam dos eventos, por exemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;@juliallabs&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/stherzada"&gt;@stherzada&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/danielhe4rt"&gt;@danielhe4rt&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/spacecoding"&gt;@spacecoding&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/nataliafdev"&gt;@nataliafdev&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;@omarcusdev &lt;/li&gt;
&lt;li&gt;@dessadev &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Esses são alguns dos exemplos que eu mais acompanhava no Instagram, algumas pessoas também têm perfil nesta plataforma, mas o conteúdo sobre eventos está no Insta!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;3) Tinha uma noção do custo de alguns eventos, e quais eu podia arcar, quais eu precisava pra me atualizar ou fazer networking específico:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conferências até R$ 1.500;&lt;/li&gt;
&lt;li&gt;Eventos/meetups de R$ 0 a 600;
&amp;gt; Ter a meia-entrada ou o ingresso solidário ajudava a pagar menos; às vezes, a faculdade ou o emprego pagava a entrada em troca de trazer o conteúdo aplicado. Vale consultar a disponibilidade pra economizar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mas qual a diferença entre conferência, eventos, meetups?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Conferência&lt;/em&gt;&lt;/strong&gt; costuma ser um evento amplo; faz-se pra apresentar conteúdos específicos, e o público costuma ser de empresas que levam o funcionário ou de pessoas que já estão no mercado e conseguem arcar com o custo do ingresso.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Eventos&lt;/em&gt;&lt;/strong&gt; podem ser pagos ou gratuitos; costumam ser organizados por empresas, mas às vezes comunidades também.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Meetups&lt;/em&gt;&lt;/strong&gt; são frequentemente organizados por comunidades e podem ou não ter custo; depende do lugar em que acontecem.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Onde eu posso achar eventos de tecnologia?
&lt;/h2&gt;

&lt;p&gt;Existem os sites de venda de ingresso como &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.sympla.com.br/eventos?s=tecnologia" rel="noopener noreferrer"&gt;Sympla&lt;/a&gt;, &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.meetup.com/find/?keywords=Technology&amp;amp;source=EVENTS" rel="noopener noreferrer"&gt;Meetup&lt;/a&gt;, &lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/agenda-tech-brasil/agenda-tech-brasil" rel="noopener noreferrer"&gt;Repositório no Github (amo esse)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://startups.com.br/eventos/agenda-2026-35-eventos-de-tecnologia-e-inovacao-para-ficar-de-olho/" rel="noopener noreferrer"&gt;Startup &lt;/a&gt;(é um jornal digital) &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Summits&lt;/em&gt;&lt;/strong&gt; são mais voltados para  empresas ou empreendedores; os valores são bem altos, mas consegue desconto dependendo da tua empresa ou faculdade. Vale a pena se pretende atuar em algum setor muito específico.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  E você? Como costuma escolher quais eventos de tecnologia ir?
&lt;/h2&gt;

</description>
      <category>beginners</category>
      <category>braziliandevs</category>
      <category>networking</category>
      <category>career</category>
    </item>
    <item>
      <title>Ownership: do "temos um rojão na mão" até "não precisa mais pensar nisso"</title>
      <dc:creator>Daniel Reis</dc:creator>
      <pubDate>Wed, 08 Jul 2026 17:25:50 +0000</pubDate>
      <link>https://dev.to/he4rt/ownership-do-temos-um-rojao-na-mao-ate-nao-precisa-mais-pensar-nisso-3ki7</link>
      <guid>https://dev.to/he4rt/ownership-do-temos-um-rojao-na-mao-ate-nao-precisa-mais-pensar-nisso-3ki7</guid>
      <description>&lt;p&gt;É aquele famoso "ou faz direito ou não faz" que todo dev deveria respeitar desde o inicio dos seus estudos. &lt;/p&gt;

&lt;h2&gt;
  
  
  Tabela de Conteúdo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. Prólogo&lt;/li&gt;
&lt;li&gt;2. Entenda o problema real&lt;/li&gt;
&lt;li&gt;3. Projete antes de codar&lt;/li&gt;
&lt;li&gt;4. Execute&lt;/li&gt;
&lt;li&gt;5. Verifique (em produção)&lt;/li&gt;
&lt;li&gt;6. Comunique&lt;/li&gt;
&lt;li&gt;7. Acompanhe&lt;/li&gt;
&lt;li&gt;É muita coisa. E olha que ainda tem mais.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Prólogo
&lt;/h2&gt;

&lt;p&gt;Acho que toda vez que eu vejo um dev iniciante, meu primeiro pensamento é: a hora de "criar" responsabilidade é literalmente agora, no inicio da carreira. Por exemplo: quando eu era junior eu pegava task, escrevia o código, mandava o Pull Request, que era mergeado e ia dormir feliz. Três dias depois o problema voltava, do mesmo jeito, e adivinha quem teve que mexer de novo? Eu. Por anos eu continuei fazendo aquilo, esquecendo daquela frase clichê de que: "30% do trabalho é de fato código". Mas ai de fato, quais são os outros 70%?&lt;/p&gt;

&lt;p&gt;Quando comecei a me perguntar isso, simplesmente entendi que: eu tinha entregado um &lt;strong&gt;código&lt;/strong&gt;, por &lt;strong&gt;anos&lt;/strong&gt; não tinha resolvido um &lt;strong&gt;problema&lt;/strong&gt;. E é sobre isso que a gente vai falar hoje.&lt;/p&gt;

&lt;p&gt;Eu uso muito a palavra &lt;em&gt;ownership&lt;/em&gt;. Vivo falando "você pode assumir isso?", "consegue cuidar disso?", "tá com você?" e faz um tempão que eu não paro pra explicar o que &lt;em&gt;ownership&lt;/em&gt; significa &lt;strong&gt;pra mim&lt;/strong&gt;. Então bora lá, que hoje o assunto é esse.&lt;/p&gt;

&lt;p&gt;Quando eu te pergunto "você pode assumir isso?", presta atenção: eu &lt;strong&gt;não&lt;/strong&gt; tô te pedindo pra escrever um código e mandar um PR. Eu tô te pedindo pra ser &lt;strong&gt;dono da solução de um problema, de ponta a ponta&lt;/strong&gt;. Do "temos um problema" até o "não precisa mais pensar nisso".&lt;/p&gt;

&lt;p&gt;E cara, isso muda TUDO. Assumir uma tarefa é entregar um código. Assumir um &lt;em&gt;problema&lt;/em&gt; é garantir que ele deixou de existir, e que ninguém, você inclusive, vai precisar voltar a pensar nele nunca mais. Quando você fala que tá &lt;em&gt;resolvendo&lt;/em&gt; alguma coisa, a expectativa é que você faça essa porra toda que vem abaixo. E ó, não é papo de gestão não: dominar isso é o que faz o time te dar problema cada vez maior sem medo. É basicamente o cheat code pra virar sênior de verdade, não sênior de tempo de casa.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Entenda o problema real
&lt;/h2&gt;

&lt;p&gt;Antes de qualquer linha de código, tenha CERTEZA de que você sabe o que tá resolvendo.&lt;/p&gt;

&lt;p&gt;O erro mais comum do mundo é começar pela solução. Você provavelmente já tem uma na cabeça sem ter parado 5 minutos pra pensar no que a gente tá de fato tentando resolver. Se você acha que "o problema é que precisamos migrar de X pra Y", pera aí mano, isso não é um problema, isso é uma &lt;strong&gt;solução&lt;/strong&gt;. O problema de verdade é algo tipo "a performance tá uma merda", "não é estável", "quebra pro cliente X".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Migrar do MySQL pro Postgres" é solução.&lt;br&gt;
"A busca de membros da comunidade demora 8 segundos" é problema.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sacou a diferença? Beleza. Uma vez que você tem o problema real na mão, a próxima pergunta é: &lt;strong&gt;será que existem outras soluções possíveis?&lt;/strong&gt; Pensa nelas. Um índice resolve? Um cache resolve? Reescrever aquela query zoada resolve, antes de trocar o banco INTEIRO? Cada caminho tem seus tradeoffs. Qual é o melhor levando isso em conta?&lt;/p&gt;

&lt;p&gt;Se você pular essa fase, você corre o risco de passar duas semanas construindo a solução perfeita pro problema errado. E aí não tem PLAU que salve.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nosso caso (a busca de 8s):&lt;/strong&gt; o problema não é "migrar pro Postgres". É "a busca de membros demora 8 segundos". Antes de trocar de banco, será que um índice não mata isso?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Projete antes de codar
&lt;/h2&gt;

&lt;p&gt;É aqui que a maioria dos problemas de produção nasce: nas perguntas que ninguém fez antes de começar. Então vamo fazer elas agora.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pensa nos edge cases.&lt;/strong&gt; Quais são? Quais são importantes? Quais dá pra ignorar de propósito (e deixar registrado que você ignorou)? Membro sem avatar, nome com emoji, lista vazia, fuso horário diferente do servidor. Escolher o que ignorar é uma decisão consciente; ignorar sem perceber é um bug esperando a hora de estourar na sua cara.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pensa nas falhas.&lt;/strong&gt; Falha de rede, por exemplo, é GARANTIDA. Como a gente lida com ela? Retry? Tá, mas com que frequência? Por quanto tempo? O webhook caiu: retry com backoff? três tentativas? e depois, descarta ou joga numa fila de reprocesso?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pensa no fluxo de dados.&lt;/strong&gt; Quanto dado tá envolvido? Precisa migrar algo? Limpar algo? Como você consegue dado de verdade pra testar direito? Quais invariantes existem nos dados? E principalmente: quais premissas sobre o formato dos dados você tem e ainda &lt;strong&gt;não confirmou&lt;/strong&gt;? "Todo membro tem e-mail"... você checou isso na base ou só assumiu e foi feliz?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pensa em como você vai testar.&lt;/strong&gt; Como você vai saber se o que construiu tá certo? Testes automatizados bastam? Precisa cutucar na mão? A diferença aparece num screenshot ou num vídeo? Um bug de dark mode não aparece em teste unitário, meu amigo. Aparece num print.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pensa em como isso vai ser anunciado.&lt;/strong&gt; Como a gente comunicaria? Você consegue visualizar o anúncio? Como isso se encaixa no quadro maior do roadmap? Dúvida ou preocupação nessa área? &lt;strong&gt;Questiona, pergunta, dá um push back.&lt;/strong&gt; Se você não consegue escrever o anúncio em duas frases, o escopo provavelmente ainda tá bagunçado.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nosso caso:&lt;/strong&gt; e se o membro não tiver avatar? E se a busca vier vazia? E se caírem 50 mil membros de uma vez? Anota tudo isso ANTES de codar.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Execute
&lt;/h2&gt;

&lt;p&gt;A fase mais curta de descrever e a mais longa de viver. O padrão é um só, e é simples.&lt;/p&gt;

&lt;p&gt;Faça o trabalho com precisão, com cuidado, com senso de urgência e com calma. Não faça nada pela metade. E ó, urgência aqui não é pressa afobada não: é não deixar o problema esperando por você, sem sacrificar o cuidado no caminho.&lt;/p&gt;

&lt;p&gt;E tem um teste antes de dar merge, o meu favorito:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Eu tenho orgulho disso?&lt;br&gt;
Eu mostraria isso pro John Carmack (o lendário programador do Doom e Quake) e falaria: "foi isso que eu construí, com essas restrições, com esses tradeoffs"?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Se a resposta for não, sinto muito, mas ainda não terminou.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nosso caso:&lt;/strong&gt; boto o índice, reescrevo a query zoada, testo local. Caiu de 8s pra 120ms. Tenho orgulho disso? Tenho.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Verifique (em produção)
&lt;/h2&gt;

&lt;p&gt;Terminar de codar NÃO é terminar. Terminar é o problema resolvido, rodando, no ar. Anota isso.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testa na mão.&lt;/strong&gt; Sim, existem testes automatizados. Mas Daniel, se eu tenho teste automatizado, pra quê testar na mão? Porque teste passa e feature quebra, meu amigo. São coisas diferentes. Em 99% dos casos você consegue confirmar na unha que o que construiu funciona: rodando você mesmo, pedindo pra um agente percorrer os cenários, cutucando os dados antes e depois, tirando screenshots, gravando uma demo. Você tem &lt;em&gt;certeza&lt;/em&gt; que resolveu o problema? Roda o fluxo inteiro como se fosse o usuário: cria a conta, dispara a ação, confere o resultado no banco.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Garanta que chegou em produção e funciona lá.&lt;/strong&gt; Tá deployado? O deploy quebrou? Precisa ativar alguma feature flag? A flag funciona mesmo? Dá pra usar em produção? Você consegue confirmar que tá de fato no ar? "Mergeou" não é "shipou", tá ligado? Abre a produção e usa a feature você mesmo.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nosso caso:&lt;/strong&gt; subo pra produção, abro EU MESMO e busco "daniel". Os 8s viraram instantâneo de verdade, com dado real, não só no meu localhost feliz.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. Comunique
&lt;/h2&gt;

&lt;p&gt;Código que ninguém sabe que existe gera bug que ninguém sabe explicar. Comunicação é parte do trabalho, não é um extra que você faz se sobrar tempo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seus colegas precisam saber?&lt;/strong&gt; É uma feature nova que todo mundo deveria testar? Uma convenção nova no código? Uma coisa traiçoeira que todos precisam conhecer? Avisa. Não subestima a visão periférica do time: saber que a pessoa X mudou ontem o comportamento de como Z funciona pode economizar TRÊS HORAS de debug da pessoa Y amanhã. Mudou como o cache de sessão funciona? Uma mensagenzinha no canal do time hoje vale três horas de debug de outra pessoa amanhã. É de graça, faz.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Os clientes precisam saber?&lt;/strong&gt; Quem reportou o bug? Quem tá bloqueado esperando isso? Avisa essas pessoas. Responde a thread de quem abriu a issue: "saiu na versão de hoje, pode testar aí".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O mundo precisa saber?&lt;/strong&gt; Anuncia que saiu. Changelog, post no canal de announcements, tweet, o formato que fizer sentido pro tamanho da mudança.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nosso caso:&lt;/strong&gt; mando no canal do time: "a busca de membros tava 8s, agora tá instantânea, quem reclamou pode testar". E respondo a thread de quem abriu a issue.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Acompanhe
&lt;/h2&gt;

&lt;p&gt;Ownership não termina no deploy. Termina quando NINGUÉM mais precisa pensar naquilo, você incluso.&lt;/p&gt;

&lt;p&gt;Tem follow-ups? Você precisa dar uma olhada nos logs pra ver como tá aquilo que shipou? Uma semana depois, quem sabe? Agenda um lembrete: "conferir a taxa de erro do endpoint novo na sexta". O ciclo só fecha de verdade quando você tem certeza que o problema ficou resolvido e &lt;strong&gt;continuou&lt;/strong&gt; resolvido.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nosso caso:&lt;/strong&gt; sexta que vem eu volto no log pra confirmar que a busca continuou rápida mesmo com a base crescendo. Aí sim o ciclo fechou.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  É muita coisa. E olha que ainda tem mais.
&lt;/h2&gt;

&lt;p&gt;Sim, é muita coisa, eu sei. E na real ainda tem mais, porque com certeza a gente esqueceu algum item aqui no meio.&lt;/p&gt;

&lt;p&gt;Mas mano, é assim que se constrói produto em time pequeno. A gente não tem PM, não tem departamento de QA pra passar a bola. Somos pequenos, mas somos bons, e a gente dá conta dessa porra toda.&lt;/p&gt;

&lt;p&gt;E ó, é SEMPRE ok pedir ajuda. É ok fazer pergunta. É ok refazer e conferir três vezes. &lt;strong&gt;O que não é ok é assumir, lá no fundo sem falar pra ninguém, que outra pessoa vai fazer as coisas que você não pensou.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Da comunidade para a comunidade.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Bebe água, me segue nas redes sociais e a gente se vê no próximo artigo!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>beginners</category>
      <category>braziliandevs</category>
      <category>career</category>
    </item>
    <item>
      <title>De 6 horas para 40 segundos: como um índice de banco de dados salvou um job crítico de produção</title>
      <dc:creator>Fernando Andrade</dc:creator>
      <pubDate>Sat, 06 Jun 2026 20:19:13 +0000</pubDate>
      <link>https://dev.to/he4rt/de-6-horas-para-40-segundos-como-um-indice-de-banco-de-dados-salvou-um-job-critico-de-producao-c90</link>
      <guid>https://dev.to/he4rt/de-6-horas-para-40-segundos-como-um-indice-de-banco-de-dados-salvou-um-job-critico-de-producao-c90</guid>
      <description>&lt;h1&gt;
  
  
  De 6 horas para 40 segundos: como um índice de banco de dados salvou um job crítico de produção
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Às vezes, a solução mais elegante não está no código está em ensinar o banco de dados a encontrar o que ele já tem."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Índice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;O cenário: um job noturno que virou um problema diurno&lt;/li&gt;
&lt;li&gt;O problema cresceu junto com a tabela&lt;/li&gt;
&lt;li&gt;O diagnóstico: o Azure Application Insights apontou o caminho&lt;/li&gt;
&lt;li&gt;A solução: um índice bem posicionado&lt;/li&gt;
&lt;li&gt;O impacto real: economia de tempo total por dia&lt;/li&gt;
&lt;li&gt;Mas afinal: o que é um índice de banco de dados?&lt;/li&gt;
&lt;li&gt;Como identificar quando você precisa de um índice&lt;/li&gt;
&lt;li&gt;Anatomia do índice que resolveu o problema&lt;/li&gt;
&lt;li&gt;Conclusão&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  O cenário: um job noturno que virou um problema diurno
&lt;/h2&gt;

&lt;p&gt;Todo sistema que lida com monitoramento contínuo eventualmente enfrenta o mesmo desafio: &lt;strong&gt;quanto mais dados acumulam, mais lenta fica a análise&lt;/strong&gt;. Foi exatamente isso que aconteceu em um projeto no qual trabalhei.&lt;/p&gt;

&lt;p&gt;A arquitetura era simples na teoria: um job agendado rodava durante a madrugada, disparando &lt;strong&gt;N processos paralelos&lt;/strong&gt; cada um cadastrado individualmente pelo cliente. A lógica de cada processo era fazer uma comparação entre o resultado do dia atual (&lt;strong&gt;D+0&lt;/strong&gt;) com o resultado do dia anterior (&lt;strong&gt;D-1&lt;/strong&gt;), algo como:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"O que mudou desde ontem?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para isso, cada processo precisava buscar &lt;strong&gt;seu último resultado registrado&lt;/strong&gt;, usando uma query com &lt;code&gt;ORDER BY last_execution DESC&lt;/code&gt; filtrada pelo identificador do processo. Parece trivial. E durante um bom tempo, foi.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema cresceu junto com a tabela
&lt;/h2&gt;

&lt;p&gt;Com o tempo, a tabela de resultados foi crescendo naturalmente, afinal, cada processo registra um novo resultado a cada execução. O que antes demorava milissegundos começou a demorar segundos. Depois, dezenas de segundos. Até que um dia percebemos:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A janela de execução do job, que deveria ser de até 4 horas, estava chegando a 12 horas.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Isso significava que um job que deveria terminar antes do horário comercial estava ainda em execução quando os usuários começavam a trabalhar de manhã, gerando inconsistências, bloqueios e reclamações.&lt;/p&gt;

&lt;p&gt;A pergunta era: &lt;strong&gt;onde estava o gargalo?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  O diagnóstico: o Azure Application Insights apontou o caminho
&lt;/h2&gt;

&lt;p&gt;Ao analisar as métricas de performance no &lt;strong&gt;Azure Application Insights&lt;/strong&gt;, ficou evidente que o problema estava concentrado em uma única operação: a query que buscava o último resultado de cada processo.&lt;/p&gt;

&lt;p&gt;Internamente, a tabela de resultados tinha crescido o suficiente para que um &lt;code&gt;ORDER BY last_execution DESC&lt;/code&gt; &lt;strong&gt;sem suporte de índice&lt;/strong&gt; forçasse o banco a fazer um &lt;strong&gt;full scan&lt;/strong&gt;, ou seja, varrer linha por linha até encontrar o registro mais recente. Multiplique isso por dezenas (ou centenas) de processos rodando em paralelo e você tem uma receita para o caos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Antes da correção
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 linha(s) recuperada(s) — 1.754s, em 2025-08-11 às 09:19:01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quase &lt;strong&gt;2 segundos por consulta&lt;/strong&gt;. Para um único processo, tolerável. Para N processos simultâneos, catastrófico.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução: um índice bem posicionado
&lt;/h2&gt;

&lt;p&gt;A correção foi aplicar um índice composto na tabela de resultados, cobrindo exatamente os campos usados na query crítica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_job_result_process_date&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;app_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_results&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fk_process_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_created&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result_payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse índice foi criado diretamente no ambiente de produção (pode ser gerado localmente também, dependendo da política da equipe) e o resultado foi imediato.&lt;/p&gt;

&lt;h3&gt;
  
  
  Depois da correção
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 linha(s) recuperada(s) — 0.003s, em 2025-08-11 às 09:32:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;De &lt;strong&gt;1,754 segundos&lt;/strong&gt; para &lt;strong&gt;0,003 segundos&lt;/strong&gt; por consulta. Uma redução de &lt;strong&gt;99,8%&lt;/strong&gt; no tempo de resposta.&lt;/p&gt;

&lt;h2&gt;
  
  
  O impacto real: economia de tempo total por dia
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cenário&lt;/th&gt;
&lt;th&gt;Tempo acumulado de processamento/dia&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;❌ Antes do índice&lt;/td&gt;
&lt;td&gt;~6 horas e 18 minutos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ Depois do índice&lt;/td&gt;
&lt;td&gt;~40 segundos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O job voltou a terminar bem antes do horário comercial. Os processos diurnos pararam de ser impactados. E tudo isso sem reescrever uma linha de código de negócio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mas afinal: o que é um índice de banco de dados?
&lt;/h2&gt;

&lt;p&gt;Se você chegou até aqui e nunca parou para entender o que um índice faz de verdade, esse é o momento.&lt;/p&gt;

&lt;h3&gt;
  
  
  A analogia do livro
&lt;/h3&gt;

&lt;p&gt;Imagine que você tem um livro enciclopédico com 10.000 páginas e precisa encontrar tudo que fala sobre "fotossíntese". Você tem duas opções:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sem índice:&lt;/strong&gt; Ler página por página do início ao fim. Funciona, mas demora.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Com índice:&lt;/strong&gt; Ir até o índice remissivo no final do livro, localizar "fotossíntese" em segundos e ir direto às páginas certas.
Um índice de banco de dados funciona exatamente assim. Ele é uma &lt;strong&gt;estrutura de dados separada&lt;/strong&gt; (geralmente uma B-Tree) que mantém uma cópia ordenada de uma ou mais colunas, com ponteiros para as linhas reais da tabela.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  O que um índice resolve?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Buscas por igualdade:&lt;/strong&gt; &lt;code&gt;WHERE id = 42&lt;/code&gt; o índice encontra o valor direto, sem varrer a tabela&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buscas por intervalo:&lt;/strong&gt; &lt;code&gt;WHERE date_created BETWEEN '2025-01-01' AND '2025-12-31'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordenação:&lt;/strong&gt; &lt;code&gt;ORDER BY last_execution DESC&lt;/code&gt; se o índice já estiver ordenado nessa direção, o banco nem precisa ordenar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries cobertas (covering index):&lt;/strong&gt; Com a cláusula &lt;code&gt;INCLUDE&lt;/code&gt;, o banco pode responder à query inteira só pelo índice, sem nem tocar na tabela original
### O que um índice &lt;em&gt;não&lt;/em&gt; é (e quando ele atrapalha)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Índice não é gratuito. Ele tem custos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Espaço em disco:&lt;/strong&gt; o índice ocupa armazenamento adicional&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custo de escrita:&lt;/strong&gt; toda vez que um &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt; ou &lt;code&gt;DELETE&lt;/code&gt; acontece, os índices afetados também precisam ser atualizados&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manutenção:&lt;/strong&gt; índices fragmentados precisam ser reorganizados periodicamente
Por isso, criar índices sem critério pode ser tão prejudicial quanto não tê-los. A regra de ouro é:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Crie índices nas colunas que aparecem frequentemente em cláusulas &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;JOIN&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt; e &lt;code&gt;GROUP BY&lt;/code&gt; de queries lentas, especialmente em tabelas grandes.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Como identificar quando você precisa de um índice
&lt;/h2&gt;

&lt;p&gt;Alguns sinais de alerta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Queries que demoram mais conforme a tabela cresce&lt;/strong&gt; (como o nosso caso)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full table scans&lt;/strong&gt; aparecendo nos planos de execução (&lt;code&gt;EXPLAIN&lt;/code&gt; / &lt;code&gt;Query Execution Plan&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeouts em operações que antes eram rápidas&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU do banco de dados consistentemente alta&lt;/strong&gt; durante períodos de consulta
Ferramentas como o &lt;strong&gt;Azure Application Insights&lt;/strong&gt;, &lt;strong&gt;pg_stat_statements&lt;/strong&gt; (PostgreSQL), &lt;strong&gt;slow query log&lt;/strong&gt; (MySQL) e o &lt;strong&gt;Query Store&lt;/strong&gt; (SQL Server) são aliadas valiosas nesse diagnóstico.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anatomia do índice que resolveu o problema
&lt;/h2&gt;

&lt;p&gt;Voltando ao índice criado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_job_result_process_date&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;app_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_results&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fk_process_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_created&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result_payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Por que esse design?&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Componente&lt;/th&gt;
&lt;th&gt;Motivo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fk_process_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filtro principal da query (cada processo tem seu identificador)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;date_created DESC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A query precisa do resultado mais recente primeiro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INCLUDE (...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Colunas retornadas pela query, incluí-las evita um segundo acesso à tabela&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;O resultado é um &lt;strong&gt;covering index&lt;/strong&gt;: o banco responde à query inteira consultando apenas o índice, sem precisar buscar dados na tabela principal. É a forma mais eficiente de otimização de leitura possível.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Performance não é só sobre algoritmos ou arquitetura de microsserviços. Às vezes, o gargalo está numa operação aparentemente simples que o banco de dados precisa executar milhares de vezes por dia, e que ninguém percebe até que o custo acumulado se torne um problema real.&lt;/p&gt;

&lt;p&gt;Nesse caso, &lt;strong&gt;um único índice bem pensado&lt;/strong&gt; transformou 6 horas de processamento em 40 segundos. Sem refatoração. Sem mudança de arquitetura. Sem downtime.&lt;/p&gt;

&lt;p&gt;Se você ainda não tem o hábito de revisar os planos de execução das suas queries críticas, comece agora. O banco de dados tem muito a te contar, basta saber ouvir.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sugestões de leitura para se aprofundar: B-Tree indexes, covering indexes, query execution plans, índices compostos e ferramentas de profiling como EXPLAIN ANALYZE.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>braziliandevs</category>
      <category>performance</category>
      <category>career</category>
    </item>
    <item>
      <title>Por que eu começo 10 projetos e não termino nenhum?</title>
      <dc:creator>Yuri Souza</dc:creator>
      <pubDate>Sun, 05 Apr 2026 23:48:52 +0000</pubDate>
      <link>https://dev.to/he4rt/por-que-eu-comeco-10-projetos-e-nao-termino-nenhum-1139</link>
      <guid>https://dev.to/he4rt/por-que-eu-comeco-10-projetos-e-nao-termino-nenhum-1139</guid>
      <description>&lt;p&gt;Você já passou uma tarde inteira configurando um projeto novo, escolhendo a stack, criando o repositório, estruturando as pastas e sentiu que estava &lt;em&gt;voando&lt;/em&gt;? Aquela sensação de que dessa vez vai ser diferente, que essa ideia é boa demais pra morrer na gaveta? Eu também. O problema é que dois dias depois eu estava fazendo exatamente a mesma coisa, só que com outra ideia.&lt;/p&gt;

&lt;p&gt;Se você se identificou, esse artigo é pra você. Não porque eu tenho a solução mágica, mas porque finalmente entendi o mecanismo por trás disso. E entender já muda muita coisa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;O prazer de começar&lt;/li&gt;
&lt;li&gt;O momento em que tudo desmorona&lt;/li&gt;
&lt;li&gt;O objeto brilhante&lt;/li&gt;
&lt;li&gt;A culpa que ninguém fala&lt;/li&gt;
&lt;li&gt;Não é preguiça. É neurologia.&lt;/li&gt;
&lt;li&gt;Conclusão&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  O prazer de começar
&lt;/h2&gt;

&lt;p&gt;Tem algo muito específico que acontece quando você começa um projeto novo.&lt;/p&gt;

&lt;p&gt;O problema ainda não existe de verdade. Tudo é possibilidade. Você ainda não encontrou o bug impossível de reproduzir, ainda não percebeu que a arquitetura que escolheu não escala, ainda não chegou na parte chata de fazer o CRUD de usuário pela décima vez na vida.&lt;/p&gt;

&lt;p&gt;Nessa fase, o cérebro libera dopamina. Bastante. A antecipação de uma recompensa futura ativa os mesmos circuitos que uma conquista real. Ou seja: &lt;strong&gt;só de imaginar o projeto funcionando, você já sente parte da recompensa.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Isso explica o prazer visceral do setup. Escolher o nome do repositório, montar a estrutura de pastas, escrever o README antes de ter uma linha de código que funciona... tudo isso alimenta aquela sensação.&lt;/p&gt;

&lt;p&gt;O problema? É que ela passa.&lt;/p&gt;




&lt;h2&gt;
  
  
  O momento em que tudo desmorona
&lt;/h2&gt;

&lt;p&gt;Existe um ponto específico em todo projeto onde a magia some. Eu chamo de &lt;strong&gt;o vale do tédio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;É quando você já sabe o que precisa fazer, mas o trabalho deixou de ser estimulante. As decisões arquiteturais foram tomadas. O setup tá pronto. Agora é só... executar. Implementar a feature chata. Escrever o teste. Lidar com o caso que você não previu.&lt;/p&gt;

&lt;p&gt;Para a maioria das pessoas, esse momento é desconfortável mas passável. Você empurra, entrega, segue.&lt;/p&gt;

&lt;p&gt;Para alguns cérebros, incluindo o meu, esse momento é quase fisicamente doloroso. Não é falta de vontade. É que o sistema de recompensa do cérebro simplesmente não libera o combustível necessário pra continuar uma tarefa que deixou de ser nova.&lt;/p&gt;

&lt;p&gt;E aí começa o ciclo de autos sabotagem mais clássico do mundo do dev:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Antes de continuar, vou refatorar essa parte aqui."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Preciso repensar a arquitetura antes de avançar."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Deixa eu criar um boilerplate melhor pra usar nos próximos projetos."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Você não está sendo preguiçoso. Você está inconscientemente procurando a dopamina do recomeço dentro do próprio projeto.&lt;/p&gt;




&lt;h2&gt;
  
  
  O objeto brilhante
&lt;/h2&gt;

&lt;p&gt;E então aparece. Uma ideia nova.&lt;/p&gt;

&lt;p&gt;Pode ser um problema que você viu no trabalho, uma conversa no Twitter, um repositório no GitHub que te inspirou. De repente, aquela ideia nova parece &lt;strong&gt;muito mais interessante&lt;/strong&gt; do que o projeto que você abandonou no vale do tédio.&lt;/p&gt;

&lt;p&gt;E ela realmente é, pelo mesmo motivo que o projeto anterior era interessante no início. Ela ainda não tem o peso da implementação. Ainda não tem os bugs, as decisões difíceis, o trabalho repetitivo.&lt;/p&gt;

&lt;p&gt;Esse fenômeno tem um nome informal: &lt;strong&gt;Síndrome do Objeto Brilhante&lt;/strong&gt;. E ele é mais intenso em cérebros que têm dificuldade de regular dopamina naturalmente.&lt;/p&gt;

&lt;p&gt;O que acontece na prática? O projeto antigo não morre oficialmente. Ele só vai pra uma pasta chamada &lt;code&gt;projetos/&lt;/code&gt; e fica lá, acumulando poeira junto com outros oito projetos que passaram pelo mesmo ciclo.&lt;/p&gt;




&lt;h2&gt;
  
  
  A culpa que ninguém fala
&lt;/h2&gt;

&lt;p&gt;Aqui está a parte que eu não via ninguém discutir: &lt;strong&gt;a culpa.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Porque quando você abandona um projeto, você não apenas perde o projeto. Você coleciona evidência contra si mesmo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Eu nunca termino nada."&lt;/em&gt;&lt;br&gt;
&lt;em&gt;"Eu começo cheio de energia e nunca entrego."&lt;/em&gt;&lt;br&gt;
&lt;em&gt;"Por que eu deveria começar esse projeto se vou abandonar igual aos outros?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;E essa narrativa é perigosa por dois motivos.&lt;/p&gt;

&lt;p&gt;Primeiro, porque ela não é completamente verdadeira. Você provavelmente termina muita coisa, no trabalho, em tarefas com prazo real, em situações onde tem alguém esperando. O problema não é terminar em si. É terminar coisas que dependem 100% da sua motivação interna, sem deadline, sem stakeholder, sem pressão externa.&lt;/p&gt;

&lt;p&gt;Segundo, você começa a acreditar que não é capaz de terminar, então para de tentar de verdade, e aí realmente não termina. O ciclo se fecha.&lt;/p&gt;




&lt;h2&gt;
  
  
  Não é preguiça. É neurologia.
&lt;/h2&gt;

&lt;p&gt;Vou ser direto: se você se reconheceu em tudo que eu escrevi acima, há uma chance real de que seu cérebro simplesmente funciona de um jeito diferente da média.&lt;/p&gt;

&lt;p&gt;Tem um tipo de cérebro que odeia repetição mas ama coisa nova. Não é fraqueza, é como ele funciona. O problema é que todo projeto tem uma fase nova e uma fase chata — e esse cérebro simplesmente apaga na segunda.&lt;/p&gt;

&lt;p&gt;Não é falta de disciplina. É que o combustível que o seu cérebro usa pra manter o foco não funciona igual ao de todo mundo.&lt;/p&gt;

&lt;p&gt;A diferença entre entender isso e não entender é enorme. Quando você não entende, você passa anos se achando preguiçoso, incompetente, incapaz de terminar o que começa e quando entende você começa a fazer perguntas diferentes:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Como eu estruturo esse projeto pra ter recompensas menores e mais frequentes?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Como eu crio pressão externa pra compensar a falta de pressão interna?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Qual é o menor projeto possível que ainda entrega valor?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Não são perguntas fáceis de responder. Mas são as perguntas certas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Eu ainda começo projetos demais. Provavelmente sempre vou começar. Mas aprendi a parar de tratar isso como um defeito de caráter e passei a tratar como uma característica que precisa de estratégia.&lt;/p&gt;

&lt;p&gt;O problema nunca foi a quantidade de projetos que eu começo. Foi a narrativa que eu construí em volta dos que eu não terminei.&lt;/p&gt;

&lt;p&gt;Se você chegou até aqui e se reconheceu em alguma parte desse texto: você não está sozinho. E você provavelmente é melhor em começar coisas do que 90% das pessoas. Isso não é pouco, é uma habilidade real, que só precisa de direção.&lt;/p&gt;

&lt;p&gt;O próximo projeto vai começar. A questão é o que você vai fazer diferente dessa vez.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Escrito por alguém que tem pelo menos dez pastas &lt;code&gt;projetos/&lt;/code&gt; abertas no VS Code agora mesmo.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>braziliandevs</category>
      <category>productivity</category>
      <category>writing</category>
    </item>
    <item>
      <title>O que uma usina nuclear tem a ver com o seu processo de QA?</title>
      <dc:creator>Alicia Marianne Gonçalves</dc:creator>
      <pubDate>Sun, 05 Apr 2026 12:02:52 +0000</pubDate>
      <link>https://dev.to/he4rt/o-que-uma-usina-nuclear-tem-a-ver-com-o-seu-processo-de-qa-103j</link>
      <guid>https://dev.to/he4rt/o-que-uma-usina-nuclear-tem-a-ver-com-o-seu-processo-de-qa-103j</guid>
      <description>&lt;p&gt;A gente sabe que testar e validar um software antes de ir para produção é importante. Mas você já parou para pensar no peso real que isso carrega?&lt;/p&gt;

&lt;p&gt;Recentemente, estava revendo a série &lt;em&gt;Chernobyl&lt;/em&gt;, e ela me fez refletir sobre muita coisa — especialmente sobre a forma como encaro minha área, sendo QA, e sobre a responsabilidade que ela traz. Resolvi compartilhar isso com vocês.&lt;/p&gt;

&lt;p&gt;Para quem não conhece, &lt;em&gt;Chernobyl&lt;/em&gt; é uma minissérie dramática lançada em 2019 que retrata o desastre nuclear ocorrido na usina de mesmo nome, na então União Soviética, em 26 de abril de 1986. A história acompanha os eventos logo após a explosão do reator número 4 — o caos, as tentativas do governo soviético de esconder a gravidade do acidente e o enorme esforço de cientistas, bombeiros, militares e trabalhadores que arriscaram, e muitas vezes perderam, suas vidas para evitar uma catástrofe ainda maior. A série também segue o cientista Valery Legasov, que tenta descobrir a verdadeira causa do acidente e expor a verdade por trás da tragédia.&lt;/p&gt;

&lt;p&gt;Mas o ponto aqui vai além da série.&lt;/p&gt;

&lt;p&gt;O que mais me chamou atenção foi o quanto aquela tragédia conversa com algo que vivemos diariamente no desenvolvimento de software: &lt;strong&gt;a responsabilidade nas decisões&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que aconteceu em Chernobyl?
&lt;/h2&gt;

&lt;p&gt;Mesmo sabendo que existem elementos dramatizados, dá para aprender muita coisa com esse desastre. Não sou física nuclear, mas vou tentar resumir o que aconteceu — porque foi justamente essa parte que mais me fez refletir sobre responsabilidade e tomada de decisão.&lt;/p&gt;

&lt;p&gt;Na madrugada do dia &lt;strong&gt;26 de abril de 1986&lt;/strong&gt;, os operadores da usina realizavam um &lt;strong&gt;teste de segurança no reator 4&lt;/strong&gt;. O objetivo era validar se, em caso de queda de energia, as turbinas ainda conseguiriam gerar eletricidade por alguns segundos — tempo suficiente até que os geradores de emergência fossem acionados. No papel, o teste parecia simples.&lt;/p&gt;

&lt;p&gt;O problema é que, para executá-lo, diversos sistemas de segurança foram desativados e a potência do reator foi reduzida para um nível muito abaixo do ideal. Foi aí que tudo começou a sair do controle.&lt;/p&gt;

&lt;p&gt;O reator utilizado era do tipo &lt;strong&gt;RBMK&lt;/strong&gt;, um modelo com uma falha crítica de projeto: em determinadas condições, quanto mais vapor era gerado dentro do sistema, maior ficava a potência do reator. Em vez de estabilizar, ele se tornava cada vez mais instável. Para piorar, o teste foi conduzido sob forte pressão da liderança, mesmo diante de sinais claros de que não era seguro continuar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1ar1wiltc1iy8426lz9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk1ar1wiltc1iy8426lz9.png" alt="Chernobyl" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ao longo da série, vemos os próprios operadores levantando preocupações — que foram ignoradas. Quando perceberam que a situação era crítica, acionaram o botão de desligamento de emergência. Em teoria, esse comando deveria encerrar a reação. Mas, por uma falha no design das barras de controle, o efeito inicial foi o oposto: a potência disparou. Em poucos segundos, temperatura e pressão subiram de forma descontrolada.&lt;/p&gt;

&lt;p&gt;O resultado foram duas explosões que destruíram o topo do reator, expuseram o núcleo à atmosfera e liberaram uma enorme quantidade de material radioativo. O incêndio que se seguiu espalhou radiação por boa parte da Europa, transformando Chernobyl no maior desastre nuclear da história.&lt;/p&gt;

&lt;p&gt;O que mais me impactou foi perceber que a tragédia não aconteceu por um único erro. Ela foi consequência de uma &lt;strong&gt;cadeia de decisões ruins&lt;/strong&gt;: falhas técnicas ignoradas, riscos mal avaliados e pessoas que não foram ouvidas.&lt;/p&gt;




&lt;h2&gt;
  
  
  E o que isso tem a ver com software?
&lt;/h2&gt;

&lt;p&gt;Depois de assistir à série, comecei a fazer um paralelo com a nossa área. Porque, no fim, quantas vezes um incidente em produção também não nasce da mesma forma?&lt;/p&gt;

&lt;p&gt;Nem sempre o problema vem de um único bug. Muitas vezes, ele é o resultado de uma sequência de decisões tomadas sem o devido cuidado: um requisito mal definido, um risco não mapeado, uma validação superficial, uma entrega apressada — ou um alerta levantado pelo time que acabou sendo ignorado.&lt;/p&gt;

&lt;p&gt;Foi aí que a série me fez enxergar algo que vai além do contexto dela: &lt;strong&gt;quando a pressa fala mais alto do que a análise, o custo quase sempre aparece depois&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  O maior problema: risco tratado de forma rasa
&lt;/h2&gt;

&lt;p&gt;Um dos maiores desafios que vejo hoje no desenvolvimento de software é justamente o planejamento e o levantamento de riscos feitos de maneira superficial.&lt;/p&gt;

&lt;p&gt;Tenho certeza de que você vai concordar: não tem coisa pior do que refazer algo ou ficar apagando incêndios que poderiam ter sido discutidos antes.&lt;/p&gt;

&lt;p&gt;Vivemos num mundo onde tempo é dinheiro. E é exatamente por isso que qualidade precisa estar presente desde o início — não como uma etapa final, mas como parte do processo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomhezjqhepvjcvwbmn1d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fomhezjqhepvjcvwbmn1d.png" alt="Cadeia de erros" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como QA, vou compartilhar duas coisas que considero essenciais.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ouça sua equipe
&lt;/h3&gt;

&lt;p&gt;Independentemente da sua posição no time, ouça as pessoas ao seu redor.&lt;/p&gt;

&lt;p&gt;Ninguém conhece melhor o produto do que quem o construiu, testou e convive com ele diariamente. Antes de uma nova funcionalidade entrar ou de um teste ser executado, converse com o time.&lt;/p&gt;

&lt;p&gt;Escute quem desenvolveu. Escute o produto. Escute suporte. Escute quem está mais próximo do usuário.&lt;/p&gt;

&lt;p&gt;Muitas vezes, o risco já foi identificado por alguém. Ele só não foi ouvido.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analise antes de agir
&lt;/h3&gt;

&lt;p&gt;Entender o impacto de uma mudança não é só uma questão técnica — é também uma questão de negócio.&lt;/p&gt;

&lt;p&gt;Por exemplo: você vai melhorar a query de uma API. Em teoria, isso pode não alterar nenhuma regra de negócio. Mas como isso afeta o usuário final? A performance realmente melhorou? Existe algum impacto colateral? Como vamos medir se essa mudança foi positiva ou negativa?&lt;/p&gt;

&lt;p&gt;Nem toda melhoria técnica gera melhoria de produto. E esse olhar crítico é parte fundamental do papel de QA.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;No fim, &lt;em&gt;Chernobyl&lt;/em&gt; me fez refletir sobre algo que vai muito além de uma série: &lt;strong&gt;a responsabilidade por trás de cada decisão que tomamos no dia a dia&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A gente não está lidando com um reator nuclear. Mas ainda assim lida com impacto real — no usuário, no negócio e no próprio time. Um risco ignorado, um teste mal planejado ou uma decisão tomada sem ouvir a equipe podem não gerar uma catástrofe, mas certamente geram problemas que poderiam ter sido evitados com mais atenção, diálogo e análise.&lt;/p&gt;

&lt;p&gt;Para mim, ser QA vai muito além de encontrar bugs.&lt;/p&gt;

&lt;p&gt;É questionar antes que o problema aconteça. É analisar cenários com olhar crítico. É antecipar riscos. É provocar conversas importantes dentro do time. É ajudar a construir decisões mais seguras e conscientes.&lt;/p&gt;

&lt;p&gt;No final, qualidade não é apenas sobre software funcionando.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;É sobre responsabilidade, colaboração e cuidado com tudo aquilo que criamos.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>testing</category>
      <category>qa</category>
      <category>software</category>
    </item>
    <item>
      <title>De front-end para UX, e de volta ao código: o que significa ser Design Engineer em 2026</title>
      <dc:creator>vitoriazzp</dc:creator>
      <pubDate>Fri, 03 Apr 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/he4rt/de-front-end-para-ux-e-de-volta-ao-codigo-o-que-significa-ser-design-engineer-em-2026-3j74</link>
      <guid>https://dev.to/he4rt/de-front-end-para-ux-e-de-volta-ao-codigo-o-que-significa-ser-design-engineer-em-2026-3j74</guid>
      <description>&lt;p&gt;&lt;strong&gt;Sou UX/UI designer, mas antes disso fui front-end.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Atuei cerca de 5 anos trabalhando com HTML e CSS, transformando layouts em páginas, entendendo hierarquia de informação e estrutura de interface. Depois, tomei o caminho oposto: migrei para UX/UI e agora completo 5 anos atuando em produtos, passando por fintech, utilities e atuando como Product Designer.&lt;/p&gt;

&lt;p&gt;Essa trajetória, de front para UX e agora voltando a se aproximar do código, é justamente o que me levou a me reconhecer em um termo que gosto bastante: &lt;strong&gt;Design Engineer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Minha trajetória: 5 anos de front-end, 5 anos de UX
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Quando era front-end&lt;/strong&gt;, eu via a tela como um resultado de código: HTML estruturando a informação, CSS dando forma e layout, um pouco de JavaScript dando comportamento.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quando migrei para UX&lt;/strong&gt;, passei a olhar mais para o todo do produto: pesquisa, fluxos, contexto do usuário, design systems, governança, revisão de interfaces, conversa com times de produto e de negócios.&lt;br&gt;
Hoje, percebo que essas duas visões não são opostas. &lt;em&gt;Elas se completam.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é design engineer em 2026?
&lt;/h2&gt;

&lt;p&gt;Se você pesquisar sobre "Design Engineer", vai encontrar muitas definições técnicas, mas na prática o que mais faz sentido para mim é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A pessoa que entende UX, código e um pouco de backend ao mesmo tempo, e usa isso para desenhar interfaces que são pensadas desde o primeiro pixel até a última chamada de API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Não é só "quem desenha + quem programa"&lt;/strong&gt;&lt;br&gt;
É quem pensa em &lt;strong&gt;&lt;em&gt;experiência e implementação juntas&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Um botão que não só parece bem-desenhado, mas também considera estados de loading, erro, disabled.&lt;/li&gt;
&lt;li&gt;Um fluxo de cadastro que não só é bonito, mas que já prevê o que o backend vai precisar para validar, salvar e devolver feedback.&lt;/li&gt;
&lt;li&gt;Um produto que pensa em performance, acessibilidade e usabilidade em uma única conversa.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Como front-end e UX mudam sua forma de ver produto
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Como eu via a tela como desenvolvedor front-end&lt;/strong&gt;&lt;br&gt;
HTML estruturando a informação. CSS dando forma. Um pouco de JavaScript dando comportamento. A tela era resultado direto do código.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que mudou quando migrei para UX/UI&lt;/strong&gt;&lt;br&gt;
Passei a olhar para o produto como um todo: pesquisa, fluxos, contexto do usuário. O código virou uma consequência, não o ponto de partida.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O que muda agora é que percebo que não preciso mais ficar só de um lado&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Por que estou voltando ao código agora
&lt;/h2&gt;

&lt;p&gt;Com o avanço de ferramentas como IA integrada ao Figma, prototipagem cada vez mais próxima do código e experiências acumuladas como Product Designer, foi aí que o conceito de Design Engineer passou a fazer sentido de verdade pra mim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript moderno, React e um pouco de backend&lt;/strong&gt;&lt;br&gt;
Comecei a estudar de forma mais focada para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Atualizar meu JavaScript (ES2024, async/await, fetch, arrays/objetos)&lt;/li&gt;
&lt;li&gt;Reativar React (componentes, hooks, estado compartilhado)&lt;/li&gt;
&lt;li&gt;Entender backend leve (Node/Express, rotas simples, persistência básica)&lt;/li&gt;
&lt;li&gt;Pensar em performance e otimização das interfaces que ajudo a construir&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Como estou usando IA no processo de aprendizado
&lt;/h3&gt;

&lt;p&gt;Hoje, uso o Claude AI não como substituto, mas como apoio para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quebrar problemas de código em etapas menores&lt;/li&gt;
&lt;li&gt;Revisar fluxos de dados entre front e backend&lt;/li&gt;
&lt;li&gt;Organizar pensamentos e lógica de features&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Design Engineer não é um cargo. É uma forma de pensar
&lt;/h3&gt;

&lt;p&gt;O que mais gosto de dizer é que, em 2026, &lt;em&gt;&lt;strong&gt;Design Engineer não é só um título de empresa grande&lt;/strong&gt;&lt;/em&gt; ou de time específico. Pode existir em qualquer lugar onde UX e código se encontram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Em um produto próprio&lt;/li&gt;
&lt;li&gt;Em um projeto freelancer&lt;/li&gt;
&lt;li&gt;Em um repositório público&lt;/li&gt;
&lt;li&gt;Em um fluxo de trabalho híbrido, mesmo sem ter um cargo oficial com esse nome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O que importa é a &lt;strong&gt;maneira de pensar&lt;/strong&gt;: UX + código ao mesmo tempo. Protótipos e implementação como parte do mesmo processo. Interfaces que consideram o que o usuário sente e o que o backend precisa.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que vem por aí: UX data-driven e componentes React
&lt;/h2&gt;

&lt;p&gt;Esse movimento não é só "voltar" ao front-end. É re-aprender JavaScript, entender melhor React e backend, e levar essa visão de UX para dentro do código.&lt;br&gt;
Em breve, quero escrever mais sobre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como transformo um fluxo de UX do Figma em componentes React&lt;/li&gt;
&lt;li&gt;Como penso em performance e carregamento em UX para web&lt;/li&gt;
&lt;li&gt;Como organizar estudos de UX + código em ciclos curtos e práticos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E numa parte que ainda não me sinto confortável, mas sei que é essencial: vou falar sobre como estou começando a me tornar uma &lt;strong&gt;UX mais data-driven&lt;/strong&gt;, porque ser Design Engineer em 2026 também é aprender a ouvir o que os números dizem sobre o UX que eu desenho.&lt;/p&gt;

&lt;h2&gt;
  
  
  Você também está nesse meio-termo entre UX e código?
&lt;/h2&gt;

&lt;p&gt;Se você chegou até aqui, é bem provável que também se sinta em algum meio-termo entre UX e código.&lt;/p&gt;

&lt;p&gt;Que tal comentar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como você se vê hoje entre design e desenvolvimento?&lt;/li&gt;
&lt;li&gt;Você já tentou voltar pro código depois de virar UX, ou está no caminho invertido?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esse é o tipo de conversa que me ajuda a entender como o conceito de &lt;strong&gt;Design Engineer&lt;/strong&gt; está se moldando, bem além de títulos de empresa.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>design</category>
      <category>ux</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Construí um gerador de playlists no Spotify com Claude</title>
      <dc:creator>Leo Garcez</dc:creator>
      <pubDate>Tue, 24 Mar 2026 02:01:08 +0000</pubDate>
      <link>https://dev.to/he4rt/construi-um-gerador-de-playlists-no-spotify-com-claude-18ge</link>
      <guid>https://dev.to/he4rt/construi-um-gerador-de-playlists-no-spotify-com-claude-18ge</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Eu queria digitar &lt;em&gt;“noite chuvosa, meio melancólica”&lt;/em&gt; e receber uma playlist perfeita. Então eu construí isso.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Eu construí um gerador de playlists com IA usando &lt;strong&gt;Claude + Spotify API&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Você descreve um humor → ele gera 50 músicas → salva direto no Spotify&lt;/li&gt;
&lt;li&gt;O maior problema foi OAuth local com NextAuth (sim, foi um inferno)&lt;/li&gt;
&lt;li&gt;Claude funciona bem, mas precisa de bastante controle pra não inventar músicas&lt;/li&gt;
&lt;li&gt;Streaming com SSE melhorou muito a UX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://moodify.com.br" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/LeoGarcez/moodify" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/playlist/0L2bStHbYyfiGJFZfB1CDO?si=a93194d73eec484c" rel="noopener noreferrer"&gt;Playlist de exemplo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Índice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Ideia&lt;/li&gt;
&lt;li&gt;A Stack&lt;/li&gt;
&lt;li&gt;O Problema de Trabalhar com OAuth Localmente&lt;/li&gt;
&lt;li&gt;Endpoints Deprecated do Spotify&lt;/li&gt;
&lt;li&gt;Spotify em Produção&lt;/li&gt;
&lt;li&gt;Fazendo o Claude Obedecer&lt;/li&gt;
&lt;li&gt;Prompt Engineering Anti-Alucinação&lt;/li&gt;
&lt;li&gt;Modo Related Artists&lt;/li&gt;
&lt;li&gt;Construindo o Perfil Musical&lt;/li&gt;
&lt;li&gt;Streaming com SSE&lt;/li&gt;
&lt;li&gt;O Que Aprendi&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Ideia
&lt;/h2&gt;

&lt;p&gt;Fazer um gerador de playlists que realmente &lt;em&gt;entendesse&lt;/em&gt; vibes, não só tags de gênero. Algo tipo: você digita &lt;strong&gt;"tarde fria num apartamento vazio"&lt;/strong&gt; e recebe uma playlist boa de verdade, já salva no seu Spotify.&lt;/p&gt;

&lt;p&gt;O conceito:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Usuário descreve um humor&lt;/li&gt;
&lt;li&gt;Claude retorna 50 músicas em JSON&lt;/li&gt;
&lt;li&gt;App busca cada faixa no Spotify&lt;/li&gt;
&lt;li&gt;Cria e salva a playlist na conta do usuário&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Três APIs, um app.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js 14 (App Router)
NextAuth v5 beta
Anthropic Claude API (claude-sonnet-4-6)
Spotify Web API
Supabase (PostgreSQL)
TypeScript + Tailwind CSS + Framer Motion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Escolhi o Claude porque ele tá bem em alta agora e, na prática, é confiável, alucina menos do que eu esperava pra esse tipo de tarefa. Ele é um pouco menos criativo que o GPT nas recomendações, mas compensa sendo mais previsível no formato das respostas, o que importa bastante quando você tá parseando JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Problema de Trabalhar com OAuth Localmente
&lt;/h2&gt;

&lt;p&gt;Isso me custou algumas horas e sessões de debug. Vou detalhar porque tem várias camadas de problema e você provavelmente vai bater na mesma parede se estiver usando NextAuth v5 com Spotify.&lt;/p&gt;

&lt;h3&gt;
  
  
  O Spotify não aceita &lt;code&gt;localhost&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;O Spotify &lt;a href="https://developer.spotify.com/documentation/web-api/concepts/redirect_uri" rel="noopener noreferrer"&gt;proíbe &lt;code&gt;localhost&lt;/code&gt; como redirect URI&lt;/a&gt; pra URIs de loopback. A solução é usar &lt;code&gt;127.0.0.1&lt;/code&gt;. Cadastrei &lt;code&gt;http://127.0.0.1:3000/api/auth/callback/spotify&lt;/code&gt; no dashboard e setei &lt;code&gt;AUTH_URL=http://127.0.0.1:3000&lt;/code&gt; no &lt;code&gt;.env.local&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Não foi suficiente.&lt;/p&gt;

&lt;h3&gt;
  
  
  O &lt;code&gt;NextRequest&lt;/code&gt; normaliza URLs pra &lt;code&gt;localhost&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;O Next.js, independente do host que você passa pra &lt;code&gt;next dev -H&lt;/code&gt;, normaliza &lt;code&gt;req.url&lt;/code&gt; e &lt;code&gt;req.nextUrl.href&lt;/code&gt; de volta pra &lt;code&gt;localhost&lt;/code&gt; em desenvolvimento. Isso não é bug documentado — é comportamento interno do framework.&lt;/p&gt;

&lt;p&gt;O NextAuth v5 tem um utilitário chamado &lt;code&gt;reqWithEnvURL&lt;/code&gt; que tenta corrigir exatamente isso, mas falha silenciosamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Dentro do next-auth — simplificado&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reqWithEnvURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ← o construtor normaliza de volta pra localhost&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo passando &lt;code&gt;127.0.0.1&lt;/code&gt; explicitamente, o construtor do &lt;code&gt;NextRequest&lt;/code&gt; sobrescreve. A "correção" não funciona.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dois momentos onde o redirect URI importa
&lt;/h3&gt;

&lt;p&gt;O OAuth tem &lt;strong&gt;dois&lt;/strong&gt; momentos distintos onde o redirect URI aparece:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requisição de autorização&lt;/strong&gt; — a URL do Spotify onde o usuário loga. O &lt;code&gt;redirect_uri&lt;/code&gt; aqui vem dos seus params de configuração, então você pode hardcodar &lt;code&gt;127.0.0.1&lt;/code&gt; na config do provider. Isso funcionou.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Troca de token&lt;/strong&gt; — quando o Spotify manda o código de volta, o &lt;code&gt;@auth/core&lt;/code&gt; envia um POST pra trocar o código por tokens. O &lt;code&gt;redirect_uri&lt;/code&gt; nessa requisição vem de &lt;code&gt;provider.callbackUrl&lt;/code&gt;, que é derivado de &lt;code&gt;params.url.origin&lt;/code&gt; — ou seja, da &lt;strong&gt;URL da requisição de callback&lt;/strong&gt;. Se essa URL ainda diz &lt;code&gt;localhost&lt;/code&gt;, a troca falha com &lt;code&gt;invalid_grant: Invalid redirect URI&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O sintoma era desconcertante: a URL de autorização mostrava &lt;code&gt;127.0.0.1&lt;/code&gt; corretamente, mas a troca de token continuava falhando. Fui atrás do código do &lt;code&gt;@auth/core&lt;/code&gt; pra entender o que tava acontecendo.&lt;/p&gt;

&lt;h3&gt;
  
  
  A solução: &lt;code&gt;Auth()&lt;/code&gt; direto com &lt;code&gt;Request&lt;/code&gt; nativo
&lt;/h3&gt;

&lt;p&gt;Objetos &lt;code&gt;Request&lt;/code&gt; nativos do browser/Node &lt;strong&gt;não normalizam URLs&lt;/strong&gt;. A correção é contornar os route handlers do NextAuth e chamar &lt;code&gt;Auth()&lt;/code&gt; do &lt;code&gt;@auth/core&lt;/code&gt; diretamente, passando um &lt;code&gt;Request&lt;/code&gt; nativo com a URL já corrigida:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/auth/[...nextauth]/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@auth/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../../../../auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fixedUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/[^/]&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authOrigin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixedUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hasBody&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// duplex necessário para streaming de body no Node.js&lt;/span&gt;
    &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;hasBody&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;duplex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;half&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;buildRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;authConfig&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Versão do &lt;code&gt;@auth/core&lt;/code&gt;:&lt;/strong&gt; ao usar &lt;code&gt;Auth()&lt;/code&gt; diretamente, instale a versão exata que o &lt;code&gt;next-auth&lt;/code&gt; usa internamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;ls&lt;/span&gt; @auth/core  &lt;span class="c"&gt;# veja qual versão o next-auth requer&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @auth/core@0.41.0 &lt;span class="nt"&gt;--save-exact&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Versões diferentes criam conflitos de tipo que explodem em runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuração explícita no &lt;code&gt;auth.ts&lt;/code&gt;:&lt;/strong&gt; ao contornar os handlers do NextAuth, &lt;code&gt;setEnvDefaults&lt;/code&gt; não roda mais. Configure &lt;code&gt;basePath&lt;/code&gt;, &lt;code&gt;secret&lt;/code&gt; e &lt;code&gt;redirect_uri&lt;/code&gt; explicitamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// auth.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextAuthConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;trustHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;basePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Spotify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_SPOTIFY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_SPOTIFY_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://accounts.spotify.com/authorize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SPOTIFY_SCOPES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;show_dialog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTH_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/auth/callback/spotify`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ... callbacks e pages como antes&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bônus: problema de domínio do cookie PKCE
&lt;/h3&gt;

&lt;p&gt;Mesmo com tudo acima, pode acontecer mais uma falha: se o usuário acessa &lt;code&gt;http://localhost:3000&lt;/code&gt;, o navegador seta o cookie PKCE pro domínio &lt;code&gt;localhost&lt;/code&gt;. Quando o Spotify redireciona de volta pra &lt;code&gt;http://127.0.0.1:3000/...&lt;/code&gt;, o navegador não envia o cookie — domínios diferentes — e o &lt;code&gt;code_verifier&lt;/code&gt; some. A troca falha de novo.&lt;/p&gt;

&lt;p&gt;A correção é garantir que &lt;code&gt;localhost:3000&lt;/code&gt; nunca apareça pro usuário, redirecionando via &lt;code&gt;next.config.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;redirects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:path*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;has&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:3000/:path*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;has&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://127.0.0.1:3000/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com isso, todo o fluxo de auth fica em &lt;code&gt;127.0.0.1&lt;/code&gt; e os cookies PKCE chegam onde precisam chegar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endpoints Deprecated do Spotify
&lt;/h2&gt;

&lt;p&gt;Com o auth funcionando, bati num muro de 403s.&lt;/p&gt;

&lt;p&gt;O Spotify deprecated alguns endpoints sem muito alarde:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Antigo (deprecated)&lt;/th&gt;
&lt;th&gt;Novo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /playlists/{id}/tracks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /playlists/{id}/items&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /playlists/{id}/tracks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /playlists/{id}/items&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GET /audio-features&lt;/code&gt;, &lt;code&gt;GET /recommendations&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Deprecated, sem substituto&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A migração &lt;code&gt;/tracks&lt;/code&gt; → &lt;code&gt;/items&lt;/code&gt; está documentada, mas é fácil de perder se você seguiu um tutorial de 2022. Audio features e recomendações sumindo foi mais chato — tive que construir contexto de energia de outra forma, mais sobre isso abaixo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spotify em Produção
&lt;/h2&gt;

&lt;p&gt;Tem uma limitação que eu não vi muito discutida: no modo de desenvolvimento, o Spotify permite apenas &lt;strong&gt;5 usuários autenticados&lt;/strong&gt; E eles precisam ser adicionados manualmente via allowlist no dashboard.&lt;/p&gt;

&lt;p&gt;Pra ir além disso, você precisa solicitar Extended Quota Mode. E o processo atual é bem pesado:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entidade jurídica registrada (pessoa física não é aceita desde maio de 2025)&lt;/li&gt;
&lt;li&gt;Serviço já lançado e ativo&lt;/li&gt;
&lt;li&gt;Mínimo de &lt;strong&gt;250.000 usuários ativos mensais&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Viabilidade comercial&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A análise pode levar até seis semanas, e sem garantia de aprovação.&lt;/p&gt;

&lt;p&gt;Na prática, isso significa que se você tá construindo algo novo como indie dev, vai ficar travado em modo de desenvolvimento. Você consegue testar e mostrar pra até 4 pessoas além de você — e só. Vale saber disso antes de planejar algum lançamento público.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fazendo o Claude Obedecer
&lt;/h2&gt;

&lt;p&gt;O system prompt instrui o Claude a retornar &lt;strong&gt;apenas&lt;/strong&gt; um array JSON, sem markdown, sem explicações. Essa parte é direta. O problema mais difícil é conseguir 50 faixas &lt;em&gt;que realmente existam&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  One-shot prompting
&lt;/h3&gt;

&lt;p&gt;Incluir uma conversa de exemplo completa (usuário + assistente) antes do request real reduziu bastante os erros de formato, especialmente com artistas não-ingleses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create a playlist for this mood/vibe: late night drive, nostalgic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Drive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The Cars&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Synth-pop clássico com energia perfeita de madrugada&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Running Up That Hill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Kate Bush&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Art-pop etéreo, emocionalmente assombroso&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;actualUserMessage&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extração de JSON como fallback
&lt;/h3&gt;

&lt;p&gt;Mesmo com prompting cuidadoso, modelos eventualmente jogam texto de introdução. Sempre extraia o array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\[[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nenhum array JSON encontrado&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;suggestions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prompt Engineering Anti-Alucinação
&lt;/h2&gt;

&lt;p&gt;Descobri que &lt;strong&gt;quanto mais músicas você pede, mais o modelo inventa títulos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pedindo 70 músicas, o Claude começa a criar faixas com nomes plausíveis que não existem. Com 50 e restrições explícitas, fica bem melhor.&lt;/p&gt;

&lt;p&gt;O que adicionei ao system prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXISTÊNCIA NO SPOTIFY — CRÍTICO:
Cada música DEVE existir no Spotify. Antes de incluir uma faixa, pergunte:
"Tenho certeza que esta música existe no Spotify com este título e artista exatos?"
Se houver qualquer dúvida, escolha outra música que você tem certeza.

REGRAS DE FORMATO DO TÍTULO:
- Use apenas o título canônico limpo do lançamento
- SEM sufixos: sem "- Remastered", "- Live at...", "- Radio Edit"
- Use o nome do lançamento mais conhecido, não compilações

ARMADILHAS COMUNS DE ALUCINAÇÃO:
- Não invente títulos de músicas que parecem plausíveis mas podem não existir
- Não confunda dois artistas com nomes similares
- Não sugira deep cuts que você não tem certeza
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também removi a abordagem de duas etapas "gerar 70, refinar pra 50". Era cara em tempo e custo, e uma única geração de 50 com boas instruções performa melhor.&lt;/p&gt;

&lt;h3&gt;
  
  
  O system prompt atual completo
&lt;/h3&gt;

&lt;p&gt;Esse é o prompt que tá rodando em produção hoje:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a world-class Spotify playlist curator.

GOAL:
Generate EXACTLY 50 high-quality songs for a playlist.

OUTPUT:
Return ONLY a raw JSON array. No markdown, no explanations.

Each item:
- "title": string — the canonical Spotify title, nothing else
- "artist": string — the primary artist exactly as listed on Spotify
- "reason": string — max 10 words

SPOTIFY EXISTENCE — CRITICAL:
Every song MUST exist on Spotify. Before including a track, ask yourself:
"Am I certain this song exists on Spotify under this exact title and artist?"
If there is any doubt, pick a different song you are certain about.

TITLE FORMAT RULES:
- Use the clean, canonical release title only
- NO suffixes: no "- Remastered", "- Live at...", "- Radio Edit", "- feat. X"
- NO parentheticals unless part of the official title
- Use the most well-known release name, not compilations or bonus versions

COMMON HALLUCINATION TRAPS TO AVOID:
- Do not invent song titles that sound plausible but may not exist
- Do not confuse two artists with similar names
- Do not suggest deep cuts you are uncertain about
- Do not suggest songs only released in specific regions unavailable globally

DISTRIBUTION:
- 50% recognizable hits (high confidence they exist)
- 40% lesser-known but confirmed tracks
- 10% deep cuts you are fully certain about

DIVERSITY:
- At least 2 genres
- At least 3 decades
- Non-English tracks welcome if you are certain they are on Spotify

CURATION:
- Cohesive flow, playlist-worthy, non-random
- No duplicates

Return ONLY the JSON array. Exactly 50 items.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Curiosidade: o prompt tá em inglês mesmo que o usuário escreva em português. O Claude entende o humor no idioma que vier e retorna os dados no formato esperado sem problema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Melhor resolução de busca no Spotify
&lt;/h3&gt;

&lt;p&gt;Mesmo com um bom prompt, algumas faixas voltam com títulos ou artistas levemente errados. Três ajustes no lado da busca:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Buscar 10 candidatos em vez de 1&lt;/strong&gt;&lt;br&gt;
Em vez de &lt;code&gt;limit=1&lt;/code&gt;, buscar &lt;code&gt;limit=10&lt;/code&gt; e escolher o melhor match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Filtro de popularidade&lt;/strong&gt;&lt;br&gt;
Pular resultados com &lt;code&gt;popularity &amp;lt; 30&lt;/code&gt; — evita gravar versões ao vivo obscuras quando a faixa correta não é encontrada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;POPULARITY_FLOOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aboveFloor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;popularity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;POPULARITY_FLOOR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aboveFloor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;aboveFloor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Fuzzy matching + seleção por popularidade&lt;/strong&gt;&lt;br&gt;
Normalizar strings e verificar correspondência bidirecional de substring, depois escolher o match com maior popularidade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fuzzyMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpotifyTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClaudeTrackSuggestion&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trackName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artistName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sugTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sugArtist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trackName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sugTitle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;sugTitle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trackName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artistMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;artistName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sugArtist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;sugArtist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;titleMatch&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;artistMatch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Modo Related Artists
&lt;/h2&gt;

&lt;p&gt;Playlists geradas por IA alucinam mais quando o humor é específico de artista — tipo "algo como Radiohead". Nesses casos, o grafo de artistas do próprio Spotify é mais confiável que o Claude.&lt;/p&gt;

&lt;p&gt;Adicionei detecção automática: uma chamada rápida ao Claude Haiku (~0,5s) classifica o prompt antes da geração principal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Retorna { mode: "ai" | "related", artists: ["Radiohead", "Nick Cave"] }&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;detectPlaylistMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mood&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se artistas são detectados, o app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Os resolve no Spotify&lt;/li&gt;
&lt;li&gt;Busca artistas relacionados (&lt;code&gt;GET /artists/{id}/related-artists&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Pega top tracks de cada artista relacionado&lt;/li&gt;
&lt;li&gt;Monta uma playlist com dados reais do Spotify, sem depender do Claude pra nada&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Se não detectar artistas, cai pro fluxo normal com Claude.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;related&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolvedSeeds&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;buildRelatedArtistsPlaylist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;detected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;energy&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;related&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seedArtists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resolvedSeeds&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// fallthrough para modo AI se não encontrou nada&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Construindo o Perfil Musical
&lt;/h2&gt;

&lt;p&gt;O app deixa usuários escolher uma playlist de referência ou seus top artistas do Spotify (último mês / 6 meses / histórico completo). Esse contexto é passado pro Claude como uma impressão digital musical.&lt;/p&gt;

&lt;p&gt;Mandar nomes de faixas brutos confunde o modelo. Em vez disso, agregue num perfil:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Contar frequência de artistas em todas as faixas da playlist&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artistFreq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;artists&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;artistFreq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;artistFreq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pra playlists, também busco dados de gênero dos artistas principais via &lt;code&gt;GET /artists/{id}&lt;/code&gt; (5 chamadas paralelas), já que os itens de playlist não retornam gêneros nativamente.&lt;/p&gt;

&lt;p&gt;Mesmo sem seleção de referência explícita, o app passa os top artistas e gêneros baseline do usuário como contexto suave pra cada geração.&lt;/p&gt;

&lt;p&gt;A instrução no prompt mudou de "não recomende esses artistas" pra algo mais útil:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;→ Biase as recomendações em direção a este DNA musical: tempo, humor e estilo de produção similar.
→ Descubra artistas com som SIMILAR — não necessariamente os mesmos artistas.
→ NÃO repita faixas já listadas acima.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Streaming com SSE
&lt;/h2&gt;

&lt;p&gt;O fluxo original: buscar todas as 50 faixas em paralelo, retornar tudo de uma vez, mostrar um spinner.&lt;/p&gt;

&lt;p&gt;O problema é que o usuário ficava olhando "Salvando no Spotify..." por 5-10 segundos sem nenhum feedback. Server-Sent Events resolve isso — cada faixa é emitida conforme resolve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Na rota da API&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReadableStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;\n\n`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;suggestions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;searchTrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;suggestion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;foundTracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suggestion&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;track&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;suggestion&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// emitido imediatamente&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Criar playlist depois que todas as buscas terminam&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createPlaylist&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
    &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;foundTracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/event-stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A busca paralela continua na velocidade máxima. Mas agora o cliente vê cada faixa aparecer com album art conforme resolve, com uma barra de progresso ao vivo — em vez de um spinner em branco por 10 segundos.&lt;/p&gt;

&lt;h2&gt;
  
  
  O Que Aprendi
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sobre prompt engineering:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Menos é mais (50 &amp;gt; 70)&lt;/li&gt;
&lt;li&gt;Exemplos one-shot (par de mensagens usuário + assistente) são mais confiáveis que instruções de formato detalhadas.&lt;/li&gt;
&lt;li&gt;Dizer o que não fazer funciona muito bem&lt;/li&gt;
&lt;li&gt;Contexto de perfil funciona melhor como guia de DNA musical, não como lista de restrições.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sobre a API do Spotify:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sempre verifique se o endpoint ainda é atual. &lt;code&gt;/tracks&lt;/code&gt; → &lt;code&gt;/items&lt;/code&gt;, audio features sumiu, recomendações sumiram.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /artists/{id}/related-artists&lt;/code&gt; funciona bem pra descoberta e quase ninguém usa.&lt;/li&gt;
&lt;li&gt;O score de popularidade nas faixas é um bom proxy pra "essa faixa existe como esperado."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sobre streaming no Next.js:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ReadableStream&lt;/code&gt; + &lt;code&gt;text/event-stream&lt;/code&gt; funciona limpo no App Router.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Promise.all&lt;/code&gt; + emit-on-resolve te dá paralelismo real com UI progressiva de graça.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sobre Next.js + OAuth:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NextRequest&lt;/code&gt; normaliza URLs pra &lt;code&gt;localhost&lt;/code&gt; em desenvolvimento, mesmo que você passe &lt;code&gt;127.0.0.1&lt;/code&gt; explicitamente. Use &lt;code&gt;Request&lt;/code&gt; nativo quando a URL importa.&lt;/li&gt;
&lt;li&gt;O NextAuth v5 tem um utilitário &lt;code&gt;reqWithEnvURL&lt;/code&gt; que tenta corrigir isso mas usa &lt;code&gt;new NextRequest()&lt;/code&gt; internamente — que normaliza de novo. A correção do framework não funciona.&lt;/li&gt;
&lt;li&gt;O OAuth tem &lt;strong&gt;dois&lt;/strong&gt; momentos onde o &lt;code&gt;redirect_uri&lt;/code&gt; é verificado: na requisição de autorização e na troca de token. Você precisa garantir que os dois mostrem &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;provider.callbackUrl&lt;/code&gt; é derivado da URL da requisição de callback — não da sua config. Se a URL da requisição ainda diz &lt;code&gt;localhost&lt;/code&gt;, a troca falha com &lt;code&gt;invalid_grant&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ao usar &lt;code&gt;Auth()&lt;/code&gt; do &lt;code&gt;@auth/core&lt;/code&gt; diretamente, pin a versão exata que o &lt;code&gt;next-auth&lt;/code&gt; requer. Versões diferentes causam conflitos de tipo em runtime.&lt;/li&gt;
&lt;li&gt;Cookies PKCE são scopados por domínio. Se o usuário começa em &lt;code&gt;localhost&lt;/code&gt; e o callback chega em &lt;code&gt;127.0.0.1&lt;/code&gt;, o &lt;code&gt;code_verifier&lt;/code&gt; some. Redirecione todo o tráfego de &lt;code&gt;localhost&lt;/code&gt; pra &lt;code&gt;127.0.0.1&lt;/code&gt; no &lt;code&gt;next.config.mjs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;NextAuth v5 é poderoso mas a documentação beta é bem escassa. Ler o código-fonte do &lt;code&gt;@auth/core&lt;/code&gt; foi necessário pra entender o que estava acontecendo.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Teste Você Mesmo
&lt;/h2&gt;

&lt;p&gt;O app se chama &lt;strong&gt;Moodify&lt;/strong&gt;. Está OpenSource no &lt;a href="https://github.com/LeoGarcez/moodify" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Se você tá fazendo algo parecido, espero que ajude um pouco.&lt;/p&gt;




&lt;p&gt;Como vocês melhorariam esse prompt? Se alguém já passou por algo parecido ou tiver ideias, comenta aí&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Construído com Next.js, Claude API, Spotify Web API e Supabase. Deploy no Vercel.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>nextjs</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Engenharia de Prompt: Por Que a Forma Como Você Pergunta Muda Tudo(Um guia introdutório)</title>
      <dc:creator>Fran Borges</dc:creator>
      <pubDate>Mon, 23 Mar 2026 16:20:34 +0000</pubDate>
      <link>https://dev.to/he4rt/engenharia-de-prompt-por-que-a-forma-como-voce-pergunta-muda-tudoum-guia-introdutorio-3hb0</link>
      <guid>https://dev.to/he4rt/engenharia-de-prompt-por-que-a-forma-como-voce-pergunta-muda-tudoum-guia-introdutorio-3hb0</guid>
      <description>&lt;p&gt;Neste artigo irei explicar alguns pontos importantes sobre Engenharia de prompt, e como saber esses pontos pode te ajudar muito no dia a dia lidando com IAs, no seus estudos, pesquisas, trabalho, vibeconding, whatever.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefácio&lt;/li&gt;
&lt;li&gt;Antes de tudo, para lembrar, o que é uma LLM mesmo?&lt;/li&gt;
&lt;li&gt;
Fazendo Perguntas

&lt;ul&gt;
&lt;li&gt;1 - Evite a Ambiguidade&lt;/li&gt;
&lt;li&gt;2 - Delimite o Escopo&lt;/li&gt;
&lt;li&gt;3 - Forneça Contexto Relevante&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Considerações Finais&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Prefácio
&lt;/h1&gt;

&lt;p&gt;A base do conhecimento é necessária em tudo que se quer aprender, com LLMs não é diferente. Analisando padrões, conversando com amigos, observei que a grande parte das pessoas que usam LLMs no dia a dia de trabalho, estudo, pessoas que trabalham com tecnologia e principalmente quem está começando na área tech, não tem ideia de como a LLM funciona e de como fazer as perguntas e tirar dúvidas de forma certa para um ChatGPT da vida.&lt;/p&gt;

&lt;p&gt;Tendo isso em vista, e como venho estudando bastante sobre esse assunto, como forma de compartilhar o conhecimento, já dizia a poetisa brasileira Cora Coralina: &lt;em&gt;"Feliz aquele que transfere o que sabe, e aprende o que ensina"&lt;/em&gt;, farei uma série de artigos explicando sobre o tema, e esse é só o primeiro deles...&lt;/p&gt;

&lt;h1&gt;
  
  
  Antes de tudo, para lembrar, o que é uma LLM mesmo?
&lt;/h1&gt;

&lt;p&gt;A LLM pode ser definida de algumas formas. Uma delas é: &lt;em&gt;"São modelos de linguagem de máquina, que usam algoritmos de aprendizado profundo (Deep Learning) para processar e aprender a linguagem natural"&lt;/em&gt;, essa é a sua definição estrutural. A definição que mais gosto é: &lt;em&gt;"A LLM, na sua essência, é composta por dois arquivos: um contendo os pesos (parâmetros) com os conhecimentos aprendidos, e o outro com o código necessário para rodar os dados aprendidos."&lt;/em&gt;, como uma pessoa visual, consigo imaginar melhor como funciona.&lt;/p&gt;

&lt;p&gt;Então, ChatGPT, Claude e muitos outros são exatamente isso, e atualmente têm a capacidade de executar várias atividades, como escrever código, traduzir texto, responder às mais variadas dúvidas e, dependendo da sua capacidade de escrever prompts mais robustos, pode até criar arquiteturas de produtos, te ajudar a resolver bugs complexos, te dar ideias, e até criar um SaaS revolucionário &lt;em&gt;(risos)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;O ponto é que a capacidade das LLMs de "compreender" e "criar" chegou a um ponto bem avançado, e você só chega na camada -17 de boas respostas fazendo as perguntas de forma certa!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; &lt;em&gt;Camada -17&lt;/em&gt; se refere à camada onde se acha o minério de ouro no jogo Minecraft ;). &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Fazendo Perguntas
&lt;/h1&gt;

&lt;p&gt;Como fazer as perguntas certas? E por que saber isso importa? Vamos lá.&lt;/p&gt;

&lt;p&gt;Saber fazer as perguntas certas para uma LLM é tão importante que existe uma área da tecnologia específica só para isso: a &lt;strong&gt;Engenharia de Prompts&lt;/strong&gt;, definida como &lt;em&gt;"a ciência empírica de planejar, criar e testar prompts para gerar melhores respostas em LLMs"&lt;/em&gt;. Saber fazer as perguntas certas te coloca em outro nível, você consegue obter as melhores respostas, e isso te traz muitos ganhos, sendo o principal deles a &lt;strong&gt;produtividade&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mas afinal, como perguntar de forma certa?
&lt;/h2&gt;

&lt;p&gt;A LLM é poderosa, mas não adivinha sua intenção. Para obter os melhores resultados nas suas buscas, você precisa se concentrar na criação de prompts claros, na especificidade e ser rico em dar contexto. E tudo isso envolve:&lt;/p&gt;

&lt;h3&gt;
  
  
  1 - Evite a Ambiguidade
&lt;/h3&gt;

&lt;p&gt;"Espaço de possibilidades"&lt;/p&gt;

&lt;p&gt;Quando você escreve um prompt muito ambíguo, vago, o modelo enxerga vários caminhos estatisticamente válidos. É como se ele estivesse numa encruzilhada com 50 estradas e todas tivessem placas dizendo &lt;em&gt;"Talvez por aqui"&lt;/em&gt;, ele vai escolher uma, mas não necessariamente a que você precisa: a estrada com o percurso mais rápido e sem trânsito (a resposta correta de fato).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exemplos:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt ambíguo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Crie uma API."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que a LLM "vê": Vários caminhos, API REST? GraphQL? Em qual linguagem? Para qual domínio? Com autenticação? Com banco? O modelo vai escolher o caminho mais estatisticamente comum nos dados de treino (provavelmente uma API REST genérica em Node.js com Express), que pode não ter nada a ver com o que você precisa.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Prompt sem ambiguidade:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Crie um microsserviço em Node.js com Express e TypeScript para processar pagamentos via
Stripe. Endpoints: criar pagamento, confirmar webhook e consultar status. O payload tem:
orderId(UUID), amount(number), currency(enum: BRL, USD) e customerId(string).
Use zod para validação, Prisma com PostgreSQL para persistir as transações e winston
para logs. Retorne status codes apropriados como(201, 200, 400, 422, 500). Trate falhas de
rede com retry automático (máx. 3 tentativas)."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que a LLM "vê": Um caminho quase único. Cada detalhe funciona como uma restrição que elimina ambiguidade: "Node.js com Express e TypeScript" define runtime, framework e linguagem de uma vez. "Pagamentos via Stripe" restringe o SDK e o domínio. "3 endpoints explícitos + payload com tipos" elimina adivinhações sobre rotas e schema. "Zod, Prisma, PostgreSQL, Winston" travam a stack, o modelo não vai sugerir alternativas. "Status codes específicos + retry com máximo de 3 tentativas" definem os status HTTP e a estratégia com limites claros. A distribuição de probabilidade fica concentrada e o modelo praticamente "só tem uma opção" a cada token gerado. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2 - Delimite o Escopo
&lt;/h3&gt;

&lt;p&gt;"Janela de atenção"&lt;/p&gt;

&lt;p&gt;LLMs têm um &lt;em&gt;context window&lt;/em&gt;(Janela), uma quantidade de tokens (pedaço de palavra) que conseguem ser processados de uma vez. Isso inclui o seu prompt e a resposta gerada. Dentro dessa janela, existe um fenômeno importante: nem todos os tokens recebem a mesma "atenção" no processamento.&lt;/p&gt;

&lt;p&gt;O mecanismo de &lt;em&gt;self-attention&lt;/em&gt; (o coração da arquitetura Transformer, não é um cubo rsrsrs, e a arquitetura de rede neural, que seria o framework da LLM se a mesma fosse uma linguagem de programação), ela define a estrutura de tudo, calcula relações entre todos os tokens do prompt. Quanto mais tokens irrelevantes existem, mais o modelo precisa "dividir atenção" entre informações úteis e inúteis. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exemplos:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sem escopo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Me ensine Docker."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que acontece internamente: O modelo precisa decidir entre centenas de subtópicos, instalação, conceitos básicos, Dockerfile, docker-compose, volumes,  orquestração, a atenção se fragmenta e o resultado é um overview superficial de tudo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Com escopo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;"Explique o conceito de multi-stage build no Docker para um dev backend pleno que já usa
Docker no dia a dia mas nunca otimizou o tamanho das imagens. Mostre um exemplo prático
com uma aplicação JavaScript, comparando o Dockerfile sem e com multi-stage build,
incluindo o tamanho final de cada imagem."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que acontece internamente: O mecanismo de atenção se concentra em uma região muito específica, a interseção entre "Docker", "multi-stage build", "otimização de imagem" e "JavaScript". Os pesos de atenção ficam fortemente direcionados.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3 - Forneça Contexto Relevante
&lt;/h3&gt;

&lt;p&gt;"Estado da aplicação"&lt;/p&gt;

&lt;p&gt;Uma LLM é &lt;em&gt;stateless&lt;/em&gt; por natureza, ela não tem memória entre requisições. Cada prompt é processado do zero, o único "estado" que ela tem é o que você coloca no prompt. Isso significa que todo contexto que você não fornece simplesmente não existe para o modelo.&lt;/p&gt;

&lt;p&gt;Internamente, o contexto funciona como um sistema de pesos no mecanismo de atenção. Quando você adiciona informações, elas criam "âncoras" que influenciam a distribuição de probabilidades de todos os tokens subsequentes, é como se cada pedaço de contexto fosse um ímã que puxa a resposta para uma direção específica.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exemplos:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sem contexto:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Revise meu código."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que a LLM faz: Sem saber a linguagem, o framework, o nível do dev, o objetivo do código, o padrão do time ou o tipo de revisão esperada, ela vai fazer comentários genéricos: "adicione tratamento de erro", "use nomes mais descritivos", "considere adicionar testes".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Com contexto:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Revise este endpoint Node.js com Express que lida com upload de arquivos para o S3.
O time usa ESLint + Prettier, então ignore estilo. O padrão do time é async/await com
try/catch e erros customizados. Endpoint em produção, recebe 200 uploads/min.
Foque em: memory leaks, tratamento de erros e uso correto do SDK do S3."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O que a LLM faz: Cada informação do prompt funciona como um filtro que elimina ruído e concentra a revisão: "Node.js com Express + upload para S3" ativa conhecimento específico sobre streams, buffers, multipart e AWS SDK. "ESLint + Prettier, ignore estilo" elimina os comentários possíveis que o linter já resolve. "async/await com erros customizados" faz o modelo pular sugestões que o time já aplica e focar em como estão sendo usadas. "Produção, 200 uploads/min" muda o peso de cada problema, um buffer não liberado que seria aceitável em dev vira um incidente crítico sob carga. "Foque em: memory leaks, erros, SDK S3" restringe a revisão a 3 eixos e ignora dezenas de outros tópicos. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Com base nisso, saber as limitações do modelo que você está usando, te ajuda também a entender até aonde você pode ir nas perguntas e inferências, então escolha a sua melhor IA, treine ela, faça as suas perguntas, teste, tente! ;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;É Lembre-se: a LLM não "pensa", ela calcula probabilidades!&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Considerações Finais
&lt;/h1&gt;

&lt;p&gt;É isto, tentei resumir cada ponto que achei interessante abordar e explicar nessa primeira etapa, mas há muito mais sobre engenharia de prompt de LLM para falar, como técnicas mais clássicas de engenharia de prompts(Zero-shot prompting, Role prompting), técnicas mais avançadas(Chain-of-Thought (CoT), Prompt Chaining), são muitas camadas que tentarei destrinchar nos próximos artigos, esse é apenas o primeiro que traz a minha volta para a escrita de artigos após alguns bons anos. Espero que você caro leitor tenha entendido e aprendido algo. Obrigado por ler até aqui. ;)&lt;/p&gt;

&lt;p&gt;Onde me encontrar:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/franciele-borges/" rel="noopener noreferrer"&gt;Meu LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/franSborges/" rel="noopener noreferrer"&gt;Meu GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Do commit ao deploy: CI/CD de uma API na AWS usando GitHub Actions, ECS e Terraform</title>
      <dc:creator>Fernando Andrade</dc:creator>
      <pubDate>Thu, 12 Mar 2026 00:07:27 +0000</pubDate>
      <link>https://dev.to/he4rt/do-commit-ao-deploy-cicd-de-uma-api-na-aws-usando-github-actions-ecs-e-terraform-433g</link>
      <guid>https://dev.to/he4rt/do-commit-ao-deploy-cicd-de-uma-api-na-aws-usando-github-actions-ecs-e-terraform-433g</guid>
      <description>&lt;h2&gt;
  
  
  Sumário
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Introdução&lt;/li&gt;
&lt;li&gt;Pré-requisitos&lt;/li&gt;
&lt;li&gt;Visão Geral da Arquitetura&lt;/li&gt;
&lt;li&gt;Configurando o IAM para o Terraform&lt;/li&gt;
&lt;li&gt;
Infraestrutura como Código com Terraform

&lt;ul&gt;
&lt;li&gt;Recursos Provisionados&lt;/li&gt;
&lt;li&gt;IAM Role para Tarefas ECS&lt;/li&gt;
&lt;li&gt;Task Definition (Fargate)&lt;/li&gt;
&lt;li&gt;ECS Service&lt;/li&gt;
&lt;li&gt;OIDC: Autenticação Sem Credenciais Estáticas&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Dockerfile Multi-Stage&lt;/li&gt;

&lt;li&gt;Pipeline de CI&lt;/li&gt;

&lt;li&gt;Protegendo a Branch Main&lt;/li&gt;

&lt;li&gt;Configurando as Secrets no GitHub&lt;/li&gt;

&lt;li&gt;Pipeline de CD&lt;/li&gt;

&lt;li&gt;Acessando a Aplicação após o Deploy&lt;/li&gt;

&lt;li&gt;Segurança: OIDC em Detalhe&lt;/li&gt;

&lt;li&gt;Fluxo Completo: Do Commit ao Deploy&lt;/li&gt;

&lt;li&gt;Considerações Finais&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Colocar uma aplicação em produção vai muito além de escrever código. Envolve compilar, testar, empacotar e entregar de forma confiável e repetível. Neste artigo, vou mostrar como construir uma pipeline completa — do commit ao deploy — usando &lt;strong&gt;GitHub Actions&lt;/strong&gt; para CI/CD, &lt;strong&gt;Terraform&lt;/strong&gt; para infraestrutura como código e &lt;strong&gt;AWS&lt;/strong&gt; (ECR, ECS Fargate) como plataforma de execução.&lt;/p&gt;

&lt;p&gt;O conceito apresentado aqui é &lt;strong&gt;agnóstico de linguagem&lt;/strong&gt; — funciona para qualquer stack que rode em um container Docker (Node.js, Go, Java, Python, etc.). Para os exemplos práticos, vamos utilizar &lt;strong&gt;.NET&lt;/strong&gt; como referência, mas os workflows, a infraestrutura Terraform e o fluxo de deploy são os mesmos independente da tecnologia escolhida.&lt;/p&gt;

&lt;p&gt;O objetivo é demonstrar como essas ferramentas se conectam para formar um fluxo automatizado onde um simples merge na branch &lt;code&gt;main&lt;/code&gt; resulta em uma nova versão rodando em produção, sem intervenção manual.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pré-requisitos
&lt;/h2&gt;

&lt;p&gt;Antes de começar, você precisa ter as seguintes ferramentas instaladas e configuradas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ferramenta&lt;/th&gt;
&lt;th&gt;Descrição&lt;/th&gt;
&lt;th&gt;Link de instalação&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para construir e executar containers&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;docs.docker.com/get-docker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para provisionar infraestrutura como código&lt;/td&gt;
&lt;td&gt;&lt;a href="https://developer.hashicorp.com/terraform/install" rel="noopener noreferrer"&gt;developer.hashicorp.com/terraform/install&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS CLI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para interagir com os serviços da AWS via terminal&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html" rel="noopener noreferrer"&gt;docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Git&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para versionamento de código&lt;/td&gt;
&lt;td&gt;&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;git-scm.com/downloads&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conta AWS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Com permissões para criar recursos (IAM, ECS, ECR)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;aws.amazon.com/free&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conta GitHub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Para hospedar o repositório e rodar os workflows&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;github.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Para o exemplo deste artigo, também é necessário o &lt;a href="https://dotnet.microsoft.com/download" rel="noopener noreferrer"&gt;.NET SDK&lt;/a&gt; instalado localmente para desenvolvimento. Se você estiver usando outra stack, substitua pelo SDK correspondente (Node.js, Go, JDK, etc.). Outro ponto é que a escolha em utilizar o ECS ao invés de um EKS ou EC2 é devido sua simplicidade na curva de aprendizado, baixo gerenciamento e que para fins de aprendizado os recursos mínimos definidos para esse laboratório não gerem altos gastos para o aprendizado.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Visão Geral da Arquitetura
&lt;/h2&gt;

&lt;p&gt;O fluxo completo funciona assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer → Feature Branch → Pull Request → Validação (CI)
                                                  ↓
                                            Merge na main
                                                  ↓
                                         Build &amp;amp; Push (CD)
                                                  ↓
                                        Deploy no ECS Fargate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Configurando o IAM para o Terraform
&lt;/h2&gt;

&lt;p&gt;Antes de rodar qualquer &lt;code&gt;terraform apply&lt;/code&gt;, é necessário que o Terraform tenha permissões para criar recursos na AWS. Para isso, precisamos de um &lt;strong&gt;usuário IAM&lt;/strong&gt; (ou role) com as permissões adequadas e configurar suas credenciais localmente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Criando um usuário IAM para o Terraform
&lt;/h3&gt;

&lt;p&gt;No console da AWS (IAM &amp;gt; Users), crie um usuário dedicado para o Terraform:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Acesse &lt;strong&gt;IAM &amp;gt; Users &amp;gt; Create User&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Nomeie o usuário (ex: &lt;code&gt;terraform-deployer&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Anexe as policies necessárias para os recursos que serão criados:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AmazonECS_FullAccess
AmazonEC2ContainerRegistryFullAccess
AmazonVPCReadOnlyAccess
IAMFullAccess
CloudWatchLogsFullAccess
AmazonS3FullAccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota de segurança:&lt;/strong&gt; Em um ambiente produtivo, o ideal é criar uma &lt;strong&gt;policy customizada&lt;/strong&gt; com o princípio do menor privilégio, concedendo apenas as permissões estritamente necessárias. Para fins de estudo, as managed policies acima simplificam o setup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Após criar o usuário, gere um &lt;strong&gt;Access Key&lt;/strong&gt; (IAM &amp;gt; Users &amp;gt; Security credentials &amp;gt; Create access key)&lt;/li&gt;
&lt;li&gt;Selecione o caso de uso &lt;strong&gt;Command Line Interface (CLI)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Configurando as credenciais localmente
&lt;/h3&gt;

&lt;p&gt;Com o AWS CLI instalado, configure as credenciais:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Será solicitado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Access Key ID: AKIA...
AWS Secret Access Key: wJal...
Default region name: us-east-1
Default output format: json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso cria o arquivo &lt;code&gt;~/.aws/credentials&lt;/code&gt; que o Terraform utilizará automaticamente via o provider AWS. Com isso feito, o Terraform tem autorização para provisionar os recursos que definiremos a seguir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Infraestrutura como Código com Terraform
&lt;/h2&gt;

&lt;p&gt;Antes de qualquer pipeline rodar, a infraestrutura precisa existir. Com o Terraform, declaramos todos os recursos AWS em arquivos &lt;code&gt;.tf&lt;/code&gt; e provisionamos com um único comando.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recursos Provisionados
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Provider AWS&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Repositório ECR para armazenar imagens Docker&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecr_repository"&lt;/span&gt; &lt;span class="s2"&gt;"app_repository"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"minha-app-repository"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Cluster ECS&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"app_cluster"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"minha-app-cluster"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IAM Role para Tarefas ECS
&lt;/h3&gt;

&lt;p&gt;O ECS precisa de uma role para puxar imagens e enviar logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"ecs_task_execution_role"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-task-execution-role"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs-tasks.amazonaws.com"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"ecs_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Task Definition (Fargate)
&lt;/h3&gt;

&lt;p&gt;Aqui definimos como o container será executado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"app_task"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;family&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"minha-app-task"&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;cpu&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"256"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"512"&lt;/span&gt;
  &lt;span class="nx"&gt;execution_role_arn&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;

  &lt;span class="nx"&gt;container_definitions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt;
    &lt;span class="nx"&gt;image&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${aws_ecr_repository.app_repository.repository_url}:latest"&lt;/span&gt;
    &lt;span class="nx"&gt;portMappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;containerPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
      &lt;span class="nx"&gt;hostPort&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="nx"&gt;logConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logDriver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awslogs"&lt;/span&gt;
      &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-group"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/ecs/minha-app"&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-region"&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-stream-prefix"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ecs"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ECS Service
&lt;/h3&gt;

&lt;p&gt;O service mantém o container rodando e gerencia o deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_service"&lt;/span&gt; &lt;span class="s2"&gt;"app_service"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"minha-app-service"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;task_definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_task_definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;desired_count&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;launch_type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;

  &lt;span class="nx"&gt;network_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subnets&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_subnets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;assign_public_ip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OIDC: Autenticação Sem Credenciais Estáticas
&lt;/h3&gt;

&lt;p&gt;Este é um dos pontos mais importantes da arquitetura. Em vez de armazenar &lt;code&gt;AWS_ACCESS_KEY&lt;/code&gt; e &lt;code&gt;AWS_SECRET_KEY&lt;/code&gt; como secrets no GitHub, usamos &lt;strong&gt;OIDC (OpenID Connect)&lt;/strong&gt; para que o GitHub Actions troque um token temporário por credenciais AWS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Registra o GitHub como provedor OIDC na AWS&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_openid_connect_provider"&lt;/span&gt; &lt;span class="s2"&gt;"github"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://token.actions.githubusercontent.com"&lt;/span&gt;
  &lt;span class="nx"&gt;client_id_list&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;thumbprint_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ffffffffffffffffffffffffffffffffffffffff"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Role que o GitHub Actions vai assumir&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role"&lt;/span&gt; &lt;span class="s2"&gt;"github_actions_role"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github-actions-role"&lt;/span&gt;

  &lt;span class="nx"&gt;assume_role_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Principal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Federated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_openid_connect_provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;
      &lt;span class="nx"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;StringEquals&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;StringLike&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"repo:meu-usuario/meu-repo:ref:refs/heads/main"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Por que isso importa?&lt;/strong&gt; Credenciais estáticas são um risco de segurança. Com OIDC, as credenciais são temporárias e o acesso é restrito a uma branch específica de um repositório específico. Mesmo que alguém tenha acesso ao repositório, não consegue assumir a role a partir de outra branch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dockerfile Multi-Stage
&lt;/h2&gt;

&lt;p&gt;Usamos um build multi-stage para separar o ambiente de compilação do ambiente de execução, resultando em uma imagem final menor e mais segura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: Build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:10.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ["MeuProjeto/MeuProjeto.csproj", "MeuProjeto/"]&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore &lt;span class="s2"&gt;"MeuProjeto/MeuProjeto.csproj"&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; "/src/MeuProjeto"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app/build

&lt;span class="c"&gt;# Stage 2: Publish&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app/publish /p:UseAppHost&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;# Stage 3: Runtime&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/aspnet:10.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=publish /app/publish .&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "MeuProjeto.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefícios do multi-stage:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A imagem final contém apenas o runtime, não o SDK completo&lt;/li&gt;
&lt;li&gt;Reduz significativamente o tamanho da imagem&lt;/li&gt;
&lt;li&gt;O código-fonte não fica presente na imagem de produção&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Pipeline de CI
&lt;/h2&gt;

&lt;p&gt;A primeira pipeline roda automaticamente quando um Pull Request é aberto contra a branch &lt;code&gt;main&lt;/code&gt;. Seu objetivo é validar que o código compila e que os testes passam.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PR Validation&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup .NET&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.0.x'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet restore MinhaSolution.sln&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet build MinhaSolution.sln --no-restore -c Release&lt;/span&gt;

  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup .NET&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.0.x'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Tests with Coverage&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;dotnet test MinhaSolution.sln \&lt;/span&gt;
            &lt;span class="s"&gt;--collect:"XPlat Code Coverage" \&lt;/span&gt;
            &lt;span class="s"&gt;--results-directory ./coverage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  O que acontece nesta pipeline:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Job &lt;code&gt;build&lt;/code&gt;&lt;/strong&gt; — Compila a solução para garantir que não há erros de compilação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job &lt;code&gt;test&lt;/code&gt;&lt;/strong&gt; — Roda os testes unitários com cobertura de código usando Coverlet&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Separar em dois jobs traz clareza: se o build falha, você sabe que é erro de compilação. Se o test falha, o código compila mas tem um bug.&lt;/p&gt;




&lt;h2&gt;
  
  
  Protegendo a Branch Main
&lt;/h2&gt;

&lt;p&gt;Com a pipeline de CI configurada, é fundamental garantir que &lt;strong&gt;nenhum código chegue à &lt;code&gt;main&lt;/code&gt; sem passar pela validação&lt;/strong&gt;. Para isso, configuramos uma &lt;strong&gt;branch protection rule&lt;/strong&gt; no GitHub.&lt;/p&gt;

&lt;p&gt;Acesse &lt;strong&gt;Settings &amp;gt; Branches &amp;gt; Add branch protection rule&lt;/strong&gt; no seu repositório e configure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Branch name pattern:&lt;/strong&gt; &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Marque &lt;strong&gt;Require a pull request before merging&lt;/strong&gt; — impede push direto na main&lt;/li&gt;
&lt;li&gt;Marque &lt;strong&gt;Require status checks to pass before merging&lt;/strong&gt; — bloqueia o merge até que os checks passem&lt;/li&gt;
&lt;li&gt;Em &lt;strong&gt;Status checks that are required&lt;/strong&gt;, busque e adicione os jobs da pipeline de CI:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Na primeira vez que configurar, os status checks podem não aparecer na busca. Eles só ficam disponíveis após a pipeline rodar pelo menos uma vez no repositório. Abra um PR de teste para que os checks sejam registrados.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Configurando as Secrets no GitHub
&lt;/h2&gt;

&lt;p&gt;Antes de configurar a pipeline de deploy, é necessário cadastrar as &lt;strong&gt;secrets&lt;/strong&gt; no repositório do GitHub. A pipeline de CD depende delas para autenticar na AWS e saber para onde enviar a imagem Docker.&lt;/p&gt;

&lt;p&gt;Acesse &lt;strong&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions &amp;gt; New repository secret&lt;/strong&gt; no seu repositório e crie as seguintes secrets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secret&lt;/th&gt;
&lt;th&gt;Valor&lt;/th&gt;
&lt;th&gt;Exemplo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWS_ROLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O &lt;strong&gt;ARN completo&lt;/strong&gt; da IAM Role criada para o GitHub Actions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;arn:aws:iam::123456789012:role/github-actions-role&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ECR_REPOSITORY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A &lt;strong&gt;URI completa&lt;/strong&gt; do repositório ECR (não apenas o nome)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;123456789012.dkr.ecr.us-east-1.amazonaws.com/minha-app-repository&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AWS_ACCOUNT_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;O ID numérico da sua conta AWS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;123456789012&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Atenção:&lt;/strong&gt; Um erro comum é colocar apenas o nome do repositório ECR (ex: &lt;code&gt;minha-app-repository&lt;/code&gt;) na secret &lt;code&gt;ECR_REPOSITORY&lt;/code&gt;. O valor correto é a &lt;strong&gt;URI completa&lt;/strong&gt; que inclui o domínio do ECR. Você pode obter essa URI no console da AWS em &lt;strong&gt;ECR &amp;gt; Repositories&lt;/strong&gt; ou via CLI:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr describe-repositories &lt;span class="nt"&gt;--repository-names&lt;/span&gt; minha-app-repository &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"repositories[0].repositoryUri"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Da mesma forma, a secret &lt;code&gt;AWS_ROLE&lt;/code&gt; deve conter o &lt;strong&gt;ARN&lt;/strong&gt; (Amazon Resource Name) completo da role, não apenas o nome. Para consultar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam get-role &lt;span class="nt"&gt;--role-name&lt;/span&gt; github-actions-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Role.Arn"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com as secrets configuradas, a pipeline de deploy consegue autenticar via OIDC, fazer push da imagem para o ECR e disparar o deploy no ECS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pipeline de CD
&lt;/h2&gt;

&lt;p&gt;Quando o PR é aprovado e mergeado na &lt;code&gt;main&lt;/code&gt;, a pipeline de deploy entra em ação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Deploy&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#permissões necessárias para autenticação OIDC&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt; 

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-east-1&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials (OIDC)&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ROLE_ARN }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AWS_REGION }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Amazon ECR&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/amazon-ecr-login@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Docker Image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker build -t minha-app .&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tag Image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker tag minha-app:latest ${{ secrets.ECR_REPOSITORY }}:latest&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push to ECR&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker push ${{ secrets.ECR_REPOSITORY }}:latest&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to ECS&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws ecs update-service \&lt;/span&gt;
            &lt;span class="s"&gt;--cluster minha-app-cluster \&lt;/span&gt;
            &lt;span class="s"&gt;--service minha-app-service \&lt;/span&gt;
            &lt;span class="s"&gt;--force-new-deployment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  O que acontece nesta pipeline:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Autenticação OIDC&lt;/strong&gt; — O GitHub troca seu token JWT por credenciais AWS temporárias&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Login no ECR&lt;/strong&gt; — Autentica o Docker no registro da AWS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build e Push&lt;/strong&gt; — Constrói a imagem Docker e envia para o ECR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt; — Dispara um novo deployment no ECS, que puxa a imagem atualizada e substitui o container antigo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O &lt;code&gt;--force-new-deployment&lt;/code&gt; garante que o ECS vai iniciar uma nova task mesmo que a tag da imagem (&lt;code&gt;latest&lt;/code&gt;) não tenha mudado.&lt;/p&gt;




&lt;h2&gt;
  
  
  Acessando a Aplicação após o Deploy
&lt;/h2&gt;

&lt;p&gt;Após a pipeline de CD concluir com sucesso, a aplicação estará rodando no ECS Fargate. Como configuramos &lt;code&gt;assign_public_ip = true&lt;/code&gt; no Terraform, a task recebe um &lt;strong&gt;IP público&lt;/strong&gt; que pode ser usado para acessar a API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Obtendo o IP público pelo Console AWS
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Acesse &lt;strong&gt;ECS &amp;gt; Clusters &amp;gt; minha-app-cluster&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Clique na aba &lt;strong&gt;Tasks&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Clique na task em execução (status &lt;code&gt;RUNNING&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Na seção &lt;strong&gt;Network&lt;/strong&gt;, copie o &lt;strong&gt;Public IP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Acesse no navegador: &lt;code&gt;http://&amp;lt;PUBLIC_IP&amp;gt;:8080&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Obtendo o IP público via CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Obtenha o ARN da task em execução&lt;/span&gt;
&lt;span class="nv"&gt;TASK_ARN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ecs list-tasks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; minha-app-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-name&lt;/span&gt; minha-app-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"taskArns[0]"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 2. Obtenha o ID da interface de rede (ENI)&lt;/span&gt;
&lt;span class="nv"&gt;ENI_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws ecs describe-tasks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; minha-app-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tasks&lt;/span&gt; &lt;span class="nv"&gt;$TASK_ARN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"tasks[0].attachments[0].details[?name=='networkInterfaceId'].value"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 3. Obtenha o IP público&lt;/span&gt;
aws ec2 describe-network-interfaces &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network-interface-ids&lt;/span&gt; &lt;span class="nv"&gt;$ENI_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"NetworkInterfaces[0].Association.PublicIp"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante:&lt;/strong&gt; O IP público muda toda vez que uma nova task é criada (ou seja, a cada deploy). Para um ambiente produtivo, o ideal é utilizar um &lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt; ou um &lt;strong&gt;domínio com Route 53&lt;/strong&gt; apontando para o serviço ECS, garantindo um endereço fixo. Para fins de estudo, o IP público direto é suficiente.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Segurança: OIDC em Detalhe
&lt;/h2&gt;

&lt;p&gt;Vale reforçar a importância do OIDC neste fluxo. O modelo tradicional funciona assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Modelo Tradicional:
   GitHub Secrets → AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
   - Credenciais estáticas que nunca expiram
   - Se vazadas, acesso total até serem rotacionadas manualmente
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com OIDC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Modelo OIDC:
   GitHub Actions → JWT Token → AWS STS → Credenciais Temporárias
   - Credenciais expiram automaticamente
   - Escopo restrito: apenas uma branch de um repositório específico
   - Sem segredos de longa duração armazenados
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A configuração requer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Registrar o GitHub como OIDC Provider na AWS (via Terraform)&lt;/li&gt;
&lt;li&gt;Criar uma IAM Role com trust policy apontando para o repositório&lt;/li&gt;
&lt;li&gt;No workflow, usar &lt;code&gt;permissions: id-token: write&lt;/code&gt; e a action &lt;code&gt;configure-aws-credentials&lt;/code&gt; com &lt;code&gt;role-to-assume&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Fluxo Completo: Do Commit ao Deploy
&lt;/h2&gt;

&lt;p&gt;Resumindo o ciclo de vida de uma mudança:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1. git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature/minha-feature
2. &lt;span class="c"&gt;# Desenvolve e commita&lt;/span&gt;
3. git push origin feature/minha-feature
4. &lt;span class="c"&gt;# Abre Pull Request → Dispara pr-validation.yml&lt;/span&gt;
   │
   ├── ✅ Build compila com sucesso
   └── ✅ Testes passam com cobertura
   │
5. &lt;span class="c"&gt;# Code review + Aprovação&lt;/span&gt;
6. &lt;span class="c"&gt;# Merge na main → Dispara build-and-deploy.yml&lt;/span&gt;
   │
   ├── 🔐 Autenticação via OIDC
   ├── 🐳 Build da imagem Docker &lt;span class="o"&gt;(&lt;/span&gt;multi-stage&lt;span class="o"&gt;)&lt;/span&gt;
   ├── 📦 Push para o ECR
   └── 🚀 Deploy no ECS Fargate
   │
7. &lt;span class="c"&gt;# Nova versão rodando em produção&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Considerações Finais
&lt;/h2&gt;

&lt;p&gt;Este setup demonstra como é possível construir uma pipeline profissional de CI/CD com ferramentas modernas e boas práticas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infraestrutura como Código&lt;/strong&gt; — Toda a infraestrutura é versionada e reproduzível&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autenticação Keyless&lt;/strong&gt; — OIDC elimina o risco de credenciais estáticas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless Containers&lt;/strong&gt; — Fargate remove a necessidade de gerenciar servidores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separação CI/CD&lt;/strong&gt; — Validação em PRs e deploy apenas na main&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Imagens otimizadas&lt;/strong&gt; — Multi-stage build reduz a superfície de ataque&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O custo de infraestrutura para um setup como esse é mínimo — com Fargate usando 0.25 vCPU e 512MB de memória, o custo fica na faixa de poucos dólares por mês para ambientes de estudo e projetos pequenos.&lt;/p&gt;

&lt;p&gt;A barreira de entrada para CI/CD profissional diminuiu muito. Ferramentas como GitHub Actions e Terraform tornam acessível o que antes exigia equipes dedicadas de DevOps. O importante é começar simples, entender cada peça, e evoluir conforme a necessidade.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Este artigo foi escrito com base em um projeto prático de estudo. Todo o código-fonte está disponível publicamente neste repositório do &lt;a href="https://github.com/fernanduandrade/api-quality-lab" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>devops</category>
      <category>aws</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Criei uma pipeline de planejamento no OpenCode e olha no que deu</title>
      <dc:creator>Clinton Rocha</dc:creator>
      <pubDate>Mon, 09 Mar 2026 04:37:00 +0000</pubDate>
      <link>https://dev.to/he4rt/sistema-de-planejamento-estruturado-no-opencode-16pb</link>
      <guid>https://dev.to/he4rt/sistema-de-planejamento-estruturado-no-opencode-16pb</guid>
      <description>&lt;p&gt;Você é como eu e vem buscando formas de otimizar o uso de ferramentas como o &lt;a href="https://opencode.ai/docs/pt-br" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt;? Talvez este conteúdo te ajude a ter novas ideias e até mesmo entender alguns fluxos dessa e de outras ferramentas.&lt;/p&gt;

&lt;p&gt;Enquanto eu estudava a documentação do OpenCode, encontrei a aba de &lt;a href="https://pt.wikipedia.org/wiki/Plug-in" rel="noopener noreferrer"&gt;plugins&lt;/a&gt; feitos pela comunidade. Ali, encontrei três plugins que, na minha visão, se complementam bem. Na verdade, um deles eu já utilizava: o &lt;a href="https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin" rel="noopener noreferrer"&gt;&lt;strong&gt;Plannotator&lt;/strong&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;A partir disso, comecei a estudar como funcionava os comandos, agentes e como eu poderia fazer um fluxo que fizesse sentido e principalmente que funcionasse, calma que já te explico oq cada um dos plugins fazem e qual a ideia final da integração.&lt;/p&gt;

&lt;h1&gt;
  
  
  Índice
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Como eu estava usando o OpenCode&lt;/li&gt;
&lt;li&gt;Objetivo da integração&lt;/li&gt;
&lt;li&gt;Plannotator: Revisão Visual&lt;/li&gt;
&lt;li&gt;Octto: Coleta estruturada de contexto&lt;/li&gt;
&lt;li&gt;Subtask2: Orquestração de Pipeline&lt;/li&gt;
&lt;li&gt;Comandos&lt;/li&gt;
&lt;li&gt;Considerações&lt;/li&gt;
&lt;li&gt;Referências&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Como eu estava usando o OpenCode
&lt;/h1&gt;

&lt;p&gt;Após começar a utilizar o OpenCode, notei o quão importante é o uso do módulo de planejamento e, principalmente, não apenas o planejamento em si, mas também as perguntas que são feitas durante esse processo. O resultado dessas duas novas abordagens (planejamento e perguntas) melhora bastante não só o código produzido, mas também a minha compreensão sobre a regra de negócio, além de comportamentos e fluxos do sistema.&lt;/p&gt;

&lt;p&gt;Mesmo com todas essas descobertas que trouxeram otimizações para o processo, eu ainda sentia que dava para melhorar mais.&lt;/p&gt;

&lt;p&gt;O fluxo de uso do OpenCode era basicamente o seguinte: eu enviava o prompt, a ferramenta vasculhava o código para obter contexto, depois vinham algumas perguntas e, em seguida, ela ia para a parte de &lt;em&gt;build&lt;/em&gt;, ou seja, para colocar em prática o que tinha sido planejado.&lt;/p&gt;

&lt;h1&gt;
  
  
  Objetivo da integração
&lt;/h1&gt;

&lt;p&gt;A ideia vai ser conseguir extrair o máximo de contexto com o &lt;code&gt;Octto&lt;/code&gt;, usar o &lt;code&gt;subtask2&lt;/code&gt; para orquestrar os comandos e visualizar o plano final com &lt;code&gt;Plannotator&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------------------+
|                                                               |
|  IDEIA -&amp;gt; CONTEXTO -&amp;gt; PLANO -&amp;gt; REFINAMENTO -&amp;gt; REVISAO -&amp;gt; OK   |
|                                                               |
+---------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado é um &lt;strong&gt;único comando&lt;/strong&gt; que orquestra todo o processo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/generate-plan &lt;span class="s2"&gt;"descrição do objetivo"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Plannotator: Revisão visual
&lt;/h1&gt;

&lt;p&gt;O primeiro plugin chegou até mim por recomendação de uma amiga da comunidade (&lt;a href="https://github.com/he4rt/" rel="noopener noreferrer"&gt;He4rtDevs&lt;/a&gt;), a &lt;a href="https://dev.to/cherryramatis"&gt;Cherry&lt;/a&gt;, e o plugin era o &lt;a href="https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin" rel="noopener noreferrer"&gt;Plannotator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Esse plugin basicamente faz com que, ao final de um planejamento, seja aberta uma página no navegador contendo todo o plano gerado. Isso facilita muito a visualização em comparação com o terminal. Na interface do navegador, você consegue selecionar trechos do planejamento e dar feedback, que retorna para o processo de planejamento. Também é possível aprovar o plano e seguir diretamente para a etapa de &lt;em&gt;build&lt;/em&gt;, ou seja, aplicar o que foi planejado.&lt;/p&gt;

&lt;h1&gt;
  
  
  Octto: Coleta estruturada de contexto
&lt;/h1&gt;

&lt;p&gt;O &lt;a href="https://github.com/vtemian/octto" rel="noopener noreferrer"&gt;Octto&lt;/a&gt; quando você envia o primeiro prompt, ele abre uma página no navegador com perguntas relacionadas ao que você pediu inicialmente. Essa página funciona como uma interface interativa de brainstorming. Conforme você responde às perguntas, o sistema utiliza essas respostas para gerar novas perguntas dentro da mesma sessão, aprofundando o entendimento do problema e ajudando a estruturar melhor o contexto antes de seguir para as próximas etapas, ao final ele gera um arquivo &lt;code&gt;.md&lt;/code&gt; no diretório &lt;code&gt;/docs/plans&lt;/code&gt; dentro do projeto.&lt;/p&gt;

&lt;p&gt;A unica configuração que adicionei a ele, foi uma que faz com que a primeira pergunta seja um campo de texto livre onde o usuário consiga descrever o contexto com maior liberdade (colocar uma task completa, por exemplo).&lt;/p&gt;

&lt;h2&gt;
  
  
  Fluxo de Funcionamento
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
|       ask_text       |  &amp;lt;-- Primeira ação obrigatória
+----------+-----------+
           |
           | Campo de texto no browser:
           | "Descreva com detalhes o que
           | você quer planejar..."
           v
+----------------------+
|   create_brainstorm  |  &amp;lt;-- Agente inicia sessão
+----------+-----------+      com contexto coletado
           |
           | Define branches de exploração
           v
+--------------------------------------------------------------+
|                    BRANCHES DE EXPLORAÇÃO                    |
|                                                              |
|  +----------------+  +-------------------+  +---------------+|
|  | Motivação &amp;amp;    |  | Requisitos &amp;amp;      |  | Riscos &amp;amp;      ||
|  | Objetivos      |  | Restrições        |  | Dependências  ||
|  +--------+-------+  +---------+---------+  +-------+-------+|
|           |                    |                    |        |
+-----------+--------------------+--------------------+--------+
            |
            v
+----------------------+
|      BROWSER UI      |
|                      |
|  [ Pergunta 1 ]      |
|  [ Pergunta 2 ]      |
|  [ Pergunta N ]      |
|                      |
|      [Responder]     |
+----------+-----------+
           |
           | Usuário responde
           v
+----------------------+
| await_brainstorm_    |
| complete             |  &amp;lt;-- Agente aguarda respostas
+----------+-----------+
           |
           | Sintetiza respostas
           v
+----------------------+
|      docs/plans/     |
| 2026-03-08-objetivo  |  &amp;lt;-- Arquivo gerado
|        .md           |
+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuração via Fragments
&lt;/h2&gt;

&lt;p&gt;O comportamento do Octto é customizado através de &lt;strong&gt;fragments&lt;/strong&gt;, instruções adicionadas ao prompt dos agentes internos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fragments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"octto"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Sempre pergunte a motivação e o 'porquê' das mudanças"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Gere nomes de arquivo no formato: YYYY-MM-DD-slug.md"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Salve os planos em docs/plans/"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"probe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Priorize perguntas sobre motivações e restrições"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Inclua perguntas sobre requisitos não-funcionais"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"bootstrapper"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"Crie branches focados em: requisitos, arquitetura, riscos"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Função de cada agente interno:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;bootstrapper&lt;/strong&gt;: cria a estrutura inicial de branches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;probe&lt;/strong&gt; : gera perguntas de aprofundamento&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;octto&lt;/strong&gt;: define o comportamento geral da coleta de contexto&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Subtask2: Orquestração de pipeline
&lt;/h1&gt;

&lt;p&gt;O &lt;a href="https://github.com/spoons-and-mirrors/subtask2" rel="noopener noreferrer"&gt;Subtask2&lt;/a&gt; permite &lt;strong&gt;encadear comandos&lt;/strong&gt; de forma sequencial. Quando um comando termina, o próximo da cadeia é executado automaticamente. &lt;/p&gt;

&lt;p&gt;Nesse meu contexto só vou usar a funcionalidade de &lt;code&gt;return&lt;/code&gt;, mas esse plugin tem muitas outras funcionalidades interessantes como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;loop&lt;/code&gt; de subtarefas até que a condição do usuário seja atendida&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;paralela&lt;/code&gt; de subtarefas simultaneamente - PR pendente.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$TURN[n]&lt;/code&gt; passa turnos da sessão (mensagens do usuário/assistente).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{as:name}&lt;/code&gt; + &lt;code&gt;$RESULT[name]&lt;/code&gt; captura e referência as saídas das subtarefas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sintaxe de Return Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;subtask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;return&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/comando-1&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/comando-2&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/comando-3&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Comandos
&lt;/h1&gt;

&lt;p&gt;Com o &lt;code&gt;subtask2&lt;/code&gt;, consigo adicionar comandos ao fluxo, e isso abre muitas possibilidades. Mas, por enquanto, estou com os pés no chão e vou fazer um fluxo simples com alguns novos comandos.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Eu criei esses comandos com o único intuito de usar na pipeline.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Comando Principal
&lt;/h2&gt;

&lt;p&gt;O comando que vai dar inicio a todo o fluxo, vai ser o &lt;code&gt;/generate-plan&lt;/code&gt; ao executar, ele da inicio a pipeline de planejamento.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Você é livre para escolher qualquer nome para o comando, para escolher o nome desse aqui eu só segui as vozes da minha cabeça.&lt;/p&gt;

&lt;p&gt;Caso queira saber mais sobre como criar e configurar &lt;a href="https://opencode.ai/docs/pt-br/commands/" rel="noopener noreferrer"&gt;comandos no opencode&lt;/a&gt;, a doc é legalzinha.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/generate-plan refatorar sistema de autenticação
/generate-plan &lt;span class="s2"&gt;"adicionar suporte a múltiplos gateways de pagamento"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As aspas são opcionais: tudo após o comando é passado como &lt;code&gt;$ARGUMENTS&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pipeline completo de planejamento&lt;/span&gt; 
&lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;octto&lt;/span&gt;
&lt;span class="na"&gt;subtask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;return&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/refine-plan&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/review-plan&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

prompt aqui, e lembre de usar o $ARGUMENTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Caso você queira ver como deixei o meu &lt;a href="https://gist.github.com/Clintonrocha98/f9f5cb146a799a3a2a340ef12a0bf9bf#file-prompt-generate-plan-md" rel="noopener noreferrer"&gt;prompt usado no comando generate-plan&lt;/a&gt; é só ir no gist.&lt;/p&gt;

&lt;p&gt;Todos os comandos podem ser usados individualmente/isolado no OpenCode, a unica diferença ao usar um comando com o &lt;code&gt;subtask&lt;/code&gt; é que ele vai ser executado em um contexto diferente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Refinamento Técnico
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;/refine-plan&lt;/code&gt; tem como objetivo expandir um plano existente com detalhes arquiteturais.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/refine-plan &lt;span class="c"&gt;# usa plano mais recente&lt;/span&gt;
/refine-plan docs/plans/meu-plano.md &lt;span class="c"&gt;# plano específico&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;refine-plan&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Expande e refina tecnicamente um plano existente com arquitetura e diagramas&lt;/span&gt;
&lt;span class="na"&gt;subtask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Mesma coisa que o anterior, aqui você consegue adicionar a instrução que você quiser.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;No meu caso, o prompt que estou sando tá no &lt;a href="https://gist.github.com/Clintonrocha98/f9f5cb146a799a3a2a340ef12a0bf9bf#file-refine-plan-md" rel="noopener noreferrer"&gt;gist 'refine-plan.md'&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Revisão Visual
&lt;/h2&gt;

&lt;p&gt;Por último vem o comando &lt;code&gt;/review-plan&lt;/code&gt; com o objetivo de mandar o plano para revisão interativa via Plannotator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/review-plan &lt;span class="c"&gt;# usa plano mais recente&lt;/span&gt;
/review-plan docs/plans/meu-plano.md &lt;span class="c"&gt;# plano específico&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Importante:&lt;/strong&gt; Este comando &lt;strong&gt;não usa &lt;code&gt;subtask: true&lt;/code&gt;&lt;/strong&gt;. Ele roda na sessão primária para que a ferramenta &lt;code&gt;submit_plan&lt;/code&gt; possa abrir a interface do Plannotator no browser corretamente. Comandos rodando como subtarefa não conseguem inicializar a UI do browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fluxo Completo de Execução:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc22oiuapqumis0k3m5ns.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc22oiuapqumis0k3m5ns.png" alt="diagrama contendo todo o fluxo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Considerações
&lt;/h1&gt;

&lt;p&gt;O mais importante de tudo: funcionou? Sim. É uma bazuca? Sim, hahahaha. Esse projeto acabou saindo porque entrei em hiperfoco ao me fazer a pergunta “será que funciona?”. No fim, tive minha resposta.&lt;/p&gt;

&lt;p&gt;Olhando para a usabilidade no dia a dia, acredito que os melhores momentos para utilizar algo assim são em novas &lt;em&gt;features&lt;/em&gt;, refatoração de projeto e até mesmo para estruturar estudos, quem sabe. Só não use para algo como “quero trocar a cor de um botão”, aí você acaba complicando.&lt;/p&gt;

&lt;p&gt;O ponto que mais curti foi o &lt;code&gt;Octto&lt;/code&gt;, com as perguntas geradas de forma dinâmica. Aquilo ali, ao meu ver, auxilia na contextualização de uma baita maneira. Fora que é possível dar instruções para as &lt;em&gt;branches&lt;/em&gt; dele, especificar qual tipo de input você quer ou não quer, entre outras coisas (vale ler a documentação).&lt;/p&gt;

&lt;p&gt;Já o &lt;code&gt;subtask2&lt;/code&gt; abre portas para muita coisa. Como eu tinha dito antes, acabei indo pelo simples, mas alguém com a mente mais aberta provavelmente vai conseguir extrair o melhor da ferramenta.&lt;/p&gt;

&lt;p&gt;Eu pretendo criar novos comandos, refinar mais os &lt;em&gt;prompts&lt;/em&gt; e ver o que acontece. É isso, obrigado por ler até aqui e tente brincar com isso ai.&lt;/p&gt;

&lt;p&gt;Como meu &lt;em&gt;Tech Lead&lt;/em&gt; fala: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Faça codigo inútil e quebre alguma coisa.&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Referências
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://opencode.ai/docs/commands/" rel="noopener noreferrer"&gt;OpenCode - Comandos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opencode.ai/docs/agents/" rel="noopener noreferrer"&gt;OpenCode - Agentes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spoons-and-mirrors/subtask2" rel="noopener noreferrer"&gt;Subtask2 - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin" rel="noopener noreferrer"&gt;Plannotator - Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vtemian/octto" rel="noopener noreferrer"&gt;Octto - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/%40summer12126/what-does-adhoc-mean-in-sw-development-2196f09826d6" rel="noopener noreferrer"&gt;what does ad-hoc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>braziliandevs</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
