Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions prisma/content/courses/bases-de-datos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,39 @@ import { bd1Dml } from "./units/08-dml";
import { bd1ConsultasReportes } from "./units/09-consultas-reportes";
import { bd1RespaldoIntegrador } from "./units/10-respaldo-integrador";

import { bd2Procedimientos } from "./units/11-procedimientos";
import { bd2TriggersJobs } from "./units/12-triggers-jobs";
import { bd2Transacciones } from "./units/13-transacciones";
import { bd2UsuariosPermisos } from "./units/14-usuarios-permisos";
import { bd2Mantenimiento } from "./units/15-mantenimiento";
import { bd2Conexiones } from "./units/16-conexiones";
import { bd2CrudInterfaz } from "./units/17-crud-interfaz";
import { bd2NosqlModelo } from "./units/18-nosql-modelo";
import { bd2MongodbCrud } from "./units/19-mongodb-crud";
import { bd2Integrador } from "./units/20-integrador";

// =====================================================================
// Paquete de curso: Bases de datos (Base de Datos I, S4).
// Paquete de curso: Bases de datos (Base de Datos I, S4 + Base de Datos
// II, S5).
//
// Curso nuevo y lenguaje nuevo de plataforma: `sql`, perfil
// `sql-sqlite3-wandbox` (ver TECHNICAL_CONTRACT del paquete de contenido
// `bd1-ceti-content-pack`). El SQL didáctico usa el subconjunto relacional
// portable compatible con SQLite; backup/restore real y la GUI de
// escritorio que pide el programa oficial quedan como laboratorio local.
//
// 10 unidades, una sola CurriculumSection semestral, las 10 nacen
// `published: true` — sin release gate para esta entrega.
// S5 (Base de Datos II, ver TECHNICAL_CONTRACT de `db2-ceti-content-pack`)
// extiende el MISMO Course con procedimientos, triggers/jobs,
// transacciones/ACID, usuarios/permisos, mantenimiento, conexión de
// aplicaciones, CRUD/vistas/informes, modelo documental, CRUD MongoDB y un
// integrador — sin agregar lenguaje, provider ni schema nuevos. Las
// capacidades que SQLite no soporta honestamente (CREATE PROCEDURE,
// Event Scheduler, GRANT/REVOKE, mantenimiento específico de MySQL,
// conexión C#↔MySQL y todo MongoDB) quedan como laboratorio local
// (`runnable:false` + `localOnlyNote`), nunca adaptadas artificialmente.
//
// 20 unidades en total (10 + 10), dos CurriculumSection semestrales; las
// 20 nacen `published: true` — sin release gate para esta entrega.
// =====================================================================

