Vendaweb-portal/docs/SHOPPING_CART_GUIDE.md

390 lines
12 KiB
Markdown

# 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)
```typescript
{
"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
```typescript
import { useCart } from './src/hooks/useCart';
```
### Uso Básico
```typescript
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
```typescript
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.
```typescript
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`.
```typescript
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.
```typescript
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.
```typescript
// 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.
```typescript
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).
```typescript
// 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]`:
```typescript
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
```typescript
// 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:
```powershell
# 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
```typescript
// 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