Ir al contenido

Capítulo 11: Manejo de errores

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


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

  • Entender qué son las excepciones y por qué ocurren
  • Usar try-catch-finally para manejar errores de forma controlada
  • Identificar tipos comunes de excepciones en C#
  • Lanzar excepciones propias con throw
  • Implementar logging para rastrear errores
  • Validar datos antes de procesarlos
  • Construir un validador robusto que nunca falla inesperadamente
  • Aplicar mejores prácticas de manejo de errores en scripts de automatización

Antes de comenzar este capítulo debes:

  • ✅ Haber completado el Capítulo 10
  • ✅ Entender clases y objetos básicos
  • ✅ Saber trabajar con colecciones (List, Dictionary)
  • ✅ Conocer funciones y métodos
  • ✅ Comprender control de flujo (if/else, loops)

Validador Robusto de Archivos y Datos EPLAN

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

  • Valida archivos antes de procesarlos (existencia, extensión, permisos)
  • Procesa listas de componentes con validación de datos
  • Maneja errores de forma elegante sin interrumpir la ejecución
  • Genera logs detallados de errores y advertencias
  • Implementa validaciones personalizadas con excepciones
  • Crea reportes de validación profesionales

Tiempo estimado: 70-80 minutos


1. Introducción: El problema de los errores

Sección titulada «1. Introducción: El problema de los errores»

Sin manejo de errores (código frágil):

public void ProcesarArchivo()
{
string archivo = @"C:\datos\componentes.txt";
// ❌ ¿Y si el archivo no existe?
string contenido = System.IO.File.ReadAllText(archivo);
// ❌ ¿Y si el contenido está vacío?
string[] lineas = contenido.Split('\n');
// ❌ ¿Y si una línea está mal formateada?
foreach (string linea in lineas)
{
string[] partes = linea.Split(',');
double corriente = double.Parse(partes[2]); // ❌ ¿Y si no es un número?
}
MessageBox.Show("Procesamiento completado");
}
// Resultado: Script se cae con excepción no controlada

Con manejo de errores (código robusto):

public void ProcesarArchivo()
{
try
{
string archivo = @"C:\datos\componentes.txt";
// Validar que existe
if (!System.IO.File.Exists(archivo))
{
MessageBox.Show("Error: El archivo no existe", "Error");
return;
}
string contenido = System.IO.File.ReadAllText(archivo);
// Validar contenido
if (string.IsNullOrEmpty(contenido))
{
MessageBox.Show("Error: El archivo está vacío", "Error");
return;
}
string[] lineas = contenido.Split('\n');
foreach (string linea in lineas)
{
try
{
string[] partes = linea.Split(',');
double corriente;
if (!double.TryParse(partes[2], out corriente))
{
MessageBox.Show(string.Format("Advertencia: Línea inválida: {0}", linea));
continue; // Salta esta línea, pero continúa con las demás
}
// Procesar corriente...
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error en línea: {0}\n{1}", linea, ex.Message));
}
}
MessageBox.Show("Procesamiento completado con éxito");
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error general: {0}", ex.Message), "Error crítico");
}
}

Ventajas del manejo de errores:

  • ✅ El script no se cae inesperadamente
  • ✅ Mensajes informativos para el usuario
  • ✅ Posibilidad de recuperación o alternativas
  • ✅ Registro de errores para debugging
  • ✅ Experiencia profesional del usuario

Una excepción es un evento anormal que ocurre durante la ejecución de un programa.

ExcepciónCausaEjemplo
FileNotFoundExceptionArchivo no existeAbrir archivo inexistente
DirectoryNotFoundExceptionCarpeta no existeAcceder a ruta inexistente
FormatExceptionConversión de tipo fallidaint.Parse("abc")
IndexOutOfRangeExceptionÍndice fuera de límitesarray[10] en array de 5
NullReferenceExceptionObjeto es nullobjeto.Metodo() cuando objeto es null
ArgumentExceptionArgumento inválidoPasar valor incorrecto a método
InvalidOperationExceptionOperación inválidaOperación en estado incorrecto
UnauthorizedAccessExceptionSin permisosAcceder a archivo protegido

Cuando ocurre una excepción, contiene información útil:

try
{
int resultado = int.Parse("abc"); // Causa FormatException
}
catch (FormatException ex)
{
// Propiedades útiles:
MessageBox.Show(ex.Message); // Descripción del error
MessageBox.Show(ex.StackTrace); // Pila de llamadas
MessageBox.Show(ex.Source); // Fuente del error
}

3. Try-Catch-Finally: La estructura fundamental

Sección titulada «3. Try-Catch-Finally: La estructura fundamental»
try
{
// Código que puede fallar
int numero = int.Parse("123");
MessageBox.Show("Número: " + numero);
}
catch (Exception ex)
{
// Código que se ejecuta si hay error
MessageBox.Show("Error: " + ex.Message);
}

