Overview
Mis Compras implements a fully client-side shopping cart using localStorage for data persistence. The cart allows users to add products, manage quantities, view totals, and proceed to checkout.
Key Features
localStorage Persistence Cart data persists across browser sessions
Dynamic Updates Real-time cart updates without page refresh
Automatic Calculations Subtotals and totals calculated automatically
Item Management Add, remove, and update product quantities
Cart Implementation
Core Structure
The cart is implemented in carrito.js as an IIFE (Immediately Invoked Function Expression) that creates a global window.carrito API:
( function () {
const STORAGE_KEY = 'miTienda_carrito_v1' ;
function getCart () {
const raw = localStorage . getItem ( STORAGE_KEY );
try {
return raw ? JSON . parse ( raw ) : [];
} catch ( e ) {
console . error ( 'Error parseando carrito:' , e );
return [];
}
}
function saveCart ( cart ) {
localStorage . setItem ( STORAGE_KEY , JSON . stringify ( cart ));
}
function findIndex ( cart , id ) {
return cart . findIndex ( item => String ( item . id ) === String ( id ));
}
function parsePrice ( v ) {
const n = Number ( String ( v ). replace ( / [ ^ 0-9.- ] + / g , '' ));
return isNaN ( n ) ? 0 : n ;
}
// Global API exposed on window.carrito
window . carrito = {
addItem ( product ) { /* ... */ },
removeItem ( id ) { /* ... */ },
clear () { /* ... */ },
getItems: getCart ,
getTotal () { /* ... */ },
checkout ( extraData ) { /* ... */ }
};
})();
The cart uses localStorage key miTienda_carrito_v1 to store cart data as JSON.
Adding Items to Cart
Add Item Flow
Product data collected
Product ID, name, price, and image are gathered from button dataset
Check for existing item
Cart is searched for product with matching ID
Update or insert
If exists, quantity increases; if new, item is added
Save and render
Cart is saved to localStorage and UI updates
Implementation
window . carrito = {
addItem ( product ) {
const cart = getCart ();
const idx = findIndex ( cart , product . id );
const precio = parsePrice ( product . precio );
if ( idx > - 1 ) {
// Product exists - increment quantity
cart [ idx ]. cantidad += 1 ;
cart [ idx ]. subtotal = + ( cart [ idx ]. cantidad * cart [ idx ]. precio ). toFixed ( 2 );
} else {
// New product - add to cart
cart . push ({
id: String ( product . id ),
nombre: product . nombre ,
precio: + precio ,
imagen: product . imagen || '' ,
cantidad: 1 ,
subtotal: + precio
});
}
saveCart ( cart );
if ( typeof renderCart === 'function' ) renderCart ();
}
};
Products can be added to the cart by clicking buttons with class agregar-carrito:
document . addEventListener ( 'click' , ( e ) => {
const btn = e . target . closest ( '.agregar-carrito' );
if ( ! btn ) return ;
const { id , nombre , precio , imagen } = btn . dataset ;
window . carrito . addItem ({ id , nombre , precio , imagen });
btn . textContent = 'Añadido ✓' ;
setTimeout (() => ( btn . textContent = 'Agregar al Carrito' ), 900 );
});
< button
class = "agregar-carrito"
data-id = "1"
data-nombre = "iPhone 15 Pro"
data-precio = "999.99"
data-imagen = "iphone15.jpg"
>
Agregar al Carrito
</ button >
Displaying the Cart
Cart Item Structure
Each cart item contains:
{
id : "1" , // Product ID as string
nombre : "iPhone 15 Pro" ,
precio : 999.99 , // Unit price as number
imagen : "iphone15.jpg" ,
cantidad : 2 , // Quantity
subtotal : 1999.98 // precio × cantidad
}
Rendering Cart Items
The renderCart() function dynamically generates cart HTML:
window . renderCart = function () {
const lista = document . querySelector ( '.carrito-lista' );
const totalSpan = document . querySelector ( '.carrito-total span' );
if ( ! lista ) return ;
const cart = getCart ();
lista . innerHTML = '' ;
if ( ! cart . length ) {
lista . innerHTML = '<p>Tu carrito está vacío.</p>' ;
if ( totalSpan ) totalSpan . textContent = '$0.00' ;
return ;
}
cart . forEach ( item => {
const div = document . createElement ( 'div' );
div . className = 'carrito-item' ;
div . innerHTML = `
<img
class="carrito-imagen"
src=" ${ item . imagen } "
alt=" ${ item . nombre } "
onerror="this.onerror=null;this.src='imagenes/default-product.svg'"
>
<div class="carrito-info">
<h3> ${ item . nombre } </h3>
<p>$ ${ ( + item . precio ). toFixed ( 2 ) } </p>
<p>Cantidad: <span class="cantidad"> ${ item . cantidad } </span></p>
<p>Subtotal: $ ${ ( + item . subtotal ). toFixed ( 2 ) } </p>
</div>
<div class="carrito-acciones">
<button
class="carrito-eliminar"
data-id=" ${ item . id } "
title="Eliminar producto"
>
×
</button>
</div>
` ;
lista . appendChild ( div );
});
if ( totalSpan ) totalSpan . textContent = `$ ${ window . carrito . getTotal () } ` ;
};
Auto-render on Page Load
document . addEventListener ( 'DOMContentLoaded' , () => {
renderCart ();
});
Removing Items
Remove Item Implementation
window . carrito = {
removeItem ( id ) {
const cart = getCart (). filter ( p => String ( p . id ) !== String ( id ));
saveCart ( cart );
if ( typeof renderCart === 'function' ) renderCart ();
}
};
document . addEventListener ( 'click' , ( e ) => {
const btn = e . target . closest ( '.carrito-eliminar' );
if ( ! btn ) return ;
const id = btn . dataset . id ;
if ( ! id ) return ;
if ( confirm ( '¿Eliminar este producto del carrito?' )) {
window . carrito . removeItem ( id );
}
});
Calculating Totals
Get Cart Total
window . carrito = {
getTotal () {
return getCart (). reduce (( sum , i ) => sum + i . subtotal , 0 ). toFixed ( 2 );
}
};
The total is calculated by summing all item subtotals and formatting to 2 decimal places.
Usage Example
const total = window . carrito . getTotal ();
console . log ( `Total: $ ${ total } ` );
// Output: Total: $2999.97
Clearing the Cart
Clear Implementation
window . carrito = {
clear () {
localStorage . removeItem ( STORAGE_KEY );
if ( typeof renderCart === 'function' ) renderCart ();
}
};
The cart is automatically cleared after successful checkout.
Checkout Integration
Checkout Flow
Validate cart
Ensure cart is not empty
Prepare payload
Collect cart items, total, and user information
Submit to server
POST request to checkout endpoint
Handle response
Clear cart on success and redirect to thank you page
Checkout Implementation
window . carrito = {
async checkout ( extraData = {}) {
const cart = getCart ();
if ( ! cart . length ) {
alert ( 'Tu carrito está vacío.' );
return ;
}
const usuario_id = localStorage . getItem ( "usuario_id" , 1 ) || null ;
const total = this . getTotal ();
const payload = {
usuario_id ,
items: cart ,
total ,
direccion: extraData . direccion || null
};
try {
const resp = await fetch ( 'php/checkout.php' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( payload )
});
const data = await resp . json ();
if ( data . success ) {
this . clear ();
// Success modal
const modal = document . createElement ( 'div' );
modal . innerHTML = `
<div style="
position:fixed;top:0;left:0;width:100%;height:100%;
background:rgba(0,0,0,0.6);
display:flex;align-items:center;justify-content:center;
z-index:9999;">
<div style="
background:#fff;padding:40px 50px;border-radius:16px;
text-align:center;box-shadow:0 5px 15px rgba(0,0,0,0.3);
font-family:'Poppins',sans-serif;">
<div style="font-size:60px;">🎉</div>
<h2 style="color:#2e7d32;">¡Compra realizada con éxito!</h2>
<p style="color:#444;">Tu pedido ha sido registrado correctamente.</p>
<p style="font-size:14px;color:#666;">Serás redirigido en unos segundos...</p>
</div>
</div>` ;
document . body . appendChild ( modal );
setTimeout (() => {
window . location . href = 'gracias.html' ;
}, 3000 );
} else {
alert ( '⚠️ No se pudo completar la compra: ' + ( data . message || 'Error desconocido.' ));
}
} catch ( e ) {
console . error ( 'Error en checkout:' , e );
alert ( '⚠️ Error de conexión con el servidor.' );
}
}
};
Triggering Checkout
document . addEventListener ( 'DOMContentLoaded' , () => {
const btnFinalizar = document . querySelector ( '.carrito-btn' );
if ( btnFinalizar ) {
btnFinalizar . addEventListener ( 'click' , () => carrito . checkout ());
}
});
< button class = "carrito-btn" > Finalizar Compra </ button >
localStorage Structure
The cart is stored as a JSON array:
[
{
"id" : "1" ,
"nombre" : "iPhone 15 Pro" ,
"precio" : 999.99 ,
"imagen" : "iphone15.jpg" ,
"cantidad" : 2 ,
"subtotal" : 1999.98
},
{
"id" : "2" ,
"nombre" : "MacBook Pro M3" ,
"precio" : 1599.99 ,
"imagen" : "macbook.jpg" ,
"cantidad" : 1 ,
"subtotal" : 1599.99
}
]
Checkout Payload
When sent to the server:
{
"usuario_id" : 17 ,
"total" : "3599.97" ,
"direccion" : null ,
"items" : [
{
"id" : "1" ,
"nombre" : "iPhone 15 Pro" ,
"precio" : 999.99 ,
"cantidad" : 2 ,
"subtotal" : 1999.98 ,
"imagen" : "iphone15.jpg"
}
]
}
Price Parsing
The parsePrice() function normalizes price strings:
function parsePrice ( v ) {
const n = Number ( String ( v ). replace ( / [ ^ 0-9.- ] + / g , '' ));
return isNaN ( n ) ? 0 : n ;
}
Examples:
"$999.99" → 999.99
"1,599.99" → 1599.99
"999" → 999
"invalid" → 0
All prices are stored as numbers in the cart for accurate calculations. String conversion only happens during display.
API Reference
window.carrito.addItem(product)
Add a product to the cart or increment its quantity.
Parameters:
product (object): Product data
id (string|number): Product ID
nombre (string): Product name
precio (string|number): Product price
imagen (string): Image filename
Example:
window . carrito . addItem ({
id: 1 ,
nombre: "iPhone 15 Pro" ,
precio: "999.99" ,
imagen: "iphone15.jpg"
});
window.carrito.removeItem(id)
Remove a product from the cart by ID.
Parameters:
id (string|number): Product ID
Example:
window . carrito . removeItem ( 1 );
window.carrito.getItems()
Get all items currently in the cart.
Returns: Array of cart item objects
Example:
const items = window . carrito . getItems ();
console . log ( `Cart has ${ items . length } items` );
window.carrito.getTotal()
Calculate the total price of all items in cart.
Returns: String with 2 decimal places
Example:
const total = window . carrito . getTotal ();
document . querySelector ( '.total' ). textContent = `$ ${ total } ` ;
window.carrito.clear()
Remove all items from the cart.
Example:
if ( confirm ( 'Clear entire cart?' )) {
window . carrito . clear ();
}
Process checkout and create order.
Parameters:
extraData (object, optional): Additional order data
direccion (string): Shipping address
Returns: Promise that resolves when checkout completes
Example:
await window . carrito . checkout ({
direccion: "123 Main St, City, State 12345"
});
Best Practices
Error Handling Always handle JSON parse errors when reading from localStorage
Price Formatting Use .toFixed(2) for all price displays to ensure consistency
Image Fallbacks Provide default images for missing product photos
User Feedback Show visual confirmation when items are added or removed
Next Steps
Order Processing Learn how orders are processed on the backend
Product Management Understand how products are created and managed