Capítulo 7: Bucles (loops) en C#

5 min de lectura

Parte I - Sección 2: Programación desde cero - Nivel: Principiante


Objetivos de aprendizaje

Al finalizar este capítulo serás capaz de:

  • Usar bucles for para repetir código un número específico de veces
  • Usar foreach para iterar colecciones de datos
  • Usar while para repetir mientras se cumpla una condición
  • Usar do-while cuando necesitas ejecutar al menos una vez
  • Controlar bucles con break y continue
  • Anidar bucles para procesar estructuras complejas
  • Construir procesadores automáticos de listas de componentes

Requisitos previos

Antes de comenzar este capítulo debes:

  • Haber completado el Capítulo 6
  • Entender estructuras if/else y switch
  • Conocer variables y operadores
  • Saber usar operadores de comparación

Proyecto del capítulo

Procesador Automático de Componentes Eléctricos

Al final de este capítulo habrás construido un script que:

  • Procesa listas de componentes automáticamente
  • Calcula resistencias en serie y paralelo
  • Genera reportes de múltiples circuitos
  • Valida datos de forma masiva

Tiempo estimado: 50-60 minutos


Contenido

1. Introducción: El problema de las tareas repetitivas

Imagina que necesitas:

  • Verificar 50 componentes en una lista
  • Calcular la resistencia total de 20 resistencias en serie
  • Generar 15 reportes con el mismo formato
  • Procesar 100 cables para verificar secciones

Sin bucles:

double resistencia1 = 100.0;
double resistencia2 = 220.0;
double resistencia3 = 330.0;
double resistencia4 = 470.0;
double resistencia5 = 560.0;
// ... repetir 15 veces más

double total = resistencia1 + resistencia2 + resistencia3 + resistencia4 + resistencia5;
// Tedioso, propenso a errores, difícil de mantener

Con bucles:

double[] resistencias = {100, 220, 330, 470, 560, 680, 820, 1000};
double total = 0;

for (int i = 0; i < resistencias.Length; i++)
{
    total += resistencias[i];
}
// Limpio, escalable, fácil de mantener

Los bucles son la base de la automatización.


2. Bucle for - Contador conocido

El bucle for se usa cuando sabes cuántas veces quieres repetir algo.

2.1 Sintaxis básica

for (inicialización; condición; incremento)
{
    // Código que se repite
}

Ejemplo simple:

for (int i = 0; i < 5; i++)
{
    MessageBox.Show("Iteración número: " + i);
}
// Muestra: 0, 1, 2, 3, 4

Anatomía:

for (int i = 0;   i < 5;   i++)
     ↑            ↑         ↑
     |            |         └─ Incremento: ejecuta después de cada iteración
     |            └─────────── Condición: evalúa antes de cada iteración
     └──────────────────────── Inicialización: ejecuta una sola vez al inicio

2.2 Cómo funciona

  1. Inicialización: int i = 0 - Crea variable contador
  2. Condición: i < 5 - ¿Continuar? Si es true, ejecuta el bloque
  3. Ejecución: Ejecuta el código dentro de { }
  4. Incremento: i++ - Aumenta el contador
  5. Vuelve al paso 2

Flujo visual:

i=0 → ¿i<5? Sí → Ejecuta → i++ → i=1
i=1 → ¿i<5? Sí → Ejecuta → i++ → i=2
i=2 → ¿i<5? Sí → Ejecuta → i++ → i=3
i=3 → ¿i<5? Sí → Ejecuta → i++ → i=4
i=4 → ¿i<5? Sí → Ejecuta → i++ → i=5
i=5 → ¿i<5? No → Termina

2.3 Ejemplos prácticos

Ejemplo 1: Generar secuencia de páginas

string paginas = "Páginas del proyecto:\n";

for (int numeroPagina = 1; numeroPagina <= 10; numeroPagina++)
{
    paginas += "Página " + numeroPagina + "\n";
}

MessageBox.Show(paginas);

Ejemplo 2: Calcular potencias de 2

string resultado = "Potencias de 2:\n";

for (int exponente = 0; exponente <= 10; exponente++)
{
    double potencia = Math.Pow(2, exponente);
    resultado += string.Format("2^{0} = {1}\n", exponente, potencia);
}

MessageBox.Show(resultado);
// 2^0 = 1
// 2^1 = 2
// 2^2 = 4
// ...

Ejemplo 3: Sumar resistencias en serie

double[] resistencias = {100, 220, 330, 470, 560};
double resistenciaTotal = 0;

for (int i = 0; i < resistencias.Length; i++)
{
    resistenciaTotal += resistencias[i];
}

MessageBox.Show(string.Format("Resistencia total en serie: {0:F2} Ω", resistenciaTotal));
// Resistencia total en serie: 1680.00 Ω

2.4 Variaciones del for

Contar hacia atrás:

for (int i = 10; i >= 1; i--)
{
    MessageBox.Show("Cuenta atrás: " + i);
}
// 10, 9, 8, 7, 6, 5, 4, 3, 2, 1

Incremento de 2 en 2:

for (int i = 0; i <= 10; i += 2)
{
    MessageBox.Show("Número par: " + i);
}
// 0, 2, 4, 6, 8, 10

Múltiples variables (poco común):