Flujo de ejecución:

  1. Se ejecuta el bloque try
  2. Si no hay error, se salta el catch
  3. Si hay error, se ejecuta el catch inmediatamente
try
{
string texto = System.IO.File.ReadAllText(@"C:\datos\archivo.txt");
int numero = int.Parse(texto);
MessageBox.Show("Número: " + numero);
}
catch (FileNotFoundException ex)
{
MessageBox.Show("El archivo no existe: " + ex.Message);
}
catch (FormatException ex)
{
MessageBox.Show("El contenido no es un número válido: " + ex.Message);
}
catch (Exception ex)
{
// Captura cualquier otra excepción
MessageBox.Show("Error inesperado: " + ex.Message);
}

Importante: Los catch más específicos deben ir antes que los genéricos.

// ❌ MAL - Nunca se ejecutarán los catch específicos
catch (Exception ex) { }
catch (FileNotFoundException ex) { } // Nunca se ejecuta
// ✓ BIEN - Específicos primero
catch (FileNotFoundException ex) { }
catch (FormatException ex) { }
catch (Exception ex) { } // Captura el resto
System.IO.StreamReader reader = null;
try
{
reader = new System.IO.StreamReader(@"C:\datos\archivo.txt");
string contenido = reader.ReadToEnd();
MessageBox.Show(contenido);
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
finally
{
// Se ejecuta SIEMPRE (haya o no error)
if (reader != null)
{
reader.Close(); // Liberar recursos
MessageBox.Show("Archivo cerrado");
}
}

Usos típicos de finally:

  • Cerrar archivos
  • Liberar conexiones de base de datos
  • Limpiar recursos temporales
  • Restaurar estados
System.IO.StreamWriter writer = null;
try
{
writer = new System.IO.StreamWriter(@"C:\datos\log.txt", true);
writer.WriteLine("Inicio del proceso");
// Código que puede fallar...
}
finally
{
// Siempre se cierra el archivo
if (writer != null)
{
writer.Close();
}
}

public void ValidarCorriente(double corriente)
{
if (corriente <= 0)
{
throw new ArgumentException("La corriente debe ser mayor que cero");
}
if (corriente > 1000)
{
throw new ArgumentException("La corriente excede el límite de 1000A");
}
MessageBox.Show("Corriente válida: " + corriente + "A");
}
// Uso
try
{
ValidarCorriente(-5.0); // Lanza excepción
}
catch (ArgumentException ex)
{
MessageBox.Show("Error de validación: " + ex.Message);
}
try
{
// Código que falla
int resultado = int.Parse("abc");
}
catch (FormatException ex)
{
// Registrar error
MessageBox.Show("Registrando error en log...");
// Re-lanzar la misma excepción
throw; // Sin "ex" para mantener stack trace original
}
// Definir excepción personalizada
public class ComponenteInvalidoException : Exception
{
public string Designacion { get; set; }
public ComponenteInvalidoException(string designacion, string mensaje)
: base(mensaje)
{
this.Designacion = designacion;
}
}
// Uso
public void ValidarComponente(string designacion, double corriente)
{
if (string.IsNullOrEmpty(designacion))
{
throw new ComponenteInvalidoException("", "La designación no puede estar vacía");
}
if (!designacion.StartsWith("K") && !designacion.StartsWith("Q"))
{
throw new ComponenteInvalidoException(designacion,
"La designación debe comenzar con K o Q");
}
if (corriente <= 0)
{
throw new ComponenteInvalidoException(designacion,
string.Format("Corriente inválida para {0}: {1}A", designacion, corriente));
}
}
// Captura
try
{
ValidarComponente("X1", 25.0);
}
catch (ComponenteInvalidoException ex)
{
MessageBox.Show(string.Format("Componente inválido: {0}\nError: {1}",
ex.Designacion, ex.Message));
}

public double CalcularPotencia(double corriente, double voltaje)
{
// Validar parámetros
if (corriente < 0)
throw new ArgumentException("La corriente no puede ser negativa", "corriente");
if (voltaje < 0)
throw new ArgumentException("El voltaje no puede ser negativo", "voltaje");
if (corriente == 0 || voltaje == 0)
return 0;
return corriente * voltaje;
}
// ❌ MAL - Usar excepciones para control de flujo
try
{
int numero = int.Parse(texto);
}
catch (FormatException)
{
numero = 0; // Valor por defecto
}
// ✓ BIEN - Usar TryParse
int numero;
if (int.TryParse(texto, out numero))
{
MessageBox.Show("Número válido: " + numero);
}
else
{
MessageBox.Show("Texto inválido, usando 0");
numero = 0;
}

TryParse está disponible para:

  • int.TryParse()
  • double.TryParse()
  • bool.TryParse()
  • DateTime.TryParse()
public bool ValidarComponente(string designacion, double corriente, out string mensajeError)
{
mensajeError = "";
if (string.IsNullOrEmpty(designacion))
{
mensajeError = "La designación no puede estar vacía";
return false;
}
if (corriente <= 0)
{
mensajeError = "La corriente debe ser mayor que cero";
return false;
}
if (corriente > 1000)
{
mensajeError = "La corriente no puede exceder 1000A";
return false;
}
return true; // Validación exitosa
}
// Uso
string error;
if (ValidarComponente("K1", 25.0, out error))
{
MessageBox.Show("Componente válido");
}
else
{
MessageBox.Show("Error: " + error);
}

public class Logger
{
private string archivoLog;
public Logger(string ruta)
{
this.archivoLog = ruta;
}
public void Info(string mensaje)
{
EscribirLog("INFO", mensaje);
}
public void Warning(string mensaje)
{
EscribirLog("WARNING", mensaje);
}
public void Error(string mensaje, Exception ex = null)
{
string mensajeCompleto = mensaje;
if (ex != null)
{
mensajeCompleto += "\n Excepción: " + ex.Message;
mensajeCompleto += "\n Stack Trace: " + ex.StackTrace;
}
EscribirLog("ERROR", mensajeCompleto);
}
private void EscribirLog(string nivel, string mensaje)
{
try
{
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
string lineaLog = string.Format("[{0}] [{1}] {2}", timestamp, nivel, mensaje);
// Agregar al archivo (append)
System.IO.File.AppendAllText(archivoLog, lineaLog + "\n");
}
catch
{
// Si falla el log, no hacer nada (evitar loop infinito)
}
}
}
// Uso
Logger log = new Logger(@"C:\logs\proceso.log");
log.Info("Iniciando procesamiento de componentes");
try
{
// Código que puede fallar
int resultado = int.Parse("abc");
}
catch (FormatException ex)
{
log.Error("Error al convertir texto a número", ex);
MessageBox.Show("Error en el procesamiento. Ver log para detalles.");
}
log.Info("Procesamiento completado");

Ejemplo de archivo log generado:

[2025-01-15 10:30:45] [INFO] Iniciando procesamiento de componentes
[2025-01-15 10:30:45] [ERROR] Error al convertir texto a número
Excepción: Input string was not in a correct format.
Stack Trace: at System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt)
[2025-01-15 10:30:46] [INFO] Procesamiento completado
public enum NivelLog
{
Debug = 0,
Info = 1,
Warning = 2,
Error = 3
}
public class LoggerAvanzado
{
private string archivoLog;
private NivelLog nivelMinimo;
public LoggerAvanzado(string ruta, NivelLog nivelMinimo = NivelLog.Info)
{
this.archivoLog = ruta;
this.nivelMinimo = nivelMinimo;
}
public void Debug(string mensaje)
{
Log(NivelLog.Debug, mensaje);
}
public void Info(string mensaje)
{
Log(NivelLog.Info, mensaje);
}
public void Warning(string mensaje)
{
Log(NivelLog.Warning, mensaje);
}
public void Error(string mensaje, Exception ex = null)
{
string mensajeCompleto = mensaje;
if (ex != null)
mensajeCompleto += "\n " + ex.ToString();
Log(NivelLog.Error, mensajeCompleto);
}
private void Log(NivelLog nivel, string mensaje)
{
// Solo registrar si el nivel es suficiente
if (nivel < nivelMinimo)
return;
try
{
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
string lineaLog = string.Format("[{0}] [{1}] {2}",
timestamp, nivel.ToString().ToUpper(), mensaje);
System.IO.File.AppendAllText(archivoLog, lineaLog + "\n");
}
catch { }
}
}
// Uso
LoggerAvanzado log = new LoggerAvanzado(@"C:\logs\app.log", NivelLog.Info);
log.Debug("Detalle interno"); // No se registra (nivel muy bajo)
log.Info("Inicio del proceso"); // Se registra
log.Warning("Archivo no óptimo"); // Se registra
log.Error("Fallo crítico", ex); // Se registra