export const basesDeDatos = defineCourse({
Expand Down Expand Up @@ -51,5 +73,22 @@ export const basesDeDatos = defineCourse({
bd1RespaldoIntegrador,
],
},
{
key: "s5-base-de-datos-2",
semester: 5,
subjectName: "Base de Datos II",
units: [
bd2Procedimientos,
bd2TriggersJobs,
bd2Transacciones,
bd2UsuariosPermisos,
bd2Mantenimiento,
bd2Conexiones,
bd2CrudInterfaz,
bd2NosqlModelo,
bd2MongodbCrud,
bd2Integrador,
],
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineUnit } from "../../../../authoring";

import { leccion01 } from "./lessons/01-por-que-procedimientos";
import { leccion02 } from "./lessons/02-parametros-contrato";
import { leccion03 } from "./lessons/03-create-procedure-mysql";
import { leccion04 } from "./lessons/04-limites-procedimientos";

export const bd2Procedimientos = defineUnit({
slug: "bd2-11-procedimientos",
title: "Procedimientos almacenados y modularización",
description:
"Diseña el contrato de un procedimiento almacenado y reconoce cuándo modularizar dentro del SGBD.",
icon: "🧱",
published: true,
lessons: [leccion01, leccion02, leccion03, leccion04],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { defineLesson } from "../../../../../authoring";

export const leccion01 = defineLesson({
slug: "por-que-procedimientos",
title: "Por qué modularizar dentro del SGBD",
description: "Entender el contrato de un procedimiento almacenado.",
estimatedMinutes: 15,
xpReward: 45,
steps: [
{
type: "theory",
markdown: `Un procedimiento almacenado encapsula una operación nombrada dentro del SGBD. Puede recibir parámetros, ejecutar varias sentencias y concentrar reglas cerca de los datos. No es automáticamente mejor que lógica en la aplicación: su valor aparece cuando centralización, permisos o reutilización lo justifican.`,
},
{
type: "matching",
pairs: [
{ left: "Operación repetida", right: "Candidata a encapsulación" },
{ left: "Parámetro", right: "Entrada" },
{ left: "Efecto", right: "Cambio observable" },
{ left: "CALL", right: "Invocación MySQL" },
],
explanation: "Primero entiende el contrato.",
},
{
type: "quiz",
question: "¿Qué beneficio puede aportar un procedimiento?",
options: [
"Evitar diseñar",
"Centralizar una operación reutilizable",
"Eliminar transacciones",
"Reemplazar permisos",
],
correctIndex: 1,
explanation: "Modulariza una operación bien definida.",
},
{
type: "fill_blank",
template: "procedimiento = nombre + {{0}} + operación + efecto",
blanks: [{ answer: "parámetros" }],
explanation: "El contrato debe ser explícito.",
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { defineLesson } from "../../../../../authoring";

export const leccion02 = defineLesson({
slug: "parametros-contrato",
title: "Parámetros y pre/postcondiciones",
description: "Diseñar entradas y efectos antes de escribir sintaxis.",
estimatedMinutes: 15,
xpReward: 45,
steps: [
{
type: "theory",
markdown: `Antes de crear \`cerrar_ticket(id)\` define precondición, efecto y comportamiento si el ticket no existe. La definición del contrato importa más que memorizar delimitadores.`,
},
{
type: "matching",
pairs: [
{ left: "IN", right: "Entrada" },
{ left: "OUT", right: "Salida" },
{ left: "INOUT", right: "Entrada/salida" },
{ left: "Precondición", right: "Debe cumplirse antes" },
{ left: "Postcondición", right: "Debe cumplirse después" },
],
explanation: "Parámetros son parte de la interfaz.",
},
{
type: "quiz",
question: "¿Qué debe quedar claro primero?",
options: ["Editor", "Qué recibe y qué cambia", "Número de líneas", "Color"],
correctIndex: 1,
explanation: "Un contrato verificable evita operaciones opacas.",
},
{
type: "fill_blank",
template: "precondición → CALL → {{0}}",
blanks: [{ answer: "postcondición" }],
explanation: "La postcondición describe el resultado.",
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { defineLesson } from "../../../../../authoring";

export const leccion03 = defineLesson({
slug: "create-procedure-mysql",
title: "CREATE PROCEDURE en MySQL",
description: "Leer un procedimiento real como laboratorio local.",
estimatedMinutes: 15,
xpReward: 45,
steps: [
{
type: "theory",
markdown: `MySQL implementa procedimientos con sintaxis propia. SQLite no los soporta; por eso el ejemplo es local-only y no se adapta artificialmente.`,
},
{
type: "code_example",
code: `DELIMITER //
CREATE PROCEDURE cerrar_ticket(IN p_id INT)
BEGIN
UPDATE ticket SET estado='CERRADO' WHERE id=p_id;
END//
DELIMITER ;
CALL cerrar_ticket(10);`,
explanation: "Define e invoca una operación.",
runnable: false,
localOnlyNote: "Requiere MySQL 8.x local.",
},
{
type: "quiz",
question: "¿Por qué runnable:false?",
options: [
"Seguridad",
"SQLite no soporta CREATE PROCEDURE",
"UPDATE no existe",
"MySQL no usa SQL",
],
correctIndex: 1,
explanation: "No ejecutar sintaxis específica contra otro SGBD.",
},
{
type: "matching",
pairs: [
{ left: "CREATE PROCEDURE", right: "Definir" },
{ left: "CALL", right: "Invocar" },
{ left: "IN p_id", right: "Entrada" },
{ left: "UPDATE", right: "Efecto" },
],
explanation: "Reconoce estructura.",
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { defineLesson } from "../../../../../authoring";

export const leccion04 = defineLesson({
slug: "limites-procedimientos",
title: "Cuándo no usarlos",
description: "Reconocer acoplamiento y complejidad.",
estimatedMinutes: 15,
xpReward: 45,
steps: [
{
type: "theory",
markdown: `La lógica propietaria dentro del SGBD reduce portabilidad y también necesita versionado, pruebas y migraciones. No conviertas cada query simple en un procedimiento.`,
},
{
type: "matching",
pairs: [
{ left: "Regla compartida por varias apps", right: "Puede justificarlo" },
{ left: "SELECT simple", right: "Normalmente no" },
{ left: "Sintaxis propietaria", right: "Reduce portabilidad" },
{ left: "Migración", right: "Debe versionarlo" },
],
explanation: "Hay beneficios y costos.",
},
{
type: "quiz",
question: "¿Cuál criterio es correcto?",
options: [
"Todo procedimiento",
"Nunca procedimientos",
"Usarlos cuando el beneficio supera el acoplamiento",
"Sólo por requisito académico",
],
correctIndex: 2,
explanation: "Es una decisión de diseño.",
},
{
type: "fill_blank",
template: "beneficio > costo de {{0}}",
blanks: [{ answer: "acoplamiento" }],
explanation: "Evalúa el trade-off.",
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineUnit } from "../../../../authoring";

import { leccion01 } from "./lessons/01-evento-accion";
import { leccion02 } from "./lessons/02-trigger-auditoria";
import { leccion03 } from "./lessons/03-trigger-vs-restriccion";
import { leccion04 } from "./lessons/04-jobs";
import { leccion05 } from "./lessons/05-automatizacion-segura";

import { practice } from "./practice";

export const bd2TriggersJobs = defineUnit({
slug: "bd2-12-triggers-jobs",
title: "Triggers y automatización programada",
description:
"Reacciona a eventos de datos con triggers, distingue jobs por tiempo y diseña automatización observable.",
icon: "⚡",
published: true,
lessons: [leccion01, leccion02, leccion03, leccion04, leccion05],
practice,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { defineLesson } from "../../../../../authoring";

export const leccion01 = defineLesson({
slug: "evento-accion",
title: "Evento y acción automática",
description: "Entender trigger como reacción del SGBD.",
estimatedMinutes: 15,
xpReward: 45,
steps: [
{
type: "theory",
markdown: `Un trigger ejecuta lógica cuando ocurre un evento sobre datos. Es útil para auditoría e invariantes que no encajan mejor en restricciones, pero puede ocultar efectos si no se documenta.`,
},
{
type: "matching",
pairs: [
{ left: "INSERT", right: "Evento" },
{ left: "AFTER INSERT", right: "Momento" },
{ left: "NEW", right: "Fila nueva" },
{ left: "auditoria", right: "Efecto" },
],
explanation: "Un trigger tiene causa y efecto.",
},
{
type: "quiz",
question: "¿Riesgo de abusar de triggers?",
options: [
"No modifican nada",
"Efectos implícitos difíciles de rastrear",
"No existen",
"Siempre son lentos",
],
correctIndex: 1,
explanation: "La automatización invisible complica diagnóstico.",
},
{
type: "fill_blank",
template: "evento + momento + {{0}} = trigger",
blanks: [{ answer: "acción" }],
explanation: "La causalidad debe ser clara.",
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineLesson } from "../../../../../authoring";

export const leccion02 = defineLesson({
slug: "trigger-auditoria",
title: "Auditoría con trigger",
description: "Crear un trigger SQLite verificable.",
estimatedMinutes: 15,
xpReward: 45,
steps: [
{
type: "theory",
markdown: `SQLite sí soporta triggers. Cada ticket insertado puede generar una entrada de auditoría.`,
},
{
type: "code_example",
code: `CREATE TABLE ticket(id INTEGER PRIMARY KEY,estado TEXT);
CREATE TABLE auditoria(ticket_id INTEGER,accion TEXT);
CREATE TRIGGER audit_ticket AFTER INSERT ON ticket
BEGIN INSERT INTO auditoria VALUES(NEW.id,'INSERT'); END;
INSERT INTO ticket VALUES(7,'ABIERTO');
SELECT ticket_id,accion FROM auditoria;`,
explanation: "El efecto ocurre automáticamente.",
runnable: true,
expectedOutput: `7|INSERT`,
},
{
type: "quiz",
question: "¿Qué representa NEW.id?",
options: ["PK de fila nueva", "Id de auditoría", "Variable global", "Id previo"],
correctIndex: 0,
explanation: "NEW expone la fila insertada.",
},
{
type: "fill_blank",
template: "AFTER INSERT ... NEW.{{0}}",
blanks: [{ answer: "id" }],
explanation: "Referencia valores nuevos.",
},
],
});
Loading
Loading