Laboratorio 7: Completar, Operar y Consultar TechStyle

Semana 7 · W07 · Unidad II · Transacciones y Consultas · Universidad San Sebastián

1 Contexto

En W06 construiste la estructura de TechStyle y cargaste datos en 5 de las 9 tablas. Hoy completarás la base de datos cargando las 4 tablas restantes, practicarás transacciones para garantizar operaciones atómicas, y ejecutarás tus primeras consultas con JOIN para extraer información útil para el negocio.

Al terminar este laboratorio tendrás la base de datos completamente poblada, con operaciones transaccionales probadas y un primer vistazo a cómo se responden preguntas de negocio con SQL.


2 Instrucciones generales

  • Trabajarás en los mismos grupos de los laboratorios anteriores.
  • Duración total: 1 hora 25 minutos.
  • Necesitas la base de datos techstyle con los datos del laboratorio W06 ya cargados.

Herramientas necesarias:

Herramienta Uso
MySQL Workbench 8.0 Ejecución de scripts e inspección de resultados
MySQL Server 8.0 Motor donde está la base de datos techstyle

Estructura del laboratorio:

Parte Actividad Tiempo
1 Cargar las tablas faltantes 40 min
2 Primer vistazo a consultas con JOIN 25 min
3 Síntesis y reflexión 20 min

3 Parte 1 — Cargar las Tablas Faltantes desde CSV

Duración: 40 minutos

En W06 cargaste categorias, clientes, productos, ventas y detalle_ventas fila por fila con INSERT. Hoy cargarás las 4 tablas restantes desde archivos CSV usando LOAD DATA LOCAL INFILE — la forma estándar de cargar volúmenes de datos en MySQL sin escribir un INSERT por cada fila.

Los archivos CSV están disponibles en la carpeta labs/data/ del repositorio del curso:

Archivo Tabla destino Filas
proveedores.csv proveedores 4
proveedor_producto.csv proveedor_producto 10
despachos.csv despachos 3
devoluciones.csv devoluciones 1

Descárgalos y guárdalos en una carpeta local antes de continuar.

3.1 Paso 1: Habilitar la carga desde archivos locales

LOAD DATA LOCAL INFILE está deshabilitado por defecto en MySQL 8. Debes activarlo en dos lugares:

1a. En el servidor — ejecuta esto una sola vez como root:

SET GLOBAL local_infile = 1;

-- Verifica que quedó activado:
SHOW GLOBAL VARIABLES LIKE 'local_infile';
-- Debe mostrar: Value = ON

1b. En la conexión de Workbench — cierra la sesión actual y edita tu conexión:

  • Database → Manage Connections → selecciona tu conexión → pestaña Advanced
  • En el campo “Others”, agrega la línea: OPT_LOCAL_INFILE=1
  • Guarda y reconecta.
Important

Si no activas ambas opciones, MySQL retornará el error Loading local data is disabled. Ambas configuraciones son necesarias.

3.2 Paso 2: Cargar proveedores

USE techstyle;

LOAD DATA LOCAL INFILE '/ruta/a/proveedores.csv'
    INTO TABLE proveedores
    CHARACTER SET utf8mb4
    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
    LINES TERMINATED BY '\n'
    IGNORE 1 ROWS
    (proveedor_id, nombre_proveedor, rut, contacto, email, telefono);

SELECT COUNT(*) AS filas_cargadas FROM proveedores;
-- Resultado esperado: 4
Tip

Ajusta la ruta según tu sistema operativo:

  • Windows: 'C:/Users/tu_usuario/Downloads/proveedores.csv'
  • Mac: '/Users/tu_usuario/Downloads/proveedores.csv'
  • Linux: '/home/tu_usuario/Downloads/proveedores.csv'

En Windows usa barras hacia adelante (/), no hacia atrás (\).

Note

Adapta los nombres de columnas si tu modelo de W05 usa nombres distintos. El criterio de evaluación valorará la coherencia con tu propio diseño.

3.3 Paso 3: Cargar proveedor_producto

