Capítulo 10: Clases, objetos y POO en C#

6 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:

  • Entender qué es una clase y qué es un objeto (plano vs instancia)
  • Crear clases propias con propiedades y métodos
  • Usar constructores para inicializar objetos
  • Instanciar objetos con la palabra clave new
  • Aplicar encapsulación básica con public/private
  • Comprender los conceptos de POO necesarios para trabajar con EPLAN API
  • Construir un sistema completo de gestión de componentes eléctricos usando POO

📋 Requisitos previos

Antes de comenzar este capítulo debes:

  • ✅ Haber completado el Capítulo 9
  • ✅ Entender funciones y métodos
  • ✅ Saber trabajar con colecciones (List, Dictionary)
  • ✅ Conocer tipos de datos básicos (string, int, double, bool)

🏆 Proyecto del capítulo

Sistema de Gestión de Componentes Eléctricos con POO

Al final de este capítulo habrás construido un sistema orientado a objetos completo para:

  • Modelar componentes eléctricos (Contactor, Guardamotor, Motor)
  • Crear circuitos con múltiples componentes
  • Calcular parámetros eléctricos (corriente total, potencia)
  • Validar compatibilidad de componentes
  • Generar reportes técnicos

Tiempo estimado: 70-80 minutos

⚠️ Nota Importante: En este capítulo, crearemos los objetos (contactores, motores, etc.) manualmente en el código instanciando las clases. Esto es intencional para comprender los fundamentos de la Programación Orientada a Objetos (POO). En capítulos posteriores, aprenderemos a crear estos objetos automáticamente a partir de los datos reales de EPLAN.


📚 Contenido

1. Introducción: ¿Por qué necesitamos POO?

Hasta ahora has trabajado con variables individuales y colecciones. Pero, ¿cómo representamos un componente eléctrico completo con todos sus datos y comportamientos?

Enfoque sin POO (código difícil de mantener):

// Contactor K1
string k1_designacion = "K1";
string k1_codigo = "3RT2026-1BB40";
double k1_corriente = 25.0;
double k1_voltaje = 400.0;
string k1_fabricante = "Siemens";

// Contactor K2
string k2_designacion = "K2";
string k2_codigo = "3RT2036-1BB40";
double k2_corriente = 40.0;
double k2_voltaje = 400.0;
string k2_fabricante = "Siemens";

// ¿Y si necesitamos 100 contactores?
// ¿Cómo agrupamos los datos de cada componente?
// ¿Cómo calculamos la corriente total?

Enfoque con POO (código organizado y escalable):

// Crear clase Contactor
public class Contactor
{
    public string Designacion { get; set; }
    public string Codigo { get; set; }
    public double Corriente { get; set; }
    public double Voltaje { get; set; }
    public string Fabricante { get; set; }

    public string ObtenerDescripcion()
    {
        return string.Format("{0} - {1} ({2}A, {3}V)",
            Designacion, Codigo, Corriente, Voltaje);
    }
}

// Crear objetos (instancias)
Contactor k1 = new Contactor();
k1.Designacion = "K1";
k1.Codigo = "3RT2026-1BB40";
k1.Corriente = 25.0;
k1.Voltaje = 400.0;
k1.Fabricante = "Siemens";

MessageBox.Show(k1.ObtenerDescripcion());
// "K1 - 3RT2026-1BB40 (25A, 400V)"

Ventajas de POO:

  • Organización: Datos y funciones relacionados juntos
  • Reutilización: Crea plantillas (clases) y úsalas múltiples veces
  • Mantenibilidad: Cambios centralizados
  • Escalabilidad: Fácil agregar nuevas funcionalidades
  • Claridad: El código se lee como el mundo real

2. Conceptos fundamentales

2.1 Clase vs Objeto

Clase: Es el plano o plantilla que define cómo será algo.

Objeto: Es una instancia concreta creada a partir de la clase.

Analogía con planos arquitectónicos:

Clase Contactor       →  Plano de un contactor (especificación)
   ↓ instanciar
Objeto k1             →  Contactor físico instalado en el tablero
Objeto k2             →  Otro contactor físico del mismo tipo

Ejemplo visual:

// CLASE: Plano (definición)
public class Contactor
{
    // Propiedades (datos)
    public string Designacion { get; set; }
    public double Corriente { get; set; }

    // Métodos (comportamientos)
    public void Activar()
    {
        MessageBox.Show(Designacion + " activado");
    }
}

// OBJETOS: Instancias concretas
Contactor k1 = new Contactor();  // Primer contactor
Contactor k2 = new Contactor();  // Segundo contactor
Contactor k3 = new Contactor();  // Tercer contactor

// Cada objeto tiene sus propios valores
k1.Designacion = "K1";
k1.Corriente = 25.0;

k2.Designacion = "K2";
k2.Corriente = 40.0;

2.2 Anatomía de una clase

public class Contactor
{
    // =====================================
    // PROPIEDADES (Datos/Atributos)
    // =====================================
    public string Designacion { get; set; }
    public string Codigo { get; set; }
    public double Corriente { get; set; }
    public double Voltaje { get; set; }

    // Propiedad privada (solo accesible dentro de la clase)
    private bool estaActivado;

    // =====================================
    // CONSTRUCTOR (Inicialización)
    // =====================================
    public Contactor(string designacion, double corriente)
    {
        this.Designacion = designacion;
        this.Corriente = corriente;
        this.estaActivado = false;
    }

