Changelog

O que mudou na Metro.

Atualizações de produto, melhorias técnicas e correções importantes.

20 Abr 2026

NovoSegurança16:00

Segurança: Cross-Origin-Opener-Policy (COOP)

Header COOP adicionado para isolar a janela do site de documentos cross-origin (pop-ups), eliminando alerta de gravidade Alta no Lighthouse.

  • Cross-Origin-Opener-Policy: same-origin adicionado no middleware.ts
  • Isola janela top-level de pop-ups e navegações cross-origin
  • Resolve audit Lighthouse "Nenhum cabeçalho COOP encontrado" (gravidade Alta)
NovoSegurança15:00

Segurança: Trusted Types contra DOM XSS

Diretivas Trusted Types adicionadas ao CSP para prevenir ataques de DOM XSS via innerHTML/eval/document.write.

  • trusted-types default nextjs#bundler 'allow-duplicates' no CSP
  • require-trusted-types-for 'script' — browser bloqueia strings não-confiáveis em sinks perigosos
  • Policy default criada em layout.tsx <head> (createHTML, createScriptURL, createScript)
  • Compatível com Next.js 15 runtime e GTM/GA4
CorreçãoSegurança14:00

Fix: CSP img-src e connect-src para API aquitempod

Ícones de categorias e chamadas de API do storefront estavam bloqueados pelo CSP. Adicionado domínio da API nas diretivas img-src e connect-src.

  • img-src: adicionado https://*.aquitempod.com — ícones SVG de categorias carregam novamente
  • connect-src: adicionado https://*.aquitempod.com — analytics/track, integrations/scripts, social-proof, storefront-config desbloqueados
  • Verificado em produção via curl — headers corretos no response
MelhoriaPerformance10:00

Diagnóstico LCP: Render Delay 2.46s identificado

LCP diagnosticado via Lighthouse: TTFB excelente (0ms), mas Element Render Delay de 2.460ms. Causa raiz: H1 localizado a 445KB de 630KB de HTML.

  • LCP element: <h1> "Seu pod perfeito está aqui" (hero-banner.tsx, server component SSR)
  • TTFB: 0ms | Render Delay: 2,460ms — browser precisa parsear 70% do HTML antes do H1
  • 78KB de CSS inline (inlineCss: true) + 46 chunks RSC flight data antes do conteúdo
  • Font Inter com display:optional pode causar texto invisível durante carregamento
  • Próximos passos: Suspense boundaries para streaming, reduzir RSC payload, priorizar hero chunk
MelhoriaPerformance06:00

Performance: Long Tasks Eliminados (-3 fetches redundantes)

Três fetches duplicados eliminados da homepage, CartDrawer condicional, e componentes movidos para dentro do StoreConfigProvider.

  • 3 chamadas fetch redundantes removidas (storefront-config duplicado em layout + page)
  • CartDrawer agora renderiza condicionalmente — lazy load só quando carrinho tem itens
  • ThemeProvider, WhatsAppButton, MobileBottomNav movidos para dentro do StoreConfigProvider (1 fetch compartilhado)
  • SSR theme vars: CSS variables aplicadas inline no <html> via buildThemeStyle() — sem flash de tema
NovoSegurança03:00

Segurança: CSP Strict com Nonce Dinâmico

Content Security Policy avançada implementada via middleware Next.js com nonce por request + strict-dynamic, eliminando risco de XSS.

  • Middleware gera nonce criptográfico (crypto.randomUUID → base64) por request
  • script-src com nonce + strict-dynamic: scripts inline do Next.js autorizados via nonce, scripts dinâmicos (GTM, GA4, MP) via propagação de trust
  • object-src none + base-uri self: bloqueio de plugins e hijacking de base URL
  • connect-src restrito a domínios conhecidos (Google Analytics, MercadoPago, Facebook, TikTok)
  • 64 scripts inline com nonce verificados em produção — Next.js propaga automaticamente
CorreçãoPerformance02:30

Fix: CLS Zero — View Transitions & Lazy Carousel

