Sesión 13

Taller: Depuración, índices seguros y validación robusta

Unidad 2 · Proyecto Entrega 2 — Consolidación y corrección de errores críticos

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

Propósito de la sesión

Convertir un borrador funcional en código de calidad ingenieril

  • Identificar y corregir los tres tipos de error más comunes en sistemas iterativos: ciclos infinitos involuntarios, desbordamientos de índice y colapsos por entradas inválidas.
  • Aplicar el depurador de VS Code (breakpoints, step-over, panel de variables) como herramienta sistemática de auditoría, no como recurso de último recurso.
  • Construir un conjunto de casos de prueba que cubran las fronteras del dominio del sistema propio antes de la entrega formal.
  • Cerrar el Proyecto Entrega 2 con el repositorio en estado entregable: README, commits descriptivos y código sin errores de ejecución.
Taxonomía de errores en sistemas iterativos
Categoría Descripción Síntoma visible Origen más frecuente
Error de sintaxis El compilador rechaza el código antes de ejecutar. Línea subrayada en rojo en VS Code. Punto y coma faltante, llaves desbalanceadas, tipo incorrecto.
Error de ejecución El programa compila pero colapsa durante la ejecución. IndexOutOfRangeException, FormatException, DivideByZeroException. Índice fuera de rango, Parse sin validación, división con denominador no controlado.
Error lógico El programa corre sin colapsar pero produce resultados incorrectos. Salida numérica equivocada, ciclo que nunca entra o nunca sale. Condición de ciclo invertida, acumulador no reinicializado, contador mal ubicado.
Error crítico 1: Ciclos infinitos involuntarios

Patrón defectuoso

// La bandera nunca cambia a false bool activo = true; do { Console.Write("Opción: "); string op = Console.ReadLine(); if (op == "0") { bool activo = false; // Nueva variable local! // La del ámbito exterior no cambia } } while (activo);

La declaración bool activo = false dentro del bloque crea una nueva variable local con el mismo nombre. La variable del do-while nunca se modifica: ciclo infinito.

Corrección

