Vendaweb-portal/docs/SHOPPING_CART_GUIDE.md

12 KiB

Guia de Uso do Carrinho de Compras - VendaWeb React

📋 Visão Geral

Este documento descreve como funciona o sistema de carrinho de compras no frontend React, baseado na implementação funcional do portal Angular. O guia cobre desde a adição de itens até a gestão completa do carrinho.

🏗️ Arquitetura

vendaweb_react/
├── src/
│   ├── hooks/
│   │   └── useCart.ts              # Hook customizado para gerenciar estado do carrinho
│   ├── services/
│   │   └── shopping.service.ts     # Serviço para interações com a API do carrinho
│   └── contexts/
│       └── AuthContext.tsx         # Contexto de autenticação (necessário para token)
├── components/
│   └── CartDrawer.tsx              # Componente visual do carrinho
└── views/
    └── CheckoutView.tsx            # View de checkout

🔑 Conceitos Fundamentais

1. ID do Carrinho (idCart)

  • Tipo: string | null
  • Armazenamento: localStorage.getItem("cart")
  • Comportamento:
    • Quando null: Backend cria um novo carrinho e retorna o idCart gerado
    • Quando existe: Backend adiciona o item ao carrinho existente
  • IMPORTANTE: Sempre enviar idCart: null (não string vazia) quando não há carrinho

2. ID do Item (id)

  • Tipo: string | null
  • Comportamento:
    • Quando null: Backend cria um novo item e retorna o id (UUID) gerado
    • Quando existe: Backend atualiza o item existente
  • IMPORTANTE: Sempre enviar id: null para novos itens

3. Fluxo de Adição de Item

1. Usuário seleciona produto
   ↓
2. productToShoppingItem() converte Product → ShoppingItem
   ↓
3. createItemShopping() envia POST /shopping/item
   ↓
4. Backend cria/atualiza carrinho e retorna idCart
   ↓
5. Frontend salva idCart no localStorage
   ↓
6. useCart hook recarrega itens do carrinho

📝 Estrutura do Payload

Payload para Adicionar Item (POST /shopping/item)

{
  "id": null,                    // Sempre null para novos itens
  "idCart": null,               // null se não há carrinho, UUID se existe
  "invoiceStore": "4",          // Código da loja
  "idProduct": 25960,          // ID do produto (número)
  "description": "Nome do Produto",
  "image": "http://...",        // URL da imagem ou "" se não houver
  "productType": "S",           // Tipo do produto
  "percentUpQuantity": 0,       // Sempre 0
  "upQuantity": 0,              // Sempre 0
  "quantity": 1,                // Quantidade
  "price": 20.99,               // Preço de venda
  "deliveryType": "EN",         // Tipo de entrega
  "stockStore": "4",            // Código da loja de estoque
  "seller": 1,                  // ID do vendedor
  "discount": 0,                // Desconto percentual (sempre 0 inicialmente)
  "discountValue": 0,           // Valor do desconto (sempre 0 inicialmente)
  "ean": 7895024019601,         // Código EAN (ou idProduct se não houver)
  "promotion": 20.99,           // Preço promocional (0 se não houver promoção)
  "listPrice": 33.9,            // Preço de tabela
  "userDiscount": null,         // Sempre null
  "mutiple": 1,                 // Múltiplo de venda
  "auxDescription": null,       // Descrição auxiliar (cor para tintométrico)
  "smallDescription": "#ARG...", // Descrição curta (NÃO usar description como fallback)
  "brand": "PORTOKOLL",         // Marca
  "base": "N",                  // Base tintométrica (S/N)
  "line": null,                 // Linha tintométrica
  "can": null,                  // Lata
  "letter": null                // Letra tintométrica
}

Regras Importantes

  1. id e idCart: Sempre presentes no payload, mesmo que sejam null
  2. smallDescription: Usar apenas o campo smallDescription do produto (não usar description como fallback)
  3. promotion:
    • Se produto tem promotion > 0: usar esse valor
    • Se price < listPrice: usar price como promotion
    • Caso contrário: usar 0
  4. image: String vazia "" quando não há imagem (não null)
  5. color: Remover do payload se for null (não incluir o campo)