    // =====================================
    // MÉTODOS (Comportamientos/Funciones)
    // =====================================
    public void Activar()
    {
        estaActivado = true;
        MessageBox.Show(Designacion + " activado");
    }

    public double CalcularPotencia(double voltaje)
    {
        return Corriente * voltaje;
    }

    public string ObtenerEstado()
    {
        return estaActivado ? "Activado" : "Desactivado";
    }
}

3. Propiedades

Las propiedades almacenan los datos de un objeto.

3.1 Propiedades automáticas (más común)

public class Componente
{
    // Sintaxis simplificada
    public string Designacion { get; set; }
    public double Corriente { get; set; }
    public string Fabricante { get; set; }
}

// Uso
Componente comp = new Componente();
comp.Designacion = "K1";              // set
string nombre = comp.Designacion;     // get

Explicación:

  • { get; set; } crea automáticamente getters y setters
  • get: Permite leer el valor
  • set: Permite escribir el valor

3.2 Propiedades de solo lectura

public class Componente
{
    // Solo se puede leer, no modificar desde fuera
    public string Codigo { get; private set; }

    public Componente(string codigo)
    {
        this.Codigo = codigo;  // Solo se asigna en el constructor
    }
}

// Uso
Componente comp = new Componente("3RT2026-1BB40");
MessageBox.Show(comp.Codigo);     // ✓ OK - lectura permitida
comp.Codigo = "otro";             // ❌ ERROR - escritura no permitida

3.3 Propiedades calculadas

public class Motor
{
    public double Potencia { get; set; }       // kW
    public double Voltaje { get; set; }        // V
    public double FactorPotencia { get; set; } // cosφ

    // Propiedad calculada (sin almacenar valor)
    public double CorrienteNominal
    {
        get
        {
            // I = P / (√3 × V × cosφ)
            return (Potencia * 1000) / (1.732 * Voltaje * FactorPotencia);
        }
    }
}

// Uso
Motor m1 = new Motor();
m1.Potencia = 7.5;         // kW
m1.Voltaje = 400.0;        // V
m1.FactorPotencia = 0.85;  // cosφ

double corriente = m1.CorrienteNominal;  // Se calcula automáticamente
MessageBox.Show("Corriente nominal: " + corriente.ToString("F2") + "A");
// "Corriente nominal: 12.77A"

3.4 Valores por defecto

public class Guardamotor
{
    public string Designacion { get; set; }
    public double Corriente { get; set; }

    // Valores por defecto
    public string Tipo { get; set; } = "Guardamotor";
    public int Polos { get; set; } = 3;
    public bool ConAuxiliar { get; set; } = false;
}

// Uso
Guardamotor q1 = new Guardamotor();
MessageBox.Show(q1.Tipo);      // "Guardamotor"
MessageBox.Show(q1.Polos.ToString());  // "3"

4. Constructores

El constructor es un método especial que se ejecuta al crear un objeto con new.

4.1 Constructor sin parámetros (por defecto)

public class Contactor
{
    public string Designacion { get; set; }
    public double Corriente { get; set; }

    // Constructor vacío (opcional, se crea automáticamente si no lo defines)
    public Contactor()
    {
        // Inicialización vacía
    }
}

// Uso
Contactor k1 = new Contactor();
k1.Designacion = "K1";
k1.Corriente = 25.0;

4.2 Constructor con parámetros (recomendado)

public class Contactor
{
    public string Designacion { get; set; }
    public double Corriente { get; set; }
    public double Voltaje { get; set; }

    // Constructor con parámetros
    public Contactor(string designacion, double corriente, double voltaje)
    {
        this.Designacion = designacion;
        this.Corriente = corriente;
        this.Voltaje = voltaje;
    }
}

// Uso (más limpio y seguro)
Contactor k1 = new Contactor("K1", 25.0, 400.0);
MessageBox.Show(k1.Designacion);  // "K1"

La palabra clave this:

  • this.Designacion se refiere a la propiedad del objeto
  • designacion (sin this) se refiere al parámetro del constructor

4.3 Múltiples constructores (sobrecarga)

public class Contactor
{
    public string Designacion { get; set; }
    public string Codigo { get; set; }
    public double Corriente { get; set; }
    public double Voltaje { get; set; }

    // Constructor 1: Solo designación y corriente
    public Contactor(string designacion, double corriente)
    {
        this.Designacion = designacion;
        this.Corriente = corriente;
        this.Voltaje = 400.0;  // Valor por defecto
    }

    // Constructor 2: Todos los parámetros
    public Contactor(string designacion, string codigo, double corriente, double voltaje)
    {
        this.Designacion = designacion;
        this.Codigo = codigo;
        this.Corriente = corriente;
        this.Voltaje = voltaje;
    }
}

// Uso de diferentes constructores
Contactor k1 = new Contactor("K1", 25.0);
Contactor k2 = new Contactor("K2", "3RT2036-1BB40", 40.0, 400.0);

4.4 Llamar a otro constructor (this)

public class Contactor
{
    public string Designacion { get; set; }
    public double Corriente { get; set; }
    public double Voltaje { get; set; }

    // Constructor principal
    public Contactor(string designacion, double corriente, double voltaje)
    {
        this.Designacion = designacion;
        this.Corriente = corriente;
        this.Voltaje = voltaje;
    }

    // Constructor simplificado que llama al principal
    public Contactor(string designacion, double corriente)
        : this(designacion, corriente, 400.0)  // Llama al constructor principal
    {
        // Voltaje = 400V por defecto
    }
}

