Compartir en Twitter
Go to Homepage

CONSTRUYE UN CARRITO DE COMPRAS CON JAVASCRIPT VANILLA DESDE CERO

November 2, 2025

Introducción al desarrollo de un carrito de compras con JavaScript puro

El desarrollo de aplicaciones web interactivas requiere un dominio sólido de los fundamentos del lenguaje. En este tutorial, se presenta la construcción completa de un carrito de compras utilizando únicamente JavaScript vanilla, es decir, sin depender de librerías externas como React, Vue o jQuery. Este enfoque permite comprender profundamente cómo funciona la manipulación del DOM, el manejo de eventos, el almacenamiento persistente en el navegador y los cálculos en tiempo real. El resultado final será una tienda de ropa funcional con productos dinámicos, incrementos y decrementos de cantidad, eliminación de ítems y cálculo automático del total.

El proyecto se divide en fases claras: configuración inicial, estructura HTML y CSS, generación de productos, implementación de funcionalidades con JavaScript y persistencia de datos mediante localStorage. Cada sección incluye ejemplos de código con explicaciones detalladas para facilitar la comprensión y reproducción del comportamiento esperado.

Configuración del entorno de desarrollo

Para iniciar el proyecto, es necesario crear una carpeta de trabajo y configurar un editor de código como VS Code. Dentro de la carpeta principal, se generan tres archivos fundamentales: index.html, style.css y main.js. Además, se incluye una carpeta images para almacenar las imágenes de los productos.

La estructura inicial de directorios es la siguiente:

shopping-cart/
├── index.html
├── style.css
├── main.js
└── images/
    ├── shirt1.jpg
    ├── shirt2.jpg
    ├── suit.jpg
    └── office-shirt.jpg

En index.html, se establece el esqueleto básico con el uso de Emmet para agilizar la escritura:

<!DOCTYPE html>
<html lang="es">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Tienda de Ropa</title>
        <link rel="stylesheet" href="style.css" />
        <link
            rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css"
        />
    </head>
    <body>
        <script src="main.js"></script>
    </body>
</html>

Se incluye el CDN de Bootstrap Icons para utilizar íconos vectoriales sin descargar archivos adicionales. En style.css, se eliminan los estilos predeterminados del navegador:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: sans-serif;
}

La extensión Live Server en VS Code permite visualizar los cambios en tiempo real al hacer clic derecho y seleccionar “Open with Live Server”.

Construcción de la barra de navegación

La barra de navegación contiene el logotipo de la tienda y un ícono de carrito con un indicador numérico. Se crea un contenedor con clase navbar que utiliza Flexbox para alinear los elementos.

<div class="navbar">
    <h2>Tienda de Ropa</h2>
    <div class="cart">
        <i class="bi bi-cart3"></i>
        <div class="cart-amount">0</div>
    </div>
</div>

El estilo asegura un fondo oscuro y texto blanco:

.navbar {
    background-color: #212529;
    color: white;
    padding: 25px 60px;
    display: flex;
    justify-content: space-between;
}

.cart {
    position: relative;
    background-color: white;
    color: #212529;
    font-size: 30px;
    padding: 5px;
    border-radius: 4px;
}

.cart-amount {
    position: absolute;
    top: -15px;
    right: -10px;
    font-size: 16px;
    background-color: red;
    color: white;
    padding: 3px;
    border-radius: 3px;
}

El uso de position: absolute en .cart-amount permite superponer el contador sobre el ícono del carrito.

Diseño de las tarjetas de productos con CSS Grid

Los productos se muestran en una cuadrícula responsiva de cuatro columnas en pantallas grandes. Se crea un contenedor con clase shop y un ID para facilitar la selección en JavaScript.

<div class="shop" id="shop">
    <div class="item">
        <img src="images/shirt1.jpg" width="220" alt="Camisa casual" />
        <div class="details">
            <h3>Camisa Casual</h3>
            <p>Camisa de algodón suave y transpirable.</p>
            <div class="price-quantity">
                <h2>$ 45</h2>
                <div class="buttons">
                    <i class="bi bi-plus-lg"></i>
                    <div class="quantity">0</div>
                    <i class="bi bi-dash-lg"></i>
                </div>
            </div>
        </div>
    </div>