LOAD DATA LOCAL INFILE '/ruta/a/proveedor_producto.csv'
    INTO TABLE proveedor_producto
    CHARACTER SET utf8mb4
    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
    LINES TERMINATED BY '\n'
    IGNORE 1 ROWS
    (proveedor_id, producto_id, precio_compra, plazo_entrega_dias, proveedor_principal);

SELECT COUNT(*) AS filas_cargadas FROM proveedor_producto;
-- Resultado esperado: 10

Pregunta: ¿Por qué precio_compra se guarda en proveedor_producto y no directamente en productos? ¿Qué pasaría si un producto tuviera dos proveedores con precios distintos?

3.4 Paso 4: Cargar despachos (con valores NULL)

El archivo despachos.csv tiene filas donde fecha_despacho, fecha_entrega_estimada y fecha_entrega_real están vacíos — representan un despacho aún no procesado o no entregado. MySQL no convierte campos vacíos a NULL automáticamente, por lo que se necesita una cláusula SET con NULLIF:

LOAD DATA LOCAL INFILE '/ruta/a/despachos.csv'
    INTO TABLE despachos
    CHARACTER SET utf8mb4
    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
    LINES TERMINATED BY '\n'
    IGNORE 1 ROWS
    (venta_id, direccion_entrega, @fecha_despacho, @fecha_entrega_estimada, @fecha_entrega_real, estado)
SET
    fecha_despacho          = NULLIF(@fecha_despacho, ''),
    fecha_entrega_estimada  = NULLIF(@fecha_entrega_estimada, ''),
    fecha_entrega_real      = NULLIF(@fecha_entrega_real, '');

SELECT * FROM despachos;
Tip

¿Qué hace NULLIF(@variable, '')? Lee el campo como texto en una variable temporal (@fecha_despacho), y luego lo convierte a NULL si el texto es una cadena vacía. Si tiene valor, lo inserta tal cual.

Esto es distinto al NULL en claves foráneas — aquí NULL representa información genuinamente desconocida (el despacho aún no ha salido), no una referencia rota.

3.5 Paso 5: Cargar devoluciones

LOAD DATA LOCAL INFILE '/ruta/a/devoluciones.csv'
    INTO TABLE devoluciones
    CHARACTER SET utf8mb4
    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
    LINES TERMINATED BY '\n'
    IGNORE 1 ROWS
    (venta_id, producto_id, fecha_devolucion, cantidad_devuelta, motivo, estado, @monto_reembolsado)
SET
    monto_reembolsado = NULLIF(@monto_reembolsado, '');

SELECT COUNT(*) AS filas_cargadas FROM devoluciones;
-- Resultado esperado: 1
-- Proveedores
INSERT INTO proveedores (proveedor_id, nombre_proveedor, rut, contacto, email, telefono) VALUES
    (1, 'Nike Distribuciones Chile', '76.543.210-1', 'Felipe Morales',  'contacto@nikedist.cl',    '+56 2 2345 6789'),
    (2, 'Adidas Importaciones Sur',  '76.234.567-8', 'Carolina Vidal',  'ventas@adidassur.com.ar', '+54 11 4567 8901'),
    (3, 'Puma Latam Distribuidora',  '76.345.678-9', 'Lucas Ferreira',  'pedidos@pumalatam.com',   '+55 11 9876 5432'),
    (4, 'Multimarca Sport Ltda.',    '76.456.789-0', 'Andrea Salinas',  'info@multimarcasport.cl', '+56 9 8765 4321');

-- Proveedor–producto
INSERT INTO proveedor_producto (proveedor_id, producto_id, precio_compra, plazo_entrega_dias, proveedor_principal) VALUES
    (1, 1, 42000.00, 14, 1), (1, 5, 13500.00, 14, 1),
    (1, 9,  6500.00, 14, 1), (1,10,  8500.00, 14, 1),
    (2, 2, 62000.00, 21, 1), (2, 4, 11000.00, 21, 1),
    (3, 3, 26000.00, 21, 1), (3, 8, 18500.00, 21, 1),
    (4, 6, 20000.00,  7, 1), (4, 7, 47000.00,  7, 1);