5. Métodos

Los métodos son las funciones que definen el comportamiento de un objeto.

5.1 Métodos sin retorno (void)

public class Contactor
{
    public string Designacion { get; set; }
    private bool estaActivado;

    public void Activar()
    {
        estaActivado = true;
        MessageBox.Show(Designacion + " activado");
    }

    public void Desactivar()
    {
        estaActivado = false;
        MessageBox.Show(Designacion + " desactivado");
    }
}

// Uso
Contactor k1 = new Contactor();
k1.Designacion = "K1";
k1.Activar();      // Muestra mensaje
k1.Desactivar();   // Muestra mensaje

5.2 Métodos con retorno

public class Motor
{
    public double Potencia { get; set; }  // kW
    public double Voltaje { get; set; }   // V

    // Método que retorna un valor
    public double CalcularCorriente(double factorPotencia)
    {
        // I = P / (√3 × V × cosφ)
        double corriente = (Potencia * 1000) / (1.732 * Voltaje * factorPotencia);
        return corriente;
    }

    public string ObtenerClasificacion()
    {
        if (Potencia < 1.0)
            return "Motor pequeño";
        else if (Potencia < 10.0)
            return "Motor mediano";
        else
            return "Motor grande";
    }
}

// Uso
Motor m1 = new Motor();
m1.Potencia = 7.5;
m1.Voltaje = 400.0;

double corriente = m1.CalcularCorriente(0.85);
MessageBox.Show("Corriente: " + corriente.ToString("F2") + "A");

string clasificacion = m1.ObtenerClasificacion();
MessageBox.Show("Clasificación: " + clasificacion);  // "Motor mediano"

5.3 Métodos con parámetros opcionales

public class Componente
{
    public string Designacion { get; set; }
    public double Corriente { get; set; }

    // Parámetros opcionales con valores por defecto
    public string GenerarEtiqueta(bool incluirCorriente = true, string prefijo = "")
    {
        string etiqueta = prefijo + Designacion;

        if (incluirCorriente)
        {
            etiqueta += " (" + Corriente + "A)";
        }

        return etiqueta;
    }
}

// Uso
Componente c = new Componente();
c.Designacion = "K1";
c.Corriente = 25.0;

MessageBox.Show(c.GenerarEtiqueta());                    // "K1 (25A)"
MessageBox.Show(c.GenerarEtiqueta(false));               // "K1"
MessageBox.Show(c.GenerarEtiqueta(true, "CONT-"));       // "CONT-K1 (25A)"
MessageBox.Show(c.GenerarEtiqueta(prefijo: "CONT-"));    // "CONT-K1 (25A)"

6. Encapsulación: public vs private

La encapsulación es el principio de ocultar detalles internos y exponer solo lo necesario.

6.1 Modificadores de acceso

public class Contactor
{
    // PUBLIC: Accesible desde fuera de la clase
    public string Designacion { get; set; }
    public double Corriente { get; set; }

    // PRIVATE: Solo accesible dentro de la clase
    private bool estaActivado;
    private int contadorActivaciones;

    public void Activar()
    {
        estaActivado = true;              // ✓ OK - acceso interno
        contadorActivaciones++;           // ✓ OK - acceso interno
        MessageBox.Show(Designacion + " activado");
    }

    public int ObtenerContadorActivaciones()
    {
        return contadorActivaciones;      // ✓ OK - retornar valor privado
    }
}

// Uso
Contactor k1 = new Contactor();
k1.Designacion = "K1";           // ✓ OK - public
k1.Activar();                    // ✓ OK - public

int contador = k1.ObtenerContadorActivaciones();  // ✓ OK - método public
MessageBox.Show("Activaciones: " + contador);

// k1.estaActivado = true;       // ❌ ERROR - private no accesible
// k1.contadorActivaciones++;    // ❌ ERROR - private no accesible

6.2 ¿Por qué usar private?

Ventajas de la encapsulación:

public class Motor
{
    public double Potencia { get; set; }     // kW

    // Corriente es privada y se calcula automáticamente
    private double corrienteNominal;

    // Método público para establecer potencia y calcular corriente
    public void EstablecerPotencia(double potencia, double voltaje, double fp)
    {
        this.Potencia = potencia;

        // Cálculo interno (encapsulado)
        this.corrienteNominal = (potencia * 1000) / (1.732 * voltaje * fp);
    }

    // Método público para obtener corriente
    public double ObtenerCorriente()
    {
        return corrienteNominal;
    }
}

Beneficios:

  • ✅ Protege datos sensibles
  • ✅ Evita modificaciones incorrectas
  • ✅ Centraliza lógica de validación
  • ✅ Permite cambiar implementación interna sin afectar código externo

7. La palabra clave this

this se refiere al objeto actual.

7.1 Distinguir entre parámetros y propiedades

public class Contactor
{
    public string Designacion { get; set; }
    public double Corriente { get; set; }

    public Contactor(string Designacion, double Corriente)
    {
        // Sin this: ambigüedad
        // Designacion = Designacion;  // ¿Cuál es cuál?

        // Con this: claridad
        this.Designacion = Designacion;  // this.Designacion = propiedad
        this.Corriente = Corriente;      // Corriente = parámetro
    }
}

7.2 Pasar el objeto actual a otro método

public class Circuito
{
    private List<Contactor> contactores;