for (int i = 0, j = 10; i < j; i++, j--)
{
    MessageBox.Show(string.Format("i={0}, j={1}", i, j));
}
// i=0, j=10
// i=1, j=9
// i=2, j=8
// ...

3. Bucle foreach - Iterar colecciones

El bucle foreach se usa para recorrer colecciones (arrays, listas) sin necesidad de índices.

3.1 Sintaxis básica

foreach (tipo variable in coleccion)
{
    // Código que se ejecuta para cada elemento
}

Ejemplo simple:

string[] fabricantes = {"Siemens", "Schneider", "ABB", "Eaton"};

foreach (string fabricante in fabricantes)
{
    MessageBox.Show("Fabricante: " + fabricante);
}

3.2 for vs foreach

Con for:

string[] componentes = {"Contactor", "Relé", "Guardamotor"};

for (int i = 0; i < componentes.Length; i++)
{
    MessageBox.Show(componentes[i]);
}

Con foreach (más simple):

string[] componentes = {"Contactor", "Relé", "Guardamotor"};

foreach (string componente in componentes)
{
    MessageBox.Show(componente);
}

Cuándo usar cada uno:

  • foreach: Cuando solo necesitas recorrer todos los elementos
  • for: Cuando necesitas el índice, o quieres saltar elementos, o modificar el array

3.3 Ejemplos prácticos

Ejemplo 1: Validar voltajes

double[] voltajesMedidos = {228.5, 232.1, 229.8, 235.2, 226.4};
int voltajesOK = 0;
int voltajesFuera = 0;

foreach (double voltaje in voltajesMedidos)
{
    if (voltaje >= 207 && voltaje <= 253)
    {
        voltajesOK++;
    }
    else
    {
        voltajesFuera++;
    }
}

string mensaje = string.Format(
    "Voltajes dentro de rango: {0}\nVoltajes fuera de rango: {1}",
    voltajesOK,
    voltajesFuera
);

MessageBox.Show(mensaje);

Ejemplo 2: Calcular corriente total

double[] corrientesCircuitos = {15.5, 8.2, 12.7, 20.3, 6.8};
double corrienteTotal = 0;

foreach (double corriente in corrientesCircuitos)
{
    corrienteTotal += corriente;
}

MessageBox.Show(string.Format("Corriente total: {0:F2} A", corrienteTotal));
// Corriente total: 63.50 A

Ejemplo 3: Buscar componente específico

string[] componentes = {"K1", "Q1", "F1", "K2", "Q2"};
string buscado = "F1";
bool encontrado = false;

foreach (string componente in componentes)
{
    if (componente == buscado)
    {
        encontrado = true;
        break;  // Sal del bucle cuando lo encuentres
    }
}

if (encontrado)
{
    MessageBox.Show(buscado + " encontrado en la lista");
}
else
{
    MessageBox.Show(buscado + " NO encontrado");
}

4. Bucle while - Mientras se cumpla una condición

El bucle while se usa cuando no sabes cuántas iteraciones necesitarás, pero tienes una condición de parada.

4.1 Sintaxis básica

while (condicion)
{
    // Código que se repite mientras condicion sea true
}

Ejemplo simple:

int contador = 0;

while (contador < 5)
{
    MessageBox.Show("Contador: " + contador);
    contador++;
}
// 0, 1, 2, 3, 4

4.2 Diferencia con for

Con for (sabes las iteraciones):

for (int i = 0; i < 5; i++)
{
    MessageBox.Show("i: " + i);
}

Con while (condición dinámica):

double voltaje = 230.0;
int iteraciones = 0;

while (voltaje >= 207.0 && voltaje <= 253.0)
{
    MessageBox.Show(string.Format("Voltaje OK: {0:F1}V", voltaje));
    voltaje -= 5.0;  // Simular caída de voltaje
    iteraciones++;

    if (iteraciones > 10) break;  // Seguridad para evitar bucle infinito
}

4.3 Ejemplos prácticos

Ejemplo 1: Dividir corriente hasta alcanzar límite

double corrienteTotal = 100.0;  // A
double corrientePorCircuito = 16.0;  // A
int numeroCir cuitos = 0;

while (corrienteTotal > 0)
{
    corrienteTotal -= corrientePorCircuito;
    numeroCir cuitos++;
}

MessageBox.Show(string.Format("Se necesitan {0} circuitos de 16A", numeroCir cuitos));
// Se necesitan 7 circuitos de 16A

Ejemplo 2: Encontrar sección de cable adecuada

double corrienteCircuito = 28.5;  // A
double[] seccionesDisponibles = {1.5, 2.5, 4.0, 6.0, 10.0, 16.0};
double[] corrientesAdmisibles = {15.0, 21.0, 27.0, 36.0, 50.0, 68.0};

int indice = 0;
while (indice < seccionesDisponibles.Length)
{
    if (corrientesAdmisibles[indice] >= corrienteCircuito)
    {
        MessageBox.Show(string.Format(
            "Para {0}A, usar cable de {1}mm² (admite {2}A)",
            corrienteCircuito,
            seccionesDisponibles[indice],
            corrientesAdmisibles[indice]
        ));
        break;
    }
    indice++;
}

Ejemplo 3: Validación de entrada

bool entradaValida = false;
int intentos = 0;
double voltaje = 150.0;  // Simulación de entrada de usuario

