Pular para o conteúdo
Justo, página inicial Justo, página inicial
Assistente Favoritos Notificações de desconto Carrinho

Documentação

Como o Justo funciona, do problema ao banco de dados

Uma leitura guiada do projeto, com diagramas e um roteiro de fala de um minuto por seção. O conteúdo vem dos ADRs, dos READMEs e da pesquisa do repositório.

Conferida em 2026-10-01 sobre o código desta branch

01

Visão geral e o problema

O Justo é uma loja de hardware que confere, peça a peça, se o PC vai funcionar antes do pagamento. Quem monta um computador esbarra em detalhes que só aparecem depois da compra: o processador de um soquete e a placa-mãe de outro, a memória da geração errada, a placa de vídeo que não cabe no gabinete, a fonte que não aguenta a carga.

A resposta é um motor de compatibilidade que avalia a montagem inteira a cada troca e explica o porquê quando algo não serve, com a peça, o requisito e uma alternativa. Em volta dele ficam o montador por espaço (cada pente de memória e cada SSD têm o seu), os PCs prontos e kits, o serviço técnico com mapa e busca por CEP e o pedido com Pix de demonstração.

O slogan resume a promessa: do carrinho ao PC funcionando. O projeto é desenvolvido para a seleção (07/10/2026) e a feira (09/11/2026).

Em uma frase por área

  • Montador e motor: encaixe conferido a cada escolha, com explicação e troca sugerida.
  • Loja: PCs prontos de vendedores, kits e peças, com preços de referência.
  • Serviço técnico: técnicos no mapa, busca por CEP (agenda e pagamento ainda não existem).
  • Base técnica: API em Java, front em HTML, CSS e JS puros, PostgreSQL 18.

02

Arquitetura

