HTML5
CSS
JavaScript

Práctica en desarrollo de un e-commerce ficticio · HTML5 + CSS + JavaScript

“Agoodshop – una tienda web de dispositivos móviles y accesorios”

Proyecto práctico de e-commerce: maquetación de la página de detalle del producto y del carrito con HTML5 y CSS, y desarrollo de la funcionalidad del carrito con JavaScript.

El desarrollo del carrito combina varias implementaciones técnicas en JavaScript:

Conexión a una API

  • ✅ Realiza peticiones a la API para cargar el catálogo de productos dinámicamente.
  • ✅ Maneja respuestas JSON y errores de la API de forma eficiente.
  • ✅ Permite actualizar el listado sin recargar la página

Gestión de clases

  • ✅ Define la entidad Producto y sus propiedades (imagen, referencia, nombre, precio)
  • ✅ Define la entidad Carrito y sus métodos (actualizar unidades o calcular el total)
  • ✅ Facilita escalabilidad y reutilización de código

Funciones modulares

  • ✅ Transformar los datos de la API en un listado de entidades Producto.
  • ✅ Mostrar placeholders cuando no hay datos de productos.
  • ✅ Gestionar los cálculos de cantidades usando los inputs y botones de añadir/quitar.

Renderizado y cálculos

  • ✅ Muestra los productos en la tabla del carrito de manera dinámica.
  • ✅ Calcula automáticamente unidades añadidas, subtotales y total del carrito.
  • ✅ Actualiza la interfaz en tiempo real al modificar cantidades

Sistema de variables y componentes en CSS

