Inicio > Cursos > SQL y Bases de Datos > Proyecto: Base de Datos E-commerce
SQL y Bases de DatosSQL en ProducciónLección 20 de 20

Proyecto: Base de Datos E-commerce

En este proyecto final diseñarás y construirás el esquema completo de una base de datos e-commerce: tabla de usuarios con autenticación, productos con categorías anidadas, órdenes con múltiples ítems, historial de pagos y tabla de reseñas. Aplicarás normalización, constraints, índices y relaciones correctas.

Luego escribirás queries analíticas reales: los 10 productos más vendidos del mes, ingresos por categoría, tasa de conversión por campaña, usuarios que compraron A y también B. Estas son exactamente las queries que piden en entrevistas técnicas y que los equipos de datos usan a diario en producción.

-- ESQUEMA COMPLETO E-COMMERCE

CREATE TABLE users (
  id           SERIAL PRIMARY KEY,
  email        VARCHAR(255) UNIQUE NOT NULL,
  nombre       VARCHAR(100) NOT NULL,
  activo       BOOLEAN DEFAULT TRUE,
  creado_en    TIMESTAMP DEFAULT NOW()
);

CREATE TABLE categories (
  id        SERIAL PRIMARY KEY,
  nombre    VARCHAR(100) NOT NULL,
  padre_id  INTEGER REFERENCES categories(id)  -- categorías anidadas
);

CREATE TABLE products (
  id           SERIAL PRIMARY KEY,
  nombre       VARCHAR(200) NOT NULL,
  precio       DECIMAL(10,2) NOT NULL CHECK (precio >= 0),
  stock        INTEGER DEFAULT 0 CHECK (stock >= 0),
  category_id  INTEGER REFERENCES categories(id),
  activo       BOOLEAN DEFAULT TRUE
);

CREATE TABLE orders (
  id         SERIAL PRIMARY KEY,
  user_id    INTEGER NOT NULL REFERENCES users(id),
  estado     VARCHAR(20) DEFAULT 'pendiente',
  total      DECIMAL(12,2) NOT NULL,
  creado_en  TIMESTAMP DEFAULT NOW()
);

CREATE TABLE order_items (
  id          SERIAL PRIMARY KEY,
  order_id    INTEGER NOT NULL REFERENCES orders(id),
  product_id  INTEGER NOT NULL REFERENCES products(id),
  cantidad    INTEGER NOT NULL CHECK (cantidad > 0),
  precio      DECIMAL(10,2) NOT NULL  -- precio histórico al momento de compra
);

CREATE TABLE payments (
  id          SERIAL PRIMARY KEY,
  order_id    INTEGER NOT NULL REFERENCES orders(id),
  monto       DECIMAL(12,2) NOT NULL,
  metodo      VARCHAR(50),  -- 'tarjeta', 'paypal', 'transferencia'
  estado      VARCHAR(20) DEFAULT 'pendiente',
  procesado_en TIMESTAMP
);

-- Índices para rendimiento
CREATE INDEX idx_orders_user ON orders(user_id);
CREATE INDEX idx_items_order ON order_items(order_id);
CREATE INDEX idx_items_product ON order_items(product_id);
CREATE INDEX idx_payments_order ON payments(order_id);
-- QUERIES ANALÍTICAS AVANZADAS

-- 1. Reporte de ventas mensual con crecimiento
WITH ventas_mes AS (
  SELECT
    DATE_TRUNC('month', o.creado_en) AS mes,
    COUNT(DISTINCT o.id) AS num_ordenes,
    COUNT(DISTINCT o.user_id) AS clientes_unicos,
    SUM(o.total) AS ingresos
  FROM orders o
  WHERE o.estado = 'completado'
  GROUP BY DATE_TRUNC('month', o.creado_en)
)
SELECT
  TO_CHAR(mes, 'YYYY-MM') AS mes,
  num_ordenes,
  clientes_unicos,
  ingresos,
  ROUND(ingresos / num_ordenes, 2) AS ticket_promedio,
  ROUND((ingresos - LAG(ingresos) OVER (ORDER BY mes)) * 100.0
        / NULLIF(LAG(ingresos) OVER (ORDER BY mes), 0), 2) AS crecimiento_pct
FROM ventas_mes
ORDER BY mes;

-- 2. Top productos por ingresos con ranking por categoría
SELECT
  c.nombre AS categoria,
  p.nombre AS producto,
  SUM(oi.cantidad) AS unidades_vendidas,
  SUM(oi.precio * oi.cantidad) AS ingresos_totales,
  RANK() OVER (
    PARTITION BY c.id
    ORDER BY SUM(oi.precio * oi.cantidad) DESC
  ) AS rank_en_categoria
FROM order_items oi
JOIN products p ON p.id = oi.product_id
JOIN categories c ON c.id = p.category_id
JOIN orders o ON o.id = oi.order_id
WHERE o.estado = 'completado'
  AND o.creado_en >= NOW() - INTERVAL '12 months'
GROUP BY c.id, c.nombre, p.id, p.nombre
HAVING SUM(oi.precio * oi.cantidad) > 0
ORDER BY categoria, ingresos_totales DESC;

-- 3. Análisis RFM: Recency, Frequency, Monetary (segmentación de clientes)
WITH rfm_base AS (
  SELECT
    u.id,
    u.nombre,
    u.email,
    MAX(o.creado_en) AS ultima_compra,
    COUNT(o.id) AS frecuencia,
    SUM(o.total) AS monetario
  FROM users u
  JOIN orders o ON o.user_id = u.id
  WHERE o.estado = 'completado'
  GROUP BY u.id, u.nombre, u.email
)
SELECT
  nombre, email,
  EXTRACT(DAY FROM NOW() - ultima_compra) AS dias_sin_comprar,
  frecuencia AS num_compras,
  monetario AS gasto_total,
  CASE
    WHEN EXTRACT(DAY FROM NOW() - ultima_compra) <= 30
      AND frecuencia >= 5 AND monetario >= 500 THEN 'Campeón'
    WHEN EXTRACT(DAY FROM NOW() - ultima_compra) <= 90
      AND frecuencia >= 2 THEN 'Cliente Leal'
    WHEN EXTRACT(DAY FROM NOW() - ultima_compra) > 180 THEN 'En Riesgo'
    ELSE 'Cliente Regular'
  END AS segmento
FROM rfm_base
ORDER BY monetario DESC;
ConsejoEste esquema cubre el 80% de los e-commerce reales. En producción agregarías: tabla de direcciones, variantes de producto (talla/color), cupones de descuento, reviews, y tablas de auditoría. La base que aprendiste en este curso es suficiente para construirlos.
← Optimización de Consultas

Practica esta lección

Resuelve los ejercicios del curso de SQL y Bases de Datos en la app, gratis y con corrección automática.

Empezar a practicar