    public void AgregarContactor(Contactor contactor)
    {
        contactores.Add(contactor);
    }
}

public class Contactor
{
    public string Designacion { get; set; }

    public void RegistrarEnCircuito(Circuito circuito)
    {
        circuito.AgregarContactor(this);  // Pasar el objeto actual
    }
}

// Uso
Circuito circuito1 = new Circuito();
Contactor k1 = new Contactor();
k1.Designacion = "K1";
k1.RegistrarEnCircuito(circuito1);  // k1 se agrega a sí mismo al circuito

💻 Manos a la obra: Construyendo el proyecto

Vamos a crear un Sistema de Gestión de Componentes Eléctricos completo usando POO.

Paso 1: Clase base ComponenteElectrico

public class ComponenteElectrico
{
    // ========================================
    // PROPIEDADES
    // ========================================
    public string Designacion { get; set; }
    public string Codigo { get; set; }
    public string Tipo { get; set; }
    public double Corriente { get; set; }      // A
    public double Voltaje { get; set; }        // V
    public string Fabricante { get; set; }

    // Propiedad calculada
    public double Potencia
    {
        get
        {
            return Corriente * Voltaje;  // P = I × V (aproximado)
        }
    }

    // ========================================
    // CONSTRUCTOR
    // ========================================
    public ComponenteElectrico(string designacion, string codigo, string tipo,
                               double corriente, double voltaje, string fabricante)
    {
        this.Designacion = designacion;
        this.Codigo = codigo;
        this.Tipo = tipo;
        this.Corriente = corriente;
        this.Voltaje = voltaje;
        this.Fabricante = fabricante;
    }

    // Constructor simplificado
    public ComponenteElectrico(string designacion, double corriente)
    {
        this.Designacion = designacion;
        this.Corriente = corriente;
        this.Voltaje = 400.0;  // Por defecto
        this.Tipo = "Genérico";
    }

    // ========================================
    // MÉTODOS
    // ========================================
    public string ObtenerDescripcionCompleta()
    {
        return string.Format("{0} [{1}]\n  Código: {2}\n  Corriente: {3}A\n  Voltaje: {4}V\n  Potencia: {5:F2}W\n  Fabricante: {6}",
            Designacion, Tipo, Codigo ?? "N/A", Corriente, Voltaje, Potencia, Fabricante ?? "N/A");
    }

    public string ObtenerDescripcionCorta()
    {
        return string.Format("{0} - {1} ({2}A, {3}V)",
            Designacion, Tipo, Corriente, Voltaje);
    }

    public bool EsCompatibleConVoltaje(double voltajeRequerido)
    {
        // Tolerancia del 10%
        double tolerancia = voltajeRequerido * 0.10;
        return Math.Abs(Voltaje - voltajeRequerido) <= tolerancia;
    }

    public void MostrarInformacion()
    {
        MessageBox.Show(ObtenerDescripcionCompleta(), "Información del Componente");
    }
}

¿Qué acabamos de hacer?

Creamos una clase base ComponenteElectrico que:

  • Almacena datos eléctricos básicos (corriente, voltaje, código)
  • Calcula potencia automáticamente
  • Tiene dos constructores (completo y simplificado)
  • Provee métodos para obtener descripciones formateadas
  • Valida compatibilidad de voltaje

Paso 2: Clase Contactor (especializada)

public class Contactor : ComponenteElectrico
{
    // ========================================
    // PROPIEDADES ESPECÍFICAS
    // ========================================
    public int Polos { get; set; }
    public string CategoriaUso { get; set; }  // AC1, AC3, AC4
    private bool estaActivado;
    private int contadorActivaciones;

    // ========================================
    // CONSTRUCTOR
    // ========================================
    public Contactor(string designacion, string codigo, double corrienteAC3,
                     double voltaje, int polos, string fabricante)
        : base(designacion, codigo, "Contactor", corrienteAC3, voltaje, fabricante)
    {
        this.Polos = polos;
        this.CategoriaUso = "AC3";
        this.estaActivado = false;
        this.contadorActivaciones = 0;
    }

    // ========================================
    // MÉTODOS ESPECÍFICOS
    // ========================================
    public void Activar()
    {
        if (!estaActivado)
        {
            estaActivado = true;
            contadorActivaciones++;
            MessageBox.Show(string.Format("Contactor {0} ACTIVADO\nActivaciones totales: {1}",
                Designacion, contadorActivaciones));
        }
        else
        {
            MessageBox.Show(Designacion + " ya está activado");
        }
    }

    public void Desactivar()
    {
        if (estaActivado)
        {
            estaActivado = false;
            MessageBox.Show(Designacion + " desactivado");
        }
    }

    public string ObtenerEstado()
    {
        return estaActivado ? "ACTIVADO" : "DESACTIVADO";
    }

    public int ObtenerContadorActivaciones()
    {
        return contadorActivaciones;
    }
}

Paso 3: Clase Circuito (contenedor)

public class Circuito
{
    // ========================================
    // PROPIEDADES
    // ========================================
    public string Nombre { get; set; }
    public double VoltajeNominal { get; set; }
    private List<ComponenteElectrico> componentes;

    // ========================================
    // CONSTRUCTOR
    // ========================================
    public Circuito(string nombre, double voltajeNominal)
    {
        this.Nombre = nombre;
        this.VoltajeNominal = voltajeNominal;
        this.componentes = new List<ComponenteElectrico>();
    }