El sistema variables se compone de dos arhcivos independiente:

  • Variables globales: tokens de colores, tipografías, espaciados, efectos, puntos de ruptura.
  • Variables de layout y componentes: estructuras básicas, componentes y estados
                                
    :root {
    /*color*/

    /*primary*/
    --color-primary: hsla(330, 65%, 52%, 1);
    --color-primary-hover : hsla(330, 72%, 40%, 1);
    --color-primary-active: hsla(330, 72%, 30%, 1);
    --color-on-primary: hsla(0, 0%, 100%, 1);
    --color-primary-subtle: hsla(330, 77%, 95%, 1);
    --color-primary-subtle-hover: hsla(331, 76%, 92%, 1);
    --color-primary-subtle-active: hsla(331, 76%, 90%, 1);
    --color-on-primary-subtle: hsla(331, 76%, 12%, 1);
    --color-primary-subtle-var: hsla(330, 78%, 80%, 1);

    /*secondary*/
    --color-secondary: hsla(245, 70%, 64%, 1);
    --color-secondary-hover: hsla(245, 60%, 56%, 1);
    --color-secondary-active: hsla(245, 60%, 45%, 1);
    --color-on-secondary: hsla(0, 0%, 100%, 1);
    --color-secondary-subtle: hsla(244, 61%, 95%, 1);
    --color-secondary-subtle-hover: hsla(244, 70%, 92%, 1);
    --color-secondary-subtle-active: hsla(244, 70%, 90%, 1);
    --color-on-secondary-subtle: hsla(244, 61%, 12%, 1);
    --color-secondary-subtle-var: hsla(244, 70%, 83%, 1);

    /*tertiary*/
    --color-tertiary: hsla(179, 60%, 37%, 1);
    --color-tertiary-hover: hsla(179, 74%, 28%, 1);
    --color-tertiary-active: hsla(179, 73%, 18%, 1);
    --color-on-tertiary: hsla(0, 0%, 100%, 1);
    --color-tertiary-subtle: hsla(180, 76%, 95%, 1);
    --color-tertiary-subtle-hover: hsla(180, 76%, 92%, 1);
    --color-tertiary-subtle-active: hsla(180, 76%, 90%, 1);
    --color-on-tertiary-subtle: hsla(180, 73%, 12%, 1);
    --color-tertiary-subtle-var: hsla(179, 66%, 80%, 1);

    /*error*/
    --color-error: hsla(354, 70%, 55%, 1);
    --color-error-hover: hsla(354, 60%, 44%, 1);
    --color-error-active: hsla(354, 60%, 30%, 1);
    --color-on-error: hsla(0, 0%, 100%, 1);
    --color-error-subtle: hsla(354, 90%, 93%, 1);
    --color-error-subtle-hover: hsla(354, 90%, 95%, 1);
    --color-error-subtle-active: hsla(354, 90%, 91%, 1);
    --color-on-error-subtle: hsla(331, 76%, 12%, 1);

    /*warning*/
    --color-warning: hsla(28, 50%, 44%, 1);
    --color-warning-hover: hsla(28, 50%, 41%, 1);
    --color-warning-active: hsla(28, 59%, 26%, 1);
    --color-on-warning: hsla(0, 0%, 100%, 1);
    --color-warning-subtle: hsla(28, 70%, 95%, 1);
    --color-warning-subtle-hover: hsla(29, 70%, 92%, 1);
    --color-warning-subtle-active: hsla(28, 70%, 89%, 1);
    --color-on-warning-subtle: hsla(28, 61%, 11%, 1);

    /*success*/
    --color-sucess: hsla(87, 50%, 45%, 1);
    --color-sucess-hover: hsla(87, 45%, 34%, 1);
    --color-sucess-active: hsla(87, 40%, 26%, 1);
    --color-on-sucess: hsla(0, 0%, 100%, 1);
    --color-sucess-subtle: hsla(87, 55%, 94%, 1);
    --color-sucess-subtle-hover: hsla(87, 55%, 91%, 1);
    --color-sucess-subtle-active: hsla(87, 55%, 88%, 1);
    --color-on-sucess-subtle: hsla(86, 41%, 10%, 1);

    /*base*/
    --color-base: hsla(0, 0%, 100%, 1);
    --color-base-hover: hsla(229, 22%, 90%, 1);
    --color-base-active: hsla(228, 20%, 80%, 1);
    --color-on-base: hsla(228, 20%, 19%, 1);
    --color-base-subtle: hsla(228, 20%, 30%, 1);
    --color-base-subtle-hover: hsla(228, 20%, 40%, 1);
    --color-base-subtle-active: hsla(228, 20%, 50%, 1);
    --color-on-base-subtle: hsla(228, 20%, 50%, 1);

    /*surface*/
    --color-surface-dark: hsla(228, 20%, 19%, 1);
    --color-surface-dark-var: hsla(228, 20%, 30%, 1);
    --color-surface-light: hsla(229, 22%, 99%, 1);
    --color-on-surface-dark: hsla(0, 0%, 100%, 1);
    --color-on-surface-light: hsla(229, 22%, 10%, 1);
    --color-on-surface-dark-subtle: hsla(229, 22%, 90%, 1);
    --color-on-surface-light-subtle: hsla(228, 20%, 50%, 1);
    --color-stroke-gray: hsla(228, 20%, 80%, 1);
    --color-stroke-white: hsla(0, 0%, 100%, 1);

    /*opacity*/

    /*neutral*/
    --color-opacity-neutral-0: hsla(0, 0%, 0%, 0);
    --color-opacity-neutral-5: hsla(0, 0%, 0%, 0.05);
    --color-opacity-neutral-10: hsla(0, 0%, 0%, 0.1);
    --color-opacity-neutral-15: hsla(0, 0%, 0%, 0.15);
    --color-opacity-neutral-50: hsla(0, 0%, 0%, 0.5);

    /*primary*/
    --color-opacity-primary-20: hsla(330, 72%, 56%, 0.2);
    --color-opacity-primary-40: hsla(330, 72%, 56%, 0.4);

    /*secondary*/
    --color-opacity-secondary-40: hsla(245, 60%, 56%, 0.4);

    /*tertiary*/
    --color-opacity-tertiary-40: hsla(179, 60%, 46%, 0.4);

    /*error*/
    --color-opacity-error-40: hsla(354, 70%, 55%, 0.4);

    /*warning*/
    --color-opacity-warning-40: hsla(28, 60%, 53%, 0.4);

    /*success*/
    --color-opacity-success-40: hsla(87, 50%, 45%, 0.4);

    /*base*/
    --color-opacity-base-10: hsla(228, 20%, 50%, 0.1);
    --color-opacity-base-25: hsla(228, 20%, 50%, 0.25);
    --color-opacity-base-40: hsla(228, 20%, 50%, 0.4);

    /*sizing*/

    /*border radius*/
    --border-radius-none: 0px;
    --border-radius-xxs: 4px;
    --border-radius-xs: 6px;
    --border-radius-s: 8px;
    --border-radius-m: 12px;
    --border-radius-l: 16px;
    --border-radius-xl: 20px;
    --border-radius-xxl: 24px;
    --border-radius-full: 1000px;

    /*border-size*/
    --border-none: 0px;
    --border-s: 1px;
    --border-m: 2px;

    /*icon-size*/
    --icon-size-xs: 12px;
    --icon-size-s: 16px;
    --icon-size-m: 20px;
    --icon-size-l: 24px;
    --icon-size-xl: 28px;

    /*spacing*/

    /*padding*/
    --padding-none: 0px;
    --padding-3xs: 1px;
    --padding-2xs: 2px;
    --padding-xs: 4px;
    --padding-s: 6px;
    --padding-m: 8px;
    --padding-l: 12px;
    --padding-xl: 16px;
    --padding-2xl: 20px;
    --padding-3xl: 32px;
    --padding-4xl: 80px;
    --padding-4xl-responsive: 64px;

    /*margin*/
    --margin-none: 0px;
    --margin-2xs: 2px;
    --margin-xs: 4px;
    --margin-s: 6px;
    --margin-m: 8px;
    --margin-l:12px;
    --margin-xl: 16px;
    --margin-2xl: 20px;
    --margin-3xl: 32px;
    --margin-4xl: 80px;
    --margin-4xl-responsive: 40px;


    /*gap*/
    --gap-none: 0px;
    --gap-2xs: 2px;
    --gap-xs: 4px;
    --gap-s: 6px;
    --gap-m: 8px;
    --gap-l: 12px;
    --gap-xl: 16px;
    --gap-2xl: 20px;
    --gap-3xl: 32px;
    --gap-4xl: 80px;
    --gap-4xl-responsive: 40px;

    /*typography*/

    --font-family-base: 'Roboto', sans-serif;
    --font-family-brand: 'Bellota Text', sans-serif;
    --font-size-72: 4.5rem;
    --font-size-60: 3.75rem;
    --font-size-56: 3.5rem;
    --font-size-48: 3rem;
    --font-size-40: 2.5rem;
    --font-size-36: 2.25rem;
    --font-size-32: 2rem;
    --font-size-28: 1.75rem;
    --font-size-24: 1.5rem;
    --font-size-22: 1.375rem;
    --font-size-20: 1.25rem;
    --font-size-18: 1.125rem;
    --font-size-16: 1rem;
    --font-size-14: 0.875rem;
    --font-size-12: 0.75rem;



    /*shadows*/
    --shadow-xs: 0px 1px 4px 0px rgba(25, 25, 25, 0.20);
    --shadow-s: 0px 1px 6px 1px rgba(25, 25, 25, 0.15);
    --shadow-m: 0px 1px 8px 2px rgba(25, 25, 25, 0.15);
    --shadow-l: 0px 1px 10px 4px rgba(25, 25, 25, 0.10);
    --shadow-xl: 0px 1px 12px 6px rgba(25, 25, 25, 0.10);

    /*breackpoints*/
    --xs: 575px;
    --s: 576px;
    --m:768px;
    --l: 992px;
    --xl:1200px;
    --xxl: 1400px;
    }
    

