From 285f5863716b431a6f10681e8c33d764f86014e9 Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Fri, 26 Dec 2025 23:08:55 -0300 Subject: [PATCH] fix: cart price display showing cents as reais (R19 instead of R.19) Root cause: Cart.tsx used formatCurrency(819) -> '819,00' instead of formatCents(819) -> 'R$ 8,19' Changes: - Cart.tsx: Replace formatCurrency with formatCents at all 5 price display points - Cart.tsx: Add debug logging (logPrice) to trace price values - cartStore.ts: Add debug logging for addItem, updateQuantity, etc. - cartStore.ts: Document unitPrice as CENTS in interface - cartStore.test.ts: Add 7 new price conversion tests for cents handling All 95 tests pass. --- marketplace/src/pages/Cart.tsx | 28 ++++- marketplace/src/stores/cartStore.test.ts | 126 +++++++++++++++++++++++ marketplace/src/stores/cartStore.ts | 41 ++++++-- 3 files changed, 183 insertions(+), 12 deletions(-) diff --git a/marketplace/src/pages/Cart.tsx b/marketplace/src/pages/Cart.tsx index a2b9019..81cd0a3 100644 --- a/marketplace/src/pages/Cart.tsx +++ b/marketplace/src/pages/Cart.tsx @@ -2,7 +2,17 @@ import { ShoppingBasket } from 'lucide-react' import { Link } from 'react-router-dom' import { Shell } from '../layouts/Shell' import { selectGroupedCart, selectCartSummary, useCartStore } from '../stores/cartStore' -import { formatCurrency } from '../utils/format' +import { formatCents } from '../utils/format' + +// Debug logging for price values +const logPrice = (label: string, value: number | undefined) => { + console.log(`%c🛒 [Cart] ${label}:`, 'color: #10B981; font-weight: bold', { + rawValue: value, + inCents: value, + inReais: value ? (value / 100).toFixed(2) : '0.00', + formatted: value ? formatCents(value) : 'N/A' + }) +} export function CartPage() { const items = useCartStore((state) => state.items) @@ -27,7 +37,9 @@ export function CartPage() { {!isEmpty && (

Itens: {summary.totalItems}

-

R$ {formatCurrency(summary.totalValue)}

+ {/* Log total value */} + {(() => { logPrice('Total do carrinho', summary.totalValue); return null })()} +

