javascript

Carrito de compras con JavaScript

En este post de JavaScript y desarrollo web te mostraré cómo implementar un carrito de compras en el lado del cliente con JS.

Será un ejemplo completo de un proyecto en donde además de tener el carrito de compras tendremos un módulo de tienda (pero no habrá transacciones ni pagos reales) y módulo de gestión de productos.

Este proyecto se basa ampliamente en el CRUD con AJAX, PHP y MySQL publicado anteriormente, pero agregamos la gestión del carrito de compras dentro de localStorage.

Como siempre, te dejaré el código completo y listo para descargar.

Gestión de productos

Gestión de productos para el carrito de compras con JS

Como ya lo comenté arriba, este proyecto se basa y toma código de otro publicado anteriormente; por lo que si no entiendes algunos conceptos te recomiendo leer el post que cité arriba.

En este caso ya no veremos cómo guardar, obtener, editar y eliminar los productos, solo nos vamos a centrar en la gestión del carrito de compras, aunque igualmente te dejaré el código completo.

Librería para carrito de compras

Voy a usar mi propia clase que encierra el comportamiento del carrito de compras:

/*

  ____          _____               _ _           _       
 |  _ \        |  __ \             (_) |         | |      
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___ 
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |        
        |___/                               |___/         
    
____________________________________
/ Si necesitas ayuda, contáctame en \
\ https://parzibyte.me               /
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Creado por Parzibyte (https://parzibyte.me).
------------------------------------------------------------------------------------------------
Si el código es útil para ti, puedes agradecerme siguiéndome: https://parzibyte.me/blog/sigueme/
Y compartiendo mi blog con tus amigos
También tengo canal de YouTube: https://www.youtube.com/channel/UCroP4BTWjfM0CkGB6AFUoBg?sub_confirmation=1
------------------------------------------------------------------------------------------------
*/class Carrito {
    constructor(clave) {
        this.clave = clave || "productos";
        this.productos = this.obtener();
    }

    agregar(producto) {
        if (!this.existe(producto.id)) {
            this.productos.push(producto);
            this.guardar();
        }
    }

    quitar(id) {
        const indice = this.productos.findIndex(p => p.id === id);
        if (indice != -1) {
            this.productos.splice(indice, 1);
            this.guardar();
        }
    }

    guardar() {
        localStorage.setItem(this.clave, JSON.stringify(this.productos));
    }

    obtener() {
        const productosCodificados = localStorage.getItem(this.clave);
        return JSON.parse(productosCodificados) || [];
    }

    existe(id) {
        return this.productos.find(producto => producto.id === id);
    }

    obtenerConteo() {
        return this.productos.length;
    }


}

Es solo una envoltura que encierra todo el comportamiento del carrito, para poder reusarlo en varios lugares. De nuevo, si quieres profundizar, leer el artículo que dejé en el enlace.

Tienda

Tienda – Lista de productos para agregar al carrito de compras con JavaScript

La tienda es el lugar en donde se muestran los productos al usuario para que se puedan agregar al carrito de compras. En este lugar mostraremos los productos y dependiendo del estado de cada uno, botones.

Si el producto está en el carrito, se muestra que ya está agregado junto con un botón para removerlo. En caso de que no, se muestra un botón para agregarlo.

const $contenedor = document.querySelector("#contenedor"),
    $conteoCarrito = document.querySelector("#conteoCarrito");


const actualizarConteo = conteo => {
    if (!conteo) {
        $conteoCarrito.textContent = "";
    } else {
        $conteoCarrito.textContent = `(${conteo})`;
    }
};


