diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs index 2b73f53..5439307 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs @@ -54,7 +54,7 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable try { await JS.InvokeVoidAsync("rt.initMobileMapControls"); } catch (Exception ex) { Console.WriteLine("rt.initMobileMapControls: " + ex); } try { await JS.InvokeVoidAsync("rt.initDraggablePopups"); } catch (Exception ex) { Console.WriteLine("rt.initDraggablePopups: " + ex); } await CargarConfiguracionLocalAsync(); - await CargarPreferenciaParametrosActualesHistorialAsync(); + await CargarModoConfiguracionHistorialAsync(); await AsegurarHistorialLocalCargadoAsync(); await CargarHistorialCompartidoAsync(); await CargarModelosHorariosAsync(); @@ -280,6 +280,9 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable [JSInvokable] public async Task OnPreviewItinerarioClick(string previewId) { + if (_inspectorActivo) + return; + if (string.IsNullOrWhiteSpace(previewId)) return; @@ -371,8 +374,26 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable if (!_paradaByCode.TryGetValue(codigoParada, out var parada)) return; + if (_inspectorActivo) + { + await MostrarInspectorAsync(parada.Latitud, parada.Longitud); + return; + } + await MostrarPopupParadaAsync(parada); } + + /// + /// Abre exclusivamente el inspector Z desde un elemento interactivo superpuesto al mapa. + /// + [JSInvokable] + public async Task OnInspectorOverlayClick(double latitud, double longitud) + { + if (!double.IsFinite(latitud) || !double.IsFinite(longitud)) + return; + + await MostrarInspectorAsync(latitud, longitud); + } /// /// Gestiona inspector activo. @@ -381,6 +402,16 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable public void SetInspectorActivo(bool activo) { _inspectorActivo = activo; + + if (activo) + { + _ctsDobleClick?.Cancel(); + _ctsDobleClick?.Dispose(); + _ctsDobleClick = null; + _primerClickPendiente = null; + _paradaPrimerClickPendiente = null; + } + StateHasChanged(); } diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Historial.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Historial.cs index e8fc0c4..e9644c0 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Historial.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Historial.cs @@ -1,8 +1,8 @@ using System.Globalization; +using System.IO.Compression; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using RutasDBUS.Modelos.Historial; @@ -12,8 +12,11 @@ public partial class PlanificadorRutas { private const string ClaveHistorialLocal = "rutasdbus.planificador.routeHistory.v1"; private const string ClaveRespaldoHistorialLocal = "rutasdbus.planificador.routeHistory.recovery.v1"; - private const string ClavePreferenciaParametrosActualesHistorial = + private const string ClavePreferenciaParametrosActualesHistorialLegacy = "rutasdbus.planificador.history.useCurrentConfig.v1"; + private const string ClaveModoConfiguracionHistorial = + "rutasdbus.planificador.history.configMode.v2"; + private const string PrefijoHistorialLocalComprimido = "gz1:"; private const int MaxItemsHistorialLocal = 25; private const int MaxBytesHistorialLocal = 24 * 1024; private const int IntervaloRefrescoHistorialCompartidoSegundos = 3; @@ -43,11 +46,11 @@ public partial class PlanificadorRutas private bool _refrescandoHistorialCompartido = false; private bool _omitirRegistroHistorialEnSiguienteCalculo = false; private bool _aplicandoRegistroHistorial = false; - private bool _usarParametrosActualesHistorial = true; + private ModoConfiguracionHistorial _modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual; private bool _cambiandoParametrosHistorial = false; private string? _historialCompartidoPendienteEliminarId; - private ConfiguracionLocalPlanificador? _configuracionTemporalPreviaHistorial; - private ConfiguracionLocalPlanificador? _configuracionParametrosActualesHistorial; + private ConfiguracionHistorialRuta? _configuracionTemporalPreviaHistorial; + private ConfiguracionHistorialRuta? _configuracionParametrosActualesHistorial; private Task? _tareaCargaHistorialLocal; private bool _historialLocalCargado = false; private bool _historialLocalCargando = false; @@ -63,6 +66,13 @@ public partial class PlanificadorRutas Compartido } + private enum ModoConfiguracionHistorial + { + Actual, + Guardada, + Predeterminada + } + private sealed record PasoDerivacionHistorial( int Numero, DateTime FechaUtc, @@ -243,16 +253,18 @@ public partial class PlanificadorRutas return false; } - var json = resultadoLectura.Value; - if (!resultadoLectura.Found || string.IsNullOrWhiteSpace(json)) + var valorHistorial = resultadoLectura.Value; + if (!resultadoLectura.Found || string.IsNullOrWhiteSpace(valorHistorial)) { FinalizarCargaHistorialLocal(Array.Empty()); LimpiarErrorHistorialLocal(); return true; } + var json = valorHistorial; try { + json = DecodificarHistorialLocal(valorHistorial); var items = DeserializarHistorialLocal( json, out var registrosOmitidos, @@ -262,7 +274,7 @@ public partial class PlanificadorRutas if (registrosOmitidos > 0) { var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync( - json, + valorHistorial, resultadoLectura.RespaldoGuardado); var detalleFalloRecuperacion = recuperacionGuardada ? null @@ -303,13 +315,15 @@ public partial class PlanificadorRutas } catch (Exception ex) when ( ex is JsonException or + InvalidDataException or + FormatException or NotSupportedException or InvalidOperationException or ArgumentException) { FinalizarCargaHistorialLocal(Array.Empty()); var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync( - json, + valorHistorial, resultadoLectura.RespaldoGuardado); var detalleFalloRecuperacion = recuperacionGuardada ? null @@ -413,6 +427,11 @@ public partial class PlanificadorRutas ? $"{item.DestinoLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.DestinoLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}" : item.DestinoTexto.Trim(); item.LineasPermitidas = NormalizarLineasHistorial(item.LineasPermitidas).ToList(); + if (item.Configuracion is not null) + { + item.Configuracion.LineasPermitidas = + NormalizarLineasHistorial(item.Configuracion.LineasPermitidas).ToList(); + } if (item.FechaCreacionUtc == default) item.FechaCreacionUtc = item.FechaActualizacionUtc == default ? DateTime.UtcNow : item.FechaActualizacionUtc; @@ -444,19 +463,45 @@ public partial class PlanificadorRutas IEnumerable origen) { var items = origen.Take(MaxItemsHistorialLocal).ToList(); - var json = JsonSerializer.Serialize(items); + var json = SerializarHistorialLocal(items); var bytes = Encoding.UTF8.GetByteCount(json); while (items.Count > 0 && bytes > MaxBytesHistorialLocal) { items.RemoveAt(items.Count - 1); - json = JsonSerializer.Serialize(items); + json = SerializarHistorialLocal(items); bytes = Encoding.UTF8.GetByteCount(json); } return (items, json, bytes); } + private static string SerializarHistorialLocal(IEnumerable items) + { + var json = JsonSerializer.Serialize(items); + var entrada = Encoding.UTF8.GetBytes(json); + using var salida = new MemoryStream(); + using (var gzip = new GZipStream(salida, CompressionLevel.Fastest, leaveOpen: true)) + { + gzip.Write(entrada, 0, entrada.Length); + } + + return PrefijoHistorialLocalComprimido + Convert.ToBase64String(salida.ToArray()); + } + + private static string DecodificarHistorialLocal(string valor) + { + if (!valor.StartsWith(PrefijoHistorialLocalComprimido, StringComparison.Ordinal)) + return valor; + + var datos = Convert.FromBase64String(valor[PrefijoHistorialLocalComprimido.Length..]); + using var entrada = new MemoryStream(datos); + using var gzip = new GZipStream(entrada, CompressionMode.Decompress); + using var salida = new MemoryStream(); + gzip.CopyTo(salida); + return Encoding.UTF8.GetString(salida.ToArray()); + } + private async Task GuardarHistorialLocalRecuperadoAsync( string jsonOriginal, bool respaldoOriginalYaGuardado) @@ -485,7 +530,7 @@ public partial class PlanificadorRutas return; var itemsOriginales = _historialLocal.ToList(); - var jsonOriginal = JsonSerializer.Serialize(itemsOriginales); + var jsonOriginal = SerializarHistorialLocal(itemsOriginales); var preparado = PrepararHistorialLocalParaGuardar(itemsOriginales); var fueRecortado = preparado.Items.Count != itemsOriginales.Count; @@ -771,6 +816,7 @@ public partial class PlanificadorRutas existente.FactorEquilibradoAndarBus = actual.FactorEquilibradoAndarBus; existente.ActivarFiltroLineas = actual.ActivarFiltroLineas; existente.LineasPermitidas = actual.LineasPermitidas.ToList(); + existente.Configuracion = actual.Configuracion?.Clonar(); existente.FechaActualizacionUtc = DateTime.UtcNow; _historialLocal.Remove(existente); @@ -854,18 +900,13 @@ public partial class PlanificadorRutas return null; } - private async Task CambiarUsoParametrosActualesHistorialAsync(ChangeEventArgs e) + private async Task CambiarModoConfiguracionHistorialAsync(ModoConfiguracionHistorial modo) { if (_cambiandoParametrosHistorial) return; - var usarParametrosActuales = e.Value switch - { - bool valor => valor, - string texto when bool.TryParse(texto, out var valor) => valor, - _ => !_usarParametrosActualesHistorial - }; - if (usarParametrosActuales == _usarParametrosActualesHistorial) + if (!Enum.IsDefined(typeof(ModoConfiguracionHistorial), modo) || + modo == _modoConfiguracionHistorial) return; _cambiandoParametrosHistorial = true; @@ -873,23 +914,34 @@ public partial class PlanificadorRutas { var itemSeleccionado = ObtenerRegistroHistorialSeleccionado(); - if (!usarParametrosActuales && _usarParametrosActualesHistorial) + if (_modoConfiguracionHistorial == ModoConfiguracionHistorial.Actual && + modo != ModoConfiguracionHistorial.Actual) + { + CapturarConfiguracionTemporalAntesDeHistorial(); _configuracionParametrosActualesHistorial = CrearConfiguracionLocal(); + } - _usarParametrosActualesHistorial = usarParametrosActuales; - await PersistirPreferenciaParametrosActualesHistorialAsync(); + _modoConfiguracionHistorial = modo; + await PersistirModoConfiguracionHistorialAsync(); - if (usarParametrosActuales && _configuracionParametrosActualesHistorial is not null) + if (modo == ModoConfiguracionHistorial.Actual && + _configuracionParametrosActualesHistorial is not null) + { AplicarConfiguracionLocal(_configuracionParametrosActualesHistorial); + } if (itemSeleccionado is null) return; await AplicarRegistroHistorialAsync(itemSeleccionado); - MostrarMensajeHistorial( - usarParametrosActuales - ? "Ruta recalculada con los parámetros actuales." - : "Ruta recalculada con los parámetros guardados."); + MostrarMensajeHistorial(modo switch + { + ModoConfiguracionHistorial.Actual => + "Ruta recalculada con la configuración actual.", + ModoConfiguracionHistorial.Guardada => + "Ruta recalculada con la configuración guardada en el historial.", + _ => "Ruta recalculada con la configuración predeterminada." + }); } catch (Exception ex) { @@ -906,29 +958,44 @@ public partial class PlanificadorRutas } } - private async Task CargarPreferenciaParametrosActualesHistorialAsync() + private async Task CargarModoConfiguracionHistorialAsync() { try { var valor = await JS.InvokeAsync( "rt.loadLocalValue", - ClavePreferenciaParametrosActualesHistorial); - if (bool.TryParse(valor, out var usarConfiguracionActual)) - _usarParametrosActualesHistorial = usarConfiguracionActual; + ClaveModoConfiguracionHistorial); + if (Enum.TryParse(valor, ignoreCase: true, out var modo) && + Enum.IsDefined(typeof(ModoConfiguracionHistorial), modo)) + { + _modoConfiguracionHistorial = modo; + return; + } + + var valorLegacy = await JS.InvokeAsync( + "rt.loadLocalValue", + ClavePreferenciaParametrosActualesHistorialLegacy); + if (bool.TryParse(valorLegacy, out var usarConfiguracionActual)) + { + _modoConfiguracionHistorial = usarConfiguracionActual + ? ModoConfiguracionHistorial.Actual + : ModoConfiguracionHistorial.Guardada; + await PersistirModoConfiguracionHistorialAsync(); + } } catch (JSException) { } } - private async Task PersistirPreferenciaParametrosActualesHistorialAsync() + private async Task PersistirModoConfiguracionHistorialAsync() { try { await JS.InvokeVoidAsync( "rt.saveLocalValue", - ClavePreferenciaParametrosActualesHistorial, - _usarParametrosActualesHistorial.ToString(CultureInfo.InvariantCulture)); + ClaveModoConfiguracionHistorial, + _modoConfiguracionHistorial.ToString()); } catch (JSException) { @@ -1268,10 +1335,18 @@ public partial class PlanificadorRutas FactorEquilibradoAndarBus = item.FactorEquilibradoAndarBus, ActivarFiltroLineas = item.ActivarFiltroLineas, LineasPermitidas = item.LineasPermitidas.ToList(), + Configuracion = item.Configuracion?.Clonar(), FechaCreacionUtc = item.FechaCreacionUtc, FechaActualizacionUtc = item.FechaActualizacionUtc }; + private RegistroHistorialRuta PrepararRegistroParaCompartir(RegistroHistorialRuta item) + { + var compartible = ClonarEscenarioHistorial(item); + compartible.Configuracion ??= CrearConfiguracionGuardadaHistorial(item); + return compartible; + } + private async Task CompartirEscenarioActualAsync() { var actual = ConstruirRegistroHistorialActual() @@ -1285,7 +1360,7 @@ public partial class PlanificadorRutas try { - await HistorialRutasCompartidoService.GuardarAsync(actual); + await HistorialRutasCompartidoService.GuardarAsync(PrepararRegistroParaCompartir(actual)); await CargarHistorialCompartidoAsync(); MostrarMensajeHistorial("Combinacion enviada al historial compartido."); } @@ -1306,7 +1381,7 @@ public partial class PlanificadorRutas try { - await HistorialRutasCompartidoService.GuardarAsync(item); + await HistorialRutasCompartidoService.GuardarAsync(PrepararRegistroParaCompartir(item)); await CargarHistorialCompartidoAsync(); MostrarMensajeHistorial("Combinacion enviada al historial compartido."); } @@ -1439,6 +1514,7 @@ public partial class PlanificadorRutas try { CapturarConfiguracionTemporalAntesDeHistorial(); + var mostrarParadasAntes = _mostrarParadas; _omitirRegistroHistorialEnSiguienteCalculo = true; _textoBusquedaOrigen = item.OrigenTexto; @@ -1446,40 +1522,33 @@ public partial class PlanificadorRutas _coordenadasOrigen = new[] { item.OrigenLatitud, item.OrigenLongitud }; _coordenadasDestino = new[] { item.DestinoLatitud, item.DestinoLongitud }; - if (!_usarParametrosActualesHistorial) + var configuracionHistorial = _modoConfiguracionHistorial switch { - _usarHorariosTeoricos = item.UsarHorariosTeoricos; - _usarHoraSimulada = item.UsarHoraSimulada; + ModoConfiguracionHistorial.Guardada => CrearConfiguracionGuardadaHistorial(item), + ModoConfiguracionHistorial.Predeterminada => CrearConfiguracionPredeterminadaHistorial(), + _ => null + }; - if (DateOnly.TryParse(item.FechaSimulada, out var fecha)) - _fechaSimulada = fecha; + if (configuracionHistorial is not null) + { + AplicarConfiguracionLocal(configuracionHistorial); - if (TimeOnly.TryParse(item.HoraSimulada, out var hora)) - _horaSimulada = hora; - - _criterioPrioritarioRuta = NormalizarCriterioRuta(item.CriterioPrioritarioRuta, item.PreferenciaEtiquetaRuta); - SincronizarPreferenciaDesdeCriterioRuta(); - - if (item.FactorEquilibradoAndarBus is { } factorEquilibrado && - double.IsFinite(factorEquilibrado) && - factorEquilibrado > 0) + // El factor cargado desde un historial ya se conservaba al cerrar el panel. + if (_modoConfiguracionHistorial == ModoConfiguracionHistorial.Guardada && + _configuracionTemporalPreviaHistorial is not null) { - _factorEquilibradoAndarBus = factorEquilibrado; - SincronizarTextosPenalizacionPie(); - - if (_configuracionTemporalPreviaHistorial is not null) - _configuracionTemporalPreviaHistorial.FactorEquilibradoAndarBus = _factorEquilibradoAndarBus; + _configuracionTemporalPreviaHistorial.FactorEquilibradoAndarBus = + _factorEquilibradoAndarBus; + _configuracionTemporalPreviaHistorial.VersionFactorEquilibrado = + VersionPersistenciaFactorEquilibrado; } - - _activarRangoLineas = item.ActivarFiltroLineas; - _lineasPermitidas.Clear(); - foreach (var linea in NormalizarLineasHistorial(item.LineasPermitidas)) - _lineasPermitidas.Add(linea); } _busFilteredCache.Clear(); SincronizarSegundosPorParadaConHorarios(); + if (_usarHorariosTeoricos) + await EnsureHorariosLoadedAsync(); StopClock(); if (_usarHoraSimulada) @@ -1495,6 +1564,14 @@ public partial class PlanificadorRutas { try { await _mapa.Geometric.DisplayPolylinesFromArray.deleteConnectors(); } catch { } await ClearRoutesJsAsync(); + + if (mostrarParadasAntes != _mostrarParadas) + { + if (_mostrarParadas) + await DibujarParadasAsync(); + else + await OcultarParadasAsync(); + } } await SincronizarMarcadoresOrigenDestinoJsAsync(); @@ -1506,6 +1583,30 @@ public partial class PlanificadorRutas } } + private ConfiguracionHistorialRuta CrearConfiguracionGuardadaHistorial(RegistroHistorialRuta item) + { + if (item.Configuracion is not null) + return item.Configuracion.Clonar(); + + // Los JSON anteriores no incluían una instantánea completa. Conservamos los + // parámetros actuales para lo desconocido y superponemos todos los que sí viajaban. + var configuracion = (_configuracionParametrosActualesHistorial ?? + _configuracionTemporalPreviaHistorial ?? + CrearConfiguracionLocal()).Clonar(); + + configuracion.UsarHorariosTeoricos = item.UsarHorariosTeoricos; + configuracion.UsarHoraSimulada = item.UsarHoraSimulada; + configuracion.FechaSimulada = item.FechaSimulada; + configuracion.HoraSimulada = item.HoraSimulada; + configuracion.CriterioPrioritarioRuta = item.CriterioPrioritarioRuta; + configuracion.PreferenciaEtiquetaRuta = item.PreferenciaEtiquetaRuta; + configuracion.FactorEquilibradoAndarBus = item.FactorEquilibradoAndarBus; + configuracion.VersionFactorEquilibrado = VersionPersistenciaFactorEquilibrado; + configuracion.ActivarRangoLineas = item.ActivarFiltroLineas; + configuracion.LineasPermitidas = NormalizarLineasHistorial(item.LineasPermitidas).ToList(); + return configuracion; + } + private void CapturarConfiguracionTemporalAntesDeHistorial() { if (_configuracionTemporalPreviaHistorial is not null) @@ -1524,8 +1625,19 @@ public partial class PlanificadorRutas 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(); @@ -1541,6 +1653,11 @@ public partial class PlanificadorRutas return null; var ahora = _ultimaHoraReferenciaRuta ?? AhoraLocal(); + var configuracion = CrearConfiguracionLocal(); + configuracion.FechaSimulada = DateOnly.FromDateTime(ahora) + .ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + configuracion.HoraSimulada = TimeOnly.FromDateTime(ahora) + .ToString("HH:mm:ss", CultureInfo.InvariantCulture); return new RegistroHistorialRuta { @@ -1563,6 +1680,7 @@ public partial class PlanificadorRutas : FactorEquilibradoAndarBusPorDefecto, ActivarFiltroLineas = _activarRangoLineas, LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(), + Configuracion = configuracion, FechaCreacionUtc = DateTime.UtcNow, FechaActualizacionUtc = DateTime.UtcNow }; diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.InspectorZ.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.InspectorZ.cs index 45d9ea1..7db1695 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.InspectorZ.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.InspectorZ.cs @@ -12,7 +12,10 @@ public partial class PlanificadorRutas double? ElevacionIdee, double? ElevacionLocal5, double? ElevacionLocal2, - double? ElevacionLocal1); + double? ElevacionLocal1, + string Direccion, + double? DistanciaAndandoMetros, + bool CalculandoDatosAdicionales); private double? ObtenerElevacionPuntoInspectorAnterior(PuntoInspectorAnterior punto) => UsarElevacionLocal1ParaCalculo @@ -23,6 +26,15 @@ public partial class PlanificadorRutas ? punto.ElevacionLocal5 : punto.ElevacionIdee; + private double? ObtenerElevacionPuntoInspectorActual() + => UsarElevacionLocal1ParaCalculo + ? _inspectorElevLocal1 + : UsarElevacionLocal2ParaCalculo + ? _inspectorElevLocal2 + : UsarElevacionLocal5ParaCalculo + ? _inspectorElevLocal5 + : _inspectorElevIdee; + private static string FormatearDistanciaPuntoInspector(double distanciaMetros) => distanciaMetros < 1000 ? distanciaMetros.ToString("0.0", CultureInfo.InvariantCulture) + " m" @@ -32,4 +44,79 @@ public partial class PlanificadorRutas => altura is { } valor ? valor.ToString("0.00", CultureInfo.InvariantCulture) + " m" : "-"; + + private static string FormatearDiferenciaAlturaPuntoInspector( + double? alturaActual, + double? alturaAnterior) + => alturaActual is { } actual && alturaAnterior is { } anterior + ? (actual - anterior).ToString("+0.00;-0.00;0.00", CultureInfo.InvariantCulture) + " m" + : "-"; + + private string ObtenerDireccionPuntoInspectorActual() + => FormatearLugarCallejeroResumen(_inspectorLugaresCercanos.FirstOrDefault()); + + private async Task ResolverDireccionPuntoInspectorAnteriorAsync(PuntoInspectorAnterior punto) + { + if (!string.IsNullOrWhiteSpace(punto.Direccion)) + return punto.Direccion; + + try + { + var cercano = await GeocodificadorLocal.BuscarMasCercanoAsync( + punto.Latitud, + punto.Longitud, + CancellationToken.None); + return FormatearLugarCallejeroResumen(cercano); + } + catch + { + return string.Empty; + } + } + + private async Task CompletarDatosPuntoInspectorAnteriorAsync( + PuntoInspectorAnterior punto, + int versionInspector) + { + try + { + var tareaDireccion = ResolverDireccionPuntoInspectorAnteriorAsync(punto); + var tareaCaminata = WalkCached( + punto.Latitud, + punto.Longitud, + _inspectorLat, + _inspectorLon); + + await Task.WhenAll(tareaDireccion, tareaCaminata); + + if (versionInspector != _versionInspector || _inspectorPuntoAnterior is not { } puntoVigente || + puntoVigente.Latitud != punto.Latitud || puntoVigente.Longitud != punto.Longitud) + { + return; + } + + var caminata = await tareaCaminata; + _inspectorPuntoAnterior = puntoVigente with + { + Direccion = await tareaDireccion, + DistanciaAndandoMetros = caminata.line is not null && caminata.dist > 0 + ? caminata.dist + : null, + CalculandoDatosAdicionales = false + }; + + await InvokeAsync(StateHasChanged); + } + catch + { + if (versionInspector != _versionInspector || _inspectorPuntoAnterior is not { } puntoVigente || + puntoVigente.Latitud != punto.Latitud || puntoVigente.Longitud != punto.Longitud) + { + return; + } + + _inspectorPuntoAnterior = puntoVigente with { CalculandoDatosAdicionales = false }; + await InvokeAsync(StateHasChanged); + } + } } diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs index df497d5..41bda0d 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs @@ -531,6 +531,9 @@ public partial class PlanificadorRutas : ComponentBase /// private async Task MostrarPopupParadaAsync(Parada paradaDetectada) { + if (_inspectorActivo) + return; + var myToken = Interlocked.Increment(ref _paradaPopupToken); _paradaClicada = null; @@ -1095,23 +1098,31 @@ public partial class PlanificadorRutas : ComponentBase /// - /// Construye un texto de trayecto usando la primera y la ultima parada de la variante. + /// Construye un texto de recorrido usando la primera y la ultima parada de la variante. /// - private string ConstruirTextoRecorridoVariante(InfoVariante? variante) + private string ConstruirTextoRecorridoVariante( + InfoVariante? variante, + bool incluirCodigosParada = false) { if (variante?.CodigosParadas is null || variante.CodigosParadas.Count == 0) return ""; - string NombreParada(string codigo) + string TextoParada(string codigo) { + var codigoFormateado = CodigoParadaFormateado(codigo); if (_paradaByCode.TryGetValue(codigo, out var parada) && !string.IsNullOrWhiteSpace(parada.Nombre)) - return parada.Nombre.Trim(); + { + var nombre = parada.Nombre.Trim(); + return incluirCodigosParada && !string.IsNullOrWhiteSpace(codigoFormateado) + ? $"{nombre} ({codigoFormateado})" + : nombre; + } - return CodigoParadaFormateado(codigo); + return codigoFormateado; } - var origen = NombreParada(variante.CodigosParadas[0]); - var destino = NombreParada(variante.CodigosParadas[^1]); + var origen = TextoParada(variante.CodigosParadas[0]); + var destino = TextoParada(variante.CodigosParadas[^1]); if (string.IsNullOrWhiteSpace(origen)) return destino; @@ -1389,7 +1400,7 @@ public partial class PlanificadorRutas : ComponentBase private bool _elevPopupMinimizado = false; - private const string _versionApp = "(v-20260902d)"; + private const string _versionApp = "(v-20260903b)"; private double? _paradaElevIdee; @@ -1518,9 +1529,9 @@ public partial class PlanificadorRutas : ComponentBase } /// - /// Obtiene el codigo y el nombre de la linea del itinerario clicado. + /// Obtiene el identificador completo y el nombre de la linea del itinerario clicado. /// - private string GetTextoLineaItinerarioClicado(InfoVariante? vReal) + private string GetNombreLineaItinerarioClicado(InfoVariante? vReal) { if (_itinerarioClicado is null) return ""; @@ -1528,19 +1539,18 @@ public partial class PlanificadorRutas : ComponentBase if (vReal is null) return _itinerarioClicado.Text; - var linea = vReal.Linea?.Codigo?.ToString() ?? "?"; - var itin = vReal.CodigoItinerario; - var sentido = NormalizarSentidoCodigo(vReal.Sentido?.Codigo); // I / V + var linea = vReal.Linea?.Codigo?.ToString()?.Trim() ?? "?"; + var sentido = NormalizarSentidoCodigo(vReal.Sentido?.Codigo); + var identificador = $"{linea}.{vReal.CodigoItinerario}-{sentido}"; var nombre = vReal.Linea?.Nombre?.Trim() ?? ""; - var codigo = $"{linea}.{itin}-{sentido}"; return string.IsNullOrWhiteSpace(nombre) - ? codigo - : $"{codigo} ({nombre})"; + ? identificador + : $"{identificador} {nombre}"; } /// - /// Obtiene la parada final del itinerario clicado. + /// Obtiene el destino indicado por las expediciones del itinerario clicado. /// private string GetDestinoItinerarioClicado(InfoVariante? vReal) { diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Persistencia.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Persistencia.cs index a144813..2bb0ed7 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Persistencia.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Persistencia.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Text.Json; using Microsoft.JSInterop; +using RutasDBUS.Modelos.Historial; using RutasDBUS.Modelos.Planificacion; namespace RutasDBUS.Components.Pages; @@ -17,66 +18,6 @@ public partial class PlanificadorRutas private bool _configuracionLocalCargada = false; private bool _mostrarConfirmacionRestablecer = false; - /// - /// Instantánea persistida de la configuración del planificador. - /// - private sealed class ConfiguracionLocalPlanificador - { - public bool? MostrarParadas { get; set; } - public bool UsarHorariosTeoricos { get; set; } - public int SegundosPorParadaBusManual { get; set; } - public bool ActivarRangoLineas { get; set; } - public int LineaMin { get; set; } - public int LineaMax { get; set; } - public List? LineasPermitidas { get; set; } - public bool ActivarRangoItinerarios { get; set; } - public int ItinerarioMin { get; set; } - public int ItinerarioMax { get; set; } - public string? FiltroSentidoItinerario { get; set; } - public bool ActivarRangoTransbordos { get; set; } - public int MinTransbordosPermitidos { get; set; } - public int MaxTransbordosPermitidos { get; set; } - public bool UsarHoraSimulada { get; set; } - public string? FechaSimulada { get; set; } - public string? HoraSimulada { get; set; } - public double? FactorPenalizacionDistanciaPie { get; set; } - public int? VersionFactorPenalizacionPie { get; set; } - public double? ParametroPenalizacionDistanciaPieMetros { get; set; } - public double? CoeficienteEsfuerzoAltura { get; set; } - public int? VersionCoeficienteEsfuerzo { get; set; } - public double? PesoTransbordoRankingMinutos { get; set; } - public double? FactorEquilibradoAndarBus { get; set; } - public int? VersionFactorEquilibrado { get; set; } - public bool? MostrarElevacionesRuta { get; set; } - public string? FuenteElevacionCalculo { get; set; } - public int? VersionFuenteElevacion { get; set; } - public int? VersionPendientes { get; set; } - public int? VersionColoresPendientes { get; set; } - public double? UmbralPendienteSubidaRuta { get; set; } - public double? UmbralPendienteSubidaMediaRuta { get; set; } - public double? UmbralPendienteSubidaFuerteRuta { get; set; } - public string? ColorPendienteBajadaRuta { get; set; } - public string? ColorPendienteLlanoRuta { get; set; } - public string? ColorPendienteSubidaSuaveRuta { get; set; } - public string? ColorPendienteSubidaMediaRuta { get; set; } - public string? ColorPendienteSubidaFuerteRuta { get; set; } - public double? UmbralPendienteBajadaRuta { get; set; } - public double? UmbralAlturaAlertaPendienteRutaMetros { get; set; } - // Compatibilidad con configuraciones guardadas cuando el umbral se expresaba en pisos. - public double? UmbralPisosPendienteRuta { get; set; } - public double VelocidadPieKmH { get; set; } - public double VelocidadBusKmH { get; set; } - public int ElevMaxPuntosPorTramo { get; set; } - public int RadioCandidatasMetros { get; set; } - public int MargenTransbordoSegundos { get; set; } - public string? WalkProvider { get; set; } - public int NivelBrilloMapa { get; set; } - public bool UsarColorRealLineaEnItinerarios { get; set; } - public bool UsarOsrmParaRanking { get; set; } - public string? CriterioPrioritarioRuta { get; set; } - public string? PreferenciaEtiquetaRuta { get; set; } - } - /// /// Carga la configuración persistida del navegador y la aplica al estado actual. /// @@ -88,7 +29,7 @@ public partial class PlanificadorRutas if (string.IsNullOrWhiteSpace(json)) return; - var config = JsonSerializer.Deserialize(json); + var config = JsonSerializer.Deserialize(json); if (config is null) return; @@ -146,9 +87,10 @@ public partial class PlanificadorRutas /// /// Construye la instantánea persistible a partir del estado actual. /// - private ConfiguracionLocalPlanificador CrearConfiguracionLocal() + private ConfiguracionHistorialRuta CrearConfiguracionLocal() => new() { + Version = 1, MostrarParadas = _mostrarParadas, UsarHorariosTeoricos = _usarHorariosTeoricos, SegundosPorParadaBusManual = Math.Max(0, _segundosPorParadaBusManual), @@ -193,6 +135,8 @@ public partial class PlanificadorRutas VelocidadBusKmH = _velocidadBusKmH, ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo, RadioCandidatasMetros = _radioCandidatasMetros, + MaxCandidatasPorLado = _maxCandidatasPorLado, + MinutosTransbordo = _minutosTransbordo, MargenTransbordoSegundos = _margenTransbordoSegundos, WalkProvider = _walkProvider.ToString(), NivelBrilloMapa = _nivelBrilloMapa, @@ -202,10 +146,72 @@ public partial class PlanificadorRutas PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta }; + private static ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaHistorial() + { + var ahora = DateTime.Now; + return new ConfiguracionHistorialRuta + { + Version = 1, + MostrarParadas = true, + UsarHorariosTeoricos = true, + SegundosPorParadaBusManual = 30, + ActivarRangoLineas = false, + LineaMin = 1, + LineaMax = 200, + LineasPermitidas = new List(), + ActivarRangoItinerarios = true, + ItinerarioMin = 1, + ItinerarioMax = 99, + FiltroSentidoItinerario = FiltroSentidoItinerario.Ambos.ToString(), + ActivarRangoTransbordos = true, + MinTransbordosPermitidos = 0, + MaxTransbordosPermitidos = 3, + UsarHoraSimulada = true, + FechaSimulada = DateOnly.FromDateTime(ahora).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), + HoraSimulada = TimeOnly.FromDateTime(ahora).ToString("HH:mm:ss", CultureInfo.InvariantCulture), + FactorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto, + VersionFactorPenalizacionPie = VersionPersistenciaFactorPenalizacionPie, + ParametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto, + CoeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto, + VersionCoeficienteEsfuerzo = VersionPersistenciaCoeficienteEsfuerzo, + PesoTransbordoRankingMinutos = 4.0, + FactorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto, + VersionFactorEquilibrado = VersionPersistenciaFactorEquilibrado, + MostrarElevacionesRuta = true, + FuenteElevacionCalculo = FuenteElevacionLocal5, + VersionFuenteElevacion = VersionPersistenciaFuenteElevacion, + VersionPendientes = VersionPersistenciaPendientes, + VersionColoresPendientes = VersionPersistenciaColoresPendientes, + UmbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto, + UmbralPendienteSubidaMediaRuta = UmbralPendienteSubidaMediaRutaPorDefecto, + UmbralPendienteSubidaFuerteRuta = UmbralPendienteSubidaFuerteRutaPorDefecto, + ColorPendienteBajadaRuta = ColorPendienteBajadaRutaPorDefecto, + ColorPendienteLlanoRuta = ColorPendienteLlanoRutaPorDefecto, + ColorPendienteSubidaSuaveRuta = ColorPendienteSubidaSuaveRutaPorDefecto, + ColorPendienteSubidaMediaRuta = ColorPendienteSubidaMediaRutaPorDefecto, + ColorPendienteSubidaFuerteRuta = ColorPendienteSubidaFuerteRutaPorDefecto, + UmbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto, + UmbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto, + VelocidadPieKmH = 3.6, + VelocidadBusKmH = 16.0, + ElevMaxPuntosPorTramo = 999, + RadioCandidatasMetros = 1200, + MaxCandidatasPorLado = 12, + MinutosTransbordo = 5, + MargenTransbordoSegundos = 120, + WalkProvider = ProveedorCaminata.Valhalla.ToString(), + NivelBrilloMapa = 0, + UsarColorRealLineaEnItinerarios = false, + UsarOsrmParaRanking = false, + CriterioPrioritarioRuta = CriterioRutaEquilibrado, + PreferenciaEtiquetaRuta = null + }; + } + /// /// Aplica al componente una configuración previamente persistida. /// - private void AplicarConfiguracionLocal(ConfiguracionLocalPlanificador config) + private void AplicarConfiguracionLocal(ConfiguracionHistorialRuta config) { _mostrarParadas = config.MostrarParadas ?? true; _usarHorariosTeoricos = config.UsarHorariosTeoricos; @@ -353,6 +359,8 @@ public partial class PlanificadorRutas 120.0); _elevMaxPuntosPorTramo = Math.Clamp(config.ElevMaxPuntosPorTramo, 2, 999); _radioCandidatasMetros = Math.Clamp(config.RadioCandidatasMetros, 200, 3500); + _maxCandidatasPorLado = Math.Clamp(config.MaxCandidatasPorLado ?? 12, 3, 20); + _minutosTransbordo = Math.Clamp(config.MinutosTransbordo ?? 5, 0, 180); _margenTransbordoSegundos = Math.Clamp(config.MargenTransbordoSegundos, 0, 1800); _nivelBrilloMapa = Math.Clamp(config.NivelBrilloMapa, 0, 4); _usarColorRealLineaEnItinerarios = config.UsarColorRealLineaEnItinerarios; @@ -475,7 +483,7 @@ public partial class PlanificadorRutas _nivelBrilloMapa = 0; _usarColorRealLineaEnItinerarios = false; _usarOsrmParaRanking = false; - _usarParametrosActualesHistorial = true; + _modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual; if (_usarHoraSimulada) StopClock(); @@ -485,7 +493,7 @@ public partial class PlanificadorRutas SincronizarSegundosPorParadaConHorarios(); await RefrescarHoraUIAsync(); await PersistirConfiguracionAsync(); - await PersistirPreferenciaParametrosActualesHistorialAsync(); + await PersistirModoConfiguracionHistorialAsync(); StateHasChanged(); } @@ -537,7 +545,7 @@ public partial class PlanificadorRutas _nivelBrilloMapa = 0; _usarColorRealLineaEnItinerarios = false; _usarOsrmParaRanking = false; - _usarParametrosActualesHistorial = true; + _modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual; if (_usarHoraSimulada) StopClock(); @@ -547,7 +555,7 @@ public partial class PlanificadorRutas SincronizarSegundosPorParadaConHorarios(); await RefrescarHoraUIAsync(); await PersistirConfiguracionAsync(); - await PersistirPreferenciaParametrosActualesHistorialAsync(); + await PersistirModoConfiguracionHistorialAsync(); StateHasChanged(); } } diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs index cd74b8a..cee0068 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs @@ -1461,7 +1461,7 @@ public partial class PlanificadorRutas : ComponentBase } if (_coordenadasOrigen is not null && _coordenadasDestino is not null) - await CalcularRutaCompletaAsync(); + await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true); StateHasChanged(); } @@ -1512,7 +1512,7 @@ public partial class PlanificadorRutas : ComponentBase await CerrarTodasLasPreviewsAsync(); await CerrarPopupsAsync(); - await CalcularRutaCompletaAsync(); + await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true); } /// /// Muestra inspector. @@ -1526,7 +1526,10 @@ public partial class PlanificadorRutas : ComponentBase _inspectorElevIdee, _inspectorElevLocal5, _inspectorElevLocal2, - _inspectorElevLocal1) + _inspectorElevLocal1, + ObtenerDireccionPuntoInspectorActual(), + DistanciaAndandoMetros: null, + CalculandoDatosAdicionales: true) : null; var versionInspector = ++_versionInspector; @@ -1554,6 +1557,9 @@ public partial class PlanificadorRutas : ComponentBase await ActualizarMarcadoresInspectorJsAsync(); StateHasChanged(); + if (_inspectorPuntoAnterior is { } puntoAnterior) + _ = CompletarDatosPuntoInspectorAnteriorAsync(puntoAnterior, versionInspector); + var tareaLugares = GeocodificadorLocal.BuscarCercanosAsync( lat, lon, @@ -1788,7 +1794,7 @@ public partial class PlanificadorRutas : ComponentBase await SincronizarMarcadoresOrigenDestinoJsAsync(); - await CalcularRutaCompletaAsync(); + await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true); return; } @@ -1801,7 +1807,7 @@ public partial class PlanificadorRutas : ComponentBase await SincronizarMarcadoresOrigenDestinoJsAsync(); } - await CalcularRutaCompletaAsync(); + await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true); } /// @@ -1841,7 +1847,7 @@ public partial class PlanificadorRutas : ComponentBase if (_coordenadasOrigen is not null && _coordenadasDestino is not null) { - await CalcularRutaCompletaAsync(); + await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true); } else { @@ -1862,11 +1868,16 @@ public partial class PlanificadorRutas : ComponentBase /// /// Calcula ruta completa. /// - private async Task CalcularRutaCompletaAsync() + private async Task CalcularRutaCompletaAsync(bool conservarSeleccionSoloAPie = false) { if (_mapa is null || _coordenadasOrigen is null || _coordenadasDestino is null) return; + var restaurarSeleccionSoloAPie = conservarSeleccionSoloAPie && + _indiceAlternativaSeleccionada >= 0 && + _indiceAlternativaSeleccionada < _opcionesRuta.Count && + _opcionesRuta[_indiceAlternativaSeleccionada].EsSoloAPie; + _mostrarElevacionesRuta = _mostrarElevacionesRutaConfiguracion; var confirmarDerivacionHistorial = !_aplicandoRegistroHistorial && @@ -1983,9 +1994,15 @@ public partial class PlanificadorRutas : ComponentBase return; } - var idxPreferida = BuscarIndicePreferidoPorCriterio(_criterioPrioritarioRuta); + var idxPreferida = restaurarSeleccionSoloAPie + ? _opcionesRuta.FindIndex(opcion => opcion.EsSoloAPie) + : BuscarIndicePreferidoPorCriterio(_criterioPrioritarioRuta); if (idxPreferida < 0 || idxPreferida >= _opcionesRuta.Count) - idxPreferida = 0; + { + idxPreferida = BuscarIndicePreferidoPorCriterio(_criterioPrioritarioRuta); + if (idxPreferida < 0 || idxPreferida >= _opcionesRuta.Count) + idxPreferida = 0; + } _indiceAlternativaSeleccionada = idxPreferida; ResetEstadoRuta(limpiarOpciones: false, limpiarMensaje: false); diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs index d8b1d44..7208d01 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs @@ -550,11 +550,10 @@ public partial class PlanificadorRutas : ComponentBase private string ObtenerNombreDestinoItinerario(InfoVariante variante) { - if (variante.CodigosParadas.Count == 0) - return string.Empty; - - var ultimaParada = ResolverParadaFisica(variante.CodigosParadas[^1]); - return ultimaParada.Nombre?.Trim() ?? string.Empty; + var destino = variante.Variante?.Destino?.Trim(); + return UtilidadesTransitoDbus.DestinoDescriptivoEsValido(destino) + ? destino! + : string.Empty; } private double? ElevacionPreferidaMapa(MuestraElevacion muestra) diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.razor b/RutasDBUS/Components/Pages/PlanificadorRutas.razor index edf7928..9138736 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.razor +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.razor @@ -367,25 +367,20 @@ @if (_itinerarioClicado is not null) { var vReal = GetVarianteRealClicada(); - var textoLineaItinerario = GetTextoLineaItinerarioClicado(vReal); + var nombreLineaItinerario = GetNombreLineaItinerarioClicado(vReal); var destinoItinerario = GetDestinoItinerarioClicado(vReal); @@ -1126,17 +1139,40 @@
- +
+ Configuración para recalcular +
+ + + +
+
diff --git a/RutasDBUS/Modelos/Historial/ConfiguracionHistorialRuta.cs b/RutasDBUS/Modelos/Historial/ConfiguracionHistorialRuta.cs new file mode 100644 index 0000000..c74b81f --- /dev/null +++ b/RutasDBUS/Modelos/Historial/ConfiguracionHistorialRuta.cs @@ -0,0 +1,70 @@ +namespace RutasDBUS.Modelos.Historial; + +/// +/// Instantánea completa de la configuración con la que se calculó una ruta. +/// +public sealed class ConfiguracionHistorialRuta +{ + public int Version { get; set; } = 1; + public bool? MostrarParadas { get; set; } + public bool UsarHorariosTeoricos { get; set; } + public int SegundosPorParadaBusManual { get; set; } + public bool ActivarRangoLineas { get; set; } + public int LineaMin { get; set; } + public int LineaMax { get; set; } + public List? LineasPermitidas { get; set; } + public bool ActivarRangoItinerarios { get; set; } + public int ItinerarioMin { get; set; } + public int ItinerarioMax { get; set; } + public string? FiltroSentidoItinerario { get; set; } + public bool ActivarRangoTransbordos { get; set; } + public int MinTransbordosPermitidos { get; set; } + public int MaxTransbordosPermitidos { get; set; } + public bool UsarHoraSimulada { get; set; } + public string? FechaSimulada { get; set; } + public string? HoraSimulada { get; set; } + public double? FactorPenalizacionDistanciaPie { get; set; } + public int? VersionFactorPenalizacionPie { get; set; } + public double? ParametroPenalizacionDistanciaPieMetros { get; set; } + public double? CoeficienteEsfuerzoAltura { get; set; } + public int? VersionCoeficienteEsfuerzo { get; set; } + public double? PesoTransbordoRankingMinutos { get; set; } + public double? FactorEquilibradoAndarBus { get; set; } + public int? VersionFactorEquilibrado { get; set; } + public bool? MostrarElevacionesRuta { get; set; } + public string? FuenteElevacionCalculo { get; set; } + public int? VersionFuenteElevacion { get; set; } + public int? VersionPendientes { get; set; } + public int? VersionColoresPendientes { get; set; } + public double? UmbralPendienteSubidaRuta { get; set; } + public double? UmbralPendienteSubidaMediaRuta { get; set; } + public double? UmbralPendienteSubidaFuerteRuta { get; set; } + public string? ColorPendienteBajadaRuta { get; set; } + public string? ColorPendienteLlanoRuta { get; set; } + public string? ColorPendienteSubidaSuaveRuta { get; set; } + public string? ColorPendienteSubidaMediaRuta { get; set; } + public string? ColorPendienteSubidaFuerteRuta { get; set; } + public double? UmbralPendienteBajadaRuta { get; set; } + public double? UmbralAlturaAlertaPendienteRutaMetros { get; set; } + public double? UmbralPisosPendienteRuta { get; set; } + public double VelocidadPieKmH { get; set; } + public double VelocidadBusKmH { get; set; } + public int ElevMaxPuntosPorTramo { get; set; } + public int RadioCandidatasMetros { get; set; } + public int? MaxCandidatasPorLado { get; set; } + public int? MinutosTransbordo { get; set; } + public int MargenTransbordoSegundos { get; set; } + public string? WalkProvider { get; set; } + public int NivelBrilloMapa { get; set; } + public bool UsarColorRealLineaEnItinerarios { get; set; } + public bool UsarOsrmParaRanking { get; set; } + public string? CriterioPrioritarioRuta { get; set; } + public string? PreferenciaEtiquetaRuta { get; set; } + + public ConfiguracionHistorialRuta Clonar() + { + var copia = (ConfiguracionHistorialRuta)MemberwiseClone(); + copia.LineasPermitidas = LineasPermitidas?.ToList(); + return copia; + } +} diff --git a/RutasDBUS/Modelos/Historial/RegistroHistorialRuta.cs b/RutasDBUS/Modelos/Historial/RegistroHistorialRuta.cs index a365b91..12547bb 100644 --- a/RutasDBUS/Modelos/Historial/RegistroHistorialRuta.cs +++ b/RutasDBUS/Modelos/Historial/RegistroHistorialRuta.cs @@ -21,6 +21,7 @@ public sealed class RegistroHistorialRuta public double? FactorEquilibradoAndarBus { get; set; } public bool ActivarFiltroLineas { get; set; } public List LineasPermitidas { get; set; } = new(); + public ConfiguracionHistorialRuta? Configuracion { get; set; } public DateTime FechaCreacionUtc { get; set; } = DateTime.UtcNow; public DateTime FechaActualizacionUtc { get; set; } = DateTime.UtcNow; } diff --git a/RutasDBUS/Modelos/RedBus/LineaBus.cs b/RutasDBUS/Modelos/RedBus/LineaBus.cs index 5617e4d..f1e4e65 100644 --- a/RutasDBUS/Modelos/RedBus/LineaBus.cs +++ b/RutasDBUS/Modelos/RedBus/LineaBus.cs @@ -55,6 +55,9 @@ public class Variante /// Código interno de la variante. public int Codigo { get; set; } + /// Destino indicado por DIR_DESTINO en las expediciones del itinerario. + public string Destino { get; set; } = ""; + /// /// Identificadores de trayectos que se recorren en esta variante, en orden. /// diff --git a/RutasDBUS/Servicios/Historial/ServicioHistorialRutasCompartido.cs b/RutasDBUS/Servicios/Historial/ServicioHistorialRutasCompartido.cs index 7762edb..c3430b1 100644 --- a/RutasDBUS/Servicios/Historial/ServicioHistorialRutasCompartido.cs +++ b/RutasDBUS/Servicios/Historial/ServicioHistorialRutasCompartido.cs @@ -89,6 +89,8 @@ public sealed class ServicioHistorialRutasCompartido : IHistorialRutasCompartido existente.FactorEquilibradoAndarBus = registro.FactorEquilibradoAndarBus; existente.ActivarFiltroLineas = registro.ActivarFiltroLineas; existente.LineasPermitidas = NormalizarLineas(registro.LineasPermitidas).ToList(); + if (registro.Configuracion is not null) + existente.Configuracion = CopiarConfiguracion(registro.Configuracion); if (registro.FechaCreacionUtc != default) existente.FechaCreacionUtc = registro.FechaCreacionUtc; @@ -300,10 +302,20 @@ public sealed class ServicioHistorialRutasCompartido : IHistorialRutasCompartido FactorEquilibradoAndarBus = item.FactorEquilibradoAndarBus, ActivarFiltroLineas = item.ActivarFiltroLineas, LineasPermitidas = NormalizarLineas(item.LineasPermitidas).ToList(), + Configuracion = item.Configuracion is null + ? null + : CopiarConfiguracion(item.Configuracion), FechaCreacionUtc = item.FechaCreacionUtc, FechaActualizacionUtc = item.FechaActualizacionUtc }; + private static ConfiguracionHistorialRuta CopiarConfiguracion(ConfiguracionHistorialRuta configuracion) + { + var copia = configuracion.Clonar(); + copia.LineasPermitidas = NormalizarLineas(copia.LineasPermitidas).ToList(); + return copia; + } + private static string CalcularClaveEscenario(RegistroHistorialRuta item) { var origenLat = Math.Round(item.OrigenLatitud, 6).ToString("0.000000", CultureInfo.InvariantCulture); diff --git a/RutasDBUS/Servicios/RedBus/CreadorRedTransporteDbus.cs b/RutasDBUS/Servicios/RedBus/CreadorRedTransporteDbus.cs index 62805a9..04fba2b 100644 --- a/RutasDBUS/Servicios/RedBus/CreadorRedTransporteDbus.cs +++ b/RutasDBUS/Servicios/RedBus/CreadorRedTransporteDbus.cs @@ -95,7 +95,7 @@ public sealed class CreadorRedTransporteDbus var codigoVariante = ExtraerCodigoVariante(exp.IdPatronExp); var key = $"{exp.IdRuta}|{codigoItinerario}|{sentido}|{exp.IdPatronExp}"; - if (!variantesTemp.ContainsKey(key)) + if (!variantesTemp.TryGetValue(key, out var varianteTemp)) { variantesTemp[key] = new VarianteTemporal { @@ -108,6 +108,11 @@ public sealed class CreadorRedTransporteDbus StopIds = stopIds }; } + else if (string.IsNullOrWhiteSpace(varianteTemp.Destino) && + !string.IsNullOrWhiteSpace(exp.DirDestino)) + { + varianteTemp.Destino = exp.DirDestino; + } } var nextTrayectoId = 1; @@ -132,7 +137,11 @@ public sealed class CreadorRedTransporteDbus itinerario.Sentidos.Add(sentido); } - var variante = new Variante { Codigo = temp.CodigoVariante }; + var variante = new Variante + { + Codigo = temp.CodigoVariante, + Destino = temp.Destino?.Trim() ?? "" + }; var stopIdsValidos = temp.StopIds.Where(paradaById.ContainsKey).ToList(); var codigosParadas = stopIdsValidos .Select(id => paradaById[id].Codigo) diff --git a/RutasDBUS/wwwroot/app.css b/RutasDBUS/wwwroot/app.css index 41b06f0..c2b8bff 100644 --- a/RutasDBUS/wwwroot/app.css +++ b/RutasDBUS/wwwroot/app.css @@ -55,14 +55,16 @@ .inspector-previous-point { display: grid; - gap: 4px; + gap: 5px; margin: 7px 0 6px; - padding: 7px 0 2px; + padding: 8px 0 2px; border-top: 1px solid rgba(148, 163, 184, .25); } -.inspector-previous-point__distance { - line-height: 1.3; +.inspector-previous-point__title { + color: #e2e8f0; + font-size: .78rem; + font-weight: 700; } .inspector-previous-point__details { @@ -74,10 +76,8 @@ line-height: 1.25; } -.inspector-previous-point__details > span:first-child { +.inspector-previous-point__detail--full { flex-basis: 100%; - color: #cbd5e1; - font-weight: 700; } .rt-inspector-place-icon { @@ -788,12 +788,6 @@ html, body { line-height: 1.3; } -.popup-itinerario-header__destination { - margin-left: .3rem; - color: #bae6fd; - font-weight: 600; -} - .popup-parada-body { font-size: .8rem; } @@ -1374,70 +1368,59 @@ html, body { gap: .75rem; } -.history-parameters-toggle { +.history-config-mode { display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: .65rem; - min-height: 30px; - padding-bottom: .6rem; + gap: .35rem; + padding-bottom: .65rem; border-bottom: 1px solid rgba(148,163,184,.2); - color: #cbd5e1; - cursor: pointer; - user-select: none; } -.history-parameters-toggle__label { - min-width: 0; - font-size: .78rem; +.history-config-mode__label { + color: #cbd5e1; + font-size: .75rem; font-weight: 700; } -.history-parameters-toggle input { - position: absolute; - opacity: 0; - pointer-events: none; +.history-config-mode__options { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 2px; + padding: 2px; + border: 1px solid rgba(148,163,184,.42); + border-radius: 8px; + background: rgba(2,6,23,.58); } -.history-parameters-toggle__track { - position: relative; - width: 36px; - height: 20px; - border: 1px solid rgba(148,163,184,.55); - border-radius: 999px; - background: rgba(2,6,23,.72); - transition: background .15s ease, border-color .15s ease; +.history-config-mode__option { + min-width: 0; + height: 28px; + overflow: hidden; + border: 0; + border-radius: 6px; + padding: 0 .35rem; + background: transparent; + color: #94a3b8; + font-size: .7rem; + font-weight: 700; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; } -.history-parameters-toggle__thumb { - position: absolute; - top: 2px; - left: 2px; - width: 14px; - height: 14px; - border-radius: 999px; - background: #94a3b8; - box-shadow: 0 1px 4px rgba(0,0,0,.35); - transition: transform .15s ease, background .15s ease; -} - -.history-parameters-toggle--active { +.history-config-mode__option:hover:not(:disabled) { + background: rgba(51,65,85,.72); color: #f8fafc; } -.history-parameters-toggle--active .history-parameters-toggle__track { - border-color: rgba(59,130,246,.85); - background: rgba(37,99,235,.82); +.history-config-mode__option--active { + background: #2563eb; + color: #fff; + box-shadow: 0 1px 4px rgba(0,0,0,.3); } -.history-parameters-toggle--active .history-parameters-toggle__thumb { - transform: translateX(16px); - background: #f8fafc; -} - -.history-parameters-toggle:has(input:disabled) { +.history-config-mode__option:disabled { cursor: default; - opacity: .6; + opacity: .58; } .history-section { diff --git a/RutasDBUS/wwwroot/app.js b/RutasDBUS/wwwroot/app.js index d50c7c4..e9ff6a7 100644 --- a/RutasDBUS/wwwroot/app.js +++ b/RutasDBUS/wwwroot/app.js @@ -7,16 +7,22 @@ }; window.initAltInspector = function (dotNetHelper) { + const setInspectorActive = function (active) { + window.rt = window.rt || {}; + window.rt._inspectorActive = !!active; + dotNetHelper.invokeMethodAsync("SetInspectorActivo", !!active); + }; + document.addEventListener("keydown", function (e) { - if (e.key === "Z" || e.key === "z") dotNetHelper.invokeMethodAsync("SetInspectorActivo", true); + if (e.key === "Z" || e.key === "z") setInspectorActive(true); }, true); document.addEventListener("keyup", function (e) { - if (e.key === "Z" || e.key === "z") dotNetHelper.invokeMethodAsync("SetInspectorActivo", false); + if (e.key === "Z" || e.key === "z") setInspectorActive(false); }, true); window.addEventListener("blur", function () { - dotNetHelper.invokeMethodAsync("SetInspectorActivo", false); + setInspectorActive(false); }); }; @@ -84,6 +90,40 @@ window.initAltInspector = function (dotNetHelper) { window.rt = window.rt || {}; +window.rt.handleInspectorLayerClick = function (event) { + if (!window.rt._inspectorActive) return false; + + const originalEvent = event && event.originalEvent; + if (originalEvent) { + if (originalEvent.preventDefault) originalEvent.preventDefault(); + if (originalEvent.stopPropagation) originalEvent.stopPropagation(); + if (originalEvent.stopImmediatePropagation) originalEvent.stopImmediatePropagation(); + } + + const alreadyHandled = !!( + (event && event.__rtInspectorHandled) || + (originalEvent && originalEvent.__rtInspectorHandled)); + + if (event) event.__rtInspectorHandled = true; + if (originalEvent) originalEvent.__rtInspectorHandled = true; + + if (!alreadyHandled && window.rt._dotNetHelper) { + const latLng = event && event.latlng + ? event.latlng + : event && event.target && typeof event.target.getLatLng === "function" + ? event.target.getLatLng() + : null; + const lat = Number(latLng && latLng.lat); + const lon = Number(latLng && latLng.lng); + + if (Number.isFinite(lat) && Number.isFinite(lon)) { + window.rt._dotNetHelper.invokeMethodAsync("OnInspectorOverlayClick", lat, lon); + } + } + + return true; +}; + window.rt.ensureRouteLayer = function () { const map = window.__rtLeafletMap; if (!map || !window.L) { @@ -290,6 +330,8 @@ window.rt.drawRoute = function (id, coords, options) { const attachRouteClick = function (line) { line.on("click", function (e) { try { + if (window.rt.handleInspectorLayerClick(e)) return; + if (e && e.originalEvent) { if (e.originalEvent.preventDefault) e.originalEvent.preventDefault(); if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation(); @@ -422,6 +464,8 @@ window.rt.drawRoute = function (id, coords, options) { segmentLine.on("mouseover", updateTooltipAtCursor); segmentLine.on("mousemove", updateTooltipAtCursor); segmentLine.on("click", function (e) { + if (window.rt.handleInspectorLayerClick(e)) return; + window.rt.selectElevationSegment( id, segmentLine, @@ -732,12 +776,15 @@ window.rt.drawElevPoints = function (id, points, options) { opacity: 0.92, className: "rt-elev-tooltip" }); - m.on("click", function (e) { - if (!window.rt.isMobileLayout || !window.rt.isMobileLayout()) return; - try { m.openTooltip(e && e.latlng); } catch { } - }); } + m.on("click", function (e) { + if (window.rt.handleInspectorLayerClick(e)) return; + if (!showSlopeTooltips || !p || typeof p.slope !== "number" || !Number.isFinite(p.slope)) return; + if (!window.rt.isMobileLayout || !window.rt.isMobileLayout()) return; + try { m.openTooltip(e && e.latlng); } catch { } + }); + m.addTo(group); } } @@ -978,6 +1025,8 @@ window.rt.drawPreviewLine = function (id, coords, options) { // ✅ Hacer la línea clicable y avisar a Blazor line.on("click", function (e) { try { + if (window.rt.handleInspectorLayerClick(e)) return; + if (e && e.originalEvent) { if (e.originalEvent.preventDefault) e.originalEvent.preventDefault(); if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation(); @@ -1166,6 +1215,8 @@ window.rt.createHighlightedStopMarker = function (stop, pane, options) { if (stop.code && window.rt._dotNetHelper) { marker.on("click", function (event) { try { + if (window.rt.handleInspectorLayerClick(event)) return; + if (event && event.originalEvent) { if (event.originalEvent.preventDefault) event.originalEvent.preventDefault(); if (event.originalEvent.stopPropagation) event.originalEvent.stopPropagation(); @@ -1355,7 +1406,10 @@ window.rt.setEndpointMarker = function (kind, lat, lon, options) { if (e.originalEvent.stopImmediatePropagation) e.originalEvent.stopImmediatePropagation(); }; - marker.on("click", stopEvent); + marker.on("click", function (event) { + if (window.rt.handleInspectorLayerClick(event)) return; + stopEvent(event); + }); marker.on("mousedown", stopEvent); marker.on("dragend", function (e) { @@ -1438,6 +1492,9 @@ window.rt.setInspectorMarkers = function (lat, lon, places) { opacity: 0.95, className: "rt-inspector-tooltip" }); + puntoZ.on("click", function (event) { + window.rt.handleInspectorLayerClick(event); + }); puntoZ.addTo(window.rt._inspectorLayer); const lugares = Array.isArray(places) ? places.slice(0, 3) : []; @@ -1510,6 +1567,9 @@ window.rt.setInspectorMarkers = function (lat, lon, places) { opacity: 0.95, className: "rt-inspector-tooltip" }); + marker.on("click", function (event) { + window.rt.handleInspectorLayerClick(event); + }); marker.addTo(window.rt._inspectorLayer); }); };