7.1 No uses excepciones para control de flujo

Sección titulada «7.1 No uses excepciones para control de flujo»
// ❌ MAL
try
{
int numero = int.Parse(input);
}
catch (FormatException)
{
numero = 0; // Usar excepción como if
}
// ✓ BIEN
int numero;
if (!int.TryParse(input, out numero))
{
numero = 0;
}

Razón: Las excepciones son costosas en rendimiento. Úsalas solo para casos excepcionales.

// ❌ MAL - Captura demasiado amplia
try
{
// ...
}
catch (Exception ex) // Captura TODO (incluso bugs del sistema)
{
MessageBox.Show("Error");
}
// ✓ BIEN - Específico
try
{
// ...
}
catch (FileNotFoundException ex)
{
MessageBox.Show("Archivo no encontrado");
}
catch (UnauthorizedAccessException ex)
{
MessageBox.Show("Sin permisos de acceso");
}
catch (IOException ex)
{
MessageBox.Show("Error de E/S: " + ex.Message);
}
public void ProcesarCorriente(string inputCorriente)
{
// Validar antes de convertir
if (string.IsNullOrWhiteSpace(inputCorriente))
{
MessageBox.Show("Debe ingresar un valor de corriente");
return;
}
double corriente;
if (!double.TryParse(inputCorriente, out corriente))
{
MessageBox.Show("El valor ingresado no es un número válido");
return;
}
if (corriente <= 0)
{
MessageBox.Show("La corriente debe ser mayor que cero");
return;
}
// Ahora sí procesar
MessageBox.Show("Corriente válida: " + corriente + "A");
}
// ❌ MAL - Mensaje genérico
catch (Exception ex)
{
MessageBox.Show("Error");
}
// ✓ BIEN - Mensaje informativo
catch (FileNotFoundException ex)
{
MessageBox.Show(string.Format(
"No se encontró el archivo:\n{0}\n\nVerifique que la ruta sea correcta.",
ex.FileName));
}
// ✓ MEJOR - Mensaje + acción sugerida
catch (UnauthorizedAccessException ex)
{
MessageBox.Show(
"Error de permisos.\n\n" +
"El archivo está protegido o en uso.\n\n" +
"Soluciones:\n" +
"1. Cierre el archivo si está abierto\n" +
"2. Ejecute como administrador\n" +
"3. Verifique permisos de carpeta");
}
// ❌ MAL - "Tragarse" el error
try
{
// Código que falla
}
catch
{
// No hacer nada - el usuario no sabe qué pasó
}
// ✓ BIEN - Al menos registrar
try
{
// Código que falla
}
catch (Exception ex)
{
Logger.Error("Error en procesamiento", ex);
MessageBox.Show("Ocurrió un error. Revise el log para detalles.");
}