while (!entradaValida && intentos < 3)
{
    if (voltaje >= 207.0 && voltaje <= 253.0)
    {
        entradaValida = true;
        MessageBox.Show("Voltaje aceptado: " + voltaje + "V");
    }
    else
    {
        MessageBox.Show("Voltaje fuera de rango. Intento " + (intentos + 1));
        voltaje += 50.0;  // Simulación de corrección
        intentos++;
    }
}

if (!entradaValida)
{
    MessageBox.Show("Máximo de intentos alcanzado");
}

4.4 Cuidado con bucles infinitos

Bucle infinito (NUNCA termina):

int contador = 0;

while (contador < 5)
{
    MessageBox.Show("Contador: " + contador);
    // ¡Olvidaste incrementar contador!
    // Este bucle NUNCA termina
}

Solución: Siempre asegúrate de que la condición pueda ser false:

int contador = 0;

while (contador < 5)
{
    MessageBox.Show("Contador: " + contador);
    contador++;  // ← Esto hace que eventualmente contador >= 5
}

5. Bucle do-while - Ejecutar al menos una vez

El bucle do-while siempre ejecuta al menos una iteración, luego verifica la condición.

5.1 Sintaxis básica

do
{
    // Código que se ejecuta AL MENOS una vez
} while (condicion);

Diferencia clave con while:

while: Verifica primero, luego ejecuta

int contador = 10;

while (contador < 5)
{
    MessageBox.Show("Nunca se ejecuta");
}
// No muestra nada porque 10 < 5 es false desde el inicio

do-while: Ejecuta primero, luego verifica

int contador = 10;

do
{
    MessageBox.Show("Se ejecuta una vez: " + contador);
} while (contador < 5);
// Muestra "Se ejecuta una vez: 10" aunque la condición sea false

5.2 Ejemplos prácticos

Ejemplo 1: Menú de opciones (se debe mostrar al menos una vez)

int opcion;

do
{
    string menu = "MENÚ:\n1. Validar proyecto\n2. Exportar PDF\n3. Salir\n\nElija opción:";
    opcion = 2;  // Simulación (en realidad vendría de usuario)

    switch (opcion)
    {
        case 1:
            MessageBox.Show("Validando proyecto...");
            break;
        case 2:
            MessageBox.Show("Exportando PDF...");
            break;
        case 3:
            MessageBox.Show("Saliendo...");
            break;
        default:
            MessageBox.Show("Opción no válida");
            break;
    }

    opcion = 3;  // Simulación de salir

} while (opcion != 3);

Ejemplo 2: Validar y reintentar

double voltaje;
bool esValido;

do
{
    voltaje = 195.0;  // Simulación de entrada
    esValido = (voltaje >= 207.0 && voltaje <= 253.0);

    if (!esValido)
    {
        MessageBox.Show(string.Format("Voltaje {0}V fuera de rango. Reintentando...", voltaje));
        voltaje = 230.0;  // Simulación de corrección
        esValido = true;  // Para salir del ejemplo
    }

} while (!esValido);

MessageBox.Show(string.Format("Voltaje válido: {0}V", voltaje));

6. break y continue - Control de flujo en bucles

6.1 break - Salir del bucle inmediatamente

for (int i = 0; i < 10; i++)
{
    if (i == 5)
    {
        break;  // Sale del bucle cuando i=5
    }
    MessageBox.Show("i: " + i);
}
// Muestra: 0, 1, 2, 3, 4 (se detiene en 5)

Ejemplo práctico: Buscar primer componente defectuoso

string[] componentes = {"K1-OK", "Q1-OK", "F1-FALLO", "K2-OK", "Q2-OK"};
string primerDefectuoso = "";

foreach (string componente in componentes)
{
    if (componente.Contains("FALLO"))
    {
        primerDefectuoso = componente;
        break;  // No necesitamos seguir buscando
    }
}

MessageBox.Show("Primer defectuoso: " + primerDefectuoso);
// Primer defectuoso: F1-FALLO

6.2 continue - Saltar a la siguiente iteración

for (int i = 0; i < 10; i++)
{
    if (i % 2 == 0)
    {
        continue;  // Salta los números pares
    }
    MessageBox.Show("Impar: " + i);
}
// Muestra: 1, 3, 5, 7, 9

Ejemplo práctico: Procesar solo componentes válidos

string[] componentes = {"K1", "", "Q1", "INVALIDO", "F1", "", "K2"};
string componentesValidos = "Componentes válidos:\n";

foreach (string componente in componentes)
{
    // Saltar vacíos o inválidos
    if (componente == "" || componente == "INVALIDO")
    {
        continue;
    }

    componentesValidos += componente + "\n";
}

MessageBox.Show(componentesValidos);
// K1, Q1, F1, K2

6.3 Diferencia entre break y continue

// break: SALE del bucle completamente
for (int i = 0; i < 5; i++)
{
    if (i == 2) break;
    MessageBox.Show("i: " + i);
}
// Muestra: 0, 1 (termina)

// continue: SALTA a la siguiente iteración
for (int i = 0; i < 5; i++)
{
    if (i == 2) continue;
    MessageBox.Show("i: " + i);
}
// Muestra: 0, 1, 3, 4 (salta el 2, pero continúa)

7. Bucles anidados - Bucles dentro de bucles

Puedes colocar un bucle dentro de otro para procesar estructuras bidimensionales o combinaciones.