HTML semántico

La maquetación está desarrollada siguiendo las mejores prácticas de accesibilidad y usabilidad desde un enfoque semántico. El uso de etiquetas nativas para secciones o artículos permite mantener el código limpio y funcional.

                            
    <section class="section">
        <article class="flex-container flex-row wrap">
            <div class="relative">
                <figure class="product-media">
                    <video class="video" src="media/iphone_13.mp4" aria-label="iFhone 13 Pro" autoplay muted loop controls></video>
                </figure>
                <strong class="badge badge success filled absolute" aria-label="Memória RAM">128 GB</strong>    
            </div>
            <div class="flex-container flex-column left">
                <strong class="badge badge error clear" aria-label="Artículo en oferta">OFERTA</strong>
                <h1>Compra un iFhone 13 Pro</h1>
                <div class="flex-container flex-row stacked">
                    <span class="label">Valoración:</span>
                    <span class="label">⭐⭐⭐⭐☆ <strong>(4,8 de 5)</strong></span>
                </div>
                <p>iFhone 13: Tu nuevo superpoder. Nuestro sistema de cámara dual más avanzado. El chip que hace morder el polvo a la competencia. Un subidón de autonomía que vaya si notarás. Ceramic Shield, más duro que cualquier vidrio de smartphone.</p> 
                <p>Super Retina XDR de 6,1 pulgadas. Diseño robusto con bordes planos y resistente al agua.</p>
                <div class="flex-container flex-row left">
                    <span class="display-small">938,99€</span>
                    <button class="button button primary">Comprar</button>
                </div>
                <div class="flex-container flex-row wrap left">
                    <div class="flex-container flex-row top max-width-200">
                        <span class="material-symbols-outlined">storefront</span>
                        <p>Envío rápido, gratuito y sin contacto</p>
                    </div>
                    <div class="flex-container flex-row top max-width-200">
                        <span class="material-symbols-outlined">replay</span>
                        <p>Devoluciones fáciles y gratuitas</p>
                    </div>
                    <div class="flex-container flex-row top max-width-200">
                        <span class="material-symbols-outlined">currency_exchange</span>
                        <p>Posibilidad de pago a plazos</p>
                    </div>
                </div>
            </div>
        </article>
    </section>
                        

