🔐 Curso SQL · Nivel N8 · Certificación

Bloque 8: Administración y proyecto final

Cierras el curso como quien gobierna el servidor: creas usuarios y repartes permisos con el mínimo privilegio, proteges las escrituras con transacciones ACID, blindas tu base con copias de seguridad y seguridad de datos, y rematas con el proyecto final de e-commerce. Al terminar, te ganas el título: consultas a nivel de DBA. 🎓

⏱ ≈ 21 horas 📚 5 lecciones ✍️ 15 ejercicios guiados 📝 Examen final + Certificado
Lección 1 · Teoría

Usuarios y permisos

Tu base de datos no se accede con una sola cuenta todopoderosa. Creas usuarios por rol (la app, el lector de informes, el administrador) y repartes privilegios con el principio del mínimo privilegio: cada cuenta, lo justo que necesita y nada más.

1.1 · Crear y dar permisos

usuario para la aplicación
CREATE USER 'app_tienda'@'localhost'
IDENTIFIED BY 'clave_fuerte_mayus9!';

GRANT SELECT, INSERT, UPDATE, DELETE
ON tw_ecommerce.* TO 'app_tienda'@'localhost';

tw_ecommerce.* significa «todas las tablas de esa base». Los privilegios y tablas pueden precisarse más: GRANT SELECT ON tw_ecommerce.pagos TO ….

1.2 · Retirar y comprobar

quitar DELETE a nivel de base y ver los permisos
REVOKE DELETE ON tw_ecommerce.* FROM 'app_tienda'@'localhost';

SHOW GRANTS FOR 'app_tienda'@'localhost';
⚠️ REVOKE encaja con lo que antes concediste: lo dado con ON tw_ecommerce.* se revoca con ON tw_ecommerce.*. Si quisieras retirar un permiso SOLO de una tabla concreta (p. ej. pagos), tendrías que haberlo concedido de forma explícita para esa tabla antes; un revoke a nivel de tabla no resta lo concedido con .*. Este comportamiento es el mismo en MySQL y MariaDB.
⚠️ Hábitos de admin: contraseñas fuertes y únicas, un usuario por rol, nunca conceder GRANT OPTION de gratis y auditar con SHOW GRANTS antes y después de tocar permisos.
🎉 Ya da de comer a la app un usuario con lo justo. Ahora a garantizar que cada escritura importante quedé bien o no quede: transacciones.
Lección 2 · Teoría

Transacciones (ACID)

Una transacción agrupa varias operaciones en una sola unidad: o se aplican todas o ninguna. De ahí cuatro propiedades que todos citan con siglas: Atomicity (indivisible), Consistency (estado coherente), Isolation (aisladas entre sí) e Durability (si queda hecha, queda hecha).

2.1 · COMMIT y ROLLBACK

un pedido: cabecera + líneas, en una misma transacción
START TRANSACTION;

INSERT INTO pedidos (usuario_id, estado) VALUES (2, 'creado');
INSERT INTO pedido_detalle (pedido_id, producto_id, cantidad, precio_unitario)
VALUES (LAST_INSERT_ID(), 1, 1, 449.00);

COMMIT;
algo ha fallado: nada se queda en la tabla
START TRANSACTION;
UPDATE productos SET stock = stock - 2 WHERE id = 1;
-- ups, el pago no llega…
ROLLBACK;

2.2 · SAVEPOINT: quedarte a mitad

No siempre quieres deshacerlo todo: SAVEPOINT marca un punto intermedio y ROLLBACK TO vuelve solo hasta ese punto.

deshacer solo la línea que hizo aguas
START TRANSACTION;
INSERT INTO usuarios (nombre, email, password_hash) VALUES
('Ana Polo', 'ana@tienda.es', '$2y$11$hash');
SAVEPOINT antes_linea;

INSERT INTO direcciones (usuario_id, calle, ciudad, cp) VALUES
(LAST_INSERT_ID(), 'C/ Sur 4', 'Zaragoza', '50001');
-- CP no válido… deshacemos solo la dirección
ROLLBACK TO antes_linea;

COMMIT;
💡 En InnoDB casi todo es transaccional de forma discreta: sin START TRANSACTION, cada sentencia se confirma sola (autocommit). La transacción explícita es para bloques donde «o todo, o nada».
🎉 Escrituras blindadas. Si tu servidor se muere mañana, ¿qué pasa? Que tienes copias de seguridad.
Lección 3 · Teoría

Copias de seguridad

El dato vale más que el servidor. La herramienta clásica de MySQL es mysqldump: vuelca esquema y datos a un fichero .sql que luego se restaura con un simple import.

3.1 · Hacer y restaurar un backup

así se hace desde la terminal
# volcar la base completa
mysqldump -u root -p tw_ecommerce > backups/tw_ecommerce_2026.sql

