# API SheikBras (v1)

Base: `/v1`. JSON em UTF-8. Autenticação: `Authorization: Bearer <accessToken>` (15 min). Renovação por `refreshToken` (30 dias, uso único).

## Erros
Sempre `{ "error": { "code", "message", "details?" } }`. Códigos estáveis:
`VALIDATION` (422, `details` por campo) · `INVALID_CREDENTIALS` / `UNAUTHORIZED` / `MFA_REQUIRED` / `MFA_INVALID` / `TOKEN_REUSED` (401) · `FORBIDDEN` (403) · `NOT_FOUND` (404) · `EMAIL_TAKEN` / `CONFLICT` (409) · `RATE_LIMITED` (429, cabeçalho `Retry-After`) · `INTERNAL` (500, com `requestId`).

## Conta
| Método e rota | Corpo | Resposta |
|---|---|---|
| `POST /auth/register` | `{nome, email, senha, aceite:true}` | `201 {user, tokens}` |
| `POST /auth/login` | `{email, senha, codigo?}` | `200 {user, tokens}`; com 2FA ativo e sem `codigo`: `401 MFA_REQUIRED` |
| `POST /auth/refresh` | `{refreshToken}` | `200 tokens` (o antigo deixa de valer; reuso encerra todas as sessões da família) |
| `POST /auth/logout` | `{refreshToken}` | `204` |
| `POST /auth/logout-all` 🔒 | — | `204` |
| `POST /auth/mfa/setup` 🔒 | — | `{secret, otpauthUrl}` (mostrar como QR code) |
| `POST /auth/mfa/confirm` 🔒 | `{codigo}` | `{recoveryCodes[8]}` (exibidos uma única vez) |
| `POST /auth/mfa/disable` 🔒 | `{senha, codigo}` | `204` |
| `POST /auth/verify-email/request` 🔒 | — | `204` (envia o link por e-mail; máx. 3 por hora) |
| `POST /auth/verify-email` | `{token}` | `200 user` |
| `POST /auth/password/forgot` | `{email}` | `202` sempre igual, exista ou não a conta |
| `POST /auth/password/reset` | `{token, senha}` | `204` (link de uso único, vale 1 h; encerra todas as sessões) |
| `POST /auth/password/change` 🔒 | `{atual, nova}` | `204` (encerra todas as sessões) |
| `GET /me` 🔒 | — | `{id, name, email, role, mfaEnabled}` |
| `GET /me/export` 🔒 | — | Todos os seus dados em JSON (LGPD: acesso e portabilidade) |
| `DELETE /me` 🔒 | `{senha}` | `204` anonimiza a conta (bloqueado se houver pedidos em andamento) |

`tokens = { accessToken, refreshToken, expiresIn }`.

## Catálogo
| Método e rota | Quem | Descrição |
|---|---|---|
| `GET /products` | público | Busca com filtros. Mesmo formato de URL da web: `?q=celular vento 256gb` ou `?c=Celulares&marca=vento&a_storage=256&min=1000&max=3000&frete=1&ordem=pa&limite=24`. Resposta: `{total, items[], filters[]}` (os `filters` já vêm no modelo que web e app desenham) |
| `GET /products/:id` | público (ativos) / dono / admin | Detalhe |
| `GET /products/mine` 🔒 | vendedor | Meus produtos |
| `POST /products` 🔒 | vendedor, admin | Cria como rascunho. Atributos validados pelo esquema da categoria |
| `PATCH /products/:id` 🔒 | dono, admin | Atualiza (reindexa se ativo) |
| `POST /products/:id/publish` 🔒 | dono, admin | Publica (exige estoque) |
| `POST /products/:id/archive` 🔒 | dono, admin | Arquiva e remove da busca |

## Vendedores
| Método e rota | Quem | Descrição |
|---|---|---|
| `POST /sellers/apply` 🔒 | cliente com e-mail confirmado | `{type:"PF"\|"PJ", document, legalName, tradeName, phone, address{cep,rua,num,comp?,bairro,cidade,uf}}`. CPF/CNPJ validados; documento criptografado; duplicidade bloqueada |
| `GET /sellers/me` 🔒 | candidato/vendedor | Situação (`pending`, `approved`, `rejected`, `suspended`), documento mascarado |
| `GET /admin/sellers?status=` 🔒 | admin | Fila de análise |
| `POST /admin/sellers/:userId/approve` / `reject` / `suspend` 🔒 | admin | `reject` e `suspend` exigem `{motivo}`. Ao aprovar, o papel vira vendedor: a pessoa precisa renovar a sessão (`/auth/refresh`) para o novo papel valer |