🔧 Uso do Hook useCart

Importação

import { useCart } from './src/hooks/useCart';

Uso Básico

const {
  cart,              // OrderItem[] - Itens do carrinho
  cartId,            // string | null - ID do carrinho
  isLoading,         // boolean - Estado de carregamento
  error,             // string | null - Mensagem de erro
  addToCart,         // (product: Product | OrderItem) => Promise<void>
  updateQuantity,    // (id: string, delta: number) => Promise<void>
  removeFromCart,    // (id: string) => Promise<void>
  refreshCart,       // () => Promise<void>
  clearCart,         // () => void
} = useCart();

Exemplo Completo

import { useCart } from './src/hooks/useCart';
import { Product } from './types';

function ProductCard({ product }: { product: Product }) {
  const { addToCart, isLoading } = useCart();

  const handleAddToCart = async () => {
    try {
      await addToCart(product);
      // Item adicionado com sucesso
      // O hook automaticamente:
      // 1. Cria o item no backend
      // 2. Salva o idCart retornado no localStorage
      // 3. Recarrega os itens do carrinho
    } catch (error) {
      console.error('Erro ao adicionar item:', error);
    }
  };

  return (
    <button onClick={handleAddToCart} disabled={isLoading}>
      {isLoading ? 'Adicionando...' : 'Adicionar ao Carrinho'}
    </button>
  );
}

🛠️ Uso do Serviço shoppingService

Métodos Principais

1. createItemShopping(item: ShoppingItem): Promise<ShoppingItem>

Adiciona um item ao carrinho.

import { shoppingService } from './src/services/shopping.service';
import { Product } from './types';

// Converter Product para ShoppingItem
const shoppingItem = shoppingService.productToShoppingItem(product);

// Criar item no carrinho
const result = await shoppingService.createItemShopping(shoppingItem);

// O idCart será salvo automaticamente no localStorage
// Se result.idCart existir, será salvo

Comportamento:

  • Se item.idCart é null: Backend cria novo carrinho
  • Se item.idCart existe: Backend adiciona ao carrinho existente
  • Sempre retorna o idCart (novo ou existente)
  • Remove paymentPlan e billing do localStorage após sucesso

2. productToShoppingItem(product: Product | OrderItem): ShoppingItem

Converte um Product ou OrderItem para ShoppingItem.

const product: Product = {
  id: "123",
  code: "25960",
  name: "Produto Exemplo",
  price: 20.99,
  // ... outros campos
};

const shoppingItem = shoppingService.productToShoppingItem(product);
// Retorna ShoppingItem pronto para ser enviado ao backend

Regras de Conversão:

  • idProduct: Extraído de product.idProduct, product.id ou product.code
  • description: product.name ou product.description
  • smallDescription: Apenas product.smallDescription (sem fallback)
  • promotion: Calculado conforme regras acima
  • ean: product.ean ou idProduct como fallback
  • idCart: Obtido do localStorage.getItem("cart") (pode ser null)

3. updateQuantityItemShopping(item: ShoppingItem): Promise<void>

Atualiza a quantidade de um item no carrinho.

const item = cart.find(i => i.id === itemId);
const shoppingItem = shoppingService.productToShoppingItem({
  ...item,
  quantity: newQuantity
});
shoppingItem.id = item.id; // IMPORTANTE: Usar o UUID do item
shoppingItem.idCart = cartId;

await shoppingService.updateQuantityItemShopping(shoppingItem);

4. deleteItemShopping(id: string): Promise<void>

Remove um item do carrinho.

// IMPORTANTE: id deve ser o UUID do item (não idProduct)
await shoppingService.deleteItemShopping(itemId);

5. getShoppingItems(idCart: string): Promise<ShoppingItem[]>

Obtém todos os itens de um carrinho.

const cartId = shoppingService.getCart();
if (cartId) {
  const items = await shoppingService.getShoppingItems(cartId);
}

⚠️ Validações e Regras de Negócio

1. Produto Tintométrico

Produtos com base === "S" requerem auxDescription (cor selecionada).

