using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using HydraulicSimulator.Models;
using CtrEditor.ObjetosSim;
namespace CtrEditor.HydraulicSimulator
{
///
/// Gestiona la simulación hidráulica similar a SimulationManagerBEPU
///
public class HydraulicSimulationManager : IDisposable
{
#region Properties and Fields
///
/// Red hidráulica principal que contiene todos los nodos y ramas
///
public HydraulicNetwork Network { get; private set; }
///
/// Lista de objetos simulables que tienen componentes hidráulicos
///
public List HydraulicObjects { get; private set; }
///
/// Fluido utilizado en la simulación (agua por defecto)
///
public Fluid SimulationFluid { get; set; }
///
/// Resultado de la última simulación
///
public SolutionResult LastSolutionResult { get; private set; }
///
/// Tiempo global de la simulación hidráulica
///
public float GlobalTime { get; private set; }
///
/// Indica si la simulación hidráulica está habilitada
///
public bool IsHydraulicSimulationEnabled { get; set; } = true;
///
/// Parámetros del solver
///
public int MaxIterations { get; set; } = 300;
public double Tolerance { get; set; } = 1e-3; // Tolerancia más relajada para convergencia inicial
public double RelaxationFactor { get; set; } = 0.8;
public bool VerboseOutput { get; set; } = false; // Activado para debugging
///
/// Contador de pasos de simulación para optimizaciones
///
private int _stepCount = 0;
///
/// Cronómetro para medir tiempo de ejecución
///
private Stopwatch _stopwatch;
///
/// Diccionario para mapear IDs de objetos a elementos hidráulicos
///
private Dictionary _objectMapping;
///
/// Indica si hay cambios pendientes en la red que requieren reconstrucción
///
private bool _networkNeedsRebuild = true;
#endregion
#region Constructor
///
/// Constructor del gestor de simulación hidráulica
///
public HydraulicSimulationManager()
{
// Inicializar componentes
SimulationFluid = Fluid.Water20C; // Agua a 20°C por defecto
Network = new HydraulicNetwork(SimulationFluid);
HydraulicObjects = new List();
_objectMapping = new Dictionary();
_stopwatch = new Stopwatch();
// Inicializar resultado vacío
LastSolutionResult = new SolutionResult(false)
{
ErrorMessage = "Simulación no ejecutada"
};
GlobalTime = 0.0f;
Debug.WriteLine("HydraulicSimulationManager inicializado");
}
#endregion
#region Object Management
///
/// Registra un objeto simulable que tiene componentes hidráulicos
///
/// Objeto con componentes hidráulicos
public void RegisterHydraulicObject(osBase hydraulicObject)
{
if (hydraulicObject == null)
return;
if (!HydraulicObjects.Contains(hydraulicObject))
{
HydraulicObjects.Add(hydraulicObject);
_objectMapping[hydraulicObject.Nombre] = hydraulicObject;
_networkNeedsRebuild = true;
Debug.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}");
}
}
///
/// Desregistra un objeto simulable hidráulico
///
/// Objeto a desregistrar
public void UnregisterHydraulicObject(osBase hydraulicObject)
{
if (hydraulicObject == null)
return;
if (HydraulicObjects.Remove(hydraulicObject))
{
_objectMapping.Remove(hydraulicObject.Nombre);
_networkNeedsRebuild = true;
Debug.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}");
}
}
///
/// Limpia todos los objetos hidráulicos registrados
///
public void ClearHydraulicObjects()
{
HydraulicObjects.Clear();
_objectMapping.Clear();
_networkNeedsRebuild = true;
Debug.WriteLine("Todos los objetos hidráulicos han sido limpiados");
}
#endregion
#region Network Building
///
/// Reconstruye la red hidráulica basada en los objetos registrados
///
private void RebuildNetwork()
{
if (!_networkNeedsRebuild)
return;
Debug.WriteLine("Reconstruyendo red hidráulica...");
// Crear nueva red
Network = new HydraulicNetwork(SimulationFluid);
// Agregar nodos y elementos basados en los objetos hidráulicos
BuildNodesFromObjects();
BuildBranchesFromObjects();
_networkNeedsRebuild = false;
Debug.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas");
if (VerboseOutput)
{
Debug.WriteLine("=== DETALLES DE LA RED HIDRÁULICA ===");
Debug.WriteLine("Nodos:");
foreach (var node in Network.Nodes)
{
string pressureInfo = node.Value.FixedP ? $"Presión fija: {node.Value.P:F0} Pa" : "Presión libre";
Debug.WriteLine($" - {node.Key}: {pressureInfo}");
}
Debug.WriteLine("Ramas:");
foreach (var branch in Network.Branches)
{
Debug.WriteLine($" - {branch.Name}: {branch.N1} -> {branch.N2} ({branch.Elements.Count} elementos)");
foreach (var element in branch.Elements)
{
Debug.WriteLine($" * {element.GetType().Name}");
}
}
Debug.WriteLine("=== FIN DETALLES RED ===");
}
}
///
/// Construye los nodos de la red basándose en los objetos hidráulicos
///
private void BuildNodesFromObjects()
{
foreach (var obj in HydraulicObjects)
{
// Verificar si el objeto implementa la interfaz hidráulica
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
{
// Obtener nodos definidos por el objeto
var nodeDefinitions = hydraulicComponent.GetHydraulicNodes();
foreach (var nodeDef in nodeDefinitions)
{
// Agregar nodo a la red
Network.AddNode(nodeDef.Name, nodeDef.IsFixedPressure ? nodeDef.Pressure : null);
if (VerboseOutput)
{
Debug.WriteLine($"Nodo agregado: {nodeDef.Name} " +
$"(Presión fija: {nodeDef.IsFixedPressure}, " +
$"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})");
}
}
}
else
{
// Objeto sin interfaz hidráulica - crear nodos básicos por compatibilidad
string nodeName = $"Node_{obj.Nombre}";
bool isFixedPressure = DetermineIfFixedPressure(obj);
double? pressure = isFixedPressure ? GetObjectPressure(obj) : null;
Network.AddNode(nodeName, pressure);
}
}
}
///
/// Construye las ramas (conexiones) entre nodos
///
private void BuildBranchesFromObjects()
{
foreach (var obj in HydraulicObjects)
{
// Verificar si el objeto implementa la interfaz hidráulica
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
{
// Obtener elementos definidos por el objeto
var elementDefinitions = hydraulicComponent.GetHydraulicElements();
foreach (var elemDef in elementDefinitions)
{
// Crear rama con el elemento
var elements = new List { elemDef.Element };
Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name);
if (VerboseOutput)
{
Debug.WriteLine($"Rama agregada: {elemDef.Name} " +
$"({elemDef.FromNode} -> {elemDef.ToNode})");
}
}
}
else
{
// Objeto sin interfaz hidráulica - crear elementos básicos por compatibilidad
var elements = CreateHydraulicElementsFromObject(obj);
if (elements.Any())
{
string fromNode = GetSourceNode(obj);
string toNode = GetTargetNode(obj);
if (!string.IsNullOrEmpty(fromNode) && !string.IsNullOrEmpty(toNode))
{
Network.AddBranch(fromNode, toNode, elements, $"Branch_{obj.Nombre}");
}
}
}
}
}
#endregion
#region Helper Methods for Object Analysis
///
/// Determina si un objeto debe tener presión fija
///
private bool DetermineIfFixedPressure(osBase obj)
{
// Implementación que será extendida con tipos específicos
// Por ejemplo: tanques, descargas atmosféricas = presión fija
// Uniones, conexiones = presión libre
return false; // Por defecto, presión libre
}
///
/// Obtiene la presión de un objeto (si tiene presión fija)
///
private double GetObjectPressure(osBase obj)
{
// Implementación que será extendida
return 0.0; // Por defecto, presión atmosférica
}
///
/// Crea elementos hidráulicos basándose en un objeto
///
private List CreateHydraulicElementsFromObject(osBase obj)
{
var elements = new List();
// Implementación que será extendida con tipos específicos
// Por ejemplo:
// - Si es una bomba: crear PumpHQ
// - Si es una tubería: crear Pipe
// - Si es una válvula: crear ValveKv
return elements;
}
///
/// Obtiene el nodo fuente de un objeto
///
private string GetSourceNode(osBase obj)
{
// Implementación que será extendida
return $"Node_{obj.Nombre}_In";
}
///
/// Obtiene el nodo destino de un objeto
///
private string GetTargetNode(osBase obj)
{
// Implementación que será extendida
return $"Node_{obj.Nombre}_Out";
}
#endregion
#region Simulation Step
///
/// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU)
///
public void Step(float deltaTime = 0.016f) // ~60 FPS por defecto
{
if (!IsHydraulicSimulationEnabled)
return;
_stopwatch.Restart();
try
{
// Actualizar tiempo global
GlobalTime += deltaTime;
_stepCount++;
// Reconstruir red si es necesario
RebuildNetwork();
// Solo resolver si tenemos objetos hidráulicos
if (HydraulicObjects.Count > 0 && Network.Branches.Count > 0)
{
// Actualizar propiedades de objetos antes de resolver
UpdateObjectProperties();
// Resolver la red hidráulica
LastSolutionResult = Network.Solve(
MaxIterations,
Tolerance,
RelaxationFactor,
VerboseOutput
);
if (LastSolutionResult.Converged)
{
// Aplicar resultados a los objetos
ApplyResultsToObjects();
if (VerboseOutput && _stepCount % 300 == 0) // Log cada 5 segundos aprox
{
//Debug.WriteLine("=== RESULTADOS SIMULACIÓN HIDRÁULICA ===");
//Debug.WriteLine("Flujos:");
foreach (var flow in LastSolutionResult.Flows)
{
Debug.WriteLine($" {flow.Key}: {flow.Value:F6} m³/s ({flow.Value * 3600:F2} m³/h)");
}
Debug.WriteLine("Presiones:");
foreach (var pressure in LastSolutionResult.Pressures)
{
Debug.WriteLine($" {pressure.Key}: {pressure.Value:F0} Pa ({pressure.Value / 100000:F2} bar)");
}
Debug.WriteLine("=== FIN RESULTADOS ===");
}
}
else
{
Debug.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}");
Debug.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}");
Debug.WriteLine($" Tolerancia requerida: {Tolerance:E6}");
if (VerboseOutput && _stepCount % 60 == 0) // Log detallado cada segundo aprox
{
Debug.WriteLine("=== DIAGNÓSTICO CONVERGENCIA ===");
Debug.WriteLine($"Nodos en red: {Network.Nodes.Count}");
Debug.WriteLine($"Ramas en red: {Network.Branches.Count}");
foreach (var node in Network.Nodes)
{
string info = node.Value.FixedP ? $"FIJA={node.Value.P:F0}Pa" : "LIBRE";
Debug.WriteLine($" Nodo {node.Key}: {info}");
}
Debug.WriteLine("=== FIN DIAGNÓSTICO ===");
}
}
}
else
{
// Sin objetos hidráulicos, resultado exitoso pero vacío
LastSolutionResult = new SolutionResult(true);
}
}
catch (Exception ex)
{
LastSolutionResult = new SolutionResult(false)
{
ErrorMessage = $"Error en simulación hidráulica: {ex.Message}"
};
Debug.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}");
}
finally
{
_stopwatch.Stop();
if (VerboseOutput && _stepCount % 60 == 0) // Log cada segundo aprox
{
Debug.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " +
$"Objetos: {HydraulicObjects.Count}, Convergió: {LastSolutionResult.Converged}");
}
}
}
///
/// Actualiza las propiedades de los objetos antes de resolver la red
///
private void UpdateObjectProperties()
{
// Actualizar objetos hidráulicos específicos
foreach (var simObj in HydraulicSimObjects)
{
simObj.UpdateProperties();
}
// Actualizar objetos hidráulicos generales (legacy)
foreach (var obj in HydraulicObjects)
{
// Aquí se implementará la actualización de propiedades específicas
// Por ejemplo: estado de válvulas, velocidad de bombas, etc.
UpdateObjectHydraulicProperties(obj);
}
}
///
/// Actualiza las propiedades hidráulicas específicas de un objeto
///
private void UpdateObjectHydraulicProperties(osBase obj)
{
// Actualizar propiedades basándose en las interfaces implementadas
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
{
// Llamar al método de actualización del objeto
hydraulicComponent.UpdateHydraulicProperties();
}
// Actualización específica por tipo de objeto
if (obj is IHydraulicPump pump)
{
// Verificar estado de la bomba, velocidad, etc.
UpdatePumpProperties(pump);
}
else if (obj is IHydraulicValve valve)
{
// Verificar apertura de la válvula
UpdateValveProperties(valve);
}
else if (obj is IHydraulicTank tank)
{
// Actualizar nivel y presión del tanque
UpdateTankProperties(tank);
}
}
///
/// Actualiza propiedades específicas de bombas
///
private void UpdatePumpProperties(IHydraulicPump pump)
{
// Aquí se pueden hacer validaciones y ajustes específicos de bombas
// Por ejemplo, limitar velocidad, verificar estado de operación, etc.
if (pump.SpeedRatio < 0.0) pump.SpeedRatio = 0.0;
if (pump.SpeedRatio > 1.0) pump.SpeedRatio = 1.0;
if (VerboseOutput)
{
Debug.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " +
$"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}");
}
}
///
/// Actualiza propiedades específicas de válvulas
///
private void UpdateValveProperties(IHydraulicValve valve)
{
// Validar apertura de válvula
if (valve.Opening < 0.0) valve.Opening = 0.0;
if (valve.Opening > 1.0) valve.Opening = 1.0;
if (VerboseOutput)
{
Debug.WriteLine($"Válvula {valve.GetType().Name}: Apertura={valve.Opening:F2}, " +
$"Cerrada={valve.IsClosed}, Abierta={valve.IsFullyOpen}");
}
}
///
/// Actualiza propiedades específicas de tanques
///
private void UpdateTankProperties(IHydraulicTank tank)
{
// Calcular presión basada en nivel si no es presión fija
if (!tank.IsFixedPressure)
{
// P = ρgh + P_atmosferica
double pressureFromLevel = SimulationFluid.Rho * 9.80665 * tank.Level; // + presión atmosférica
tank.TankPressure = pressureFromLevel;
}
if (VerboseOutput)
{
Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " +
$"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}");
}
}
///
/// Aplica los resultados de la simulación a los objetos
///
private void ApplyResultsToObjects()
{
// Aplicar resultados a objetos hidráulicos específicos
foreach (var simObj in HydraulicSimObjects)
{
ApplyResultsToSimObject(simObj);
simObj.ApplySimulationResults();
}
// Aplicar resultados a objetos hidráulicos generales (legacy)
foreach (var obj in HydraulicObjects)
{
// Aplicar caudales y presiones calculados al objeto
ApplyResultsToObject(obj);
}
}
///
/// Aplica los resultados de la simulación a un objeto específico
///
private void ApplyResultsToSimObject(simHydraulicBase simObj)
{
// Aplicar resultados específicos según el tipo de objeto
if (simObj is simHydraulicPump pump)
{
// Buscar flujo y presión de la bomba en los resultados
string pumpBranchName = $"{pump.Nombre}_Pump";
if (LastSolutionResult.Flows.TryGetValue(pumpBranchName, out double flow))
{
pump.CurrentFlow = flow;
}
string inletNodeName = $"{pump.Nombre}_In";
if (LastSolutionResult.Pressures.TryGetValue(inletNodeName, out double pressure))
{
pump.CurrentPressure = pressure;
}
}
else if (simObj is simHydraulicTank tank)
{
// Buscar flujos de entrada y salida del tanque
// Esto requeriría mapeo de tuberías conectadas
// Por ahora, aplicar presión del tanque
string tankNodeName = $"{tank.Nombre}_Tank";
if (LastSolutionResult.Pressures.TryGetValue(tankNodeName, out double pressure))
{
tank.CurrentPressure = pressure;
}
}
else if (simObj is simHydraulicPipe pipe)
{
// Buscar flujo a través de la tubería
string pipeBranchName = $"{pipe.Nombre}_Pipe";
if (LastSolutionResult.Flows.TryGetValue(pipeBranchName, out double flow))
{
pipe.CurrentFlow = flow;
}
// Calcular pérdida de presión
string fromNode = pipe.ComponenteAId;
string toNode = pipe.ComponenteBId;
if (LastSolutionResult.Pressures.TryGetValue(fromNode, out double pressureA) &&
LastSolutionResult.Pressures.TryGetValue(toNode, out double pressureB))
{
pipe.PressureDrop = pressureA - pressureB;
}
}
}
///
/// Aplica los resultados a un objeto específico
///
private void ApplyResultsToObject(osBase obj)
{
// Aplicar resultados usando las interfaces hidráulicas
if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents)
{
// Llamar al método de aplicación de resultados del objeto
hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures);
}
// Aplicación específica por tipo de interfaz
if (obj is IHydraulicFlowReceiver flowReceiver)
{
// Buscar caudal asociado al objeto
string branchName = FindBranchNameForObject(obj);
if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName))
{
double flow = LastSolutionResult.Flows[branchName];
flowReceiver.SetFlow(flow);
}
}
if (obj is IHydraulicPressureReceiver pressureReceiver)
{
// Buscar presión asociada al objeto
string nodeName = FindNodeNameForObject(obj);
if (!string.IsNullOrEmpty(nodeName) && LastSolutionResult.Pressures.ContainsKey(nodeName))
{
double pressure = LastSolutionResult.Pressures[nodeName];
pressureReceiver.SetPressure(pressure);
}
}
}
///
/// Encuentra el nombre de rama asociado a un objeto
///
private string FindBranchNameForObject(osBase obj)
{
// Buscar en las ramas de la red
foreach (var branch in Network.Branches)
{
if (branch.Name.Contains(obj.Nombre))
{
return branch.Name;
}
}
// Fallback a nombre por defecto
return $"Branch_{obj.Nombre}";
}
///
/// Encuentra el nombre de nodo asociado a un objeto
///
private string FindNodeNameForObject(osBase obj)
{
// Buscar en los nodos de la red
foreach (var nodeKvp in Network.Nodes)
{
if (nodeKvp.Key.Contains(obj.Nombre))
{
return nodeKvp.Key;
}
}
// Fallback a nombre por defecto
return $"Node_{obj.Nombre}";
}
///
/// Establece el caudal en un objeto
///
private void SetObjectFlow(osBase obj, double flow)
{
if (obj is IHydraulicFlowReceiver flowReceiver)
{
flowReceiver.SetFlow(flow);
}
}
///
/// Establece la presión en un objeto
///
private void SetObjectPressure(osBase obj, double pressure)
{
if (obj is IHydraulicPressureReceiver pressureReceiver)
{
pressureReceiver.SetPressure(pressure);
}
}
#endregion
#region Public Methods
///
/// Fuerza la reconstrucción de la red en el próximo paso
///
public void InvalidateNetwork()
{
_networkNeedsRebuild = true;
}
///
/// Obtiene estadísticas de la simulación
///
public string GetSimulationStats()
{
return $"Objetos hidráulicos: {HydraulicObjects.Count}\n" +
$"Nodos: {Network.Nodes.Count}\n" +
$"Ramas: {Network.Branches.Count}\n" +
$"Última simulación convergió: {LastSolutionResult.Converged}\n" +
$"Iteraciones: {LastSolutionResult.Iterations}\n" +
$"Residual: {LastSolutionResult.Residual:E3}\n" +
$"Tiempo global: {GlobalTime:F2}s\n" +
$"Pasos: {_stepCount}";
}
///
/// Reinicia la simulación
///
public void Reset()
{
GlobalTime = 0.0f;
_stepCount = 0;
_networkNeedsRebuild = true;
// Reinicializar resultado
LastSolutionResult = new SolutionResult(false)
{
ErrorMessage = "Simulación reiniciada"
};
Debug.WriteLine("Simulación hidráulica reiniciada");
}
#endregion
#region IDisposable
private bool _disposed = false;
///
/// Libera recursos del gestor de simulación
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Limpiar recursos gestionados
HydraulicObjects?.Clear();
_objectMapping?.Clear();
Network = null;
_stopwatch?.Stop();
Debug.WriteLine("HydraulicSimulationManager disposed");
}
_disposed = true;
}
}
#endregion
#region Hydraulic Object Creation
///
/// Lista de objetos hidráulicos específicos del simulador
///
public List HydraulicSimObjects { get; private set; } = new List();
///
/// Crea una bomba hidráulica en la simulación
///
public simHydraulicPump AddPump(double pumpHead, double maxFlow, double speedRatio, bool isRunning, int direction)
{
var pump = new simHydraulicPump(this)
{
PumpHead = pumpHead,
MaxFlow = maxFlow,
SpeedRatio = speedRatio,
IsRunning = isRunning,
PumpDirection = direction
};
HydraulicSimObjects.Add(pump);
_networkNeedsRebuild = true;
Debug.WriteLine($"Bomba hidráulica creada: H={pumpHead}m, Q={maxFlow}m³/s");
return pump;
}
///
/// Crea un tanque hidráulico en la simulación
///
public simHydraulicTank AddTank(double pressure, double currentLevel, double maxLevel, double minLevel, double crossSectionalArea, bool isFixedPressure)
{
var tank = new simHydraulicTank(this)
{
TankPressure = pressure,
CurrentLevel = currentLevel,
MaxLevel = maxLevel,
MinLevel = minLevel,
CrossSectionalArea = crossSectionalArea,
IsFixedPressure = isFixedPressure
};
HydraulicSimObjects.Add(tank);
_networkNeedsRebuild = true;
Debug.WriteLine($"Tanque hidráulico creado: P={pressure}Pa, Nivel={currentLevel}m");
return tank;
}
///
/// Crea una tubería hidráulica en la simulación
///
public simHydraulicPipe AddPipe(double length, double diameter, double roughness, string componenteAId, string componenteBId)
{
var pipe = new simHydraulicPipe(this)
{
Length = length,
Diameter = diameter,
Roughness = roughness,
ComponenteAId = componenteAId,
ComponenteBId = componenteBId
};
HydraulicSimObjects.Add(pipe);
_networkNeedsRebuild = true;
Debug.WriteLine($"Tubería hidráulica creada: L={length}m, D={diameter}m");
return pipe;
}
///
/// Remueve un objeto hidráulico de la simulación
///
public void Remove(simHydraulicBase hydraulicObject)
{
if (hydraulicObject != null && HydraulicSimObjects.Contains(hydraulicObject))
{
HydraulicSimObjects.Remove(hydraulicObject);
_networkNeedsRebuild = true;
Debug.WriteLine($"Objeto hidráulico removido: {hydraulicObject.SimObjectType}");
}
}
///
/// Limpia todos los objetos hidráulicos específicos
///
public void ClearHydraulicSimObjects()
{
HydraulicSimObjects.Clear();
_networkNeedsRebuild = true;
Debug.WriteLine("Todos los objetos hidráulicos específicos han sido limpiados");
}
#endregion
}
}