# 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 |