💻 Manos a la obra: Construyendo el proyecto

Sección titulada «💻 Manos a la obra: Construyendo el proyecto»

Vamos a crear un Validador Robusto de Archivos y Datos completo.

//
// Proyecto: Validador Robusto de Archivos y Datos EPLAN
// Capítulo: 11
// Descripción: Sistema completo de validación con manejo de errores robusto,
// logging detallado, y validaciones personalizadas
// Autor: C.D. López
// Fecha: Enero 2025
//
using System;
using System.Collections.Generic;
using System.IO;
using Eplan.EplApi.Base;
using Eplan.EplApi.ApplicationFramework;
// ========================================
// LOGGER PROFESIONAL
// ========================================
public enum NivelLog
{
Debug = 0,
Info = 1,
Warning = 2,
Error = 3
}
public class Logger
{
private string archivoLog;
private NivelLog nivelMinimo;
public Logger(string ruta, NivelLog nivelMinimo = NivelLog.Info)
{
this.archivoLog = ruta;
this.nivelMinimo = nivelMinimo;
// Crear archivo de log si no existe
try
{
string directorio = Path.GetDirectoryName(ruta);
if (!Directory.Exists(directorio))
{
Directory.CreateDirectory(directorio);
}
}
catch { }
}
public void Debug(string mensaje)
{
Log(NivelLog.Debug, mensaje);
}
public void Info(string mensaje)
{
Log(NivelLog.Info, mensaje);
}
public void Warning(string mensaje)
{
Log(NivelLog.Warning, mensaje);
}
public void Error(string mensaje, Exception ex = null)
{
string mensajeCompleto = mensaje;
if (ex != null)
{
mensajeCompleto += string.Format("\n Excepción: {0}\n Tipo: {1}",
ex.Message, ex.GetType().Name);
}
Log(NivelLog.Error, mensajeCompleto);
}
private void Log(NivelLog nivel, string mensaje)
{
if (nivel < nivelMinimo)
return;
try
{
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
string lineaLog = string.Format("[{0}] [{1}] {2}",
timestamp, nivel.ToString().ToUpper(), mensaje);
File.AppendAllText(archivoLog, lineaLog + "\n");
}
catch { }
}
}
// ========================================
// EXCEPCIÓN PERSONALIZADA
// ========================================
public class ComponenteInvalidoException : Exception
{
public string Designacion { get; set; }
public string Campo { get; set; }
public ComponenteInvalidoException(string designacion, string campo, string mensaje)
: base(mensaje)
{
this.Designacion = designacion;
this.Campo = campo;
}
}
// ========================================
// CLASE COMPONENTE
// ========================================
public class ComponenteElectrico
{
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 ComponenteElectrico(string designacion, string codigo, string tipo,
double corriente, double voltaje)
{
this.Designacion = designacion;
this.Codigo = codigo;
this.Tipo = tipo;
this.Corriente = corriente;
this.Voltaje = voltaje;
}
public override string ToString()
{
return string.Format("{0} | {1} | {2} | {3}A | {4}V",
Designacion, Codigo, Tipo, Corriente, Voltaje);
}
}
// ========================================
// VALIDADOR DE COMPONENTES
// ========================================
public class ValidadorComponentes
{
private Logger log;
public ValidadorComponentes(Logger logger)
{
this.log = logger;
}
// Validar designación
public void ValidarDesignacion(string designacion)
{
if (string.IsNullOrWhiteSpace(designacion))
{
throw new ComponenteInvalidoException("", "Designacion",
"La designación no puede estar vacía");
}
if (designacion.Length < 2)
{
throw new ComponenteInvalidoException(designacion, "Designacion",
"La designación debe tener al menos 2 caracteres");
}
char primerCaracter = designacion[0];
if (!char.IsLetter(primerCaracter))
{
throw new ComponenteInvalidoException(designacion, "Designacion",
"La designación debe comenzar con letra");
}
}
// Validar código
public void ValidarCodigo(string designacion, string codigo)
{
if (string.IsNullOrWhiteSpace(codigo))
{
throw new ComponenteInvalidoException(designacion, "Codigo",
"El código no puede estar vacío");
}
if (codigo.Length < 5)
{
throw new ComponenteInvalidoException(designacion, "Codigo",
"El código debe tener al menos 5 caracteres");
}
}
// Validar corriente
public void ValidarCorriente(string designacion, double corriente)
{
if (corriente <= 0)
{
throw new ComponenteInvalidoException(designacion, "Corriente",
string.Format("La corriente debe ser mayor que 0 (actual: {0}A)", corriente));
}
if (corriente > 10000)
{
throw new ComponenteInvalidoException(designacion, "Corriente",
string.Format("La corriente excede el límite de 10000A (actual: {0}A)", corriente));
}
}
// Validar voltaje
public void ValidarVoltaje(string designacion, double voltaje)
{
double[] voltajesPermitidos = {24, 48, 110, 220, 230, 380, 400, 440, 480, 690};
bool voltajeValido = false;
foreach (double v in voltajesPermitidos)
{
if (Math.Abs(voltaje - v) < 5) // Tolerancia de ±5V
{
voltajeValido = true;
break;
}
}
if (!voltajeValido)
{
throw new ComponenteInvalidoException(designacion, "Voltaje",
string.Format("Voltaje no estándar: {0}V", voltaje));
}
}
// Validar componente completo
public bool ValidarComponente(ComponenteElectrico componente, out string mensajeError)
{
mensajeError = "";
try
{
ValidarDesignacion(componente.Designacion);
ValidarCodigo(componente.Designacion, componente.Codigo);
ValidarCorriente(componente.Designacion, componente.Corriente);
ValidarVoltaje(componente.Designacion, componente.Voltaje);
log.Info(string.Format("Componente válido: {0}", componente.Designacion));
return true;
}
catch (ComponenteInvalidoException ex)
{
mensajeError = string.Format("[{0}] Campo '{1}': {2}",
ex.Designacion, ex.Campo, ex.Message);
log.Warning(mensajeError);
return false;
}
catch (Exception ex)
{
mensajeError = "Error inesperado: " + ex.Message;
log.Error(mensajeError, ex);
return false;
}
}
}
// ========================================
// VALIDADOR DE ARCHIVOS
// ========================================
public class ValidadorArchivos
{
private Logger log;
public ValidadorArchivos(Logger logger)
{
this.log = logger;
}
// Validar que archivo existe
public bool ValidarExistencia(string rutaArchivo, out string mensajeError)
{
mensajeError = "";
if (string.IsNullOrWhiteSpace(rutaArchivo))
{
mensajeError = "La ruta del archivo no puede estar vacía";
log.Error(mensajeError);
return false;
}
if (!File.Exists(rutaArchivo))
{
mensajeError = string.Format("El archivo no existe:\n{0}", rutaArchivo);
log.Error(mensajeError);
return false;
}
log.Info(string.Format("Archivo existe: {0}", rutaArchivo));
return true;
}
// Validar extensión
public bool ValidarExtension(string rutaArchivo, string[] extensionesPermitidas,
out string mensajeError)
{
mensajeError = "";
string extension = Path.GetExtension(rutaArchivo).ToLower();
bool extensionValida = false;
foreach (string ext in extensionesPermitidas)
{
if (extension == ext.ToLower())
{
extensionValida = true;
break;
}
}
if (!extensionValida)
{
mensajeError = string.Format(
"Extensión inválida: {0}\nExtensiones permitidas: {1}",
extension, string.Join(", ", extensionesPermitidas));
log.Error(mensajeError);
return false;
}
log.Info(string.Format("Extensión válida: {0}", extension));
return true;
}
// Validar permisos de lectura
public bool ValidarPermisosLectura(string rutaArchivo, out string mensajeError)
{
mensajeError = "";
try
{
using (FileStream fs = File.Open(rutaArchivo, FileMode.Open, FileAccess.Read))
{
// Si llegamos aquí, tenemos permisos
}
log.Info("Permisos de lectura: OK");
return true;
}
catch (UnauthorizedAccessException ex)
{
mensajeError = "Sin permisos de lectura para el archivo";
log.Error(mensajeError, ex);
return false;
}
catch (Exception ex)
{
mensajeError = "Error al verificar permisos: " + ex.Message;
log.Error(mensajeError, ex);
return false;
}
}
// Validar que archivo no está vacío
public bool ValidarContenido(string rutaArchivo, out string mensajeError)
{
mensajeError = "";
try
{
FileInfo info = new FileInfo(rutaArchivo);
if (info.Length == 0)
{
mensajeError = "El archivo está vacío";
log.Warning(mensajeError);
return false;
}
log.Info(string.Format("Tamaño del archivo: {0} bytes", info.Length));
return true;
}
catch (Exception ex)
{
mensajeError = "Error al leer información del archivo: " + ex.Message;
log.Error(mensajeError, ex);
return false;
}
}
}
// ========================================
// PROCESADOR DE ARCHIVOS
// ========================================
public class ProcesadorArchivos
{
private Logger log;
private ValidadorArchivos validadorArchivos;
private ValidadorComponentes validadorComponentes;
public ProcesadorArchivos(Logger logger)
{
this.log = logger;
this.validadorArchivos = new ValidadorArchivos(logger);
this.validadorComponentes = new ValidadorComponentes(logger);
}
// Procesar archivo de componentes
public void ProcesarArchivoComponentes(string rutaArchivo)
{
log.Info("========================================");
log.Info("Iniciando procesamiento de archivo");
log.Info("Archivo: " + rutaArchivo);
int lineasProcesadas = 0;
int lineasValidas = 0;
int lineasInvalidas = 0;
List<string> errores = new List<string>();
try
{
// Validar archivo
string error;
if (!validadorArchivos.ValidarExistencia(rutaArchivo, out error))
{
MessageBox.Show(error, "Error de archivo");
return;
}
string[] extensionesPermitidas = {".txt", ".csv"};
if (!validadorArchivos.ValidarExtension(rutaArchivo, extensionesPermitidas, out error))
{
MessageBox.Show(error, "Error de extensión");
return;
}
if (!validadorArchivos.ValidarPermisosLectura(rutaArchivo, out error))
{
MessageBox.Show(error, "Error de permisos");
return;
}
if (!validadorArchivos.ValidarContenido(rutaArchivo, out error))
{
MessageBox.Show(error, "Error de contenido");
return;
}
// Leer archivo
log.Info("Leyendo archivo...");
string[] lineas = File.ReadAllLines(rutaArchivo);
log.Info(string.Format("Total de líneas: {0}", lineas.Length));
// Procesar cada línea
foreach (string linea in lineas)
{
lineasProcesadas++;
// Saltar líneas vacías
if (string.IsNullOrWhiteSpace(linea))
{
log.Debug("Línea vacía ignorada");
continue;
}
// Saltar líneas de comentario
if (linea.Trim().StartsWith("#") || linea.Trim().StartsWith("//"))
{
log.Debug("Comentario ignorado: " + linea);
continue;
}
try
{
// Parsear línea (formato: Designacion,Codigo,Tipo,Corriente,Voltaje)
string[] partes = linea.Split(',');
if (partes.Length != 5)
{
string errorLinea = string.Format(
"Línea {0}: Formato inválido (esperaba 5 campos, encontró {1})",
lineasProcesadas, partes.Length);
errores.Add(errorLinea);
log.Warning(errorLinea);
lineasInvalidas++;
continue;
}
// Parsear campos
string designacion = partes[0].Trim();
string codigo = partes[1].Trim();
string tipo = partes[2].Trim();
double corriente;
if (!double.TryParse(partes[3].Trim(), out corriente))
{
string errorLinea = string.Format(
"Línea {0}: Corriente inválida '{1}'", lineasProcesadas, partes[3]);
errores.Add(errorLinea);
log.Warning(errorLinea);
lineasInvalidas++;
continue;
}
double voltaje;
if (!double.TryParse(partes[4].Trim(), out voltaje))
{
string errorLinea = string.Format(
"Línea {0}: Voltaje inválido '{1}'", lineasProcesadas, partes[4]);
errores.Add(errorLinea);
log.Warning(errorLinea);
lineasInvalidas++;
continue;
}
// Crear componente
ComponenteElectrico componente = new ComponenteElectrico(
designacion, codigo, tipo, corriente, voltaje);
// Validar componente
string errorValidacion;
if (validadorComponentes.ValidarComponente(componente, out errorValidacion))
{
lineasValidas++;
log.Info(string.Format("Línea {0}: OK - {1}",
lineasProcesadas, componente));
}
else
{
errores.Add(string.Format("Línea {0}: {1}",
lineasProcesadas, errorValidacion));
lineasInvalidas++;
}
}
catch (Exception ex)
{
string errorLinea = string.Format(
"Línea {0}: Error inesperado - {1}", lineasProcesadas, ex.Message);
errores.Add(errorLinea);
log.Error(errorLinea, ex);
lineasInvalidas++;
}
}
// Generar reporte
GenerarReporte(rutaArchivo, lineasProcesadas, lineasValidas, lineasInvalidas, errores);
}
catch (IOException ex)
{
string mensaje = "Error de lectura/escritura: " + ex.Message;
log.Error(mensaje, ex);
MessageBox.Show(mensaje, "Error de E/S");
}
catch (UnauthorizedAccessException ex)
{
string mensaje = "Sin permisos de acceso al archivo";
log.Error(mensaje, ex);
MessageBox.Show(mensaje, "Error de permisos");
}
catch (Exception ex)
{
string mensaje = "Error inesperado: " + ex.Message;
log.Error(mensaje, ex);
MessageBox.Show(mensaje, "Error crítico");
}
finally
{
log.Info("Procesamiento finalizado");
log.Info("========================================");
}
}
// Generar reporte de validación
private void GenerarReporte(string archivoOrigen, int totalLineas, int lineasValidas,
int lineasInvalidas, List<string> errores)
{
string reporte = "REPORTE DE VALIDACIÓN\n";
reporte += "========================================\n";
reporte += string.Format("Archivo: {0}\n", Path.GetFileName(archivoOrigen));
reporte += string.Format("Fecha: {0}\n\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
reporte += "RESUMEN:\n";
reporte += string.Format(" Líneas procesadas: {0}\n", totalLineas);
reporte += string.Format(" Líneas válidas: {0}\n", lineasValidas);
reporte += string.Format(" Líneas inválidas: {0}\n\n", lineasInvalidas);
if (lineasInvalidas > 0)
{
reporte += "ERRORES ENCONTRADOS:\n";
reporte += "----------------------------------------\n";
foreach (string error in errores)
{
reporte += error + "\n";
}
}
else
{
reporte += "✓ Todos los componentes son válidos\n";
}
reporte += "========================================\n";
log.Info("Reporte generado");
MessageBox.Show(reporte, "Reporte de Validación");
}
}
// ========================================
// SCRIPT PRINCIPAL
// ========================================
public class ValidadorRobusto
{
[Start]
public void Ejecutar()
{
try
{
// Crear logger
string rutaLog = @"C:\temp\eplan_logs\validacion.log";
Logger log = new Logger(rutaLog, NivelLog.Info);
log.Info("==================================================");
log.Info("VALIDADOR ROBUSTO DE ARCHIVOS Y DATOS");
log.Info("==================================================");
// Crear procesador
ProcesadorArchivos procesador = new ProcesadorArchivos(log);
// Archivo de prueba
string archivoComponentes = @"C:\temp\componentes.txt";
// Procesar
procesador.ProcesarArchivoComponentes(archivoComponentes);
// Mostrar ubicación del log
MessageBox.Show(string.Format("Procesamiento completado.\n\nLog disponible en:\n{0}", rutaLog),
"Validación completada");
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error crítico en la aplicación:\n\n{0}\n\n{1}",
ex.Message, ex.StackTrace), "Error crítico");
}
}
}

