Cambio de algoritmia en el descarte de paradas posibles antes del recalculo de soluciones: se analiza las 48 paradas de cada arista en bsuqueda de rutas directas scoreandolas antes de entrar en el recorrido BFS con transbordos.

configuraciones guardadas cambios

hora simualda en central
This commit is contained in:
2026-09-17 13:27:39 +02:00
parent 7090dfa9be
commit f63a1b8b08
13 changed files with 331 additions and 119 deletions

View File

@@ -0,0 +1,156 @@
using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;
using RutasDBUS.Modelos.Historial;
namespace RutasDBUS.Components.Pages;
public partial class PlanificadorRutas
{
private ConfiguracionHistorialRuta? _configuracionAvanzadaAplicada;
private readonly List<(string Campo, string Antes, string Despues)> _cambiosConfiguracionSinGuardar = new();
private bool _editandoConfiguracionAvanzada;
private bool _forzarPersistenciaConfiguracion;
private bool _mostrarConfirmacionCerrarConfiguracion;
private static readonly HashSet<string> PropiedadesFueraConfiguracionAvanzada = new(StringComparer.Ordinal)
{
nameof(ConfiguracionHistorialRuta.MostrarParadas),
nameof(ConfiguracionHistorialRuta.UsarColorRealLineaEnItinerarios),
nameof(ConfiguracionHistorialRuta.CriterioPrioritarioRuta),
nameof(ConfiguracionHistorialRuta.PreferenciaEtiquetaRuta)
};
private void ToggleConfigPanel()
{
if (!_mostrarConfigAvanzada)
{
_configuracionAvanzadaAplicada = CrearConfiguracionLocal().Clonar();
_editandoConfiguracionAvanzada = true;
_cambiosConfiguracionSinGuardar.Clear();
_mostrarConfirmacionCerrarConfiguracion = false;
_mostrarConfigAvanzada = true;
return;
}
SolicitarCierreConfiguracionAvanzada();
}
private bool SolicitarCierreConfiguracionAvanzada()
{
ActualizarCambiosConfiguracionSinGuardar();
if (_cambiosConfiguracionSinGuardar.Count > 0)
{
_mostrarConfirmacionCerrarConfiguracion = true;
return false;
}
CerrarConfiguracionAvanzada();
return true;
}
private void ActualizarCambiosConfiguracionSinGuardar()
{
_cambiosConfiguracionSinGuardar.Clear();
if (_configuracionAvanzadaAplicada is null)
return;
var actual = CrearConfiguracionLocal();
foreach (var propiedad in typeof(ConfiguracionHistorialRuta).GetProperties())
{
if (propiedad.Name.StartsWith("Version", StringComparison.Ordinal) ||
PropiedadesFueraConfiguracionAvanzada.Contains(propiedad.Name))
{
continue;
}
var valorAntes = propiedad.GetValue(_configuracionAvanzadaAplicada);
var valorDespues = propiedad.GetValue(actual);
if (JsonSerializer.Serialize(valorAntes) == JsonSerializer.Serialize(valorDespues))
continue;
var nombre = Regex.Replace(propiedad.Name, "([a-z0-9])([A-Z])", "$1 $2");
_cambiosConfiguracionSinGuardar.Add((
nombre,
FormatearValorConfiguracion(valorAntes),
FormatearValorConfiguracion(valorDespues)));
}
}
private static string FormatearValorConfiguracion(object? valor)
=> valor switch
{
null => "-",
bool booleano => booleano ? "Sí" : "No",
IEnumerable<int> numeros => numeros.Any() ? string.Join(", ", numeros) : "Ninguno",
IFormattable formateable => formateable.ToString(null, CultureInfo.InvariantCulture),
_ => valor.ToString() ?? "-"
};
private void CancelarCierreConfiguracionAvanzada()
{
_mostrarConfirmacionCerrarConfiguracion = false;
_cambiosConfiguracionSinGuardar.Clear();
}
private async Task DescartarCambiosConfiguracionAvanzadaAsync()
{
if (_configuracionAvanzadaAplicada is not null)
{
var actual = CrearConfiguracionLocal();
var restaurada = _configuracionAvanzadaAplicada.Clonar();
restaurada.MostrarParadas = actual.MostrarParadas;
restaurada.UsarColorRealLineaEnItinerarios = actual.UsarColorRealLineaEnItinerarios;
restaurada.CriterioPrioritarioRuta = actual.CriterioPrioritarioRuta;
restaurada.PreferenciaEtiquetaRuta = actual.PreferenciaEtiquetaRuta;
AplicarConfiguracionLocal(restaurada);
if (_usarHoraSimulada)
StopClock();
else
StartClock();
await RefrescarHoraUIAsync();
await PersistirConfiguracionConfirmadaAsync();
}
CerrarConfiguracionAvanzada();
StateHasChanged();
}
private void CerrarConfiguracionAvanzada()
{
_mostrarConfirmacionCerrarConfiguracion = false;
_mostrarConfiguracionPendientesRuta = false;
_selectorColorPendienteRuta = null;
_cambiosConfiguracionSinGuardar.Clear();
_configuracionAvanzadaAplicada = null;
_editandoConfiguracionAvanzada = false;
_mostrarConfigAvanzada = false;
}
private async Task PersistirConfiguracionConfirmadaAsync()
{
var forzarAnterior = _forzarPersistenciaConfiguracion;
_forzarPersistenciaConfiguracion = true;
try
{
await PersistirConfiguracionAsync();
}
finally
{
_forzarPersistenciaConfiguracion = forzarAnterior;
}
}
private void MarcarConfiguracionAvanzadaComoGuardada()
{
if (!_editandoConfiguracionAvanzada)
return;
_configuracionAvanzadaAplicada = CrearConfiguracionLocal().Clonar();
_cambiosConfiguracionSinGuardar.Clear();
_mostrarConfirmacionCerrarConfiguracion = false;
}
}