# restaurar meses después en un servidor nuevo
mysql -u root -p < backups/tw_ecommerce_2026.sql

3.2 · Variantes útiles

mysqldump --single-transaction — volcado sin bloquear escrituras (InnoDB)
--no-data — solo esquema (CREATE TABLE), sin filas
--no-create-info — solo datos, para sumarlos a un esquema existente
Comprime el volcado: mysqldump … | gzip > backup.sql.gz
⚠️ Un backup que nunca se prueba no es un backup. Programa la copia nocturna, pero también ensaya la restauración en otro equipo: es el único simulacro que vale la pena.
💡 Para sitios grandes conviene réplicas y binlog (copia punto a punto), pero el «backup con mysqldump + restauración probada» ya coloca tu e-commerce por encima del 90 % de las bases de datos del mundo.
🎉 Copias al día. Ahora que nadie robe ni destroce los datos: seguridad.
Lección 4 · Teoría

Seguridad de datos

Tres frentes de seguridad trabajan en tu esquema: contraseñas no legibles, consulta sin inyección y tipos de dato modernos (como JSON).

4.1 · Ni una contraseña en claro

El campo password_hash VARCHAR(255) guarda un hash (bcrypt aquí): nunca la contraseña original. En tu código solo guardas password_hash() y verificas con password_verify().

jamás así
INSERT INTO usuarios (nombre, email, password_hash)
VALUES ('Ana', 'ana@tienda.es', 'clara12');

4.2 · SQL injection y consultas paramétricas

Si pegas $_POST['email'] en una consulta, un atacante la reescribe. La defensa es no pegar nunca texto en SQL: usar consultas con parámetros (prepared statements). Antes de escribir cualquier WHERE, pregúntate si el valor viene del usuario.

la columna queda limpia; el valor va fuera
-- versión insegura (nunca)
SELECT * FROM usuarios
WHERE email = 'clara@tienda.es' OR 1=1';

-- versión segura con placeholder
SELECT * FROM usuarios WHERE email = ?;

4.3 · Tipos modernos: JSON

Las columnas JSON guardan atributos flexibles sin explotar en tablas: metadatos de producto, preferencias… Se consultan con JSON_VALUE o ->>:

autonomía del patinete desde su JSON
SELECT nombre,
       JSON_VALUE(datos_extra, '$.autonomia_km') AS autonomia_km
FROM   productos
WHERE  categoria_id = 1;
💡 JSON es comodín: no lo uses para todo. Si un atributo cuenta para informes o filtros, mejor su columna propia. JSON para lo ocasional, columnas para lo que se consulta.
🎉 Servidor blindado. Llega lo mejor del bloque: unir todo en tu proyecto final.
Lección 5 · Teoría

Proyecto final e-commerce

Este bloque se cierra con un proyecto final que integra los 8 bloques: administras la base tw_ecommerce (normalizada en el bloque 5, poblada aquí con datos reales) como si fuera una tienda de producción. Operaciones típicas de tu «trabajo»:

5.1 · El arranque de un día de tienda

un solo usuario y un SELECT para verificar
SELECT u.nombre, p.fecha, p.estado
FROM   pedidos p
INNER JOIN usuarios u ON u.id = p.usuario_id
WHERE p.estado = 'creado'
ORDER BY p.fecha DESC;

5.2 · Tu guion de despliegue

permisos + backup + consulta = proyecto al día
CREATE USER 'app_tienda'@'localhost' IDENTIFIED BY 'clave_fuerte';
GRANT SELECT, INSERT, UPDATE ON tw_ecommerce.* TO 'app_tienda'@'localhost';

-- nada de borrar desde la app
REVOKE DELETE ON tw_ecommerce.* FROM 'app_tienda'@'localhost';

-- mysqldump -u root -p tw_ecommerce > backup.sql
SELECT * FROM pagos;
⚠️ Todo en orden: esquema normalizado, usuarios con mínimo privilegio, transacción para cada pedido, backup diario y consultas parametrizadas en la app. Eso era… el proyecto.
🎉 Has gobernado el servidor de principio a fin. Abre el capítulo de ejercicios, vale la pena: con esto te lleva el certificado final del curso.
Manos a la obra

✍️ Los 15 ejercicios del bloque

Abre MySQL Workbench y ejecuta sobre la base tw_ecommerce. Usa root para los ejercicios de administración (usuarios, permisos) y el usuario app_tienda solo para consulta. Añade un comentario -- con el número de cada ejercicio.

📥 ¿No tienes la base de datos? Descárgala: sql/sql-bloque8_ecommerce.sql   y cárgala con mysql -u root -p < sql/sql-bloque8_ecommerce.sql.

Sección A · Usuarios y permisos

1 Crear el usuario de la app

Crea el usuario 'app_tienda'@'localhost' con CREATE USER y contraseña 'clave_fuerte_mayus9!'.