7.1 Sintaxis básica

for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3; j++)
    {
        MessageBox.Show(string.Format("i={0}, j={1}", i, j));
    }
}
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
// i=1, j=1
// ...

Iteraciones totales: 3 × 3 = 9

7.2 Ejemplos prácticos

Ejemplo 1: Procesar múltiples proyectos con múltiples idiomas

string[] proyectos = {"Proyecto_A", "Proyecto_B", "Proyecto_C"};
string[] idiomas = {"ES", "EN", "FR"};

string reporte = "Exportaciones a realizar:\n";

foreach (string proyecto in proyectos)
{
    foreach (string idioma in idiomas)
    {
        reporte += string.Format("- {0} en {1}\n", proyecto, idioma);
    }
}

MessageBox.Show(reporte);
// Proyecto_A en ES
// Proyecto_A en EN
// Proyecto_A en FR
// Proyecto_B en ES
// ...

Ejemplo 2: Calcular resistencias en matriz (serie + paralelo)

// 3 ramas en paralelo, cada una con 4 resistencias en serie
double[][] resistencias = new double[][]
{
    new double[] {100, 220, 330, 470},  // Rama 1
    new double[] {150, 270, 390, 510},  // Rama 2
    new double[] {180, 300, 420, 560}   // Rama 3
};

string reporte = "CÁLCULO DE RESISTENCIAS:\n\n";

// Calcular resistencia de cada rama (serie)
double[] resistenciasPorRama = new double[resistencias.Length];

for (int rama = 0; rama < resistencias.Length; rama++)
{
    double resistenciaRama = 0;

    for (int r = 0; r < resistencias[rama].Length; r++)
    {
        resistenciaRama += resistencias[rama][r];
    }

    resistenciasPorRama[rama] = resistenciaRama;
    reporte += string.Format("Rama {0}: {1:F2} Ω\n", rama + 1, resistenciaRama);
}

// Calcular resistencia total en paralelo
// 1/Rtotal = 1/R1 + 1/R2 + 1/R3
double sumaInversos = 0;

for (int i = 0; i < resistenciasPorRama.Length; i++)
{
    sumaInversos += 1.0 / resistenciasPorRama[i];
}

double resistenciaTotal = 1.0 / sumaInversos;

reporte += string.Format("\nResistencia total (paralelo): {0:F2} Ω", resistenciaTotal);

MessageBox.Show(reporte);

Ejemplo 3: Generar tabla de multiplicar

string tabla = "TABLA DE VALORES DE CABLES:\n\n";
tabla += "Sección(mm²) | 10A | 16A | 20A | 25A\n";
tabla += "--------------------------------------\n";

double[] secciones = {1.5, 2.5, 4.0, 6.0};
double[] corrientes = {10, 16, 20, 25};

foreach (double seccion in secciones)
{
    tabla += string.Format("{0,12} |", seccion);

    foreach (double corriente in corrientes)
    {
        // Caída de tensión simplificada: ΔV = 2 × L × I × ρ / S
        double longitud = 50.0;  // m
        double resistividad = 0.018;
        double caidaTension = (2 * longitud * corriente * resistividad) / seccion;

        tabla += string.Format(" {0:F1}V |", caidaTension);
    }

    tabla += "\n";
}

MessageBox.Show(tabla);

7.3 Cuidado con la complejidad

3 bucles anidados: 10 × 10 × 10 = 1,000 iteraciones

for (int i = 0; i < 10; i++)
{
    for (int j = 0; j < 10; j++)
    {
        for (int k = 0; k < 10; k++)
        {
            // 1000 iteraciones
        }
    }
}

Regla general: Evita más de 2-3 niveles de anidación para mantener el código legible.


Manos a la obra: Construyendo el proyecto

Vamos a crear un Procesador Automático de Componentes Eléctricos que use todos los tipos de bucles.

Paso 1: Estructura básica y datos de entrada

using System;
using Eplan.EplApi.Base;
using Eplan.EplApi.ApplicationFramework;

public class ProcesadorComponentes
{
    [Start]
    public void Ejecutar()
    {
        // ========================================
        // DATOS DE ENTRADA
        // ========================================

        // Lista de componentes a procesar
        string[] componentesID = {"K1", "K2", "Q1", "Q2", "F1", "F2", "M1", "M2"};

        // Corrientes por componente (en orden)
        double[] corrientes = {25.0, 40.0, 16.0, 20.0, 10.0, 16.0, 7.5, 11.0};

        // Resistencias para cálculo (Ohmios)
        double[] resistenciasSerie = {100, 220, 330, 470, 560};

        // Voltajes medidos en diferentes puntos
        double[] voltajesMedidos = {232.5, 228.3, 235.1, 226.8, 230.2, 233.7};
    }
}

Paso 2: Procesar componentes con for

// ========================================
// PROCESAMIENTO 1: VALIDAR COMPONENTES
// ========================================

string reporteComponentes = "=== VALIDACIÓN DE COMPONENTES ===\n\n";
int componentesOK = 0;
int componentesAdvertencia = 0;