{formatCents(summary.totalValue)}

)} @@ -81,7 +93,9 @@ export function CartPage() {

Preço

-

R$ {formatCurrency(item.unitPrice)}

+ {/* Log unit price */} + {(() => { logPrice(`Preço unitário ${item.name}`, item.unitPrice); return null })()} +

{formatCents(item.unitPrice)}

Subtotal

+ {/* Log subtotal */} + {(() => { logPrice(`Subtotal ${item.name} (${item.quantity}x)`, item.quantity * item.unitPrice); return null })()}

- R$ {formatCurrency(item.quantity * item.unitPrice)} + {formatCents(item.quantity * item.unitPrice)}

diff --git a/marketplace/src/stores/cartStore.test.ts b/marketplace/src/stores/cartStore.test.ts index 7161cef..77ebab2 100644 --- a/marketplace/src/stores/cartStore.test.ts +++ b/marketplace/src/stores/cartStore.test.ts @@ -198,3 +198,129 @@ describe('selectors', () => { }) }) }) + +/** + * PRICE CONVERSION TESTS + * These tests verify that prices are handled correctly in CENTS + * The API returns price_cents (e.g., 819 = R$ 8,19) + */ +describe('price conversion (cents)', () => { + const mockItemCents: Omit = { + id: 'prod-cents-1', + name: 'Paracetamol 750mg', + activeIngredient: 'Paracetamol', + lab: 'EMS', + batch: 'L2025840', + expiry: '2027-09-15', + vendorId: 'vendor-anapolis', + vendorName: 'Anápolis/GO', + unitPrice: 819 // 819 cents = R$ 8,19 + } + + const mockItemCents2: Omit = { + id: 'prod-cents-2', + name: 'Dipirona 500mg', + activeIngredient: 'Dipirona', + lab: 'Medley', + batch: 'L2025123', + expiry: '2026-06-30', + vendorId: 'vendor-anapolis', + vendorName: 'Anápolis/GO', + unitPrice: 1299 // 1299 cents = R$ 12,99 + } + + beforeEach(() => { + useCartStore.setState({ items: [] }) + }) + + describe('storing prices in cents', () => { + it('should store unitPrice as cents (819 = R$ 8,19)', () => { + useCartStore.getState().addItem(mockItemCents) + + const state = useCartStore.getState() + expect(state.items[0].unitPrice).toBe(819) + // Verify it's NOT stored as reais (8.19) + expect(state.items[0].unitPrice).not.toBe(8.19) + }) + + it('should preserve cents precision for subtotal calculation', () => { + useCartStore.getState().addItem(mockItemCents, 3) + + const state = useCartStore.getState() + const item = state.items[0] + const subtotal = item.quantity * item.unitPrice + + // 3 * 819 = 2457 cents = R$ 24,57 + expect(subtotal).toBe(2457) + expect(subtotal / 100).toBe(24.57) + }) + }) + + describe('cents calculations in selectors', () => { + it('should calculate vendor total in cents', () => { + // 819 cents * 2 = 1638 cents + useCartStore.getState().addItem(mockItemCents, 2) + // 1299 cents * 1 = 1299 cents + useCartStore.getState().addItem(mockItemCents2, 1) + + const state = useCartStore.getState() + const groups = selectGroupedCart(state) + + // Total: 1638 + 1299 = 2937 cents = R$ 29,37 + expect(groups['vendor-anapolis'].total).toBe(2937) + expect(groups['vendor-anapolis'].total / 100).toBeCloseTo(29.37) + }) + + it('should calculate cart summary total in cents', () => { + useCartStore.getState().addItem(mockItemCents, 2) // 1638 cents + useCartStore.getState().addItem(mockItemCents2, 1) // 1299 cents + + const state = useCartStore.getState() + const summary = selectCartSummary(state) + + expect(summary.totalItems).toBe(3) + expect(summary.totalValue).toBe(2937) // cents + expect(summary.totalValue / 100).toBeCloseTo(29.37) // reais + }) + }) + + describe('edge cases for cents', () => { + it('should handle single unit correctly', () => { + useCartStore.getState().addItem(mockItemCents, 1) + + const state = useCartStore.getState() + const summary = selectCartSummary(state) + + expect(summary.totalValue).toBe(819) + expect(summary.totalValue / 100).toBe(8.19) + }) + + it('should handle large quantities without overflow', () => { + useCartStore.getState().addItem(mockItemCents, 1000) + + const state = useCartStore.getState() + const summary = selectCartSummary(state) + + // 819 * 1000 = 819000 cents = R$ 8,190.00 + expect(summary.totalValue).toBe(819000) + expect(summary.totalValue / 100).toBe(8190) + }) + + it('should handle small cent values (R$ 0,01)', () => { + const cheapItem: Omit = { + ...mockItemCents, + id: 'cheap-1', + unitPrice: 1 // 1 cent = R$ 0,01 + } + + useCartStore.getState().addItem(cheapItem, 10) + + const state = useCartStore.getState() + const summary = selectCartSummary(state) + + expect(summary.totalValue).toBe(10) // 10 cents + expect(summary.totalValue / 100).toBe(0.10) + }) + }) +}) + diff --git a/marketplace/src/stores/cartStore.ts b/marketplace/src/stores/cartStore.ts index bea9d87..73a4194 100644 --- a/marketplace/src/stores/cartStore.ts +++ b/marketplace/src/stores/cartStore.ts @@ -10,7 +10,7 @@ export interface CartItem { expiry: string vendorId: string vendorName: string - unitPrice: number + unitPrice: number // Price in CENTS (e.g., 819 = R$ 8,19) quantity: number } @@ -23,14 +23,31 @@ interface CartState { clearAll: () => void } +// Debug logging for cart operations +const logCartOp = (operation: string, data: Record) => { + console.log(`%c🛒 [CartStore] ${operation}`, 'color: #8B5CF6; font-weight: bold', data) +} + export const useCartStore = create()( persist( (set, get) => ({ items: [], addItem: (item, quantity = 1) => { + // Log the incoming item with price details + logCartOp('addItem', { + id: item.id, + name: item.name, + unitPrice: item.unitPrice, + unitPriceInReais: (item.unitPrice / 100).toFixed(2), + quantity, + subtotalCents: item.unitPrice * quantity, + subtotalReais: ((item.unitPrice * quantity) / 100).toFixed(2) + }) + set((state) => { const exists = state.items.find((i) => i.id === item.id) if (exists) { + logCartOp('addItem - updating existing', { existingQty: exists.quantity, newQty: exists.quantity + quantity }) return { items: state.items.map((i) => i.id === item.id ? { ...i, quantity: i.quantity + quantity } : i @@ -40,14 +57,24 @@ export const useCartStore = create()( return { items: [...state.items, { ...item, quantity }] } }) }, - updateQuantity: (id, quantity) => + updateQuantity: (id, quantity) => { + logCartOp('updateQuantity', { id, newQuantity: quantity }) set((state) => ({ items: state.items.map((i) => (i.id === id ? { ...i, quantity } : i)) - })), - removeItem: (id) => set((state) => ({ items: state.items.filter((i) => i.id !== id) })), - clearVendor: (vendorId) => - set((state) => ({ items: state.items.filter((i) => i.vendorId !== vendorId) })), - clearAll: () => set({ items: [] }) + })) + }, + removeItem: (id) => { + logCartOp('removeItem', { id }) + set((state) => ({ items: state.items.filter((i) => i.id !== id) })) + }, + clearVendor: (vendorId) => { + logCartOp('clearVendor', { vendorId }) + set((state) => ({ items: state.items.filter((i) => i.vendorId !== vendorId) })) + }, + clearAll: () => { + logCartOp('clearAll', {}) + set({ items: [] }) + } }), { name: 'mp-cart-storage',