Ver solución
-- Ejercicio 1: usuario de la aplicación
CREATE USER 'app_tienda'@'localhost'
IDENTIFIED BY 'clave_fuerte_mayus9!';

2 Dar permisos a la app

Con GRANT da a 'app_tienda'@'localhost' los privilegios SELECT, INSERT y UPDATE (no DELETE) sobre tw_ecommerce.*.

Ver solución
-- Ejercicio 2: mínimo privilegio para la app
GRANT SELECT, INSERT, UPDATE
ON tw_ecommerce.* TO 'app_tienda'@'localhost';

3 Quitar un privilegio

La app ya no debe poder borrar nada: REVOKE DELETE ON tw_ecommerce.* de 'app_tienda'@'localhost' (mantiene SELECT, INSERT y UPDATE intactos).

Ver solución
-- Ejercicio 3: quitar DELETE a nivel de base
REVOKE DELETE ON tw_ecommerce.* FROM 'app_tienda'@'localhost';

4 Comprobar los permisos

Muestra con SHOW GRANTS todos los permisos de 'app_tienda'@'localhost'.

Ver solución
-- Ejercicio 4: auditoría de permisos
SHOW GRANTS FOR 'app_tienda'@'localhost';

Sección B · Transacciones

5 Deshacer una escritura

Abre una transacción (START TRANSACTION), actualiza el stock del producto 1, comprueba el cambio y deshazlo con ROLLBACK.

Ver solución
-- Ejercicio 5: transacción deshecha
START TRANSACTION;
UPDATE productos SET stock = stock - 2 WHERE id = 1;
-- SELECT stock FROM productos WHERE id = 1; (cambia)
ROLLBACK;

6 Pedido completo y confirmado

En una transacción añade un pedido para el usuario 2, su línea de pedido_detalle con el producto 1, y termina con COMMIT para quedarlo.

Ver solución
-- Ejercicio 6: pedido que llega al final
START TRANSACTION;
INSERT INTO pedidos (usuario_id, estado) VALUES (2, 'creado');
INSERT INTO pedido_detalle (pedido_id, producto_id, cantidad, precio_unitario)
VALUES (LAST_INSERT_ID(), 1, 1, 449.00);
COMMIT;

7 SAVEPOINT: a mitad del camino

Con START TRANSACTION, inserta un usuario, marca SAVEPOINT s1, inserta una dirección que luego deshaces con ROLLBACK TO s1, y cierra con COMMIT.

Ver solución
-- Ejercicio 7: punto intermedio
START TRANSACTION;
INSERT INTO usuarios (nombre, email, password_hash)
VALUES ('Zoe Ruiz', 'zoe@tienda.es', '$2y$11$hashfake');
SAVEPOINT s1;

INSERT INTO direcciones (usuario_id, calle, ciudad, cp)
VALUES (LAST_INSERT_ID(), 'C/ Norte 2', 'Bilbao', '000');

ROLLBACK TO s1;
COMMIT;

Sección C · Copias de seguridad

8 Volcar la base (terminal)

Desde la terminal, haz una copia completa de tw_ecommerce a un fichero con mysqldump (no hace falta escribirlo en el fichero SQL).

Ver solución
-- Ejercicio 8: volcado completo (terminal)
mysqldump -u root -p tw_ecommerce > backups/tw_ecommerce_dia.sql

9 Restaurar la copia (terminal)

Simula la restauración del backup tw_ecommerce_dia.sql en el servidor con el cliente mysql.

Ver solución
-- Ejercicio 9: restauración (terminal)
mysql -u root -p < backups/tw_ecommerce_dia.sql

10 Solo esquema (terminal)

Vuelca únicamente el esquema (sin datos) de tw_ecommerce con mysqldump --no-data.

Ver solución
-- Ejercicio 10: solo la estructura
mysqldump -u root -p --no-data tw_ecommerce > backups/tw_ecommerce_schema.sql

Sección D · Seguridad de datos

11 Leer un JSON

Muestra nombre y la autonomía ($.autonomia_km) con JSON_VALUE(datos_extra, …) de los productos de la categoría 1.

Ver solución
-- Ejercicio 11: consultar un campo JSON
SELECT nombre,
       JSON_VALUE(datos_extra, '$.autonomia_km') AS autonomia_km
FROM   productos
WHERE  categoria_id = 1;

12 Login nunca en claro

Consulta el usuario de Clara solo por email, mostrando id, nombre y password_hash (¡el hash, nunca la contraseña real!).

Ver solución
-- Ejercicio 12: el hash, jamás el texto plano
SELECT id, nombre, password_hash
FROM   usuarios
WHERE  email = 'clara@tienda.es';

13 Pago con su importe esperado

Muestra los pagos con pedido_id, metodo e importe cuyo importe sea mayor o igual a 100 y ordena por importe descendente.