for (int i = 0; i < componentesID.Length; i++)
{
    string id = componentesID[i];
    double corriente = corrientes[i];

    // Clasificar según corriente
    string clasificacion;
    if (corriente < 16.0)
    {
        clasificacion = "Baja corriente";
        componentesOK++;
    }
    else if (corriente <= 32.0)
    {
        clasificacion = "Corriente media";
        componentesOK++;
    }
    else
    {
        clasificacion = "ALTA CORRIENTE - Verificar";
        componentesAdvertencia++;
    }

    reporteComponentes += string.Format(
        "{0}: {1:F1}A - {2}\n",
        id,
        corriente,
        clasificacion
    );
}

reporteComponentes += string.Format(
    "\nResumen: {0} OK, {1} con advertencia\n",
    componentesOK,
    componentesAdvertencia
);

¿Qué acabamos de hacer? Usamos for con índice para acceder simultáneamente a dos arrays paralelos (IDs y corrientes).


Paso 3: Calcular resistencias con foreach

// ========================================
// PROCESAMIENTO 2: CALCULAR RESISTENCIAS
// ========================================

string reporteResistencias = "\n=== CÁLCULO DE RESISTENCIAS ===\n\n";

// Resistencias en serie (sumar todas)
double resistenciaTotalSerie = 0;

foreach (double resistencia in resistenciasSerie)
{
    resistenciaTotalSerie += resistencia;
}

reporteResistencias += string.Format(
    "Resistencia total en serie: {0:F2} Ω\n",
    resistenciaTotalSerie
);

// Resistencias en paralelo (fórmula: 1/Rtotal = 1/R1 + 1/R2 + ...)
double sumaInversos = 0;

foreach (double resistencia in resistenciasSerie)
{
    sumaInversos += 1.0 / resistencia;
}

double resistenciaTotalParalelo = 1.0 / sumaInversos;

reporteResistencias += string.Format(
    "Resistencia total en paralelo: {0:F2} Ω\n",
    resistenciaTotalParalelo
);

// Mostrar detalle de cada resistencia
reporteResistencias += "\nDetalle:\n";
int numeroResistencia = 1;

foreach (double resistencia in resistenciasSerie)
{
    reporteResistencias += string.Format(
        "R{0}: {1} Ω\n",
        numeroResistencia,
        resistencia
    );
    numeroResistencia++;
}

¿Qué acabamos de hacer? Usamos foreach para recorrer arrays sin necesidad de índices, ideal para sumas y cálculos acumulativos.


Paso 4: Validar voltajes con while

// ========================================
// PROCESAMIENTO 3: VALIDAR VOLTAJES
// ========================================

string reporteVoltajes = "\n=== VALIDACIÓN DE VOLTAJES ===\n\n";

double voltajeMinimo = 207.0;  // 230V - 10%
double voltajeMaximo = 253.0;  // 230V + 10%
int voltajesCorrectos = 0;
int voltajesIncorrectos = 0;
int indice = 0;

while (indice < voltajesMedidos.Length)
{
    double voltaje = voltajesMedidos[indice];

    if (voltaje >= voltajeMinimo && voltaje <= voltajeMaximo)
    {
        reporteVoltajes += string.Format(
            "Punto {0}: {1:F1}V - OK\n",
            indice + 1,
            voltaje
        );
        voltajesCorrectos++;
    }
    else
    {
        reporteVoltajes += string.Format(
            "Punto {0}: {1:F1}V - FUERA DE RANGO\n",
            indice + 1,
            voltaje
        );
        voltajesIncorrectos++;
    }

    indice++;
}

reporteVoltajes += string.Format(
    "\nTotal: {0} correctos, {1} fuera de rango\n",
    voltajesCorrectos,
    voltajesIncorrectos
);

¿Qué acabamos de hacer? Usamos while con control manual del índice, útil cuando necesitas lógica más compleja dentro del bucle.


Paso 5: Calcular corriente total por tipo con bucles anidados

// ========================================
// PROCESAMIENTO 4: ANÁLISIS POR TIPO
// ========================================

string reporteTipos = "\n=== ANÁLISIS POR TIPO DE COMPONENTE ===\n\n";

// Tipos de componentes a buscar
string[] tiposComponentes = {"K", "Q", "F", "M"};
string[] nombresDescriptivos = {"Contactores", "Guardamotores", "Relés térmicos", "Motores"};

// Bucle externo: cada tipo de componente
for (int t = 0; t < tiposComponentes.Length; t++)
{
    string tipo = tiposComponentes[t];
    string nombre = nombresDescriptivos[t];
    double corrienteTotal = 0;
    int cantidad = 0;

    // Bucle interno: cada componente en la lista
    for (int i = 0; i < componentesID.Length; i++)
    {
        // Verificar si el componente es de este tipo
        if (componentesID[i].StartsWith(tipo))
        {
            corrienteTotal += corrientes[i];
            cantidad++;
        }
    }

    if (cantidad > 0)
    {
        double corrientePromedio = corrienteTotal / cantidad;

        reporteTipos += string.Format(
            "{0}:\n" +
            "  Cantidad: {1}\n" +
            "  Corriente total: {2:F1}A\n" +
            "  Corriente promedio: {3:F1}A\n\n",
            nombre,
            cantidad,
            corrienteTotal,
            corrientePromedio
        );
    }
}

¿Qué acabamos de hacer? Usamos bucles anidados para agrupar componentes por tipo y calcular estadísticas.


Paso 6: Buscar componentes problemáticos con break

