Capítulo 11: Manejo de errores
Capítulo 11: Manejo de errores
Sección titulada «Capítulo 11: Manejo de errores»Parte I - Sección 2: Programación desde cero - Nivel: Principiante
🎯 Objetivos de aprendizaje
Sección titulada «🎯 Objetivos de aprendizaje»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
📋 Requisitos previos
Sección titulada «📋 Requisitos previos»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)
🏆 Proyecto del capítulo
Sección titulada «🏆 Proyecto del capítulo»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
📚 Contenido
Sección titulada «📚 Contenido»1. Introducción: El problema de los errores
Sección titulada «1. Introducción: El problema de los errores»1.1 ¿Qué puede salir mal?
Sección titulada «1.1 ¿Qué puede salir mal?»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 controladaCon 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
2. Excepciones: ¿Qué son?
Sección titulada «2. Excepciones: ¿Qué son?»Una excepción es un evento anormal que ocurre durante la ejecución de un programa.
2.1 Tipos comunes de excepciones
Sección titulada «2.1 Tipos comunes de excepciones»| Excepción | Causa | Ejemplo |
|---|---|---|
FileNotFoundException | Archivo no existe | Abrir archivo inexistente |
DirectoryNotFoundException | Carpeta no existe | Acceder a ruta inexistente |
FormatException | Conversión de tipo fallida | int.Parse("abc") |
IndexOutOfRangeException | Índice fuera de límites | array[10] en array de 5 |
NullReferenceException | Objeto es null | objeto.Metodo() cuando objeto es null |
ArgumentException | Argumento inválido | Pasar valor incorrecto a método |
InvalidOperationException | Operación inválida | Operación en estado incorrecto |
UnauthorizedAccessException | Sin permisos | Acceder a archivo protegido |
2.2 Anatomía de una excepción
Sección titulada «2.2 Anatomía de una excepción»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»3.1 Try-Catch básico
Sección titulada «3.1 Try-Catch básico»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:
- Se ejecuta el bloque
try - Si no hay error, se salta el
catch - Si hay error, se ejecuta el
catchinmediatamente
3.2 Múltiples catch (específicos primero)
Sección titulada «3.2 Múltiples catch (específicos primero)»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íficoscatch (Exception ex) { }catch (FileNotFoundException ex) { } // Nunca se ejecuta
// ✓ BIEN - Específicos primerocatch (FileNotFoundException ex) { }catch (FormatException ex) { }catch (Exception ex) { } // Captura el resto3.3 Finally: Código que siempre se ejecuta
Sección titulada «3.3 Finally: Código que siempre se ejecuta»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
3.4 Try-Finally (sin catch)
Sección titulada «3.4 Try-Finally (sin catch)»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(); }}4. Lanzar excepciones: throw
Sección titulada «4. Lanzar excepciones: throw»4.1 Throw básico
Sección titulada «4.1 Throw básico»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");}
// Usotry{ ValidarCorriente(-5.0); // Lanza excepción}catch (ArgumentException ex){ MessageBox.Show("Error de validación: " + ex.Message);}4.2 Re-lanzar excepciones
Sección titulada «4.2 Re-lanzar excepciones»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}4.3 Excepciones personalizadas
Sección titulada «4.3 Excepciones personalizadas»// Definir excepción personalizadapublic class ComponenteInvalidoException : Exception{ public string Designacion { get; set; }
public ComponenteInvalidoException(string designacion, string mensaje) : base(mensaje) { this.Designacion = designacion; }}
// Usopublic 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)); }}
// Capturatry{ ValidarComponente("X1", 25.0);}catch (ComponenteInvalidoException ex){ MessageBox.Show(string.Format("Componente inválido: {0}\nError: {1}", ex.Designacion, ex.Message));}5. Validación de datos
Sección titulada «5. Validación de datos»5.1 Validación defensiva
Sección titulada «5.1 Validación defensiva»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;}5.2 TryParse: Validación sin excepciones
Sección titulada «5.2 TryParse: Validación sin excepciones»// ❌ MAL - Usar excepciones para control de flujotry{ int numero = int.Parse(texto);}catch (FormatException){ numero = 0; // Valor por defecto}
// ✓ BIEN - Usar TryParseint 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()
5.3 Validación con retorno booleano
Sección titulada «5.3 Validación con retorno booleano»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}
// Usostring error;if (ValidarComponente("K1", 25.0, out error)){ MessageBox.Show("Componente válido");}else{ MessageBox.Show("Error: " + error);}6. Logging de errores
Sección titulada «6. Logging de errores»6.1 Logger simple a archivo
Sección titulada «6.1 Logger simple a archivo»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) } }}
// UsoLogger 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 completado6.2 Logger con niveles
Sección titulada «6.2 Logger con niveles»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 { } }}
// UsoLoggerAvanzado 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 registralog.Warning("Archivo no óptimo"); // Se registralog.Error("Fallo crítico", ex); // Se registra7. Mejores prácticas
Sección titulada «7. Mejores prácticas»7.1 No uses excepciones para control de flujo
Sección titulada «7.1 No uses excepciones para control de flujo»// ❌ MALtry{ int numero = int.Parse(input);}catch (FormatException){ numero = 0; // Usar excepción como if}
// ✓ BIENint numero;if (!int.TryParse(input, out numero)){ numero = 0;}Razón: Las excepciones son costosas en rendimiento. Úsalas solo para casos excepcionales.
7.2 Captura excepciones específicas
Sección titulada «7.2 Captura excepciones específicas»// ❌ MAL - Captura demasiado ampliatry{ // ...}catch (Exception ex) // Captura TODO (incluso bugs del sistema){ MessageBox.Show("Error");}
// ✓ BIEN - Específicotry{ // ...}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);}7.3 Siempre valida entrada de usuario
Sección titulada «7.3 Siempre valida entrada de usuario»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");}7.4 Proporciona mensajes útiles
Sección titulada «7.4 Proporciona mensajes útiles»// ❌ MAL - Mensaje genéricocatch (Exception ex){ MessageBox.Show("Error");}
// ✓ BIEN - Mensaje informativocatch (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 sugeridacatch (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");}7.5 No ocultes excepciones
Sección titulada «7.5 No ocultes excepciones»// ❌ MAL - "Tragarse" el errortry{ // Código que falla}catch{ // No hacer nada - el usuario no sabe qué pasó}
// ✓ BIEN - Al menos registrartry{ // 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 completo: ValidadorRobusto.cs
Sección titulada «Proyecto completo: ValidadorRobusto.cs»//// 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"); } }}Archivo de prueba: componentes.txt
Sección titulada «Archivo de prueba: componentes.txt»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,400K2,3RT2036-1BB40,Contactor,40.0,400Q1,3RV2011-1JA10,Guardamotor,4.5,400M1,WEG-7.5KW,Motor,15.2,400
# Líneas con errores para probar validaciónK3,ABC,Contactor,-10.0,400,3RT2026,Contactor,25.0,400K5,3RT2026-1BB40,Contactor,25.0,999K6,3RT2026-1BB40,Contactor,abc,400K7,SHORT,Contactor,25.0,400,extra🔍 Deep Dive: Entendiendo en profundidad
Sección titulada «🔍 Deep Dive: Entendiendo en profundidad»¿Cuándo usar excepciones vs validación?
Sección titulada «¿Cuándo usar excepciones vs validación?»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
Stack unwinding
Sección titulada «Stack unwinding»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 manejaUsing: Manejo automático de recursos
Sección titulada «Using: Manejo automático de recursos»// Sin using - manualStreamReader reader = null;try{ reader = new StreamReader("archivo.txt"); string contenido = reader.ReadToEnd();}finally{ if (reader != null) reader.Close();}
// Con using - automáticousing (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
📝 Resumen
Sección titulada «📝 Resumen»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
finallypara liberar recursos (o mejor aún,using)
🔗 Conexiones
Sección titulada «🔗 Conexiones»⬅️ Capítulo anterior
Sección titulada «⬅️ Capítulo anterior»Capítulo 10: Clases y objetos (POO)
➡️ Próximo capítulo
Sección titulada «➡️ Próximo capítulo»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.
📚 Recursos adicionales
Sección titulada «📚 Recursos adicionales»- 📖 Documentación de C# - Excepciones
- 📖 Documentación de C# - Try-Catch
- 📖 Documentación de C# - Throw
- 📖 Best Practices for Exceptions
- 💻 [Código completo]:
code/cap-11/
❓ Preguntas frecuentes
Sección titulada «❓ Preguntas frecuentes»P: ¿Debo capturar todas las excepciones?
Sección titulada «P: ¿Debo capturar todas las excepciones?»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 completothrow ex;re-lanza pero pierde el stack trace original
Siempre usa throw; sin parámetro para re-lanzar.
P: ¿Las excepciones son lentas?
Sección titulada «P: ¿Las excepciones son lentas?»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 restoP: ¿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.
📋 Checklist de completitud
Sección titulada «📋 Checklist de completitud»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/