const obtenerProductos = async () => {
    // Es una petición GET, no necesitamos indicar el método ni el cuerpo
    const respuestaRaw = await fetch("obtener_productos.php");
    const productos = await respuestaRaw.json();
    // Limpiamos la tabla
    $contenedor.innerHTML = "";
    const c = new Carrito();
    refrescarConteoDeCarrito();
    // Ahora ya tenemos a los productos. Los recorremos
    for (const producto of productos) {
        const $div = document.createElement("div");
        $div.classList.add("columns");
        const $columna = document.createElement("div");
        $columna.classList.add("column", "is-full");
        $div.appendChild($columna);
        const $tarjeta = document.createElement("div");
        $tarjeta.classList.add("card");
        $columna.appendChild($tarjeta);
        const $header = document.createElement("header");
        $header.classList.add("card-header");
        const $parrafoNombre = document.createElement("p");
        $parrafoNombre.classList.add("card-header-title", "is-size-4");
        $parrafoNombre.textContent = producto.nombre;
        $header.appendChild($parrafoNombre);
        $tarjeta.appendChild($header);
        const $contenidoTarjeta = document.createElement("div");
        $contenidoTarjeta.classList.add("card-content");
        const $contenido = document.createElement("div");
        $contenido.classList.add("content");
        $contenido.innerText = producto.descripcion;
        $contenidoTarjeta.appendChild($contenido);
        $tarjeta.appendChild($contenidoTarjeta);
        const $encabezadoPrecio = document.createElement("h1");
        $encabezadoPrecio.classList.add("is-size-3");
        $encabezadoPrecio.textContent = producto.precio;
        $contenidoTarjeta.appendChild($encabezadoPrecio);
        if (c.existe(producto.id)) {
            const $spanYaPresenteEnCarrito = document.createElement("span");
            $spanYaPresenteEnCarrito.classList.add("button", "is-success");
            $spanYaPresenteEnCarrito.innerHTML = `<i class="fa fa-check"></i>&nbsp;En el carrito`;
            $contenidoTarjeta.appendChild($spanYaPresenteEnCarrito);

            const $botonQuitar = document.createElement("button");
            $botonQuitar.classList.add("button", "is-danger", "ml-2");
            $botonQuitar.innerHTML = `<i class="fa fa-trash-o"></i>&nbsp;Quitar`;
            $botonQuitar.onclick = () => {
                c.quitar(producto.id);
                obtenerProductos();
                refrescarConteoDeCarrito();
            };
            $contenidoTarjeta.appendChild($botonQuitar);
        } else {
            const $botonAgregar = document.createElement("button");
            $botonAgregar.classList.add("button", "is-primary");
            $botonAgregar.innerHTML = `<i class="fa fa-cart-plus"></i>&nbsp;Agregar al carrito`;
            $botonAgregar.onclick = () => {
                c.agregar(producto);
                obtenerProductos();
                refrescarConteoDeCarrito();
            };
            $contenidoTarjeta.appendChild($botonAgregar);
        }
        $contenedor.appendChild($div);
    }
};

// Y cuando se incluya este script, invocamos a la función
obtenerProductos();

En este caso hacemos un filtro, pues primero obtenemos todos los productos del back-end y ya en el lado del cliente al renderizar los vamos filtrando.

Hay un montón de código debido a que la plantilla lleva varios divs y clases; aquí se ve claramente el uso de frameworks o de al menos librerías para el manejo de plantillas con JS.

Para cada clic del botón existe una función que invoca a un método del carrito y refresca el conteo del encabezado. Presta atención a las líneas 59 y 69.

Refrescar conteo de productos en el encabezado

Como te puedes dar cuenta, en el encabezado existe el botón de ver carrito junto con el conteo de productos. En este caso se necesita refrescar cuando el usuario agrega o quita un producto, pero también cuando navega en otros lugares.

La solución a esto fue colocar la función que refresca el conteo del carrito de compras en el encabezado. Para empezar puse un id al span que tiene el conteo, mismo que es conteoCarrito:

<div class="navbar-item">
    <div class="buttons">
        <a href="ver_carrito.php" class="button is-success">
            <strong>Ver carrito <span id="conteoCarrito">
                    (1)
                </span>&nbsp;<i class="fa fa-shopping-cart"></i></strong>
        </a>
    </div>
</div>

Después lo he refrescado con JavaScript, además de exponer esa función de manera global para que pueda ser llamada desde otros lugares.

// Simple función para refrescar el conteo del carrito siempre
const refrescarConteoDeCarrito = () => {
    const $conteoCarrito = document.querySelector("#conteoCarrito");
    const carritoEncabezado = new Carrito();
    const conteo = carritoEncabezado.obtenerConteo();
    if (conteo > 0) {
        $conteoCarrito.textContent = "(".concat(conteo, ")");
    } else {
        $conteoCarrito.textContent = "";
    }
};
// Hacerla global
window.refrescarConteoDeCarrito = refrescarConteoDeCarrito;
refrescarConteoDeCarrito();

