# ARQUITETURA — Loja Virtual Própria da Oferta Distribuidora

> Ler junto de `AUDITORIA-ONDA0.md` (diagnóstico) e `DATA-OWNERSHIP.md` (quem manda em cada dado).

---

## C) PLATAFORMA ESCOLHIDA: **Medusa v2**

### Comparação, no contexto desta empresa

| Critério | Medusa v2 | Vendure | Peso aqui |
|---|---|---|---|
| Linguagem/stack | Node + TypeScript, Postgres, Redis | Node + TypeScript, Postgres | empate |
| API | **REST** (Store + Admin) + JS SDK | **GraphQL** (Shop + Admin) | 🔴 **Medusa** — o ERP é PHP puro com cURL. REST é `curl_init` + `json_decode`. GraphQL em PHP sem framework é dor de cabeça permanente |
| Extensão | Módulos + **Workflows** com passos compensáveis, retry e idempotência nativos | Plugins + eventos, estilo NestJS | 🔴 **Medusa** — este projeto é 80 % integração; workflow com compensação é exatamente o que evita "meio pedido" |
| Admin | React, extensível por rota/widget dentro do próprio projeto | Angular (3.x) — extensão exige stack separada da loja | 🟠 **Medusa** — customizar admin em React é o mesmo mundo do Next.js |
| Catálogo / variantes | Bom (product / variant / option, sem limite prático) | **Excelente** (facets, channels, coleções por regra) | 🟠 Vendure ganha, mas o catálogo daqui é simples: móvel, cor, medida |
| Promoções / cupons | Motor de promoções nativo (regras por item, carrinho, frete) | Motor de promoções nativo | empate |
| Multi-canal | Sales Channels + Regions nativos | Channels nativos | empate |
| Integração com Next.js | Starter oficial mantido | Storefronts de exemplo | 🟠 Medusa |
| Facilidade para Claude Code | Arquivos TS pequenos e previsíveis; scaffolding por CLI | Decorators e DI; mais cerimônia | 🟠 Medusa |
| Maturidade | v2 estável, comunidade grande, muito conteúdo BR | Estável, comunidade menor no Brasil | 🟠 Medusa |
| Custo de infra | Igual (Node + Postgres + Redis) | Igual | empate |

**Decisão: Medusa v2.** O fator que decide não é catálogo — é que **este projeto é uma
peça de integração**, e a peça precisa conversar com um ERP em PHP puro. REST + workflows
idempotentes valem mais aqui do que o modelo de catálogo mais rico do Vendure.

**Onde o Vendure seria melhor:** se o negócio fosse catálogo enorme com facetas
complexas e múltiplos canais/idiomas. Não é o caso.

**Não trocar de stack no meio.** Se algum dia trocar, o motivo tem que estar escrito aqui.

### O que Medusa **não** entrega e nós teremos que construir

- Storefront (é headless — o Next.js é nosso).
- Busca boa: precisa Meilisearch (ou Postgres full-text no começo).
- CMS de banner/vitrine: entra como módulo próprio no admin.
- Nada de MMN, CRM, WhatsApp, montagem — **isso já existe e continua onde está**.

---

## B) ARQUITETURA PROPOSTA

```
                      ┌──────────────────────────────────────┐
   Cliente  ──────►   │  STOREFRONT  (Next.js 15 + TS)       │  lojavirtual.ofertadistribuidora.com.br
   (mobile 1º)        │  SSR/ISR · PWA · SEO · Pixel/GTM     │  → depois: ofertadistribuidora.com.br
                      └───────────────┬──────────────────────┘
                                      │ REST (Store API + BFF)
                      ┌───────────────▼──────────────────────┐
                      │  OFERTA COMMERCE API                 │  ← camada nossa, o "tradutor"
                      │  · atribuição de afiliado            │
                      │  · eventos (cart/checkout/order)     │
                      │  · cashback (consulta ledger do ERP) │
                      │  · webhooks in/out idempotentes      │
                      │  · fachada compatível-Nuvemshop      │
                      └───┬───────────────────────────────┬──┘
                          │                               │
          ┌───────────────▼────────────┐   ┌──────────────▼─────────────────────────┐
          │  MEDUSA v2 (commerce)      │   │  SISTEMAS QUE JÁ EXISTEM (não tocar)   │
          │  catálogo · carrinho       │   │  ERP vo/ · MMN · CRM · Marketing OS    │
          │  checkout · pedido         │   │  Agente WhatsApp · Cashback · OS/monta │
          │  promoções · estoque       │   │  Painel cliente · Motorista            │
          │  PostgreSQL + Redis        │   │  PHP + MySQL ofertadi_os               │
          └────────────────────────────┘   └────────────────────────────────────────┘
```

### Regra de ouro

> O storefront **nunca** fala com o MySQL antigo, e o ERP **nunca** fala com o Postgres novo.
> Tudo passa pela Commerce API.

### A jogada que reduz o risco: **fachada compatível com a Nuvemshop**

12 arquivos PHP chamam `NS_API_BASE . '/products'` ou `/orders` ou `/coupons`.
A Commerce API vai expor as **mesmas rotas, no mesmo formato de resposta**:

```
GET  /ns/v1/5466314/products          → produtos da loja nova, no JSON da Nuvemshop
GET  /ns/v1/5466314/products/{id}
GET  /ns/v1/5466314/orders
POST /ns/v1/5466314/coupons           (+ PUT / DELETE)
GET  /ns/v1/5466314/checkouts         → carrinhos abandonados nossos
```