Eliminado CLS 0.241 no PageSpeed mobile. Removido @view-transition auto do CSS (causava scale animation no carregamento) e corrigido minHeight do LazyShowcaseCarousel.

  • CLS 0.212 no <main>: removido @view-transition { navigation: auto } e view-transition-name do CSS — agora aplicado via JS apenas em navegações SPA
  • CLS 0.029 em Mais Vendidos: LazyShowcaseCarousel agora mantém minHeight: 400 fixo (não condicional)
  • Animações de view-transition simplificadas: removido scale(1.02→1) que causava layout shift, mantido apenas opacity
  • ViewTransitions component refatorado: skip na montagem inicial (isFirstRender), aplica view-transition-name dinamicamente

19 Abr 2026

InfraInfra20:00

Deploy: Otimizações Lighthouse em Produção

Todas as 6 otimizações Lighthouse (performance + acessibilidade) deployadas em produção via SCP + Docker rebuild.

  • 18 arquivos transferidos via SCP para Hetzner (web + api)
  • Docker build web: Next.js compilado em 25s, 28 páginas estáticas, 132 kB First Load JS
  • Docker build api: NestJS compilado em 22.8s, imagem ecos-api:latest exportada
  • Containers ecos-prod-web e ecos-prod-api recriados — health checks OK (postgres, redis, elasticsearch)
  • Site respondendo HTTP 200 em ~256ms após deploy
MelhoriaStorefront18:00

Lighthouse: Touch Targets Acessíveis

Dots de navegação dos carrosséis agora atendem ao mínimo de 48px de área de toque (WCAG).

  • carousel-3d.tsx e image-morph.tsx: dots h-2 w-2 agora dentro de button com p-3 (48px touch area)
  • Visual idêntico — dot pequeno, área de toque grande
MelhoriaStorefront17:00

Lighthouse: Contraste de Cores WCAG AA

Todas as cores de texto agora passam WCAG AA (4.5:1 mínimo). Afeta trust badges, footer, botões e mobile nav.

  • --color-muted: #777→#666 (4.48:1→5.74:1 em fundo branco)
  • --color-accent: #e0853e→#a96220 (2.77:1→4.71:1 texto branco em botões)
  • Footer: SSL/Compra Segura gray-400→gray-300 (6.59:1), copyright gray-500→gray-400 (4.92:1)
  • Hero "está aqui": cor fixa #e0853e mantida (4.52:1 em fundo escuro)
  • Mobile bottom nav: corrigido automaticamente via --color-muted
MelhoriaStorefront16:00

Lighthouse: CLS Zero — Sem Layout Shift

CLS reduzido de 0.219 para ~0. View transition names movidos de JS para CSS e font display otimizado.

  • view-transition-name: main-content e site-header declarados em CSS (antes: JS useEffect causava shift 0.208)
  • Font Inter: display swap→optional (elimina relayout 0.011 no font swap)
  • ViewTransitions component simplificado — só gerencia hero e direction/page data attributes
MelhoriaStorefront14:00

Lighthouse: Forced Reflow Eliminado (229ms→~0)

Forced reflows causados por múltiplos scroll listeners e matchMedia recriado a cada mousemove foram eliminados.

  • DepthBanner: 4 ParallaxLayer com scroll listeners separados → 1 handler batched (1 read + N writes por frame)
  • MagneticButton: window.matchMedia() cacheado em useRef (antes: chamado a cada mousemove)
  • useParallaxScroll: setState→el.style.transform direto (sem re-render React por frame)
  • ScrollProgressBar: useState→useRef + bar.style.width via rAF (zero re-renders)
MelhoriaStorefront12:00

Lighthouse: JavaScript Legado Removido (-12 KiB)

Polyfills desnecessários do Next.js removidos via webpack. Widgets não-críticos carregados sob demanda.

  • NormalModuleReplacementPlugin substitui polyfill-module.js por empty-polyfill.js (Array.prototype.at, Object.hasOwn, etc.)
  • DeferredWidgets: 5 componentes (CursorLight, ScrollProgress, ViewTransitions, ExitIntent, SocialProof) carregados lazy via dynamic(ssr:false)
  • ShowcaseCarousel: IntersectionObserver + dynamic import (placeholder até viewport)
  • browserslist configurado para chrome/edge/firefox ≥92, safari ≥15.4
  • Homepage First Load JS mantido em 132 kB