bool activo = true; do { Console.Write("Opción: "); string op = Console.ReadLine(); if (op == "0") { activo = false; // Asignación a la variable exterior } } while (activo);

Nunca redeclares (bool, int, etc.) una variable de control dentro del bloque que depende de ella. Solo asigna.

Error crítico 2: Off-by-one en arreglos y listas

¿Qué es off-by-one?

Un error de tipo "uno de más" o "uno de menos" en los límites de un ciclo que recorre una estructura indexada. Es el error más frecuente en código iterativo de estudiantes e ingenieros por igual.

// Error: condición usa <= en lugar de < // registros.Count = 5 → índices válidos: 0..4 for (int i = 0; i <= registros.Count; i++) { // En i=5: registros[5] no existe // → IndexOutOfRangeException double v = registros[i]; }

Regla mnemónica universal

  • Los índices válidos de una colección de N elementos son siempre 0 hasta N-1.
  • La condición del for debe ser i < coleccion.Count (o .Length), nunca i <= coleccion.Count.
// Corrección for (int i = 0; i < registros.Count; i++) { double v = registros[i]; // Siempre seguro }
Error crítico 3: FormatException por Parse sin guardia

Por qué Parse es frágil

double.Parse(string) lanza FormatException ante cualquier entrada no convertible. En un sistema interactivo real, el usuario siempre cometerá al menos un error de tipado.

// Código que colapsa ante "abc" o una coma Console.Write("Ingrese precio: "); double precio = double.Parse(Console.ReadLine()); // Si el usuario escribe "abc" → excepción no manejada // El proceso termina abruptamente

Solución: TryParse con bucle de reintento

double precio; bool entradaValida = false; do { Console.Write("Ingrese precio: "); string entrada = Console.ReadLine(); if (double.TryParse(entrada, out precio) && precio >= 0) { entradaValida = true; } else { Console.WriteLine("Valor inválido. Intente nuevamente."); } } while (!entradaValida);

El sistema nunca sale del sub-ciclo hasta recibir datos correctos. El usuario no puede romper el flujo con texto arbitrario.

Error crítico 4: División por cero en reportes

Cuándo ocurre

El cálculo del promedio (suma / Count) colapsa si el usuario solicita el reporte antes de registrar cualquier dato. La colección vacía hace que Count == 0, y la división no está definida.

// Versión peligrosa double promedio = suma / registros.Count; // Si Count == 0 → DivideByZeroException (enteros) // o → NaN / Infinity (doubles)

Guardia defensiva obligatoria

case "2": if (registros.Count == 0) { Console.WriteLine("No hay datos registrados."); Console.WriteLine("Registre al menos un valor primero."); } else { // Solo se ejecuta si hay datos double suma = 0.0; for (int i = 0; i < registros.Count; i++) suma += registros[i]; double promedio = suma / registros.Count; Console.WriteLine($"Promedio: {promedio:F2}"); } Console.ReadLine(); break;
El depurador de VS Code como herramienta analítica

Flujo de depuración sistemático

  1. Colocar un breakpoint (F9) en la primera línea del ciclo sospechoso.
  2. Iniciar en modo depuración (F5). El programa se detiene en el breakpoint.
  3. Inspeccionar el panel Variables: verificar el valor actual del índice, el estado de la colección, el valor del acumulador.
  4. Avanzar con F10 (Step Over) línea por línea observando cómo cambia cada variable.
  5. Si el ciclo es largo, usar Condición en el breakpoint: detener solo cuando i == registros.Count - 1.

Qué buscar en cada iteración

  • ¿El acumulador crece en el valor esperado?
  • ¿El índice avanza correctamente sin saltar ni quedarse?
  • ¿La bandera de control cambia en el momento correcto?
  • ¿La condición del ciclo evalúa exactamente lo que el algoritmo requiere?

El depurador convierte el razonamiento abstracto sobre el algoritmo en observación directa del estado de la máquina.

Diseño de casos de prueba para sistemas iterativos

Cobertura mínima obligatoria antes de entregar

Caso Entrada Resultado esperado Error que detecta
Colección vacía Solicitar reporte sin registrar nada. Mensaje de advertencia. No hay excepción. División por cero, acceso a índice -1.
Un solo registro Registrar un valor, luego pedir reporte. Promedio == ese valor. Máx == Mín == ese valor. Error de índice en ciclos con lógica de comparación.
Entrada no numérica Ingresar "abc" cuando se pide un número. Mensaje de error. Sistema sigue activo. FormatException por Parse sin guardia.
Valor en frontera Ingresar exactamente el valor límite del dominio (ej. 0.0 o 5.0). El sistema acepta el valor. No lo rechaza ni genera error silencioso. Condición de validación con operador incorrecto (< vs <=).
Salida limpia Seleccionar "Salir" desde el menú. El programa termina sin colgar ni lanzar excepción. Ciclo infinito por bandera no actualizada.
Ejercicio de diagnóstico: ¿cuántos errores hay?

Analiza este fragmento antes de ejecutarlo

List<int> valores = new List<int>(); bool continuar = true; do { Console.Write("Número (0 para salir): "); int n = int.Parse(Console.ReadLine()); // (A) if (n == 0) { bool continuar = false; // (B) } else { valores.Add(n); } } while (continuar); int suma = 0; for (int i = 0; i <= valores.Count; i++) // (C) { suma += valores[i]; } Console.WriteLine("Promedio: " + suma / valores.Count); // (D)

(A) FormatException si el usuario escribe texto. (B) Redeclaración local — ciclo infinito. (C) Off-by-one — IndexOutOfRangeException en la última iteración. (D) DivideByZeroException si la colección está vacía; además, división entera si suma y Count son int.

Versión corregida del fragmento anterior
List<int> valores = new List<int>(); bool continuar = true; do { Console.Write("Número (0 para salir): "); string entrada = Console.ReadLine(); int n; if (!int.TryParse(entrada, out n)) // (A) corregido { Console.WriteLine("Entrada inválida."); continue; } if (n == 0) { continuar = false; // (B) corregido: solo asignación } else { valores.Add(n); } } while (continuar); if (valores.Count == 0) // (D) guardia previa { Console.WriteLine("Sin datos registrados."); } else { double suma = 0; for (int i = 0; i < valores.Count; i++) // (C) corregido: < en lugar de <= { suma += valores[i]; } Console.WriteLine($"Promedio: {suma / valores.Count:F2}"); }
Validación en capas: modelo profesional

Capa 1 — Formato

¿Es el texto convertible al tipo de dato esperado?

if (!double.TryParse(entrada, out valor)) { // Rechazar: texto no numérico }

Esta capa la provee el runtime de .NET a través de TryParse. No requiere lógica manual.

Capa 2 — Dominio

¿El valor numérico cumple las restricciones del negocio?

if (valor < 0 || valor > 100) { // Rechazar: fuera del rango válido del sistema }

Esta capa la define el programador según los requisitos. Cambia entre sistemas; la Capa 1 es universal.

Checklist de entrega: verificación antes del commit final

Antes de hacer el commit de entrega, verifica manualmente cada ítem:

  • El menú aparece al inicio y vuelve a mostrarse después de cada operación sin que queden residuos de texto anterior (Console.Clear()).
  • Ingresar texto no numérico en cualquier campo numérico no colapsa el sistema.
  • El reporte solicitado con colección vacía muestra un mensaje descriptivo, no una excepción.
  • Registrar un solo elemento y pedir el reporte produce métricas coherentes (mínimo = máximo = ese valor; promedio = ese valor).
  • La opción "Salir" termina el programa de forma limpia y controlada.
  • El repositorio tiene un README que describe el problema, las instrucciones de ejecución y al menos tres casos de prueba documentados.
  • Los commits tienen mensajes descriptivos en formato tipo: descripción (feat, fix, docs).
Dinámica de taller: revisión cruzada de código

Metodología: Code Review en pares

  1. Cada estudiante comparte su código actual con un compañero (GitHub o compartir pantalla).
  2. El revisor ejecuta el sistema e intenta romperlo con entradas inválidas: texto, negativos, colección vacía, salida inmediata.
  3. Documenta los fallos encontrados en comentarios directos en el código o en un issue de GitHub.
  4. Autor corrige y hace commit con mensaje fix: descripción del error corregido.

Criterio del revisor

  • ¿Los nombres de variables son semánticos y claros?
  • ¿Hay algún bloque que supere 20 líneas mezclando captura, validación y cálculo?
  • ¿Existe al menos una situación donde el sistema produzca un resultado incorrecto sin lanzar excepción (error lógico silencioso)?
Entrega formal: fechas y condiciones

Fechas de entrega

  • Grupo 810: 07/04/2026 — Proyecto Entrega 2 (10%).
  • Grupo 811: 08/04/2026 — Proyecto Entrega 2 (10%).

Estas fechas también corresponden a la prueba individual de la Unidad 2 (810: 09/04 · 811: 10/04). El dominio del sistema propio es la mejor preparación para esa prueba.

Condiciones de entrega

  • Repositorio público en GitHub con historial de commits visible.
  • Archivo README en la raíz del repositorio.
  • El código debe compilar y ejecutarse con dotnet run sin configuración adicional.
  • No se aceptan entregas por correo ni por el chat del LMS; solo por el enlace del repositorio.
Cierre: la diferencia entre código que funciona y código confiable

Un programa de ingeniería no solo produce resultados correctos

  • Un programa que colapsa ante una entrada inesperada no está terminado, está incompleto. La robustez no es un extra; es un requisito mínimo.
  • La depuración sistemática (breakpoints, inspección de variables, pruebas de frontera) no es señal de debilidad; es el método de trabajo de cualquier ingeniero de software profesional.
  • El código que entregas hoy será el material de refactorización de la Unidad 3. Entregarlo limpio y modular en sus responsabilidades reducirá el esfuerzo de la siguiente fase a la mitad.
  • La práctica de code review en pares es una de las herramientas de mayor impacto en la industria para reducir defectos antes de producción.