// ========================================
// PROCESAMIENTO 5: DETECTAR PROBLEMAS
// ========================================

string reporteProblemas = "\n=== DETECCIÓN DE PROBLEMAS ===\n\n";

// Buscar primer componente con corriente excesiva
string primerProblematico = "";
double corrienteProblematica = 0;
double limite = 35.0;  // A

for (int i = 0; i < componentesID.Length; i++)
{
    if (corrientes[i] > limite)
    {
        primerProblematico = componentesID[i];
        corrienteProblematica = corrientes[i];
        break;  // Encontramos el primero, no necesitamos seguir
    }
}

if (primerProblematico != "")
{
    reporteProblemas += string.Format(
        "ADVERTENCIA: Componente {0} con corriente excesiva: {1:F1}A (límite: {2}A)\n",
        primerProblematico,
        corrienteProblematica,
        limite
    );
}
else
{
    reporteProblemas += "Todos los componentes dentro de límites normales.\n";
}

// Contar componentes válidos (saltando los problemáticos con continue)
int componentesValidos = 0;

foreach (double corriente in corrientes)
{
    if (corriente > limite)
    {
        continue;  // Saltar este componente
    }

    componentesValidos++;
}

reporteProblemas += string.Format(
    "Componentes válidos: {0} de {1}\n",
    componentesValidos,
    corrientes.Length
);

¿Qué acabamos de hacer? Usamos break para detener búsqueda cuando encontramos el primer problema, y continue para saltar elementos en el conteo.


Paso 7: Construcción del reporte final

// ========================================
// REPORTE FINAL
// ========================================

string separador = new string('=', 60);

string reporteFinal = string.Format(
    "{0}\n" +
    "PROCESADOR AUTOMÁTICO DE COMPONENTES ELÉCTRICOS\n" +
    "{0}\n\n" +
    "{1}\n" +
    "{2}\n" +
    "{3}\n" +
    "{4}\n" +
    "{5}\n\n" +
    "{0}\n" +
    "Procesamiento completado con {6} componentes\n" +
    "{0}",
    separador,
    reporteComponentes,
    reporteResistencias,
    reporteVoltajes,
    reporteTipos,
    reporteProblemas,
    componentesID.Length
);

// ========================================
// MOSTRAR RESULTADO
// ========================================

MessageBox.Show(reporteFinal, "Procesador de Componentes", MessageBoxButtons.OK, MessageBoxIcon.Information);

Código completo del proyecto


using System;
using Eplan.EplApi.Base;
using Eplan.EplApi.ApplicationFramework;