## Pedidos e pagamentos
Um pedido **por vendedor** (cada um envia o seu pacote); um pagamento por finalização de compra.
| Método e rota | Quem | Descrição |
|---|---|---|
| `POST /orders/quote` | público | `{items:[{productId,qty}], cep}` → frete por vendedor (padrão e expresso) |
| `POST /orders` 🔒 | cliente | `{items, address, shipMethod:"std"\|"exp", payMethod:"pix"\|"card"\|"boleto", installments?, cardToken?, coupon?}`. Cabeçalho `Idempotency-Key` evita duplicar. Preços vêm do servidor; estoque é reservado de forma atômica. Resposta: `{orders[], payment{pixCode?, boletoUrl?, status}}` |
| `GET /orders`, `GET /orders/:id` 🔒 | comprador | Meus pedidos / detalhe |
| `POST /orders/:id/cancel` 🔒 | comprador | Só enquanto aguarda pagamento ou pago e ainda não em preparo (reembolsa e devolve estoque) |
| `GET /seller/orders` 🔒 | vendedor | Pedidos recebidos (inclui comissão e repasse) |
| `POST /seller/orders/:id/prepare` · `/ship {carrier, code}` · `/deliver` 🔒 | vendedor | `paid → preparing → shipped → delivered` |
| `POST /webhooks/payments` | provedor | Assinatura (`x-signature`) conferida sobre o corpo bruto. Idempotente |

**Cartão:** a API **nunca** recebe o número do cartão. O app/site usa o SDK do provedor, gera um `cardToken` e envia só o token.

## Integrações que dependem da sua escolha
Hoje os provedores são “fake” (só desenvolvimento; **a API se recusa a subir em produção com eles**). Para produção implemente:
- `PaymentGateway` (`core/ports.ts`): `charge`, `refund` e `verifyWebhook` para o provedor de pagamento escolhido (com divisão de valor entre vendedores, se o provedor oferecer).
- `ShippingQuoter`: cotação de frete (agregador ou transportadoras).
- `EmailSender` já tem adaptador SMTP (`SMTP_URL`).

Preços sempre em **centavos** (inteiro). 🔒 = exige login.

## Avaliações e financeiro
| Método e rota | Quem | Descrição |
|---|---|---|
| `GET /products/:id/reviews` | público | Avaliações verificadas (autor só como “Marina L.”) |
| `POST /reviews` 🔒 | comprador | `{orderId, productId, rating 1-5, text}`. Só depois da entrega, uma vez por produto e pedido; nota ≤ 2 exige comentário. Recalcula a nota do produto e atualiza a busca |
| `GET /seller/reviews` 🔒 | vendedor | Avaliações dos meus produtos |
| `POST /seller/reviews/:id/reply` 🔒 | vendedor | `{text}` (3 a 600 caracteres, uma resposta por avaliação) |
| `GET /seller/finance` 🔒 | vendedor | Extrato: `{releasedCents, toReleaseCents, inProgressCents, commissionCents, lines[]}`. Não move dinheiro |

## Vitrine, cupons e banners
| Método e rota | Quem | Descrição |
|---|---|---|
| `GET /home?device=` | público | `{banners, offers, bestsellers, recommended}` para a página inicial |
| `GET /banners?device=&slot=` | público | Banners no ar (por data, dispositivo e prioridade) |
| `POST /coupons/check` 🔒 | cliente | `{code}` → `{code, pct}` ou erro genérico “Cupom inválido ou expirado.” |
| `GET/POST /admin/banners`, `PATCH /admin/banners/:id`, `POST /admin/banners/:id/status` 🔒 | admin | Criar, editar, pausar |
| `GET/POST /admin/coupons`, `POST /admin/coupons/:code/active` 🔒 | admin | Cupom: 1 a 50%, limite de usos, validade, “só primeira compra”, quem paga (plataforma ou vendedor). Um uso por pessoa |

## Devoluções e disputas
| Método e rota | Quem | Descrição |
|---|---|---|
| `POST /returns` 🔒 | comprador | `{orderId, reason: arrependimento\|defeito\|diferente\|nao_recebido\|outro, description, items?}`. Arrependimento: até 7 dias após o recebimento; demais: até 30 dias; “não recebido” só depois do prazo de entrega + 7 dias. Reembolso calculado com os descontos proporcionais |
| `GET /returns` 🔒 | comprador | Minhas solicitações |
| `POST /returns/:id/dispute` 🔒 | comprador | `{text}` — só depois de uma recusa |
| `GET /seller/returns`, `POST /seller/returns/:id/approve`, `/reject {motivo}`, `/received` 🔒 | vendedor | Ao confirmar o recebimento do produto, o reembolso é feito |
| `GET /admin/disputes`, `POST /admin/returns/:id/resolve {decision, motivo}` 🔒 | admin | Decide a disputa (aprovar reembolsa) |

## Notificações, fotos e administração
| Método e rota | Quem | Descrição |
|---|---|---|
| `GET /notifications`, `POST /notifications/:id/read`, `POST /notifications/read-all` 🔒 | todos | Caixa de avisos (pedido pago, enviado, entregue, cancelado, devoluções). Eventos importantes também vão por e-mail |
| `POST /seller/uploads/product-image` 🔒 | vendedor | `{contentType: image/jpeg\|png\|webp, sizeBytes ≤ 5 MB}` → URL assinada (5 min) para enviar a foto direto ao armazenamento + `imageUrl` final. O arquivo não passa pela API |
| `GET /admin/orders?status=`, `GET /admin/audit?categoria=`, `GET /admin/metrics` 🔒 | admin | Pedidos, auditoria (Contas, Segurança, Vendedores, Pedidos) e indicadores de 30 dias |

## Indexação
A API grava a intenção em `search_outbox`; um worker aplica no OpenSearch (entrega pelo menos uma vez, operações idempotentes). A busca pode ficar alguns segundos atrás da escrita.