Assim, no dia do cutover, **precificação, painel de lucro, agente IA, catálogo PDF,
marketing/criativos e cashback trocam UMA constante** (`NS_API_BASE`) e continuam
funcionando. Sem reescrever 12 arquivos de um sistema sem teste automatizado.
Onde a fachada não couber (ex.: `PUT /variants` para escrever preço), o arquivo ganha
uma rota nova — mas isso é a exceção, não a regra.

---

## Infra (R1 — decisão que trava a Onda 1)

O cPanel compartilhado atual **não roda Node, Postgres nem Redis**. Precisa de servidor novo.

| Peça | Onde | Custo/mês (começo) | Custo/mês (com volume) |
|---|---|---|---|
| Medusa + Postgres + Redis + Commerce API | 1 VPS — Hetzner CX22 (2 vCPU/4 GB) no começo, CPX32 (4 vCPU/8 GB) depois | R$ 25 – 40 | R$ 90 – 250 |
| Storefront Next.js | Vercel (plano free serve no começo) **ou** a mesma VPS via Docker | R$ 0 | R$ 0 – 120 |
| Imagens | Volume da VPS + Cloudflare na frente (grátis) | R$ 0 | R$ 0 |
| Backup | Reaproveitar a rotina do Drive que já existe, apontando pro Postgres | R$ 0 | R$ 0 |
| **Total** | | **~R$ 25 – 40/mês** | **~R$ 90 – 370/mês** |

### Por que não roda no cPanel compartilhado atual

Perguntado pelo dono em 15/08/2026. Motivo principal, e é evidência do próprio sistema:
**processo de linha de comando nesta hospedagem não tem saída para a internet** —
documentado no ESTADO.md do sistema irmão (backup falhou 07, 08 e 09/08 com `HTTP 0`;
pela web o mesmo backup subia em 4 s). Um servidor Medusa é justamente um processo fora do
Apache que precisa falar com gateway, Meta e webhooks o tempo todo.

Somando: PostgreSQL raramente é oferecido em plano compartilhado, Redis praticamente nunca,
o Passenger do Node Selector reinicia e mata workers de fila, o limite LVE derruba o build
(`npm install` do Medusa pede ~2 GB), e a loja dividindo servidor com o ERP significa que um
build pesado pode derrubar `vo/` e `afiliado/` — o sistema que move comissão e saque.

Para não decidir no chute: `scripts/diag-hospedagem.php` (só leitura) mede Node, PostgreSQL,
Redis, recursos e **a saída de internet pelo CLI**, direto no servidor.

Tudo em **Docker Compose** para o ambiente ser reproduzível e o rollback ser um `git revert`.
O sistema PHP atual **fica exatamente onde está** — não muda de hospedagem, não muda de banco.

---

## D) DIAGRAMA DE COMPONENTES

```
STOREFRONT (Next.js)
├── app/(loja)/                 home, categoria, produto, busca, carrinho, checkout
├── app/(conta)/                pedidos, dados, cashback, cupons
├── middleware.ts               captura ?ref= / utm_* → cookie 1st-party (90 dias)
├── lib/commerce.ts             cliente da Commerce API
└── analytics/                  Pixel + GTM + dataLayer (event_id compartilhado com o CAPI)

COMMERCE API (Node/TS — Fastify ou rotas custom do Medusa)
├── /products /categories /search        → Medusa
├── /cart /checkout                      → Medusa + regras nossas
├── /affiliate/resolve                   → valida GO_n no MySQL (leitura)
├── /cashback/saldo /cashback/aplicar    → consome o ledger cashback_mov (não duplica)
├── /orders                              → cria no Medusa e ESPELHA no ERP
├── /events                              → cart_created … order_paid
├── /webhooks/{provider}                 → idempotente (event_id + payload_hash)
└── /ns/v1/{store}/*                     → fachada compatível-Nuvemshop (legado PHP)

MEDUSA v2
├── módulos padrão: product, inventory, pricing, cart, order, promotion, customer
└── módulos nossos: oferta-attribution · oferta-cashback · oferta-erp-sync · oferta-cms

ERP PHP (aditivo — arquivos NOVOS, nada reescrito)
├── vo/api/loja-pedido.php        recebe pedido da loja nova → pedidos_cache (LP-000123)
├── vo/api/loja-cliente.php       resolve identidade pela cascata que já existe
├── vo/api/loja-afiliado.php      valida GO_n / cupom → afiliado_id
├── vo/api/loja-cashback.php      saldo e aplicação, chamando config/cashback.php
└── vo/api/loja-eventos.php       repassa evento pro CRM/Marketing que já existem
```

**Autenticação entre as camadas:** HMAC-SHA256 do corpo + timestamp + `X-Oferta-Signature`,
segredo em variável de ambiente dos dois lados. Nunca `?chave=` na URL para dado de pedido.

---

## Decisões travadas (não reabrir sem motivo escrito)

1. **Medusa v2**, não Vendure.
2. **Postgres** para a loja nova; **MySQL `ofertadi_os` intocado** para o ERP.
3. **Baixa continua manual no PDV** na primeira versão — comissão e cashback não mudam de gatilho.
4. **Comissão nunca é calculada na loja nova.** Ela só entrega o pedido com `afiliado_id`.
5. **Cashback não ganha tabela nova.** `cashback_mov` continua sendo a única verdade.
6. **Atribuição resolvida no servidor**, nunca no navegador.
7. **Nuvemshop é fonte de verdade do catálogo até o cutover.** Sincronização de mão única.
8. **Cores da marca:** azul `#07166F`, vermelho `#9A0808`, branco. Nada de coral/rosa.
9. **UI só com ícone SVG, nunca emoji** (regra do dono, 23/07) — WhatsApp continua com emoji.