View File

@@ -39,10 +39,6 @@ public partial class PlanificadorRutas : ComponentBase
private const string DashArrayPiePunteado = "1 7";
private bool _mostrarConfigAvanzada = false;
/// <summary>
/// Alterna la configuracion panel.
/// </summary>
private void ToggleConfigPanel() => _mostrarConfigAvanzada = !_mostrarConfigAvanzada;
private int _nivelBrilloMapa = 0;
@@ -332,7 +328,7 @@ public partial class PlanificadorRutas : ComponentBase
{
new RealTimeMap.BasemapLayer
{
url = "https://osm.tecnosis.net/osm_tiles/{z}/{x}/{y}.png",
url = "http://192.168.41.12/osm_tiles/{z}/{x}/{y}.png",
attribution = "© OpenStreetMap",
title = "",
detectRetina = true

View File

@@ -150,7 +150,8 @@ public partial class PlanificadorRutas
_mostrarInformacionDerivacionHistorial = false;
_historialLocalCargando = false;
StopRefrescoHistorialCompartido();
await RestaurarConfiguracionTemporalHistorialAsync();
// Cerrar el panel no debe separar la configuracion visible de la ruta calculada.
// La configuracion previa solo se recupera al elegir expresamente "Actual".
return;
}
@@ -1572,25 +1573,13 @@ public partial class PlanificadorRutas
{
ModoConfiguracionHistorial.Guardada => CrearConfiguracionGuardadaHistorial(item),
ModoConfiguracionHistorial.Predeterminada =>
CrearConfiguracionPredeterminadaParaHistorial(item),
CrearConfiguracionPredeterminadaParaHistorial(),
_ => null
};
if (configuracionHistorial is not null)
{
AplicarConfiguracionLocal(configuracionHistorial);
// El factor cargado desde un historial ya se conservaba al cerrar el panel.
if (_modoConfiguracionHistorial == ModoConfiguracionHistorial.Guardada &&
_configuracionTemporalPreviaHistorial is not null)
{
_configuracionTemporalPreviaHistorial.FactorEquilibradoAndarBus =
_factorEquilibradoAndarBus;
_configuracionTemporalPreviaHistorial.VersionFactorEquilibrado =
VersionPersistenciaFactorEquilibrado;
}
}
_busFilteredCache.Clear();
SincronizarSegundosPorParadaConHorarios();
@@ -1654,29 +1643,8 @@ public partial class PlanificadorRutas
return configuracion;
}
private ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaParaHistorial(
RegistroHistorialRuta item)
{
var predeterminada = CrearConfiguracionPredeterminadaHistorial();
var guardada = CrearConfiguracionGuardadaHistorial(item);
predeterminada.UsarHoraSimulada = guardada.UsarHoraSimulada;
if (guardada.UsarHoraSimulada)
{
predeterminada.FechaSimulada = guardada.FechaSimulada;
predeterminada.HoraSimulada = guardada.HoraSimulada;
}
else
{
var ahora = DateTime.Now;
predeterminada.FechaSimulada = DateOnly.FromDateTime(ahora)
.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
predeterminada.HoraSimulada = TimeOnly.FromDateTime(ahora)
.ToString("HH:mm:ss", CultureInfo.InvariantCulture);
}
return predeterminada;
}
private ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaParaHistorial()
=> CrearConfiguracionPredeterminadaHistorial();
private void CapturarConfiguracionTemporalAntesDeHistorial()
{
@@ -1687,37 +1655,6 @@ public partial class PlanificadorRutas
_configuracionParametrosActualesHistorial = CrearConfiguracionLocal();
}
private async Task RestaurarConfiguracionTemporalHistorialAsync()
{
var configuracionPrevia = _configuracionTemporalPreviaHistorial;
_configuracionTemporalPreviaHistorial = null;
_configuracionParametrosActualesHistorial = null;
if (configuracionPrevia is null)
return;
var mostrarParadasAntes = _mostrarParadas;
AplicarConfiguracionLocal(configuracionPrevia);
if (_mapa is not null && mostrarParadasAntes != _mostrarParadas)
{
if (_mostrarParadas)
await DibujarParadasAsync();
else
await OcultarParadasAsync();
await SincronizarMarcadoresOrigenDestinoJsAsync();
}
StopClock();
if (_usarHoraSimulada)
await RefrescarHoraUIAsync();
else
StartClock();
await PersistirConfiguracionAsync();
}
private RegistroHistorialRuta? ConstruirRegistroHistorialActual()
{
if (_coordenadasOrigen is null || _coordenadasDestino is null)

View File

@@ -1506,7 +1506,7 @@ public partial class PlanificadorRutas : ComponentBase
private bool _elevPopupMinimizado = false;
private const string _versionApp = "(v-20260916a)";
private const string _versionApp = "(v-20260917a)";
private double? _paradaElevIdee;

View File

@@ -54,7 +54,8 @@ public partial class PlanificadorRutas
/// </summary>
private async Task PersistirConfiguracionAsync()
{
if (!_configuracionLocalCargada)
if (!_configuracionLocalCargada ||
(_editandoConfiguracionAvanzada && !_forzarPersistenciaConfiguracion))
return;
try
@@ -454,7 +455,8 @@ public partial class PlanificadorRutas
SincronizarTextosPenalizacionPie();
SincronizarSegundosPorParadaConHorarios();
await RefrescarHoraUIAsync();
await PersistirConfiguracionAsync();
await PersistirConfiguracionConfirmadaAsync();
MarcarConfiguracionAvanzadaComoGuardada();
await PersistirModoConfiguracionHistorialAsync();
StateHasChanged();
}
@@ -476,7 +478,8 @@ public partial class PlanificadorRutas
SincronizarTextosPenalizacionPie();
SincronizarSegundosPorParadaConHorarios();
await RefrescarHoraUIAsync();
await PersistirConfiguracionAsync();
await PersistirConfiguracionConfirmadaAsync();
MarcarConfiguracionAvanzadaComoGuardada();
await PersistirModoConfiguracionHistorialAsync();
StateHasChanged();
}

