diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs index 0e13c8b..9a15384 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs @@ -49,6 +49,8 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable try { await JS.InvokeVoidAsync("initBrilloMapa", _dotRef); } catch (Exception ex) { Console.WriteLine("initBrilloMapa: " + ex); } try { await JS.InvokeVoidAsync("initAltInspector", _dotRef); } catch (Exception ex) { Console.WriteLine("initAltInspector: " + ex); } + try { await JS.InvokeVoidAsync("initZonasShortcut", _dotRef); } catch (Exception ex) { Console.WriteLine("initZonasShortcut: " + ex); } + try { await JS.InvokeVoidAsync("initFabricaShortcut", _dotRef); } catch (Exception ex) { Console.WriteLine("initFabricaShortcut: " + 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); } diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs index 31395f0..d9d6669 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs @@ -1400,7 +1400,7 @@ public partial class PlanificadorRutas : ComponentBase private bool _elevPopupMinimizado = false; - private const string _versionApp = "(v-20260908a)"; + private const string _versionApp = "(v-20260908b)"; private double? _paradaElevIdee; diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs index 14c6bb6..01016f1 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs @@ -1541,6 +1541,8 @@ public partial class PlanificadorRutas : ComponentBase _inspectorElevLocal2 = null; _inspectorElevLocal1 = null; _inspectorLugaresCercanos = Array.Empty(); + _inspectorSuperficieTexto = null; + _inspectorSuperficieCargando = true; _popupInspectorVisible = true; ObtenerElevacionesLocalesComparativasPorLote(new[] { (lat, lon) }); @@ -1558,6 +1560,8 @@ public partial class PlanificadorRutas : ComponentBase await ActualizarMarcadoresInspectorJsAsync(); StateHasChanged(); + _ = CompletarSuperficiePuntoInspectorAsync(lat, lon, versionInspector); + if (_inspectorPuntoAnterior is { } puntoAnterior) _ = CompletarDatosPuntoInspectorAnteriorAsync(puntoAnterior, versionInspector); @@ -1648,6 +1652,8 @@ public partial class PlanificadorRutas : ComponentBase _popupInspectorVisible = false; _inspectorPuntoAnterior = null; _inspectorLugaresCercanos = Array.Empty(); + _inspectorSuperficieCargando = false; + _inspectorSuperficieTexto = null; try { await JS.InvokeVoidAsync("rt.clearInspectorMarkers"); } catch { } } diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.RestriccionesCaminata.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.RestriccionesCaminata.cs index 1cd5fbb..3f8c216 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.RestriccionesCaminata.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.RestriccionesCaminata.cs @@ -20,7 +20,6 @@ public partial class PlanificadorRutas _zonasExclusionCaminata.Find(zona => zona.Id == _zonaExclusionSeleccionada); private bool _modoDibujoExclusionCaminata => _mostrarHerramientaZonas && ZonaExclusionSeleccionada is { Cerrada: false }; - private int NumeroZonasExclusion => _zonasExclusionCaminata.Count(zona => zona.Cerrada); private List>? ObtenerPoligonosExclusionCaminata() { @@ -37,6 +36,17 @@ public partial class PlanificadorRutas await ActualizarEditorExclusionCaminataAsync(); } + [JSInvokable] + public async Task AlternarHerramientaZonas() + { + if (_mostrarHerramientaZonas) + await CerrarHerramientaZonasAsync(); + else + await AbrirHerramientaZonasAsync(); + + await InvokeAsync(StateHasChanged); + } + private async Task CerrarHerramientaZonasAsync() { _zonasExclusionCaminata.RemoveAll(zona => !zona.Cerrada); diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Superficie.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Superficie.cs index fb95bf2..a4768e6 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Superficie.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Superficie.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Globalization; using Microsoft.JSInterop; using RutasDBUS.Modelos.Planificacion; @@ -7,6 +8,96 @@ namespace RutasDBUS.Components.Pages; public partial class PlanificadorRutas { private int _versionConsultaSuperficieSegmento; + private bool _inspectorSuperficieCargando; + private string? _inspectorSuperficieTexto; + private readonly ConcurrentDictionary> _superficiePuntosInspectorCache = new(); + + /// + /// Consulta la superficie del camino más cercano a un punto Z. Valhalla necesita + /// una traza, por eso se construye una pequeña línea centrada en el punto. + /// + private async Task CompletarSuperficiePuntoInspectorAsync( + double latitud, + double longitud, + int versionInspector) + { + try + { + var informacion = await ObtenerSuperficiePuntoInspectorCacheadaAsync(latitud, longitud); + if (versionInspector != _versionInspector + || !_popupInspectorVisible + || Math.Abs(_inspectorLat - latitud) > 0.00000001 + || Math.Abs(_inspectorLon - longitud) > 0.00000001) + { + return; + } + + _inspectorSuperficieTexto = FormatearSuperficiePuntoInspector(informacion); + _inspectorSuperficieCargando = false; + await InvokeAsync(StateHasChanged); + } + catch + { + if (versionInspector != _versionInspector + || !_popupInspectorVisible + || Math.Abs(_inspectorLat - latitud) > 0.00000001 + || Math.Abs(_inspectorLon - longitud) > 0.00000001) + { + return; + } + + _inspectorSuperficieTexto = "No disponible"; + _inspectorSuperficieCargando = false; + await InvokeAsync(StateHasChanged); + } + } + + private Task ObtenerSuperficiePuntoInspectorCacheadaAsync( + double latitud, + double longitud) + { + var clave = $"{latitud:0.000000}|{longitud:0.000000}"; + return _superficiePuntosInspectorCache.GetOrAdd( + clave, + _ => ClienteValhalla.ObtenerInformacionSueloAsync( + CrearTrazaSondaSuperficie(latitud, longitud))); + } + + private static double[,] CrearTrazaSondaSuperficie(double latitud, double longitud) + { + const double semilongitudMetros = 12.0; + const double metrosPorGrado = 111_320.0; + + var deltaLatitud = semilongitudMetros / metrosPorGrado; + + return new double[,] + { + { latitud - deltaLatitud, longitud }, + { latitud, longitud }, + { latitud + deltaLatitud, longitud } + }; + } + + private static string FormatearSuperficiePuntoInspector(InformacionSueloTramo? informacion) + { + if (informacion is null || !informacion.Disponible || informacion.Segmentos.Count == 0) + return "No disponible"; + + var superficies = informacion.Segmentos + .Select(segmento => + { + var uso = string.IsNullOrWhiteSpace(segmento.Uso) + ? "" + : $" ({segmento.Uso})"; + return $"{segmento.Superficie}{uso}"; + }) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + return superficies.Count == 0 + ? "No disponible" + : string.Join(" · ", superficies); + } /// /// Consulta la superficie únicamente del segmento de elevación que se acaba de seleccionar. diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.ValoresFabrica.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.ValoresFabrica.cs index af3741a..891c044 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.ValoresFabrica.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.ValoresFabrica.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; using RutasDBUS.Modelos.Historial; using RutasDBUS.Servicios.Configuracion; @@ -34,4 +35,18 @@ public partial class PlanificadorRutas AplicarConfiguracionLocal(predeterminada); _modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual; } + + [JSInvokable] + public async Task GuardarConfiguracionFabricaDesdeAtajoAsync() + { + if (!_configuracionLocalCargada) + return; + + var actual = CrearConfiguracionLocal(); + if (!await ValoresFabrica.GuardarAsync(actual)) + return; + + _configuracionFabrica = actual.Clonar(); + await InvokeAsync(StateHasChanged); + } } diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.razor b/RutasDBUS/Components/Pages/PlanificadorRutas.razor index 4fdef7b..43cc1fa 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.razor +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.razor @@ -228,15 +228,6 @@ } - @* STACK DE POPUPS (izquierda) *@ @if (_paradaClicada is not null || _itinerarioClicado is not null || _popupInspectorVisible) @@ -455,6 +446,17 @@ } +
+ Superficie: + @if (_inspectorSuperficieCargando) + { + Consultando... + } + else + { + @(_inspectorSuperficieTexto ?? "No disponible") + } +
@if (_inspectorLugaresCercanos.Count > 0) { diff --git a/RutasDBUS/Servicios/Configuracion/ServicioValoresFabrica.cs b/RutasDBUS/Servicios/Configuracion/ServicioValoresFabrica.cs index 35b6cbf..60828a1 100644 --- a/RutasDBUS/Servicios/Configuracion/ServicioValoresFabrica.cs +++ b/RutasDBUS/Servicios/Configuracion/ServicioValoresFabrica.cs @@ -37,6 +37,42 @@ public sealed class ServicioValoresFabrica finally { _bloqueo.Release(); } } + public async Task GuardarAsync(ConfiguracionHistorialRuta config) + { + if (!EsValida(config)) + return false; + + await _bloqueo.WaitAsync(); + var temporal = _ruta + ".tmp"; + try + { + var directorio = Path.GetDirectoryName(_ruta); + if (!string.IsNullOrWhiteSpace(directorio)) + Directory.CreateDirectory(directorio); + + await using (var archivo = File.Create(temporal)) + { + await JsonSerializer.SerializeAsync( + archivo, + config, + new JsonSerializerOptions { WriteIndented = true }); + } + + File.Move(temporal, _ruta, overwrite: true); + return true; + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException) + { + _logger.LogWarning("No se pudieron guardar los valores de fabrica: {Tipo}", ex.GetType().Name); + try { if (File.Exists(temporal)) File.Delete(temporal); } catch { } + return false; + } + finally + { + _bloqueo.Release(); + } + } + private static bool EsValida(ConfiguracionHistorialRuta config) { static bool Positivo(double? valor) => valor is { } v && double.IsFinite(v) && v > 0; diff --git a/RutasDBUS/wwwroot/app.css b/RutasDBUS/wwwroot/app.css index bc41d80..d698d7d 100644 --- a/RutasDBUS/wwwroot/app.css +++ b/RutasDBUS/wwwroot/app.css @@ -1363,6 +1363,10 @@ html, body { color: #9ca3af; } +.inspector-surface-row { + overflow-wrap: anywhere; +} + /* LISTA DE TRAMOS TIPO “DIRECCIONES” CON NÚMEROS */ .lista-tramos-ruta { diff --git a/RutasDBUS/wwwroot/app.js b/RutasDBUS/wwwroot/app.js index c41f19b..99a3835 100644 --- a/RutasDBUS/wwwroot/app.js +++ b/RutasDBUS/wwwroot/app.js @@ -14,7 +14,10 @@ window.initAltInspector = function (dotNetHelper) { }; document.addEventListener("keydown", function (e) { - if (e.key === "Z" || e.key === "z") setInspectorActive(true); + if (!e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey && + (e.key === "Z" || e.key === "z")) { + setInspectorActive(true); + } }, true); document.addEventListener("keyup", function (e) { @@ -26,6 +29,36 @@ window.initAltInspector = function (dotNetHelper) { }); }; +window.initZonasShortcut = function (dotNetHelper) { + if (window.rt && window.rt._zonasShortcutInicializado) return; + + window.rt = window.rt || {}; + window.rt._zonasShortcutInicializado = true; + + document.addEventListener("keydown", function (e) { + if (e.repeat || e.code !== "KeyZ" || !e.ctrlKey || !e.altKey || e.shiftKey) return; + + e.preventDefault(); + e.stopImmediatePropagation(); + dotNetHelper.invokeMethodAsync("AlternarHerramientaZonas").catch(function () { }); + }, true); +}; + +window.initFabricaShortcut = function (dotNetHelper) { + if (window.rt && window.rt._fabricaShortcutInicializado) return; + + window.rt = window.rt || {}; + window.rt._fabricaShortcutInicializado = true; + + document.addEventListener("keydown", function (e) { + if (e.repeat || e.code !== "KeyF" || !e.ctrlKey || !e.altKey || e.shiftKey || e.metaKey) return; + + e.preventDefault(); + e.stopImmediatePropagation(); + dotNetHelper.invokeMethodAsync("GuardarConfiguracionFabricaDesdeAtajoAsync").catch(function () { }); + }, true); +}; +