    // ========================================
    // MÉTODOS DE GESTIÓN
    // ========================================
    public bool AgregarComponente(ComponenteElectrico componente)
    {
        // Validar compatibilidad de voltaje
        if (!componente.EsCompatibleConVoltaje(VoltajeNominal))
        {
            MessageBox.Show(string.Format("Error: {0} no es compatible con {1}V\n(Voltaje del componente: {2}V)",
                componente.Designacion, VoltajeNominal, componente.Voltaje),
                "Voltaje incompatible");
            return false;
        }

        componentes.Add(componente);
        MessageBox.Show(string.Format("{0} agregado al circuito {1}",
            componente.Designacion, Nombre));
        return true;
    }

    public void EliminarComponente(string designacion)
    {
        ComponenteElectrico comp = componentes.Find(c => c.Designacion == designacion);

        if (comp != null)
        {
            componentes.Remove(comp);
            MessageBox.Show(designacion + " eliminado del circuito");
        }
        else
        {
            MessageBox.Show(designacion + " no encontrado");
        }
    }

    public ComponenteElectrico BuscarComponente(string designacion)
    {
        return componentes.Find(c => c.Designacion == designacion);
    }

    // ========================================
    // MÉTODOS DE CÁLCULO
    // ========================================
    public double CalcularCorrienteTotal()
    {
        double total = 0;
        foreach (ComponenteElectrico comp in componentes)
        {
            total += comp.Corriente;
        }
        return total;
    }

    public double CalcularPotenciaTotal()
    {
        double total = 0;
        foreach (ComponenteElectrico comp in componentes)
        {
            total += comp.Potencia;
        }
        return total;
    }

    public int ObtenerCantidadComponentes()
    {
        return componentes.Count;
    }

    // ========================================
    // MÉTODOS DE REPORTE
    // ========================================
    public void MostrarResumen()
    {
        string reporte = "=======================================\n";
        reporte += string.Format("CIRCUITO: {0}\n", Nombre);
        reporte += string.Format("Voltaje nominal: {0}V\n", VoltajeNominal);
        reporte += "=======================================\n\n";
        reporte += string.Format("Componentes: {0}\n", componentes.Count);
        reporte += string.Format("Corriente total: {0:F2}A\n", CalcularCorrienteTotal());
        reporte += string.Format("Potencia total: {0:F2}W\n\n", CalcularPotenciaTotal());

        reporte += "LISTA DE COMPONENTES:\n";
        reporte += "---------------------------------------\n";

        foreach (ComponenteElectrico comp in componentes)
        {
            reporte += comp.ObtenerDescripcionCorta() + "\n";
        }

        MessageBox.Show(reporte, "Resumen del Circuito");
    }

    public void MostrarDetalleCompleto()
    {
        string reporte = string.Format("CIRCUITO: {0} ({1}V)\n\n", Nombre, VoltajeNominal);

        if (componentes.Count == 0)
        {
            reporte += "No hay componentes en este circuito.";
        }
        else
        {
            foreach (ComponenteElectrico comp in componentes)
            {
                reporte += comp.ObtenerDescripcionCompleta();
                reporte += "\n\n---------------------------------------\n\n";
            }
        }

        MessageBox.Show(reporte, "Detalle Completo del Circuito");
    }
}

Código completo del proyecto

//
// Proyecto: Sistema de Gestión de Componentes Eléctricos con POO
// Capítulo: 10
// Descripción: Sistema completo para gestionar componentes eléctricos usando
//              programación orientada a objetos (clases, propiedades, métodos,
//              constructores, encapsulación)
// Autor: C.D. López
// Fecha: Enero 2025
//

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

// ========================================
// CLASE BASE: ComponenteElectrico
// ========================================
public class ComponenteElectrico
{
    // Propiedades
    public string Designacion { get; set; }
    public string Codigo { get; set; }
    public string Tipo { get; set; }
    public double Corriente { get; set; }
    public double Voltaje { get; set; }
    public string Fabricante { get; set; }

    // Propiedad calculada
    public double Potencia
    {
        get { return Corriente * Voltaje; }
    }

    // Constructores
    public ComponenteElectrico(string designacion, string codigo, string tipo,
                               double corriente, double voltaje, string fabricante)
    {
        this.Designacion = designacion;
        this.Codigo = codigo;
        this.Tipo = tipo;
        this.Corriente = corriente;
        this.Voltaje = voltaje;
        this.Fabricante = fabricante;
    }

    public ComponenteElectrico(string designacion, double corriente)
    {
        this.Designacion = designacion;
        this.Corriente = corriente;
        this.Voltaje = 400.0;
        this.Tipo = "Genérico";
    }

    // Métodos
    public string ObtenerDescripcionCompleta()
    {
        return string.Format("{0} [{1}]\n  Código: {2}\n  Corriente: {3}A\n  Voltaje: {4}V\n  Potencia: {5:F2}W\n  Fabricante: {6}",
            Designacion, Tipo, Codigo ?? "N/A", Corriente, Voltaje, Potencia, Fabricante ?? "N/A");
    }

    public string ObtenerDescripcionCorta()
    {
        return string.Format("{0} - {1} ({2}A, {3}V)",
            Designacion, Tipo, Corriente, Voltaje);
    }

    public bool EsCompatibleConVoltaje(double voltajeRequerido)
    {
        double tolerancia = voltajeRequerido * 0.10;
        return Math.Abs(Voltaje - voltajeRequerido) <= tolerancia;
    }

