From 08fe7f09824bb56b06536d39578743f5daea4ee2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 14:36:04 +0000 Subject: [PATCH] =?UTF-8?q?Agrega=20S5=20=E2=80=94=20Modelos=20y=20m=C3=A9?= =?UTF-8?q?todos=20de=20desarrollo=20de=20software?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nuevo Course `modelos-metodos-desarrollo-software` (lenguaje csharp, perfil csharp-mono-6.12) con una CurriculumSection de 5.º semestre (`s5-modelos-metodos-desarrollo-software-1`), transcrita del paquete de contenido canónico: 10 units (cascada, requisitos, UML/diseño, Git, planificación, pruebas, mantenimiento, SOLID, incremental, integrador), 44 lessons, 176 steps y 32 prácticas independientes, todas publicadas desde esta entrega. Co-localizada por unidad bajo prisma/content/courses/modelos-metodos-desarrollo-software/, registrada con una línea en prisma/content/courses/index.ts. Actualiza los tests de contrato de contenido para cubrir el curso nuevo (metadata, sección/semestre, orden y publicación de units, totales, slugs de práctica) y para reconciliar dos políticas globales existentes con el contenido S5: la lista exacta de cursos en el registry y la allowlist de retos C# "sólo comportamiento" sin contrato estructural (S5 sólo declara `structure` en los retos de diseño/UML de las unidades 3, 5, 8 y 10, igual que pide el paquete). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01WczEt1mSsgzuGc9aE5kypU --- prisma/content/courses/index.ts | 2 + .../index.ts | 52 +++++++ .../units/01-proyecto-cascada/index.ts | 16 ++ .../lessons/01-de-programa-a-proyecto.ts | 49 ++++++ .../lessons/02-fases-cascada.ts | 54 +++++++ .../lessons/03-ventajas-limites-cascada.ts | 51 ++++++ .../lessons/04-roles-y-entregables.ts | 50 ++++++ .../units/02-requerimientos/index.ts | 20 +++ .../lessons/01-problema-actores-alcance.ts | 106 +++++++++++++ .../lessons/02-requisitos-funcionales.ts | 100 ++++++++++++ .../lessons/03-requisitos-no-funcionales.ts | 95 ++++++++++++ .../lessons/04-criterios-aceptacion.ts | 106 +++++++++++++ .../lessons/05-cambio-de-alcance.ts | 107 +++++++++++++ .../units/02-requerimientos/practice.ts | 73 +++++++++ .../units/03-uml-diseno/index.ts | 19 +++ .../03-uml-diseno/lessons/01-casos-de-uso.ts | 95 ++++++++++++ .../03-uml-diseno/lessons/02-uml-trazable.ts | 80 ++++++++++ .../03-responsabilidades-acoplamiento.ts | 81 ++++++++++ .../lessons/04-maquetado-reporte-diseno.ts | 46 ++++++ .../units/03-uml-diseno/practice.ts | 79 ++++++++++ .../units/04-git-versiones/index.ts | 16 ++ .../lessons/01-repositorio-snapshot.ts | 47 ++++++ .../lessons/02-status-add-commit.ts | 53 +++++++ .../lessons/03-ramas-integracion.ts | 53 +++++++ .../lessons/04-repositorio-evidencia.ts | 52 +++++++ .../05-planificacion-implementacion/index.ts | 19 +++ .../lessons/01-descomponer-trabajo.ts | 97 ++++++++++++ .../lessons/02-dependencias-gantt.ts | 90 +++++++++++ .../lessons/03-implementar-trazabilidad.ts | 115 ++++++++++++++ .../lessons/04-seguimiento-desviaciones.ts | 87 +++++++++++ .../practice.ts | 146 ++++++++++++++++++ .../units/06-pruebas-calidad/index.ts | 19 +++ .../lessons/01-verificacion-validacion.ts | 73 +++++++++ .../lessons/02-tipos-prueba.ts | 77 +++++++++ .../lessons/03-casos-fronteras.ts | 78 ++++++++++ .../lessons/04-regresion-resultados.ts | 79 ++++++++++ .../units/06-pruebas-calidad/practice.ts | 66 ++++++++ .../units/07-mantenimiento/index.ts | 19 +++ .../lessons/01-tipos-mantenimiento.ts | 69 +++++++++ .../lessons/02-reproducir-antes-corregir.ts | 70 +++++++++ .../lessons/03-impacto-regresion.ts | 70 +++++++++ .../lessons/04-plan-mejora.ts | 63 ++++++++ .../units/07-mantenimiento/practice.ts | 62 ++++++++ .../units/08-solid/index.ts | 21 +++ .../08-solid/lessons/01-por-que-solid.ts | 49 ++++++ .../units/08-solid/lessons/02-srp.ts | 70 +++++++++ .../units/08-solid/lessons/03-ocp.ts | 70 +++++++++ .../units/08-solid/lessons/04-lsp.ts | 74 +++++++++ .../units/08-solid/lessons/05-isp.ts | 63 ++++++++ .../units/08-solid/lessons/06-dip.ts | 69 +++++++++ .../units/08-solid/practice.ts | 61 ++++++++ .../units/09-incremental/index.ts | 20 +++ .../lessons/01-modelo-incremental.ts | 50 ++++++ .../lessons/02-iteracion-feedback.ts | 50 ++++++ .../lessons/03-plan-incrementos.ts | 50 ++++++ .../lessons/04-cronograma-entregas.ts | 49 ++++++ .../lessons/05-cascada-vs-incremental.ts | 49 ++++++ .../units/09-incremental/practice.ts | 62 ++++++++ .../units/10-integrador/index.ts | 19 +++ .../lessons/01-problema-requisitos-plan.ts | 51 ++++++ .../lessons/02-arquitectura-dominio.ts | 73 +++++++++ .../lessons/03-persistencia-gui-local.ts | 52 +++++++ .../lessons/04-entrega-evolucion.ts | 70 +++++++++ .../units/10-integrador/practice.ts | 82 ++++++++++ tests/content/authoring.test.ts | 4 +- tests/content/course-contract.test.ts | 138 +++++++++++++++++ tests/content/structure-contracts.test.ts | 61 ++++++++ 67 files changed, 4156 insertions(+), 2 deletions(-) create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/01-de-programa-a-proyecto.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/02-fases-cascada.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/03-ventajas-limites-cascada.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/04-roles-y-entregables.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/01-problema-actores-alcance.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/02-requisitos-funcionales.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/03-requisitos-no-funcionales.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/04-criterios-aceptacion.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/05-cambio-de-alcance.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/practice.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/01-casos-de-uso.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/02-uml-trazable.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/03-responsabilidades-acoplamiento.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/04-maquetado-reporte-diseno.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/practice.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/01-repositorio-snapshot.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/02-status-add-commit.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/03-ramas-integracion.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/04-repositorio-evidencia.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/01-descomponer-trabajo.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/02-dependencias-gantt.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/03-implementar-trazabilidad.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/04-seguimiento-desviaciones.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/practice.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/01-verificacion-validacion.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/02-tipos-prueba.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/03-casos-fronteras.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/04-regresion-resultados.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/practice.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/01-tipos-mantenimiento.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/02-reproducir-antes-corregir.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/03-impacto-regresion.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/04-plan-mejora.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/practice.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/01-por-que-solid.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/02-srp.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/03-ocp.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/04-lsp.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/05-isp.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/06-dip.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/practice.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/01-modelo-incremental.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/02-iteracion-feedback.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/03-plan-incrementos.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/04-cronograma-entregas.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/05-cascada-vs-incremental.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/practice.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/index.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/01-problema-requisitos-plan.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/02-arquitectura-dominio.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/03-persistencia-gui-local.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/04-entrega-evolucion.ts create mode 100644 prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/practice.ts diff --git a/prisma/content/courses/index.ts b/prisma/content/courses/index.ts index 3b4c3d1..f2ad7a7 100644 --- a/prisma/content/courses/index.ts +++ b/prisma/content/courses/index.ts @@ -16,10 +16,12 @@ import type { CoursePackageDefinition } from "../authoring"; import { cppDesdeCero } from "./cpp-desde-cero"; import { csharpPoo1 } from "./csharp-poo-1"; +import { modelosMetodosDesarrolloSoftware } from "./modelos-metodos-desarrollo-software"; const packages = [ cppDesdeCero, csharpPoo1, + modelosMetodosDesarrolloSoftware, ] satisfies readonly CoursePackageDefinition[]; export const { allCourses, allPracticeSets } = buildContentRegistry(packages); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/index.ts new file mode 100644 index 0000000..daf1824 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/index.ts @@ -0,0 +1,52 @@ +import { defineCourse } from "../../authoring"; + +import { mmProyectoCascada } from "./units/01-proyecto-cascada"; +import { mmRequerimientos } from "./units/02-requerimientos"; +import { mmUmlDiseno } from "./units/03-uml-diseno"; +import { mmGitVersiones } from "./units/04-git-versiones"; +import { mmPlanificacionImplementacion } from "./units/05-planificacion-implementacion"; +import { mmPruebasCalidad } from "./units/06-pruebas-calidad"; +import { mmMantenimiento } from "./units/07-mantenimiento"; +import { mmSolid } from "./units/08-solid"; +import { mmIncremental } from "./units/09-incremental"; +import { mmIntegrador } from "./units/10-integrador"; + +// ===================================================================== +// Paquete de curso: Modelos y métodos de desarrollo de software. +// +// S5 (nueva materia): 10 unidades, una sola CurriculumSection semestral. +// Lenguaje práctico C#, pero la identidad del Course es proceso/ingeniería +// de desarrollo (cascada, requisitos, UML, Git, planificación, pruebas, +// mantenimiento, SOLID, incrementos, proyecto integrador). Las 10 unidades +// nacen `published: true` — no hay release gate para esta entrega. +// ===================================================================== + +export const modelosMetodosDesarrolloSoftware = defineCourse({ + slug: "modelos-metodos-desarrollo-software", + title: "Modelos y métodos de desarrollo de software", + description: + "Convierte problemas en proyectos de software trazables: requisitos, diseño, control de versiones, pruebas, mantenimiento, SOLID y entregas incrementales.", + subjectName: "Modelos y métodos de desarrollo de software", + academicContext: "CETI · Tecnólogo en Desarrollo de Software", + language: "csharp", + executionProfile: "csharp-mono-6.12", + curriculum: [ + { + key: "s5-modelos-metodos-desarrollo-software-1", + semester: 5, + subjectName: "Modelos y Métodos de Desarrollo de Software I", + units: [ + mmProyectoCascada, + mmRequerimientos, + mmUmlDiseno, + mmGitVersiones, + mmPlanificacionImplementacion, + mmPruebasCalidad, + mmMantenimiento, + mmSolid, + mmIncremental, + mmIntegrador, + ], + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/index.ts new file mode 100644 index 0000000..a2c674b --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/index.ts @@ -0,0 +1,16 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-de-programa-a-proyecto"; +import { leccion02 } from "./lessons/02-fases-cascada"; +import { leccion03 } from "./lessons/03-ventajas-limites-cascada"; +import { leccion04 } from "./lessons/04-roles-y-entregables"; + +export const mmProyectoCascada = defineUnit({ + slug: "mm-01-proyecto-cascada", + title: "Del programa al proyecto: modelo en cascada", + description: + "Reconoce qué distingue un proyecto de software de un programa aislado y recorre las fases del modelo en cascada.", + icon: "🧭", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/01-de-programa-a-proyecto.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/01-de-programa-a-proyecto.ts new file mode 100644 index 0000000..53a157c --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/01-de-programa-a-proyecto.ts @@ -0,0 +1,49 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "de-programa-a-proyecto", + title: "De programa aislado a proyecto de software", + description: + "Reconoce que un proyecto incluye problema, personas, decisiones, artefactos y evidencia, no sólo código.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un programa puede resolver una operación puntual. Un **proyecto de software** intenta resolver un problema para personas concretas, durante un periodo, con restricciones y evidencia de que la solución cumple lo acordado. + +Antes de escribir código aparecen preguntas que el compilador no responde: quién usa el sistema, qué necesita, qué queda fuera, cómo sabremos que funciona y quién aprueba un cambio. + +Durante S5 usaremos un sistema de solicitudes de soporte. El código importa, pero también requisitos, modelos, commits, pruebas, cambios y entregables.`, + }, + { + type: "matching", + pairs: [ + { left: "Problema", right: "Situación observable que justifica construir algo" }, + { left: "Requisito", right: "Condición que la solución debe satisfacer" }, + { left: "Implementación", right: "Código/configuración que materializa el diseño" }, + { left: "Evidencia", right: "Prueba o artefacto que verifica una afirmación" }, + ], + explanation: "Proyecto conecta necesidad, decisión, construcción y evidencia.", + }, + { + type: "quiz", + question: + "Hay 800 líneas de C#, pero nadie puede decir qué problema resuelven ni cómo aceptar la entrega. ¿Qué falta primero?", + options: ["Más clases", "Problema y requisitos verificables", "Optimización", "Otro lenguaje"], + correctIndex: 1, + explanation: "Más código no sustituye una definición verificable del objetivo.", + }, + { + type: "fill_blank", + prompt: "Completa los espacios.", + template: "Problema → {{0}} → diseño → implementación → {{1}}", + blanks: [ + { answer: "requisitos", hint: "Expresan qué debe cumplirse." }, + { answer: "evidencia", hint: "Permite comprobarlo." }, + ], + explanation: + "La trazabilidad mínima conecta por qué existe una pieza y cómo se demuestra.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/02-fases-cascada.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/02-fases-cascada.ts new file mode 100644 index 0000000..1ea8f07 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/02-fases-cascada.ts @@ -0,0 +1,54 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "fases-cascada", + title: "Fases y ciclo de vida en cascada", + description: "Ordena las fases y explica qué evidencia produce cada una.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `En cascada las fases avanzan con dependencia fuerte: requisitos, diseño, implementación, verificación, entrega y mantenimiento. + +El valor no está en dibujar flechas hacia abajo, sino en hacer explícitos los **criterios de salida**. Si requisitos termina ambiguo, el diseño hereda ambigüedad; programar más rápido sólo acelera el error.`, + }, + { + type: "code_completion", + prompt: "Ordena los elementos.", + lines: [ + "Requisitos", + "Diseño", + "Implementación", + "Verificación", + "Entrega", + "Mantenimiento", + ], + explanation: + "Cada fase usa resultados de la anterior y deja una salida comprobable.", + }, + { + type: "quiz", + question: "¿Cuál es la mejor condición para cerrar requisitos?", + options: [ + "Ya pasaron dos semanas", + "El equipo empezó a programar", + "Los requisitos están revisados y tienen criterios verificables", + "Existe un repo Git", + ], + correctIndex: 2, + explanation: "Una fase se cierra por evidencia suficiente, no por calendario.", + }, + { + type: "matching", + pairs: [ + { left: "Requisitos", right: "Necesidades/restricciones acordadas" }, + { left: "Diseño", right: "Modelo de solución" }, + { left: "Implementación", right: "Producto construido" }, + { left: "Verificación", right: "Resultados de pruebas" }, + { left: "Mantenimiento", right: "Correcciones/evolución" }, + ], + explanation: "Los entregables hacen observable el ciclo de vida.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/03-ventajas-limites-cascada.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/03-ventajas-limites-cascada.ts new file mode 100644 index 0000000..a1f65e1 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/03-ventajas-limites-cascada.ts @@ -0,0 +1,51 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "ventajas-limites-cascada", + title: "Ventajas, límites y contexto", + description: "Elige cascada sólo cuando sus supuestos encajan con el problema.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Cascada facilita planificación, documentación e hitos claros cuando el problema es estable y el coste de cambio es alto. Puede fallar cuando el usuario descubre lo que necesita al ver versiones reales, porque el feedback llega tarde. + +La pregunta útil no es "¿cascada es buena o mala?", sino **qué incertidumbre existe y cuánto cuesta descubrir tarde un error**.`, + }, + { + type: "matching", + pairs: [ + { left: "Requisitos regulatorios estables", right: "Cascada puede encajar bien" }, + { left: "Producto nuevo con necesidades inciertas", right: "Riesgo de feedback tardío" }, + { left: "Contrato con entregables formales", right: "Trazabilidad útil" }, + { left: "Interfaz experimental", right: "Conviene feedback temprano" }, + ], + explanation: "El contexto decide qué ventaja o desventaja pesa más.", + }, + { + type: "quiz", + question: "¿Cuál es un riesgo típico si los requisitos cambian mucho?", + options: [ + "No permite documentación", + "El aprendizaje del usuario puede llegar después de comprometer diseño/implementación", + "Prohíbe pruebas", + "No puede usar Git", + ], + correctIndex: 1, + explanation: + "El problema es el coste de corregir decisiones tomadas con información incompleta.", + }, + { + type: "fill_blank", + prompt: "Completa los espacios.", + template: + "Si la incertidumbre es {{0}} y el costo de cambio tardío es {{1}}, necesito feedback temprano.", + blanks: [ + { answer: "alta", hint: "Hay mucho por descubrir." }, + { answer: "alto", hint: "Corregir tarde es costoso." }, + ], + explanation: "Es una heurística, no una ley universal.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/04-roles-y-entregables.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/04-roles-y-entregables.ts new file mode 100644 index 0000000..727a892 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/01-proyecto-cascada/lessons/04-roles-y-entregables.ts @@ -0,0 +1,50 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "roles-y-entregables", + title: "Roles, responsabilidades y entregables", + description: "Distingue responsabilidad de rol y actividad de evidencia.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Analista, desarrollador o tester son agrupaciones de responsabilidades. En un equipo pequeño una persona puede cubrir varias; lo importante es que ninguna responsabilidad quede implícita. + +También distingue **actividad** de **entregable**: reunirse no es evidencia; requisitos revisados sí. Programar no es evidencia de calidad; resultados de pruebas sí.`, + }, + { + type: "matching", + pairs: [ + { left: "Analizar necesidades", right: "Requisitos revisados" }, + { left: "Diseñar solución", right: "UML/maquetado" }, + { left: "Implementar", right: "Código versionado" }, + { left: "Verificar", right: "Casos/resultados de prueba" }, + { left: "Gestionar avance", right: "Lista de tareas/seguimiento" }, + ], + explanation: "Una responsabilidad debe dejar una salida observable.", + }, + { + type: "quiz", + question: "En un equipo de dos personas, ¿hacen falta cinco personas para cubrir cinco roles?", + options: [ + "Sí", + "No; una persona puede asumir varias responsabilidades explícitas", + "Sólo con cascada", + "Sólo en proyectos grandes", + ], + correctIndex: 1, + explanation: "Rol describe responsabilidad, no obliga a una persona diferente.", + }, + { + type: "matching", + pairs: [ + { left: "Entrevistar usuario", right: "Requisitos acordados" }, + { left: "Revisar diseño", right: "UML corregido" }, + { left: "Ejecutar pruebas", right: "Reporte de resultados" }, + { left: "Planear cambio", right: "Tarea/cronograma actualizado" }, + ], + explanation: "El curso evalúa evidencia, no sólo actividad.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/index.ts new file mode 100644 index 0000000..58861d4 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/index.ts @@ -0,0 +1,20 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-problema-actores-alcance"; +import { leccion02 } from "./lessons/02-requisitos-funcionales"; +import { leccion03 } from "./lessons/03-requisitos-no-funcionales"; +import { leccion04 } from "./lessons/04-criterios-aceptacion"; +import { leccion05 } from "./lessons/05-cambio-de-alcance"; + +import { practice } from "./practice"; + +export const mmRequerimientos = defineUnit({ + slug: "mm-02-requerimientos", + title: "Requerimientos, alcance y criterios de aceptación", + description: + "Convierte una petición vaga en problema, actores, alcance y requisitos funcionales y no funcionales verificables.", + icon: "📋", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04, leccion05], + practice, +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/01-problema-actores-alcance.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/01-problema-actores-alcance.ts new file mode 100644 index 0000000..fdc695f --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/01-problema-actores-alcance.ts @@ -0,0 +1,106 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "problema-actores-alcance", + title: "Problema, actores y frontera del sistema", + description: "Convierte una petición vaga en problema, actores y alcance explícito.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `"Necesitamos una app" no es un problema. "Las solicitudes de soporte se pierden entre mensajes y no existe trazabilidad de quién las atiende" sí describe una situación observable. + +Después identifica actores y **frontera**: qué responsabilidad pertenece al sistema y qué queda fuera. El alcance protege al proyecto de una lista infinita de deseos.`, + }, + { + type: "matching", + pairs: [ + { left: "Empleado", right: "Reporta una solicitud" }, + { left: "Técnico", right: "Atiende/cambia estado" }, + { left: "Administrador", right: "Gestiona catálogos/reglas acordadas" }, + { left: "Sistema", right: "Conserva solicitudes y aplica reglas" }, + ], + explanation: "Actor es quien intercambia información con el sistema; no todo sustantivo es actor.", + }, + { + type: "quiz", + question: "¿Cuál enunciado define mejor alcance?", + options: [ + "Todo lo que el usuario pueda pedir algún día", + "Qué responsabilidades sí pertenecen a esta entrega y cuáles no", + "La lista de clases", + "El número de commits", + ], + correctIndex: 1, + explanation: "Alcance es una frontera acordada.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Implementa la prioridad: servicio detenido→CRITICA; si no, >=10 afectados→ALTA; 2..9→MEDIA; 0..1→BAJA.", + starterCode: `using System; +class Program +{ + static string Prioridad(int afectados, bool detenido) + { + return ""; + } + static void Main() + { + int afectados = int.Parse(Console.ReadLine()); + bool detenido = Console.ReadLine() == "SI"; + Console.WriteLine(Prioridad(afectados, detenido)); + } +}`, + solutionCode: `using System; +class Program +{ + static string Prioridad(int afectados, bool detenido) + { + if (detenido) return "CRITICA"; + if (afectados >= 10) return "ALTA"; + if (afectados >= 2) return "MEDIA"; + return "BAJA"; + } + static void Main() + { + int afectados = int.Parse(Console.ReadLine()); + bool detenido = Console.ReadLine() == "SI"; + Console.WriteLine(Prioridad(afectados, detenido)); + } +}`, + hints: ["Evalúa primero servicio detenido.", "Después usa los límites de afectados."], + difficulty: "easy", + xpReward: 25, + testCases: [ + { + visible: true, + stdin: "25\nSI\n", + expectedStdout: "CRITICA\n", + description: "Servicio detenido domina", + }, + { + visible: false, + stdin: "12\nNO\n", + expectedStdout: "ALTA\n", + description: "Muchos afectados", + }, + { + visible: false, + stdin: "5\nNO\n", + expectedStdout: "MEDIA\n", + description: "Impacto medio", + }, + { + visible: false, + stdin: "1\nNO\n", + expectedStdout: "BAJA\n", + description: "Impacto bajo", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/02-requisitos-funcionales.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/02-requisitos-funcionales.ts new file mode 100644 index 0000000..509aee1 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/02-requisitos-funcionales.ts @@ -0,0 +1,100 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "requisitos-funcionales", + title: "Requisitos funcionales", + description: + "Escribe comportamiento observable sin mezclar decisiones accidentales de diseño.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un requisito funcional describe una capacidad observable. "Usar \`Dictionary\`" normalmente es diseño; "consultar un ticket por folio" es comportamiento. + +Evita "intuitivo", "moderno" o "fácil" si no tienen criterio concreto.`, + }, + { + type: "matching", + pairs: [ + { left: "Cerrar ticket abierto", right: "Funcional" }, + { left: "Responder en <500 ms", right: "No funcional" }, + { left: "Usar una clase Ticket", right: "Diseño" }, + { left: "Conservar historial de estado", right: "Funcional" }, + ], + explanation: "Funcional describe capacidad; diseño describe cómo construirla.", + }, + { + type: "quiz", + question: "¿Cuál requisito es más verificable?", + options: [ + "El sistema será amigable", + "Permitirá cerrar tickets", + "Al cerrar registra fecha y técnico responsable", + "La interfaz será bonita", + ], + correctIndex: 2, + explanation: "Especifica evento y evidencia observable.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Implementa `PuedeCerrar(estado, tecnico)`: sólo ABIERTO o EN_PROCESO y técnico no vacío.", + starterCode: `using System; +class Program +{ + static bool PuedeCerrar(string estado, string tecnico) + { + return false; + } + static void Main() + { + Console.WriteLine(PuedeCerrar(Console.ReadLine(), Console.ReadLine()) ? "SI" : "NO"); + } +}`, + solutionCode: `using System; +class Program +{ + static bool PuedeCerrar(string estado, string tecnico) + { + bool estadoValido = estado == "ABIERTO" || estado == "EN_PROCESO"; + return estadoValido && tecnico.Length > 0; + } + static void Main() + { + Console.WriteLine(PuedeCerrar(Console.ReadLine(), Console.ReadLine()) ? "SI" : "NO"); + } +}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { + visible: true, + stdin: "ABIERTO\nAna\n", + expectedStdout: "SI\n", + description: "Abierto asignado", + }, + { + visible: false, + stdin: "EN_PROCESO\nLuis\n", + expectedStdout: "SI\n", + description: "En proceso asignado", + }, + { + visible: false, + stdin: "CERRADO\nAna\n", + expectedStdout: "NO\n", + description: "Cerrado", + }, + { + visible: false, + stdin: "ABIERTO\n\n", + expectedStdout: "NO\n", + description: "Sin técnico", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/03-requisitos-no-funcionales.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/03-requisitos-no-funcionales.ts new file mode 100644 index 0000000..e41486d --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/03-requisitos-no-funcionales.ts @@ -0,0 +1,95 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "requisitos-no-funcionales", + title: "Requisitos no funcionales", + description: "Traduce atributos de calidad abstractos a restricciones verificables.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Rendimiento, seguridad, disponibilidad, mantenibilidad y usabilidad son atributos de calidad. El error común es dejarlos como adjetivos. + +"Debe ser rápido" no se puede aceptar o rechazar. "La consulta por folio responde en menos de 500 ms bajo la carga definida" sí establece una medida.`, + }, + { + type: "matching", + pairs: [ + { left: "Tiempo máximo de respuesta", right: "Rendimiento" }, + { left: "Sólo técnicos pueden cerrar", right: "Seguridad" }, + { left: "Restaurar operación <4h", right: "Recuperación" }, + { left: "Aislar regla de prioridad", right: "Mantenibilidad" }, + ], + explanation: "El atributo nombra la preocupación; el requisito fija una condición.", + }, + { + type: "quiz", + question: '¿Qué mejora "el sistema debe ser seguro"?', + options: ['Agregar "muy"', "Nombrar quién puede hacer qué y bajo qué control", "Cambiar lenguaje", "Añadir pantallas"], + correctIndex: 1, + explanation: "Seguridad se vuelve evaluable con permisos/amenazas/controles concretos.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Implementa autorización: ADMIN y TECNICO pueden VER/CERRAR; EMPLEADO sólo VER; cualquier otro caso NO.", + starterCode: `using System; +class Program +{ + static bool Permitido(string rol, string accion) + { + return false; + } + static void Main() + { + Console.WriteLine(Permitido(Console.ReadLine(), Console.ReadLine()) ? "SI" : "NO"); + } +}`, + solutionCode: `using System; +class Program +{ + static bool Permitido(string rol, string accion) + { + if (rol == "ADMIN" || rol == "TECNICO") return accion == "VER" || accion == "CERRAR"; + if (rol == "EMPLEADO") return accion == "VER"; + return false; + } + static void Main() + { + Console.WriteLine(Permitido(Console.ReadLine(), Console.ReadLine()) ? "SI" : "NO"); + } +}`, + difficulty: "medium", + xpReward: 30, + testCases: [ + { + visible: true, + stdin: "EMPLEADO\nVER\n", + expectedStdout: "SI\n", + description: "Permiso básico", + }, + { + visible: false, + stdin: "EMPLEADO\nCERRAR\n", + expectedStdout: "NO\n", + description: "Acción negada", + }, + { + visible: false, + stdin: "TECNICO\nCERRAR\n", + expectedStdout: "SI\n", + description: "Técnico autorizado", + }, + { + visible: false, + stdin: "INVITADO\nVER\n", + expectedStdout: "NO\n", + description: "Rol desconocido", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/04-criterios-aceptacion.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/04-criterios-aceptacion.ts new file mode 100644 index 0000000..1bce322 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/04-criterios-aceptacion.ts @@ -0,0 +1,106 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "criterios-aceptacion", + title: "Criterios de aceptación", + description: + "Convierte expectativas en ejemplos que permiten aceptar o rechazar una entrega.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un requisito sin criterio puede discutirse para siempre. Un criterio de aceptación baja la expectativa a comportamiento observable. + +Forma útil: **Dado / Cuando / Entonces**. No prescribe cómo guardar el dato; prescribe qué debe ser cierto.`, + }, + { + type: "code_completion", + prompt: "Ordena los elementos.", + lines: [ + "Dado un ticket ABIERTO asignado a Ana", + "Cuando Ana solicita cerrarlo", + "Entonces el estado queda CERRADO", + "Y se conserva Ana como responsable", + ], + explanation: "Contexto → acción → resultado verificable.", + }, + { + type: "quiz", + question: "¿Para qué sirve principalmente un criterio de aceptación?", + options: [ + "Elegir nombres de variables", + "Acordar evidencia concreta de que un requisito se cumple", + "Reemplazar todos los tests", + "Diseñar DB", + ], + correctIndex: 1, + explanation: "Es puente entre expectativa y verificación.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Implementa tarifa por prioridad: BAJA=0, MEDIA=50, ALTA=100, CRITICA=200; otro valor imprime INVALIDA.", + starterCode: `using System; +class Program +{ + static int Costo(string prioridad) + { + return -1; + } + static void Main() + { + int c = Costo(Console.ReadLine()); + Console.WriteLine(c < 0 ? "INVALIDA" : c.ToString()); + } +}`, + solutionCode: `using System; +class Program +{ + static int Costo(string p) + { + if (p == "BAJA") return 0; + if (p == "MEDIA") return 50; + if (p == "ALTA") return 100; + if (p == "CRITICA") return 200; + return -1; + } + static void Main() + { + int c = Costo(Console.ReadLine()); + Console.WriteLine(c < 0 ? "INVALIDA" : c.ToString()); + } +}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { + visible: true, + stdin: "BAJA\n", + expectedStdout: "0\n", + description: "Baja", + }, + { + visible: false, + stdin: "CRITICA\n", + expectedStdout: "200\n", + description: "Crítica", + }, + { + visible: false, + stdin: "MEDIA\n", + expectedStdout: "50\n", + description: "Media", + }, + { + visible: false, + stdin: "OTRA\n", + expectedStdout: "INVALIDA\n", + description: "Fuera del contrato", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/05-cambio-de-alcance.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/05-cambio-de-alcance.ts new file mode 100644 index 0000000..cc0d354 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/lessons/05-cambio-de-alcance.ts @@ -0,0 +1,107 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion05 = defineLesson({ + slug: "cambio-de-alcance", + title: "Cambios e impacto", + description: "Evalúa una solicitud de cambio antes de aceptarla.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Cambiar no es fallar; cambiar sin analizar sí. Una solicitud nueva puede ser válida y aun no pertenecer a la entrega actual. + +Antes de aceptarla se analiza impacto en requisitos, diseño, pruebas, trabajo pendiente y riesgo. En cascada es especialmente importante porque una decisión tardía atraviesa fases ya cerradas.`, + }, + { + type: "matching", + pairs: [ + { left: "Nuevo estado PAUSADO", right: "Requisitos + lógica + pruebas + documentación" }, + { left: "Cambiar texto de ayuda", right: "UI/documentación, bajo impacto" }, + { left: "Cambiar quién puede cerrar", right: "Seguridad + requisitos + pruebas" }, + { left: "Nuevo reporte", right: "Alcance + diseño + implementación + pruebas" }, + ], + explanation: "El impacto sigue las dependencias del cambio.", + }, + { + type: "quiz", + question: + "El cliente pide una función nueva a dos días de entrega. ¿Qué acción es profesional?", + options: [ + "Aceptarla sin registrar", + "Rechazar todo cambio", + "Registrar, analizar impacto y decidir alcance/fecha", + "Ocultarla en un commit", + ], + correctIndex: 2, + explanation: "El control de cambios vuelve explícito coste y decisión.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Costo base=100; ALTA/CRITICA suma 50. Nuevo requisito: convenio aplica 20% de descuento al total final. Lee prioridad y SI/NO.", + starterCode: `using System; +class Program +{ + static int Total(string prioridad, bool convenio) + { + int total = 100; + return total; + } + static void Main() + { + string p = Console.ReadLine(); + bool convenio = Console.ReadLine() == "SI"; + Console.WriteLine(Total(p, convenio)); + } +}`, + solutionCode: `using System; +class Program +{ + static int Total(string prioridad, bool convenio) + { + int total = 100; + if (prioridad == "ALTA" || prioridad == "CRITICA") total += 50; + if (convenio) total = total * 80 / 100; + return total; + } + static void Main() + { + string p = Console.ReadLine(); + bool convenio = Console.ReadLine() == "SI"; + Console.WriteLine(Total(p, convenio)); + } +}`, + difficulty: "medium", + xpReward: 35, + testCases: [ + { + visible: true, + stdin: "BAJA\nNO\n", + expectedStdout: "100\n", + description: "Regla anterior", + }, + { + visible: false, + stdin: "ALTA\nNO\n", + expectedStdout: "150\n", + description: "Recargo", + }, + { + visible: false, + stdin: "ALTA\nSI\n", + expectedStdout: "120\n", + description: "Cambio aplicado", + }, + { + visible: false, + stdin: "BAJA\nSI\n", + expectedStdout: "80\n", + description: "Descuento", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/practice.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/practice.ts new file mode 100644 index 0000000..a6202cd --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/02-requerimientos/practice.ts @@ -0,0 +1,73 @@ +import type { PracticeExerciseDefinition } from "../../../../exercises/types"; + +export const practice = [ + { + slug: "mm-requisitos-prioridad", + title: "Prioridad por impacto", + description: "Convierte reglas de negocio en comportamiento verificable.", + prompt: + "Lee impacto (1..5) y urgencia (1..5). CRITICA si ambos >=4; ALTA si alguno >=4; MEDIA si alguno >=2; en otro caso BAJA.", + starterCode: `using System; +class Program { static void Main() { int impacto=int.Parse(Console.ReadLine()); int urgencia=int.Parse(Console.ReadLine()); /* completa */ } }`, + solutionCode: `using System; +class Program { static void Main() { int i=int.Parse(Console.ReadLine()); int u=int.Parse(Console.ReadLine()); if(i>=4&&u>=4)Console.WriteLine("CRITICA"); else if(i>=4||u>=4)Console.WriteLine("ALTA"); else if(i>=2||u>=2)Console.WriteLine("MEDIA"); else Console.WriteLine("BAJA"); } }`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "5\n5\n", expectedStdout: "CRITICA\n", visible: true, description: "Ambos altos" }, + { stdin: "5\n1\n", expectedStdout: "ALTA\n", visible: false, description: "Uno alto" }, + { stdin: "2\n1\n", expectedStdout: "MEDIA\n", visible: false, description: "Medio" }, + { stdin: "1\n1\n", expectedStdout: "BAJA\n", visible: false, description: "Bajo" }, + ], + }, + { + slug: "mm-requisitos-sla", + title: "SLA verificable", + description: "Aplica límites por prioridad.", + prompt: + "Límites en horas: CRITICA=2, ALTA=4, MEDIA=8, BAJA=24. Imprime EN_TIEMPO si horas <= límite; prioridad desconocida PRIORIDAD_INVALIDA; si excede FUERA_DE_TIEMPO.", + starterCode: `using System; class Program { static void Main(){ string p=Console.ReadLine(); int h=int.Parse(Console.ReadLine()); /* completa */ } }`, + solutionCode: `using System; class Program { static void Main(){ string p=Console.ReadLine(); int h=int.Parse(Console.ReadLine()); int l=-1; if(p=="CRITICA")l=2; else if(p=="ALTA")l=4; else if(p=="MEDIA")l=8; else if(p=="BAJA")l=24; if(l<0)Console.WriteLine("PRIORIDAD_INVALIDA"); else Console.WriteLine(h<=l?"EN_TIEMPO":"FUERA_DE_TIEMPO"); } }`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "CRITICA\n2\n", expectedStdout: "EN_TIEMPO\n", visible: true, description: "Frontera" }, + { stdin: "CRITICA\n3\n", expectedStdout: "FUERA_DE_TIEMPO\n", visible: false, description: "Excede" }, + { stdin: "BAJA\n24\n", expectedStdout: "EN_TIEMPO\n", visible: false, description: "Frontera baja" }, + { stdin: "X\n1\n", expectedStdout: "PRIORIDAD_INVALIDA\n", visible: false, description: "Valor inválido" }, + ], + }, + { + slug: "mm-requisitos-transicion", + title: "Transición de estados", + description: "Implementa una máquina de estados mínima.", + prompt: + "ABIERTO→EN_PROCESO, EN_PROCESO→CERRADO, EN_PROCESO→ABIERTO son válidas; cualquier otra NO.", + starterCode: `using System; class Program { static void Main(){ string a=Console.ReadLine(); string b=Console.ReadLine(); /* completa */ } }`, + solutionCode: `using System; class Program { static void Main(){ string a=Console.ReadLine(); string b=Console.ReadLine(); bool ok=(a=="ABIERTO"&&b=="EN_PROCESO")||(a=="EN_PROCESO"&&(b=="CERRADO"||b=="ABIERTO")); Console.WriteLine(ok?"SI":"NO"); } }`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "ABIERTO\nEN_PROCESO\n", expectedStdout: "SI\n", visible: true, description: "Avance" }, + { stdin: "EN_PROCESO\nCERRADO\n", expectedStdout: "SI\n", visible: false, description: "Cierre" }, + { stdin: "CERRADO\nABIERTO\n", expectedStdout: "NO\n", visible: false, description: "Reapertura no definida" }, + { stdin: "ABIERTO\nCERRADO\n", expectedStdout: "NO\n", visible: false, description: "Salto" }, + ], + }, + { + slug: "mm-requisitos-cambio", + title: "Impacto de cambio", + description: "Decide si un cambio cabe en presupuesto.", + prompt: + "Lee costoBase, porcentajeCambio y presupuestoMax. estimado=base+base*porcentaje/100. Imprime ACEPTAR si estimado<=presupuesto, en otro caso REPLANEAR.", + starterCode: `using System; class Program { static void Main(){ int b=int.Parse(Console.ReadLine()); int p=int.Parse(Console.ReadLine()); int max=int.Parse(Console.ReadLine()); /* completa */ } }`, + solutionCode: `using System; class Program { static void Main(){ int b=int.Parse(Console.ReadLine()); int p=int.Parse(Console.ReadLine()); int max=int.Parse(Console.ReadLine()); int e=b+b*p/100; Console.WriteLine(e<=max?"ACEPTAR":"REPLANEAR"); } }`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "1000\n20\n1200\n", expectedStdout: "ACEPTAR\n", visible: true, description: "Justo" }, + { stdin: "1000\n20\n1199\n", expectedStdout: "REPLANEAR\n", visible: false, description: "Excede" }, + { stdin: "500\n0\n500\n", expectedStdout: "ACEPTAR\n", visible: false, description: "Sin cambio" }, + ], + }, +] satisfies PracticeExerciseDefinition[]; diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/index.ts new file mode 100644 index 0000000..48afa3e --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/index.ts @@ -0,0 +1,19 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-casos-de-uso"; +import { leccion02 } from "./lessons/02-uml-trazable"; +import { leccion03 } from "./lessons/03-responsabilidades-acoplamiento"; +import { leccion04 } from "./lessons/04-maquetado-reporte-diseno"; + +import { practice } from "./practice"; + +export const mmUmlDiseno = defineUnit({ + slug: "mm-03-uml-diseno", + title: "UML, responsabilidades y diseño", + description: + "Usa UML como artefacto trazable a requisitos: casos de uso, responsabilidades, cohesión y maquetado antes de implementar.", + icon: "📐", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04], + practice, +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/01-casos-de-uso.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/01-casos-de-uso.ts new file mode 100644 index 0000000..308a26f --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/01-casos-de-uso.ts @@ -0,0 +1,95 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "casos-de-uso", + title: "Actores, objetivos y casos de uso", + description: "Modela objetivos del usuario sin convertir cada clic en caso de uso.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un actor interactúa con el sistema para lograr un resultado con valor. "Presionar Guardar" es demasiado bajo nivel; "Registrar solicitud" expresa una meta. + +El caso de uso conecta requisitos y diseño: deja visibles actor, precondición, flujo principal, alternativas y resultado.`, + }, + { + type: "matching", + pairs: [ + { left: "Empleado", right: "Registrar solicitud" }, + { left: "Técnico", right: "Atender solicitud" }, + { left: "Administrador", right: "Gestionar catálogo" }, + { left: "Sistema de correo", right: "Actor externo si intercambia notificaciones" }, + ], + explanation: "Actor se define por interacción y objetivo.", + }, + { + type: "quiz", + question: "¿Cuál es un caso de uso mejor nombrado?", + options: ["Botón azul", "Hacer clic", "Registrar solicitud de soporte", "Textbox descripción"], + correctIndex: 2, + explanation: "El nombre expresa una meta, no un detalle de UI.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Crea `Solicitud` con propiedades públicas Folio y Descripcion de setter privado, constructor de dos parámetros. Lee ambos datos e imprime `folio|descripcion`.", + starterCode: `using System; +// escribe Solicitud +class Program +{ + static void Main() + { + string folio=Console.ReadLine(); + string descripcion=Console.ReadLine(); + // crea e imprime + } +}`, + solutionCode: `using System; +class Solicitud +{ + public string Folio { get; private set; } + public string Descripcion { get; private set; } + public Solicitud(string folio,string descripcion){Folio=folio;Descripcion=descripcion;} +} +class Program +{ + static void Main() + { + Solicitud s=new Solicitud(Console.ReadLine(),Console.ReadLine()); + Console.WriteLine(s.Folio+"|"+s.Descripcion); + } +}`, + difficulty: "easy", + xpReward: 25, + structure: { + classes: [ + { + name: "Solicitud", + properties: [ + { name: "Folio", type: "string", visibility: "public" }, + { name: "Descripcion", type: "string", visibility: "public" }, + ], + constructors: [{ paramCount: 2 }], + }, + ], + }, + testCases: [ + { + visible: true, + stdin: "T-10\nSin red\n", + expectedStdout: "T-10|Sin red\n", + description: "Caso normal", + }, + { + visible: false, + stdin: "A1\nPantalla azul\n", + expectedStdout: "A1|Pantalla azul\n", + description: "Texto variable", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/02-uml-trazable.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/02-uml-trazable.ts new file mode 100644 index 0000000..c1367b1 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/02-uml-trazable.ts @@ -0,0 +1,80 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "uml-trazable", + title: "Del requisito al diagrama de clases", + description: "Justifica cada clase y relación por una responsabilidad del problema.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un diagrama útil permite preguntar: "¿qué requisito justifica esta responsabilidad?". No diseñes una clase por cada sustantivo. Identifica comportamiento, invariantes y propiedad de datos; luego decide estructura.`, + }, + { + type: "matching", + pairs: [ + { left: "Ticket conoce al técnico asignado", right: "Asociación almacenada" }, + { left: "Ticket posee entradas de historial", right: "Composición conceptual" }, + { left: "Servicio recibe Ticket sólo para operar", right: "Dependencia" }, + { left: "TicketUrgente sustituye legítimamente Ticket", right: "Posible generalización" }, + ], + explanation: "La relación depende del significado/ciclo de vida.", + }, + { + type: "quiz", + question: "¿Qué pregunta mejora más un diagrama?", + options: [ + "¿Tiene suficientes cajas?", + "¿Cada responsabilidad/relación responde a requisito o invariante?", + "¿Todo hereda?", + "¿Tiene colores?", + ], + correctIndex: 1, + explanation: "El modelo es una decisión trazable.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Implementa `Tecnico` y `Ticket`; Ticket almacena al técnico asignado y Main imprime `Folio|NombreTecnico`.", + starterCode: `using System; +class Tecnico { public string Nombre { get; private set; } public Tecnico(string n){Nombre=n;} } +// escribe Ticket +class Program { static void Main(){ string f=Console.ReadLine(); string n=Console.ReadLine(); Tecnico t=new Tecnico(n); /* completa */ } }`, + solutionCode: `using System; +class Tecnico { public string Nombre { get; private set; } public Tecnico(string n){Nombre=n;} } +class Ticket { public string Folio { get; private set; } public Tecnico Asignado { get; private set; } public Ticket(string f,Tecnico t){Folio=f;Asignado=t;} } +class Program { static void Main(){ string f=Console.ReadLine(); string n=Console.ReadLine(); Tecnico t=new Tecnico(n); Ticket x=new Ticket(f,t); Console.WriteLine(x.Folio+"|"+x.Asignado.Nombre); } }`, + difficulty: "medium", + xpReward: 30, + structure: { + classes: [ + { + name: "Ticket", + properties: [ + { name: "Folio", type: "string" }, + { name: "Asignado", type: "Tecnico" }, + ], + stores: [{ type: "Tecnico" }], + }, + ], + }, + testCases: [ + { + visible: true, + stdin: "T1\nAna\n", + expectedStdout: "T1|Ana\n", + description: "Relación", + }, + { + visible: false, + stdin: "X9\nLuis\n", + expectedStdout: "X9|Luis\n", + description: "Valores variables", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/03-responsabilidades-acoplamiento.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/03-responsabilidades-acoplamiento.ts new file mode 100644 index 0000000..0c9b453 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/03-responsabilidades-acoplamiento.ts @@ -0,0 +1,81 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "responsabilidades-acoplamiento", + title: "Responsabilidades, cohesión y dependencias", + description: "Evita objetos que concentran responsabilidades no relacionadas.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Si \`Ticket\` valida reglas, guarda XML, imprime reportes, manda correo y dibuja formularios, cualquier cambio toca la misma clase. + +La **cohesión** mejora cuando una clase agrupa comportamiento del mismo propósito. El acoplamiento es riesgoso cuando una pieza conoce detalles que no necesita.`, + }, + { + type: "matching", + pairs: [ + { left: "Ticket", right: "Estado e invariantes" }, + { left: "RepositorioTickets", right: "Cargar/guardar" }, + { left: "Notificador", right: "Enviar notificaciones" }, + { left: "ReporteTickets", right: "Preparar reportes" }, + ], + explanation: "Separar por responsabilidad prepara el terreno para SOLID.", + }, + { + type: "quiz", + question: "¿Qué cambio NO debería obligar a editar Ticket?", + options: [ + "Regla de transición", + "Invariante de descripción", + "Cambiar persistencia XML por otro medio", + "Prohibir doble cierre", + ], + correctIndex: 2, + explanation: "Persistencia es responsabilidad diferente del dominio.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Separa cálculo y presentación: `CalculadoraCosto.Calcular(int horas)` devuelve horas*50; `FormatoCosto.Texto(int total)` devuelve `TOTAL:N`. Main usa ambas.", + starterCode: `using System; +// escribe las dos clases +class Program { static void Main(){ int h=int.Parse(Console.ReadLine()); /* completa */ } }`, + solutionCode: `using System; +class CalculadoraCosto { public int Calcular(int horas){return horas*50;} } +class FormatoCosto { public string Texto(int total){return "TOTAL:"+total;} } +class Program { static void Main(){ int h=int.Parse(Console.ReadLine()); CalculadoraCosto c=new CalculadoraCosto(); FormatoCosto f=new FormatoCosto(); Console.WriteLine(f.Texto(c.Calcular(h))); } }`, + difficulty: "medium", + xpReward: 30, + structure: { + classes: [ + { name: "CalculadoraCosto", methods: [{ name: "Calcular" }] }, + { name: "FormatoCosto", methods: [{ name: "Texto" }] }, + ], + }, + testCases: [ + { + visible: true, + stdin: "2\n", + expectedStdout: "TOTAL:100\n", + description: "Normal", + }, + { + visible: false, + stdin: "0\n", + expectedStdout: "TOTAL:0\n", + description: "Cero", + }, + { + visible: false, + stdin: "7\n", + expectedStdout: "TOTAL:350\n", + description: "Variable", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/04-maquetado-reporte-diseno.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/04-maquetado-reporte-diseno.ts new file mode 100644 index 0000000..fa9af55 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/lessons/04-maquetado-reporte-diseno.ts @@ -0,0 +1,46 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "maquetado-reporte-diseno", + title: "Maquetado y reporte de diseño", + description: "Usa maquetas para validar flujo antes de comprometer implementación.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Una maqueta permite detectar barato qué información se pide, qué acciones existen y cómo navega el usuario. + +El reporte de diseño debe conectar: problema → requisitos → casos de uso/UML → maquetas → decisiones. No es una colección de capturas sin explicación.`, + }, + { + type: "matching", + pairs: [ + { left: "Folio sólo lectura", right: "Identidad estable" }, + { left: "Cerrar oculto a empleado", right: "Autorización" }, + { left: "Historial visible", right: "Trazabilidad" }, + { left: "Pantalla separada de alta", right: "Decisión de flujo que debe justificarse" }, + ], + explanation: "Cada elemento importante necesita un motivo.", + }, + { + type: "quiz", + question: "¿Qué hallazgo debe regresar a requisitos antes de programar?", + options: [ + "Nadie sabe qué significa prioridad y cada usuario la interpreta distinto", + "El borde no gusta", + "Aún no hay código", + "El commit es pequeño", + ], + correctIndex: 0, + explanation: "Ambigüedad de significado es un problema de requisito/dominio.", + }, + { + type: "fill_blank", + prompt: "Completa los espacios.", + template: "Problema → requisitos → {{0}} → maqueta → implementación", + blanks: [{ answer: "diseño", hint: "Transforma necesidades en una solución antes de codificar." }], + explanation: "La maqueta valida diseño, no sustituye análisis.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/practice.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/practice.ts new file mode 100644 index 0000000..86af426 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/03-uml-diseno/practice.ts @@ -0,0 +1,79 @@ +import type { PracticeExerciseDefinition } from "../../../../exercises/types"; + +export const practice = [ + { + slug: "mm-diseno-ticket-asignado", + title: "Ticket asignado", + description: "Modela una relación almacenada.", + prompt: "Lee folio y técnico; crea Tecnico y Ticket; imprime folio|tecnico.", + starterCode: `using System; class Tecnico{public string Nombre{get;private set;} public Tecnico(string n){Nombre=n;}} // completa`, + solutionCode: `using System; class Tecnico{public string Nombre{get;private set;} public Tecnico(string n){Nombre=n;}} class Ticket{public string Folio{get;private set;} public Tecnico Asignado{get;private set;} public Ticket(string f,Tecnico t){Folio=f;Asignado=t;}} class Program{static void Main(){Ticket x=new Ticket(Console.ReadLine(),new Tecnico(Console.ReadLine()));Console.WriteLine(x.Folio+"|"+x.Asignado.Nombre);}}`, + difficulty: "medium", + xpReward: 28, + structure: { + classes: [{ name: "Ticket", stores: [{ type: "Tecnico" }] }], + }, + testCases: [ + { stdin: "F1\nAna\n", expectedStdout: "F1|Ana\n", visible: true, description: "Normal" }, + { stdin: "Z7\nBeto\n", expectedStdout: "Z7|Beto\n", visible: false, description: "Variable" }, + ], + }, + { + slug: "mm-diseno-separar-reporte", + title: "Cálculo y formato", + description: "Separa responsabilidades.", + prompt: "Lee cantidad y precio. Calculadora calcula subtotal; Formateador devuelve `SUBTOTAL:N`.", + starterCode: `using System; // crea Calculadora y Formateador`, + solutionCode: `using System; class Calculadora{public int Subtotal(int c,int p){return c*p;}} class Formateador{public string Texto(int n){return "SUBTOTAL:"+n;}} class Program{static void Main(){int c=int.Parse(Console.ReadLine());int p=int.Parse(Console.ReadLine());Calculadora x=new Calculadora();Formateador f=new Formateador();Console.WriteLine(f.Texto(x.Subtotal(c,p)));}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "3\n20\n", expectedStdout: "SUBTOTAL:60\n", visible: true, description: "Normal" }, + { stdin: "0\n99\n", expectedStdout: "SUBTOTAL:0\n", visible: false, description: "Cero" }, + ], + }, + { + slug: "mm-diseno-historial", + title: "Historial de estados", + description: "Modela propiedad de datos.", + prompt: + "Ticket recibe tres estados en secuencia y los guarda en un arreglo interno de tamaño 3; imprime en orden.", + starterCode: `using System; // crea Ticket con historial`, + solutionCode: `using System; class Ticket{private string[] historial=new string[3]; private int n=0; public void Agregar(string e){historial[n++]=e;} public void Mostrar(){for(int i=0;i", + "git diff --cached", + 'git commit -m "mensaje"', + "git log --oneline", + ], + explanation: "Inspecciona, selecciona, revisa y registra.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/02-status-add-commit.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/02-status-add-commit.ts new file mode 100644 index 0000000..c123476 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/02-status-add-commit.ts @@ -0,0 +1,53 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "status-add-commit", + title: "Inspeccionar antes de registrar", + description: + "Usa status, diff, add y commit como un ciclo de evidencia antes de registrar cambios.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `\`git status\` responde qué está modificado/preparado. \`git diff\` muestra cambios no staged y \`git diff --cached\` lo que realmente entrará al commit. + +El hábito clave es comparar el mensaje con el diff.`, + }, + { + type: "matching", + pairs: [ + { left: "git status", right: "Estado resumido" }, + { left: "git diff", right: "Cambios no staged" }, + { left: "git diff --cached", right: "Cambios preparados" }, + { left: "git add", right: "Seleccionar para commit" }, + { left: "git commit", right: "Registrar snapshot" }, + ], + explanation: "Cada comando responde una pregunta.", + }, + { + type: "quiz", + question: + "Descubres una contraseña dentro del diff antes de commit. ¿Qué haces?", + options: [ + "Commit y luego borrar", + "No registrarla; retirarla y usar configuración/secretos adecuados", + "Cambiar mensaje", + "Agregar README", + ], + correctIndex: 1, + explanation: + "Evitar introducir secretos es mucho más barato que limpiar historial.", + }, + { + type: "matching", + pairs: [ + { left: "Cambio lógico pequeño", right: "Commit enfocado" }, + { left: "Refactor + feature sin relación", right: "Separar" }, + { left: "Archivo generado innecesario", right: "gitignore" }, + { left: "Secreto", right: "Configuración externa" }, + ], + explanation: "El historial también es un artefacto de calidad.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/03-ramas-integracion.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/03-ramas-integracion.ts new file mode 100644 index 0000000..bea20b1 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/03-ramas-integracion.ts @@ -0,0 +1,53 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "ramas-integracion", + title: "Ramas e integración controlada", + description: + "Entiende cómo una rama aísla trabajo y qué implica integrar cambios con merge y resolver un conflicto.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Una rama es un nombre que apunta a una línea de commits. Permite aislar una unidad de trabajo mientras \`main\` conserva una versión integrada. + +Un merge combina historias. Un conflicto significa que Git necesita una decisión humana sobre la intención correcta.`, + }, + { + type: "code_completion", + prompt: "Ordena los elementos.", + lines: [ + "git switch -c feature/cierre-ticket", + "# editar y probar", + "git add ...", + 'git commit -m "Implementa cierre de tickets"', + "git switch main", + "git merge feature/cierre-ticket", + ], + explanation: "La rama contiene commits revisables antes de integrar.", + }, + { + type: "quiz", + question: "¿Qué significa resolver bien un conflicto?", + options: [ + "Elegir siempre ours", + "Elegir siempre theirs", + "Entender ambas intenciones, producir resultado correcto y probarlo", + "Borrar archivo", + ], + correctIndex: 2, + explanation: "El conflicto es una decisión semántica.", + }, + { + type: "matching", + pairs: [ + { left: "branch", right: "Línea de trabajo nombrada" }, + { left: "merge", right: "Integración de historias" }, + { left: "conflicto", right: "Decisión que Git no puede resolver con seguridad" }, + { left: "main", right: "Línea integrada protegida" }, + ], + explanation: "El valor está en aislar y revisar cambios.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/04-repositorio-evidencia.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/04-repositorio-evidencia.ts new file mode 100644 index 0000000..6d1446c --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/04-git-versiones/lessons/04-repositorio-evidencia.ts @@ -0,0 +1,52 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "repositorio-evidencia", + title: "Repositorio como evidencia del proyecto", + description: + "Relaciona commits con requisitos, tareas y pruebas para que el repositorio sirva como evidencia del proyecto.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un buen repositorio permite responder: ¿cuándo apareció esta regla?, ¿qué tarea la justificó?, ¿qué pruebas la acompañaron? + +Para el integrador se espera historial real, no un único commit final. Trazabilidad mínima: tarea identificable, commits enfocados y evidencia de prueba.`, + }, + { + type: "matching", + pairs: [ + { left: "REQ-03 cerrar ticket", right: "Tarea implementable" }, + { left: "Commit Implementa REQ-03", right: "Evidencia de cambio" }, + { left: "Casos REQ-03", right: "Evidencia de verificación" }, + { left: "Reporte de entrega", right: "Estado de fase" }, + ], + explanation: "La trazabilidad conecta intención y evidencia.", + }, + { + type: "quiz", + question: "¿Cuál historial demuestra mejor proceso?", + options: [ + "Un commit final enorme", + "Commits coherentes asociados a tareas/pruebas", + "Muchos commits x", + "Recrear repo cada semana", + ], + correctIndex: 1, + explanation: "El objetivo es reconstruir decisiones.", + }, + { + type: "fill_blank", + prompt: "Completa los espacios.", + template: "requisito → tarea → {{0}} → pruebas → entrega", + blanks: [ + { + answer: "commit", + hint: "Registra el cambio de manera rastreable.", + }, + ], + explanation: "Cadena mínima de trazabilidad práctica.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/index.ts new file mode 100644 index 0000000..241af0f --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/index.ts @@ -0,0 +1,19 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-descomponer-trabajo"; +import { leccion02 } from "./lessons/02-dependencias-gantt"; +import { leccion03 } from "./lessons/03-implementar-trazabilidad"; +import { leccion04 } from "./lessons/04-seguimiento-desviaciones"; + +import { practice } from "./practice"; + +export const mmPlanificacionImplementacion = defineUnit({ + slug: "mm-05-planificacion-implementacion", + title: "Planificación, tareas e implementación", + description: + "Descompone entregables en tareas verificables, ordénalas con dependencias y da seguimiento a su avance implementando contra requisitos y diseño de forma trazable.", + icon: "🗓️", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04], + practice, +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/01-descomponer-trabajo.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/01-descomponer-trabajo.ts new file mode 100644 index 0000000..d05c92f --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/01-descomponer-trabajo.ts @@ -0,0 +1,97 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "descomponer-trabajo", + title: "De requisito a tareas implementables", + description: "Dividir entregables en trabajo pequeño con condición de terminado.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `"Hacer sistema" no es una tarea. "Implementar transición ABIERTO→EN_PROCESO con casos de prueba" sí tiene frontera. + +Descomponer permite estimar, detectar dependencias y revisar avance real.`, + }, + { + type: "matching", + pairs: [ + { left: "Crear proyecto", right: "Demasiado amplio" }, + { left: "Definir modelo Ticket", right: "Acotada" }, + { left: "Implementar cierre con pruebas", right: "Acotada" }, + { left: "Mejorar todo", right: "No verificable" }, + ], + explanation: "Si no sabes demostrar que terminó, aún no está bien descompuesta.", + }, + { + type: "quiz", + question: "¿Cuál es mejor definición de terminado?", + options: [ + "Ya me cansé", + "Compila", + "Cumple criterio, pasa pruebas y queda registrable/revisable", + "Tiene muchas líneas", + ], + correctIndex: 2, + explanation: "Terminar combina comportamiento, evidencia y estado versionado.", + }, + { + type: "code_challenge", + exercise: { + prompt: "Horas estimadas: complejidad*2 y suma 3 si hay dependencia externa.", + starterCode: `using System; +class Program +{ + static int Horas(int c, bool externa) + { + return 0; + } + static void Main() + { + int c = int.Parse(Console.ReadLine()); + bool e = Console.ReadLine() == "SI"; + Console.WriteLine(Horas(c, e)); + } +}`, + solutionCode: `using System; +class Program +{ + static int Horas(int c, bool externa) + { + int h = c * 2; + if (externa) h += 3; + return h; + } + static void Main() + { + int c = int.Parse(Console.ReadLine()); + bool e = Console.ReadLine() == "SI"; + Console.WriteLine(Horas(c, e)); + } +}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { + visible: true, + stdin: "3\nNO\n", + expectedStdout: "6\n", + description: "Sin dependencia", + }, + { + visible: false, + stdin: "3\nSI\n", + expectedStdout: "9\n", + description: "Con dependencia", + }, + { + visible: false, + stdin: "1\nSI\n", + expectedStdout: "5\n", + description: "Pequeña", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/02-dependencias-gantt.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/02-dependencias-gantt.ts new file mode 100644 index 0000000..49f1fc7 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/02-dependencias-gantt.ts @@ -0,0 +1,90 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "dependencias-gantt", + title: "Dependencias, estimación y carta Gantt", + description: "Construir una secuencia temporal que respete dependencias.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `La carta Gantt sirve si refleja tareas, duración, dependencias e hitos. No vuelve cierta una estimación; vuelve visibles sus supuestos. + +Si "probar cierre" depende de "implementar cierre", programarlas como independientes oculta información.`, + }, + { + type: "matching", + pairs: [ + { left: "Diseñar modelo", right: "Puede bloquear implementación" }, + { left: "Implementar cierre", right: "Bloquea pruebas de cierre" }, + { left: "Preparar datos de prueba", right: "Puede adelantarse con requisito claro" }, + { left: "Reporte final", right: "Depende de resultados" }, + ], + explanation: "Dependencia describe qué resultado necesita otra tarea.", + }, + { + type: "quiz", + question: "Una tarea estimada 2 días tarda 4. ¿Qué haces?", + options: [ + "Ocultarlo", + "Actualizar seguimiento y analizar impacto", + "Cambiar historia para fingir 4 desde el inicio", + "Eliminar tarea", + ], + correctIndex: 1, + explanation: "El plan sirve para detectar desviaciones.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Lee duraciones A, B, C. B y C dependen de A y pueden ir en paralelo. Imprime duración mínima A+max(B,C).", + starterCode: `using System; +class Program +{ + static void Main() + { + int a = int.Parse(Console.ReadLine()); + int b = int.Parse(Console.ReadLine()); + int c = int.Parse(Console.ReadLine()); + /* completa */ + } +}`, + solutionCode: `using System; +class Program +{ + static void Main() + { + int a = int.Parse(Console.ReadLine()); + int b = int.Parse(Console.ReadLine()); + int c = int.Parse(Console.ReadLine()); + Console.WriteLine(a + (b > c ? b : c)); + } +}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { + visible: true, + stdin: "2\n3\n5\n", + expectedStdout: "7\n", + description: "C domina", + }, + { + visible: false, + stdin: "4\n1\n1\n", + expectedStdout: "5\n", + description: "Iguales", + }, + { + visible: false, + stdin: "1\n8\n2\n", + expectedStdout: "9\n", + description: "B domina", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/03-implementar-trazabilidad.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/03-implementar-trazabilidad.ts new file mode 100644 index 0000000..2e8fc01 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/03-implementar-trazabilidad.ts @@ -0,0 +1,115 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "implementar-trazabilidad", + title: "Implementar contra requisitos y diseño", + description: "Evitar reinterpretación silenciosa durante implementación.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Durante implementación aparecen detalles no especificados. Si cambian comportamiento observable, no deben decidirse silenciosamente dentro del código: se aclara requisito o se registra decisión. + +Trazabilidad mínima permite revisar una feature desde requisito hasta commit y pruebas.`, + }, + { + type: "matching", + pairs: [ + { left: "REQ-07 no cerrar sin técnico", right: "Regla" }, + { left: "Ticket.PuedeCerrar", right: "Implementación" }, + { left: "test sin técnico→NO", right: "Evidencia" }, + { left: "commit asociado", right: "Historial" }, + ], + explanation: "Cada nivel responde una pregunta diferente.", + }, + { + type: "quiz", + question: "Detectas dos interpretaciones válidas del requisito con resultados distintos. ¿Qué haces?", + options: [ + "Elegir menos código", + "Hacer ambas", + "Aclarar/registrar y luego implementar", + "Ocultar en comentario", + ], + correctIndex: 2, + explanation: "Implementación no debe resolver ambigüedad de negocio silenciosamente.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Ticket inicia ABIERTO. `CambiarEstado` permite ABIERTO→EN_PROCESO y EN_PROCESO→CERRADO; inválida no cambia y devuelve false.", + starterCode: `using System; +class Ticket +{ + public string Estado { get; private set; } + public Ticket() { Estado = "ABIERTO"; } + public bool CambiarEstado(string nuevo) + { + return false; + } +} +class Program +{ + static void Main() + { + Ticket t = new Ticket(); + string a = Console.ReadLine(); + string b = Console.ReadLine(); + Console.WriteLine(t.CambiarEstado(a) ? t.Estado : "INVALIDA"); + Console.WriteLine(t.CambiarEstado(b) ? t.Estado : "INVALIDA"); + } +}`, + solutionCode: `using System; +class Ticket +{ + public string Estado { get; private set; } + public Ticket() { Estado = "ABIERTO"; } + public bool CambiarEstado(string nuevo) + { + bool ok = (Estado == "ABIERTO" && nuevo == "EN_PROCESO") || (Estado == "EN_PROCESO" && nuevo == "CERRADO"); + if (ok) Estado = nuevo; + return ok; + } +} +class Program +{ + static void Main() + { + Ticket t = new Ticket(); + string a = Console.ReadLine(); + string b = Console.ReadLine(); + Console.WriteLine(t.CambiarEstado(a) ? t.Estado : "INVALIDA"); + Console.WriteLine(t.CambiarEstado(b) ? t.Estado : "INVALIDA"); + } +}`, + difficulty: "medium", + xpReward: 35, + structure: { + classes: [ + { + name: "Ticket", + properties: [{ name: "Estado", type: "string", visibility: "public" }], + methods: [{ name: "CambiarEstado", visibility: "public", paramCount: 1 }], + }, + ], + }, + testCases: [ + { + visible: true, + stdin: "EN_PROCESO\nCERRADO\n", + expectedStdout: "EN_PROCESO\nCERRADO\n", + description: "Camino válido", + }, + { + visible: false, + stdin: "CERRADO\nEN_PROCESO\n", + expectedStdout: "INVALIDA\nEN_PROCESO\n", + description: "Primer cambio inválido", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/04-seguimiento-desviaciones.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/04-seguimiento-desviaciones.ts new file mode 100644 index 0000000..aa86242 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/lessons/04-seguimiento-desviaciones.ts @@ -0,0 +1,87 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "seguimiento-desviaciones", + title: "Seguimiento y desviaciones", + description: "Usar datos de avance para replanear sin maquillar estado.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Seguimiento no es preguntar "¿cómo vamos?". Necesitas tareas previstas, completadas, bloqueadas y evidencia. + +Reportar "80%" sin criterio suele ser ruido. Es mejor saber qué entregables están terminados y qué bloqueo impide avanzar.`, + }, + { + type: "matching", + pairs: [ + { left: "Terminada", right: "Cumple definición de terminado" }, + { left: "Bloqueada", right: "Depende de impedimento no resuelto" }, + { left: "En curso", right: "Tiene trabajo pendiente identificable" }, + { left: "No iniciada", right: "Sin evidencia de ejecución" }, + ], + explanation: "Los estados deben significar algo observable.", + }, + { + type: "quiz", + question: "¿Qué dato permite mejor decisión?", + options: [ + "Se siente casi listo", + "7/10 verificadas, 2 bloqueadas, 1 no iniciada", + "Trabajamos mucho", + "Hay muchos commits", + ], + correctIndex: 1, + explanation: "Permite actuar sobre entregables/bloqueos.", + }, + { + type: "code_challenge", + exercise: { + prompt: "Lee total y completadas. Imprime porcentaje entero truncado; si total=0 imprime 0.", + starterCode: `using System; +class Program +{ + static void Main() + { + int total = int.Parse(Console.ReadLine()); + int hechas = int.Parse(Console.ReadLine()); + /* completa */ + } +}`, + solutionCode: `using System; +class Program +{ + static void Main() + { + int total = int.Parse(Console.ReadLine()); + int hechas = int.Parse(Console.ReadLine()); + Console.WriteLine(total == 0 ? 0 : hechas * 100 / total); + } +}`, + difficulty: "easy", + xpReward: 20, + testCases: [ + { + visible: true, + stdin: "10\n7\n", + expectedStdout: "70\n", + description: "Normal", + }, + { + visible: false, + stdin: "3\n1\n", + expectedStdout: "33\n", + description: "Truncado", + }, + { + visible: false, + stdin: "0\n0\n", + expectedStdout: "0\n", + description: "Sin división por cero", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/practice.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/practice.ts new file mode 100644 index 0000000..be3ce64 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/05-planificacion-implementacion/practice.ts @@ -0,0 +1,146 @@ +import type { PracticeExerciseDefinition } from "../../../../exercises/types"; + +export const practice = [ + { + slug: "mm-plan-ruta", + title: "Ruta crítica simple", + description: "Calcula duración con ramas paralelas.", + prompt: "A seguido por B y C paralelos; imprime A+max(B,C).", + starterCode: `using System; +class Program +{ + static void Main() + { + int a = int.Parse(Console.ReadLine()); + int b = int.Parse(Console.ReadLine()); + int c = int.Parse(Console.ReadLine()); + /* completa */ + } +}`, + solutionCode: `using System; +class Program +{ + static void Main() + { + int a = int.Parse(Console.ReadLine()); + int b = int.Parse(Console.ReadLine()); + int c = int.Parse(Console.ReadLine()); + Console.WriteLine(a + (b > c ? b : c)); + } +}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "2\n4\n7\n", expectedStdout: "9\n", visible: true, description: "C" }, + { stdin: "5\n8\n1\n", expectedStdout: "13\n", visible: false, description: "B" }, + ], + }, + { + slug: "mm-plan-porcentaje", + title: "Avance medible", + description: "Calcula avance sin dividir por cero.", + prompt: "total/completadas → porcentaje entero.", + starterCode: `using System; +class Program +{ + static void Main() + { + int t = int.Parse(Console.ReadLine()); + int c = int.Parse(Console.ReadLine()); + /* completa */ + } +}`, + solutionCode: `using System; +class Program +{ + static void Main() + { + int t = int.Parse(Console.ReadLine()); + int c = int.Parse(Console.ReadLine()); + Console.WriteLine(t == 0 ? 0 : c * 100 / t); + } +}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "8\n6\n", expectedStdout: "75\n", visible: true, description: "Normal" }, + { stdin: "0\n0\n", expectedStdout: "0\n", visible: false, description: "Cero" }, + { stdin: "6\n1\n", expectedStdout: "16\n", visible: false, description: "Truncado" }, + ], + }, + { + slug: "mm-plan-ticket", + title: "Cambio trazable", + description: "Implementa dos transiciones permitidas de Ticket.", + prompt: + "Ticket inicia ABIERTO; lee un nuevo estado, aplica si es EN_PROCESO; luego lee otro, aplica si CERRADO. Imprime estado final.", + starterCode: `using System; +class Ticket +{ + /* completa */ +} +class Program +{ + /* completa */ +}`, + solutionCode: `using System; +class Ticket +{ + public string Estado { get; private set; } + public Ticket() { Estado = "ABIERTO"; } + public bool Cambiar(string n) + { + if (Estado == "ABIERTO" && n == "EN_PROCESO") { Estado = n; return true; } + if (Estado == "EN_PROCESO" && n == "CERRADO") { Estado = n; return true; } + return false; + } +} +class Program +{ + static void Main() + { + Ticket t = new Ticket(); + t.Cambiar(Console.ReadLine()); + t.Cambiar(Console.ReadLine()); + Console.WriteLine(t.Estado); + } +}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "EN_PROCESO\nCERRADO\n", expectedStdout: "CERRADO\n", visible: true, description: "Completo" }, + { stdin: "CERRADO\nEN_PROCESO\n", expectedStdout: "EN_PROCESO\n", visible: false, description: "Primero inválido" }, + ], + }, + { + slug: "mm-plan-costo-cambio", + title: "Costo de una desviación", + description: "Calcula impacto presupuestal.", + prompt: "Lee costoActual, horasExtra y tarifaHora; imprime nuevo costo.", + starterCode: `using System; +class Program +{ + static void Main() + { + /* completa */ + } +}`, + solutionCode: `using System; +class Program +{ + static void Main() + { + int c = int.Parse(Console.ReadLine()); + int h = int.Parse(Console.ReadLine()); + int t = int.Parse(Console.ReadLine()); + Console.WriteLine(c + h * t); + } +}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "1000\n3\n100\n", expectedStdout: "1300\n", visible: true, description: "Normal" }, + { stdin: "500\n0\n90\n", expectedStdout: "500\n", visible: false, description: "Sin extra" }, + ], + }, +] satisfies PracticeExerciseDefinition[]; diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/index.ts new file mode 100644 index 0000000..1cf2fe8 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/index.ts @@ -0,0 +1,19 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-verificacion-validacion"; +import { leccion02 } from "./lessons/02-tipos-prueba"; +import { leccion03 } from "./lessons/03-casos-fronteras"; +import { leccion04 } from "./lessons/04-regresion-resultados"; + +import { practice } from "./practice"; + +export const mmPruebasCalidad = defineUnit({ + slug: "mm-06-pruebas-calidad", + title: "Pruebas, verificación y calidad", + description: + "Distingue verificación de validación, elige el tipo de prueba adecuado y diseña casos de prueba que cubran fronteras, particiones y regresiones con evidencia documentada.", + icon: "🧪", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04], + practice, +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/01-verificacion-validacion.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/01-verificacion-validacion.ts new file mode 100644 index 0000000..153df16 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/01-verificacion-validacion.ts @@ -0,0 +1,73 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "verificacion-validacion", + title: "Verificación y validación", + description: + "Distingue construir conforme a especificación de construir lo que se necesita.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `**Verificación:** ¿construimos conforme a requisitos/diseño? +**Validación:** ¿el producto resuelve la necesidad de uso? + +Un sistema puede verificar perfectamente una especificación equivocada. Calidad necesita ambas preguntas.`, + }, + { + type: "matching", + pairs: [ + { left: "Comparar salida con REQ-03", right: "Verificación" }, + { left: "Usuario prueba flujo real", right: "Validación" }, + { left: "Revisar UML contra requisitos", right: "Verificación" }, + { left: "Piloto con técnicos reales", right: "Validación" }, + ], + explanation: "La fuente de verdad cambia según la pregunta.", + }, + { + type: "quiz", + question: + "Pruebas automatizadas pasan, pero el usuario no puede completar su trabajo. ¿Qué falló principalmente?", + options: [ + "Compilación", + "Validación contra necesidad real", + "Git", + "Sintaxis", + ], + correctIndex: 1, + explanation: + "Pasar casos escritos no garantiza haber especificado el problema correcto.", + }, + { + type: "code_challenge", + exercise: { + prompt: "`EsValida(descripcion)` acepta longitud 5..80 inclusive.", + starterCode: `using System; class Program{static bool EsValida(string d){return false;}static void Main(){Console.WriteLine(EsValida(Console.ReadLine())?"VALIDA":"INVALIDA");}}`, + solutionCode: `using System; class Program{static bool EsValida(string d){return d.Length>=5&&d.Length<=80;}static void Main(){Console.WriteLine(EsValida(Console.ReadLine())?"VALIDA":"INVALIDA");}}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { + stdin: "ABCDE\n", + expectedStdout: "VALIDA\n", + visible: true, + description: "Mínimo", + }, + { + stdin: "ABCD\n", + expectedStdout: "INVALIDA\n", + visible: false, + description: "Debajo", + }, + { + stdin: "1234567890\n", + expectedStdout: "VALIDA\n", + visible: false, + description: "Dentro", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/02-tipos-prueba.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/02-tipos-prueba.ts new file mode 100644 index 0000000..ead0cbc --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/02-tipos-prueba.ts @@ -0,0 +1,77 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "tipos-prueba", + title: "Tipos de pruebas", + description: "Elige unidad, integración, sistema o aceptación según la pregunta.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Una prueba unitaria aísla una pieza. Integración verifica colaboración entre piezas. Sistema observa el producto integrado. Aceptación compara la entrega con criterios acordados. + +Cuanto más pequeña sea la prueba que reproduce correctamente el defecto, más fácil suele ser diagnosticarlo.`, + }, + { + type: "matching", + pairs: [ + { left: "Prioridad.Calcular aislada", right: "Unitaria" }, + { left: "Servicio + repositorio", right: "Integración" }, + { left: "Registrar/asignar/cerrar completo", right: "Sistema" }, + { left: "REQ-12 contra criterio del cliente", right: "Aceptación" }, + ], + explanation: "El nivel depende de cuántas fronteras necesitas comprobar.", + }, + { + type: "quiz", + question: + "Falla al guardar aunque `Ticket.Validar()` pasa aislado. ¿Dónde buscar primero?", + options: [ + "Sólo Ticket", + "Integración servicio/repositorio/persistencia", + "Colores UI", + "Mensaje commit", + ], + correctIndex: 1, + explanation: "El síntoma aparece al cruzar componentes.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "`ClasificarNota`: 0..59 REPROBADO, 60..79 SUFICIENTE, 80..100 ALTO, fuera INVALIDA.", + starterCode: `using System; class Program{static string Clasificar(int n){return "";}static void Main(){Console.WriteLine(Clasificar(int.Parse(Console.ReadLine())));}}`, + solutionCode: `using System; class Program{static string Clasificar(int n){if(n<0||n>100)return "INVALIDA";if(n<60)return "REPROBADO";if(n<80)return "SUFICIENTE";return "ALTO";}static void Main(){Console.WriteLine(Clasificar(int.Parse(Console.ReadLine())));}}`, + difficulty: "medium", + xpReward: 30, + testCases: [ + { + stdin: "59\n", + expectedStdout: "REPROBADO\n", + visible: true, + description: "Borde", + }, + { + stdin: "60\n", + expectedStdout: "SUFICIENTE\n", + visible: false, + description: "Borde", + }, + { + stdin: "80\n", + expectedStdout: "ALTO\n", + visible: false, + description: "Borde", + }, + { + stdin: "101\n", + expectedStdout: "INVALIDA\n", + visible: false, + description: "Fuera", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/03-casos-fronteras.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/03-casos-fronteras.ts new file mode 100644 index 0000000..bd3c9d4 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/03-casos-fronteras.ts @@ -0,0 +1,78 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "casos-fronteras", + title: "Casos de prueba, fronteras y particiones", + description: "Diseña pocos casos que discriminen reglas.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Si una regla cambia en 60 y 80, probar 65,70,75 aporta menos que 59/60 y 79/80. Las **fronteras** concentran errores. + +Las particiones agrupan inputs con comportamiento equivalente; elige representantes y fronteras.`, + }, + { + type: "matching", + pairs: [ + { left: "Edad mínima 18", right: "17 y 18" }, + { left: "Máximo 5 intentos", right: "5 y 6" }, + { left: "Texto obligatorio", right: "vacío/no vacío" }, + { left: "Estado enum", right: "válido/desconocido" }, + ], + explanation: "Diseña alrededor de cambios de comportamiento.", + }, + { + type: "quiz", + question: + "Para rango 10..20 inclusive, ¿qué conjunto discrimina mejor fronteras?", + options: ["12,13,14", "10,15,20", "9,10,20,21", "1,100"], + correctIndex: 2, + explanation: "Comprueba justo fuera/dentro de ambos límites.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Descuento por cantidad: <1 INVALIDA; 1..9=0; 10..49=5; >=50=10.", + starterCode: `using System; class Program{static void Main(){int n=int.Parse(Console.ReadLine());/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int n=int.Parse(Console.ReadLine());if(n<1)Console.WriteLine("INVALIDA");else if(n<10)Console.WriteLine(0);else if(n<50)Console.WriteLine(5);else Console.WriteLine(10);}}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { + stdin: "9\n", + expectedStdout: "0\n", + visible: true, + description: "9", + }, + { + stdin: "10\n", + expectedStdout: "5\n", + visible: false, + description: "10", + }, + { + stdin: "49\n", + expectedStdout: "5\n", + visible: false, + description: "49", + }, + { + stdin: "50\n", + expectedStdout: "10\n", + visible: false, + description: "50", + }, + { + stdin: "0\n", + expectedStdout: "INVALIDA\n", + visible: false, + description: "0", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/04-regresion-resultados.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/04-regresion-resultados.ts new file mode 100644 index 0000000..c1ac101 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/lessons/04-regresion-resultados.ts @@ -0,0 +1,79 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "regresion-resultados", + title: "Regresión y documentación de resultados", + description: + "Convierte un defecto corregido en protección repetible y registra evidencia.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Una regresión es comportamiento que antes funcionaba y dejó de hacerlo. Al corregir un bug, captura un caso que lo reproduzca; después queda como protección. + +Documentar resultados significa guardar qué se probó, entradas relevantes, esperado, obtenido y estado. "Probado" sin evidencia no permite reproducir nada.`, + }, + { + type: "code_example", + code: `using System; +class Tarifas{public static int Calcular(int h){return h<=1?100:100+(h-1)*50;}} +class Program{static void Main(){Console.WriteLine(Tarifas.Calcular(1)==100?"OK":"FALLO");Console.WriteLine(Tarifas.Calcular(3)==200?"OK":"FALLO");}}`, + runnable: true, + expectedOutput: `OK +OK`, + explanation: "Las afirmaciones capturan comportamiento repetible.", + }, + { + type: "quiz", + question: + "Corriges un bug que ocurría con cantidad=50. ¿Qué evita mejor que vuelva?", + options: [ + "Comentario", + "Agregar caso de regresión con 50 y mantenerlo", + "Renombrar método", + "No tocar archivo", + ], + correctIndex: 1, + explanation: + "El caso automatizado convierte defecto histórico en condición verificable.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Costo: primera hora 100, cada adicional 50; 0 o negativo devuelve 0.", + starterCode: `using System; class Program{static int Costo(int horas){return 0;}static void Main(){Console.WriteLine(Costo(int.Parse(Console.ReadLine())));}}`, + solutionCode: `using System; class Program{static int Costo(int horas){if(horas<=0)return 0;return 100+(horas-1)*50;}static void Main(){Console.WriteLine(Costo(int.Parse(Console.ReadLine())));}}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { + stdin: "1\n", + expectedStdout: "100\n", + visible: true, + description: "Primera", + }, + { + stdin: "3\n", + expectedStdout: "200\n", + visible: false, + description: "Adicionales", + }, + { + stdin: "0\n", + expectedStdout: "0\n", + visible: false, + description: "Cero", + }, + { + stdin: "-2\n", + expectedStdout: "0\n", + visible: false, + description: "Negativo", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/practice.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/practice.ts new file mode 100644 index 0000000..53e8958 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/06-pruebas-calidad/practice.ts @@ -0,0 +1,66 @@ +import type { PracticeExerciseDefinition } from "../../../../exercises/types"; + +export const practice = [ + { + slug: "mm-test-rangos", + title: "Rangos y fronteras", + description: "Practica particiones.", + prompt: "0..17 MENOR, 18..64 ADULTO, >=65 MAYOR; negativo INVALIDA.", + starterCode: `using System; class Program{static void Main(){int n=int.Parse(Console.ReadLine());/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int n=int.Parse(Console.ReadLine());if(n<0)Console.WriteLine("INVALIDA");else if(n<18)Console.WriteLine("MENOR");else if(n<65)Console.WriteLine("ADULTO");else Console.WriteLine("MAYOR");}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "17\n", expectedStdout: "MENOR\n", visible: true, description: "Borde" }, + { stdin: "18\n", expectedStdout: "ADULTO\n", visible: false, description: "Borde" }, + { stdin: "65\n", expectedStdout: "MAYOR\n", visible: false, description: "Borde" }, + { stdin: "-1\n", expectedStdout: "INVALIDA\n", visible: false, description: "Inválido" }, + ], + }, + { + slug: "mm-test-intentos", + title: "Máximo de intentos", + description: "Evalúa frontera inclusiva.", + prompt: "1..5 PERMITIDO; >5 BLOQUEADO; <=0 INVALIDA.", + starterCode: `using System; class Program{static void Main(){int n=int.Parse(Console.ReadLine());/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int n=int.Parse(Console.ReadLine());if(n<=0)Console.WriteLine("INVALIDA");else Console.WriteLine(n<=5?"PERMITIDO":"BLOQUEADO");}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "5\n", expectedStdout: "PERMITIDO\n", visible: true, description: "Límite" }, + { stdin: "6\n", expectedStdout: "BLOQUEADO\n", visible: false, description: "Sobre límite" }, + { stdin: "0\n", expectedStdout: "INVALIDA\n", visible: false, description: "Inválido" }, + ], + }, + { + slug: "mm-test-regresion", + title: "Caso de regresión", + description: "Corrige una tarifa preservando comportamiento.", + prompt: "Base 80 + 20 por unidad adicional; cantidad<=0=0.", + starterCode: `using System; class Program{static int Total(int n){return 0;}static void Main(){Console.WriteLine(Total(int.Parse(Console.ReadLine())));}}`, + solutionCode: `using System; class Program{static int Total(int n){if(n<=0)return 0;return 80+(n-1)*20;}static void Main(){Console.WriteLine(Total(int.Parse(Console.ReadLine())));}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "1\n", expectedStdout: "80\n", visible: true, description: "Base" }, + { stdin: "2\n", expectedStdout: "100\n", visible: false, description: "Regresión" }, + { stdin: "0\n", expectedStdout: "0\n", visible: false, description: "Cero" }, + ], + }, + { + slug: "mm-test-matriz", + title: "Matriz de autorización", + description: "Cubre combinaciones de roles/acciones.", + prompt: "ADMIN puede VER/EDITAR; TECNICO VER; otro NO.", + starterCode: `using System; class Program{static void Main(){string r=Console.ReadLine();string a=Console.ReadLine();/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){string r=Console.ReadLine();string a=Console.ReadLine();bool ok=(r=="ADMIN"&&(a=="VER"||a=="EDITAR"))||(r=="TECNICO"&&a=="VER");Console.WriteLine(ok?"SI":"NO");}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "ADMIN\nEDITAR\n", expectedStdout: "SI\n", visible: true, description: "Admin" }, + { stdin: "TECNICO\nEDITAR\n", expectedStdout: "NO\n", visible: false, description: "Técnico" }, + { stdin: "TECNICO\nVER\n", expectedStdout: "SI\n", visible: false, description: "Ver" }, + { stdin: "X\nVER\n", expectedStdout: "NO\n", visible: false, description: "Desconocido" }, + ], + }, +] satisfies PracticeExerciseDefinition[]; diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/index.ts new file mode 100644 index 0000000..00f1d67 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/index.ts @@ -0,0 +1,19 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-tipos-mantenimiento"; +import { leccion02 } from "./lessons/02-reproducir-antes-corregir"; +import { leccion03 } from "./lessons/03-impacto-regresion"; +import { leccion04 } from "./lessons/04-plan-mejora"; + +import { practice } from "./practice"; + +export const mmMantenimiento = defineUnit({ + slug: "mm-07-mantenimiento", + title: "Mantenimiento y evolución segura", + description: + "Clasifica el mantenimiento por intención, reproduce defectos antes de corregirlos y prioriza mejoras protegiendo el comportamiento previo.", + icon: "🔧", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04], + practice, +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/01-tipos-mantenimiento.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/01-tipos-mantenimiento.ts new file mode 100644 index 0000000..3b734c2 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/01-tipos-mantenimiento.ts @@ -0,0 +1,69 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "tipos-mantenimiento", + title: "Tipos de mantenimiento", + description: "Clasificar correctivo, adaptativo, perfectivo y preventivo por intención.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Mantenimiento empieza después de una entrega, pero no significa sólo "arreglar bugs". + +- **Correctivo:** corrige defecto. +- **Adaptativo:** responde a cambio externo. +- **Perfectivo:** mejora capacidad/uso/rendimiento. +- **Preventivo:** reduce riesgo o deuda antes de fallo observable.`, + }, + { + type: "matching", + pairs: [ + { left: "Corrige cálculo erróneo", right: "Correctivo" }, + { left: "Nueva versión obligatoria del SO/API", right: "Adaptativo" }, + { left: "Añade filtro solicitado", right: "Perfectivo" }, + { left: "Refactor para eliminar duplicación riesgosa", right: "Preventivo" }, + ], + explanation: "Clasifica por motivo principal del cambio.", + }, + { + type: "quiz", + question: + "¿Qué mantenimiento es cambiar un formato de integración porque el proveedor dejó de aceptar el anterior?", + options: ["Correctivo", "Adaptativo", "Perfectivo", "Ninguno"], + correctIndex: 1, + explanation: "El entorno externo cambió.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Clasifica código C/A/P/R: C→CORRECTIVO, A→ADAPTATIVO, P→PERFECTIVO, R→PREVENTIVO; otro INVALIDO.", + starterCode: `using System; class Program{static void Main(){string x=Console.ReadLine();/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){string x=Console.ReadLine();if(x=="C")Console.WriteLine("CORRECTIVO");else if(x=="A")Console.WriteLine("ADAPTATIVO");else if(x=="P")Console.WriteLine("PERFECTIVO");else if(x=="R")Console.WriteLine("PREVENTIVO");else Console.WriteLine("INVALIDO");}}`, + difficulty: "easy", + xpReward: 20, + testCases: [ + { + stdin: "C\n", + expectedStdout: "CORRECTIVO\n", + visible: true, + description: "Correctivo", + }, + { + stdin: "R\n", + expectedStdout: "PREVENTIVO\n", + visible: false, + description: "Preventivo", + }, + { + stdin: "X\n", + expectedStdout: "INVALIDO\n", + visible: false, + description: "Inválido", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/02-reproducir-antes-corregir.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/02-reproducir-antes-corregir.ts new file mode 100644 index 0000000..884fbd9 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/02-reproducir-antes-corregir.ts @@ -0,0 +1,70 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "reproducir-antes-corregir", + title: "Reproducir antes de corregir", + description: "Separar síntoma, causa, reproducción y fix.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un buen mantenimiento correctivo empieza por una reproducción mínima. Sin ella no sabes si arreglaste la causa o sólo moviste el síntoma. + +Registra entrada, estado inicial, resultado esperado y resultado real. Después reduce el caso hasta conservar el fallo.`, + }, + { + type: "matching", + pairs: [ + { left: "Síntoma", right: "Lo observable que está mal" }, + { left: "Reproducción", right: "Pasos/datos que lo hacen aparecer" }, + { left: "Causa", right: "Condición interna que lo provoca" }, + { left: "Fix", right: "Cambio que elimina causa sin romper contrato" }, + ], + explanation: "No confundir lo que se ve con por qué sucede.", + }, + { + type: "quiz", + question: "¿Cuál es mejor primer paso ante un bug intermitente?", + options: [ + "Reescribir módulo", + "Capturar condiciones y reproducción mínima", + "Agregar try/catch global", + "Cambiar arquitectura", + ], + correctIndex: 1, + explanation: "La evidencia reduce hipótesis.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Bug: `Promedio` divide entre cantidad cero. Corrige para devolver 0 si cantidad<=0, si no total/cantidad entero.", + starterCode: `using System; class Program{static int Promedio(int total,int cantidad){return total/cantidad;}static void Main(){int t=int.Parse(Console.ReadLine());int c=int.Parse(Console.ReadLine());Console.WriteLine(Promedio(t,c));}}`, + solutionCode: `using System; class Program{static int Promedio(int total,int cantidad){if(cantidad<=0)return 0;return total/cantidad;}static void Main(){int t=int.Parse(Console.ReadLine());int c=int.Parse(Console.ReadLine());Console.WriteLine(Promedio(t,c));}}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { + stdin: "100\n4\n", + expectedStdout: "25\n", + visible: true, + description: "Normal", + }, + { + stdin: "100\n0\n", + expectedStdout: "0\n", + visible: false, + description: "Reproducción bug", + }, + { + stdin: "5\n-1\n", + expectedStdout: "0\n", + visible: false, + description: "Inválido", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/03-impacto-regresion.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/03-impacto-regresion.ts new file mode 100644 index 0000000..8e1a2ca --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/03-impacto-regresion.ts @@ -0,0 +1,70 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "impacto-regresion", + title: "Impacto y regresión de un cambio", + description: "Identificar qué comportamiento anterior necesita protección.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Cambiar una regla no autoriza a romper las demás. Antes del fix, identifica consumidores, casos existentes e invariantes. + +Después del cambio ejecuta pruebas nuevas y regresiones relevantes. No hace falta probar "todo el universo", sino cubrir aquello que el cambio puede afectar razonablemente.`, + }, + { + type: "matching", + pairs: [ + { left: "Cambio en cálculo de prioridad", right: "Pruebas de límites de prioridad" }, + { left: "Cambio sólo de texto", right: "Pruebas de reglas de dominio no son foco" }, + { left: "Cambio en autorización", right: "Matriz de roles/acciones" }, + { left: "Cambio en transición", right: "Estados válidos e inválidos" }, + ], + explanation: "El área de regresión sigue dependencias del cambio.", + }, + { + type: "quiz", + question: "Añades descuento a clientes con convenio. ¿Qué prueba anterior debes conservar?", + options: [ + "Sólo convenio=true", + "Caso sin convenio para demostrar que precio anterior no cambió", + "Sólo UI", + "Ninguna", + ], + correctIndex: 1, + explanation: "La regresión protege el comportamiento previo.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Tarifa anterior: base 100 + 50 si ALTA. Cambio: convenio descuenta 20%. Conserva caso sin convenio.", + starterCode: `using System; class Program{static int Total(string p,bool conv){return 0;}static void Main(){string p=Console.ReadLine();bool c=Console.ReadLine()=="SI";Console.WriteLine(Total(p,c));}}`, + solutionCode: `using System; class Program{static int Total(string p,bool conv){int t=100;if(p=="ALTA")t+=50;if(conv)t=t*80/100;return t;}static void Main(){string p=Console.ReadLine();bool c=Console.ReadLine()=="SI";Console.WriteLine(Total(p,c));}}`, + difficulty: "medium", + xpReward: 30, + testCases: [ + { + stdin: "ALTA\nNO\n", + expectedStdout: "150\n", + visible: true, + description: "Regresión", + }, + { + stdin: "ALTA\nSI\n", + expectedStdout: "120\n", + visible: false, + description: "Cambio", + }, + { + stdin: "BAJA\nNO\n", + expectedStdout: "100\n", + visible: false, + description: "Anterior", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/04-plan-mejora.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/04-plan-mejora.ts new file mode 100644 index 0000000..55f3fad --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/lessons/04-plan-mejora.ts @@ -0,0 +1,63 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "plan-mejora", + title: "Plan de mejora y actualización", + description: "Priorizar mantenimiento por impacto, riesgo, esfuerzo y evidencia.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un plan de mejora no es una lista de deseos. Cada propuesta necesita problema/evidencia, impacto esperado, esfuerzo aproximado, riesgo y criterio de aceptación. + +Priorizar significa aceptar que no todo cabe a la vez.`, + }, + { + type: "matching", + pairs: [ + { left: "Fallo que bloquea operación", right: "Prioridad alta" }, + { left: "Mejora estética sin impacto medido", right: "Menor prioridad" }, + { left: "Dependencia sin soporte próximo", right: "Riesgo preventivo relevante" }, + { left: "Duplicación que causa defectos repetidos", right: "Mejora preventiva respaldada" }, + ], + explanation: "Prioridad se justifica con evidencia y riesgo.", + }, + { + type: "quiz", + question: "¿Qué elemento vuelve accionable una mejora?", + options: [ + "“Hacerlo mejor”", + "Problema concreto + criterio de aceptación + prioridad", + "Más reuniones", + "Nombre creativo", + ], + correctIndex: 1, + explanation: "Permite decidir y verificar.", + }, + { + type: "code_challenge", + exercise: { + prompt: "Puntaje de prioridad = impacto*3 + riesgo*2 - esfuerzo. Lee tres enteros e imprime puntaje.", + starterCode: `using System; class Program{static void Main(){int i=int.Parse(Console.ReadLine());int r=int.Parse(Console.ReadLine());int e=int.Parse(Console.ReadLine());/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int i=int.Parse(Console.ReadLine());int r=int.Parse(Console.ReadLine());int e=int.Parse(Console.ReadLine());Console.WriteLine(i*3+r*2-e);}}`, + difficulty: "easy", + xpReward: 20, + testCases: [ + { + stdin: "5\n4\n3\n", + expectedStdout: "20\n", + visible: true, + description: "Alta", + }, + { + stdin: "1\n1\n5\n", + expectedStdout: "0\n", + visible: false, + description: "Baja", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/practice.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/practice.ts new file mode 100644 index 0000000..631068f --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/07-mantenimiento/practice.ts @@ -0,0 +1,62 @@ +import type { PracticeExerciseDefinition } from "../../../../exercises/types"; + +export const practice = [ + { + slug: "mm-mant-clasificar", + title: "Clasifica mantenimiento", + description: "Identifica intención del cambio.", + prompt: "Lee C/A/P/R y devuelve nombre completo; otro INVALIDO.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){string x=Console.ReadLine();if(x=="C")Console.WriteLine("CORRECTIVO");else if(x=="A")Console.WriteLine("ADAPTATIVO");else if(x=="P")Console.WriteLine("PERFECTIVO");else if(x=="R")Console.WriteLine("PREVENTIVO");else Console.WriteLine("INVALIDO");}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "A\n", expectedStdout: "ADAPTATIVO\n", visible: true, description: "Adaptativo" }, + { stdin: "P\n", expectedStdout: "PERFECTIVO\n", visible: false, description: "Perfectivo" }, + { stdin: "Z\n", expectedStdout: "INVALIDO\n", visible: false, description: "Inválido" }, + ], + }, + { + slug: "mm-mant-cero", + title: "Corrige división", + description: "Reproduce y corrige borde.", + prompt: "Promedio devuelve 0 con cantidad<=0.", + starterCode: `using System; class Program{static int P(int t,int c){return t/c;}static void Main(){Console.WriteLine(P(int.Parse(Console.ReadLine()),int.Parse(Console.ReadLine())));}}`, + solutionCode: `using System; class Program{static int P(int t,int c){return c<=0?0:t/c;}static void Main(){Console.WriteLine(P(int.Parse(Console.ReadLine()),int.Parse(Console.ReadLine())));}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "8\n2\n", expectedStdout: "4\n", visible: true, description: "Normal" }, + { stdin: "8\n0\n", expectedStdout: "0\n", visible: false, description: "Bug" }, + ], + }, + { + slug: "mm-mant-regresion", + title: "Cambio sin romper anterior", + description: "Aplica nueva regla y conserva antigua.", + prompt: "Base 200; URGENTE suma 100; convenio descuenta 10%.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){string p=Console.ReadLine();bool c=Console.ReadLine()=="SI";int t=200;if(p=="URGENTE")t+=100;if(c)t=t*90/100;Console.WriteLine(t);}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "NORMAL\nNO\n", expectedStdout: "200\n", visible: true, description: "Anterior" }, + { stdin: "URGENTE\nNO\n", expectedStdout: "300\n", visible: false, description: "Anterior urgente" }, + { stdin: "URGENTE\nSI\n", expectedStdout: "270\n", visible: false, description: "Cambio" }, + ], + }, + { + slug: "mm-mant-prioridad", + title: "Prioriza mejoras", + description: "Calcula puntaje explícito.", + prompt: "impacto*3+riesgo*2-esfuerzo.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int i=int.Parse(Console.ReadLine());int r=int.Parse(Console.ReadLine());int e=int.Parse(Console.ReadLine());Console.WriteLine(i*3+r*2-e);}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "4\n5\n2\n", expectedStdout: "20\n", visible: true, description: "Alto" }, + { stdin: "1\n1\n1\n", expectedStdout: "4\n", visible: false, description: "Bajo" }, + ], + }, +] satisfies PracticeExerciseDefinition[]; diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/index.ts new file mode 100644 index 0000000..1c1d78e --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/index.ts @@ -0,0 +1,21 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-por-que-solid"; +import { leccion02 } from "./lessons/02-srp"; +import { leccion03 } from "./lessons/03-ocp"; +import { leccion04 } from "./lessons/04-lsp"; +import { leccion05 } from "./lessons/05-isp"; +import { leccion06 } from "./lessons/06-dip"; + +import { practice } from "./practice"; + +export const mmSolid = defineUnit({ + slug: "mm-08-solid", + title: "SOLID aplicado a código mantenible", + description: + "Presenta cada principio SOLID (SRP, OCP, LSP, ISP, DIP) como respuesta a un costo de cambio observable, no como una sigla aislada.", + icon: "🧱", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04, leccion05, leccion06], + practice, +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/01-por-que-solid.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/01-por-que-solid.ts new file mode 100644 index 0000000..573a375 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/01-por-que-solid.ts @@ -0,0 +1,49 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "por-que-solid", + title: "Por qué existen los principios SOLID", + description: "Relaciona diseño con coste de cambio, pruebas y mantenimiento.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `SOLID no promete código “perfecto”. Son heurísticas para evitar dependencias y responsabilidades que hacen costoso cambiar el sistema. + +Una mala señal: un cambio pequeño obliga a editar muchos lugares no relacionados o una clase cambia por razones completamente diferentes.`, + }, + { + type: "matching", + pairs: [ + { left: "Cambio toca demasiadas clases", right: "Acoplamiento alto" }, + { left: "Una clase cambia por UI y persistencia", right: "Responsabilidades mezcladas" }, + { left: "Nueva variante exige editar if central", right: "Posible cierre a extensión" }, + { left: "Test necesita infraestructura real", right: "Dependencias rígidas" }, + ], + explanation: "Los síntomas dan contexto a los principios.", + }, + { + type: "quiz", + question: "¿Qué objetivo describe mejor SOLID?", + options: [ + "Maximizar clases", + "Eliminar todos los if", + "Hacer cambios más localizables y contratos más claros", + "Usar interfaces siempre", + ], + correctIndex: 2, + explanation: "Las siglas son medios, no fines.", + }, + { + type: "fill_blank", + prompt: "Completa los espacios.", + template: "Diseño mantenible = cambio {{0}} + contratos {{1}}", + blanks: [ + { answer: "localizable", hint: "El cambio debe afectar el menor conjunto razonable." }, + { answer: "claros", hint: "Los consumidores deben saber qué pueden esperar." }, + ], + explanation: "Resume el propósito operativo del bloque.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/02-srp.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/02-srp.ts new file mode 100644 index 0000000..8952dcb --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/02-srp.ts @@ -0,0 +1,70 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "srp", + title: "SRP — Responsabilidad única", + description: "Separa razones independientes de cambio.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `SRP no significa “una clase sólo puede tener un método”. Significa que una unidad debe agrupar responsabilidades que cambian por la misma razón. + +Si \`Ticket\` conoce reglas de estado, formato de reporte y persistencia, tres decisiones independientes están acopladas.`, + }, + { + type: "matching", + pairs: [ + { left: "Ticket", right: "Reglas/invariantes del dominio" }, + { left: "Repositorio", right: "Persistencia" }, + { left: "Formateador", right: "Presentación de texto" }, + { left: "Notificador", right: "Entrega de mensajes" }, + ], + explanation: "Separa razones de cambio.", + }, + { + type: "quiz", + question: "¿Cuál clase viola más claramente SRP?", + options: [ + "Ticket con CambiarEstado", + "Reporte que sólo formatea", + "Ticket que valida, guarda archivo y manda correo", + "Repositorio que guarda/carga", + ], + correctIndex: 2, + explanation: "Tiene razones de cambio independientes.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Separa cálculo de descuento y formato. `Calculadora.Descuento(int total,int porcentaje)` y `Formato.Texto(int total)`.", + starterCode: `using System; // crea Calculadora, Formato y Program`, + solutionCode: `using System; class Calculadora{public int Descuento(int total,int p){return total-total*p/100;}}class Formato{public string Texto(int total){return "TOTAL:"+total;}}class Program{static void Main(){int t=int.Parse(Console.ReadLine());int p=int.Parse(Console.ReadLine());Calculadora c=new Calculadora();Formato f=new Formato();Console.WriteLine(f.Texto(c.Descuento(t,p)));}}`, + difficulty: "medium", + xpReward: 30, + structure: { + classes: [ + { name: "Calculadora", methods: [{ name: "Descuento" }] }, + { name: "Formato", methods: [{ name: "Texto" }] }, + ], + }, + testCases: [ + { + stdin: "1000\n10\n", + expectedStdout: "TOTAL:900\n", + visible: true, + description: "Normal", + }, + { + stdin: "500\n0\n", + expectedStdout: "TOTAL:500\n", + visible: false, + description: "Sin descuento", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/03-ocp.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/03-ocp.ts new file mode 100644 index 0000000..a79b2e3 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/03-ocp.ts @@ -0,0 +1,70 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "ocp", + title: "OCP — Abierto a extensión, cerrado a modificación", + description: "Agrega variantes sin reescribir lógica estable.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `OCP busca que una nueva variante pueda añadirse extendiendo un contrato en vez de editar un bloque central lleno de condiciones. No significa “nunca modificar código”. + +Tiene sentido cuando existe variación real y repetida; abstraer una única posibilidad futura imaginaria puede empeorar el diseño.`, + }, + { + type: "matching", + pairs: [ + { left: "Nuevo tipo de descuento obliga a editar if central", right: "Señal para OCP" }, + { left: "Regla nunca cambia y es simple", right: "No abstraer por deporte" }, + { left: "Variantes comparten contrato Calcular", right: "Extensión controlada" }, + ], + explanation: "OCP responde a ejes reales de variación.", + }, + { + type: "quiz", + question: "¿Cuándo es razonable introducir una abstracción para OCP?", + options: [ + "Siempre antes de saber variantes", + "Cuando existe un eje real de variantes/cambios repetidos", + "Para tener más archivos", + "Para evitar tests", + ], + correctIndex: 1, + explanation: "La abstracción debe pagar un coste real.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Crea `abstract class Descuento` con `Aplicar(int total)` y dos derivadas `SinDescuento` y `DiezPorCiento`. Main lee `NINGUNO` o `DIEZ` y total.", + starterCode: `using System; // crea jerarquía y Program`, + solutionCode: `using System; abstract class Descuento{public abstract int Aplicar(int total);}class SinDescuento:Descuento{public override int Aplicar(int total){return total;}}class DiezPorCiento:Descuento{public override int Aplicar(int total){return total*90/100;}}class Program{static void Main(){string tipo=Console.ReadLine();int total=int.Parse(Console.ReadLine());Descuento d=tipo=="DIEZ"?(Descuento)new DiezPorCiento():new SinDescuento();Console.WriteLine(d.Aplicar(total));}}`, + difficulty: "medium", + xpReward: 35, + structure: { + classes: [ + { name: "Descuento", abstract: true, methods: [{ name: "Aplicar", abstract: true }] }, + { name: "SinDescuento", extends: "Descuento", methods: [{ name: "Aplicar", override: true }] }, + { name: "DiezPorCiento", extends: "Descuento", methods: [{ name: "Aplicar", override: true }] }, + ], + }, + testCases: [ + { + stdin: "NINGUNO\n1000\n", + expectedStdout: "1000\n", + visible: true, + description: "Base", + }, + { + stdin: "DIEZ\n1000\n", + expectedStdout: "900\n", + visible: false, + description: "Variante", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/04-lsp.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/04-lsp.ts new file mode 100644 index 0000000..26a01cc --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/04-lsp.ts @@ -0,0 +1,74 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "lsp", + title: "LSP — Sustitución de Liskov", + description: "Exige que una derivada respete las expectativas del contrato base.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Si \`B\` es subtipo de \`A\`, un consumidor de \`A\` debería poder usar \`B\` sin descubrir sorpresas que contradicen el contrato de \`A\`. + +LSP no se prueba sólo con \`class B : A\`; se prueba con comportamiento: precondiciones no más estrictas, postcondiciones compatibles e invariantes preservadas.`, + }, + { + type: "matching", + pairs: [ + { left: "Derivada lanza donde base promete aceptar", right: "Viola sustitución" }, + { left: "Derivada produce resultado compatible", right: "Sustituible" }, + { left: "Herencia sólo para reutilizar campos", right: "Motivo insuficiente" }, + ], + explanation: "La semántica importa más que la sintaxis.", + }, + { + type: "quiz", + question: "Base `Procesar` acepta cualquier cantidad >=0. Derivada rechaza 0. ¿Qué riesgo hay?", + options: [ + "Ninguno", + "La derivada fortalece precondición y puede romper consumidores", + "Sólo rendimiento", + "Git", + ], + correctIndex: 1, + explanation: "Un consumidor válido para la base deja de ser válido para la derivada.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "`Tarifa` define virtual `Calcular(int horas)` para horas>=0. `TarifaPremium` override mantiene 0→0 y cobra 80 por hora; base cobra 100 por hora. Main usa referencia base.", + starterCode: `using System; class Tarifa{public virtual int Calcular(int h){return h*100;}} // crea TarifaPremium y Program`, + solutionCode: `using System; class Tarifa{public virtual int Calcular(int h){return h*100;}}class TarifaPremium:Tarifa{public override int Calcular(int h){return h*80;}}class Program{static void Main(){string tipo=Console.ReadLine();int h=int.Parse(Console.ReadLine());Tarifa t=tipo=="PREMIUM"?(Tarifa)new TarifaPremium():new Tarifa();Console.WriteLine(t.Calcular(h));}}`, + difficulty: "medium", + xpReward: 35, + structure: { + classes: [ + { name: "TarifaPremium", extends: "Tarifa", methods: [{ name: "Calcular", override: true }] }, + ], + }, + testCases: [ + { + stdin: "NORMAL\n2\n", + expectedStdout: "200\n", + visible: true, + description: "Base", + }, + { + stdin: "PREMIUM\n2\n", + expectedStdout: "160\n", + visible: false, + description: "Derivada", + }, + { + stdin: "PREMIUM\n0\n", + expectedStdout: "0\n", + visible: false, + description: "Contrato", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/05-isp.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/05-isp.ts new file mode 100644 index 0000000..6b9fc9d --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/05-isp.ts @@ -0,0 +1,63 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion05 = defineLesson({ + slug: "isp", + title: "ISP — Segregación de interfaces", + description: "Da a cada consumidor sólo las operaciones que necesita.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Una interfaz enorme obliga a implementadores a depender de métodos irrelevantes. ISP prefiere contratos pequeños y cohesionados según necesidades de consumidores. + +No significa “una interfaz por método”; significa no obligar a un tipo a prometer capacidades que no tiene sentido cumplir.`, + }, + { + type: "matching", + pairs: [ + { left: "ILector con Leer", right: "Contrato de lectura" }, + { left: "IEscritor con Escribir", right: "Contrato de escritura" }, + { left: "Dispositivo sólo lectura obligado a Escribir", right: "Interfaz demasiado amplia" }, + ], + explanation: "Segrega por capacidad real.", + }, + { + type: "quiz", + question: "¿Qué síntoma apunta a ISP?", + options: [ + "Implementaciones con métodos que lanzan NotSupportedException por obligación", + "Clase con constructor", + "Uso de private", + "Dos tests", + ], + correctIndex: 0, + explanation: "El contrato exige capacidades que el tipo no puede cumplir honestamente.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Declara `ILector` con `Leer()` y `IEscritor` con `Escribir(string)`. `Memoria` implementa ambas. Main escribe y luego lee.", + starterCode: `using System; // define interfaces, Memoria, Program`, + solutionCode: `using System; interface ILector{string Leer();}interface IEscritor{void Escribir(string texto);}class Memoria:ILector,IEscritor{private string valor="";public string Leer(){return valor;}public void Escribir(string texto){valor=texto;}}class Program{static void Main(){Memoria m=new Memoria();m.Escribir(Console.ReadLine());Console.WriteLine(m.Leer());}}`, + difficulty: "medium", + xpReward: 30, + testCases: [ + { + stdin: "hola\n", + expectedStdout: "hola\n", + visible: true, + description: "Ambas capacidades", + }, + { + stdin: "abc\n", + expectedStdout: "abc\n", + visible: false, + description: "Variable", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/06-dip.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/06-dip.ts new file mode 100644 index 0000000..6413665 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/lessons/06-dip.ts @@ -0,0 +1,69 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion06 = defineLesson({ + slug: "dip", + title: "DIP — Inversión de dependencias", + description: "Haz que la política dependa de contratos, no de detalles rígidos.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Si \`ServicioTickets\` construye directamente \`ArchivoXml\`, su lógica queda unida a un detalle concreto. DIP hace que política de alto nivel dependa de una abstracción que también cumple el detalle. + +Esto permite probar con un sustituto simple y cambiar persistencia sin reescribir reglas.`, + }, + { + type: "matching", + pairs: [ + { left: "ServicioTickets", right: "Política de alto nivel" }, + { left: "IRepositorio", right: "Abstracción" }, + { left: "RepositorioMemoria", right: "Detalle sustituible" }, + { left: "RepositorioArchivo", right: "Otro detalle" }, + ], + explanation: "La dirección de dependencia apunta al contrato.", + }, + { + type: "quiz", + question: "¿Qué beneficio directo ofrece DIP en pruebas?", + options: [ + "No requiere asserts", + "Permite sustituir dependencias externas por implementaciones controladas", + "Elimina constructores", + "Evita Git", + ], + correctIndex: 1, + explanation: "La política puede probarse sin infraestructura real.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Define `IContador` con `Siguiente()`. `ContadorMemoria` implementa. `ServicioFolio` recibe IContador por constructor y devuelve `T-`.", + starterCode: `using System; // define IContador, ContadorMemoria, ServicioFolio y Program`, + solutionCode: `using System; interface IContador{int Siguiente();}class ContadorMemoria:IContador{private int n;public ContadorMemoria(int inicio){n=inicio;}public int Siguiente(){return n++;}}class ServicioFolio{private IContador contador;public ServicioFolio(IContador c){contador=c;}public string Nuevo(){return "T-"+contador.Siguiente();}}class Program{static void Main(){int i=int.Parse(Console.ReadLine());ServicioFolio s=new ServicioFolio(new ContadorMemoria(i));Console.WriteLine(s.Nuevo());Console.WriteLine(s.Nuevo());}}`, + difficulty: "medium", + xpReward: 35, + structure: { + classes: [ + { name: "ServicioFolio", stores: [{ type: "IContador" }], constructors: [{ paramCount: 1 }] }, + ], + }, + testCases: [ + { + stdin: "10\n", + expectedStdout: "T-10\nT-11\n", + visible: true, + description: "Inyección", + }, + { + stdin: "1\n", + expectedStdout: "T-1\nT-2\n", + visible: false, + description: "Variable", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/practice.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/practice.ts new file mode 100644 index 0000000..ce7d42e --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/08-solid/practice.ts @@ -0,0 +1,61 @@ +import type { PracticeExerciseDefinition } from "../../../../exercises/types"; + +export const practice = [ + { + slug: "mm-solid-srp", + title: "SRP práctico", + description: "Separa cálculo y formato.", + prompt: "Calculador multiplica cantidad*precio; Formato devuelve IMPORTE:N.", + starterCode: `using System; // crea dos clases`, + solutionCode: `using System; class Calculador{public int Total(int c,int p){return c*p;}}class Formato{public string Texto(int n){return "IMPORTE:"+n;}}class Program{static void Main(){int c=int.Parse(Console.ReadLine());int p=int.Parse(Console.ReadLine());Calculador x=new Calculador();Formato f=new Formato();Console.WriteLine(f.Texto(x.Total(c,p)));}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "2\n50\n", expectedStdout: "IMPORTE:100\n", visible: true, description: "Normal" }, + { stdin: "0\n50\n", expectedStdout: "IMPORTE:0\n", visible: false, description: "Cero" }, + ], + }, + { + slug: "mm-solid-ocp", + title: "OCP práctico", + description: "Extiende comportamiento por polimorfismo.", + prompt: "Base `Recargo` abstracta; Normal suma 0, Urgente suma 100.", + starterCode: `using System; // completa`, + solutionCode: `using System; abstract class Recargo{public abstract int Aplicar(int total);}class Normal:Recargo{public override int Aplicar(int total){return total;}}class Urgente:Recargo{public override int Aplicar(int total){return total+100;}}class Program{static void Main(){string t=Console.ReadLine();int n=int.Parse(Console.ReadLine());Recargo r=t=="URGENTE"?(Recargo)new Urgente():new Normal();Console.WriteLine(r.Aplicar(n));}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "NORMAL\n500\n", expectedStdout: "500\n", visible: true, description: "Normal" }, + { stdin: "URGENTE\n500\n", expectedStdout: "600\n", visible: false, description: "Extensión" }, + ], + }, + { + slug: "mm-solid-isp", + title: "ISP práctico", + description: "Segrega lectura y escritura.", + prompt: "Define ILector/IEscritor; Buffer implementa ambos.", + starterCode: `using System; // completa`, + solutionCode: `using System; interface ILector{string Leer();}interface IEscritor{void Escribir(string t);}class Buffer:ILector,IEscritor{private string v="";public string Leer(){return v;}public void Escribir(string t){v=t;}}class Program{static void Main(){Buffer b=new Buffer();b.Escribir(Console.ReadLine());Console.WriteLine(b.Leer());}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "x\n", expectedStdout: "x\n", visible: true, description: "Normal" }, + { stdin: "texto largo\n", expectedStdout: "texto largo\n", visible: false, description: "Variable" }, + ], + }, + { + slug: "mm-solid-dip", + title: "DIP práctico", + description: "Inyecta una abstracción.", + prompt: + "ILogger tiene Escribir; LoggerMemoria guarda último texto; Servicio recibe ILogger y registra `OK:`, luego Program imprime lo guardado.", + starterCode: `using System; // completa`, + solutionCode: `using System; interface ILogger{void Escribir(string t);}class LoggerMemoria:ILogger{public string Ultimo{get;private set;}public void Escribir(string t){Ultimo=t;}}class Servicio{private ILogger log;public Servicio(ILogger l){log=l;}public void Procesar(string d){log.Escribir("OK:"+d);}}class Program{static void Main(){LoggerMemoria l=new LoggerMemoria();new Servicio(l).Procesar(Console.ReadLine());Console.WriteLine(l.Ultimo);}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "T1\n", expectedStdout: "OK:T1\n", visible: true, description: "Inyección" }, + { stdin: "abc\n", expectedStdout: "OK:abc\n", visible: false, description: "Variable" }, + ], + }, +] satisfies PracticeExerciseDefinition[]; diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/index.ts new file mode 100644 index 0000000..97b13a8 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/index.ts @@ -0,0 +1,20 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-modelo-incremental"; +import { leccion02 } from "./lessons/02-iteracion-feedback"; +import { leccion03 } from "./lessons/03-plan-incrementos"; +import { leccion04 } from "./lessons/04-cronograma-entregas"; +import { leccion05 } from "./lessons/05-cascada-vs-incremental"; + +import { practice } from "./practice"; + +export const mmIncremental = defineUnit({ + slug: "mm-09-incremental", + title: "Desarrollo incremental e iterativo", + description: + "Divide una entrega en incrementos de valor demostrable, refínalos con feedback y planea su cronograma frente a cascada.", + icon: "📈", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04, leccion05], + practice, +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/01-modelo-incremental.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/01-modelo-incremental.ts new file mode 100644 index 0000000..5b0e7c3 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/01-modelo-incremental.ts @@ -0,0 +1,50 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "modelo-incremental", + title: "Modelo incremental: valor por partes", + description: "Distingue una entrega incremental de dividir tareas técnicas sin valor visible.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Un **incremento** añade una porción utilizable del producto. No es "hicimos la mitad de las clases"; es una capacidad coherente que puede demostrarse. + +El modelo reduce el tamaño de cada compromiso y permite aprender entre entregas.`, + }, + { + type: "matching", + pairs: [ + { left: "Registrar ticket completo", right: "Incremento funcional" }, + { left: "Crear 7 clases vacías", right: "Trabajo técnico, no incremento de valor" }, + { left: "Consultar por folio", right: "Incremento posible" }, + { left: "Sólo cambiar nombres internos", right: "No añade valor visible por sí mismo" }, + ], + explanation: "Incremento se define por capacidad entregable.", + }, + { + type: "quiz", + question: "¿Qué caracteriza mejor un incremento?", + options: ["Más archivos", "Porción integrada y demostrable del producto", "Una semana de trabajo", "Un branch"], + correctIndex: 1, + explanation: "Debe producir valor verificable.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Simula dos incrementos con función `Disponible(version, funcion)`: v1 permite REGISTRAR; v2 permite REGISTRAR y CONSULTAR; otro NO.", + starterCode: `using System; class Program{static bool Disponible(int v,string f){return false;}static void Main(){Console.WriteLine(Disponible(int.Parse(Console.ReadLine()),Console.ReadLine())?"SI":"NO");}}`, + solutionCode: `using System; class Program{static bool Disponible(int v,string f){if(v>=1&&f=="REGISTRAR")return true;if(v>=2&&f=="CONSULTAR")return true;return false;}static void Main(){Console.WriteLine(Disponible(int.Parse(Console.ReadLine()),Console.ReadLine())?"SI":"NO");}}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { stdin: "1\nREGISTRAR\n", expectedStdout: "SI\n", visible: true, description: "v1" }, + { stdin: "1\nCONSULTAR\n", expectedStdout: "NO\n", visible: false, description: "Aún no" }, + { stdin: "2\nCONSULTAR\n", expectedStdout: "SI\n", visible: false, description: "v2" }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/02-iteracion-feedback.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/02-iteracion-feedback.ts new file mode 100644 index 0000000..4448494 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/02-iteracion-feedback.ts @@ -0,0 +1,50 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "iteracion-feedback", + title: "Iteración y feedback", + description: "Usa feedback para ajustar una capacidad ya entregada.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Incremental responde **qué capacidad nueva** se añade; iterativo responde **cómo una capacidad existente se refina** al aprender. Pueden combinarse. + +Ejemplo: incremento 1 registra tickets; iteración posterior mejora validación de descripción a partir de uso real.`, + }, + { + type: "matching", + pairs: [ + { left: "Añadir consulta por folio", right: "Incremento" }, + { left: "Mejorar validación del registro existente", right: "Iteración" }, + { left: "Añadir cierre de ticket", right: "Incremento" }, + { left: "Refinar mensaje de error tras feedback", right: "Iteración" }, + ], + explanation: "Incremento agrega capacidad; iteración mejora una existente.", + }, + { + type: "quiz", + question: "¿Cuál ejemplo es iterativo?", + options: ["Agregar módulo nuevo", "Refinar búsqueda existente tras feedback", "Crear repo", "Añadir nuevo actor"], + correctIndex: 1, + explanation: "Se aprende y mejora algo ya disponible.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Versión 1 acepta descripción >=3; versión 2 exige >=5. Lee versión y texto, imprime VALIDA/INVALIDA.", + starterCode: `using System; class Program{static void Main(){int v=int.Parse(Console.ReadLine());string d=Console.ReadLine();/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int v=int.Parse(Console.ReadLine());string d=Console.ReadLine();int min=v>=2?5:3;Console.WriteLine(d.Length>=min?"VALIDA":"INVALIDA");}}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { stdin: "1\nabc\n", expectedStdout: "VALIDA\n", visible: true, description: "v1" }, + { stdin: "2\nabc\n", expectedStdout: "INVALIDA\n", visible: false, description: "v2 refina" }, + { stdin: "2\nabcde\n", expectedStdout: "VALIDA\n", visible: false, description: "v2 válida" }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/03-plan-incrementos.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/03-plan-incrementos.ts new file mode 100644 index 0000000..d7a1de1 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/03-plan-incrementos.ts @@ -0,0 +1,50 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "plan-incrementos", + title: "Planear incrementos por dependencia y valor", + description: "Ordena capacidades para que cada entrega sea coherente.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `No todo orden de features produce entregas utilizables. Si consultar requiere que existan tickets, registrar debe llegar antes. + +Un buen plan equilibra valor, dependencias, riesgo y tamaño; cada incremento tiene criterio de terminado.`, + }, + { + type: "code_completion", + prompt: "Ordena los elementos.", + lines: [ + "Incremento 1: registrar ticket", + "Incremento 2: consultar ticket por folio", + "Incremento 3: asignar técnico", + "Incremento 4: cerrar y conservar historial", + ], + explanation: "Cada paso usa capacidades anteriores y deja producto demostrable.", + }, + { + type: "quiz", + question: "¿Qué feature conviene adelantar si es muy riesgosa técnicamente pero fundamental?", + options: ["Siempre dejarla al final", "Considerar validarla temprano para descubrir riesgo", "Ocultarla", "Eliminar tests"], + correctIndex: 1, + explanation: "Los incrementos también sirven para reducir incertidumbre.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Lee tres valores de valor (V), riesgo (R) y esfuerzo (E). Puntaje de prioridad = V*2 + R - E. Imprime puntaje.", + starterCode: `using System; class Program{static void Main(){int v=int.Parse(Console.ReadLine());int r=int.Parse(Console.ReadLine());int e=int.Parse(Console.ReadLine());/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int v=int.Parse(Console.ReadLine());int r=int.Parse(Console.ReadLine());int e=int.Parse(Console.ReadLine());Console.WriteLine(v*2+r-e);}}`, + difficulty: "easy", + xpReward: 20, + testCases: [ + { stdin: "5\n4\n2\n", expectedStdout: "12\n", visible: true, description: "Alta" }, + { stdin: "1\n1\n3\n", expectedStdout: "0\n", visible: false, description: "Baja" }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/04-cronograma-entregas.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/04-cronograma-entregas.ts new file mode 100644 index 0000000..048b7c0 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/04-cronograma-entregas.ts @@ -0,0 +1,49 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "cronograma-entregas", + title: "Cronograma de entregas graduales", + description: "Transforma el plan incremental en hitos verificables.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `El cronograma incremental no sólo pone fechas: asocia cada entrega con alcance concreto y criterio de aceptación. + +Si una entrega se retrasa, puedes mover capacidad posterior, reducir alcance o resolver dependencia; lo importante es conservar visibilidad.`, + }, + { + type: "matching", + pairs: [ + { left: "Fecha", right: "Cuándo se espera demostrar" }, + { left: "Alcance", right: "Qué capacidades incluye" }, + { left: "Criterio", right: "Cómo se acepta" }, + { left: "Dependencia", right: "Qué debe existir antes" }, + ], + explanation: "Una fecha sin alcance no define entrega.", + }, + { + type: "quiz", + question: "¿Qué hace verificable un hito?", + options: ["Nombre bonito", "Fecha + alcance + criterio de aceptación", "Más personas", "Más commits"], + correctIndex: 1, + explanation: "Permite decidir si llegó o no.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Lee día inicial y duraciones de 3 incrementos. Imprime día de fin acumulado de cada uno, una línea por incremento.", + starterCode: `using System; class Program{static void Main(){int d=int.Parse(Console.ReadLine());int a=int.Parse(Console.ReadLine());int b=int.Parse(Console.ReadLine());int c=int.Parse(Console.ReadLine());/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int d=int.Parse(Console.ReadLine());int a=int.Parse(Console.ReadLine());int b=int.Parse(Console.ReadLine());int c=int.Parse(Console.ReadLine());d+=a;Console.WriteLine(d);d+=b;Console.WriteLine(d);d+=c;Console.WriteLine(d);}}`, + difficulty: "easy", + xpReward: 25, + testCases: [ + { stdin: "1\n2\n3\n4\n", expectedStdout: "3\n6\n10\n", visible: true, description: "Acumulado" }, + { stdin: "10\n1\n1\n1\n", expectedStdout: "11\n12\n13\n", visible: false, description: "Otro inicio" }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/05-cascada-vs-incremental.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/05-cascada-vs-incremental.ts new file mode 100644 index 0000000..015bcb4 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/lessons/05-cascada-vs-incremental.ts @@ -0,0 +1,49 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion05 = defineLesson({ + slug: "cascada-vs-incremental", + title: "Elegir modelo según evidencia", + description: "Compara cascada e incremental sin convertir uno en caricatura.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `Cascada favorece compromiso de fases completas; incremental divide la entrega en capacidades progresivas. Ambos requieren planificación, requisitos, diseño, implementación y pruebas; cambia cómo se organizan y cuándo llega feedback. + +Elegir modelo es justificar supuestos, no seguir moda.`, + }, + { + type: "matching", + pairs: [ + { left: "Requisitos muy estables + auditoría documental", right: "Cascada puede encajar" }, + { left: "Necesidad de validar valor temprano", right: "Incremental gana atractivo" }, + { left: "Alto riesgo desconocido", right: "Incrementos/prototipos reducen incertidumbre" }, + { left: "Contrato rígido de fase", right: "Puede favorecer cascada" }, + ], + explanation: "No hay elección universal.", + }, + { + type: "quiz", + question: "¿Qué argumento es válido para elegir incremental?", + options: [ + "Es moderno", + "Permite entregar/validar capacidades antes y ajustar con feedback", + "No necesita documentación", + "No necesita pruebas", + ], + correctIndex: 1, + explanation: "La ventaja está en feedback y reducción de compromiso.", + }, + { + type: "fill_blank", + prompt: "Completa los espacios.", + template: "Cascada compromete más {{0}} antes de entrega; incremental reparte el compromiso en {{1}}.", + blanks: [ + { answer: "alcance", hint: "Se diseña/implementa una porción amplia antes de feedback de producto." }, + { answer: "entregas", hint: "Cada incremento agrega capacidad integrada." }, + ], + explanation: "Comparación sin caricaturas.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/practice.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/practice.ts new file mode 100644 index 0000000..a92ca4d --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/09-incremental/practice.ts @@ -0,0 +1,62 @@ +import type { PracticeExerciseDefinition } from "../../../../exercises/types"; + +export const practice = [ + { + slug: "mm-inc-disponibilidad", + title: "Feature por versión", + description: "Modela capacidades por incremento.", + prompt: "v1 REGISTRAR; v2 además CONSULTAR; v3 además CERRAR. Lee versión y función.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int v=int.Parse(Console.ReadLine());string f=Console.ReadLine();bool ok=(v>=1&&f=="REGISTRAR")||(v>=2&&f=="CONSULTAR")||(v>=3&&f=="CERRAR");Console.WriteLine(ok?"SI":"NO");}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "1\nREGISTRAR\n", expectedStdout: "SI\n", visible: true, description: "v1" }, + { stdin: "1\nCERRAR\n", expectedStdout: "NO\n", visible: false, description: "No" }, + { stdin: "3\nCERRAR\n", expectedStdout: "SI\n", visible: false, description: "v3" }, + ], + }, + { + slug: "mm-inc-validacion", + title: "Itera una regla", + description: "Cambia criterio entre versiones.", + prompt: "v1 longitud>=3; v2>=5; v3>=8.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int v=int.Parse(Console.ReadLine());string t=Console.ReadLine();int min=v>=3?8:(v>=2?5:3);Console.WriteLine(t.Length>=min?"VALIDA":"INVALIDA");}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "1\nabc\n", expectedStdout: "VALIDA\n", visible: true, description: "v1" }, + { stdin: "2\nabc\n", expectedStdout: "INVALIDA\n", visible: false, description: "v2" }, + { stdin: "3\nabcdefgh\n", expectedStdout: "VALIDA\n", visible: false, description: "v3" }, + ], + }, + { + slug: "mm-inc-prioridad", + title: "Prioriza por valor/riesgo/esfuerzo", + description: "Calcula score explícito.", + prompt: "score=valor*2+riesgo-esfuerzo.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int v=int.Parse(Console.ReadLine());int r=int.Parse(Console.ReadLine());int e=int.Parse(Console.ReadLine());Console.WriteLine(v*2+r-e);}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "4\n3\n2\n", expectedStdout: "9\n", visible: true, description: "Score" }, + { stdin: "1\n0\n2\n", expectedStdout: "0\n", visible: false, description: "Bajo" }, + ], + }, + { + slug: "mm-inc-fechas", + title: "Entregas acumuladas", + description: "Calcula hitos.", + prompt: "Lee inicio y 4 duraciones; imprime fin de cada incremento.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){int d=int.Parse(Console.ReadLine());for(int i=0;i<4;i++){d+=int.Parse(Console.ReadLine());Console.WriteLine(d);}}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "1\n1\n2\n3\n4\n", expectedStdout: "2\n4\n7\n11\n", visible: true, description: "Acumulado" }, + { stdin: "10\n1\n1\n1\n1\n", expectedStdout: "11\n12\n13\n14\n", visible: false, description: "Uniforme" }, + ], + }, +] satisfies PracticeExerciseDefinition[]; diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/index.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/index.ts new file mode 100644 index 0000000..31da018 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/index.ts @@ -0,0 +1,19 @@ +import { defineUnit } from "../../../../authoring"; + +import { leccion01 } from "./lessons/01-problema-requisitos-plan"; +import { leccion02 } from "./lessons/02-arquitectura-dominio"; +import { leccion03 } from "./lessons/03-persistencia-gui-local"; +import { leccion04 } from "./lessons/04-entrega-evolucion"; + +import { practice } from "./practice"; + +export const mmIntegrador = defineUnit({ + slug: "mm-10-integrador", + title: "Proyecto integrador", + description: + "Construye un sistema de gestión de solicitudes de soporte técnico de principio a fin, desde requisitos hasta una evolución incremental controlada.", + icon: "🧩", + published: true, + lessons: [leccion01, leccion02, leccion03, leccion04], + practice, +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/01-problema-requisitos-plan.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/01-problema-requisitos-plan.ts new file mode 100644 index 0000000..0236418 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/01-problema-requisitos-plan.ts @@ -0,0 +1,51 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion01 = defineLesson({ + slug: "problema-requisitos-plan", + title: "Problema, requisitos y plan de fase", + description: "Cierra el alcance de una primera entrega en cascada.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `El integrador empieza con evidencia: problema observable, actores, alcance, requisitos funcionales/no funcionales y criterios de aceptación. + +Después se crea lista de tareas y carta Gantt con dependencias. No se programa antes de poder explicar qué se va a aceptar.`, + }, + { + type: "matching", + pairs: [ + { left: "Problema", right: "Solicitudes se pierden y no hay trazabilidad" }, + { left: "RF-01", right: "Empleado registra ticket" }, + { left: "RNF-01", right: "Sólo técnico asignado puede cerrar" }, + { left: "Criterio", right: "Dado ticket asignado, al cerrar conserva responsable" }, + ], + explanation: "Cada artefacto reduce ambigüedad.", + }, + { + type: "quiz", + question: "¿Qué falta si hay UML pero ningún requisito identificable?", + options: [ + "Más clases", + "Trazabilidad desde problema/requisito al diseño", + "Otro lenguaje", + "Más commits", + ], + correctIndex: 1, + explanation: "El diseño necesita una razón verificable.", + }, + { + type: "fill_blank", + prompt: "Completa los espacios.", + template: `problema → {{0}} → UML/maqueta → tareas/Gantt → implementación`, + blanks: [ + { + answer: "requisitos", + hint: "Definen qué debe cumplir la primera entrega.", + }, + ], + explanation: "El orden prepara una entrega de cascada documentada.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/02-arquitectura-dominio.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/02-arquitectura-dominio.ts new file mode 100644 index 0000000..b1ac6c6 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/02-arquitectura-dominio.ts @@ -0,0 +1,73 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion02 = defineLesson({ + slug: "arquitectura-dominio", + title: "Arquitectura del dominio verificable", + description: "Construye un núcleo de consola que luego pueda alimentar persistencia y GUI locales.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `El proyecto tendrá \`Ticket\`, \`Tecnico\`, un servicio de asignación/cierre y un repositorio abstracto. El dominio debe poder probarse sin Windows Forms ni almacenamiento real. + +La interfaz gráfica local es un adaptador: no debe contener reglas que sólo existan dentro de eventos de botones.`, + }, + { + type: "matching", + pairs: [ + { left: "Ticket", right: "Estado/invariantes" }, + { left: "ServicioTickets", right: "Casos de uso" }, + { left: "IRepositorioTickets", right: "Contrato de persistencia" }, + { left: "Windows Forms", right: "Interfaz local/adaptador" }, + ], + explanation: "Las reglas viven donde pueden probarse.", + }, + { + type: "quiz", + question: "¿Dónde debe vivir la regla “no cerrar sin técnico asignado”?", + options: [ + "En el color del botón", + "En dominio/servicio verificable", + "Sólo en formulario", + "En README", + ], + correctIndex: 1, + explanation: "La UI puede prevenir, pero la regla debe protegerse en el núcleo.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Crea Ticket con Folio, Estado inicialmente ABIERTO, Tecnico opcional string. `Asignar` guarda técnico; `Cerrar` devuelve false si no hay técnico o ya está cerrado, si no cambia a CERRADO. Main lee folio, técnico (puede vacío), llama Asignar si no vacío, luego Cerrar e imprime `SI/NO|estado`.", + starterCode: `using System; // crea Ticket y Program`, + solutionCode: `using System; class Ticket{public string Folio{get;private set;}public string Estado{get;private set;}public string Tecnico{get;private set;}public Ticket(string f){Folio=f;Estado="ABIERTO";Tecnico="";}public void Asignar(string t){Tecnico=t;}public bool Cerrar(){if(Tecnico.Length==0||Estado=="CERRADO")return false;Estado="CERRADO";return true;}}class Program{static void Main(){Ticket t=new Ticket(Console.ReadLine());string tecnico=Console.ReadLine();if(tecnico.Length>0)t.Asignar(tecnico);Console.WriteLine((t.Cerrar()?"SI":"NO")+"|"+t.Estado);}}`, + difficulty: "medium", + xpReward: 35, + structure: { + classes: [ + { + name: "Ticket", + properties: [{ name: "Folio" }, { name: "Estado" }, { name: "Tecnico" }], + methods: [{ name: "Asignar" }, { name: "Cerrar" }], + }, + ], + }, + testCases: [ + { + stdin: "T1\nAna\n", + expectedStdout: "SI|CERRADO\n", + visible: true, + description: "Asignado", + }, + { + stdin: "T2\n\n", + expectedStdout: "NO|ABIERTO\n", + visible: false, + description: "Sin técnico", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/03-persistencia-gui-local.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/03-persistencia-gui-local.ts new file mode 100644 index 0000000..340f423 --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/03-persistencia-gui-local.ts @@ -0,0 +1,52 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion03 = defineLesson({ + slug: "persistencia-gui-local", + title: "Persistencia e interfaz gráfica: laboratorio local", + description: "Separa el núcleo verificable de los adaptadores requeridos por el producto oficial.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `El producto oficial exige persistencia e interfaz gráfica. En cpp-ceti, ambos se trabajan como **laboratorio local**. + +El alumno debe conectar el mismo dominio verificado a una persistencia local y una interfaz Windows Forms, sin mover reglas de negocio a eventos. La evidencia de entrega incluye capturas/ejecución local y explicación de qué adaptador llama a qué servicio.`, + }, + { + type: "matching", + pairs: [ + { left: "Dominio C# de consola", right: "Verificable en plataforma" }, + { left: "Persistencia real", right: "Laboratorio local" }, + { left: "Windows Forms", right: "Laboratorio local" }, + { left: "Reglas de cierre", right: "Nunca sólo en UI" }, + ], + explanation: "La separación permite verificar lógica aun cuando el adaptador no corre en navegador.", + }, + { + type: "quiz", + question: "¿Qué patrón de implementación es correcto?", + options: [ + "Click del botón contiene toda regla", + "Formulario llama a ServicioTickets; servicio protege reglas", + "Base de datos decide todo", + "Duplicar regla en cada pantalla", + ], + correctIndex: 1, + explanation: "La UI coordina interacción; el dominio conserva invariantes.", + }, + { + type: "code_example", + code: `// Esqueleto conceptual; NO copiar reglas al evento. +private void btnCerrar_Click(object sender, EventArgs e) +{ + bool cerrado = servicio.Cerrar(txtFolio.Text); + lblEstado.Text = cerrado ? "CERRADO" : "NO SE PUDO CERRAR"; +}`, + explanation: "El formulario delega; el servicio/dominio sigue siendo la autoridad de la regla.", + runnable: false, + localOnlyNote: + "Laboratorio local: conecta el dominio del proyecto a Windows Forms y persistencia en Visual Studio/entorno local. No se ejecuta en el juez del navegador.", + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/04-entrega-evolucion.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/04-entrega-evolucion.ts new file mode 100644 index 0000000..fb51d3c --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/lessons/04-entrega-evolucion.ts @@ -0,0 +1,70 @@ +import { defineLesson } from "../../../../../authoring"; + +export const leccion04 = defineLesson({ + slug: "entrega-evolucion", + title: "Entrega, evidencia y cambio posterior", + description: "Cierra el producto en cascada y demuestra una evolución incremental segura.", + estimatedMinutes: 15, + xpReward: 45, + steps: [ + { + type: "theory", + markdown: `La entrega incluye sistema, reporte, requisitos, UML, maquetas, tareas/Gantt, historial Git y resultados de pruebas. + +Después se introduce un cambio: **estado PAUSADO con motivo obligatorio**. El alumno debe analizar impacto, actualizar requisito/diseño, añadir pruebas y entregar como incremento posterior sin romper cierre/asignación existentes. Así la materia conecta cascada, mantenimiento, SOLID e incremental.`, + }, + { + type: "matching", + pairs: [ + { left: "Primera entrega", right: "Baseline de cascada" }, + { left: "Cambio PAUSADO", right: "Solicitud de mantenimiento/evolución" }, + { left: "Actualización de requisito/UML", right: "Análisis de impacto" }, + { left: "Nuevo incremento", right: "Capacidad integrada con regresiones" }, + ], + explanation: "El mismo producto recorre el ciclo completo.", + }, + { + type: "quiz", + question: "¿Qué evidencia demuestra mejor que el cambio fue seguro?", + options: [ + "Sólo captura nueva", + "Nuevo criterio + tests del cambio + regresiones anteriores + commits trazables", + "Más líneas", + "Renombrar versión", + ], + correctIndex: 1, + explanation: "Demuestra cambio y preservación del contrato anterior.", + }, + { + type: "code_challenge", + exercise: { + prompt: + "Extiende una máquina de estados: ABIERTO→EN_PROCESO; EN_PROCESO→PAUSADO/CERRADO; PAUSADO→EN_PROCESO. Cualquier otra NO. Lee estado actual y nuevo.", + starterCode: `using System; class Program{static void Main(){string a=Console.ReadLine();string b=Console.ReadLine();/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){string a=Console.ReadLine();string b=Console.ReadLine();bool ok=(a=="ABIERTO"&&b=="EN_PROCESO")||(a=="EN_PROCESO"&&(b=="PAUSADO"||b=="CERRADO"))||(a=="PAUSADO"&&b=="EN_PROCESO");Console.WriteLine(ok?"SI":"NO");}}`, + difficulty: "medium", + xpReward: 30, + testCases: [ + { + stdin: "EN_PROCESO\nPAUSADO\n", + expectedStdout: "SI\n", + visible: true, + description: "Nueva transición", + }, + { + stdin: "PAUSADO\nEN_PROCESO\n", + expectedStdout: "SI\n", + visible: false, + description: "Reanudar", + }, + { + stdin: "ABIERTO\nCERRADO\n", + expectedStdout: "NO\n", + visible: false, + description: "Regresión de regla anterior", + }, + ], + }, + }, + ], +}); diff --git a/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/practice.ts b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/practice.ts new file mode 100644 index 0000000..900e1de --- /dev/null +++ b/prisma/content/courses/modelos-metodos-desarrollo-software/units/10-integrador/practice.ts @@ -0,0 +1,82 @@ +import type { PracticeExerciseDefinition } from "../../../../exercises/types"; + +export const practice = [ + { + slug: "mm-final-ticket", + title: "Núcleo del Ticket", + description: "Construye el dominio mínimo.", + prompt: + "Ticket inicia ABIERTO; asigna técnico y permite cerrar sólo con técnico. Imprime estado.", + starterCode: `using System; // completa Ticket y Program`, + solutionCode: `using System; class Ticket{public string Estado{get;private set;}public string Tecnico{get;private set;}public Ticket(){Estado="ABIERTO";Tecnico="";}public void Asignar(string t){Tecnico=t;}public bool Cerrar(){if(Tecnico.Length==0)return false;Estado="CERRADO";return true;}}class Program{static void Main(){Ticket t=new Ticket();string x=Console.ReadLine();if(x.Length>0)t.Asignar(x);t.Cerrar();Console.WriteLine(t.Estado);}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "Ana\n", expectedStdout: "CERRADO\n", visible: true, description: "Con técnico" }, + { stdin: "\n", expectedStdout: "ABIERTO\n", visible: false, description: "Sin técnico" }, + ], + }, + { + slug: "mm-final-autorizacion", + title: "Autorización del servicio", + description: "Protege un criterio no funcional.", + prompt: + "TECNICO/ADMIN pueden cerrar; EMPLEADO no. Lee rol y técnico asignado SI/NO; imprime PUEDE/NO_PUEDE.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){string r=Console.ReadLine();bool asignado=Console.ReadLine()=="SI";bool rol=r=="TECNICO"||r=="ADMIN";Console.WriteLine(rol&&asignado?"PUEDE":"NO_PUEDE");}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "TECNICO\nSI\n", expectedStdout: "PUEDE\n", visible: true, description: "Válido" }, + { stdin: "EMPLEADO\nSI\n", expectedStdout: "NO_PUEDE\n", visible: false, description: "Rol" }, + { stdin: "ADMIN\nNO\n", expectedStdout: "NO_PUEDE\n", visible: false, description: "Sin asignación" }, + ], + }, + { + slug: "mm-final-prioridad", + title: "Prioridad con criterios", + description: "Implementa regla del proyecto.", + prompt: "Detenido→CRITICA; >=10 afectados→ALTA; 2..9→MEDIA; otro BAJA.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; +class Program +{ + static string Prioridad(int afectados, bool detenido) + { + if (detenido) return "CRITICA"; + if (afectados >= 10) return "ALTA"; + if (afectados >= 2) return "MEDIA"; + return "BAJA"; + } + static void Main() + { + int afectados = int.Parse(Console.ReadLine()); + bool detenido = Console.ReadLine() == "SI"; + Console.WriteLine(Prioridad(afectados, detenido)); + } +}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "20\nSI\n", expectedStdout: "CRITICA\n", visible: true, description: "Detenido" }, + { stdin: "20\nNO\n", expectedStdout: "ALTA\n", visible: false, description: "Alta" }, + { stdin: "3\nNO\n", expectedStdout: "MEDIA\n", visible: false, description: "Media" }, + { stdin: "0\nNO\n", expectedStdout: "BAJA\n", visible: false, description: "Baja" }, + ], + }, + { + slug: "mm-final-pausado", + title: "Incremento PAUSADO", + description: "Implementa la evolución final sin romper transiciones.", + prompt: "ABIERTO→EN_PROCESO; EN_PROCESO→PAUSADO/CERRADO; PAUSADO→EN_PROCESO.", + starterCode: `using System; class Program{static void Main(){/* completa */}}`, + solutionCode: `using System; class Program{static void Main(){string a=Console.ReadLine();string b=Console.ReadLine();bool ok=(a=="ABIERTO"&&b=="EN_PROCESO")||(a=="EN_PROCESO"&&(b=="PAUSADO"||b=="CERRADO"))||(a=="PAUSADO"&&b=="EN_PROCESO");Console.WriteLine(ok?"SI":"NO");}}`, + difficulty: "medium", + xpReward: 28, + testCases: [ + { stdin: "ABIERTO\nEN_PROCESO\n", expectedStdout: "SI\n", visible: true, description: "Regresión" }, + { stdin: "EN_PROCESO\nPAUSADO\n", expectedStdout: "SI\n", visible: false, description: "Nuevo" }, + { stdin: "PAUSADO\nCERRADO\n", expectedStdout: "NO\n", visible: false, description: "No definido" }, + ], + }, +] satisfies PracticeExerciseDefinition[]; diff --git a/tests/content/authoring.test.ts b/tests/content/authoring.test.ts index be3e3fc..c0ae3b9 100644 --- a/tests/content/authoring.test.ts +++ b/tests/content/authoring.test.ts @@ -870,9 +870,9 @@ describe("validate: paths precisos", () => { // ======================================================================= describe("los cursos actuales pasan por el registry nuevo", () => { - it("cpp-desde-cero y csharp-poo-1 están en allCourses", () => { + it("cpp-desde-cero, csharp-poo-1 y modelos-metodos-desarrollo-software están en allCourses", () => { expect(allCourses.map((c) => c.slug).sort()).toEqual( - ["cpp-desde-cero", "csharp-poo-1"].sort(), + ["cpp-desde-cero", "csharp-poo-1", "modelos-metodos-desarrollo-software"].sort(), ); }); diff --git a/tests/content/course-contract.test.ts b/tests/content/course-contract.test.ts index cb4ff83..725d448 100644 --- a/tests/content/course-contract.test.ts +++ b/tests/content/course-contract.test.ts @@ -494,3 +494,141 @@ describe("política de ejecución de S4 (red, sockets, concurrencia no determini } }); }); + +describe("curso S5: Modelos y métodos de desarrollo de software", () => { + it("el curso está registrado EXACTAMENTE una vez", () => { + const matches = allCourses.filter( + (c) => c.slug === "modelos-metodos-desarrollo-software", + ); + expect(matches).toHaveLength(1); + }); + + const mm = courseBySlug.get("modelos-metodos-desarrollo-software")!; + + it("declara la metadata exacta del contrato", () => { + expect(mm).toBeDefined(); + expect(mm.title).toBe("Modelos y métodos de desarrollo de software"); + expect(mm.description).toBe( + "Convierte problemas en proyectos de software trazables: requisitos, diseño, control de versiones, pruebas, mantenimiento, SOLID y entregas incrementales.", + ); + expect(mm.subjectName).toBe("Modelos y métodos de desarrollo de software"); + expect(mm.academicContext).toBe("CETI · Tecnólogo en Desarrollo de Software"); + expect(mm.language).toBe("csharp"); + expect(mm.executionProfile).toBe("csharp-mono-6.12"); + }); + + it("declara EXACTAMENTE una CurriculumSection de semestre 5", () => { + expect(mm.curriculum).toBeDefined(); + expect(mm.curriculum).toHaveLength(1); + const s5 = mm.curriculum![0]; + expect(s5.key).toBe("s5-modelos-metodos-desarrollo-software-1"); + expect(s5.semester).toBe(5); + expect(s5.order).toBe(1); + expect(s5.subjectName).toBe("Modelos y Métodos de Desarrollo de Software I"); + }); + + it("declara las 10 Units EXACTAS, en el orden contractual", () => { + expect(mm.units.map((u) => u.slug)).toEqual([ + "mm-01-proyecto-cascada", + "mm-02-requerimientos", + "mm-03-uml-diseno", + "mm-04-git-versiones", + "mm-05-planificacion-implementacion", + "mm-06-pruebas-calidad", + "mm-07-mantenimiento", + "mm-08-solid", + "mm-09-incremental", + "mm-10-integrador", + ]); + // La sección semestral referencia exactamente esas mismas 10 units, en + // el mismo orden, y ese aplanado es el orden GLOBAL de navegación. + expect(mm.curriculum![0].unitSlugs).toEqual(mm.units.map((u) => u.slug)); + }); + + it("las 10 Units están published=true (sin release gate para S5)", () => { + for (const unit of mm.units) { + expect(unit.published, unit.slug).toBe(true); + } + }); + + it("totales EXACTOS: 44 lessons, 176 steps, 32 practices", () => { + const lessons = mm.units.flatMap((u) => u.lessons); + const steps = lessons.flatMap((l) => l.steps); + const totalPractice = allPracticeSets + .filter((s) => s.courseSlug === "modelos-metodos-desarrollo-software") + .reduce((n, s) => n + s.exercises.length, 0); + + expect(mm.units.length).toBe(10); + expect(lessons.length).toBe(44); + expect(steps.length).toBe(176); + expect(totalPractice).toBe(32); + }); + + it("cada lección tiene EXACTAMENTE 4 steps", () => { + for (const unit of mm.units) { + for (const lesson of unit.lessons) { + expect(lesson.steps.length, `${unit.slug}/${lesson.slug}`).toBe(4); + } + } + }); + + it("U1 (cascada) y U4 (Git) no fuerzan retos C# artificiales: sin práctica independiente", () => { + const practiceUnitSlugs = new Set( + allPracticeSets + .filter((s) => s.courseSlug === "modelos-metodos-desarrollo-software") + .map((s) => s.unitSlug), + ); + expect(practiceUnitSlugs.has("mm-01-proyecto-cascada")).toBe(false); + expect(practiceUnitSlugs.has("mm-04-git-versiones")).toBe(false); + }); + + it("las otras 8 unidades sí traen práctica independiente", () => { + const practiceUnitSlugs = new Set( + allPracticeSets + .filter((s) => s.courseSlug === "modelos-metodos-desarrollo-software") + .map((s) => s.unitSlug), + ); + const expected = [ + "mm-02-requerimientos", + "mm-03-uml-diseno", + "mm-05-planificacion-implementacion", + "mm-06-pruebas-calidad", + "mm-07-mantenimiento", + "mm-08-solid", + "mm-09-incremental", + "mm-10-integrador", + ]; + for (const slug of expected) { + expect(practiceUnitSlugs.has(slug), slug).toBe(true); + } + }); + + it("el laboratorio local de Windows Forms del integrador no es ejecutable y no despublica la unidad", () => { + const integrador = mm.units.find((u) => u.slug === "mm-10-integrador")!; + expect(integrador.published).toBe(true); + const nonRunnable = integrador.lessons + .flatMap((l) => l.steps) + .filter( + (s): s is CodeExampleStep => s.type === "code_example" && s.runnable !== true, + ); + expect(nonRunnable.length).toBeGreaterThan(0); + for (const step of nonRunnable) { + expect(step.localOnlyNote?.trim()).toBeTruthy(); + } + }); + + it("el curso C++ y el curso C# (S3+S4) siguen intactos tras agregar S5", () => { + const cpp = courseBySlug.get("cpp-desde-cero")!; + const csharp = courseBySlug.get("csharp-poo-1")!; + + expect(cpp.units.length).toBe(10); + expect(cpp.units.flatMap((u) => u.lessons).length).toBe(67); + + expect(csharp.units.length).toBe(16); + expect(csharp.units.flatMap((u) => u.lessons).length).toBe(73); + expect(csharp.curriculum!.map((s) => s.key)).toEqual([ + "s3-programacion-orientada-objetos-1", + "s4-programacion-orientada-objetos-2", + ]); + }); +}); diff --git a/tests/content/structure-contracts.test.ts b/tests/content/structure-contracts.test.ts index 1c5c9cc..c219263 100644 --- a/tests/content/structure-contracts.test.ts +++ b/tests/content/structure-contracts.test.ts @@ -101,6 +101,67 @@ describe("contratos estructurales publicados", () => { // alumno EXTRAIGA el diagrama UML correcto como texto, no la // estructura de clases (que no escribe). "csharp-poo-1/csharp-poo-03-uml/csharp-poo-codigo-a-uml-pelicula", + + // S5 (modelos-metodos-desarrollo-software): el paquete curricular + // declara `structure` SÓLO donde el objetivo del reto es diseño/UML + // (unidades 3, 5, 8 y 10 — relaciones, propiedades, herencia, + // abstracción). El resto del curso evalúa reglas de negocio, lógica + // de estados y cálculos (requisitos, pruebas, mantenimiento, + // incrementos, ISP) donde el objetivo declarado es comportamiento, + // no estructura de clases — igual que el resto del contenido C# sin + // contrato. Ver COURSE_CONTRACT.md / SONNET_PROMPT.md del paquete S5. + "modelos-metodos-desarrollo-software/mm-02-requerimientos/problema-actores-alcance", + "modelos-metodos-desarrollo-software/mm-02-requerimientos/requisitos-funcionales", + "modelos-metodos-desarrollo-software/mm-02-requerimientos/requisitos-no-funcionales", + "modelos-metodos-desarrollo-software/mm-02-requerimientos/criterios-aceptacion", + "modelos-metodos-desarrollo-software/mm-02-requerimientos/cambio-de-alcance", + "modelos-metodos-desarrollo-software/mm-05-planificacion-implementacion/descomponer-trabajo", + "modelos-metodos-desarrollo-software/mm-05-planificacion-implementacion/dependencias-gantt", + "modelos-metodos-desarrollo-software/mm-05-planificacion-implementacion/seguimiento-desviaciones", + "modelos-metodos-desarrollo-software/mm-06-pruebas-calidad/verificacion-validacion", + "modelos-metodos-desarrollo-software/mm-06-pruebas-calidad/tipos-prueba", + "modelos-metodos-desarrollo-software/mm-06-pruebas-calidad/casos-fronteras", + "modelos-metodos-desarrollo-software/mm-06-pruebas-calidad/regresion-resultados", + "modelos-metodos-desarrollo-software/mm-07-mantenimiento/tipos-mantenimiento", + "modelos-metodos-desarrollo-software/mm-07-mantenimiento/reproducir-antes-corregir", + "modelos-metodos-desarrollo-software/mm-07-mantenimiento/impacto-regresion", + "modelos-metodos-desarrollo-software/mm-07-mantenimiento/plan-mejora", + "modelos-metodos-desarrollo-software/mm-08-solid/isp", + "modelos-metodos-desarrollo-software/mm-09-incremental/modelo-incremental", + "modelos-metodos-desarrollo-software/mm-09-incremental/iteracion-feedback", + "modelos-metodos-desarrollo-software/mm-09-incremental/plan-incrementos", + "modelos-metodos-desarrollo-software/mm-09-incremental/cronograma-entregas", + "modelos-metodos-desarrollo-software/mm-10-integrador/entrega-evolucion", + "modelos-metodos-desarrollo-software/mm-02-requerimientos/mm-requisitos-prioridad", + "modelos-metodos-desarrollo-software/mm-02-requerimientos/mm-requisitos-sla", + "modelos-metodos-desarrollo-software/mm-02-requerimientos/mm-requisitos-transicion", + "modelos-metodos-desarrollo-software/mm-02-requerimientos/mm-requisitos-cambio", + "modelos-metodos-desarrollo-software/mm-03-uml-diseno/mm-diseno-separar-reporte", + "modelos-metodos-desarrollo-software/mm-03-uml-diseno/mm-diseno-historial", + "modelos-metodos-desarrollo-software/mm-05-planificacion-implementacion/mm-plan-ruta", + "modelos-metodos-desarrollo-software/mm-05-planificacion-implementacion/mm-plan-porcentaje", + "modelos-metodos-desarrollo-software/mm-05-planificacion-implementacion/mm-plan-ticket", + "modelos-metodos-desarrollo-software/mm-05-planificacion-implementacion/mm-plan-costo-cambio", + "modelos-metodos-desarrollo-software/mm-06-pruebas-calidad/mm-test-rangos", + "modelos-metodos-desarrollo-software/mm-06-pruebas-calidad/mm-test-intentos", + "modelos-metodos-desarrollo-software/mm-06-pruebas-calidad/mm-test-regresion", + "modelos-metodos-desarrollo-software/mm-06-pruebas-calidad/mm-test-matriz", + "modelos-metodos-desarrollo-software/mm-07-mantenimiento/mm-mant-clasificar", + "modelos-metodos-desarrollo-software/mm-07-mantenimiento/mm-mant-cero", + "modelos-metodos-desarrollo-software/mm-07-mantenimiento/mm-mant-regresion", + "modelos-metodos-desarrollo-software/mm-07-mantenimiento/mm-mant-prioridad", + "modelos-metodos-desarrollo-software/mm-08-solid/mm-solid-srp", + "modelos-metodos-desarrollo-software/mm-08-solid/mm-solid-ocp", + "modelos-metodos-desarrollo-software/mm-08-solid/mm-solid-isp", + "modelos-metodos-desarrollo-software/mm-08-solid/mm-solid-dip", + "modelos-metodos-desarrollo-software/mm-09-incremental/mm-inc-disponibilidad", + "modelos-metodos-desarrollo-software/mm-09-incremental/mm-inc-validacion", + "modelos-metodos-desarrollo-software/mm-09-incremental/mm-inc-prioridad", + "modelos-metodos-desarrollo-software/mm-09-incremental/mm-inc-fechas", + "modelos-metodos-desarrollo-software/mm-10-integrador/mm-final-ticket", + "modelos-metodos-desarrollo-software/mm-10-integrador/mm-final-autorizacion", + "modelos-metodos-desarrollo-software/mm-10-integrador/mm-final-prioridad", + "modelos-metodos-desarrollo-software/mm-10-integrador/mm-final-pausado", ]); const sinContrato = challenges .filter((c) => c.language === "csharp" && c.structure === undefined)