From 7dc251f44ef5b488f8ea5627acf11ba8dd41c3e6 Mon Sep 17 00:00:00 2001 From: Pedro Date: Fri, 17 Jul 2026 12:00:30 +0200 Subject: [PATCH] adaptacion a movil de funcionalidad y vista --- RutasDBUS/Components/App.razor | 4 +- .../Pages/PlanificadorRutas.CicloVidaYMapa.cs | 2 + .../Pages/PlanificadorRutas.Estado.cs | 5 +- .../Pages/PlanificadorRutas.Historial.cs | 163 +++- .../Pages/PlanificadorRutas.Itinerarios.cs | 3 +- .../Pages/PlanificadorRutas.Planificacion.cs | 43 + .../Pages/PlanificadorRutas.Renderizado.cs | 1 + .../Components/Pages/PlanificadorRutas.razor | 48 +- RutasDBUS/wwwroot/app.css | 782 ++++++++++++++++++ RutasDBUS/wwwroot/app.js | 123 ++- 10 files changed, 1123 insertions(+), 51 deletions(-) diff --git a/RutasDBUS/Components/App.razor b/RutasDBUS/Components/App.razor index 41d455b..38ecdf7 100644 --- a/RutasDBUS/Components/App.razor +++ b/RutasDBUS/Components/App.razor @@ -7,14 +7,14 @@ - + - + diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs index 913c010..54f5261 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs @@ -50,6 +50,7 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable try { await JS.InvokeVoidAsync("initAltInspector", _dotRef); } catch (Exception ex) { Console.WriteLine("initAltInspector: " + ex); } try { await JS.InvokeVoidAsync("rt.setDotNetHelper", _dotRef); } catch (Exception ex) { Console.WriteLine("rt.setDotNetHelper: " + ex); } try { await JS.InvokeVoidAsync("rt.blockLeafletDoubleClick"); } catch (Exception ex) { Console.WriteLine("rt.blockLeafletDoubleClick: " + ex); } + 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 AsegurarHistorialLocalCargadoAsync(); @@ -253,6 +254,7 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable private double _inspectorLat, _inspectorLon; private double? _inspectorElev; private double? _inspectorElevIdee; + private string? _inspectorCallejero; /// diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Estado.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Estado.cs index 865f754..986aa72 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Estado.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Estado.cs @@ -351,6 +351,7 @@ public partial class PlanificadorRutas : ComponentBase private TimeSpan? _tiempoUltimoCalculoRuta = null; private bool _mostrarElevacionesRuta = false; private bool _pintandoElevacionesRuta = false; + private bool _panelRutasMovilContraido = false; private double _umbralPendienteSubidaRuta = 3.0; private double _umbralPendienteBajadaRuta = 3.0; private readonly Dictionary> _paradasItinerarioVisiblesPorId = new(StringComparer.OrdinalIgnoreCase); @@ -1111,9 +1112,7 @@ public partial class PlanificadorRutas : ComponentBase if (string.IsNullOrWhiteSpace(distancia)) return nombre; - return cercano.DistanciaMetros <= 20 - ? $"{nombre} ({distancia})" - : $"mas cercano: {nombre} ({distancia})"; + return $"{nombre} ({distancia})"; } private static string LimpiarTextoCallejeroResumen(string texto) diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Historial.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Historial.cs index bac82fe..e4f4398 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Historial.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Historial.cs @@ -1,5 +1,6 @@ using System.Globalization; using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.JSInterop; using RutasDBUS.Modelos.Historial; @@ -8,8 +9,14 @@ namespace RutasDBUS.Components.Pages; public partial class PlanificadorRutas { private const string ClaveHistorialLocal = "rutasdbus.planificador.routeHistory.v1"; + private const string ClaveRespaldoHistorialLocal = "rutasdbus.planificador.routeHistory.recovery.v1"; private const int MaxItemsHistorialLocal = 100; private const int IntervaloRefrescoHistorialCompartidoSegundos = 3; + private static readonly JsonSerializerOptions OpcionesLecturaHistorialLocal = new() + { + PropertyNameCaseInsensitive = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString + }; private readonly List _historialLocal = new(); private readonly List _historialCompartido = new(); @@ -26,25 +33,38 @@ public partial class PlanificadorRutas private ConfiguracionLocalPlanificador? _configuracionTemporalPreviaHistorial; private Task? _tareaCargaHistorialLocal; private bool _historialLocalCargado = false; + private bool _historialLocalCargando = false; + private int _versionAperturaHistorial; private async Task ToggleHistorialPanel() { if (_mostrarHistorialRutas) { + _versionAperturaHistorial++; _mostrarHistorialRutas = false; + _historialLocalCargando = false; StopRefrescoHistorialCompartido(); await RestaurarConfiguracionTemporalHistorialAsync(); return; } - if (!await AsegurarHistorialLocalCargadoAsync()) - { - MostrarMensajeHistorial("No he podido cargar el historial personal. Intentalo de nuevo.", esError: true); - return; - } - + var versionApertura = ++_versionAperturaHistorial; _mostrarHistorialRutas = true; + _historialLocalCargando = !_historialLocalCargado; + StateHasChanged(); + + var historialLocalCargado = await AsegurarHistorialLocalCargadoAsync(); + if (!_mostrarHistorialRutas || versionApertura != _versionAperturaHistorial) + return; + + _historialLocalCargando = false; + if (!historialLocalCargado) + MostrarMensajeHistorial("No he podido cargar el historial personal. Intentalo de nuevo.", esError: true); + await CargarHistorialCompartidoAsync(); + if (!_mostrarHistorialRutas || versionApertura != _versionAperturaHistorial) + return; + StartRefrescoHistorialCompartido(); } @@ -55,44 +75,127 @@ public partial class PlanificadorRutas _tareaCargaHistorialLocal ??= CargarHistorialLocalDesdeNavegadorAsync(); var tareaCarga = _tareaCargaHistorialLocal; - var cargado = await tareaCarga; - - if (!cargado && ReferenceEquals(_tareaCargaHistorialLocal, tareaCarga)) - _tareaCargaHistorialLocal = null; - - return cargado; + try + { + return await tareaCarga; + } + catch + { + return false; + } + finally + { + if (!_historialLocalCargado && ReferenceEquals(_tareaCargaHistorialLocal, tareaCarga)) + _tareaCargaHistorialLocal = null; + } } private async Task CargarHistorialLocalDesdeNavegadorAsync() { + string? json; try { - var json = await JS.InvokeAsync("rt.loadLocalValue", ClaveHistorialLocal); - if (!string.IsNullOrWhiteSpace(json)) - { - var items = JsonSerializer.Deserialize>(json); - if (items is null) - return false; - - _historialLocal.Clear(); - _historialLocal.AddRange(items - .Where(x => !string.IsNullOrWhiteSpace(x.Id)) - .OrderByDescending(x => x.FechaActualizacionUtc) - .Take(MaxItemsHistorialLocal)); - } - - _historialLocalSeleccionadoId = _historialLocal.FirstOrDefault()?.Id ?? string.Empty; - _historialLocalCargado = true; - return true; + json = await JS.InvokeAsync("rt.loadLocalValue", ClaveHistorialLocal); } catch (JSException) { return false; } - catch (JsonException) + catch (Exception) { return false; } + + if (string.IsNullOrWhiteSpace(json)) + { + FinalizarCargaHistorialLocal(Array.Empty()); + return true; + } + + try + { + var items = DeserializarHistorialLocal(json, out var registrosOmitidos); + FinalizarCargaHistorialLocal(items); + + if (registrosOmitidos > 0) + { + await GuardarHistorialLocalRecuperadoAsync(json); + MostrarMensajeHistorial( + $"Se han recuperado {_historialLocal.Count} registros del historial; {registrosOmitidos} estaban dañados.", + esError: true); + } + + return true; + } + catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException) + { + FinalizarCargaHistorialLocal(Array.Empty()); + await GuardarHistorialLocalRecuperadoAsync(json); + MostrarMensajeHistorial( + "El historial personal guardado estaba dañado. Se ha conservado una copia y se ha iniciado uno nuevo.", + esError: true); + return true; + } + } + + private static IReadOnlyList DeserializarHistorialLocal( + string json, + out int registrosOmitidos) + { + using var documento = JsonDocument.Parse(json); + if (documento.RootElement.ValueKind != JsonValueKind.Array) + throw new JsonException("El historial local no contiene una lista."); + + registrosOmitidos = 0; + var recuperados = new List(); + foreach (var elemento in documento.RootElement.EnumerateArray()) + { + try + { + var item = elemento.Deserialize(OpcionesLecturaHistorialLocal); + if (item is null) + { + registrosOmitidos++; + continue; + } + + if (string.IsNullOrWhiteSpace(item.Id)) + item.Id = Guid.NewGuid().ToString("N"); + + item.LineasPermitidas ??= new List(); + recuperados.Add(item); + } + catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException) + { + registrosOmitidos++; + } + } + + return recuperados + .OrderByDescending(x => x.FechaActualizacionUtc) + .Take(MaxItemsHistorialLocal) + .ToList(); + } + + private void FinalizarCargaHistorialLocal(IEnumerable items) + { + _historialLocal.Clear(); + _historialLocal.AddRange(items); + _historialLocalSeleccionadoId = _historialLocal.FirstOrDefault()?.Id ?? string.Empty; + _historialLocalCargado = true; + } + + private async Task GuardarHistorialLocalRecuperadoAsync(string jsonOriginal) + { + try + { + await JS.InvokeVoidAsync("rt.saveLocalValue", ClaveRespaldoHistorialLocal, jsonOriginal); + var jsonRecuperado = JsonSerializer.Serialize(_historialLocal); + await JS.InvokeVoidAsync("rt.saveLocalValue", ClaveHistorialLocal, jsonRecuperado); + } + catch (JSException) + { + } } private async Task PersistirHistorialLocalAsync() diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs index ec2854d..2db5773 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs @@ -34,6 +34,7 @@ public partial class PlanificadorRutas : ComponentBase private void ToggleBuscadorMapa() { _mostrarBuscadorMapa = !_mostrarBuscadorMapa; + _panelRutasMovilContraido = _mostrarBuscadorMapa; } /// @@ -1181,7 +1182,7 @@ public partial class PlanificadorRutas : ComponentBase private bool _elevPopupMinimizado = false; - private const string _versionApp = "(v-20260715a)"; + private const string _versionApp = "(v-20260716a)"; private double? _paradaElevG; diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs index 4acc6ef..03618fd 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs @@ -1269,6 +1269,7 @@ public partial class PlanificadorRutas : ComponentBase { _inspectorLat = lat; _inspectorLon = lon; + _inspectorCallejero = null; try { @@ -1316,6 +1317,19 @@ public partial class PlanificadorRutas : ComponentBase _inspectorElevIdee = null; } + try + { + var lugar = await GeocodificadorLocal.BuscarMasCercanoAsync( + lat, + lon, + CancellationToken.None); + _inspectorCallejero = FormatearLugarCallejeroResumen(lugar); + } + catch + { + _inspectorCallejero = null; + } + _popupInspectorVisible = true; StateHasChanged(); } @@ -1411,6 +1425,34 @@ public partial class PlanificadorRutas : ComponentBase } }); } + + /// + /// Selecciona origen o destino mediante pulsacion larga en dispositivos tactiles. + /// + [JSInvokable] + public async Task OnMobileMapLongPress(double lat, double lon) + { + await InvokeAsync(async () => + { + if (_inspectorActivo) + return; + + _ctsDobleClick?.Cancel(); + _ctsDobleClick?.Dispose(); + _ctsDobleClick = null; + _primerClickPendiente = null; + _paradaPrimerClickPendiente = null; + + var paradaHit = await DetectarParadaHitAsync(lat, lon); + if (paradaHit is null && !_mostrarParadas) + paradaHit = await DetectarParadaDestacadaHitAsync(lat, lon); + + if (paradaHit is not null) + await EjecutarSeleccionOrigenDestinoAsync(paradaHit.Latitud, paradaHit.Longitud); + else + await EjecutarSeleccionOrigenDestinoAsync(lat, lon); + }); + } /// /// Gestiona seleccion origen destino. @@ -1515,6 +1557,7 @@ public partial class PlanificadorRutas : ComponentBase return; var cronometroCalculo = System.Diagnostics.Stopwatch.StartNew(); + _panelRutasMovilContraido = _mostrarBuscadorMapa; await CerrarTodasLasPreviewsAsync(); diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs index 3926c32..194ac0d 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs @@ -984,6 +984,7 @@ public partial class PlanificadorRutas : ComponentBase _duracionTotalSegundos = 0; _opcionesRuta.Clear(); _indiceAlternativaSeleccionada = 0; + _panelRutasMovilContraido = false; if (_mapa != null) { diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.razor b/RutasDBUS/Components/Pages/PlanificadorRutas.razor index 6a6c4a2..a3480f7 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.razor +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.razor @@ -26,11 +26,13 @@

- Planificador de rutas en Dbus + Planificador de rutas en Dbus + Rutas Dbus @_versionApp

- Haz doble clic en el mapa para marcar origen y destino + Haz doble clic en el mapa para marcar origen y destino + Mantén pulsado el mapa para marcar origen y destino
@@ -77,10 +79,11 @@ 🕒 @_horaAhoraTxt @if (_inspectorActivo) { @@ -90,24 +93,29 @@ } - - - @@ -336,6 +344,10 @@