Sesión 12

Proyecto Integrador — Entrega 2

Unidad 2 · Escalado iterativo: menú continuo, registros múltiples y reportes estadísticos

Navegación: ← → · Home / End · F pantalla completa · Táctil: desliza

Propósito de la sesión

De una solución secuencial a un sistema iterativo completo

  • Comprender qué significa escalar un programa de Unidad 1 (lineal, de una sola ejecución) a un sistema que gestiona múltiples registros en memoria durante una misma sesión.
  • Diseñar y justificar la arquitectura de control del sistema: menú do-while como núcleo del ciclo de vida del programa.
  • Integrar List<T> como estructura de almacenamiento principal y producir reportes estadísticos mediante ciclos explícitos.
  • Aplicar la rúbrica de evaluación de la Entrega 2 como guía de diseño desde el inicio del desarrollo.
Análisis: Entrega 1 vs Entrega 2
Dimensión Entrega 1 (Unidad 1) Entrega 2 (Unidad 2)
Ciclo de vida Ejecución única, flujo secuencial, termina tras mostrar la salida. Ciclo continuo: el sistema vive mientras el usuario no decida salir.
Datos procesados Variables atómicas (un cliente, un pedido, una operación). Colecciones de N registros almacenados en List<T>.
Salida Resultado de una sola operación. Reportes estadísticos sobre el conjunto completo de registros.
Robustez Opcional / básica. Validación estricta obligatoria con TryParse y guardas defensivas.
Rúbrica de Entrega 2 como mapa de diseño
Criterio Indicador evaluado Error que invalida
Ciclo de control Menú implementado con do-while funcional. Menú implementado con while(true) sin bandera ni condición semántica.
Almacenamiento Registros múltiples en arreglo o List<T>. Variables atómicas reutilizadas: el registro anterior se sobreescribe sin guardarse.
Reportes Métricas calculadas con ciclos explícitos sobre la colección. Cálculos acumulados dentro del ciclo de carga (confunde las dos fases).
Robustez Cero colapsos ante entradas inválidas. Uso de Parse sin manejo de excepción.
Legibilidad Nombres semánticos, sin números mágicos, commits descriptivos. Variables x, temp1, aux2.
Arquitectura del sistema: visión estructural

Estructura en tres capas lógicas

  • Capa de control: El ciclo do-while + switch que orquesta todo el sistema. Responsabilidad única: recibir la intención del usuario y delegar.
  • Capa de datos: La List<T> declarada antes del ciclo principal. Responsabilidad única: persistir los registros en memoria volátil mientras el sistema esté activo.
  • Capa de lógica de negocio: Los bloques de código que registran, validan y calculan. En Unidad 3 estos bloques se convertirán en funciones.

Flujo de ejecución

  1. Sistema inicia → List vacía instanciada.
  2. Menú presentado → usuario elige opción.
  3. Opción ejecuta su bloque lógico.
  4. Sistema regresa al menú (vuelve al paso 2).
  5. Opción "Salir" activa bandera → ciclo termina.