Crea este archivo en C:\temp\componentes.txt:

# Archivo de componentes eléctricos
# Formato: Designacion,Codigo,Tipo,Corriente,Voltaje
K1,3RT2026-1BB40,Contactor,25.0,400
K2,3RT2036-1BB40,Contactor,40.0,400
Q1,3RV2011-1JA10,Guardamotor,4.5,400
M1,WEG-7.5KW,Motor,15.2,400
# Líneas con errores para probar validación
K3,ABC,Contactor,-10.0,400
,3RT2026,Contactor,25.0,400
K5,3RT2026-1BB40,Contactor,25.0,999
K6,3RT2026-1BB40,Contactor,abc,400
K7,SHORT,Contactor,25.0,400,extra

Usa excepciones para:

  • Condiciones verdaderamente excepcionales
  • Errores que no esperas normalmente
  • Violaciones de contratos (pre/postcondiciones)

Usa validación para:

  • Entrada de usuario
  • Datos externos (archivos, red)
  • Condiciones esperadas del negocio

Cuando se lanza una excepción, el programa “desenrolla” la pila de llamadas hasta encontrar un catch:

public void Metodo1()
{
try
{
Metodo2();
}
catch (Exception ex)
{
MessageBox.Show("Capturado en Metodo1: " + ex.Message);
}
}
public void Metodo2()
{
Metodo3(); // No captura excepciones
}
public void Metodo3()
{
throw new Exception("Error en Metodo3");
}
// Flujo:
// Metodo3 lanza excepción
// → Metodo2 no la captura, sube
// → Metodo1 la captura y maneja
// Sin using - manual
StreamReader reader = null;
try
{
reader = new StreamReader("archivo.txt");
string contenido = reader.ReadToEnd();
}
finally
{
if (reader != null)
reader.Close();
}
// Con using - automático
using (StreamReader reader = new StreamReader("archivo.txt"))
{
string contenido = reader.ReadToEnd();
} // Automáticamente llama reader.Close()