18 Abr 2026

CorreçãoAPI + Search21:30

Fix Filtro NicSalt por Concentração (mg) + Reindex ES

Filtro de NicSalt 20mg mostrava produtos sem variante 20mg em estoque. Corrigido syncStock para reavaliar categorias de concentração e reindexar ElasticSearch.

  • Root cause: syncStock() não reavaliava categorias mg ao atualizar estoque de variantes
  • Novo método reevaluateConcentrationCategories() — lê variantes locais (ativas + stock > 0), extrai mg via regex
  • syncStock() agora chama reevaluate + emite product.synced para trigger ES reindex
  • Reindex completo do ElasticSearch: 731 produtos indexados, 1636 variações-filho ignoradas
  • Resultado: 20mg=11, 35mg=53, 50mg=77 produtos (dados corretos vs 108 antes no ES)
  • Script _tmp_reindex.js criado para reindex manual (PrismaPg adapter + ES client)
NovoStorefront + API17:00

Social Proof Widget (Prova Social)

Widget flutuante de prova social mostrando compras recentes reais e simuladas.

  • Backend: social-proof.service.ts com Redis cache, fake entries baseadas em best-sellers
  • Frontend: toast flutuante com ciclo automático (25s delay, 8s interval)
  • Anonimização de nomes, timestamps frescos (3-40 min atrás)
  • Prioriza produtos mais vendidos, Fisher-Yates shuffle, sem duplicata consecutiva
NovoAdmin + API16:00

Upload Manual de Imagens de Produto

Agora é possível enviar imagens manualmente para produtos pelo admin, além da geração por IA.

  • Novo endpoint POST /ai/images/upload — aceita arquivo via multipart/form-data
  • Suporta JPG, PNG, WebP e GIF com validação de MIME type
  • Salva no storage local em /storage/{tenantId}/products/{hash}{ext}
  • Admin: botão "📤 Enviar imagens" + zona de drag & drop na aba Imagens
  • Aceita múltiplos arquivos de uma vez
  • Fix: invalidação de cache Redis após upload/delete/set-main (produto aparece imediatamente)
CorreçãoAPI14:00

Fix Filtro de Disponibilidade (inStock)

Filtro inStock=false retornava os mesmos resultados que inStock=true. Corrigido no DTO de listagem de produtos.

  • Bug: @Type(() => Boolean) convertia "false" → Boolean("false") → true via enableImplicitConversion
  • Fix: substituído por @Transform com obj[key] para acessar valor raw da query string
  • Afeta tanto admin (/produtos) quanto storefront (/storefront/products)
  • Validado: inStock=true→97, inStock=false→247, sem filtro→344 (NicSalt)
  • Mesmo fix aplicado ao campo isFeatured
CorreçãoAdmin12:00

Fix Busca de Itens do Kit

Busca de produtos no Kit Builder do admin não retornava resultados.

  • API retornava { items: [...] } mas componente lia res.data
  • Corrigido kit-items-editor.tsx: res.data → res.items
MelhoriaStorefront10:00

Avise-me com Suporte a Variantes

Formulário "Avise-me quando voltar" agora suporta variantes individuais do produto.

  • StockAlertForm aceita variantId e variantName opcionais
  • Na PDP, variante sem estoque mostra formulário específico para aquela variante
  • API /stock-alerts/subscribe aceita variantId no body
  • Schema stock_alerts: nova coluna variantId (FK opcional para product_variants)
NovoStorefront08:00

Barra Frete Grátis e Botão no Cart Drawer

Cart drawer agora mostra barra de progresso de frete grátis e botão para continuar comprando.

  • FreeShippingBar integrada dentro do cart-drawer com progresso visual
  • Botão "Adicionar mais itens" fecha o drawer e navega para a home
  • Threshold dinâmico via storeConfig (freeShippingThreshold)
NovoAI + Blog06:00

AI Blog: Sugestão de Tópicos e Geração de Posts

IA sugere tópicos de blog baseados nos produtos da loja e gera posts completos com SEO.

  • POST /ai/blog/suggest-topics — sugere 8 tópicos com título, slug, keywords e search intent
  • POST /ai/blog/generate-post — gera post completo (título, excerpt, conteúdo HTML, tags, meta SEO)
  • Usa Gemini 2.5 Flash com contexto dos produtos/categorias da loja
  • Integrado no admin /blog com botão de geração