public class ProcesadorComponentes
{
    [Start]
    public void Ejecutar()
    {
        // ========================================
        // DATOS DE ENTRADA
        // ========================================

        string[] componentesID = {"K1", "K2", "Q1", "Q2", "F1", "F2", "M1", "M2"};
        double[] corrientes = {25.0, 40.0, 16.0, 20.0, 10.0, 16.0, 7.5, 11.0};
        double[] resistenciasSerie = {100, 220, 330, 470, 560};
        double[] voltajesMedidos = {232.5, 228.3, 235.1, 226.8, 230.2, 233.7};

        // ========================================
        // PROCESAMIENTO 1: VALIDAR COMPONENTES (FOR)
        // ========================================

        string reporteComponentes = "=== VALIDACIÓN DE COMPONENTES ===\n\n";
        int componentesOK = 0;
        int componentesAdvertencia = 0;

        for (int i = 0; i < componentesID.Length; i++)
        {
            string id = componentesID[i];
            double corriente = corrientes[i];

            string clasificacion;
            if (corriente < 16.0)
            {
                clasificacion = "Baja corriente";
                componentesOK++;
            }
            else if (corriente <= 32.0)
            {
                clasificacion = "Corriente media";
                componentesOK++;
            }
            else
            {
                clasificacion = "ALTA CORRIENTE - Verificar";
                componentesAdvertencia++;
            }

            reporteComponentes += string.Format("{0}: {1:F1}A - {2}\n", id, corriente, clasificacion);
        }

        reporteComponentes += string.Format("\nResumen: {0} OK, {1} con advertencia\n", componentesOK, componentesAdvertencia);

        // ========================================
        // PROCESAMIENTO 2: CALCULAR RESISTENCIAS (FOREACH)
        // ========================================

        string reporteResistencias = "\n=== CÁLCULO DE RESISTENCIAS ===\n\n";

        double resistenciaTotalSerie = 0;
        foreach (double resistencia in resistenciasSerie)
        {
            resistenciaTotalSerie += resistencia;
        }

        reporteResistencias += string.Format("Resistencia total en serie: {0:F2} Ω\n", resistenciaTotalSerie);

        double sumaInversos = 0;
        foreach (double resistencia in resistenciasSerie)
        {
            sumaInversos += 1.0 / resistencia;
        }

        double resistenciaTotalParalelo = 1.0 / sumaInversos;
        reporteResistencias += string.Format("Resistencia total en paralelo: {0:F2} Ω\n", resistenciaTotalParalelo);

        reporteResistencias += "\nDetalle:\n";
        int numeroResistencia = 1;
        foreach (double resistencia in resistenciasSerie)
        {
            reporteResistencias += string.Format("R{0}: {1} Ω\n", numeroResistencia, resistencia);
            numeroResistencia++;
        }

        // ========================================
        // PROCESAMIENTO 3: VALIDAR VOLTAJES (WHILE)
        // ========================================

        string reporteVoltajes = "\n=== VALIDACIÓN DE VOLTAJES ===\n\n";

        double voltajeMinimo = 207.0;
        double voltajeMaximo = 253.0;
        int voltajesCorrectos = 0;
        int voltajesIncorrectos = 0;
        int indice = 0;

        while (indice < voltajesMedidos.Length)
        {
            double voltaje = voltajesMedidos[indice];

            if (voltaje >= voltajeMinimo && voltaje <= voltajeMaximo)
            {
                reporteVoltajes += string.Format("Punto {0}: {1:F1}V - OK\n", indice + 1, voltaje);
                voltajesCorrectos++;
            }
            else
            {
                reporteVoltajes += string.Format("Punto {0}: {1:F1}V - FUERA DE RANGO\n", indice + 1, voltaje);
                voltajesIncorrectos++;
            }

            indice++;
        }

        reporteVoltajes += string.Format("\nTotal: {0} correctos, {1} fuera de rango\n", voltajesCorrectos, voltajesIncorrectos);

        // ========================================
        // PROCESAMIENTO 4: ANÁLISIS POR TIPO (BUCLES ANIDADOS)
        // ========================================

        string reporteTipos = "\n=== ANÁLISIS POR TIPO DE COMPONENTE ===\n\n";

        string[] tiposComponentes = {"K", "Q", "F", "M"};
        string[] nombresDescriptivos = {"Contactores", "Guardamotores", "Relés térmicos", "Motores"};

        for (int t = 0; t < tiposComponentes.Length; t++)
        {
            string tipo = tiposComponentes[t];
            string nombre = nombresDescriptivos[t];
            double corrienteTotal = 0;
            int cantidad = 0;

            for (int i = 0; i < componentesID.Length; i++)
            {
                if (componentesID[i].StartsWith(tipo))
                {
                    corrienteTotal += corrientes[i];
                    cantidad++;
                }
            }

            if (cantidad > 0)
            {
                double corrientePromedio = corrienteTotal / cantidad;
                reporteTipos += string.Format("{0}:\n  Cantidad: {1}\n  Corriente total: {2:F1}A\n  Corriente promedio: {3:F1}A\n\n",
                    nombre, cantidad, corrienteTotal, corrientePromedio);
            }
        }

        // ========================================
        // PROCESAMIENTO 5: DETECTAR PROBLEMAS (BREAK/CONTINUE)
        // ========================================

        string reporteProblemas = "\n=== DETECCIÓN DE PROBLEMAS ===\n\n";

        string primerProblematico = "";
        double corrienteProblematica = 0;
        double limite = 35.0;

        for (int i = 0; i < componentesID.Length; i++)
        {
            if (corrientes[i] > limite)
            {
                primerProblematico = componentesID[i];
                corrienteProblematica = corrientes[i];
                break;
            }
        }

        if (primerProblematico != "")
        {
            reporteProblemas += string.Format("ADVERTENCIA: Componente {0} con corriente excesiva: {1:F1}A (límite: {2}A)\n",
                primerProblematico, corrienteProblematica, limite);
        }
        else
        {
            reporteProblemas += "Todos los componentes dentro de límites normales.\n";
        }

        int componentesValidos = 0;
        foreach (double corriente in corrientes)
        {
            if (corriente > limite)
            {
                continue;
            }
            componentesValidos++;
        }

        reporteProblemas += string.Format("Componentes válidos: {0} de {1}\n", componentesValidos, corrientes.Length);

        // ========================================
        // REPORTE FINAL
        // ========================================

        string separador = new string('=', 60);

        string reporteFinal = string.Format(
            "{0}\n" +
            "PROCESADOR AUTOMÁTICO DE COMPONENTES ELÉCTRICOS\n" +
            "{0}\n\n" +
            "{1}\n{2}\n{3}\n{4}\n{5}\n\n" +
            "{0}\n" +
            "Procesamiento completado con {6} componentes\n" +
            "{0}",
            separador, reporteComponentes, reporteResistencias, reporteVoltajes,
            reporteTipos, reporteProblemas, componentesID.Length
        );

        MessageBox.Show(reporteFinal, "Procesador de Componentes", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
}

Deep Dive: Entendiendo en profundidad

Rendimiento de bucles

for vs foreach - ¿Cuál es más rápido?

Para arrays simples, for es ligeramente más rápido, pero la diferencia es despreciable en la mayoría de casos.

// for: acceso directo por índice
for (int i = 0; i < array.Length; i++)
{
    var item = array[i];
}

// foreach: usa enumerador interno
foreach (var item in array)
{
    // Ligeramente más lento, pero más legible
}

Recomendación: Usa foreach por defecto (más legible). Usa for solo cuando necesites el índice o performance crítica.

Bucles infinitos útiles

En algunos casos, bucles infinitos son intencionales:

while (true)
{
    // Procesar algo...

    if (condicionSalida)
    {
        break;  // Salida controlada
    }
}

Uso en EPLAN: Monitorear estado hasta que usuario cancele.

Optimización de bucles anidados

Malo (ineficiente):

for (int i = 0; i < 1000; i++)
{
    for (int j = 0; j < 1000; j++)
    {
        // 1,000,000 iteraciones
        string resultado = ExpensiveOperation();
    }
}

Mejor (optimizado):

string resultado = ExpensiveOperation();  // Solo una vez

for (int i = 0; i < 1000; i++)
{
    for (int j = 0; j < 1000; j++)
    {
        // Usar resultado pre-calculado
    }
}

Mejores prácticas

1. Nombres descriptivos para variables de bucle

// Mal
for (int i = 0; i < c.Length; i++)
{
    // ¿Qué es i? ¿Qué es c?
}

// Bien
for (int indiceComponente = 0; indiceComponente < componentes.Length; indiceComponente++)
{
    // Claro y legible
}

// Aceptable para bucles simples
for (int i = 0; i < items.Length; i++)
{
    // 'i' es estándar para índices
}

2. Evita modificar contador dentro del bucle

// Mal (confuso)
for (int i = 0; i < 10; i++)
{
    if (condicion)
    {
        i += 2;  // Modificar el contador puede causar bugs
    }
}

// Bien
for (int i = 0; i < 10; i++)
{
    if (condicion)
    {
        continue;  // Saltar iteraciones específicas
    }
}

3. Caché la longitud si es costosa

// Ineficiente (calcula Length en cada iteración si es costoso)
for (int i = 0; i < MetodoComplejo().Length; i++)
{
    // ...
}

// Eficiente
int longitud = MetodoComplejo().Length;
for (int i = 0; i < longitud; i++)
{
    // ...
}

Errores comunes

Error 1: Off-by-one (uno de más/menos)

int[] numeros = {10, 20, 30, 40, 50};

// Mal: va de 0 a 5 (¡índice 5 no existe!)
for (int i = 0; i <= numeros.Length; i++)  // ERROR
{
    MessageBox.Show(numeros[i].ToString());
}

// Bien: va de 0 a 4
for (int i = 0; i < numeros.Length; i++)
{
    MessageBox.Show(numeros[i].ToString());
}

Error 2: Modificar colección durante foreach

List<string> componentes = new List<string>{"K1", "Q1", "F1"};

// ERROR: No puedes modificar la colección durante foreach
foreach (string comp in componentes)
{
    componentes.Remove(comp);  // ERROR en tiempo de ejecución
}

// Solución: Usa for hacia atrás
for (int i = componentes.Count - 1; i >= 0; i--)
{
    componentes.RemoveAt(i);  // OK
}

Error 3: Bucle infinito accidental

int contador = 0;

// Bucle infinito (nunca termina)
while (contador < 10)
{
    MessageBox.Show("Contador: " + contador);
    // ¡Olvidaste incrementar!
}

// Correcto
while (contador < 10)
{
    MessageBox.Show("Contador: " + contador);
    contador++;  // ← Esencial
}

Desafíos (Opcional)

Desafío 1: Generador de secuencia Fibonacci

Dificultad: Medio

Genera los primeros 10 números de la secuencia Fibonacci (1, 1, 2, 3, 5, 8, 13, 21, 34, 55) usando un bucle for.

Pista

Necesitas dos variables para almacenar los dos números anteriores, y en cada iteración calculas el siguiente sumando los dos anteriores.


Desafío 2: Calculadora de costos por tipo de componente

Dificultad: Medio

Tienes arrays de: componentesID, tiposComponente (K, Q, F, M), precios. Calcula el costo total por cada tipo de componente usando bucles anidados.


Desafío 3: Validador de proyecto con múltiples criterios

Dificultad: Alta

Crea un script que procese una lista de páginas del proyecto y valide:

  • Páginas numeradas correctamente (secuencia 1, 2, 3...)
  • Páginas sin nombre (error)
  • Páginas duplicadas

Usa diferentes tipos de bucles según convenga y genera reporte detallado.


Recursos adicionales


Preguntas frecuentes

P: ¿Cuándo usar for y cuándo foreach?

R: Usa foreach cuando solo necesites recorrer todos los elementos (más simple y legible). Usa for cuando necesites el índice, quieras saltar elementos, o modificar el array.

P: ¿Puedo usar break en un bucle anidado para salir de ambos?

R: No, break solo sale del bucle más interno. Para salir de ambos, usa una variable bandera o una función con return.

P: ¿Qué pasa si el array está vacío en un foreach?

R: El bucle simplemente no ejecuta ninguna iteración (no da error).

P: ¿Cuántos niveles de anidación son recomendables?

R: Máximo 2-3 niveles. Si necesitas más, considera refactorizar usando funciones.

P: ¿do-while se usa mucho?

R: No, es el menos común. Se usa principalmente para menús que deben mostrarse al menos una vez, o validaciones que deben ejecutarse mínimo una vez.


Checklist de completitud

Antes de pasar al siguiente capítulo, asegúrate de:

  • Entender la diferencia entre for, foreach, while y do-while
  • Saber cuándo usar cada tipo de bucle
  • Poder usar break y continue correctamente
  • Entender cómo funcionan los bucles anidados
  • Haber ejecutado exitosamente el procesador de componentes
  • Poder modificar los arrays de entrada y ver diferentes resultados
  • Evitar bucles infinitos accidentales
  • (Opcional) Haber intentado al menos un desafío

Última actualización: Enero 2025 Tiempo de lectura estimado: 50-60 minutos Código de ejemplo: code/cap-07/