Ventajas de using:

  • Garantiza liberación de recursos
  • Código más limpio
  • Menos propenso a errores

En este capítulo aprendiste:

  • Excepciones: Eventos anormales durante la ejecución
  • Try-catch-finally: Estructura fundamental de manejo de errores
  • Tipos de excepciones: FileNotFoundException, FormatException, etc.
  • Throw: Lanzar excepciones propias y personalizadas
  • Validación: Prevenir errores antes de que ocurran
  • Logging: Registrar eventos y errores para debugging
  • Mejores prácticas: Capturar específico, validar entrada, mensajes útiles
  • Proyecto construido: Validador robusto con manejo completo de errores

Conceptos clave:

  • Las excepciones son para casos excepcionales, no para control de flujo
  • Siempre valida entrada de usuario antes de procesar
  • Captura excepciones específicas, no genéricas
  • Proporciona mensajes de error útiles y accionables
  • Registra errores para poder diagnosticar problemas
  • Usa finally para liberar recursos (o mejor aún, using)

Capítulo 10: Clases y objetos (POO)

Capítulo 12: Arquitectura de EPLAN API

En el próximo capítulo comenzarás a trabajar con EPLAN API. Aprenderás su estructura, namespaces principales, cómo leer la documentación, y crearás tus primeros scripts que interactúan con proyectos EPLAN reales.