NovoAI + Produtos05:00

AI Bundling: Comprados Juntos e Sugestão de Kits

Mineração de co-compra e sugestões inteligentes de bundles baseadas em dados reais de pedidos.

  • GET /ai/bundling/co-purchases — pares de produtos comprados juntos (confidence + lift)
  • GET /ai/bundling/category-affinities — afinidade entre categorias
  • POST /ai/bundling/suggest — sugestões de bundles com IA (nome, desconto, evidência)
  • GET /ai/bundling/frequently-bought-together/:productId — storefront público
  • Componente FrequentlyBoughtTogether na PDP — "Frequentemente comprados juntos" com add-all-to-cart
NovoStorefront + Admin02:30

Pacotão: Avise-me, Timeline, Recomprar, Selos, Marcas e Kits

6 features de uma vez: alerta de estoque, timeline de pedido, botão recomprar, selos de segurança, páginas de marca e kit builder.

  • Botão "Avise-me quando voltar" na PDP quando produto esgotado — integra com API de stock alerts existente
  • Timeline visual de progresso do pedido (Pedido → Confirmado → Preparo → Enviado → Entregue) com ícones e barra de progresso
  • Botão "Comprar novamente" na lista de pedidos — adiciona todos os itens do pedido ao carrinho com um clique
  • Footer: ícones SVG de bandeiras de pagamento (Visa, Master, Elo, PIX, etc.) substituindo badges de texto + selos de segurança (SSL, Compra Segura)
  • Página dedicada /marca/[slug] com banner hero, descrição e grid de produtos filtrados — brand-showcase na home agora linka para ela
  • Kit Builder no admin: aba "Kit" no editor de produto para buscar e adicionar itens ao kit com quantidades
  • KitItemsDisplay no storefront: mostra itens incluídos no kit com imagens, preços e cálculo de economia
  • API: novos endpoints storefront GET /products/brands e GET /products/brands/:slug
  • API: listagem de pedidos agora retorna productId, slug e imagem para suportar recompra

17 Abr 2026

NovoStorefront23:50

Carrossel 3D de Produtos na Home

Seção "Destaques 3D" na home — carrossel tridimensional com os 8 produtos mais vendidos.

  • Componente ShowcaseCarousel conecta Carousel3D com dados reais de produtos
  • CSS perspective + rotateY + translateZ para efeito de anel 3D real
  • Auto-rotação a cada 5s, drag/swipe para navegar, setas + dots
  • Responsivo: raio menor no mobile, card ativo com escala + sombra destacada
  • Fix z-index + pointer-events nos cards não-ativos para navegação funcionar
CorreçãoAdmin19:45

Sincronização Telefone TopBar ↔ Geral

O telefone da top bar do storefront agora reflete automaticamente o campo de telefone configurado em Admin > Geral.

  • syncTopBarPhone() sobrescreve o item phone da topBar com settings.phone
  • Aplicado nos dois caminhos de getStorefrontConfig (com e sem slug)
  • Cache Redis invalidado no deploy
NovoShipping15:30

Motoboy Zones + Orçamentador de Frete

Frete motoboy por faixa de CEP + orçamentador na página do produto.

  • Novo model ShippingZone no Prisma (zipRanges JSON, price, deliveryMin/Max, minOrder)
  • MotoboyProvider implementa ShippingProvider — busca zones ativas por CEP destino
  • calculateShipping() injeta cotações motoboy junto com Melhor Envio automaticamente
  • Novos endpoints CRUD: GET/POST/PUT/DELETE /shipping/zones (ADMIN)
  • Página admin /configuracoes/motoboy — CRUD completo de zonas
  • Widget shipping-quote.tsx na página do produto — input CEP + resultados com preço e prazo
  • Mostra Melhor Envio + Motoboy lado a lado no storefront

16 Abr 2026

NovoAdmin22:10

Relatórios de Clientes

