# Divergências Frontend ↔ Backend — SaveInMed

> Análise realizada em 25/02/2026
> Branch analisada: `chore/monorepo-cleanup-and-structure`

---

## Resumo Executivo

| # | Fluxo | Severidade | Impacto |
|---|-------|-----------|---------|
| 1 | Cálculo de Frete — request body | 🔴 CRÍTICO | Frontend e backend são completamente incompatíveis; nenhuma requisição de cálculo de frete funciona |
| 2 | Cálculo de Frete — response body | 🔴 CRÍTICO | Frontend lê campos que não existem na resposta do backend |
| 3 | Criação de Pedido — `payment_method` | 🔴 CRÍTICO | Frontend envia `string`, backend espera `{type, installments}`; campo ignorado silenciosamente |
| 4 | Auth 401 — limpeza de sessão | 🟡 MÉDIO | 401 loga aviso mas não invalida token local; usuário fica preso com sessão expirada |
| 5 | Criação de Pedido — `buyer_id` | 🟢 BAIXO | Frontend envia `buyer_id` no body, mas backend usa JWT claims; campo extra inofensivo |
| 6 | Paginação de pedidos | 🟢 BAIXO | Frontend chama `listOrders()` sem parâmetros de paginação; backend usa padrão page=1/size=20 |

---

## Divergência 1 🔴 — Cálculo de Frete: Request Body

### O que o Frontend envia
```typescript
// frontend/src/services/shippingService.ts
POST /api/v1/shipping/calculate
{
  buyer_id: string,          // ← NÃO existe no backend
  order_total_cents: number, // ← backend chama de: cart_total_cents
  items: [                   // ← NÃO existe no backend
    {
      seller_id: string,
      product_id: string,
      quantity: number,
      price_cents: number
    }
  ]
}
```

### O que o Backend espera
```go
// backend/internal/http/handler/dto.go
type shippingCalculateRequest struct {
  VendorID       uuid.UUID  `json:"vendor_id"`               // ← NÃO enviado pelo frontend
  CartTotalCents int64      `json:"cart_total_cents"`         // ← frontend usa: order_total_cents
  BuyerLatitude  *float64   `json:"buyer_latitude,omitempty"` // ← NÃO enviado pelo frontend
  BuyerLongitude *float64   `json:"buyer_longitude,omitempty"`// ← NÃO enviado pelo frontend
  AddressID      *uuid.UUID `json:"address_id,omitempty"`
  PostalCode     string     `json:"postal_code,omitempty"`
}
```

### Efeito em Produção
O backend rejeita todas as requisições com `400 Bad Request: "vendor_id is required"` pois o campo `vendor_id` nunca é enviado. O cálculo de frete **jamais funciona**.

### Correção sugerida (duas opções)
**Opção A — Corrigir o frontend** (menor esforço):
```typescript
// Substituir CalculateShippingRequest no frontend por:
interface CalculateShippingRequest {
  vendor_id: string
  cart_total_cents: number
  buyer_latitude: number
  buyer_longitude: number
}
```

**Opção B — Adaptar o backend** para aceitar o formato atual do frontend (mais trabalho).

---

## Divergência 2 🔴 — Cálculo de Frete: Response Body

### O que o Frontend espera receber
```typescript
// frontend/src/services/shippingService.ts
interface CalculateShippingResponse {
  options: {
    seller_id: string          // ← campo ausente na resposta do backend
    delivery_fee_cents: number // ← backend retorna: value_cents
    distance_km: number        // ← campo ausente na resposta do backend
    estimated_days: number     // ← backend retorna: estimated_minutes (em minutos!)
    pickup_available: boolean  // ← campo ausente; backend usa type="pickup" para indicar
    pickup_address?: string
    pickup_hours?: string
  }[]
}
```

### O que o Backend realmente retorna
```go
// backend/internal/http/handler/shipping_handler.go + domain.ShippingOption
[]domain.ShippingOption{
  {
    Type:             "delivery" | "pickup",
    Description:      "Entrega padrão" | "Frete Grátis",
    ValueCents:       fee,          // ← frontend espera: delivery_fee_cents
    EstimatedMinutes: 120,          // ← hardcoded; frontend espera: estimated_days
  }
}
```

### Efeito em Produção
O frontend sempre exibe `undefined` para o valor do frete, distância e prazo estimado.

### Correção sugerida
Padronizar o response do backend para incluir todos os campos esperados pelo frontend, ou criar um adapter no frontend que converta `ShippingOption` para `CalculateShippingResponse`.

---

## Divergência 3 🔴 — Criação de Pedido: `payment_method`

### O que o Frontend envia
```typescript
// frontend/src/services/ordersService.ts
interface CreateOrderRequest {
  // ...
  payment_method: 'pix' | 'credit_card' | 'debit_card'  // ← string direta
}

// Exemplo real:
{ payment_method: "pix" }
```

### O que o Backend espera
```go
// backend/internal/http/handler/dto.go
type createOrderRequest struct {
  // ...
  PaymentMethod orderPaymentMethod `json:"payment_method"`  // ← objeto!
}

type orderPaymentMethod struct {
  Type         string `json:"type"`
  Installments int    `json:"installments"`
}

// Exemplo esperado:
{ "payment_method": { "type": "pix", "installments": 1 } }
```