// Validação automática em createItemShopping()
if (base === "S" && auxDescription === "") {
  throw new Error("Esse produto só pode ser adicionado com coloração selecionada");
}

2. IDs dos Itens

  • Novos itens: Sempre enviar id: null
  • Itens existentes: Usar o UUID retornado pelo backend
  • IMPORTANTE: Não usar idProduct como id do item do carrinho

3. ID do Carrinho

  • Primeiro item: idCart: null → Backend cria novo carrinho
  • Itens subsequentes: idCart: <UUID> → Backend adiciona ao carrinho existente
  • Armazenamento: Sempre salvar o idCart retornado no localStorage

🔍 Debugging

Logs do Serviço

O serviço gera logs detalhados com prefixo 🛒 [SHOPPING]:

console.log("🛒 [SHOPPING] createItemShopping: " + JSON.stringify(cleanItem));
console.log("🛒 [SHOPPING] Item criado com sucesso:", result);
console.log("🛒 [SHOPPING] idCart retornado:", result.idCart);

Verificar Estado do Carrinho

// No console do navegador
localStorage.getItem("cart"); // ID do carrinho
localStorage.getItem("token"); // Token de autenticação

Testar Requisição Manualmente

Use o script test_add_item.ps1 para testar a adição de itens via curl:

# 1. Obter token do localStorage
# 2. Editar test_add_item.ps1 com o token
# 3. Executar: .\test_add_item.ps1

🐛 Problemas Comuns e Soluções

Erro 400: "Erro ao criar item no carrinho de compras"

Causas possíveis:

  1. idCart sendo enviado como string vazia "" em vez de null
  2. smallDescription usando description como fallback (muito longo)
  3. promotion calculado incorretamente
  4. Campos obrigatórios faltando ou com valores inválidos

Solução: Verificar logs do console e comparar payload com o exemplo funcional.

Item não aparece no carrinho após adicionar

Causas possíveis:

  1. idCart não foi salvo no localStorage
  2. refreshCart() não foi chamado após adicionar item
  3. Hook useCart não está recarregando itens

Solução: Verificar se result.idCart foi salvo e se loadCartItems() foi chamado.

Erro ao remover item

Causas possíveis:

  1. id do item não é um UUID válido
  2. id está usando idProduct em vez do UUID do backend

Solução: Garantir que item.id seja o UUID retornado pelo backend, não idProduct.

📚 Referências

  • Backend API: POST /api/v1/shopping/item
  • Angular Reference: vendaweb_portal/src/app/sales/product-detail/product-detail.component.ts
  • Backend Service: vendaweb_api/src/sales/shopping/shopping.service.ts

Checklist de Implementação

Ao implementar funcionalidades de carrinho, verificar:

  • id e idCart sempre presentes no payload (mesmo que null)
  • smallDescription usa apenas campo do produto (sem fallback)
  • promotion calculado corretamente
  • idCart salvo no localStorage após criação
  • refreshCart() chamado após modificações
  • Validação de produto tintométrico implementada
  • IDs dos itens são UUIDs (não idProduct)
  • Tratamento de erros implementado
  • Logs de debug adicionados

🔄 Fluxo Completo de Exemplo

// 1. Usuário seleciona produto
const product: Product = await productService.getProductDetail(storeId, productId);

// 2. Adicionar ao carrinho usando hook
const { addToCart } = useCart();
await addToCart(product);

// 3. Hook internamente:
//    - Converte Product → ShoppingItem
//    - Envia POST /shopping/item com idCart: null (se primeiro item)
//    - Backend cria carrinho e retorna idCart
//    - Salva idCart no localStorage
//    - Recarrega itens do carrinho

// 4. Atualizar quantidade
const { updateQuantity } = useCart();
await updateQuantity(itemId, 1); // +1

// 5. Remover item
const { removeFromCart } = useCart();
await removeFromCart(itemId);

// 6. Limpar carrinho
const { clearCart } = useCart();
clearCart(); // Limpa estado e localStorage

Última atualização: 2026-01-06
Versão: 1.0
Autor: Sistema de Documentação Automática