View File

@@ -1302,8 +1302,14 @@ public partial class PlanificadorRutas : ComponentBase
/// </summary>
private async Task RecalcularConParametros()
{
if (_coordenadasOrigen is null || _coordenadasDestino is null) return;
await PersistirConfiguracionAsync();
await PersistirConfiguracionConfirmadaAsync();
MarcarConfiguracionAvanzadaComoGuardada();
if (_coordenadasOrigen is null || _coordenadasDestino is null)
{
StateHasChanged();
return;
}
if (_mapa is not null)
{

View File

@@ -96,7 +96,9 @@ public partial class PlanificadorRutas
private async Task AbrirHerramientaZonasAsync()
{
_mostrarConfigAvanzada = false;
if (_mostrarConfigAvanzada && !SolicitarCierreConfiguracionAvanzada())
return;
_mostrarHerramientaZonas = true;
await ActualizarEditorExclusionCaminataAsync();
}

View File

@@ -15,21 +15,12 @@ public partial class PlanificadorRutas
private bool _guardandoFabrica;
private ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaHistorial()
{
var config = _configuracionFabrica?.Clonar() ?? CrearConfiguracionBaseHistorial();
var ahora = DateTime.Now;
config.FechaSimulada = ahora.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
config.HoraSimulada = ahora.ToString("HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
return config;
}
=> _configuracionFabrica?.Clonar() ?? CrearConfiguracionBaseHistorial();
private void AplicarValoresFabricaConservandoSeleccion()
{
var actual = CrearConfiguracionLocal();
var predeterminada = CrearConfiguracionPredeterminadaHistorial();
predeterminada.UsarHoraSimulada = actual.UsarHoraSimulada;
predeterminada.FechaSimulada = actual.FechaSimulada;
predeterminada.HoraSimulada = actual.HoraSimulada;
predeterminada.CriterioPrioritarioRuta = actual.CriterioPrioritarioRuta;
predeterminada.PreferenciaEtiquetaRuta = actual.PreferenciaEtiquetaRuta;
AplicarConfiguracionLocal(predeterminada);

View File

@@ -1968,7 +1968,7 @@
<div class="config-actions-row">
<button class="btn-ghost" @onclick="RecalcularConParametros">
Recalcular
Guardar y recalcular
</button>
<button class="btn-ghost btn-ghost--secondary" @onclick="PedirConfirmacionRestablecer">
@@ -2389,6 +2389,34 @@
</div>
}
@if (_mostrarConfirmacionCerrarConfiguracion)
{
<div class="custom-modal-backdrop" @onclick="CancelarCierreConfiguracionAvanzada">
<div class="custom-modal-card config-unsaved-confirmation"
@onclick:stopPropagation="true"
role="dialog"
aria-modal="true"
aria-label="Cambios de configuración sin guardar">
<div class="custom-modal-title">Cambios sin guardar</div>
<p>Si cierras la configuración avanzada, se perderán estos cambios:</p>
<div class="config-unsaved-changes">
@foreach (var cambio in _cambiosConfiguracionSinGuardar)
{
<div><b>@cambio.Campo:</b> @cambio.Antes → @cambio.Despues</div>
}
</div>
<div class="custom-modal-actions">
<button class="btn-ghost" @onclick="CancelarCierreConfiguracionAvanzada">
Seguir editando
</button>
<button class="btn-ghost btn-ghost--secondary" @onclick="DescartarCambiosConfiguracionAvanzadaAsync">
Descartar cambios
</button>
</div>
</div>
</div>
}
@if (_fabricaPendiente is not null || _mensajeFabrica is not null)
{
<div class="custom-modal-backdrop" @onclick="CancelarGuardadoFabrica">

View File

@@ -125,8 +125,20 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
}
var alternativasSoloPie = await CrearAlternativasSoloPieAsync(origenLatLon, destinoLatLon, parametros);
var candidatasOrigen = await SeleccionarCandidatasAsync(indiceTransito, origenLatLon[0], origenLatLon[1], parametros, ct);
var candidatasDestino = await SeleccionarCandidatasAsync(indiceTransito, destinoLatLon[0], destinoLatLon[1], parametros, ct);
var seleccionOrigen = await SeleccionarCandidatasAsync(
indiceTransito,
origenLatLon[0],
origenLatLon[1],
parametros,
ct);
var seleccionDestino = await SeleccionarCandidatasAsync(
indiceTransito,
destinoLatLon[0],
destinoLatLon[1],
parametros,
ct);
var candidatasOrigen = seleccionOrigen.Principales;
var candidatasDestino = seleccionDestino.Principales;
if (candidatasOrigen.Count == 0 || candidatasDestino.Count == 0)
{
@@ -167,8 +179,8 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
{
var directasRobustas = BuscarDirectasRobustasConHorario(
indiceTransito,
candidatasOrigen,
candidatasDestino,
seleccionOrigen.Amplias,
seleccionDestino.Amplias,
instanteSalida,
parametros);
var continuidadesCirculares = BuscarContinuidadesCircularesConHorario(
@@ -2017,7 +2029,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
/// <summary>
/// Selecciona las paradas cercanas mas prometedoras.
/// </summary>
private async Task<List<ParadaCandidata>> SeleccionarCandidatasAsync(
private async Task<(List<ParadaCandidata> Principales, List<ParadaCandidata> Amplias)> SeleccionarCandidatasAsync(
IndiceTransitoDbus indiceTransito,
double latitud,
double longitud,
@@ -2064,7 +2076,11 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
if (!validarExclusionesV2 &&
(!parametros.UsarOsrmParaRanking || parametros.ActivarRangoLineas || parametros.ActivarRangoItinerarios))
return SeleccionarCandidatasConCoberturaPatrones(candidatas, maximo, indiceTransito, parametros);
{
return (
SeleccionarCandidatasConCoberturaPatrones(candidatas, maximo, indiceTransito, parametros),
candidatas);
}
var semaforo = new SemaphoreSlim(4, 4);
var tareas = candidatas.Select(async candidata =>
@@ -2113,7 +2129,9 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
.ThenBy(x => x.DistanciaEuclidea)
.ToList();
return SeleccionarCandidatasConCoberturaPatrones(candidatasOrdenadas, maximo, indiceTransito, parametros);
return (
SeleccionarCandidatasConCoberturaPatrones(candidatasOrdenadas, maximo, indiceTransito, parametros),
candidatasOrdenadas);
}
/// <summary>

View File

@@ -1413,9 +1413,12 @@ html, body {
.btn-ghost.inspector-walk-eye { display: inline-flex; padding: 2px 5px; min-height: 0; vertical-align: middle; margin-left: 5px; }
.inspector-walk-eye[aria-pressed="true"] { color: #22d3ee; border-color: #22d3ee; }
.fabrica-confirmacion { width: min(560px, calc(100vw - 24px)); max-height: calc(100dvh - 32px); overflow-y: auto; }
.fabrica-cambios { max-height: 45dvh; overflow-y: auto; font-size: .8rem; overflow-wrap: anywhere; }
.fabrica-cambios > div { padding: 5px 0; border-bottom: 1px solid #374151; }
.fabrica-confirmacion,
.config-unsaved-confirmation { width: min(560px, calc(100vw - 24px)); max-height: calc(100dvh - 32px); overflow-y: auto; }
.fabrica-cambios,
.config-unsaved-changes { max-height: 45dvh; overflow-y: auto; font-size: .8rem; overflow-wrap: anywhere; }
.fabrica-cambios > div,
.config-unsaved-changes > div { padding: 5px 0; border-bottom: 1px solid #374151; }
.route-score-detail-line {
display: inline-flex;
@@ -2196,6 +2199,14 @@ html, body {
white-space: nowrap;
}
.config-actions-row .btn-ghost:first-child {
flex-grow: 1.45;
}
.config-actions-row .btn-ghost:last-child {
flex-grow: .85;
}
.custom-modal-backdrop {
position: fixed;
inset: 0;

View File

@@ -1,5 +1,5 @@
const TILE_CACHE = 'tile-cache-v1';
const TILE_PREFIX = 'https://osm.tecnosis.net/osm_tiles/';
const TILE_PREFIX = 'http://192.168.41.12/osm_tiles/';
self.addEventListener('install', event => {
self.skipWaiting();

View File

@@ -20,6 +20,30 @@ var baseMethod = typeof(PlanificadorRutas).GetMethod("CrearConfiguracionBaseHist
var defaults = (ConfiguracionHistorialRuta)baseMethod.Invoke(null, null)!;
Check(defaults.PesoTransbordoRankingMinutos == 6, "Penalizacion inicial de 6 minutos");
var panelSinCambios = new PlanificadorRutas();
var alternarPanel = typeof(PlanificadorRutas).GetMethod(
"ToggleConfigPanel",
BindingFlags.Instance | BindingFlags.NonPublic)!;
var solicitarCierre = typeof(PlanificadorRutas).GetMethod(
"SolicitarCierreConfiguracionAvanzada",
BindingFlags.Instance | BindingFlags.NonPublic)!;
alternarPanel.Invoke(panelSinCambios, null);
Check(
solicitarCierre.Invoke(panelSinCambios, null) is true,
"Configuracion avanzada sin cambios se cierra directamente");
var panelConCambios = new PlanificadorRutas();
alternarPanel.Invoke(panelConCambios, null);
typeof(PlanificadorRutas)
.GetField("_velocidadPieKmH", BindingFlags.Instance | BindingFlags.NonPublic)!
.SetValue(panelConCambios, 4.2);
Check(
solicitarCierre.Invoke(panelConCambios, null) is false &&
(bool)typeof(PlanificadorRutas)
.GetField("_mostrarConfirmacionCerrarConfiguracion", BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(panelConCambios)!,
"Configuracion avanzada avisa antes de descartar cambios");
var folder = Path.Combine(Path.GetTempPath(), "rutasdbus-regression-" + Guid.NewGuid().ToString("N"));
IConfiguration Config() => new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
{
@@ -37,7 +61,29 @@ try
await File.WriteAllTextAsync(file, JsonSerializer.Serialize(defaults));
var loaded = await Service().CargarAsync();
Check(loaded?.PesoTransbordoRankingMinutos == 9, "Valores compartidos entre instancias");
Check(loaded is { FechaSimulada: null, HoraSimulada: null }, "Los valores de fabrica no fijan la fecha y hora");
Check(loaded is { FechaSimulada: null, HoraSimulada: null }, "Valores antiguos sin fecha y hora siguen siendo validos");
var guardadaConFecha = defaults.Clonar();
guardadaConFecha.FechaSimulada = "2026-09-16";
guardadaConFecha.HoraSimulada = "13:40:00";
Check(await Service().GuardarAsync(guardadaConFecha), "Los valores de fabrica permiten guardar fecha y hora");
var cargadaConFecha = await Service().CargarAsync();
Check(
cargadaConFecha is { FechaSimulada: "2026-09-16", HoraSimulada: "13:40:00" },
"Los valores de fabrica recuperan la fecha y hora guardadas");
var pagina = new PlanificadorRutas();
typeof(PlanificadorRutas)
.GetField("_configuracionFabrica", BindingFlags.Instance | BindingFlags.NonPublic)!
.SetValue(pagina, cargadaConFecha);
var crearPredeterminada = typeof(PlanificadorRutas).GetMethod(
"CrearConfiguracionPredeterminadaHistorial",
BindingFlags.Instance | BindingFlags.NonPublic)!;
var predeterminada = (ConfiguracionHistorialRuta)crearPredeterminada.Invoke(pagina, null)!;
Check(
predeterminada is { FechaSimulada: "2026-09-16", HoraSimulada: "13:40:00" },
"Restaurar conserva la fecha y hora de los valores de fabrica");
File.WriteAllText(file, "{incompleto");
Check(await Service().CargarAsync() is null, "JSON danado permite usar valores base");
}
@@ -183,32 +229,50 @@ var page = new PlanificadorRutas();
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
var fabricaHistorial = (ConfiguracionHistorialRuta)baseMethod.Invoke(null, null)!;
fabricaHistorial.FactorEquilibradoAndarBus = 6;
fabricaHistorial.UsarHoraSimulada = true;
fabricaHistorial.FechaSimulada = "2026-09-16";
fabricaHistorial.HoraSimulada = "13:40:00";
typeof(PlanificadorRutas).GetField("_configuracionFabrica", flags)!.SetValue(page, fabricaHistorial);
var crearPredeterminadaHistorial = typeof(PlanificadorRutas).GetMethod(
"CrearConfiguracionPredeterminadaParaHistorial", flags)!;
var crearGuardadaHistorial = typeof(PlanificadorRutas).GetMethod(
"CrearConfiguracionGuardadaHistorial", flags)!;
var configuracionGuardada = fabricaHistorial.Clonar();
configuracionGuardada.UsarHoraSimulada = true;
configuracionGuardada.FechaSimulada = "2026-04-08";
configuracionGuardada.HoraSimulada = "08:46:17";
configuracionGuardada.FactorEquilibradoAndarBus = 99;
var registroConHora = new RegistroHistorialRuta { Configuracion = configuracionGuardada };
var predeterminadaConHora = (ConfiguracionHistorialRuta)crearPredeterminadaHistorial.Invoke(
var predeterminadaHistorial = (ConfiguracionHistorialRuta)crearPredeterminadaHistorial.Invoke(page, null)!;
Check(predeterminadaHistorial.UsarHoraSimulada &&
predeterminadaHistorial.FechaSimulada == "2026-09-16" &&
predeterminadaHistorial.HoraSimulada == "13:40:00" &&
predeterminadaHistorial.FactorEquilibradoAndarBus == 6,
"Predeterminada usa tambien la fecha y hora de fabrica");
var guardada = (ConfiguracionHistorialRuta)crearGuardadaHistorial.Invoke(
page, new object[] { registroConHora })!;
Check(predeterminadaConHora.UsarHoraSimulada &&
predeterminadaConHora.FechaSimulada == "2026-04-08" &&
predeterminadaConHora.HoraSimulada == "08:46:17" &&
predeterminadaConHora.FactorEquilibradoAndarBus == 6,
"Predeterminada conserva solo la fecha y hora simuladas del historial");
configuracionGuardada.UsarHoraSimulada = false;
configuracionGuardada.FechaSimulada = "2020-01-01";
configuracionGuardada.HoraSimulada = "01:02:03";
var predeterminadaSinHora = (ConfiguracionHistorialRuta)crearPredeterminadaHistorial.Invoke(
page, new object[] { registroConHora })!;
Check(!predeterminadaSinHora.UsarHoraSimulada &&
predeterminadaSinHora.FechaSimulada == DateOnly.FromDateTime(DateTime.Now)
.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture) &&
predeterminadaSinHora.HoraSimulada != "01:02:03",
"Predeterminada usa la hora actual cuando el historial no tenia simulacion");
Check(guardada.UsarHoraSimulada &&
guardada.FechaSimulada == "2026-04-08" &&
guardada.HoraSimulada == "08:46:17" &&
guardada.FactorEquilibradoAndarBus == 99,
"Guardada conserva la fecha, hora y parametros del historial");
var paginaCierreHistorial = new PlanificadorRutas();
typeof(PlanificadorRutas).GetField("_mostrarHistorialRutas", flags)!
.SetValue(paginaCierreHistorial, true);
typeof(PlanificadorRutas).GetField("_factorEquilibradoAndarBus", flags)!
.SetValue(paginaCierreHistorial, 99.0);
var configuracionAnteriorHistorial = fabricaHistorial.Clonar();
configuracionAnteriorHistorial.FactorEquilibradoAndarBus = 6;
typeof(PlanificadorRutas).GetField("_configuracionTemporalPreviaHistorial", flags)!
.SetValue(paginaCierreHistorial, configuracionAnteriorHistorial);
var alternarHistorial = typeof(PlanificadorRutas).GetMethod("ToggleHistorialPanel", flags)!;
await (Task)alternarHistorial.Invoke(paginaCierreHistorial, null)!;
Check(
!(bool)typeof(PlanificadorRutas).GetField("_mostrarHistorialRutas", flags)!
.GetValue(paginaCierreHistorial)! &&
(double)typeof(PlanificadorRutas).GetField("_factorEquilibradoAndarBus", flags)!
.GetValue(paginaCierreHistorial)! == 99.0,
"Cerrar el historial conserva la configuracion de la ruta visible");
typeof(PlanificadorRutas).GetField("_factorEquilibradoAndarBus", flags)!.SetValue(page, 6.0);
typeof(PlanificadorRutas).GetField("_factorPenalizacionDistanciaPie", flags)!.SetValue(page, 1.8);
typeof(PlanificadorRutas).GetField("_parametroPenalizacionDistanciaPieMetros", flags)!.SetValue(page, 200.0);