Ahora esa función se llama en el encabezado, pero podemos llamarla desde cualquier otro lugar. En estos casos obtenemos el mismo carrito porque usamos la clave por defecto al instanciarlo, si tú usas otra clave, recuerda indicarla también al construir el objeto en el encabezado.

Ver carrito de compras

Ver carrito de compras con productos almacenados en localStorage usando JavaScript

Para finalizar con los fragmentos de código importante, otro módulo de gran relevancia es el de ver el carrito de compras. En el mismo renderizamos una tabla con los productos, pero estos ya no vienen del servidor, sino que son tomados del lado del cliente.

Aquí deberías enviar los productos o cada id de los mismos al servidor cuando se termine la compra, pero hacer una comprobación en cuanto al precio e ID.

const $cuerpoTabla = document.querySelector("#cuerpoTabla"),
    $celdaTotal = document.querySelector("#celdaTotal"),
    $btnTerminarCompra = document.querySelector("#btnTerminarCompra"),
    c = new Carrito();

$btnTerminarCompra.onclick = () => {
    // Aquí haz lo que gustes con el carrito
    const productos = c.obtener();
    console.log(productos);
};
const refrescarCarrito = () => {
    const productos = c.obtener();
    // Limpiamos la tabla
    $cuerpoTabla.innerHTML = "";
    // Ahora ya tenemos a los productos. Los recorremos
    let total = 0;
    for (const producto of productos) {
        total += parseFloat(producto.precio);
        // Vamos a ir adjuntando elementos a la tabla.
        const $fila = document.createElement("tr");
        // La celda del nombre
        const $celdaNombre = document.createElement("td");
        // Colocamos su valor y lo adjuntamos a la fila
        $celdaNombre.innerText = producto.nombre;
        $fila.appendChild($celdaNombre);
        // Lo mismo para lo demás
        const $celdaDescripcion = document.createElement("td");
        $celdaDescripcion.innerText = producto.descripcion;
        $fila.appendChild($celdaDescripcion);
        const $celdaPrecio = document.createElement("td");
        $celdaPrecio.innerText = producto.precio;
        $fila.appendChild($celdaPrecio);
        // Extraer el id del producto en el que estamos dentro del ciclo
        const idProducto = producto.id;

        // Para el botón de eliminar primero creamos el botón, agregamos su listener y luego lo adjuntamos a su celda
        const $botonEliminar = document.createElement("button");
        $botonEliminar.classList.add("button", "is-danger")
        $botonEliminar.innerHTML = `<i class="fa fa-trash"></i>`;
        $botonEliminar.onclick = async () => {
            c.quitar(idProducto);
            refrescarCarrito();
            refrescarConteoDeCarrito();
        };
        const $celdaBoton = document.createElement("td");
        $celdaBoton.appendChild($botonEliminar);
        $fila.appendChild($celdaBoton);
        // Adjuntamos la fila a la tabla
        $cuerpoTabla.appendChild($fila);
    }
    $celdaTotal.textContent = total.toString();
};

// Y cuando se incluya este script, invocamos a la función
refrescarCarrito();

Además de tener una referencia a la tabla para dibujar todos los productos, tenemos una referencia al total (para mostrar el total una vez calculado) y una al botón que termina la compra.

El total lo estamos calculando y dibujando en las líneas 16, 18 y 51. Para el caso del clic del botón (línea 6) solo imprimimos los productos, aquí el programador es libre de hacer con la lista lo que mejor le parezca, pues este ejemplo solo contempla el manejo del carrito de compras.

Configurando el proyecto

Si descargas el código, recuerda crear la base de datos y el archivo env.php (no incluido) basándote en el archivo env.ejemplo.php.

Configura tus credenciales en el archivo env.php e importa las tablas de esquema.sql; después de eso puedes visitar el proyecto en tu localhost o servidor de internet y comenzar a probar.

Poniendo todo junto

El código completo y actualizado de esta implementación de carrito de compras con JavaScript usando almacenamiento del lado del cliente lo encuentras en su respectivo repositorio.

Recuerda que esto se basa en el CRUD de PHP con JavaScript y MySQL, pero los productos bien podrían venir de cualquier servicio del lado del servidor.

En caso de que te guste PHP, ya hice una implementación del carrito de compras usando sesiones.

Te dejo con más sobre JavaScript, así como un vistazo a otros proyectos open source.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

20 min hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

6 días hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

6 días hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

6 días hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

6 días hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

6 días hace

Esta web usa cookies.