Ver solución
-- Ejercicio 13: importes relevantes
SELECT pedido_id, metodo, importe
FROM   pagos
WHERE  importe >= 100.00
ORDER BY importe DESC;

Sección E · Consultas de la tienda

14 La factura de un pedido

Junta pedidos, usuarios, pedido_detalle y productos para mostrar u.nombre, pr.nombre, d.cantidad y d.precio_unitario del pedido 1.

Ver solución
-- Ejercicio 14: factura completa
SELECT u.nombre, pr.nombre AS producto,
       d.cantidad, d.precio_unitario
FROM   pedidos p
INNER JOIN usuarios u       ON u.id  = p.usuario_id
INNER JOIN pedido_detalle d ON d.pedido_id = p.id
INNER JOIN productos pr     ON pr.id = d.producto_id
WHERE  p.id = 1;

15 Reponer el stock

Consulta nombre y stock de los productos con stock menor que 10, ordenados por stock (menores primero).

Ver solución
-- Ejercicio 15: bajo de stock
SELECT nombre, stock
FROM   productos
WHERE  stock < 10
ORDER BY stock;

16 🏆 Proyecto final: guion de despliegue seguro

Crea el fichero despliegue_seguro.sql que prepare el e-commerce de producción. Requisitos:

  • Crea la cuenta 'app_tienda'@'localhost' con CREATE USER.
  • Dale solo lo necesario con GRANT (SELECT, INSERT, UPDATE sobre tw_ecommerce.*).
  • Quítale DELETE a nivel de base (ON tw_ecommerce.*) con REVOKE.
  • Abre una transacción con START TRANSACTION.
  • Inserta un pedido del usuario 2 (con INSERT) y una línea de detalle con LAST_INSERT_ID().
  • Confirma con COMMIT.
  • Verifica con un SELECT que una pedidos con usuarios (JOIN) y filtra WHERE estado = 'creado'.
  • Ordena la verificación con ORDER BY fecha DESC.
Ver una solución posible
-- Proyecto final: despliegue seguro del e-commerce
CREATE USER 'app_tienda'@'localhost' IDENTIFIED BY 'clave_fuerte_mayus9!';
GRANT SELECT, INSERT, UPDATE ON tw_ecommerce.* TO 'app_tienda'@'localhost';
REVOKE DELETE ON tw_ecommerce.* FROM 'app_tienda'@'localhost';

START TRANSACTION;
INSERT INTO pedidos (usuario_id, estado) VALUES (2, 'creado');
INSERT INTO pedido_detalle (pedido_id, producto_id, cantidad, precio_unitario)
VALUES (LAST_INSERT_ID(), 1, 1, 449.00);
COMMIT;

-- verificación final
SELECT u.nombre, p.id AS pedido_id, p.estado, p.fecha
FROM   pedidos p
INNER JOIN usuarios u ON u.id = p.usuario_id
WHERE  p.estado = 'creado'
ORDER BY p.fecha DESC;
Evaluación final del bloque

📝 Examen del Bloque 8

Dos partes: un test teórico autoevaluable (mínimo 70 %) y el reto práctico final de autocorrección con laboratorio. Supera ambas y el curso de SQL es tuyo. 🎓

Parte A · Test teórico (aprueba con 11/15)

1 · ¿Qué sentencia crea una cuenta?
2 · ¿Con qué sentencia das permisos?
3 · ¿Con qué sentencia quitas permisos?
4 · El principio del mínimo privilegio dice…
5 · ¿Qué muestra los permisos de un usuario?
6 · Para que varias sentencias se guarden juntas, se usa…
7 · ¿Qué deshace TODAS las operaciones desde el inicio de la transacción?
8 · ¿Con qué vuelves a un punto intermedio marcado antes?
9 · ¿Qué herramienta vuelca la base a un fichero?
10 · Para restaurar un backup tipo backup.sql…
11 · La defensa contra SQL injection es…
12 · Las contraseñas deben guardarse…
13 · La columna JSON sirve para…
14 · ON DELETE CASCADE en una FK hace que…
15 · Un backup que «nunca se prueba»…

Parte B · Reto «despliegue seguro»

Crea despliegue_seguro.sql: usuario de la app, permisos con mínimos privilegios, transacción para un pedido y verificación con JOIN. Tu SQL no se ejecuta en el laboratorio: se valida con reglas automáticas. Mínimo para aprobar: 6 de 8 casillas.

Cumplidos: 0 / 8

🎓 ¿Superaste las dos partes? ENHORABUENA: has terminado el curso de SQL de Técnico Web. Desde el primer SELECT hasta administrar un servidor: eres SQL de 0 a nivel experto. Actualiza tu perfil, añade «SQL · TécnicoWeb 2026» y ve a por el siguiente reto. 🔥
Volver al catálogo de cursos