Capítulo 11: Manejo de errores en C# (try/catch)
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é 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
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
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
1. Introducción: El problema de los errores
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 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
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
| 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
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
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)
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
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)
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
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");
}
// Uso
try
{
ValidarCorriente(-5.0); // Lanza excepción
}
catch (ArgumentException ex)
{
MessageBox.Show("Error de validación: " + ex.Message);
}
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
// 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));
}
5. Validación de datos
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
// ❌ 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()
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
}
// Uso
string error;
if (ValidarComponente("K1", 25.0, out error))
{
MessageBox.Show("Componente válido");
}
else
{
MessageBox.Show("Error: " + error);
}
6. Logging de errores
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)
}
}
}
// 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
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 { }
}
}
// 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. Mejores prácticas
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.
7.2 Captura excepciones específicas
// ❌ 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);
}
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
// ❌ 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");
}
7.5 No ocultes excepciones
// ❌ 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
Vamos a crear un Validador Robusto de Archivos y Datos completo.
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
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
🔍 Deep Dive: Entendiendo en profundidad
¿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
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
Using: Manejo automático de recursos
// 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
📝 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
⬅️ Capítulo anterior
Capítulo 10: Clases y objetos (POO)
➡️ 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
- 📖 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
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?
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?
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?
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?
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?
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
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 de la guía. 🎉
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/