Desarrollo funcional modular

Este proyecto demuestra un enfoque de desarrollo modular y funcional aplicado a la gestión de un carrito de compras en JavaScript. Cada componente del sistema está organizado en módulos independientes, lo que permite reutilización, mantenimiento y escalabilidad del código.

Entre las principales funcionalidades se destacan:

  • Generación de productos: creación dinámica de instancias de Product desde datos JSON.
  • Renderizado de interfaz: tablas de productos y resumen del carrito con actualización en tiempo real.
  • Gestión de carrito: control de cantidades, totales y productos mediante la clase Cart para mantener la información organizada.
  • Placeholders dinámicos: mensajes visuales cuando no hay productos disponibles o el carrito está vacío.
  • Integración con API: consumo de datos externos simulados para mostrar productos de manera dinámica.
                            
    'use strict'

    export class Cart {
        constructor(products, currency) {
            this.products = products;
            this.currency = currency;
        }

        /* Actualizar unidades del artículo en el carrito */
        updateQuantity(sku, quantity) {
            if (quantity < 0) {
                throw new Error('La cantidad no puede ser negativa');
            }

            for (let product of this.products) {
                if (sku === product.sku) {
                    product.quantity = quantity;
                    return product.quantity;
                }
            }
            throw new Error("SKU no encontrado");
        }

        /* Obtener info de un artículo en el carrito */
        getInfoProduct(sku) {
            for (const product of this.products) {
                if (sku === product.sku) {
                    return {
                        "sku": product.sku,
                        "title": product.title,
                        "quantity": product.quantity
                    };
                }
            }
            throw new Error("SKU no encontrado");
        }

        /* Calcular total del carrito */
        getTotalCart() {
            let total = 0;
            for (const product of this.products) {
                total += product.quantity * product.price;
            }
            return total;
        }

        /* Obtener información del carrito */
        getCart() {
            return {
                "total": this.getTotalCart(),
                "currency": this.currency,
                "products": this.products,
            };
        }
    }
                            
                        

Interfaz amigable

La maquetación está desarrollada siguiendo las mejores prácticas de accesibilidad y usabilidad desde un enfoque semántico. El uso de etiquetas nativas para secciones o artículos permite mantener el código limpio y funcional.