9.2 KiB
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
// 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
// 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):
// 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
// 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
// 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
// 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
// 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:
payment_method: { type: 'pix', installments: 1 }
Divergência 4 🟡 — Auth: Resposta 401 Não Limpa a Sessão
Comportamento atual do frontend
// 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:
- Limpar o token do localStorage
- Zerar o token no
apiClient - 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
// 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:
// 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
// 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 |