Esqueleto base del sistema (punto de partida)
using System; using System.Collections.Generic; class Program { static void Main(string[] args) { // Capa de datos: alcance global respecto al ciclo List<double> registros = new List<double>(); bool sistemaActivo = true; do { Console.Clear(); Console.WriteLine("=== MI SISTEMA - ENTREGA 2 ==="); Console.WriteLine("1. Registrar nuevo dato"); Console.WriteLine("2. Ver reporte estadístico"); Console.WriteLine("0. Salir"); Console.Write("\nOpción: "); string opcion = Console.ReadLine(); switch (opcion) { case "1": // Bloque de captura y validación break; case "2": // Bloque de reportes estadísticos break; case "0": sistemaActivo = false; break; default: Console.WriteLine("Opción no válida. Presione Enter."); Console.ReadLine(); break; } } while (sistemaActivo); } }
Bloque de Registro: Validación y Almacenamiento

Implementación del caso "1": registrar con defensa total

case "1": Console.Write("Ingrese el valor a registrar: "); string entrada = Console.ReadLine(); double valorIngresado; if (double.TryParse(entrada, out valorIngresado)) { // Segunda guardia: validación de dominio del negocio if (valorIngresado < 0) { Console.WriteLine("El valor no puede ser negativo."); } else { registros.Add(valorIngresado); Console.WriteLine($"Registro #{registros.Count} guardado: {valorIngresado:F2}"); } } else { Console.WriteLine("Formato inválido. Solo se aceptan valores numéricos."); } Console.WriteLine("Presione Enter para continuar."); Console.ReadLine(); break;

TryParse opera como primera guardia (¿es parseable?). La condición de dominio opera como segunda guardia (¿cumple la regla de negocio?). Ambas capas son independientes y obligatorias.

Bloque de Reporte: Procesamiento Estadístico

Implementación del caso "2": métricas sobre la colección

case "2": if (registros.Count == 0) { Console.WriteLine("No hay registros para procesar."); } else { double suma = 0.0; // Acumulador — se reinicia en cada reporte double maximo = double.MinValue; double minimo = double.MaxValue; int superanUmbral = 0; const double UMBRAL = 50.0; // Sin números mágicos for (int i = 0; i < registros.Count; i++) { double val = registros[i]; suma += val; if (val > maximo) maximo = val; if (val < minimo) minimo = val; if (val > UMBRAL) superanUmbral++; } double promedio = suma / registros.Count; Console.WriteLine($"\nTotal de registros : {registros.Count}"); Console.WriteLine($"Promedio : {promedio:F2}"); Console.WriteLine($"Máximo : {maximo:F2}"); Console.WriteLine($"Mínimo : {minimo:F2}"); Console.WriteLine($"Superan {UMBRAL} : {superanUmbral}"); } Console.WriteLine("Presione Enter para continuar."); Console.ReadLine(); break;
Principio fundamental: separación de fases

Fase de carga (caso "1")

Responsabilidad única: capturar un dato, validarlo y añadirlo a la colección. No debe calcular métricas. No debe iterar sobre datos históricos. Solo agrega y confirma.

Anti-patrón: Acumular la suma dentro del bloque de registro para "ahorrar" el ciclo del reporte. Esto obliga a reinicializar acumuladores manualmente y rompe la lógica si el usuario elimina o edita registros.

Fase de procesamiento (caso "2")

Responsabilidad única: recorrer la colección y producir métricas desde cero. Sus acumuladores internos son variables locales temporales que nacen y mueren en cada invocación del reporte.

Este patrón es directamente transferible a funciones en la Unidad 3: cada bloque de switch se convierte en un método con una firma clara.

Decisión de Diseño: ¿Qué almacena la colección?

Colección de primitivos (List<double>)

Adecuada cuando cada registro es un valor numérico simple (medición, precio, calificación). El sistema produce estadísticas sobre ese único campo.

  • Simple de recorrer.
  • Limitada para datos con múltiples atributos (ej. nombre + valor).

Colección de strings (List<string>)

Permite guardar texto libre (nombres, categorías) para luego filtrar o mostrar en reportes. Puede combinarse con una List<double> paralela indexada.

  • Requiere mantener índices sincronizados entre ambas listas.
  • Antecede conceptualmente al uso de clases en cursos futuros.
Extensión: Múltiples métricas en un solo recorrido

Eficiencia algorítmica: un solo ciclo O(N) para todas las métricas

Un error común es implementar un ciclo separado por cada métrica (uno para suma, uno para máximo, uno para contar condición). Esto genera complejidad O(k·N) siendo k el número de métricas.

La solución óptima combina todas las métricas en un único recorrido: cada iteración actualiza simultáneamente acumulador, extremos y contadores condicionales.

// Un solo recorrido — O(N) — múltiples métricas for (int i = 0; i < registros.Count; i++) { double v = registros[i]; suma += v; // Acumulador if (v > maximo) maximo = v; // Seguimiento del máximo if (v < minimo) minimo = v; // Seguimiento del mínimo if (v >= UMBRAL_ALTO) contadorAlto++; // Contador condicional 1 if (v < UMBRAL_BAJO) contadorBajo++; // Contador condicional 2 }
Control de versiones: Estrategia de Commits

Principio de commits atómicos

Cada commit debe representar un estado funcional y coherente del sistema, no un archivo a medias. Una buena estrategia de commits para la Entrega 2:

  1. feat: agregar esqueleto do-while con menú base
  2. feat: implementar registro con validación TryParse
  3. feat: agregar bloque de reporte estadístico
  4. fix: corregir reinicialización de acumuladores en reporte
  5. docs: actualizar README con instrucciones de ejecución

README mínimo exigido

  • Descripción del problema que resuelve el sistema.
  • Instrucciones para compilar y ejecutar (dotnet run).
  • Casos de prueba: entrada válida, entrada inválida, colección vacía.
  • Restricciones de dominio conocidas (ej. valores positivos, rango máximo).
Anti-patrones que invalidan la entrega

En el ciclo de control

  • Usar while(true) sin bandera semántica: el código no expresa intención; es una caja negra para quien lo lee.
  • Anidar un segundo menú dentro de un caso sin un sub-ciclo propio: mezcla dos niveles de navegación en el mismo bloque.
  • No limpiar la consola (Console.Clear()) entre iteraciones: la interfaz acumula basura visual que confunde al usuario.

En el manejo de datos

  • Declarar la List<T> dentro del do-while: todos los registros se pierden en cada vuelta del menú.
  • Confiar en Parse directamente: un solo carácter no numérico colapsa el sistema sin posibilidad de recuperación.
  • Mostrar el reporte cuando Count == 0 sin validar: división por cero al calcular el promedio.
El código de hoy es el material de Unidad 3

Lo que ocurrirá en Unidad 3

Cada bloque del switch que hoy escriben como código incrustado se convertirá en un método estático con firma explícita:

// Hoy (monolítico): case "2": // 30 líneas de reporte aquí break; // Unidad 3 (modular): case "2": MostrarReporte(registros); break;

Deuda técnica intencional

Si cada bloque de case tiene menos de 20 líneas y una responsabilidad clara, la refactorización en Unidad 3 será trivial.

Si un bloque supera las 40 líneas con lógica entremezclada, la modularización se vuelve una cirugía costosa.

Escribir código modularizable no requiere funciones todavía; requiere disciplina de responsabilidad única desde el principio.

Trabajo en clase: construcción guiada

Desarrollo de la Entrega 2 desde el esqueleto

Punto de partida obligatorio: El esqueleto base (slide 6) debe estar en su repositorio con un primer commit antes de agregar cualquier lógica.

  • Definir la entidad del proyecto propio: ¿Qué registra su sistema? (temperaturas, ventas, velocidades, notas, inventario, otros).
  • Implementar el bloque de registro con doble guardia (formato + dominio) y confirmar con prueba manual de entrada inválida.
  • Implementar el bloque de reporte con al menos tres métricas distintas en un único ciclo for.
  • Verificar el comportamiento cuando la colección está vacía: el sistema debe responder con mensaje, no con excepción.
  • Alcance evaluado en esta entrega: solo do-while, switch, List<T>, ciclos y TryParse (no se exige modularizar en funciones todavía).
  • Realizar mínimo tres commits semánticamente descriptivos antes de finalizar la sesión.
Cierre: lo que debe quedar sólido

Criterios de éxito de la Entrega 2

  • El sistema sobrevive cualquier entrada del usuario sin colapsar: es la diferencia entre un prototipo y un producto mínimo viable.
  • La colección persiste correctamente entre todas las invocaciones del menú: el alcance de la lista es el contrato de memoria del sistema.
  • Los acumuladores del reporte se reinician en cada invocación: mezclar estado de reporte con estado de datos es el error de arquitectura más costoso en sistemas iterativos.
  • El código está preparado para ser refactorizado en Unidad 3 sin reescribirse: la responsabilidad única por bloque es hoy una decisión de diseño, no una exigencia de sintaxis.