Em produção, tudo roda em contêineres Docker Compose numa VPS de 1 vCPU e 4 GB. O tráfego entra por um Caddy na borda, com HTTPS automático, que separa dois caminhos: /api/* vai para a API Java e o resto vai para o contêiner do front, que só serve arquivos estáticos.

Arquitetura de produção do Justo O navegador fala com o Caddy da borda pela porta 443. O Caddy manda /api/* para a API Java e o resto para o contêiner do front estático. Só a API Java fala com o PostgreSQL, por uma rede interna. O contêiner migrate aplica as migrações no banco. O túnel cloudflared é só para teste. Navegador HTML, CSS e JS VPS · DOCKER COMPOSE · 1 VCPU, 4 GB Caddy da borda HTTPS · limites Front estático Caddy · arquivos API Java Spring Boot 4 · Java 25 PostgreSQL 18 rede interna · sem porta migrate Flyway · papel de DDL Serviços externos mapas · PSP · IA cloudflared só para teste 443 demais rotas /api/* SQL
Componentes e rede. Só a API Java conversa com o banco. O contêiner migrate é o único com a senha de DDL. O cloudflared (túnel) é só para teste; na feira se usa o domínio.
Serviços de produção (docker-compose.prod.yml).
Componente O que é Limites vistos no compose
Caddy da borda HTTPS automático, roteia /api/* e o resto; /api/internal* dá 404 de fora; com a API fora responde 503 com Retry-After 256 MB, 0,5 CPU
front Caddy com os arquivos estáticos do site, CSP estrita sem nonce, sem segredos 128 MB, 0,5 CPU, disco somente leitura
api Java 25 + Spring Boot 4 (MVC, JPA, Flyway, Security); a saída é só para mapas, PSP e IA 768 MB, 1 CPU
db PostgreSQL 18.6 fixado por digest; rede interna, nenhuma porta publicada 512 MB
migrate a mesma imagem da API com o perfil migrate: aplica o Flyway e grava a demonstração 384 MB

Por que assim

  • Backend em Java (ADR-018): um monólito modular, um pacote por módulo, com as camadas (controller, service, repository, dto, domain) impostas pelo ArchUnit no build.
  • Front sem framework (ADR-019): módulos ES nativos, sem bundler, servido como arquivos estáticos e falando com a API na mesma origem.
  • Banco só pela API: a aplicação conecta com o papel justo_app (só DML); o DDL fica com o justo_migrator, que só o contêiner migrate usa.
  • API fora do ar não inventa dado: as páginas mostram um aviso de indisponível, nunca números de exemplo.

03

O motor de compatibilidade

O motor é uma função pura: evaluate(build, catálogo, contexto) recebe a montagem e devolve uma avaliação, sem I/O, relógio nem aleatoriedade. Cada fato da peça no catálogo carrega a fonte (a ficha do fabricante e a data da conferência).

Como o motor de compatibilidade funciona A montagem entra no motor, que avalia as 13 regras e devolve os achados e o veredito. Quando há conflito, o suggestRepairs propõe peças que resolvem. O motor existe em Java (referência) e em JavaScript (espelho no navegador), e as mesmas fixtures provam que os dois dão o mesmo resultado. Montagem uma peça por espaço evaluate() 13 regras · função pura Resultado achados + veredito suggestRepairs() peças que resolvem se há bloqueio a pessoa aceita a troca e a montagem é reavaliada PARIDADE JAVA × JAVASCRIPT Java implementação de referência Fixtures 1.356 casos de avaliação JavaScript espelho no navegador
A montagem passa pelo motor; havendo bloqueio, suggestRepairs propõe trocas e a montagem é reavaliada. A mesma lógica existe em Java (referência) e em JavaScript (espelho), provadas iguais pelas mesmas fixtures.

O que sai da avaliação

  • Achados (findings), cada um com a regra, a severidade, as peças envolvidas, a medida (valor, limite e margem) e a chave da mensagem em português.
  • Severidades: incompatível (bloqueio), atenção, informação e não verificado.
  • Veredito geral, em ordem de precedência: incompatível, incompleto, compatível com ressalvas, não verificado, compatível.
  • Dado ausente nunca vira "ok": o fato que falta gera "não verificado" (em uma regra, atenção) e entra na lista do que falta.
  • Peça que falta deixa a regra pendente, não com erro.

As 13 regras de hoje

Contadas em packages/compatibility/src/rules/index.js e nas 13 classes Java do módulo compat.
Regra O que confere Pior caso
CPU-MB-001 soquete do processador = soquete da placa-mãe incompatível
CPU-MB-002 BIOS mínima, pela lista de suporte da placa atenção (nunca bloqueia)
MEM-MB-001 geração da memória (DDR4 ou DDR5) contra a placa incompatível
MEM-MB-004 número de pentes contra os espaços de memória da placa incompatível
NVME-MB-001 SSDs NVMe contra os espaços M.2 da placa que aceitam PCIe incompatível
FF-CASE-001 formato da placa contra a lista de formatos do gabinete incompatível
GPU-CASE-001 comprimento da placa de vídeo contra o espaço do gabinete (folga abaixo de 10 mm avisa) incompatível
COOL-CASE-001 altura do cooler a ar contra o limite do gabinete (folga abaixo de 5 mm avisa) incompatível
COOL-CPU-001 processador sem cooler na caixa e nenhum cooler escolhido pendente na montagem, bloqueia no checkout
PSU-001 potência da fonte contra a carga estimada e a recomendação do fabricante da GPU incompatível
PSU-002 conector de 16 pinos da GPU contra a fonte (nativo, adaptador ou cabos insuficientes) incompatível
PSU-003 entradas PCIe de 8 pinos da GPU contra os cabos da fonte incompatível
VID-001 processador sem vídeo integrado e sem placa de vídeo pendente na montagem, bloqueia no checkout

Bloqueio que redireciona

Quando há um conflito, o montador mostra o que acontece, quais peças e quais valores, e oferece alternativas calculadas por suggestRepairs: para cada lado do conflito o motor tira a peça, testa candidatos do catálogo, reavalia a montagem inteira e só oferece a troca que resolve sem criar outro bloqueio. Avisos novos aparecem como efeito colateral, com a diferença de preço. O botão leva direto à peça que precisa mudar.

Escolha por espaço (slots)

A ADR-020 propõe modelar a placa-mãe com seus espaços reais: memória, M.2 (com interface e tamanho aceitos), SATA e PCIe. A primeira fatia já está no montador: o número de espaços de memória e de M.2 vem da placa, e a tela mostra qual pente e qual SSD ocupa cada um. A regra NVME-MB-001 é dessa fatia. ainda não as regras que dividem linhas entre SATA e M.2, nem o campo de espaço na URL da montagem.

Paridade entre Java e JavaScript

A implementação de referência é em Java (com.justopc.api.compat); o JavaScript roda no navegador só como espelho de experiência e não decide nada: quem valida o pedido é a API. O contrato são as fixtures em packages/compatibility/fixtures (com cópia idêntica no backend): 736 casos do catálogo completo, 120 de borda e 500 gerados por propriedade (seed fixa), mais 25 vetores de JSON canônico, 48 mensagens e 5 casos de reparo. Os dois lados comparam o resultado pelo JSON canônico, e o CI falha se divergirem.

Limites declarados

  • A potência é uma estimativa (parâmetros documentados, não medidos).
  • A BIOS de cada lote em estoque é desconhecida: onde a placa pede BIOS mínima, o motor só avisa.
  • Periféricos não entram no motor; radiador de water cooler e VRM ficaram fora do escopo do MVP.
  • A IA nunca decide compatibilidade: quando consultada, chama o motor.

04

Modelo de dados

Dois níveis, da pesquisa em mer-conceitual.md e der-fisico.md. O conceitual descreve o negócio; o físico é o que o PostgreSQL realmente garante.

MER conceitual

Modelo conceitual do Justo (MER) Do lado esquerdo, conta, pedido, cobrança Pix, cupom e favorito; o produto, o preço e o kit ficam fora do banco, em JSON. Do lado direito, o marketplace: vendedor e anúncio de PC pronto, prestador e serviço ofertado. Agendamento, visita e avaliação verificada são planejados. CONTA E COMÉRCIO MARKETPLACE Cupom Conta Pedido Cobrança Pix Sessão preferências, consentimento Item do pedido Favorito Produto, preço, kit fora do banco (JSON) desconta contém vende Vendedor Anúncio PC pronto Componente declarado Prestador Serviço ofertado Tipo de serviço fora do banco liga ao SKU Agendamento, visita e avaliação planejados para a feira (M2)
Tracejado: fora do banco (produto, preço e kit vivem em JSON versionado) ou planejado. Modelo de 2026-09-29; a migração 20260930120000 (endereço e avaliações do prestador) é posterior a ele.

DER físico

DER físico: 23 tabelas em três schemas Schema identity: conta com preferências, sessões, consentimentos e favoritos; tentativas de login e eventos de segurança ficam isolados. Schema commerce: pedido com itens e pagamentos, cupom com resgates; inbox de webhook e chave de idempotência isolados. Schema marketplace: vendedor com verificações e anúncios, anúncio com fotos e componentes, prestador com verificações e serviços; a trilha de preço e estoque é isolada. SCHEMA IDENTITY · 7 TABELAS account preferences session consent favorite auth_throttle security_event isoladas: sem chave estrangeira SCHEMA COMMERCE · 7 TABELAS customer_order coupon coupon_redemption order_item payment conta (opcional) webhook_inbox idempotency_key isoladas: sem chave estrangeira SCHEMA MARKETPLACE · 9 TABELAS seller listing seller_verification_check listing_photo listing_component provider provider_verification_check provider_service price_stock_audit isolada: sem chave
23 tabelas, 15 chaves estrangeiras reais. Setas: chave estrangeira, da tabela pai para a filha; tracejadas: relação opcional ou não identificadora. Tabelas isoladas (contorno tracejado) não têm chave estrangeira, por decisão registrada nas migrações.
Números do der-fisico.md (2026-09-29).
Schema Tabelas Para quê
identity 7 conta, sessão, preferências, consentimentos (só inclusão), favoritos, tentativas de login e eventos de segurança
commerce 7 pedidos, itens, pagamentos, cupons e resgates, inbox de webhook e chaves de idempotência
marketplace 9 vendedores e anúncios de PC pronto, prestadores e serviços, verificações e a trilha de preço e estoque

Decisões que aparecem no modelo

  • Catálogo fora do banco: 74 peças, 63 periféricos, os preços de referência e os kits vivem em JSON versionado, empacotado na API e carregado em memória. Por isso order_item.sku_id e semelhantes não têm chave estrangeira; a API confere na gravação.
  • Integridade no banco: cerca de 150 restrições CHECK e 44 índices, e a migração 20260929180000 reforçou a integridade financeira.
  • Trilhas que sobrevivem: eventos de segurança e a trilha de preço e estoque não têm chave estrangeira de propósito, para continuarem depois que a conta ou o anúncio forem removidos.
  • Lacunas conhecidas (auditoria de 2026-09-29): histórico de status do pedido, desistência em 7 dias, métricas e, em estudo, a chave estrangeira do resgate de cupom.

05

Compra e pagamento

O pagamento é Pix de demonstração: nenhum dinheiro de verdade se move. O fluxo, porém, é o de um checkout real, e as proteções já estão no lugar.

Fluxo de compra com Pix de demonstração O navegador pede a cotação e o preço vem do servidor. Ao criar o pedido, envia um Idempotency-Key e a API grava o pedido e a chave no banco. Em seguida pede o Pix, e a cobrança é criada no provedor simulado, que devolve um QR de demonstração. O webhook chega com um token, a API reconsulta a cobrança, confere o valor e marca o pedido como pago. O navegador consulta o estado até ver pago. Navegador API Java PostgreSQL PSP simulado fake · sem dinheiro 1 POST /api/quote · só ids e quantidades preço calculado no servidor 2 POST /api/orders · Idempotency-Key (UUID) grava o pedido e a chave (vale 24 h) 3 POST /api/orders/{id}/pix cria a cobrança QR de demonstração 4 webhook · token · reconsulta a cobrança o valor confere? marca como pago 5 GET /api/orders/{id}/pix · consulta o estado pago
Os cinco passos do pedido. O estado do “PSP” simulado vive na memória da API; o QR sai rotulado como demonstração.

Regras de cada passo

  1. Cotação. O navegador manda só os ids, quantidades, CEP, formato e cupom. O preço é sempre do servidor, nunca do cliente.
  2. Pedido e idempotência. POST /api/orders exige o cabeçalho Idempotency-Key (um UUID). A chave fica no PostgreSQL por 24 h com o hash do corpo: repetir o pedido devolve a resposta já gravada, e usar a mesma chave com outro corpo é recusado. Erro de validação não consome a chave.
  3. Pix. A cobrança é criada no provedor. O CPF vai ao provedor e só a versão mascarada fica no banco. O QR de demonstração vem rotulado e sem valor real.
  4. Webhook. Exige um token secreto (sem ele configurado, a rota responde 404), descarta reentregas pelo identificador do evento, reconsulta a cobrança em vez de confiar no corpo e só marca como pago se o valor bater com o total do pedido.
  5. Estado. O navegador consulta o pedido (a API reconsulta o provedor no máximo a cada 5 s) até ver pago.

CSRF sem token

A proteção contra requisições de outros sites usa Fetch Metadata: nas rotas que alteram dados, só passa o que vem de Sec-Fetch-Site: same-origin; sem esse cabeçalho, a Origin precisa estar na lista da aplicação. O resto é recusado com 403. O webhook não usa isso: ele se autentica pelo token.

06

Segurança e privacidade

Na borda e no navegador

  • CSP estrita no front, sem nonce: script-src 'self' e style-src 'self', sem unsafe-inline nem unsafe-eval. Nada de script inline; um teste varre o HTML e o JS. O front nunca usa innerHTML com dado da API.
  • Cabeçalhos: nosniff, política de referência, frame-ancestors 'none', isolamento de origem (COOP e CORP) e HSTS em HTTPS. A API tem a própria CSP fechada e no-store.
  • Limites na borda: tempos de leitura curtos (10 s para cabeçalho) e corpo máximo de 256 KB. Um teste de conexão lenta fechou em 10 s.

No front, nesta versão

  • Guarda central dos atributos: a função que monta os elementos recusa manipuladores de evento como texto, style, srcdoc e endereços com protocolo fora de http, https, mailto e tel (os que executam código ou embutem conteúdo), qualquer que seja a origem do dado. É uma segunda camada, além da CSP, com testes.
  • Links externos: os que abrem em nova aba levam rel="noopener noreferrer" (um teste confere cada target="_blank"); a fonte de uma ficha só vira link se for http ou https.
  • O que fica no navegador: carrinho e favoritos (só códigos de peça), tema e a preferência de menu, todos validados ao ler e com tamanho limitado. O CPF do pagador fica só em memória até ir ao Pix; nada de cookie nem de dado pessoal no localStorage.
  • Sem terceiros no navegador: fontes próprias, sem analytics, e os mapas passam por um proxy do nosso servidor, então o provedor dos tiles não recebe o seu IP.
  • Assistente e LGPD: comando nunca vai à IA. Havendo provedor de IA, a primeira mensagem livre só segue com o seu aceite, com o provedor e o país informados pelo servidor e o aviso para não enviar dados pessoais. O texto do aceite ainda é provisório, em revisão. em revisão

Pontos conhecidos: o CEP da busca de técnicos vai na barra de endereço, então fica no histórico do navegador (e, quando existir log de acesso na borda, convém mascarar a consulta); e a política de privacidade final ainda depende do dono do projeto.

Na API

  • Rate limit por IP em cada rota (por exemplo: pedidos 30, cotação 60, Pix 20 e assistente 20 por minuto). Um teste quebra o build se uma rota nova ficar sem limite nem justificativa.
  • Erros padronizados em RFC 9457, com slugs estáveis.
  • Sessão de conta (desligada por ora): cookie __Host- com HttpOnly, Secure e SameSite=Lax; só o hash SHA-256 do token vai ao banco; senha com argon2id.

No banco e nos segredos

  • Papéis separados: justo_app (só DML, até 10 conexões, tempos limite curtos) e justo_migrator (DDL, só no contêiner migrate).
  • Segredos fora do repositório: arquivos em /etc/justo/secrets, lidos como segredos do Compose; a API recusa subir com o marcador troque-me.
  • Contêineres endurecidos: usuário sem privilégio, disco somente leitura, capacidades removidas.

LGPD: minimização

  • Só o CPF mascarado fica no banco; nenhum log registra corpo, CPF, e-mail, nome, token ou conversa do assistente.
  • O consentimento é só de inclusão, e os eventos de segurança não guardam e-mail nem IP.
  • Ligar as contas depende da política de privacidade, que é decisão do dono do projeto.

O que ainda falta

Do endurecimento registrado em endurecimento-f4.md: firewall de saída, varredura de vulnerabilidades de dependências no CI, log de acesso da borda e preload do HSTS. pendente

07

Qualidade

Fontes: endurecimento-f4.md, infra/README.md e o repositório.
O quê Como Números
Testes do backend JUnit 5, MockMvc e Testcontainers com a mesma imagem do PostgreSQL de produção 5.715 testes, 0 falhas em 2026-09-30
Arquitetura ArchUnit no build: controller não acessa repository nem domain; dto não conhece as camadas de dentro; domain sem Spring; um módulo só usa outro pelo service e pelo dto 6 regras
Testes do front Vitest nos módulos puros e nas varreduras de segurança (sem innerHTML, sem script inline, sem import nu) 50 arquivos e 1.245 testes (todo o JS) em 2026-10-01
Ponta a ponta Playwright contra as imagens de produção, nos temas claro e escuro 376 passando, 4 pulados em 2026-10-01
Acessibilidade axe (WCAG 2.2 AA) em cada página e nos dois temas, foco visível e contraste AA zero violações é condição do teste
Paridade do motor as mesmas fixtures no Java e no JavaScript 1.356 casos de avaliação

CI (GitHub Actions)

  1. front: Biome (lint e formatação), Vitest e a geração do vendor.
  2. backend: verify com JDK 25: testes, ArchUnit e paridade das fixtures.
  3. e2e: sobe a pilha de desenvolvimento inteira e roda o Playwright com axe.
  4. imagens: constrói as duas imagens, sobe o Compose de produção isolado, confere HTTPS, HSTS, CSP e o 404 de /api/internal, roda o e2e e ensaia o backup e a restauração.

As ações são fixadas por hash (SHA) e as permissões do fluxo são só de leitura.

Backup

Um timer às 03:30 faz pg_dump (7 diários e 4 semanais), e aos domingos o último dump é restaurado num banco descartável e conferido. A cópia fora da VPS ainda não existe, então a perda máxima hoje é de até 24 h. pendente

08

Design, animação e carregamento

O front é HTML, CSS e JavaScript puros: um módulo por página, sem framework, sem build e sem biblioteca de animação. O visual foi pensado para ser rico sem pesar, e tudo abaixo existe no código desta versão.

Layout e leitura

  • Fluido, de largura total: tipografia e espaços em clamp() e rem, margens proporcionais à tela e cartões em colunas auto-fill com largura mínima por tamanho de tela (de 390 a 2560 px).
  • Menu e filtros presos à esquerda: no computador, o menu (com as categorias de compra, que são a única parte que rola) e a coluna de filtros ficam abaixo do cabeçalho com a altura da tela, e o anúncio de PCs prontos fica fixo embaixo. No celular o menu vira gaveta e o cabeçalho mostra o rótulo de cada ação embaixo do ícone.
  • Cartões em que a imagem manda: imagem grande no alto (foto com licença e crédito, ou ilustração própria rotulada “Ilustração”), título, poucas especificações, preço, selo factual do motor e um botão com texto. Nenhum selo de desconto ou de “mais vendido” é inventado.

Interação

  • Filtros, ordem e páginas ao vivo em peças, kits e PCs prontos: o link continua sendo um link (funciona sem JavaScript e abre em outra aba), e o clique refaz a lista no lugar com history.pushState; o voltar restaura.
  • Faixas por categoria na loja: “Mais usados nos kits” (contagem real dos kits conferidos pelo motor), “Melhor custo-benefício” (conta explícita de capacidade por real, só onde a ficha tem o número) e uma fileira por categoria, com “Mostrar mais”.
  • Favoritar, adicionar ao carrinho e comparar até 3 PCs, com aviso falado por uma região aria-live. Favoritos e carrinho ficam só neste navegador.
  • Assistente em conversa (/assistente): resposta por SSE em “modo comandos”, com cartões do sistema (produto, comparação, compatibilidade, PC e ficha). Sem provedor de IA ligado, não há IA generativa, e a tela diz isso.

Movimento e interação

  • Só transform, opacity, clip-path e mask; requestAnimationFrame só enquanto algo se mexe; IntersectionObserver para entrar e para pausar fora da tela. Com prefers-reduced-motion: reduce ou com tela de toque, nenhum efeito liga e a página fica parada e inteira.
  • Cartões que seguem o ponteiro: um só ouvinte de movimento grava a posição do mouse em variáveis CSS do cartão (uma vez por quadro); o CSS faz a inclinação leve, o foco de luz, a borda acesa e o preço que cresce. Na ilustração do PC, uma lente aberta por mask mostra o interior por baixo do vidro fumê.
  • Herói da home: uma forma geométrica própria (gradiente recortado por clip-path, que muda de forma devagar) com o PC dentro, que inclina com o ponteiro. Em computador com mouse e WebGL2 por hardware, o modelo 3D do montador entra depois que a página fica ociosa e gira com o ponteiro e com a rolagem.
  • Revelação por ponteiro: o mouse deixa um rastro que mostra a placa de vídeo por dentro e some sozinho em pouco mais de um segundo (canvas 2D, sem WebGL); o botão "Mostrar por dentro" faz o mesmo em qualquer aparelho.
  • Narrativa por rolagem: a placa-mãe fica presa ao lado e cada passo lido acende a peça dele. Carrossel de kits e destaques que giram com dados reais, sempre com botão Pausar, parando com o mouse ou o foco em cima. Selos com brilho, entrada escalonada e a mídia do cartão que vira a da página de detalhe (View Transitions).
  • Custo medido: os efeitos somam 16 KB comprimidos na home; o three (449 KB) só baixa no computador com mouse. Detalhes e a pesquisa ao vivo em research/ux-ui/referencias-design-fase5.md.

Desenhos próprios e 3D

  • Ilustrações em SVG feitas para o Justo: o PC de cada cartão (variação estável pelo id), o diagrama da placa na home, o mapa ilustrado dos técnicos, os ícones de uso do quiz e os estados vazios. Cores por classe, então os dois temas trocam sozinhos.
  • 3D sob demanda: o three já vem no vendor e só é carregado, por import(), quando a seção chega perto da tela (no montador, na seção 3D e no herói da home). Sem WebGL2 ou com aparelho fraco, entra o diagrama 2D com as mesmas medidas: o conteúdo nunca depende do 3D.

Carregamento e orçamento

  • HTML útil antes do JavaScript; um módulo por página, sem script de terceiros; fontes próprias (self-hosted); imagens com loading="lazy"; arquivos pré-comprimidos (zstd, brotli e gzip) pelo Caddy.
  • Esqueleto de carregamento só depois de 400 ms de espera; paginação numerada (sem rolagem infinita); prefetch de páginas do próprio site ao apontar ou focar um link, no máximo 8 por página e nunca com a economia de dados ligada. Como o HTML revalida (no-cache), o ganho do prefetch é de melhor esforço.

Acessibilidade

WCAG 2.2 AA conferida com axe nos dois temas; reflow em 320 px sem rolagem lateral (teste em todas as páginas); botões do cabeçalho com texto visível a partir de 1024 px e nome acessível sempre; alvo de toque e foco visível mantidos. A pesquisa que embasou as escolhas (lojas de hardware e dois sites criativos) está em research/ux-ui/referencias-design-fase3.md no repositório.

Ainda não há: modelos 3D próprios para cada peça (hoje são genéricos, com as medidas do catálogo) e IA generativa no assistente. pendente

09

Decisões de arquitetura

Cada decisão é um ADR no repositório, com o contexto e as alternativas. Os mais antigos (22/09) eram só propostas de pesquisa; vários foram superados quando o dono escolheu Java e o front puro, e cada um aponta para o que o substituiu.

ADR Decisão Situação
ADR-001 Monólito modular num monorepo. A linguagem do backend foi trocada pela ADR-018; o monólito modular continua. mantida
ADR-002 Processos separados para web e API. Hoje: contêiner da API em Java e contêiner do front; não há worker. superada em parte
ADR-003 PostgreSQL como fonte da verdade. O acesso a dados virou JPA e as migrações, Flyway. superada no acesso
ADR-004 Sem Redis no MVP: fila, outbox e inbox no PostgreSQL. proposta
ADR-005 Um único motor de compatibilidade, com parâmetros versionados. Referência em Java desde a ADR-018. mantida
ADR-006 Busca de produtos no Meilisearch e de técnicos no PostGIS. proposta
ADR-007 Front em Next.js com guarda-corpos de segurança. As guardas viraram a CSP estrita do Caddy. superada pela ADR-019
ADR-008 API REST com OpenAPI e tempo real por SSE. O framework virou Spring MVC; REST, OpenAPI e SSE continuam. superada no framework
ADR-009 Sessões no servidor. Sessões próprias no PostgreSQL, hoje em Java. superada
ADR-010 Um único provedor de pagamento, com divisão entre vendedores. proposta
ADR-011 Portas e adaptadores para todo fornecedor externo. proposta
ADR-012 IA com provedor trocável. O assistente foi portado para Java, com resposta em SSE. superada na implementação
ADR-013 Mapas com MapLibre, CEP primeiro e rota estimada no MVP. proposta
ADR-014 3D como aprimoramento progressivo e nunca fonte de verdade. proposta
ADR-015 Armazenamento de objetos no Cloudflare R2. proposta
ADR-016 Observabilidade leve e CI com a cadeia de suprimentos protegida. proposta
ADR-017 Contas com PostgreSQL em contêiner, senhas com argon2id e sessões próprias. A segurança e a LGPD continuam; a implementação passou ao Java. aceita, superada na implementação
ADR-018 Backend em Java 25 com Spring Boot, Maven e ArchUnit. aceita
ADR-019 Front em HTML, CSS e JavaScript puros, servido pelo Caddy; o Next foi apagado. aceita
ADR-020 Catálogo rico, regras que redirecionam e escolha por espaço da placa. proposta (fatia 1 feita)
ADR-021 Contas, papéis e o marketplace de verdade. Nada liga as contas em produção antes da decisão do dono. proposta
ADR-022 Home de apresentação em / e a vitrine em /loja. aceita

10

Roteiro

Marcos: seleção em 07/10/2026 e feira em 09/11/2026 (PLANO-FEIRA). O que o dono pediu em 2026-09-30, e onde cada item está:

Fontes: a página /proximas-etapas e as ADRs 020 e 021.
Frente Situação hoje
Montador por espaço e mais peças com especificações reais fatia 1 entregue: espaços de memória e M.2 da placa e a regra de NVMe; próximas: SATA, linhas compartilhadas, mais placas em andamento
Mapa de técnicos com localização exata e avaliações busca por CEP e mapa funcionam com prestadores de exemplo; a migração de endereço e avaliações já existe em andamento
Catálogo rico: marcas, nomes de kits e imagens com licença clara catálogo de 74 peças e 63 periféricos; fotos só de fontes com licença, com créditos em /licencas em andamento
Contas, login e área de vendedores e prestadores desenhadas na ADR-021; ACCOUNTS_ENABLED desligado até decisão do dono proposta
Assistente com IA que monta por tema caixa de perguntas na home e na loja, em modo comandos (busca e conferência pelo catálogo, sem IA generativa); a resposta por IA depende de chave de provedor em andamento
Montagem em 3D listada nas próximas etapas do site ainda não
Serviços e softwares de comunidade, avaliações e links de referência do vendedor ideias registradas ainda não

11

Demonstração × produção

O que uma pessoa usa hoje é uma demonstração sobre uma base de produção. Esta tabela separa uma coisa da outra.

Fonte: infra/README.md e endurecimento-f4.md.
Área Hoje Produção de verdade
Pagamento Pix simulado (provedor fake por padrão; o sandbox da Asaas recusa chave de produção) provedor real só com decisão do dono
Contas e login desligados (ACCOUNTS_ENABLED=0); as rotas respondem 404 depende da política de privacidade e da decisão do dono
Vendedores, anúncios e prestadores dados de demonstração, idempotentes, sem tocar em dado real área de venda e KYC ainda por decidir
Preços preços de referência com data de conferência preço e estoque de vendedores reais
Assistente de IA modo de teste; sem chave de provedor, fica de fora chaves e custo por decisão do dono
Zerar a demonstração rota interna, só por dentro da VPS, com token e uma flag no banco desativada na loja real
Túnel (cloudflared) só para teste domínio próprio com HTTPS automático

Já é de produção

  • API Java e front em contêineres endurecidos; PostgreSQL 18 com papéis separados e segredos em arquivo.
  • Checkout com preço do servidor, idempotência no banco, CSRF por Fetch Metadata e webhook com reconsulta.
  • Rate limit, HTTPS e HSTS, migrações com Flyway e backup diário com restauração testada.

O site diz isso a quem visita: o rodapé de todas as páginas e a página de próximas etapas avisam o que é demonstração.