R: No. Solo captura excepciones que puedas manejar de forma significativa. Si no puedes hacer nada útil con la excepción, déjala propagarse. Evita catch (Exception) genérico a menos que sea el nivel más alto de tu aplicación.

P: ¿Cuál es la diferencia entre throw y throw ex?

Sección titulada «P: ¿Cuál es la diferencia entre throw y throw ex?»

R:

  • throw; re-lanza la excepción original manteniendo el stack trace completo
  • throw ex; re-lanza pero pierde el stack trace original

Siempre usa throw; sin parámetro para re-lanzar.

R: Sí, las excepciones tienen un costo de rendimiento. Por eso no debes usarlas para control de flujo normal. Usa TryParse en lugar de try-catch para conversiones, y validaciones if para condiciones esperadas.

P: ¿Cuándo debo crear excepciones personalizadas?

Sección titulada «P: ¿Cuándo debo crear excepciones personalizadas?»

R: Cuando necesitas comunicar un error específico de tu dominio que no está cubierto por las excepciones estándar. Ejemplo: ComponenteInvalidoException, VoltajeIncompatibleException.

P: ¿Qué hacer si un método lanza múltiples tipos de excepciones?

Sección titulada «P: ¿Qué hacer si un método lanza múltiples tipos de excepciones?»

