using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Media;
using CtrEditor.HydraulicSimulator;
using CtrEditor.ObjetosSim;
using CtrEditor.FuncionesBase;
using HydraulicSimulator.Models;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using CommunityToolkit.Mvvm.ComponentModel;
namespace CtrEditor.ObjetosSim
{
///
/// Bomba hidráulica que implementa las interfaces del simulador hidráulico
///
public partial class osHydPump : osBase, IHydraulicComponent, IHydraulicFlowReceiver, IHydraulicPressureReceiver, IosBase
{
// Referencia al objeto de simulación hidráulica específico
private simHydraulicPump SimHydraulicPump;
public static string NombreCategoria() => "Componentes Hidráulicos";
public static string NombreClase()
{
return "Bomba Hidráulica";
}
// Properties
[ObservableProperty]
[property: JsonIgnore]
[property: Category("Apariencia")]
[property: Description("Imagen visual")]
[property: Name("Imagen")]
public ImageSource imageSource_oculta;
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Tamaño visual de la bomba")]
[property: Name("Tamaño")]
public float tamano;
public override void OnResize(float Delta_Width, float Delta_Height)
{
Tamano += Delta_Width + Delta_Height;
}
private double _pumpHead = 50.0; // metros
private double _maxFlow = 0.01; // m³/s (36 m³/h)
private double _speedRatio = 1.0;
private bool _isRunning = true;
private int _pumpDirection = 1;
private double _currentFlow = 0.0;
private double _currentPressure = 0.0;
// Propiedades visuales específicas de la bomba
[ObservableProperty]
[property: Category("🎨 Apariencia")]
[property: Description("Color visual de la bomba")]
[property: Name("Color")]
private Brush colorButton_oculto = Brushes.Blue;
[Category("🔧 Bomba Hidráulica")]
[DisplayName("Cabeza de bomba")]
[Description("Cabeza de la bomba a caudal cero (m)")]
public double PumpHead
{
get => _pumpHead;
set
{
if (SetProperty(ref _pumpHead, Math.Max(0, value)))
{
InvalidateHydraulicNetwork();
}
}
}
[Category("🔧 Bomba Hidráulica")]
[DisplayName("Caudal máximo")]
[Description("Caudal máximo de la bomba (m³/s)")]
public double MaxFlow
{
get => _maxFlow;
set
{
// Si el valor es mayor a 1, asumir que está en m³/h y convertir a m³/s
double valueInM3s = value > 1.0 ? value / 3600.0 : value;
if (SetProperty(ref _maxFlow, Math.Max(0.0001, valueInM3s)))
{
InvalidateHydraulicNetwork();
Debug.WriteLine($"Bomba {Nombre}: MaxFlow establecido a {_maxFlow:F6} m³/s ({_maxFlow * 3600:F2} m³/h)");
}
}
}
[Category("🔧 Bomba Hidráulica")]
[DisplayName("Velocidad relativa")]
[Description("Velocidad relativa de la bomba (0.0 a 1.0)")]
public double SpeedRatio
{
get => _speedRatio;
set
{
SetProperty(ref _speedRatio, Math.Max(0.0, Math.Min(1.0, value)));
}
}
[Category("🔧 Bomba Hidráulica")]
[DisplayName("Funcionando")]
[Description("Indica si la bomba está encendida")]
public bool IsRunning
{
get => _isRunning;
set
{
if (SetProperty(ref _isRunning, value))
{
UpdatePumpImage();
}
}
}
private void UpdatePumpImage()
{
if (IsRunning)
ImageSource_oculta = ImageFromPath("/imagenes/pump_run.png");
else
ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png");
}
[Category("🔧 Bomba Hidráulica")]
[DisplayName("Dirección")]
[Description("Dirección de la bomba (+1 o -1)")]
[ItemsSource(typeof(PumpDirectionItemsSource))]
public int PumpDirection
{
get => _pumpDirection;
set
{
if (SetProperty(ref _pumpDirection, value == -1 ? -1 : 1))
{
InvalidateHydraulicNetwork();
}
}
}
[Category("📊 Estado Actual")]
[DisplayName("Caudal actual")]
[Description("Caudal actual de la bomba (m³/s)")]
[JsonIgnore]
public double CurrentFlow
{
get => _currentFlow;
private set => SetProperty(ref _currentFlow, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Presión actual")]
[Description("Presión actual en la bomba (Pa)")]
[JsonIgnore]
public double CurrentPressure
{
get => _currentPressure;
private set => SetProperty(ref _currentPressure, value);
}
[Category("📊 Estado Actual")]
[DisplayName("Presión (bar)")]
[Description("Presión actual en la bomba (bar)")]
[JsonIgnore]
public double CurrentPressureBar => CurrentPressure / 100000.0;
[Category("📊 Estado Actual")]
[DisplayName("Caudal (L/min)")]
[Description("Caudal actual en L/min")]
[JsonIgnore]
public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min
[Category("📊 Estado Actual")]
[DisplayName("NPSH Disponible")]
[Description("NPSH disponible calculado (m)")]
[JsonIgnore]
public double NPSHAvailable
{
get
{
var (inletNodeName, _) = GetConnectedNodeNames();
if (!string.IsNullOrEmpty(inletNodeName) &&
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
{
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
return pump.CalculateNPSHAvailable(suctionPressure, hydraulicSimulationManager.SimulationFluid);
}
return 0.0;
}
}
[Category("📊 Estado Actual")]
[DisplayName("Factor Cavitación")]
[Description("Factor de cavitación (1=sin cavitación, 0=cavitación total)")]
[JsonIgnore]
public double CavitationFactor
{
get
{
var (inletNodeName, _) = GetConnectedNodeNames();
if (!string.IsNullOrEmpty(inletNodeName) &&
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
{
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
return pump.GetCavitationFactor(suctionPressure, hydraulicSimulationManager.SimulationFluid);
}
return 1.0;
}
}
[Category("📊 Estado Actual")]
[DisplayName("Puede Operar")]
[Description("Indica si la bomba puede operar sin cavitación")]
[JsonIgnore]
public bool CanOperateWithoutCavitation
{
get
{
var (inletNodeName, _) = GetConnectedNodeNames();
if (!string.IsNullOrEmpty(inletNodeName) &&
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
{
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
return pump.CanOperateWithoutCavitation(suctionPressure, hydraulicSimulationManager.SimulationFluid);
}
return false;
}
}
[Category("📊 Estado Actual")]
[DisplayName("Estado Cavitación")]
[Description("Estado actual de cavitación de la bomba")]
[JsonIgnore]
public string CavitationStatus
{
get
{
if (!IsRunning) return "Bomba detenida";
var factor = CavitationFactor;
var canOperate = CanOperateWithoutCavitation;
var npshAvailable = NPSHAvailable;
if (canOperate && factor >= 0.9)
return "✅ Operación normal";
else if (factor >= 0.5)
return $"⚠️ Riesgo cavitación (Factor: {factor:F2})";
else
return $"❌ NPSH insuficiente ({npshAvailable:F2}m)";
}
}
[Category("📊 Estado Actual")]
[DisplayName("Estado Detallado")]
[Description("Información detallada del estado operacional")]
[JsonIgnore]
public string DetailedStatus
{
get
{
if (!IsRunning) return "Bomba detenida";
var factor = CavitationFactor;
var npshAvailable = NPSHAvailable;
var flow = CurrentFlowLMin;
return $"NPSH: {npshAvailable:F2}m | Factor: {factor:F2} | Flujo: {flow:F1} L/min";
}
}
// Propiedades de fluido actual
private FluidProperties _currentFluid = new FluidProperties(FluidType.Air);
[Category("🧪 Fluido Actual")]
[DisplayName("Tipo de fluido")]
[Description("Tipo de fluido que está bombeando")]
[ReadOnly(true)]
public FluidType CurrentFluidType => _currentFluid.Type;
[Category("🧪 Fluido Actual")]
[DisplayName("Descripción")]
[Description("Descripción del fluido que se está bombeando")]
[ReadOnly(true)]
public string CurrentFluidDescription => _currentFluid.Description;
[Category("📊 Estado Actual")]
[DisplayName("Factor Viscosidad")]
[Description("Factor de eficiencia debido a la viscosidad del fluido")]
[ReadOnly(true)]
public double ViscosityEffect
{
get
{
// Factor basado en viscosidad del fluido vs agua
var waterViscosity = 0.001; // Pa·s
var currentViscosity = _currentFluid.Viscosity;
return Math.Max(0.1, Math.Min(1.0, waterViscosity / currentViscosity));
}
}
[Category("📊 Estado Actual")]
[DisplayName("Flujo Efectivo (L/min)")]
[Description("Flujo real ajustado por viscosidad")]
[ReadOnly(true)]
public double EffectiveFlowLMin => CurrentFlowLMin * ViscosityEffect;
[Category("📊 Estado Actual")]
[DisplayName("Temperatura (°C)")]
[Description("Temperatura del fluido bombeado")]
[ReadOnly(true)]
public double FluidTemperature => _currentFluid.Temperature;
[Category("📊 Estado Actual")]
[DisplayName("Densidad (kg/L)")]
[Description("Densidad del fluido bombeado")]
[ReadOnly(true)]
public double FluidDensity => _currentFluid.Density;
[Category("📊 Estado Actual")]
[DisplayName("Brix (%)")]
[Description("Concentración en grados Brix (para jarabes)")]
[ReadOnly(true)]
public double FluidBrix => _currentFluid.Brix;
[Category("📊 Estado Actual")]
[DisplayName("Concentración (%)")]
[Description("Concentración en porcentaje (para químicos)")]
[ReadOnly(true)]
public double FluidConcentration => _currentFluid.Concentration;
[Category("📊 Estado Actual")]
[DisplayName("Viscosidad (cP)")]
[Description("Viscosidad dinámica en centipoise")]
[ReadOnly(true)]
public double FluidViscosity => _currentFluid.Viscosity;
[Category("📊 Estado Actual")]
[DisplayName("Descripción del Fluido")]
[Description("Descripción completa del fluido")]
[ReadOnly(true)]
public string FluidDescription => _currentFluid.Description;
// Constructor y Métodos Base
private string nombre = NombreClase();
[Category("🏷️ Identificación")]
[Description("Nombre identificativo del objeto")]
[Name("Nombre")]
public override string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value);
}
public osHydPump()
{
Nombre = "Bomba Hidráulica";
Tamano = 0.30f;
// Inicializar dimensiones usando las propiedades de osBase
Ancho = 0.15f;
Alto = 0.10f;
Angulo = 0f;
// Asegurar que el movimiento esté habilitado
Lock_movement = false;
ImageSource_oculta = ImageFromPath("/imagenes/pump_stop.png");
}
public override void UpdateGeometryStart()
{
// Se llama cuando inicia la simulación - crear objeto hidráulico si no existe
if (SimHydraulicPump == null)
{
SimHydraulicPump = hydraulicSimulationManager.AddPump(PumpHead, MaxFlow, SpeedRatio, IsRunning, PumpDirection);
SimHydraulicPump.SimObjectType = "HydraulicPump";
SimHydraulicPump.WpfObject = this;
SimHydraulicPump.Nombre = Nombre;
}
else
{
// Actualizar propiedades si el objeto ya existe
SimHydraulicPump.PumpHead = PumpHead;
SimHydraulicPump.MaxFlow = MaxFlow;
SimHydraulicPump.SpeedRatio = SpeedRatio;
SimHydraulicPump.IsRunning = IsRunning;
SimHydraulicPump.PumpDirection = PumpDirection;
}
}
public override void UpdateGeometryStep()
{
// Los objetos hidráulicos actualizan sus resultados
// a través de ApplySimulationResults() desde HydraulicSimulationManager
}
// Método para actualizar fluido desde tanque de succión
public void UpdateFluidFromSuction()
{
if (_mainViewModel?.ObjetosSimulables == null) return;
try
{
// Buscar el componente conectado en la succión (entrada de la bomba)
var suctionComponent = FindSuctionComponent();
if (suctionComponent is osHydTank tank)
{
var sourceFluid = tank.CurrentOutputFluid;
if (sourceFluid != null)
{
_currentFluid = sourceFluid.Clone();
OnPropertyChanged(nameof(CurrentFluidType));
OnPropertyChanged(nameof(CurrentFluidDescription));
OnPropertyChanged(nameof(ViscosityEffect));
OnPropertyChanged(nameof(EffectiveFlowLMin));
OnPropertyChanged(nameof(FluidTemperature));
OnPropertyChanged(nameof(FluidDensity));
// Actualizar color de la bomba según el fluido
UpdatePumpColorFromFluid();
}
}
}
catch (Exception ex)
{
// Mantener fluido por defecto en caso de error
System.Diagnostics.Debug.WriteLine($"Error updating pump fluid: {ex.Message}");
}
}
private void UpdatePumpColorFromFluid()
{
if (!IsRunning)
{
ColorButton_oculto = Brushes.Gray;
return;
}
// Si no hay flujo, verificar cavitación primero
if (Math.Abs(CurrentFlow) < 1e-6)
{
if (hydraulicSimulationManager?.EnableNPSHVerification == true)
{
var cavitationFactor = CavitationFactor;
if (cavitationFactor < 0.1)
ColorButton_oculto = Brushes.Red; // Cavitación severa
else if (cavitationFactor < 0.5)
ColorButton_oculto = Brushes.Orange; // Riesgo de cavitación
else
ColorButton_oculto = Brushes.Yellow; // Standby
}
else
{
ColorButton_oculto = Brushes.Yellow; // Standby
}
return;
}
// Colorear según tipo de fluido bombeado
try
{
var fluidColorHex = _currentFluid.Color;
var fluidColor = (Color)System.Windows.Media.ColorConverter.ConvertFromString(fluidColorHex);
ColorButton_oculto = new SolidColorBrush(fluidColor);
}
catch
{
ColorButton_oculto = Brushes.Green; // Color por defecto si hay flujo
}
}
private IHydraulicComponent FindSuctionComponent()
{
// Buscar tuberías conectadas a esta bomba
var connectedPipes = _mainViewModel.ObjetosSimulables
.OfType()
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
.ToList();
foreach (var pipe in connectedPipes)
{
// La succión es donde el flujo viene HACIA la bomba
string suctionComponentName = null;
if (pipe.Id_ComponenteB == Nombre && pipe.CurrentFlow >= 0)
{
// Flujo de A hacia B (hacia esta bomba)
suctionComponentName = pipe.Id_ComponenteA;
}
else if (pipe.Id_ComponenteA == Nombre && pipe.CurrentFlow < 0)
{
// Flujo de B hacia A (hacia esta bomba)
suctionComponentName = pipe.Id_ComponenteB;
}
if (!string.IsNullOrEmpty(suctionComponentName))
{
return _mainViewModel.ObjetosSimulables
.OfType()
.FirstOrDefault(c => c is osBase osb && osb.Nombre == suctionComponentName);
}
}
return null;
}
public override void UpdateControl(int elapsedMilliseconds)
{
// Actualizar propiedades desde la simulación hidráulica
if (SimHydraulicPump != null)
{
CurrentFlow = SimHydraulicPump.CurrentFlow;
CurrentPressure = SimHydraulicPump.CurrentPressure;
}
// Actualizar propiedades del fluido cada ciclo
UpdateFluidFromSuction();
// Actualizar propiedades de UI
OnPropertyChanged(nameof(CurrentFlowLMin));
OnPropertyChanged(nameof(EffectiveFlowLMin));
// Actualizar el color según el estado (ya se hace en UpdatePumpColorFromFluid)
if (!IsRunning)
{
ColorButton_oculto = Brushes.Gray; // Bomba apagada
}
}
public override void ucLoaded()
{
// Objeto hidráulico se crea en UpdateGeometryStart cuando inicia la simulación
base.ucLoaded();
UpdatePumpImage();
}
public override void ucUnLoaded()
{
// Remover objeto hidráulico de la simulación
if (SimHydraulicPump != null)
{
hydraulicSimulationManager.Remove(SimHydraulicPump);
SimHydraulicPump = null;
}
}
// IHydraulicComponent Implementation
[JsonIgnore]
public bool HasHydraulicComponents => true;
public List GetHydraulicNodes()
{
var nodes = new List();
// Las bombas no crean nodos propios - deben estar conectadas a otros componentes para funcionar
if (!HasConnectedComponents())
{
Debug.WriteLine($"Bomba {Nombre}: Sin componentes conectados - no puede funcionar");
}
else
{
//Debug.WriteLine($"Bomba {Nombre}: Conectada a otros componentes - lista para operar");
}
return nodes;
}
public List GetHydraulicElements()
{
var elements = new List();
// Solo crear elementos si la bomba está conectada a otros componentes
if (!HasConnectedComponents())
{
Debug.WriteLine($"Bomba {Nombre}: Sin conexiones - no se puede crear elemento hidráulico");
return elements;
}
if (HasHydraulicComponents)
{
// Calcular velocidad efectiva basada en el estado de la bomba
double effectiveSpeed = IsRunning ? SpeedRatio : 0.0;
// Asegurar que la velocidad esté en rango válido
effectiveSpeed = Math.Max(0.0, Math.Min(1.0, effectiveSpeed));
// Obtener los nombres de nodos correctos basados en las conexiones
var (inletNode, outletNode) = GetConnectedNodeNames();
// Verificar que tenemos nodos válidos
if (string.IsNullOrEmpty(inletNode) || string.IsNullOrEmpty(outletNode))
{
Debug.WriteLine($"Bomba {Nombre}: Nodos de conexión inválidos - inlet: '{inletNode}', outlet: '{outletNode}'");
return elements;
}
// Crear bomba con parámetros actuales
Element pump;
if (hydraulicSimulationManager.EnableNPSHVerification)
{
// Usar bomba con verificación de NPSH
pump = new PumpHQWithSuctionCheck(
h0: PumpHead,
q0: MaxFlow,
speedRel: effectiveSpeed,
direction: PumpDirection,
enableNpshCheck: true
);
}
else
{
// Usar bomba estándar
pump = new PumpHQ(
h0: PumpHead,
q0: MaxFlow,
speedRel: effectiveSpeed,
direction: PumpDirection
);
}
// Asignar nombres de nodos para verificación de NPSH
if (pump is PumpHQ pumpHQ)
{
pumpHQ.InletNodeName = inletNode;
pumpHQ.OutletNodeName = outletNode;
}
var pumpElement = new HydraulicElementDefinition(
$"{Nombre}_Pump",
inletNode,
outletNode,
pump,
$"Bomba {Nombre}"
);
elements.Add(pumpElement);
Debug.WriteLine($"Bomba {Nombre}: Creando elemento hidráulico - H0={PumpHead}m, Q0={MaxFlow:F6}m³/s ({MaxFlow*3600:F2}m³/h), Velocidad={effectiveSpeed:F2}, Dirección={PumpDirection}");
Debug.WriteLine($"Bomba {Nombre}: Conectando {inletNode} -> {outletNode}");
}
return elements;
}
public void UpdateHydraulicProperties()
{
// Aquí se pueden hacer validaciones o ajustes antes de la simulación
if (!IsRunning)
{
SpeedRatio = 0.0;
}
if (SpeedRatio < 0.0) SpeedRatio = 0.0;
if (SpeedRatio > 1.0) SpeedRatio = 1.0;
// Las verificaciones de NPSH ahora se muestran a través de propiedades
// Se eliminaron los Debug.WriteLine para evitar saturación del sistema
//Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}");
}
public void ApplyHydraulicResults(Dictionary flows, Dictionary pressures)
{
// Buscar resultados para esta bomba
string pumpBranchName = $"{Nombre}_Pump";
var (inletNodeName, outletNodeName) = GetConnectedNodeNames();
if (flows.ContainsKey(pumpBranchName))
{
CurrentFlow = flows[pumpBranchName];
//Debug.WriteLine($"Bomba {Nombre}: Aplicando caudal={CurrentFlow:F6} m³/s ({CurrentFlowLMin:F2} L/min)");
}
if (pressures.ContainsKey(inletNodeName))
{
CurrentPressure = pressures[inletNodeName];
//Debug.WriteLine($"Bomba {Nombre}: Presión entrada={CurrentPressure:F2} Pa ({CurrentPressureBar:F2} bar)");
}
else if (pressures.ContainsKey(outletNodeName))
{
CurrentPressure = pressures[outletNodeName];
//Debug.WriteLine($"Bomba {Nombre}: Presión salida={CurrentPressure:F2} Pa ({CurrentPressureBar:F2} bar)");
}
// Disparar PropertyChanged para actualizar el UI
OnPropertyChanged(nameof(CurrentFlow));
OnPropertyChanged(nameof(CurrentFlowLMin));
OnPropertyChanged(nameof(CurrentPressure));
OnPropertyChanged(nameof(CurrentPressureBar));
}
// IHydraulicFlowReceiver Implementation
public void SetFlow(double flow)
{
CurrentFlow = flow;
}
public double GetFlow()
{
return CurrentFlow;
}
// IHydraulicPressureReceiver Implementation
public void SetPressure(double pressure)
{
CurrentPressure = pressure;
}
public double GetPressure()
{
return CurrentPressure;
}
// Helper Methods
///
/// Verifica si la bomba tiene componentes conectados a través de tuberías
///
private bool HasConnectedComponents()
{
if (_mainViewModel == null) return false;
// Buscar tuberías que conecten esta bomba con otros componentes
var connectedPipes = _mainViewModel.ObjetosSimulables
.OfType()
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
.ToList();
return connectedPipes.Any();
}
///
/// Obtiene los nombres de nodos correctos para la bomba basándose en las conexiones
///
private (string inletNode, string outletNode) GetConnectedNodeNames()
{
if (_mainViewModel == null)
return (string.Empty, string.Empty);
string inletNode = string.Empty;
string outletNode = string.Empty;
// Buscar tuberías conectadas a esta bomba
var connectedPipes = _mainViewModel.ObjetosSimulables
.OfType()
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
.ToList();
foreach (var pipe in connectedPipes)
{
if (pipe.Id_ComponenteB == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteA))
{
// Esta bomba es el destino, el componente A es la fuente (inlet)
inletNode = pipe.Id_ComponenteA;
//Debug.WriteLine($"Bomba {Nombre}: Nodo inlet identificado como '{inletNode}'");
}
else if (pipe.Id_ComponenteA == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteB))
{
// Esta bomba es la fuente, el componente B es el destino (outlet)
outletNode = pipe.Id_ComponenteB;
//Debug.WriteLine($"Bomba {Nombre}: Nodo outlet identificado como '{outletNode}'");
}
}
return (inletNode, outletNode);
}
private void InvalidateHydraulicNetwork()
{
hydraulicSimulationManager?.InvalidateNetwork();
}
}
///
/// Proveedor de items para la dirección de la bomba
///
public class PumpDirectionItemsSource : IItemsSource
{
public Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection GetValues()
{
var items = new Xceed.Wpf.Toolkit.PropertyGrid.Attributes.ItemCollection();
items.Add(1, "Adelante (+1)");
items.Add(-1, "Atrás (-1)");
return items;
}
}
}