-- Despachos
INSERT INTO despachos (venta_id, direccion_entrega, fecha_despacho, fecha_entrega_estimada, fecha_entrega_real, estado) VALUES
    (1, 'Av. Providencia 1234, Providencia, Santiago', '2026-01-16', '2026-01-19', '2026-01-18', 'Entregado'),
    (2, 'Cerro Alegre 567, Valparaíso',                '2026-01-21', '2026-01-24',  NULL,        'En tránsito'),
    (3, 'Av. O''Higgins 890, Concepción',               NULL,         NULL,          NULL,        'Pendiente');

-- Devoluciones
INSERT INTO devoluciones (venta_id, producto_id, fecha_devolucion, cantidad_devuelta, motivo, estado) VALUES
    (1, 4, '2026-01-25', 1, 'Talla incorrecta', 'Pendiente');

3.6 Paso 6: Verificar el estado completo de la base de datos

Ejecuta esta consulta resumen para confirmar que las 9 tablas tienen datos:

SELECT 'categorias'       AS tabla, COUNT(*) AS filas FROM categorias      UNION ALL
SELECT 'clientes',                  COUNT(*)           FROM clientes         UNION ALL
SELECT 'proveedores',               COUNT(*)           FROM proveedores      UNION ALL
SELECT 'productos',                 COUNT(*)           FROM productos        UNION ALL
SELECT 'ventas',                    COUNT(*)           FROM ventas           UNION ALL
SELECT 'detalle_ventas',            COUNT(*)           FROM detalle_ventas   UNION ALL
SELECT 'proveedor_producto',        COUNT(*)           FROM proveedor_producto UNION ALL
SELECT 'despachos',                 COUNT(*)           FROM despachos        UNION ALL
SELECT 'devoluciones',              COUNT(*)           FROM devoluciones;

Completa la tabla con los resultados:

Tabla Filas esperadas Filas reales ¿Coincide?
categorias 10
clientes 6
proveedores 4
productos 10
ventas 3
detalle_ventas 5
proveedor_producto 10
despachos 3
devoluciones 1

4 Parte 2 — Primer Vistazo a Consultas con JOIN

Duración: 25 minutos

Hasta ahora has usado INSERT y UPDATE. Ahora harás las primeras preguntas a la base de datos. Los JOIN que ves aquí se estudiarán en profundidad en W08 — por ahora, úsalos para leer los resultados y conectarlos con decisiones de negocio.

4.1 Paso 10: ¿De dónde vienen nuestros productos?

-- ¿Qué productos provee cada proveedor y a qué precio?
SELECT pv.nombre_proveedor,
       pr.nombre_producto,
       pp.precio_compra,
       pr.precio_venta,
       ROUND(pr.precio_venta - pp.precio_compra, 0) AS margen_bruto
FROM proveedores pv
JOIN proveedor_producto pp ON pv.proveedor_id = pp.proveedor_id
JOIN productos pr           ON pp.producto_id  = pr.producto_id
ORDER BY pv.nombre_proveedor, margen_bruto DESC;

Pregunta: ¿Cuál es el producto con mayor margen bruto? ¿Qué decisión de negocio podría tomar Roberto con esta información?

4.2 Paso 11: ¿Cuál es el estado de los despachos?

-- Estado de todos los despachos con nombre del cliente
SELECT d.despacho_id,
       c.nombre,
       c.apellido,
       c.region,
       d.direccion_entrega,
       d.fecha_despacho,
       d.fecha_entrega_estimada,
       d.fecha_entrega_real,
       d.estado
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;

Pregunta: ¿Qué debería hacer el equipo de logística con los despachos en estado 'Pendiente'?

4.3 Paso 12: ¿Hay devoluciones que atender?

-- Devoluciones pendientes con detalle del cliente y producto
SELECT dev.devolucion_id,
       c.nombre,
       c.apellido,
       p.nombre_producto,
       dev.motivo,
       dev.estado,
       dev.fecha_devolucion