### O que acontece em produção
O JSON decoder do Go não falha explicitamente (campo incompatível → zero value), então `PaymentMethod.Type` fica vazio (`""`). O pedido é criado sem método de pagamento definido, causando falhas silenciosas no processamento de pagamento.

### Correção sugerida
Unificar o contrato. **Recomendado: corrigir o frontend** para enviar objeto:
```typescript
payment_method: { type: 'pix', installments: 1 }
```

---

## Divergência 4 🟡 — Auth: Resposta 401 Não Limpa a Sessão

### Comportamento atual do frontend
```typescript
// frontend/src/services/apiClient.ts
instance.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      logger.warn('Sessão expirada, por favor, faça login novamente.')
      // ← NÃO limpa o token, NÃO redireciona, NÃO chama logout
    }
    return Promise.reject(error)
  }
)
```

### Comportamento esperado
Quando o backend retorna 401:
1. Limpar o token do localStorage
2. Zerar o token no `apiClient`
3. Redirecionar o usuário para `/login`

### Efeito em Produção
Usuário com token expirado continua fazendo requisições que falham com 401 indefinidamente até recarregar a página manualmente.

### Correção sugerida
```typescript
// No interceptor de response do apiClient:
if (error.response?.status === 401) {
  apiClient.setToken(null)
  localStorage.removeItem('mp-auth-user')
  window.location.href = '/login'
}
```

---

## Divergência 5 🟢 — Order: `buyer_id` enviado mas ignorado

### Comportamento
O frontend envia `buyer_id` no body do pedido, mas o backend extrai o comprador do JWT:
```go
// backend/internal/http/handler/order_handler.go
claims, ok := middleware.GetClaims(r.Context())
order.BuyerID = *claims.CompanyID  // ← usa JWT, ignora o buyer_id do body
```

### Risco
Baixo. O backend usa sempre o JWT (mais seguro). O campo extra no body é inofensivo.
Porém, cria confusão e falsa sensação de que o `buyer_id` no body tem efeito.

---

## Divergência 6 🟢 — Paginação de Pedidos: Sem Parâmetros

### Comportamento
```typescript
// frontend/src/services/ordersService.ts
listOrders: () => apiClient.get('/v1/orders')
// ← sem page, page_size, role
```

O backend suporta `?page=N&page_size=M&role=buyer|seller` mas o frontend nunca envia.
Resultado: sempre retorna a primeira página com 20 itens.

---

## Mapeamento de Campos Corretos

### Shipping Settings (✅ alinhados)
| Campo Frontend | Campo Backend | OK? |
|---|---|---|
| `vendor_id` | `vendor_id` | ✅ |
| `active` | `active` | ✅ |
| `max_radius_km` | `max_radius_km` | ✅ |
| `price_per_km_cents` | `price_per_km_cents` | ✅ |
| `min_fee_cents` | `min_fee_cents` | ✅ |
| `pickup_active` | `pickup_active` | ✅ |
| `pickup_address` | `pickup_address` | ✅ |
| `pickup_hours` | `pickup_hours` | ✅ |

### Auth (✅ alinhados)
| Campo Frontend | Campo Backend | OK? |
|---|---|---|
| `username` | `username` | ✅ |
| `password` | `password` | ✅ |
| `access_token` | `access_token` | ✅ |
| `expires_at` | `expires_at` (time.Time → RFC3339) | ✅ |

### Shipping Calculate Request (🔴 divergentes)
| Campo Frontend | Campo Backend | OK? |
|---|---|---|
| `buyer_id` | — (não existe) | 🔴 |
| `order_total_cents` | `cart_total_cents` | 🔴 nome diferente |
| `items[]` | — (não existe) | 🔴 |
| — | `vendor_id` (obrigatório) | 🔴 não enviado |
| — | `buyer_latitude` (obrigatório) | 🔴 não enviado |
| — | `buyer_longitude` (obrigatório) | 🔴 não enviado |

### Shipping Calculate Response (🔴 divergentes)
| Campo Resposta Backend | Campo Esperado Frontend | OK? |
|---|---|---|
| `type` | — | — |
| `description` | — | — |
| `value_cents` | `delivery_fee_cents` | 🔴 nome diferente |
| `estimated_minutes` | `estimated_days` | 🔴 nome e unidade diferentes |
| — | `seller_id` | 🔴 ausente |
| — | `distance_km` | 🔴 ausente |
| — | `pickup_available` | 🔴 ausente |

### Order Create (🔴 divergente em payment_method)
| Campo Frontend | Campo Backend | OK? |
|---|---|---|
| `seller_id` | `seller_id` | ✅ |
| `items[]` | `items[]` (domain.OrderItem) | ✅ |
| `shipping.recipient_name` | `shipping.recipient_name` | ✅ |
| `shipping.street` | `shipping.street` | ✅ |
| `payment_method: "pix"` | `payment_method: {type, installments}` | 🔴 tipo errado |
| `buyer_id` | ignorado (usa JWT) | 🟡 inofensivo |