</div>

Se duplican tres veces más el bloque .item para tener cuatro tarjetas inicialmente. El estilo utiliza CSS Grid:

.shop {
    display: grid;
    grid-template-columns: repeat(4, 223px);
    gap: 30px;
    justify-content: center;
    margin-top: 30px;
}

.item {
    border: 2px solid #212529;
    border-radius: 4px;
}

.details {
    display: flex;
    flex-direction: column;
    padding: 10px;
    gap: 10px;
}

.price-quantity {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.buttons {
    display: flex;
    gap: 8px;
}

Se agregan media queries para adaptabilidad:

@media (max-width: 1000px) {
    .shop {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 500px) {
    .shop {
        grid-template-columns: 1fr;
    }
}

Generación dinámica de productos con JavaScript

En main.js, se define un arreglo de datos de productos:

let shopItemsData = [
    {
        id: "ioy1",
        name: "Camisa Casual",
        price: 45,
        desc: "Camisa de algodón suave y transpirable.",
        img: "images/shirt1.jpg",
    },
    // ... otros 11 productos
];

La función generateShop recorre este arreglo y genera el HTML dinámicamente:

let generateShop = () => {
    return (shop.innerHTML = shopItemsData
        .map((x) => {
            let { id, name, price, desc, img } = x;
            let search = basket.find((y) => y.id === id) || { item: 0 };
            return `
            <div class="item" id="${id}">
                <img width="220" src="${img}" alt="${name}">
                <div class="details">
                    <h3>${name}</h3>
                    <p>${desc}</p>
                    <div class="price-quantity">
                        <h2>$ ${price}</h2>
                        <div class="buttons">
                            <i onclick="decrement('${id}')" class="bi bi-dash-lg"></i>
                            <div class="quantity">${search.item}</div>
                            <i onclick="increment('${id}')" class="bi bi-plus-lg"></i>
                        </div>
                    </div>
                </div>
            </div>
            `;
        })
        .join(""));
};

generateShop();

El uso de basket.find() permite mostrar la cantidad actual almacenada en localStorage, incluso si es cero.

Gestión del estado del carrito con localStorage

Se inicializa el arreglo basket desde localStorage o como vacío:

let basket = JSON.parse(localStorage.getItem("data")) || [];

Las funciones increment y decrement actualizan el estado:

let increment = (id) => {
    let selectedItem = id;
    let search = basket.find((x) => x.id === selectedItem);
    if (search === undefined) {
        basket.push({ id: selectedItem, item: 1 });
    } else {
        search.item += 1;
    }
    update(selectedItem);
    localStorage.setItem("data", JSON.stringify(basket));
};

let decrement = (id) => {
    let selectedItem = id;
    let search = basket.find((x) => x.id === selectedItem);
    if (search === undefined || search.item === 0) return;
    else search.item -= 1;
    update(selectedItem);
    basket = basket.filter((x) => x.item !== 0);
    localStorage.setItem("data", JSON.stringify(basket));
};

La función update refresca la interfaz:

let update = (id) => {
    let search = basket.find((x) => x.id === id);
    document.querySelectorAll(".quantity").forEach((el) => {
        if (el.closest(".item").id === id) {
            el.innerHTML = search.item;
        }
    });
    calculation();
};

Cálculo del total de ítems en el carrito

La función calculation actualiza el contador del ícono:

let calculation = () => {
    let cartIcon = document.querySelector(".cart-amount");
    cartIcon.innerHTML = basket.map((x) => x.item).reduce((x, y) => x + y, 0);
};

calculation();

Este cálculo se ejecuta cada vez que se modifica el carrito, asegurando que el número refleje el total de productos seleccionados en tiempo real.

Implementación de la página del carrito

Se crea un nuevo archivo cart.html con estructura similar a index.html, pero con un contenedor #shopping-cart. En cart.js, se define generateCartItems:

let generateCartItems = () => {
    if (basket.length !== 0) {
        return (shoppingCart.innerHTML = basket
            .map((x) => {
                let { id, item } = x;
                let search = shopItemsData.find((y) => y.id === id) || {};
                return `
                <div class="cart-item">
                    <img width="100" src="${search.img}" alt="${search.name}">
                    <div class="details">
                        <div class="title-price-x">
                            <h4>
                                <p>${search.name}</p>
                                <p class="cart-item-price">$ ${search.price}</p>
                            </h4>
                            <i onclick="removeItem('${id}')" class="bi bi-x-lg"></i>
                        </div>
                        <div class="cart-buttons">
                            <i onclick="decrement('${id}')" class="bi bi-dash-lg"></i>
                            <div class="quantity">${item}</div>
                            <i onclick="increment('${id}')" class="bi bi-plus-lg"></i>
                        </div>
                        <h3>$ ${item * search.price}</h3>
                    </div>
                </div>
                `;
            })
            .join(""));
    } else {
        shoppingCart.innerHTML = ``;
        label.innerHTML = `
            <h2>El carrito está vacío</h2>
            <a href="index.html">
                <button class="home-button">Volver al inicio</button>
            </a>
        `;
    }
};

generateCartItems();

Funcionalidad de eliminación de ítems

La función removeItem filtra el ítem del arreglo y actualiza el almacenamiento:

let removeItem = (id) => {
    let selectedItem = id;
    basket = basket.filter((x) => x.id !== selectedItem);
    generateCartItems();
    totalAmount();
    localStorage.setItem("data", JSON.stringify(basket));
};

Cálculo del monto total de la compra

La función totalAmount calcula el subtotal:

let totalAmount = () => {
    if (basket.length !== 0) {
        let amount = basket
            .map((x) => {
                let { id, item } = x;
                let search = shopItemsData.find((y) => y.id === id) || {};
                return item * search.price;
            })
            .reduce((x, y) => x + y, 0);
        label.innerHTML = `
            <h2>Total: $ ${amount}</h2>
            <button class="checkout">Pagar</button>
            <button onclick="clearCart()" class="remove-all">Vaciar carrito</button>
        `;
    } else return;
};

totalAmount();

Vaciar el carrito completamente

let clearCart = () => {
    basket = [];
    generateCartItems();
    localStorage.setItem("data", JSON.stringify(basket));
};

Estilos específicos para la página del carrito

En style.css, se agregan reglas para el carrito:

#shopping-cart {
    display: grid;
    grid-template-columns: 320px;
    justify-content: center;
    gap: 15px;
    margin: 30px auto;
}

.cart-item {
    border: 2px solid #212529;
    border-radius: 5px;
    display: flex;
}

.title-price-x {
    width: 195px;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.title-price {
    display: flex;
    gap: 10px;
}

.cart-item-price {
    background-color: #212529;
    color: white;
    padding: 3px 6px;
    border-radius: 4px;
}

Integración entre páginas

En index.html, el ícono del carrito enlaza a cart.html:

<div class="cart" onclick="location.href='cart.html'"></div>

Esto permite navegar entre la tienda y el carrito manteniendo el estado gracias a localStorage.

Pruebas de funcionalidad completa

  1. Incrementar y decrementar cantidades en la tienda.
  2. Ver actualización inmediata en el contador del carrito.
  3. Navegar al carrito y ver los ítems seleccionados.
  4. Modificar cantidades desde el carrito.
  5. Eliminar ítems con el botón X.
  6. Vaciar completamente el carrito.
  7. Refrescar la página y verificar persistencia de datos.

Todas las operaciones deben reflejarse en tiempo real y mantenerse tras recargar la página gracias al uso de localStorage como almacenamiento persistente.

Conclusiones

La construcción de un carrito de compras con JavaScript vanilla demuestra el poder de los fundamentos del lenguaje para crear aplicaciones interactivas complejas sin dependencias externas. Se ha implementado una solución completa que incluye generación dinámica de contenido, gestión de estado, persistencia de datos, cálculos en tiempo real y diseño responsivo. Este proyecto sirve como base sólida para comprender cómo funcionan los sistemas de e-commerce a nivel de código y permite extender funcionalidades como procesamiento de pagos, filtros de productos o integración con APIs externas en futuros desarrollos.