Laboratorio 8: Consultando TechStyle
Semana 8 · W08 · Unidad II · SELECT, Agregaciones y JOINs · Universidad San Sebastián
1 Contexto
En los laboratorios W06 y W07 construiste la base de datos de TechStyle: 9 tablas, datos reales y operaciones transaccionales garantizadas. Hoy llega la parte que justifica todo ese trabajo: responder preguntas de negocio reales con SQL.
Roberto (CEO), María (Marketing) y Carlos (Operaciones) tienen preguntas concretas que esperan respuesta. Juan —tu rol como analista de datos— usará SELECT, WHERE, GROUP BY, HAVING y JOIN para convertir los datos almacenados en información accionable.
Al terminar este laboratorio tendrás un set de 8 consultas documentadas que servirán como base para el proyecto de la Solemne 2.
2 Instrucciones generales
- Trabajarás en los mismos grupos de los laboratorios anteriores.
- Duración total: 2 horas 30 minutos.
- Necesitas la base de datos
techstylecompletamente poblada del laboratorio W07.
Herramientas necesarias:
| Herramienta | Uso |
|---|---|
| MySQL Workbench 8.0 | Escritura y ejecución de consultas |
| MySQL Server 8.0 | Motor donde está la base de datos techstyle |
Estructura del laboratorio:
| Parte | Actividad | Tiempo |
|---|---|---|
| 1 | SELECT y WHERE: filtrar para decidir | 35 min |
| 2 | Agregaciones: GROUP BY y HAVING | 40 min |
| 3 | JOINs: cruzar tablas para reportes completos | 40 min |
| 4 | Síntesis: reportes para el equipo directivo | 35 min |
3 Parte 1 — SELECT y WHERE: Filtrar para Decidir
Duración: 35 minutos
3.1 Paso 1: Conectar y explorar la base de datos
Abre MySQL Workbench y conéctate al servidor local. Antes de escribir cualquier consulta, oriéntate:
USE techstyle;
-- ¿Cuántos registros hay en cada tabla?
SELECT 'clientes' AS tabla, COUNT(*) AS filas FROM clientes UNION ALL
SELECT 'productos', COUNT(*) FROM productos UNION ALL
SELECT 'ventas', COUNT(*) FROM ventas UNION ALL
SELECT 'detalle_ventas', COUNT(*) FROM detalle_ventas;Completa la tabla con los resultados:
| Tabla | Filas |
|---|---|
clientes |
|
productos |
|
ventas |
|
detalle_ventas |
3.2 Paso 2: Catálogo completo ordenado por precio
María necesita revisar el catálogo completo para la campaña de invierno, ordenado de más caro a más barato:
SELECT nombre_producto AS producto,
precio_venta AS "Precio ($)",
costo_unitario AS "Costo ($)",
precio_venta - costo_unitario AS "Margen Bruto ($)",
stock AS "Stock disponible"
FROM productos
ORDER BY precio_venta DESC;Pregunta 1.1: ¿Qué producto tiene el mayor margen bruto en pesos? ¿Y el menor? ¿Qué decisión comercial podría tomar María con esta información?
3.3 Paso 3: Alertas de stock bajo
Carlos necesita saber qué productos requieren reposición urgente. Define como umbral crítico un stock inferior a 100 unidades:
SELECT nombre_producto,
stock,
precio_venta,
stock * costo_unitario AS "Valor a reponer ($)"
FROM productos
WHERE stock < 100
ORDER BY stock ASC;Pregunta 1.2: ¿Cuántos productos están en alerta de stock? ¿Cuál representa mayor urgencia para el negocio — el de menor stock o el de mayor valor a reponer?
3.4 Paso 4: Clientes por región y segmento
María quiere identificar los clientes Premium de la Región Metropolitana para la campaña de invierno:
SELECT nombre,
apellido,
email,
segmento,
fecha_registro
FROM clientes
WHERE region = 'Metropolitana'
AND segmento = 'Premium'
ORDER BY fecha_registro ASC;Ahora escribe tú la consulta que responde: ¿qué clientes NO son de la Región Metropolitana ni de Valparaíso?
-- Escribe tu consulta aquí:
SELECT ...
FROM clientes
WHERE ...
ORDER BY region, apellido;Usa NOT IN ('Metropolitana', 'Valparaíso') o combina dos condiciones con AND y <>.
3.5 Paso 5: Buscar productos por nombre parcial
Carlos necesita encontrar todos los productos de la marca Nike en el catálogo:
SELECT nombre_producto,
precio_venta,
stock
FROM productos
WHERE nombre_producto LIKE '%Nike%'
ORDER BY precio_venta DESC;Pregunta 1.3: ¿Cuántos productos Nike tiene TechStyle? ¿Cuál es su precio promedio? (No uses SQL aún — estimación a ojo de la tabla resultante)
3.6 Paso 6: Ventas en un rango de fechas
Roberto quiere ver todas las ventas registradas en enero de 2026:
SELECT venta_id,
fecha,
cliente_id
FROM ventas
WHERE fecha BETWEEN '2026-01-01' AND '2026-01-31'
ORDER BY fecha;Pregunta 1.4: ¿Cuántas ventas se realizaron en enero? ¿Y cuántas en total tiene la base de datos? ¿Qué limitación tiene esta consulta para informar a Roberto? (Pista: piensa en qué información falta)
4 Parte 2 — Agregaciones: GROUP BY y HAVING
Duración: 40 minutos
4.1 Paso 7: Resumen del catálogo por categoría
SELECT cat.nombre_categoria,
COUNT(p.producto_id) AS cantidad_productos,
ROUND(AVG(p.precio_venta), 0) AS precio_promedio,
MIN(p.precio_venta) AS precio_minimo,
MAX(p.precio_venta) AS precio_maximo,
SUM(p.stock) AS stock_total
FROM productos p
JOIN categorias cat ON p.categoria_id = cat.categoria_id
GROUP BY cat.nombre_categoria
ORDER BY precio_promedio DESC;Completa la tabla de análisis:
| Categoría | ¿Tiene más de 1 producto? | ¿Es la categoría con mayor precio promedio? |
|---|---|---|
| Zapatillas | ||
| Poleras | ||
| Pantalones | ||
| Chaquetas | ||
| Mochilas | ||
| Gorros |
Pregunta 2.1: María quiere enfocar la campaña en las 2 categorías con mayor precio promedio. ¿Cuáles son?
4.2 Paso 8: Valor total del inventario
Roberto necesita saber cuánto capital tiene TechStyle inmovilizado en inventario:
-- Valor total del inventario al costo
SELECT SUM(stock * costo_unitario) AS "Capital en inventario ($)",
SUM(stock * precio_venta) AS "Valor potencial de venta ($)",
SUM(stock * precio_venta)
- SUM(stock * costo_unitario) AS "Margen potencial total ($)"
FROM productos;Pregunta 2.2: ¿Cuánto ganaría TechStyle si vendiera todo su inventario actual? ¿Es ese un escenario realista? ¿Por qué?
4.3 Paso 9: Valor de inventario por categoría — solo las que superan $2.000.000
SELECT cat.nombre_categoria,
SUM(p.stock * p.precio_venta) AS valor_inventario
FROM productos p
JOIN categorias cat ON p.categoria_id = cat.categoria_id
GROUP BY cat.nombre_categoria
HAVING SUM(p.stock * p.precio_venta) > 2000000
ORDER BY valor_inventario DESC;Ahora escribe tú la consulta que responde: ¿qué categorías tienen un precio promedio de venta superior a $40.000?
-- Escribe tu consulta aquí:
SELECT cat.nombre_categoria,
ROUND(AVG(p.precio_venta), 0) AS precio_promedio
FROM productos p
JOIN categorias cat ON p.categoria_id = cat.categoria_id
GROUP BY ...
HAVING ...
ORDER BY precio_promedio DESC;4.4 Paso 10: Ventas por mes
Roberto quiere ver la evolución mensual de ventas para identificar estacionalidad:
SELECT YEAR(fecha) AS año,
MONTH(fecha) AS mes,
COUNT(venta_id) AS total_ventas
FROM ventas
GROUP BY YEAR(fecha), MONTH(fecha)
ORDER BY año, mes;Pregunta 2.3: ¿En qué mes se concentran más ventas? ¿Qué limitación tiene este análisis con los datos actuales de TechStyle? (Pista: piensa en el volumen de datos disponible)
4.5 Paso 11: Clientes por segmento
SELECT segmento,
COUNT(cliente_id) AS cantidad_clientes,
COUNT(cliente_id) * 100.0
/ (SELECT COUNT(*) FROM clientes) AS porcentaje
FROM clientes
GROUP BY segmento
ORDER BY cantidad_clientes DESC;Pregunta 2.4: ¿Cuál es el segmento más numeroso? ¿Cuál debería recibir más atención en la estrategia de retención según Roberto?
La subquery (SELECT COUNT(*) FROM clientes) dentro del SELECT es una forma de calcular porcentajes sin necesitar una tabla adicional. La verás con más detalle en W09.
5 Parte 3 — JOINs: Cruzar Tablas para Reportes Completos
Duración: 40 minutos
5.1 Paso 12: Detalle completo de ventas (INNER JOIN)
La pregunta más básica que Roberto no puede responder solo con la tabla ventas: ¿quién compró qué y cuándo?
SELECT v.venta_id,
v.fecha,
c.nombre AS cliente,
c.apellido,
c.region,
p.nombre_producto AS producto,
dv.cantidad,
dv.precio_unitario,
ROUND(dv.precio_unitario
* dv.cantidad
* (1 - dv.descuento_pct / 100), 0) AS subtotal_con_descuento
FROM ventas v
JOIN clientes c ON v.cliente_id = c.cliente_id
JOIN detalle_ventas dv ON v.venta_id = dv.venta_id
JOIN productos p ON dv.producto_id = p.producto_id
ORDER BY v.fecha DESC, v.venta_id;Pregunta 3.1: ¿Qué línea de venta generó el mayor subtotal? ¿Cuál fue la compra total de cada cliente? (Estimación a partir del resultado — no uses SQL aún)
5.2 Paso 13: Total facturado por venta
Ahora agrega los subtotales de cada venta para obtener el total facturado:
SELECT v.venta_id,
v.fecha,
c.nombre AS cliente,
c.apellido,
COUNT(dv.producto_id) AS lineas_de_detalle,
ROUND(SUM(dv.precio_unitario
* dv.cantidad
* (1 - dv.descuento_pct / 100)), 0) AS total_facturado
FROM ventas v
JOIN clientes c ON v.cliente_id = c.cliente_id
JOIN detalle_ventas dv ON v.venta_id = dv.venta_id
GROUP BY v.venta_id, v.fecha, c.nombre, c.apellido
ORDER BY total_facturado DESC;Pregunta 3.2: ¿Cuál fue la venta de mayor valor? ¿Y el cliente con mayor gasto acumulado?
5.3 Paso 14: Estado de despachos con nombre del cliente (INNER JOIN)
Carlos necesita el reporte de estado de todos los despachos:
SELECT d.despacho_id,
c.nombre AS cliente,
c.apellido,
c.region,
d.fecha_despacho,
d.fecha_entrega_estimada,
d.fecha_entrega_real,
d.estado,
CASE
WHEN d.fecha_entrega_real IS NOT NULL
AND d.fecha_entrega_real <= d.fecha_entrega_estimada
THEN 'A tiempo'
WHEN d.fecha_entrega_real IS NOT NULL
AND d.fecha_entrega_real > d.fecha_entrega_estimada
THEN 'Atrasado'
WHEN d.estado = 'En tránsito'
THEN 'En camino'
ELSE 'Sin despachar'
END AS cumplimiento
FROM despachos d
JOIN ventas v ON d.venta_id = v.venta_id
JOIN clientes c ON v.cliente_id = c.cliente_id
ORDER BY d.estado, d.fecha_entrega_estimada;CASE WHEN ... THEN ... ELSE ... END es el equivalente SQL de un IF de Excel. Permite clasificar filas según condiciones, generando una columna calculada con categorías de texto. Lo usarás con frecuencia en reportes.
Pregunta 3.3: ¿Hay algún despacho atrasado? ¿Qué debería hacer Carlos con los despachos en estado 'Pendiente'?
5.4 Paso 15: Ventas con y sin despacho (LEFT JOIN)
¿Hay ventas que aún no tienen despacho asignado? Con INNER JOIN no lo sabrías — necesitas LEFT JOIN:
SELECT v.venta_id,
v.fecha,
c.nombre AS cliente,
d.despacho_id,
IFNULL(d.estado, '⚠ Sin despacho') AS estado_despacho
FROM ventas v
JOIN clientes c ON v.cliente_id = c.cliente_id
LEFT JOIN despachos d ON v.venta_id = d.venta_id
ORDER BY d.despacho_id IS NULL DESC, -- las ventas sin despacho aparecen primero
v.fecha;Pregunta 3.4: ¿Cuántas ventas tienen despacho asignado? ¿Cuántas no? ¿Qué implica para el área de operaciones?
5.5 Paso 16: Productos sin ventas (LEFT JOIN inverso)
María quiere saber qué productos nunca han sido vendidos — para evaluar si deben retirarse del catálogo:
SELECT p.nombre_producto,
p.precio_venta,
p.stock,
cat.nombre_categoria
FROM productos p
JOIN categorias cat ON p.categoria_id = cat.categoria_id
LEFT JOIN detalle_ventas dv ON p.producto_id = dv.producto_id
WHERE dv.producto_id IS NULL
ORDER BY p.precio_venta DESC;El patrón LEFT JOIN ... WHERE tabla_derecha.id IS NULL es la forma estándar de encontrar registros sin contraparte — en este caso, productos sin ninguna línea de venta. Es uno de los patrones más útiles para detectar datos huérfanos o inactivos.
Pregunta 3.5: ¿Hay productos sin ventas? ¿Qué recomendaría Juan a María con esta información?
6 Parte 4 — Síntesis: Reportes para el Equipo Directivo
Duración: 35 minutos
Con las herramientas aprendidas hoy, construye los reportes que Roberto, María y Carlos necesitan para tomar decisiones.
6.1 Paso 17: Reporte de Roberto — Productos más vendidos
Combina JOINs con agregaciones para identificar qué productos generan más ingresos:
SELECT p.nombre_producto,
cat.nombre_categoria,
SUM(dv.cantidad) AS unidades_vendidas,
ROUND(SUM(dv.precio_unitario
* dv.cantidad
* (1 - dv.descuento_pct / 100)), 0) AS ingresos_netos,
ROUND(AVG(dv.precio_unitario), 0) AS precio_promedio_venta
FROM detalle_ventas dv
JOIN productos p ON dv.producto_id = p.producto_id
JOIN categorias cat ON p.categoria_id = cat.categoria_id
GROUP BY p.producto_id, p.nombre_producto, cat.nombre_categoria
ORDER BY ingresos_netos DESC;Pregunta 4.1: ¿Cuál es el producto estrella de TechStyle? ¿Coincide el producto más vendido en unidades con el que genera más ingresos? ¿Qué implica esa diferencia (si existe)?
6.2 Paso 18: Reporte de María — Compras por segmento de cliente
SELECT cl.segmento,
COUNT(DISTINCT v.venta_id) AS total_ventas,
COUNT(DISTINCT cl.cliente_id) AS clientes_activos,
ROUND(SUM(dv.precio_unitario
* dv.cantidad
* (1 - dv.descuento_pct / 100)), 0) AS ingresos_totales,
ROUND(SUM(dv.precio_unitario
* dv.cantidad
* (1 - dv.descuento_pct / 100))
/ COUNT(DISTINCT v.venta_id), 0) AS ticket_promedio
FROM clientes cl
JOIN ventas v ON cl.cliente_id = v.cliente_id
JOIN detalle_ventas dv ON v.venta_id = dv.venta_id
GROUP BY cl.segmento
ORDER BY ingresos_totales DESC;Pregunta 4.2: ¿Qué segmento tiene el ticket promedio más alto? ¿Coincide con el segmento que genera más ingresos totales? ¿Qué estrategia podría diseñar María con este análisis?
6.3 Paso 19: Reporte de Carlos — Margen por proveedor
Carlos necesita saber qué proveedores abastecen los productos con mayor margen:
SELECT pv.nombre_proveedor,
COUNT(pp.producto_id) AS productos_abastecidos,
ROUND(AVG(p.precio_venta - pp.precio_compra), 0) AS margen_promedio,
ROUND(MIN(p.precio_venta - pp.precio_compra), 0) AS margen_minimo,
ROUND(MAX(p.precio_venta - pp.precio_compra), 0) AS margen_maximo
FROM proveedores pv
JOIN proveedor_producto pp ON pv.proveedor_id = pp.proveedor_id
JOIN productos p ON pp.producto_id = p.producto_id
GROUP BY pv.proveedor_id, pv.nombre_proveedor
ORDER BY margen_promedio DESC;Pregunta 4.3: ¿Con qué proveedor trabaja TechStyle de forma más rentable? ¿Qué negociación podría proponerle Carlos al proveedor con menor margen?
6.4 Paso 20: Documentar las 8 consultas del laboratorio
Copia cada consulta que respondió una pregunta de negocio y completa la siguiente tabla. Estas 8 consultas formarán la base del proyecto de Solemne 2.
| N° | Consulta (descripción breve) | Pregunta de negocio que responde | Persona interesada |
|---|---|---|---|
| Q1 | Catálogo ordenado por precio con margen bruto | ¿Qué productos tienen mayor margen? | María |
| Q2 | Alertas de stock bajo (< 100 unidades) | ¿Qué productos necesitan reposición? | Carlos |
| Q3 | Clientes por segmento con porcentaje | ¿Cómo se distribuye la base de clientes? | Roberto |
| Q4 | Valor de inventario por categoría (> $2.000.000) | ¿Dónde está concentrado el capital? | Roberto |
| Q5 | Detalle completo de ventas con subtotales | ¿Quién compró qué y a qué precio? | Roberto |
| Q6 | Estado de despachos con clasificación de cumplimiento | ¿Hay despachos atrasados? | Carlos |
| Q7 | Productos más vendidos por ingresos netos | ¿Cuáles son los productos estrella? | Roberto / María |
| Q8 | Margen promedio por proveedor | ¿Qué proveedor es más rentable? | Carlos |
Para cada consulta, agrega un comentario SQL con la pregunta de negocio que responde:
-- Q7: Productos más vendidos por ingresos netos
-- Pregunta: ¿Cuáles son los productos estrella de TechStyle?
-- Interesado: Roberto (decisión de campaña de invierno)
SELECT p.nombre_producto,
...7 Entregable
Sube a la plataforma los siguientes archivos comprimidos en W08_lab_[apellido1]_[apellido2].zip:
| Archivo | Contenido |
|---|---|
W08_consultas.sql |
Las 8 consultas documentadas con comentario de pregunta de negocio (Paso 20) |
W08_reportes.pdf o .docx |
Respuestas a las preguntas de análisis (1.1 a 4.3), con los resultados de al menos 3 consultas capturados en tabla o screenshot |
8 Criterios de Evaluación
| Criterio | Puntaje |
|---|---|
| Consultas de SELECT y WHERE correctas: sintaxis, filtros aplicados, resultados coherentes con los datos (Parte 1) | 25 pts |
| Consultas con GROUP BY y HAVING: agrupación correcta, función de agregación apropiada, HAVING bien usado (Parte 2) | 25 pts |
| JOINs correctos: tipo de JOIN apropiado (INNER vs LEFT), condición ON con las FK correctas, resultados completos (Parte 3) | 30 pts |
| Reportes documentados: 8 consultas con comentario de negocio, análisis escrito de al menos 3 preguntas (Parte 4) | 20 pts |
| Total | 100 pts |