R: Usa múltiples bloques catch, ordenados de más específico a más genérico:

try
{
// código
}
catch (FileNotFoundException ex) { }
catch (UnauthorizedAccessException ex) { }
catch (IOException ex) { }
catch (Exception ex) { } // Captura el resto

P: ¿Es malo usar catch sin especificar el tipo?

Sección titulada «P: ¿Es malo usar catch sin especificar el tipo?»

R: Sí, evita catch { } sin tipo. Siempre especifica al menos catch (Exception ex) para poder acceder a la información del error y registrarlo.


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

  • Entender qué son las excepciones y cuándo ocurren
  • Poder escribir try-catch-finally correctamente
  • Conocer los tipos comunes de excepciones
  • Saber lanzar excepciones con throw
  • Poder crear excepciones personalizadas
  • Implementar validación de datos con TryParse
  • Crear un logger simple para registrar errores
  • Haber ejecutado el validador robusto completo
  • Entender las mejores prácticas de manejo de errores
  • (Opcional) Adaptar el validador para tus propios datos

¡Felicitaciones! Has completado la Parte I del libro. 🎉

Ya tienes todos los fundamentos de programación necesarios para trabajar con EPLAN API. En la Parte II comenzarás a crear scripts reales que automatizan tareas en EPLAN Electric P8.

¿Listo para empezar con EPLAN API? ¡Vamos al Capítulo 12! 🚀


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