Área /relatorios no admin com ranking de clientes, clientes inativos e aniversariantes.

  • Ranking por total gasto, total de pedidos ou última compra
  • Clientes sem comprar há X dias
  • Aniversariantes nos próximos X dias
  • Filtros por UF, Cidade, Loja ID, pedidos mínimo/máximo
  • birthdayWindow aceita valores de 1 a 365
NovoClientes CRM20:30

Filtro por Loja ID

Filtrar clientes pela loja/canal Bling da última compra válida.

  • GET /customers aceita query param blingLojaId
  • Novo endpoint GET /customers/stores com contagem por loja
  • Coluna Loja com nome amigável (TotalPOD, ReinoPOD, AquitemPOD)
NovoClientes CRM19:00

Página de Detalhe do Cliente

Página /clientes/[id] com dados completos, endereços e histórico de compras por data.

  • Resumo: pedidos, total gasto, ticket médio, itens comprados
  • Dados cadastrais, IDs, telefone, CPF/CNPJ, aniversário
  • Histórico de compras agrupado por data com status, pagamento, frete, itens
  • Link direto para pedido completo
MelhoriaClientes CRM18:15

Coluna Loja da Última Compra

Coluna Loja em /clientes mostra a loja Bling da última compra válida do cliente.

  • Mapa visual: 204347000→TotalPOD, 205153577→ReinoPOD, 206023212→AquitemPOD
  • IDs fora do mapa mostram ID bruto
  • 14.800 clientes mapeados de 16.634 com pedidos
CorreçãoBling16:40

Telefone e Data de Nascimento dos Clientes

Sync de contatos do Bling corrigido para capturar telefone e data de nascimento.

  • Parser aceita celular, telefone, fone, phone e variações
  • Parser de aniversário aceita múltiplos formatos (YYYY-MM-DD, DD/MM/YYYY, YYYYMMDD)
  • Importação em duas fases: lista rápida + detalhe individual
  • POST /bling/sync/contacts roda em background
  • Reconciliação de emails placeholder com emails reais
  • Backfill: 7→16.048 telefones, 2→210 aniversários
HotfixAuth15:20

Hotfix — /clientes "User not authenticated"

CustomersController não aplicava AuthGuard(jwt), bloqueando acesso autenticado.

  • Adicionado AuthGuard(jwt) antes de TenantGuard e RolesGuard
  • Mesmo padrão de OrdersController e outros endpoints admin
InfraInfra14:00

Deploy e Infraestrutura

Validação completa, deploy em produção e correção de cache read-only nos containers Next.js.

  • 8 suites, 88 testes passaram
  • tmpfs gravável para .next/cache em web e admin
  • Dockerfiles com ownership correto para nextjs:nodejs
  • Todos os containers healthy pós-deploy
NovoAdmin12:30

Clientes CRM em /clientes

Página de clientes transformada em CRM com métricas reais, filtros avançados e importação do Bling.

  • Tabela com ID, nome, telefone, pedidos, aniversário, cidade/UF, última compra, total gasto
  • Filtros: busca, aniversariantes, ranking, inativos, total pedidos, UF/Cidade
  • Métricas calculadas em tempo real a partir de orders
  • Botão de importação de contatos do Bling
MelhoriaBling11:00

Sync de Contatos Enriquecido

Sync grava birthDate e endereço do contato Bling, usado por CRM e filtros de localização.

  • birthDate gravada quando Bling envia data de nascimento
  • Endereço criado/atualizado a partir do contato Bling
  • Usado para Cidade/UF e filtros de localização
CorreçãoStorefront09:30

Widget de Prova Social — Intervalo Correto

Toast de prova social respeitando o intervalo configurado em /widgets/prova-social.

  • Timer agenda próxima notificação somente após a atual sumir
  • Intervalo usa config.intervalBetween do admin
  • Lista em ref — refresh não reinicia o ciclo
  • Entradas duplicadas deduplicadas por chave estável
MelhoriaStorefront08:45

Prova Social — Privacidade do Nome

Sobrenome real não é mais exibido. Nome público usa sobrenome fictício determinístico.

  • Nome público: PrimeiroNome + SobrenomeFictício (ex: Lucas Almeida)
  • Sobrenomes escolhidos de lista ampla, determinístico por entrada
  • Entradas antigas do Redis também anonimizadas
  • Cidade/UF continuam permitidos