    public void MostrarInformacion()
    {
        MessageBox.Show(ObtenerDescripcionCompleta(), "Información del Componente");
    }
}

// ========================================
// CLASE ESPECIALIZADA: Contactor
// ========================================
public class Contactor : ComponenteElectrico
{
    public int Polos { get; set; }
    public string CategoriaUso { get; set; }
    private bool estaActivado;
    private int contadorActivaciones;

    public Contactor(string designacion, string codigo, double corrienteAC3,
                     double voltaje, int polos, string fabricante)
        : base(designacion, codigo, "Contactor", corrienteAC3, voltaje, fabricante)
    {
        this.Polos = polos;
        this.CategoriaUso = "AC3";
        this.estaActivado = false;
        this.contadorActivaciones = 0;
    }

    public void Activar()
    {
        if (!estaActivado)
        {
            estaActivado = true;
            contadorActivaciones++;
            MessageBox.Show(string.Format("Contactor {0} ACTIVADO\nActivaciones totales: {1}",
                Designacion, contadorActivaciones));
        }
        else
        {
            MessageBox.Show(Designacion + " ya está activado");
        }
    }

    public void Desactivar()
    {
        if (estaActivado)
        {
            estaActivado = false;
            MessageBox.Show(Designacion + " desactivado");
        }
    }

    public string ObtenerEstado()
    {
        return estaActivado ? "ACTIVADO" : "DESACTIVADO";
    }

    public int ObtenerContadorActivaciones()
    {
        return contadorActivaciones;
    }
}

// ========================================
// CLASE CONTENEDOR: Circuito
// ========================================
public class Circuito
{
    public string Nombre { get; set; }
    public double VoltajeNominal { get; set; }
    private List<ComponenteElectrico> componentes;

    public Circuito(string nombre, double voltajeNominal)
    {
        this.Nombre = nombre;
        this.VoltajeNominal = voltajeNominal;
        this.componentes = new List<ComponenteElectrico>();
    }

    public bool AgregarComponente(ComponenteElectrico componente)
    {
        if (!componente.EsCompatibleConVoltaje(VoltajeNominal))
        {
            MessageBox.Show(string.Format("Error: {0} no es compatible con {1}V\n(Voltaje del componente: {2}V)",
                componente.Designacion, VoltajeNominal, componente.Voltaje),
                "Voltaje incompatible");
            return false;
        }

        componentes.Add(componente);
        MessageBox.Show(string.Format("{0} agregado al circuito {1}",
            componente.Designacion, Nombre));
        return true;
    }

    public void EliminarComponente(string designacion)
    {
        ComponenteElectrico comp = componentes.Find(c => c.Designacion == designacion);

        if (comp != null)
        {
            componentes.Remove(comp);
            MessageBox.Show(designacion + " eliminado del circuito");
        }
        else
        {
            MessageBox.Show(designacion + " no encontrado");
        }
    }

    public ComponenteElectrico BuscarComponente(string designacion)
    {
        return componentes.Find(c => c.Designacion == designacion);
    }

    public double CalcularCorrienteTotal()
    {
        double total = 0;
        foreach (ComponenteElectrico comp in componentes)
        {
            total += comp.Corriente;
        }
        return total;
    }

    public double CalcularPotenciaTotal()
    {
        double total = 0;
        foreach (ComponenteElectrico comp in componentes)
        {
            total += comp.Potencia;
        }
        return total;
    }

    public int ObtenerCantidadComponentes()
    {
        return componentes.Count;
    }

    public void MostrarResumen()
    {
        string reporte = "=======================================\n";
        reporte += string.Format("CIRCUITO: {0}\n", Nombre);
        reporte += string.Format("Voltaje nominal: {0}V\n", VoltajeNominal);
        reporte += "=======================================\n\n";
        reporte += string.Format("Componentes: {0}\n", componentes.Count);
        reporte += string.Format("Corriente total: {0:F2}A\n", CalcularCorrienteTotal());
        reporte += string.Format("Potencia total: {0:F2}W\n\n", CalcularPotenciaTotal());

        reporte += "LISTA DE COMPONENTES:\n";
        reporte += "---------------------------------------\n";

        foreach (ComponenteElectrico comp in componentes)
        {
            reporte += comp.ObtenerDescripcionCorta() + "\n";
        }

        MessageBox.Show(reporte, "Resumen del Circuito");
    }

    public void MostrarDetalleCompleto()
    {
        string reporte = string.Format("CIRCUITO: {0} ({1}V)\n\n", Nombre, VoltajeNominal);

        if (componentes.Count == 0)
        {
            reporte += "No hay componentes en este circuito.";
        }
        else
        {
            foreach (ComponenteElectrico comp in componentes)
            {
                reporte += comp.ObtenerDescripcionCompleta();
                reporte += "\n\n---------------------------------------\n\n";
            }
        }

        MessageBox.Show(reporte, "Detalle Completo del Circuito");
    }
}

