persist middleware. Checkout routes the order through either a formatted WhatsApp message or Mercado Pago payment preference. Loyalty points are automatically calculated and awarded on every completed order.
Cart State
The fullCartState interface from src/stores/cart.store.ts:
Persistence
localStorage key
vsm-cart — stored as JSON by Zustand persist middleware.Persisted fields
Only
items is persisted. isOpen, bundleOffer are always reset to defaults on page load.version: 2). If the persisted data is from version < 2, the cart is cleared during hydration to avoid stale Product objects with missing fields:
storage events to synchronize the cart across multiple open browser tabs.
Cart Actions
addItem(product, quantity?, variant?)
addItem(product, quantity?, variant?)
Rejects silently if
!product.is_active or product.status === 'discontinued'.If the product+variant combination already exists in the cart, increments quantity (clamped to product.stock). Otherwise appends a new CartItem.Fires trackAddToCart(product, quantity) (GA4 add_to_cart event) asynchronously via dynamic import.removeItem(productId, variantId?)
removeItem(productId, variantId?)
Removes the item matching both
productId and variantId from items[]. Variant-aware — removing a base product does not remove its variants.updateQuantity(productId, quantity, variantId?)
updateQuantity(productId, quantity, variantId?)
Clamps the new quantity to
Math.min(quantity, item.product.stock). If quantity <= 0, delegates to removeItem().loadOrderItems(items)
loadOrderItems(items)
Replaces the entire cart with items from a previous order. Used by the re-order feature on the Order History page (
/orders). Preserves variant_id and variant_name from historical order items.validateCart()
validateCart()
Fetches current product data via
getProductsByIds() and checks each cart item for:- Removed / deactivated products → removes from cart, records
type: 'removed' - Out of stock → removes from cart, records
type: 'out_of_stock' - Price changes → updates product reference, records
type: 'price_changed' - Stock adjustments → clamps quantity, records
type: 'stock_adjusted'
CartValidationResult { issues: CartValidationIssue[], hasIssues: boolean }.Cart Item and Types
Checkout Flow
CartSidebar
Rendered as a slide-in panel (
src/components/cart/CartSidebar.tsx). Displays items, subtotal, and a CTA to proceed to checkout. Accessible via openCart() / toggleCart().Checkout page
/checkout renders <CheckoutForm /> (src/components/cart/CheckoutForm.tsx). Collects customerName, customerPhone, deliveryType, address, and paymentMethod.Coupon validation
validateCoupon(code) from coupons.service.ts checks the coupon against the coupons table (min purchase, max uses, active status). A valid coupon returns discount amount.Payment Methods
- WhatsApp
- Mercado Pago
- Cash / Transfer
When After the WhatsApp window opens,
paymentMethod === 'whatsapp', SITE_CONFIG.orderWhatsApp.generateMessage(order) builds a formatted message and opens https://wa.me/{number}?text={encodedMessage}.markWhatsAppSent(orderId) is called to record whatsapp_sent: true and whatsapp_sent_at on the order.Coupon Validation
coupons table stores:
code— unique coupon stringdiscount_type: 'fixed' | 'percent'discount_value— amount or percentagemin_purchase— minimum cart subtotal requiredmax_uses— maximum redemption count
Loyalty Points on Checkout
Loyalty points are awarded automatically insidecreateOrder() without any customer action required:
src/lib/domain/loyalty.ts:
$100 MXN = 10 loyalty pointsIf
addLoyaltyPoints fails, the error is logged but does not throw — the order succeeds regardless.
GA4 Tracking
src/lib/analytics.ts:
view_item— fires on product detail page loadbegin_checkout— fires when checkout form openspurchase— fires on successful order creation