FROM devoluciones dev
JOIN ventas v    ON dev.venta_id    = v.venta_id
JOIN clientes c  ON v.cliente_id   = c.cliente_id
JOIN productos p ON dev.producto_id = p.producto_id
WHERE dev.estado = 'Pendiente';
Note

Observa la estructura: siempre hay un FROM (tabla principal), y luego JOIN ... ON para cada relación que quieres cruzar. No necesitas memorizar la sintaxis hoy — en W08 la aprenderás formalmente. Hoy el objetivo es leer el resultado y conectarlo con el negocio.

Pregunta grupal: De las tres consultas anteriores (proveedores, despachos, devoluciones), ¿cuál respondería una pregunta que no era posible responder con una hoja de Excel simple? ¿Por qué?


5 Parte 3 — Síntesis y Reflexión

Duración: 20 minutos

5.1 Paso 13: Comparar el modelo diseñado vs. el modelo implementado

Dimensión Modelo en papel (W05) Base de datos real (W06–W07)
¿Se puede saber qué proveedor abastece un producto? Sí (diagrama)
¿Las FK impiden ventas de clientes inexistentes? En diseño
¿Un producto puede tener stock negativo? Según diseño, no
¿Qué pasa si se borra un cliente con ventas? RESTRICT (diseño)

5.2 Paso 14: Preguntas de diseño e implementación

Responde en grupo:

Pregunta Respuesta
¿Por qué se usa SET @variable = LAST_INSERT_ID() en lugar de poner el número directamente (ej. venta_id = 3)?
¿Qué pasaría si dos vendedores insertan una venta para el mismo cliente al mismo tiempo sin transacciones?
¿Por qué precio_unitario se guarda en detalle_ventas y no se calcula desde productos.precio_venta en cada consulta?
Si TechStyle aplica un descuento general del 20% en una promoción, ¿qué operación SQL ejecutarías? ¿Dentro de una transacción?
Un cliente quiere borrar su cuenta. ¿Qué debe hacer Juan antes de ejecutar DELETE FROM clientes WHERE cliente_id = 1?

5.3 Paso 15: Análisis de impacto — del modelo a las decisiones

Para cada capacidad implementada, identifica la decisión de negocio que habilita:

Capacidad implementada Decisión de negocio que habilita
FK con ON DELETE RESTRICT en ventas.cliente_id Ej: “Garantizamos que el historial de ventas nunca pierde la referencia al cliente”
CHECK stock >= 0 en productos
Índice idx_ventas_fecha en ventas(fecha)
CHECK segmento IN ('Premium', 'Estándar', 'Básico')

5.4 Paso 16: Reporte de implementación

Redacta un párrafo breve (6–8 líneas) como si fueras Juan informando a Roberto sobre el estado del sistema al cierre del día:

Para: Roberto Fernández, CEO TechStyle

Asunto: Base de datos completamente operativa — estado al 2026-04-28

La base de datos techstyle está completamente poblada en MySQL Server. Contiene… (continúa)

Incluye: - Cuántas tablas están operativas y cuántos registros tiene cada una. - Qué restricciones de integridad garantizan la consistencia de los datos. - Qué puede hacerse ahora que no era posible con Excel. - Qué falta aún para que el sistema sea completamente operativo.


6 Entregable

Sube a la plataforma los siguientes archivos comprimidos en W07_lab_[apellido1]_[apellido2].zip:

Archivo Contenido
W07_carga.sql Script con los comandos LOAD DATA LOCAL INFILE de la Parte 1 (ajustados a tu ruta real)
W07_reflexion.pdf o .docx Respuestas de las preguntas de reflexión + reporte para Roberto

7 Criterios de Evaluación

Criterio Puntaje
Carga completa de las 4 tablas faltantes, verificación de filas (Parte 1) 40 pts
Consultas con JOIN: resultados correctos, respuestas a preguntas de negocio (Parte 2) 30 pts
Síntesis: análisis de impacto, preguntas de diseño y reporte para Roberto (Parte 3) 30 pts
Total 100 pts