// ========================================
// SCRIPT PRINCIPAL DE DEMOSTRACIÓN
// ========================================
public class GestionComponentesPOO
{
    [Start]
    public void Ejecutar()
    {
        try
        {
            // Crear circuito
            Circuito circuitoPrincipal = new Circuito("Circuito Motor Principal", 400.0);

            // Crear componentes
            Contactor k1 = new Contactor("K1", "3RT2026-1BB40", 25.0, 400.0, 3, "Siemens");
            Contactor k2 = new Contactor("K2", "3RT2036-1BB40", 40.0, 400.0, 3, "Siemens");

            ComponenteElectrico q1 = new ComponenteElectrico("Q1", "3RV2011-1JA10", "Guardamotor",
                                                             4.5, 400.0, "Siemens");

            ComponenteElectrico m1 = new ComponenteElectrico("M1", 15.0);  // Constructor simple
            m1.Tipo = "Motor";
            m1.Fabricante = "WEG";

            // Agregar componentes al circuito
            circuitoPrincipal.AgregarComponente(k1);
            circuitoPrincipal.AgregarComponente(k2);
            circuitoPrincipal.AgregarComponente(q1);
            circuitoPrincipal.AgregarComponente(m1);

            // Mostrar resumen
            circuitoPrincipal.MostrarResumen();

            // Activar contactores
            k1.Activar();
            k1.Activar();  // Intento de activar de nuevo
            k2.Activar();

            // Buscar y mostrar componente específico
            ComponenteElectrico componenteBuscado = circuitoPrincipal.BuscarComponente("Q1");
            if (componenteBuscado != null)
            {
                componenteBuscado.MostrarInformacion();
            }

            // Mostrar detalle completo
            circuitoPrincipal.MostrarDetalleCompleto();

            // Intentar agregar componente incompatible (220V en circuito 400V)
            ComponenteElectrico x1 = new ComponenteElectrico("X1", "XYZ-123", "Relé",
                                                             2.0, 220.0, "Generic");
            circuitoPrincipal.AgregarComponente(x1);  // Debería fallar

            // Mostrar contadores de activaciones
            MessageBox.Show(string.Format("K1 activado {0} veces\nK2 activado {1} veces",
                k1.ObtenerContadorActivaciones(),
                k2.ObtenerContadorActivaciones()));

        }
        catch (Exception ex)
        {
            MessageBox.Show("Error: " + ex.Message, "Error en la ejecución");
        }
    }
}

🔍 Deep Dive: Entendiendo en profundidad

Herencia (adelanto)

En el código usamos herencia cuando Contactor : ComponenteElectrico.

public class Contactor : ComponenteElectrico
{
    // Hereda todas las propiedades y métodos de ComponenteElectrico
    // Puede agregar propiedades y métodos específicos
}

Beneficios:

  • ✅ Reutilización de código
  • ✅ Jerarquía lógica (especialización)
  • ✅ Polimorfismo (veremos en capítulos avanzados)

Propiedades vs Campos

Campo (field):

private bool estaActivado;  // Almacenamiento directo

Propiedad (property):

public string Designacion { get; set; }  // Con getters/setters automáticos

Cuándo usar cada uno:

  • Propiedades públicas: Para datos que se acceden desde fuera
  • Campos privados: Para datos internos que no se exponen

El operador ??

Codigo ?? "N/A"

Significa: "Si Codigo es null, usa "N/A" en su lugar".

Mejores prácticas

  • Hacer: Nombrar clases con sustantivos (Contactor, Motor)
  • Hacer: Nombrar métodos con verbos (Activar(), Calcular())
  • Hacer: Usar constructores para inicializar objetos correctamente
  • Hacer: Encapsular (private) datos internos
  • Hacer: Validar datos en constructores y métodos públicos
  • Evitar: Exponer todos los campos como public
  • Evitar: Constructores sin validación
  • Evitar: Métodos que hacen demasiadas cosas (responsabilidad única)

Errores comunes

Error 1: Olvidar new al crear objetos

Síntoma: Error "Use of unassigned variable"

Causa: No instanciar el objeto

// ❌ MAL
Contactor k1;
k1.Designacion = "K1";  // ERROR: k1 no está inicializado

// ✓ BIEN
Contactor k1 = new Contactor("K1", "3RT2026", 25.0, 400.0, 3, "Siemens");
k1.Designacion = "K1";  // OK

Error 2: Confundir clase con objeto

Síntoma: Error "An object reference is required..."

Causa: Intentar llamar métodos de instancia en la clase

// ❌ MAL
Contactor.Activar();  // ERROR: Contactor es la clase, no un objeto

// ✓ BIEN
Contactor k1 = new Contactor(...);
k1.Activar();  // OK: k1 es un objeto

Error 3: NullReferenceException

Síntoma: Error "Object reference not set to an instance of an object"

Causa: Usar un objeto que es null

Circuito circuito = null;
circuito.AgregarComponente(k1);  // ❌ ERROR: circuito es null

// Solución: Siempre inicializar
Circuito circuito = new Circuito("Principal", 400.0);
circuito.AgregarComponente(k1);  // ✓ OK

⚡ Desafíos (Opcional)

Desafío 1: Clase Motor con cálculo de corriente

Dificultad: ⭐☆☆

Crea una clase Motor que herede de ComponenteElectrico y que calcule automáticamente la corriente nominal basada en potencia, voltaje y factor de potencia.

💡 Pista

Usa la fórmula: I = P / (√3 × V × cosφ)

Crea una propiedad FactorPotencia y un método CalcularCorriente().

✅ Solución
public class Motor : ComponenteElectrico
{
    public double PotenciaKW { get; set; }
    public double FactorPotencia { get; set; }
    public int Polos { get; set; }
    public double Rpm { get; set; }

    public Motor(string designacion, double potenciaKW, double voltaje,
                 double factorPotencia, int polos, string fabricante)
        : base(designacion, "", "Motor", 0, voltaje, fabricante)
    {
        this.PotenciaKW = potenciaKW;
        this.FactorPotencia = factorPotencia;
        this.Polos = polos;

        // Calcular corriente automáticamente
        this.Corriente = CalcularCorrienteNominal();

        // Calcular RPM aproximadas (frecuencia 50Hz)
        this.Rpm = (120 * 50) / polos;
    }

    private double CalcularCorrienteNominal()
    {
        // I = P / (√3 × V × cosφ)
        return (PotenciaKW * 1000) / (1.732 * Voltaje * FactorPotencia);
    }

    public string ObtenerDatosMotor()
    {
        return string.Format("{0}\nPotencia: {1}kW\nRPM: {2}\nFactor de potencia: {3}\nCorriente nominal: {4:F2}A",
            Designacion, PotenciaKW, Rpm, FactorPotencia, Corriente);
    }
}

// Uso
Motor m1 = new Motor("M1", 7.5, 400.0, 0.85, 4, "WEG");
MessageBox.Show(m1.ObtenerDatosMotor());

Desafío 2: Clase Guardamotor con rango de regulación

Dificultad: ⭐⭐☆

Crea una clase Guardamotor con propiedades CorrienteMin y CorrienteMax (rango de regulación). Agrega un método que valide si una corriente dada está dentro del rango.

💡 Pista

Usa propiedades CorrienteMin y CorrienteMax, y crea método EsValidaParaMotor(double corrienteMotor).


Desafío 3: Exportar circuito a texto

Dificultad: ⭐⭐⭐

Agrega un método a la clase Circuito que exporte toda la información del circuito a un archivo de texto (.txt) con formato profesional.

💡 Pista

Usa System.IO.File.WriteAllText() y construye un string con formato tabular.


📝 Resumen

En este capítulo aprendiste:

  • Qué es POO: Programación orientada a objetos como forma de organizar código
  • Clase vs Objeto: Plano (clase) vs instancia concreta (objeto)
  • Propiedades: Almacenar datos del objeto (public/private, calculadas)
  • Constructores: Inicializar objetos correctamente con new
  • Métodos: Definir comportamientos del objeto
  • Encapsulación: Ocultar detalles internos (public/private)
  • Herencia básica: Reutilizar código con class Hijo : Padre
  • Proyecto construido: Sistema completo de gestión de componentes eléctricos

Conceptos clave:

  • Las clases son plantillas, los objetos son instancias
  • new crea un nuevo objeto a partir de la clase
  • Las propiedades almacenan datos, los métodos definen comportamientos
  • La encapsulación protege datos internos (private) y expone interfaz pública (public)
  • POO te prepara para trabajar con EPLAN API (que usa POO intensivamente)

🔗 Conexiones

⬅️ Capítulo anterior

Capítulo 9: Colecciones de datos

➡️ Próximo capítulo

Capítulo 11: Manejo de errores

En el próximo capítulo aprenderás a manejar errores y excepciones de forma robusta usando try-catch-finally. Construirás validadores que no fallan ante datos incorrectos y aprenderás logging profesional.


📚 Recursos adicionales


❓ Preguntas frecuentes

P: ¿Cuál es la diferencia entre una clase y un objeto?

R: Una clase es el plano o plantilla (ej. class Contactor). Un objeto es una instancia concreta creada con new (ej. Contactor k1 = new Contactor(...)). Puedes crear muchos objetos a partir de una sola clase.

P: ¿Cuándo debo usar public y cuándo private?

R: Usa public para propiedades y métodos que necesitan ser accesibles desde fuera de la clase. Usa private para datos internos que no deben ser modificados directamente. Regla general: expón solo lo necesario.

P: ¿Puedo tener múltiples constructores en una clase?

R: Sí, se llama sobrecarga de constructores. Cada constructor debe tener diferente número o tipo de parámetros.

P: ¿Qué es this y cuándo debo usarlo?

R: this se refiere al objeto actual. Úsalo para:

  1. Distinguir entre parámetros y propiedades con el mismo nombre
  2. Llamar a otro constructor desde un constructor
  3. Pasar el objeto actual a otro método

P: ¿Necesito entender POO avanzado para trabajar con EPLAN API?

R: No. Con lo aprendido en este capítulo (clases, objetos, propiedades, métodos, constructores) es suficiente para usar EPLAN API. Los conceptos avanzados (interfaces, polimorfismo, clases abstractas) son opcionales.

P: ¿Qué es la herencia y cómo funciona?

R: La herencia permite crear una clase nueva (hijo) basada en una clase existente (padre). La clase hijo hereda todas las propiedades y métodos del padre, y puede agregar los suyos propios o sobrescribirlos.

public class ComponenteElectrico { ... }  // Padre
public class Contactor : ComponenteElectrico { ... }  // Hijo hereda de padre

📋 Checklist de completitud

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

  • Entender la diferencia entre clase y objeto
  • Poder crear clases con propiedades y métodos
  • Saber usar constructores para inicializar objetos
  • Entender public vs private (encapsulación)
  • Poder instanciar objetos con new
  • Comprender qué es la herencia básica
  • Haber ejecutado el sistema de gestión de componentes
  • Poder agregar nuevas clases (Motor, Guardamotor)
  • (Opcional) Haber intentado al menos un desafío

¿Listo para aprender manejo robusto de errores? ¡Vamos al Capítulo 11! 🚀


Última actualización: Enero 2025 Tiempo de lectura estimado: 70-80 minutos Código de ejemplo: code/cap-10/