- Click del punto Z prevalece

- Añadida info del punto z anterior
      + Titulo cambiado
      + Direccion
      + Distancia andando
- Lugares reubicados en el modal del punto Z
- Añadida Configuracion completa al json del historial
- Modo por defecto añadido al switch de configuracion de historial
- Foco añadido a las versiones andando
This commit is contained in:
2026-09-04 13:36:46 +02:00
parent 792992a67d
commit bb9a407c04
15 changed files with 718 additions and 274 deletions

View File

@@ -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.initMobileMapControls"); } catch (Exception ex) { Console.WriteLine("rt.initMobileMapControls: " + ex); }
try { await JS.InvokeVoidAsync("rt.initDraggablePopups"); } catch (Exception ex) { Console.WriteLine("rt.initDraggablePopups: " + ex); } try { await JS.InvokeVoidAsync("rt.initDraggablePopups"); } catch (Exception ex) { Console.WriteLine("rt.initDraggablePopups: " + ex); }
await CargarConfiguracionLocalAsync(); await CargarConfiguracionLocalAsync();
await CargarPreferenciaParametrosActualesHistorialAsync(); await CargarModoConfiguracionHistorialAsync();
await AsegurarHistorialLocalCargadoAsync(); await AsegurarHistorialLocalCargadoAsync();
await CargarHistorialCompartidoAsync(); await CargarHistorialCompartidoAsync();
await CargarModelosHorariosAsync(); await CargarModelosHorariosAsync();
@@ -280,6 +280,9 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
[JSInvokable] [JSInvokable]
public async Task OnPreviewItinerarioClick(string previewId) public async Task OnPreviewItinerarioClick(string previewId)
{ {
if (_inspectorActivo)
return;
if (string.IsNullOrWhiteSpace(previewId)) if (string.IsNullOrWhiteSpace(previewId))
return; return;
@@ -371,9 +374,27 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
if (!_paradaByCode.TryGetValue(codigoParada, out var parada)) if (!_paradaByCode.TryGetValue(codigoParada, out var parada))
return; return;
if (_inspectorActivo)
{
await MostrarInspectorAsync(parada.Latitud, parada.Longitud);
return;
}
await MostrarPopupParadaAsync(parada); await MostrarPopupParadaAsync(parada);
} }
/// <summary>
/// Abre exclusivamente el inspector Z desde un elemento interactivo superpuesto al mapa.
/// </summary>
[JSInvokable]
public async Task OnInspectorOverlayClick(double latitud, double longitud)
{
if (!double.IsFinite(latitud) || !double.IsFinite(longitud))
return;
await MostrarInspectorAsync(latitud, longitud);
}
/// <summary> /// <summary>
/// Gestiona inspector activo. /// Gestiona inspector activo.
/// </summary> /// </summary>
@@ -381,6 +402,16 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
public void SetInspectorActivo(bool activo) public void SetInspectorActivo(bool activo)
{ {
_inspectorActivo = activo; _inspectorActivo = activo;
if (activo)
{
_ctsDobleClick?.Cancel();
_ctsDobleClick?.Dispose();
_ctsDobleClick = null;
_primerClickPendiente = null;
_paradaPrimerClickPendiente = null;
}
StateHasChanged(); StateHasChanged();
} }

View File

@@ -1,8 +1,8 @@
using System.Globalization; using System.Globalization;
using System.IO.Compression;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using RutasDBUS.Modelos.Historial; using RutasDBUS.Modelos.Historial;
@@ -12,8 +12,11 @@ public partial class PlanificadorRutas
{ {
private const string ClaveHistorialLocal = "rutasdbus.planificador.routeHistory.v1"; private const string ClaveHistorialLocal = "rutasdbus.planificador.routeHistory.v1";
private const string ClaveRespaldoHistorialLocal = "rutasdbus.planificador.routeHistory.recovery.v1"; private const string ClaveRespaldoHistorialLocal = "rutasdbus.planificador.routeHistory.recovery.v1";
private const string ClavePreferenciaParametrosActualesHistorial = private const string ClavePreferenciaParametrosActualesHistorialLegacy =
"rutasdbus.planificador.history.useCurrentConfig.v1"; "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 MaxItemsHistorialLocal = 25;
private const int MaxBytesHistorialLocal = 24 * 1024; private const int MaxBytesHistorialLocal = 24 * 1024;
private const int IntervaloRefrescoHistorialCompartidoSegundos = 3; private const int IntervaloRefrescoHistorialCompartidoSegundos = 3;
@@ -43,11 +46,11 @@ public partial class PlanificadorRutas
private bool _refrescandoHistorialCompartido = false; private bool _refrescandoHistorialCompartido = false;
private bool _omitirRegistroHistorialEnSiguienteCalculo = false; private bool _omitirRegistroHistorialEnSiguienteCalculo = false;
private bool _aplicandoRegistroHistorial = false; private bool _aplicandoRegistroHistorial = false;
private bool _usarParametrosActualesHistorial = true; private ModoConfiguracionHistorial _modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual;
private bool _cambiandoParametrosHistorial = false; private bool _cambiandoParametrosHistorial = false;
private string? _historialCompartidoPendienteEliminarId; private string? _historialCompartidoPendienteEliminarId;
private ConfiguracionLocalPlanificador? _configuracionTemporalPreviaHistorial; private ConfiguracionHistorialRuta? _configuracionTemporalPreviaHistorial;
private ConfiguracionLocalPlanificador? _configuracionParametrosActualesHistorial; private ConfiguracionHistorialRuta? _configuracionParametrosActualesHistorial;
private Task<bool>? _tareaCargaHistorialLocal; private Task<bool>? _tareaCargaHistorialLocal;
private bool _historialLocalCargado = false; private bool _historialLocalCargado = false;
private bool _historialLocalCargando = false; private bool _historialLocalCargando = false;
@@ -63,6 +66,13 @@ public partial class PlanificadorRutas
Compartido Compartido
} }
private enum ModoConfiguracionHistorial
{
Actual,
Guardada,
Predeterminada
}
private sealed record PasoDerivacionHistorial( private sealed record PasoDerivacionHistorial(
int Numero, int Numero,
DateTime FechaUtc, DateTime FechaUtc,
@@ -243,16 +253,18 @@ public partial class PlanificadorRutas
return false; return false;
} }
var json = resultadoLectura.Value; var valorHistorial = resultadoLectura.Value;
if (!resultadoLectura.Found || string.IsNullOrWhiteSpace(json)) if (!resultadoLectura.Found || string.IsNullOrWhiteSpace(valorHistorial))
{ {
FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>()); FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
LimpiarErrorHistorialLocal(); LimpiarErrorHistorialLocal();
return true; return true;
} }
var json = valorHistorial;
try try
{ {
json = DecodificarHistorialLocal(valorHistorial);
var items = DeserializarHistorialLocal( var items = DeserializarHistorialLocal(
json, json,
out var registrosOmitidos, out var registrosOmitidos,
@@ -262,7 +274,7 @@ public partial class PlanificadorRutas
if (registrosOmitidos > 0) if (registrosOmitidos > 0)
{ {
var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync( var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync(
json, valorHistorial,
resultadoLectura.RespaldoGuardado); resultadoLectura.RespaldoGuardado);
var detalleFalloRecuperacion = recuperacionGuardada var detalleFalloRecuperacion = recuperacionGuardada
? null ? null
@@ -303,13 +315,15 @@ public partial class PlanificadorRutas
} }
catch (Exception ex) when ( catch (Exception ex) when (
ex is JsonException or ex is JsonException or
InvalidDataException or
FormatException or
NotSupportedException or NotSupportedException or
InvalidOperationException or InvalidOperationException or
ArgumentException) ArgumentException)
{ {
FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>()); FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync( var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync(
json, valorHistorial,
resultadoLectura.RespaldoGuardado); resultadoLectura.RespaldoGuardado);
var detalleFalloRecuperacion = recuperacionGuardada var detalleFalloRecuperacion = recuperacionGuardada
? null ? null
@@ -413,6 +427,11 @@ public partial class PlanificadorRutas
? $"{item.DestinoLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.DestinoLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}" ? $"{item.DestinoLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.DestinoLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}"
: item.DestinoTexto.Trim(); : item.DestinoTexto.Trim();
item.LineasPermitidas = NormalizarLineasHistorial(item.LineasPermitidas).ToList(); item.LineasPermitidas = NormalizarLineasHistorial(item.LineasPermitidas).ToList();
if (item.Configuracion is not null)
{
item.Configuracion.LineasPermitidas =
NormalizarLineasHistorial(item.Configuracion.LineasPermitidas).ToList();
}
if (item.FechaCreacionUtc == default) if (item.FechaCreacionUtc == default)
item.FechaCreacionUtc = item.FechaActualizacionUtc == default ? DateTime.UtcNow : item.FechaActualizacionUtc; item.FechaCreacionUtc = item.FechaActualizacionUtc == default ? DateTime.UtcNow : item.FechaActualizacionUtc;
@@ -444,19 +463,45 @@ public partial class PlanificadorRutas
IEnumerable<RegistroHistorialRuta> origen) IEnumerable<RegistroHistorialRuta> origen)
{ {
var items = origen.Take(MaxItemsHistorialLocal).ToList(); var items = origen.Take(MaxItemsHistorialLocal).ToList();
var json = JsonSerializer.Serialize(items); var json = SerializarHistorialLocal(items);
var bytes = Encoding.UTF8.GetByteCount(json); var bytes = Encoding.UTF8.GetByteCount(json);
while (items.Count > 0 && bytes > MaxBytesHistorialLocal) while (items.Count > 0 && bytes > MaxBytesHistorialLocal)
{ {
items.RemoveAt(items.Count - 1); items.RemoveAt(items.Count - 1);
json = JsonSerializer.Serialize(items); json = SerializarHistorialLocal(items);
bytes = Encoding.UTF8.GetByteCount(json); bytes = Encoding.UTF8.GetByteCount(json);
} }
return (items, json, bytes); return (items, json, bytes);
} }
private static string SerializarHistorialLocal(IEnumerable<RegistroHistorialRuta> 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<bool> GuardarHistorialLocalRecuperadoAsync( private async Task<bool> GuardarHistorialLocalRecuperadoAsync(
string jsonOriginal, string jsonOriginal,
bool respaldoOriginalYaGuardado) bool respaldoOriginalYaGuardado)
@@ -485,7 +530,7 @@ public partial class PlanificadorRutas
return; return;
var itemsOriginales = _historialLocal.ToList(); var itemsOriginales = _historialLocal.ToList();
var jsonOriginal = JsonSerializer.Serialize(itemsOriginales); var jsonOriginal = SerializarHistorialLocal(itemsOriginales);
var preparado = PrepararHistorialLocalParaGuardar(itemsOriginales); var preparado = PrepararHistorialLocalParaGuardar(itemsOriginales);
var fueRecortado = preparado.Items.Count != itemsOriginales.Count; var fueRecortado = preparado.Items.Count != itemsOriginales.Count;
@@ -771,6 +816,7 @@ public partial class PlanificadorRutas
existente.FactorEquilibradoAndarBus = actual.FactorEquilibradoAndarBus; existente.FactorEquilibradoAndarBus = actual.FactorEquilibradoAndarBus;
existente.ActivarFiltroLineas = actual.ActivarFiltroLineas; existente.ActivarFiltroLineas = actual.ActivarFiltroLineas;
existente.LineasPermitidas = actual.LineasPermitidas.ToList(); existente.LineasPermitidas = actual.LineasPermitidas.ToList();
existente.Configuracion = actual.Configuracion?.Clonar();
existente.FechaActualizacionUtc = DateTime.UtcNow; existente.FechaActualizacionUtc = DateTime.UtcNow;
_historialLocal.Remove(existente); _historialLocal.Remove(existente);
@@ -854,18 +900,13 @@ public partial class PlanificadorRutas
return null; return null;
} }
private async Task CambiarUsoParametrosActualesHistorialAsync(ChangeEventArgs e) private async Task CambiarModoConfiguracionHistorialAsync(ModoConfiguracionHistorial modo)
{ {
if (_cambiandoParametrosHistorial) if (_cambiandoParametrosHistorial)
return; return;
var usarParametrosActuales = e.Value switch if (!Enum.IsDefined(typeof(ModoConfiguracionHistorial), modo) ||
{ modo == _modoConfiguracionHistorial)
bool valor => valor,
string texto when bool.TryParse(texto, out var valor) => valor,
_ => !_usarParametrosActualesHistorial
};
if (usarParametrosActuales == _usarParametrosActualesHistorial)
return; return;
_cambiandoParametrosHistorial = true; _cambiandoParametrosHistorial = true;
@@ -873,23 +914,34 @@ public partial class PlanificadorRutas
{ {
var itemSeleccionado = ObtenerRegistroHistorialSeleccionado(); var itemSeleccionado = ObtenerRegistroHistorialSeleccionado();
if (!usarParametrosActuales && _usarParametrosActualesHistorial) if (_modoConfiguracionHistorial == ModoConfiguracionHistorial.Actual &&
modo != ModoConfiguracionHistorial.Actual)
{
CapturarConfiguracionTemporalAntesDeHistorial();
_configuracionParametrosActualesHistorial = CrearConfiguracionLocal(); _configuracionParametrosActualesHistorial = CrearConfiguracionLocal();
}
_usarParametrosActualesHistorial = usarParametrosActuales; _modoConfiguracionHistorial = modo;
await PersistirPreferenciaParametrosActualesHistorialAsync(); await PersistirModoConfiguracionHistorialAsync();
if (usarParametrosActuales && _configuracionParametrosActualesHistorial is not null) if (modo == ModoConfiguracionHistorial.Actual &&
_configuracionParametrosActualesHistorial is not null)
{
AplicarConfiguracionLocal(_configuracionParametrosActualesHistorial); AplicarConfiguracionLocal(_configuracionParametrosActualesHistorial);
}
if (itemSeleccionado is null) if (itemSeleccionado is null)
return; return;
await AplicarRegistroHistorialAsync(itemSeleccionado); await AplicarRegistroHistorialAsync(itemSeleccionado);
MostrarMensajeHistorial( MostrarMensajeHistorial(modo switch
usarParametrosActuales {
? "Ruta recalculada con los parámetros actuales." ModoConfiguracionHistorial.Actual =>
: "Ruta recalculada con los parámetros guardados."); "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) catch (Exception ex)
{ {
@@ -906,29 +958,44 @@ public partial class PlanificadorRutas
} }
} }
private async Task CargarPreferenciaParametrosActualesHistorialAsync() private async Task CargarModoConfiguracionHistorialAsync()
{ {
try try
{ {
var valor = await JS.InvokeAsync<string?>( var valor = await JS.InvokeAsync<string?>(
"rt.loadLocalValue", "rt.loadLocalValue",
ClavePreferenciaParametrosActualesHistorial); ClaveModoConfiguracionHistorial);
if (bool.TryParse(valor, out var usarConfiguracionActual)) if (Enum.TryParse<ModoConfiguracionHistorial>(valor, ignoreCase: true, out var modo) &&
_usarParametrosActualesHistorial = usarConfiguracionActual; Enum.IsDefined(typeof(ModoConfiguracionHistorial), modo))
{
_modoConfiguracionHistorial = modo;
return;
}
var valorLegacy = await JS.InvokeAsync<string?>(
"rt.loadLocalValue",
ClavePreferenciaParametrosActualesHistorialLegacy);
if (bool.TryParse(valorLegacy, out var usarConfiguracionActual))
{
_modoConfiguracionHistorial = usarConfiguracionActual
? ModoConfiguracionHistorial.Actual
: ModoConfiguracionHistorial.Guardada;
await PersistirModoConfiguracionHistorialAsync();
}
} }
catch (JSException) catch (JSException)
{ {
} }
} }
private async Task PersistirPreferenciaParametrosActualesHistorialAsync() private async Task PersistirModoConfiguracionHistorialAsync()
{ {
try try
{ {
await JS.InvokeVoidAsync( await JS.InvokeVoidAsync(
"rt.saveLocalValue", "rt.saveLocalValue",
ClavePreferenciaParametrosActualesHistorial, ClaveModoConfiguracionHistorial,
_usarParametrosActualesHistorial.ToString(CultureInfo.InvariantCulture)); _modoConfiguracionHistorial.ToString());
} }
catch (JSException) catch (JSException)
{ {
@@ -1268,10 +1335,18 @@ public partial class PlanificadorRutas
FactorEquilibradoAndarBus = item.FactorEquilibradoAndarBus, FactorEquilibradoAndarBus = item.FactorEquilibradoAndarBus,
ActivarFiltroLineas = item.ActivarFiltroLineas, ActivarFiltroLineas = item.ActivarFiltroLineas,
LineasPermitidas = item.LineasPermitidas.ToList(), LineasPermitidas = item.LineasPermitidas.ToList(),
Configuracion = item.Configuracion?.Clonar(),
FechaCreacionUtc = item.FechaCreacionUtc, FechaCreacionUtc = item.FechaCreacionUtc,
FechaActualizacionUtc = item.FechaActualizacionUtc FechaActualizacionUtc = item.FechaActualizacionUtc
}; };
private RegistroHistorialRuta PrepararRegistroParaCompartir(RegistroHistorialRuta item)
{
var compartible = ClonarEscenarioHistorial(item);
compartible.Configuracion ??= CrearConfiguracionGuardadaHistorial(item);
return compartible;
}
private async Task CompartirEscenarioActualAsync() private async Task CompartirEscenarioActualAsync()
{ {
var actual = ConstruirRegistroHistorialActual() var actual = ConstruirRegistroHistorialActual()
@@ -1285,7 +1360,7 @@ public partial class PlanificadorRutas
try try
{ {
await HistorialRutasCompartidoService.GuardarAsync(actual); await HistorialRutasCompartidoService.GuardarAsync(PrepararRegistroParaCompartir(actual));
await CargarHistorialCompartidoAsync(); await CargarHistorialCompartidoAsync();
MostrarMensajeHistorial("Combinacion enviada al historial compartido."); MostrarMensajeHistorial("Combinacion enviada al historial compartido.");
} }
@@ -1306,7 +1381,7 @@ public partial class PlanificadorRutas
try try
{ {
await HistorialRutasCompartidoService.GuardarAsync(item); await HistorialRutasCompartidoService.GuardarAsync(PrepararRegistroParaCompartir(item));
await CargarHistorialCompartidoAsync(); await CargarHistorialCompartidoAsync();
MostrarMensajeHistorial("Combinacion enviada al historial compartido."); MostrarMensajeHistorial("Combinacion enviada al historial compartido.");
} }
@@ -1439,6 +1514,7 @@ public partial class PlanificadorRutas
try try
{ {
CapturarConfiguracionTemporalAntesDeHistorial(); CapturarConfiguracionTemporalAntesDeHistorial();
var mostrarParadasAntes = _mostrarParadas;
_omitirRegistroHistorialEnSiguienteCalculo = true; _omitirRegistroHistorialEnSiguienteCalculo = true;
_textoBusquedaOrigen = item.OrigenTexto; _textoBusquedaOrigen = item.OrigenTexto;
@@ -1446,40 +1522,33 @@ public partial class PlanificadorRutas
_coordenadasOrigen = new[] { item.OrigenLatitud, item.OrigenLongitud }; _coordenadasOrigen = new[] { item.OrigenLatitud, item.OrigenLongitud };
_coordenadasDestino = new[] { item.DestinoLatitud, item.DestinoLongitud }; _coordenadasDestino = new[] { item.DestinoLatitud, item.DestinoLongitud };
if (!_usarParametrosActualesHistorial) var configuracionHistorial = _modoConfiguracionHistorial switch
{ {
_usarHorariosTeoricos = item.UsarHorariosTeoricos; ModoConfiguracionHistorial.Guardada => CrearConfiguracionGuardadaHistorial(item),
_usarHoraSimulada = item.UsarHoraSimulada; ModoConfiguracionHistorial.Predeterminada => CrearConfiguracionPredeterminadaHistorial(),
_ => null
};
if (DateOnly.TryParse(item.FechaSimulada, out var fecha)) if (configuracionHistorial is not null)
_fechaSimulada = fecha;
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)
{ {
_factorEquilibradoAndarBus = factorEquilibrado; AplicarConfiguracionLocal(configuracionHistorial);
SincronizarTextosPenalizacionPie();
if (_configuracionTemporalPreviaHistorial is not null) // El factor cargado desde un historial ya se conservaba al cerrar el panel.
_configuracionTemporalPreviaHistorial.FactorEquilibradoAndarBus = _factorEquilibradoAndarBus; if (_modoConfiguracionHistorial == ModoConfiguracionHistorial.Guardada &&
_configuracionTemporalPreviaHistorial is not null)
{
_configuracionTemporalPreviaHistorial.FactorEquilibradoAndarBus =
_factorEquilibradoAndarBus;
_configuracionTemporalPreviaHistorial.VersionFactorEquilibrado =
VersionPersistenciaFactorEquilibrado;
} }
_activarRangoLineas = item.ActivarFiltroLineas;
_lineasPermitidas.Clear();
foreach (var linea in NormalizarLineasHistorial(item.LineasPermitidas))
_lineasPermitidas.Add(linea);
} }
_busFilteredCache.Clear(); _busFilteredCache.Clear();
SincronizarSegundosPorParadaConHorarios(); SincronizarSegundosPorParadaConHorarios();
if (_usarHorariosTeoricos)
await EnsureHorariosLoadedAsync();
StopClock(); StopClock();
if (_usarHoraSimulada) if (_usarHoraSimulada)
@@ -1495,6 +1564,14 @@ public partial class PlanificadorRutas
{ {
try { await _mapa.Geometric.DisplayPolylinesFromArray.deleteConnectors(); } catch { } try { await _mapa.Geometric.DisplayPolylinesFromArray.deleteConnectors(); } catch { }
await ClearRoutesJsAsync(); await ClearRoutesJsAsync();
if (mostrarParadasAntes != _mostrarParadas)
{
if (_mostrarParadas)
await DibujarParadasAsync();
else
await OcultarParadasAsync();
}
} }
await SincronizarMarcadoresOrigenDestinoJsAsync(); 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() private void CapturarConfiguracionTemporalAntesDeHistorial()
{ {
if (_configuracionTemporalPreviaHistorial is not null) if (_configuracionTemporalPreviaHistorial is not null)
@@ -1524,8 +1625,19 @@ public partial class PlanificadorRutas
if (configuracionPrevia is null) if (configuracionPrevia is null)
return; return;
var mostrarParadasAntes = _mostrarParadas;
AplicarConfiguracionLocal(configuracionPrevia); AplicarConfiguracionLocal(configuracionPrevia);
if (_mapa is not null && mostrarParadasAntes != _mostrarParadas)
{
if (_mostrarParadas)
await DibujarParadasAsync();
else
await OcultarParadasAsync();
await SincronizarMarcadoresOrigenDestinoJsAsync();
}
StopClock(); StopClock();
if (_usarHoraSimulada) if (_usarHoraSimulada)
await RefrescarHoraUIAsync(); await RefrescarHoraUIAsync();
@@ -1541,6 +1653,11 @@ public partial class PlanificadorRutas
return null; return null;
var ahora = _ultimaHoraReferenciaRuta ?? AhoraLocal(); 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 return new RegistroHistorialRuta
{ {
@@ -1563,6 +1680,7 @@ public partial class PlanificadorRutas
: FactorEquilibradoAndarBusPorDefecto, : FactorEquilibradoAndarBusPorDefecto,
ActivarFiltroLineas = _activarRangoLineas, ActivarFiltroLineas = _activarRangoLineas,
LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(), LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(),
Configuracion = configuracion,
FechaCreacionUtc = DateTime.UtcNow, FechaCreacionUtc = DateTime.UtcNow,
FechaActualizacionUtc = DateTime.UtcNow FechaActualizacionUtc = DateTime.UtcNow
}; };

View File

@@ -12,7 +12,10 @@ public partial class PlanificadorRutas
double? ElevacionIdee, double? ElevacionIdee,
double? ElevacionLocal5, double? ElevacionLocal5,
double? ElevacionLocal2, double? ElevacionLocal2,
double? ElevacionLocal1); double? ElevacionLocal1,
string Direccion,
double? DistanciaAndandoMetros,
bool CalculandoDatosAdicionales);
private double? ObtenerElevacionPuntoInspectorAnterior(PuntoInspectorAnterior punto) private double? ObtenerElevacionPuntoInspectorAnterior(PuntoInspectorAnterior punto)
=> UsarElevacionLocal1ParaCalculo => UsarElevacionLocal1ParaCalculo
@@ -23,6 +26,15 @@ public partial class PlanificadorRutas
? punto.ElevacionLocal5 ? punto.ElevacionLocal5
: punto.ElevacionIdee; : punto.ElevacionIdee;
private double? ObtenerElevacionPuntoInspectorActual()
=> UsarElevacionLocal1ParaCalculo
? _inspectorElevLocal1
: UsarElevacionLocal2ParaCalculo
? _inspectorElevLocal2
: UsarElevacionLocal5ParaCalculo
? _inspectorElevLocal5
: _inspectorElevIdee;
private static string FormatearDistanciaPuntoInspector(double distanciaMetros) private static string FormatearDistanciaPuntoInspector(double distanciaMetros)
=> distanciaMetros < 1000 => distanciaMetros < 1000
? distanciaMetros.ToString("0.0", CultureInfo.InvariantCulture) + " m" ? distanciaMetros.ToString("0.0", CultureInfo.InvariantCulture) + " m"
@@ -32,4 +44,79 @@ public partial class PlanificadorRutas
=> altura is { } valor => altura is { } valor
? valor.ToString("0.00", CultureInfo.InvariantCulture) + " m" ? 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<string> 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);
}
}
} }

View File

@@ -531,6 +531,9 @@ public partial class PlanificadorRutas : ComponentBase
/// </summary> /// </summary>
private async Task MostrarPopupParadaAsync(Parada paradaDetectada) private async Task MostrarPopupParadaAsync(Parada paradaDetectada)
{ {
if (_inspectorActivo)
return;
var myToken = Interlocked.Increment(ref _paradaPopupToken); var myToken = Interlocked.Increment(ref _paradaPopupToken);
_paradaClicada = null; _paradaClicada = null;
@@ -1095,23 +1098,31 @@ public partial class PlanificadorRutas : ComponentBase
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
private string ConstruirTextoRecorridoVariante(InfoVariante? variante) private string ConstruirTextoRecorridoVariante(
InfoVariante? variante,
bool incluirCodigosParada = false)
{ {
if (variante?.CodigosParadas is null || variante.CodigosParadas.Count == 0) if (variante?.CodigosParadas is null || variante.CodigosParadas.Count == 0)
return ""; return "";
string NombreParada(string codigo) string TextoParada(string codigo)
{ {
var codigoFormateado = CodigoParadaFormateado(codigo);
if (_paradaByCode.TryGetValue(codigo, out var parada) && !string.IsNullOrWhiteSpace(parada.Nombre)) if (_paradaByCode.TryGetValue(codigo, out var parada) && !string.IsNullOrWhiteSpace(parada.Nombre))
return parada.Nombre.Trim(); {
var nombre = parada.Nombre.Trim();
return CodigoParadaFormateado(codigo); return incluirCodigosParada && !string.IsNullOrWhiteSpace(codigoFormateado)
? $"{nombre} ({codigoFormateado})"
: nombre;
} }
var origen = NombreParada(variante.CodigosParadas[0]); return codigoFormateado;
var destino = NombreParada(variante.CodigosParadas[^1]); }
var origen = TextoParada(variante.CodigosParadas[0]);
var destino = TextoParada(variante.CodigosParadas[^1]);
if (string.IsNullOrWhiteSpace(origen)) if (string.IsNullOrWhiteSpace(origen))
return destino; return destino;
@@ -1389,7 +1400,7 @@ public partial class PlanificadorRutas : ComponentBase
private bool _elevPopupMinimizado = false; private bool _elevPopupMinimizado = false;
private const string _versionApp = "(v-20260902d)"; private const string _versionApp = "(v-20260903b)";
private double? _paradaElevIdee; private double? _paradaElevIdee;
@@ -1518,9 +1529,9 @@ public partial class PlanificadorRutas : ComponentBase
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
private string GetTextoLineaItinerarioClicado(InfoVariante? vReal) private string GetNombreLineaItinerarioClicado(InfoVariante? vReal)
{ {
if (_itinerarioClicado is null) if (_itinerarioClicado is null)
return ""; return "";
@@ -1528,19 +1539,18 @@ public partial class PlanificadorRutas : ComponentBase
if (vReal is null) if (vReal is null)
return _itinerarioClicado.Text; return _itinerarioClicado.Text;
var linea = vReal.Linea?.Codigo?.ToString() ?? "?"; var linea = vReal.Linea?.Codigo?.ToString()?.Trim() ?? "?";
var itin = vReal.CodigoItinerario; var sentido = NormalizarSentidoCodigo(vReal.Sentido?.Codigo);
var sentido = NormalizarSentidoCodigo(vReal.Sentido?.Codigo); // I / V var identificador = $"{linea}.{vReal.CodigoItinerario}-{sentido}";
var nombre = vReal.Linea?.Nombre?.Trim() ?? ""; var nombre = vReal.Linea?.Nombre?.Trim() ?? "";
var codigo = $"{linea}.{itin}-{sentido}";
return string.IsNullOrWhiteSpace(nombre) return string.IsNullOrWhiteSpace(nombre)
? codigo ? identificador
: $"{codigo} ({nombre})"; : $"{identificador} {nombre}";
} }
/// <summary> /// <summary>
/// Obtiene la parada final del itinerario clicado. /// Obtiene el destino indicado por las expediciones del itinerario clicado.
/// </summary> /// </summary>
private string GetDestinoItinerarioClicado(InfoVariante? vReal) private string GetDestinoItinerarioClicado(InfoVariante? vReal)
{ {

View File

@@ -1,6 +1,7 @@
using System.Globalization; using System.Globalization;
using System.Text.Json; using System.Text.Json;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using RutasDBUS.Modelos.Historial;
using RutasDBUS.Modelos.Planificacion; using RutasDBUS.Modelos.Planificacion;
namespace RutasDBUS.Components.Pages; namespace RutasDBUS.Components.Pages;
@@ -17,66 +18,6 @@ public partial class PlanificadorRutas
private bool _configuracionLocalCargada = false; private bool _configuracionLocalCargada = false;
private bool _mostrarConfirmacionRestablecer = false; private bool _mostrarConfirmacionRestablecer = false;
/// <summary>
/// Instantánea persistida de la configuración del planificador.
/// </summary>
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<int>? 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; }
}
/// <summary> /// <summary>
/// Carga la configuración persistida del navegador y la aplica al estado actual. /// Carga la configuración persistida del navegador y la aplica al estado actual.
/// </summary> /// </summary>
@@ -88,7 +29,7 @@ public partial class PlanificadorRutas
if (string.IsNullOrWhiteSpace(json)) if (string.IsNullOrWhiteSpace(json))
return; return;
var config = JsonSerializer.Deserialize<ConfiguracionLocalPlanificador>(json); var config = JsonSerializer.Deserialize<ConfiguracionHistorialRuta>(json);
if (config is null) if (config is null)
return; return;
@@ -146,9 +87,10 @@ public partial class PlanificadorRutas
/// <summary> /// <summary>
/// Construye la instantánea persistible a partir del estado actual. /// Construye la instantánea persistible a partir del estado actual.
/// </summary> /// </summary>
private ConfiguracionLocalPlanificador CrearConfiguracionLocal() private ConfiguracionHistorialRuta CrearConfiguracionLocal()
=> new() => new()
{ {
Version = 1,
MostrarParadas = _mostrarParadas, MostrarParadas = _mostrarParadas,
UsarHorariosTeoricos = _usarHorariosTeoricos, UsarHorariosTeoricos = _usarHorariosTeoricos,
SegundosPorParadaBusManual = Math.Max(0, _segundosPorParadaBusManual), SegundosPorParadaBusManual = Math.Max(0, _segundosPorParadaBusManual),
@@ -193,6 +135,8 @@ public partial class PlanificadorRutas
VelocidadBusKmH = _velocidadBusKmH, VelocidadBusKmH = _velocidadBusKmH,
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo, ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
RadioCandidatasMetros = _radioCandidatasMetros, RadioCandidatasMetros = _radioCandidatasMetros,
MaxCandidatasPorLado = _maxCandidatasPorLado,
MinutosTransbordo = _minutosTransbordo,
MargenTransbordoSegundos = _margenTransbordoSegundos, MargenTransbordoSegundos = _margenTransbordoSegundos,
WalkProvider = _walkProvider.ToString(), WalkProvider = _walkProvider.ToString(),
NivelBrilloMapa = _nivelBrilloMapa, NivelBrilloMapa = _nivelBrilloMapa,
@@ -202,10 +146,72 @@ public partial class PlanificadorRutas
PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta 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<int>(),
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
};
}
/// <summary> /// <summary>
/// Aplica al componente una configuración previamente persistida. /// Aplica al componente una configuración previamente persistida.
/// </summary> /// </summary>
private void AplicarConfiguracionLocal(ConfiguracionLocalPlanificador config) private void AplicarConfiguracionLocal(ConfiguracionHistorialRuta config)
{ {
_mostrarParadas = config.MostrarParadas ?? true; _mostrarParadas = config.MostrarParadas ?? true;
_usarHorariosTeoricos = config.UsarHorariosTeoricos; _usarHorariosTeoricos = config.UsarHorariosTeoricos;
@@ -353,6 +359,8 @@ public partial class PlanificadorRutas
120.0); 120.0);
_elevMaxPuntosPorTramo = Math.Clamp(config.ElevMaxPuntosPorTramo, 2, 999); _elevMaxPuntosPorTramo = Math.Clamp(config.ElevMaxPuntosPorTramo, 2, 999);
_radioCandidatasMetros = Math.Clamp(config.RadioCandidatasMetros, 200, 3500); _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); _margenTransbordoSegundos = Math.Clamp(config.MargenTransbordoSegundos, 0, 1800);
_nivelBrilloMapa = Math.Clamp(config.NivelBrilloMapa, 0, 4); _nivelBrilloMapa = Math.Clamp(config.NivelBrilloMapa, 0, 4);
_usarColorRealLineaEnItinerarios = config.UsarColorRealLineaEnItinerarios; _usarColorRealLineaEnItinerarios = config.UsarColorRealLineaEnItinerarios;
@@ -475,7 +483,7 @@ public partial class PlanificadorRutas
_nivelBrilloMapa = 0; _nivelBrilloMapa = 0;
_usarColorRealLineaEnItinerarios = false; _usarColorRealLineaEnItinerarios = false;
_usarOsrmParaRanking = false; _usarOsrmParaRanking = false;
_usarParametrosActualesHistorial = true; _modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual;
if (_usarHoraSimulada) if (_usarHoraSimulada)
StopClock(); StopClock();
@@ -485,7 +493,7 @@ public partial class PlanificadorRutas
SincronizarSegundosPorParadaConHorarios(); SincronizarSegundosPorParadaConHorarios();
await RefrescarHoraUIAsync(); await RefrescarHoraUIAsync();
await PersistirConfiguracionAsync(); await PersistirConfiguracionAsync();
await PersistirPreferenciaParametrosActualesHistorialAsync(); await PersistirModoConfiguracionHistorialAsync();
StateHasChanged(); StateHasChanged();
} }
@@ -537,7 +545,7 @@ public partial class PlanificadorRutas
_nivelBrilloMapa = 0; _nivelBrilloMapa = 0;
_usarColorRealLineaEnItinerarios = false; _usarColorRealLineaEnItinerarios = false;
_usarOsrmParaRanking = false; _usarOsrmParaRanking = false;
_usarParametrosActualesHistorial = true; _modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual;
if (_usarHoraSimulada) if (_usarHoraSimulada)
StopClock(); StopClock();
@@ -547,7 +555,7 @@ public partial class PlanificadorRutas
SincronizarSegundosPorParadaConHorarios(); SincronizarSegundosPorParadaConHorarios();
await RefrescarHoraUIAsync(); await RefrescarHoraUIAsync();
await PersistirConfiguracionAsync(); await PersistirConfiguracionAsync();
await PersistirPreferenciaParametrosActualesHistorialAsync(); await PersistirModoConfiguracionHistorialAsync();
StateHasChanged(); StateHasChanged();
} }
} }

View File

@@ -1461,7 +1461,7 @@ public partial class PlanificadorRutas : ComponentBase
} }
if (_coordenadasOrigen is not null && _coordenadasDestino is not null) if (_coordenadasOrigen is not null && _coordenadasDestino is not null)
await CalcularRutaCompletaAsync(); await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true);
StateHasChanged(); StateHasChanged();
} }
@@ -1512,7 +1512,7 @@ public partial class PlanificadorRutas : ComponentBase
await CerrarTodasLasPreviewsAsync(); await CerrarTodasLasPreviewsAsync();
await CerrarPopupsAsync(); await CerrarPopupsAsync();
await CalcularRutaCompletaAsync(); await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true);
} }
/// <summary> /// <summary>
/// Muestra inspector. /// Muestra inspector.
@@ -1526,7 +1526,10 @@ public partial class PlanificadorRutas : ComponentBase
_inspectorElevIdee, _inspectorElevIdee,
_inspectorElevLocal5, _inspectorElevLocal5,
_inspectorElevLocal2, _inspectorElevLocal2,
_inspectorElevLocal1) _inspectorElevLocal1,
ObtenerDireccionPuntoInspectorActual(),
DistanciaAndandoMetros: null,
CalculandoDatosAdicionales: true)
: null; : null;
var versionInspector = ++_versionInspector; var versionInspector = ++_versionInspector;
@@ -1554,6 +1557,9 @@ public partial class PlanificadorRutas : ComponentBase
await ActualizarMarcadoresInspectorJsAsync(); await ActualizarMarcadoresInspectorJsAsync();
StateHasChanged(); StateHasChanged();
if (_inspectorPuntoAnterior is { } puntoAnterior)
_ = CompletarDatosPuntoInspectorAnteriorAsync(puntoAnterior, versionInspector);
var tareaLugares = GeocodificadorLocal.BuscarCercanosAsync( var tareaLugares = GeocodificadorLocal.BuscarCercanosAsync(
lat, lat,
lon, lon,
@@ -1788,7 +1794,7 @@ public partial class PlanificadorRutas : ComponentBase
await SincronizarMarcadoresOrigenDestinoJsAsync(); await SincronizarMarcadoresOrigenDestinoJsAsync();
await CalcularRutaCompletaAsync(); await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true);
return; return;
} }
@@ -1801,7 +1807,7 @@ public partial class PlanificadorRutas : ComponentBase
await SincronizarMarcadoresOrigenDestinoJsAsync(); await SincronizarMarcadoresOrigenDestinoJsAsync();
} }
await CalcularRutaCompletaAsync(); await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true);
} }
/// <summary> /// <summary>
@@ -1841,7 +1847,7 @@ public partial class PlanificadorRutas : ComponentBase
if (_coordenadasOrigen is not null && _coordenadasDestino is not null) if (_coordenadasOrigen is not null && _coordenadasDestino is not null)
{ {
await CalcularRutaCompletaAsync(); await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true);
} }
else else
{ {
@@ -1862,11 +1868,16 @@ public partial class PlanificadorRutas : ComponentBase
/// <summary> /// <summary>
/// Calcula ruta completa. /// Calcula ruta completa.
/// </summary> /// </summary>
private async Task CalcularRutaCompletaAsync() private async Task CalcularRutaCompletaAsync(bool conservarSeleccionSoloAPie = false)
{ {
if (_mapa is null || _coordenadasOrigen is null || _coordenadasDestino is null) if (_mapa is null || _coordenadasOrigen is null || _coordenadasDestino is null)
return; return;
var restaurarSeleccionSoloAPie = conservarSeleccionSoloAPie &&
_indiceAlternativaSeleccionada >= 0 &&
_indiceAlternativaSeleccionada < _opcionesRuta.Count &&
_opcionesRuta[_indiceAlternativaSeleccionada].EsSoloAPie;
_mostrarElevacionesRuta = _mostrarElevacionesRutaConfiguracion; _mostrarElevacionesRuta = _mostrarElevacionesRutaConfiguracion;
var confirmarDerivacionHistorial = !_aplicandoRegistroHistorial && var confirmarDerivacionHistorial = !_aplicandoRegistroHistorial &&
@@ -1983,9 +1994,15 @@ public partial class PlanificadorRutas : ComponentBase
return; return;
} }
var idxPreferida = BuscarIndicePreferidoPorCriterio(_criterioPrioritarioRuta); var idxPreferida = restaurarSeleccionSoloAPie
? _opcionesRuta.FindIndex(opcion => opcion.EsSoloAPie)
: BuscarIndicePreferidoPorCriterio(_criterioPrioritarioRuta);
if (idxPreferida < 0 || idxPreferida >= _opcionesRuta.Count)
{
idxPreferida = BuscarIndicePreferidoPorCriterio(_criterioPrioritarioRuta);
if (idxPreferida < 0 || idxPreferida >= _opcionesRuta.Count) if (idxPreferida < 0 || idxPreferida >= _opcionesRuta.Count)
idxPreferida = 0; idxPreferida = 0;
}
_indiceAlternativaSeleccionada = idxPreferida; _indiceAlternativaSeleccionada = idxPreferida;
ResetEstadoRuta(limpiarOpciones: false, limpiarMensaje: false); ResetEstadoRuta(limpiarOpciones: false, limpiarMensaje: false);

View File

@@ -550,11 +550,10 @@ public partial class PlanificadorRutas : ComponentBase
private string ObtenerNombreDestinoItinerario(InfoVariante variante) private string ObtenerNombreDestinoItinerario(InfoVariante variante)
{ {
if (variante.CodigosParadas.Count == 0) var destino = variante.Variante?.Destino?.Trim();
return string.Empty; return UtilidadesTransitoDbus.DestinoDescriptivoEsValido(destino)
? destino!
var ultimaParada = ResolverParadaFisica(variante.CodigosParadas[^1]); : string.Empty;
return ultimaParada.Nombre?.Trim() ?? string.Empty;
} }
private double? ElevacionPreferidaMapa(MuestraElevacion muestra) private double? ElevacionPreferidaMapa(MuestraElevacion muestra)

View File

@@ -367,25 +367,20 @@
@if (_itinerarioClicado is not null) @if (_itinerarioClicado is not null)
{ {
var vReal = GetVarianteRealClicada(); var vReal = GetVarianteRealClicada();
var textoLineaItinerario = GetTextoLineaItinerarioClicado(vReal); var nombreLineaItinerario = GetNombreLineaItinerarioClicado(vReal);
var destinoItinerario = GetDestinoItinerarioClicado(vReal); var destinoItinerario = GetDestinoItinerarioClicado(vReal);
<div class="popup-parada popup-slot popup-itinerario draggable-popup" data-drag-key="popup-itinerario"> <div class="popup-parada popup-slot popup-itinerario draggable-popup" data-drag-key="popup-itinerario">
<div class="popup-parada-header popup-itinerario-header draggable-popup-handle"> <div class="popup-parada-header popup-itinerario-header draggable-popup-handle">
<span>@textoLineaItinerario</span> <span>@nombreLineaItinerario</span>
@if (!string.IsNullOrWhiteSpace(destinoItinerario))
{
<span class="popup-itinerario-header__destination">
<span aria-hidden="true">→</span> @destinoItinerario
</span>
}
</div> </div>
<div class="popup-parada-body popup-itinerario-body"> <div class="popup-parada-body popup-itinerario-body">
@if (vReal is not null) @if (vReal is not null)
{ {
<div style="margin-top:.25rem;"> <div style="margin-top:.25rem;">
<div><b>Destino:</b> @(string.IsNullOrWhiteSpace(destinoItinerario) ? "-" : destinoItinerario)</div>
<div><b>Paradas:</b> @(vReal.CodigosParadas?.Count ?? 0)</div> <div><b>Paradas:</b> @(vReal.CodigosParadas?.Count ?? 0)</div>
<div><b>Recorrido:</b> @ConstruirTextoRecorridoVariante(vReal)</div> <div><b>Recorrido:</b> @ConstruirTextoRecorridoVariante(vReal, incluirCodigosParada: true)</div>
<div><b>Longitud:</b> @FormatearDistanciaVariante(vReal)</div> <div><b>Longitud:</b> @FormatearDistanciaVariante(vReal)</div>
</div> </div>
} }
@@ -451,29 +446,6 @@
} }
</div> </div>
@if (_inspectorPuntoAnterior is { } puntoAnterior)
{
var distanciaPuntoAnterior = CalcularDistanciaMetros(
puntoAnterior.Latitud,
puntoAnterior.Longitud,
_inspectorLat,
_inspectorLon);
var elevacionPuntoAnterior = ObtenerElevacionPuntoInspectorAnterior(puntoAnterior);
<div class="inspector-previous-point">
<div class="inspector-previous-point__distance">
<b>Distancia al punto Z anterior:</b>
@FormatearDistanciaPuntoInspector(distanciaPuntoAnterior)
</div>
<div class="inspector-previous-point__details">
<span>Punto anterior</span>
<span>Lat: @puntoAnterior.Latitud.ToString("0.000000", CultureInfo.InvariantCulture)</span>
<span>Lon: @puntoAnterior.Longitud.ToString("0.000000", CultureInfo.InvariantCulture)</span>
<span>@EtiquetaFuenteElevacionCalculo: @FormatearAlturaPuntoInspector(elevacionPuntoAnterior)</span>
</div>
</div>
}
@if (_inspectorLugaresCercanos.Count > 0) @if (_inspectorLugaresCercanos.Count > 0)
{ {
<div class="inspector-address-list"> <div class="inspector-address-list">
@@ -487,6 +459,47 @@
} }
</div> </div>
} }
@if (_inspectorPuntoAnterior is { } puntoAnterior)
{
var distanciaPuntoAnterior = CalcularDistanciaMetros(
puntoAnterior.Latitud,
puntoAnterior.Longitud,
_inspectorLat,
_inspectorLon);
var elevacionPuntoAnterior = ObtenerElevacionPuntoInspectorAnterior(puntoAnterior);
var elevacionPuntoActual = ObtenerElevacionPuntoInspectorActual();
var direccionPuntoAnterior = string.IsNullOrWhiteSpace(puntoAnterior.Direccion)
? (puntoAnterior.CalculandoDatosAdicionales ? "Calculando..." : "-")
: puntoAnterior.Direccion;
<div class="inspector-previous-point">
<div class="inspector-previous-point__title">Datos del punto anterior</div>
<div class="inspector-previous-point__details">
<span class="inspector-previous-point__detail--full">Dirección: @direccionPuntoAnterior</span>
<span>Lat: @puntoAnterior.Latitud.ToString("0.000000", CultureInfo.InvariantCulture)</span>
<span>Lon: @puntoAnterior.Longitud.ToString("0.000000", CultureInfo.InvariantCulture)</span>
<span>@EtiquetaFuenteElevacionCalculo: @FormatearAlturaPuntoInspector(elevacionPuntoAnterior)</span>
<span class="inspector-previous-point__detail--full">
Diferencia de altura: @FormatearDiferenciaAlturaPuntoInspector(elevacionPuntoActual, elevacionPuntoAnterior)
</span>
<span class="inspector-previous-point__detail--full">
Distancia en línea recta: @FormatearDistanciaPuntoInspector(distanciaPuntoAnterior)
</span>
<span class="inspector-previous-point__detail--full">
Distancia andando:
@if (puntoAnterior.CalculandoDatosAdicionales)
{
<span>Calculando...</span>
}
else
{
<span>@(puntoAnterior.DistanciaAndandoMetros is { } distanciaAndando ? FormatearDistanciaPuntoInspector(distanciaAndando) : "-")</span>
}
</span>
</div>
</div>
}
</div> </div>
<button class="popup-parada-cerrar" @onclick="CerrarInspectorAsync">×</button> <button class="popup-parada-cerrar" @onclick="CerrarInspectorAsync">×</button>
@@ -1126,17 +1139,40 @@
</div> </div>
<div class="history-panel-body"> <div class="history-panel-body">
<label class="history-parameters-toggle @(_usarParametrosActualesHistorial ? "history-parameters-toggle--active" : "")" <div class="history-config-mode">
title="Recalcular las rutas del historial con la configuración actual"> <span class="history-config-mode__label">Configuración para recalcular</span>
<span class="history-parameters-toggle__label">Usar configuración actual</span> <div class="history-config-mode__options"
<input type="checkbox" role="radiogroup"
checked="@_usarParametrosActualesHistorial" aria-label="Configuración usada al cargar una ruta del historial">
<button type="button"
class="history-config-mode__option @(_modoConfiguracionHistorial == ModoConfiguracionHistorial.Actual ? "history-config-mode__option--active" : "")"
role="radio"
aria-checked="@(_modoConfiguracionHistorial == ModoConfiguracionHistorial.Actual)"
title="Usar la configuración actual"
disabled="@(_aplicandoRegistroHistorial || _cambiandoParametrosHistorial)" disabled="@(_aplicandoRegistroHistorial || _cambiandoParametrosHistorial)"
@onchange="CambiarUsoParametrosActualesHistorialAsync" /> @onclick="() => CambiarModoConfiguracionHistorialAsync(ModoConfiguracionHistorial.Actual)">
<span class="history-parameters-toggle__track" aria-hidden="true"> Actual
<span class="history-parameters-toggle__thumb"></span> </button>
</span> <button type="button"
</label> class="history-config-mode__option @(_modoConfiguracionHistorial == ModoConfiguracionHistorial.Guardada ? "history-config-mode__option--active" : "")"
role="radio"
aria-checked="@(_modoConfiguracionHistorial == ModoConfiguracionHistorial.Guardada)"
title="Usar la configuración guardada con la ruta"
disabled="@(_aplicandoRegistroHistorial || _cambiandoParametrosHistorial)"
@onclick="() => CambiarModoConfiguracionHistorialAsync(ModoConfiguracionHistorial.Guardada)">
Guardada
</button>
<button type="button"
class="history-config-mode__option @(_modoConfiguracionHistorial == ModoConfiguracionHistorial.Predeterminada ? "history-config-mode__option--active" : "")"
role="radio"
aria-checked="@(_modoConfiguracionHistorial == ModoConfiguracionHistorial.Predeterminada)"
title="Usar los valores predeterminados de fábrica"
disabled="@(_aplicandoRegistroHistorial || _cambiandoParametrosHistorial)"
@onclick="() => CambiarModoConfiguracionHistorialAsync(ModoConfiguracionHistorial.Predeterminada)">
Predeterminada
</button>
</div>
</div>
<div class="history-section"> <div class="history-section">
<label class="history-section-label">Personal:</label> <label class="history-section-label">Personal:</label>

View File

@@ -0,0 +1,70 @@
namespace RutasDBUS.Modelos.Historial;
/// <summary>
/// Instantánea completa de la configuración con la que se calculó una ruta.
/// </summary>
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<int>? 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;
}
}

View File

@@ -21,6 +21,7 @@ public sealed class RegistroHistorialRuta
public double? FactorEquilibradoAndarBus { get; set; } public double? FactorEquilibradoAndarBus { get; set; }
public bool ActivarFiltroLineas { get; set; } public bool ActivarFiltroLineas { get; set; }
public List<int> LineasPermitidas { get; set; } = new(); public List<int> LineasPermitidas { get; set; } = new();
public ConfiguracionHistorialRuta? Configuracion { get; set; }
public DateTime FechaCreacionUtc { get; set; } = DateTime.UtcNow; public DateTime FechaCreacionUtc { get; set; } = DateTime.UtcNow;
public DateTime FechaActualizacionUtc { get; set; } = DateTime.UtcNow; public DateTime FechaActualizacionUtc { get; set; } = DateTime.UtcNow;
} }

View File

@@ -55,6 +55,9 @@ public class Variante
/// <summary>Código interno de la variante.</summary> /// <summary>Código interno de la variante.</summary>
public int Codigo { get; set; } public int Codigo { get; set; }
/// <summary>Destino indicado por DIR_DESTINO en las expediciones del itinerario.</summary>
public string Destino { get; set; } = "";
/// <summary> /// <summary>
/// Identificadores de trayectos que se recorren en esta variante, en orden. /// Identificadores de trayectos que se recorren en esta variante, en orden.
/// </summary> /// </summary>

View File

@@ -89,6 +89,8 @@ public sealed class ServicioHistorialRutasCompartido : IHistorialRutasCompartido
existente.FactorEquilibradoAndarBus = registro.FactorEquilibradoAndarBus; existente.FactorEquilibradoAndarBus = registro.FactorEquilibradoAndarBus;
existente.ActivarFiltroLineas = registro.ActivarFiltroLineas; existente.ActivarFiltroLineas = registro.ActivarFiltroLineas;
existente.LineasPermitidas = NormalizarLineas(registro.LineasPermitidas).ToList(); existente.LineasPermitidas = NormalizarLineas(registro.LineasPermitidas).ToList();
if (registro.Configuracion is not null)
existente.Configuracion = CopiarConfiguracion(registro.Configuracion);
if (registro.FechaCreacionUtc != default) if (registro.FechaCreacionUtc != default)
existente.FechaCreacionUtc = registro.FechaCreacionUtc; existente.FechaCreacionUtc = registro.FechaCreacionUtc;
@@ -300,10 +302,20 @@ public sealed class ServicioHistorialRutasCompartido : IHistorialRutasCompartido
FactorEquilibradoAndarBus = item.FactorEquilibradoAndarBus, FactorEquilibradoAndarBus = item.FactorEquilibradoAndarBus,
ActivarFiltroLineas = item.ActivarFiltroLineas, ActivarFiltroLineas = item.ActivarFiltroLineas,
LineasPermitidas = NormalizarLineas(item.LineasPermitidas).ToList(), LineasPermitidas = NormalizarLineas(item.LineasPermitidas).ToList(),
Configuracion = item.Configuracion is null
? null
: CopiarConfiguracion(item.Configuracion),
FechaCreacionUtc = item.FechaCreacionUtc, FechaCreacionUtc = item.FechaCreacionUtc,
FechaActualizacionUtc = item.FechaActualizacionUtc 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) private static string CalcularClaveEscenario(RegistroHistorialRuta item)
{ {
var origenLat = Math.Round(item.OrigenLatitud, 6).ToString("0.000000", CultureInfo.InvariantCulture); var origenLat = Math.Round(item.OrigenLatitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);

View File

@@ -95,7 +95,7 @@ public sealed class CreadorRedTransporteDbus
var codigoVariante = ExtraerCodigoVariante(exp.IdPatronExp); var codigoVariante = ExtraerCodigoVariante(exp.IdPatronExp);
var key = $"{exp.IdRuta}|{codigoItinerario}|{sentido}|{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 variantesTemp[key] = new VarianteTemporal
{ {
@@ -108,6 +108,11 @@ public sealed class CreadorRedTransporteDbus
StopIds = stopIds StopIds = stopIds
}; };
} }
else if (string.IsNullOrWhiteSpace(varianteTemp.Destino) &&
!string.IsNullOrWhiteSpace(exp.DirDestino))
{
varianteTemp.Destino = exp.DirDestino;
}
} }
var nextTrayectoId = 1; var nextTrayectoId = 1;
@@ -132,7 +137,11 @@ public sealed class CreadorRedTransporteDbus
itinerario.Sentidos.Add(sentido); 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 stopIdsValidos = temp.StopIds.Where(paradaById.ContainsKey).ToList();
var codigosParadas = stopIdsValidos var codigosParadas = stopIdsValidos
.Select(id => paradaById[id].Codigo) .Select(id => paradaById[id].Codigo)

View File

@@ -55,14 +55,16 @@
.inspector-previous-point { .inspector-previous-point {
display: grid; display: grid;
gap: 4px; gap: 5px;
margin: 7px 0 6px; margin: 7px 0 6px;
padding: 7px 0 2px; padding: 8px 0 2px;
border-top: 1px solid rgba(148, 163, 184, .25); border-top: 1px solid rgba(148, 163, 184, .25);
} }
.inspector-previous-point__distance { .inspector-previous-point__title {
line-height: 1.3; color: #e2e8f0;
font-size: .78rem;
font-weight: 700;
} }
.inspector-previous-point__details { .inspector-previous-point__details {
@@ -74,10 +76,8 @@
line-height: 1.25; line-height: 1.25;
} }
.inspector-previous-point__details > span:first-child { .inspector-previous-point__detail--full {
flex-basis: 100%; flex-basis: 100%;
color: #cbd5e1;
font-weight: 700;
} }
.rt-inspector-place-icon { .rt-inspector-place-icon {
@@ -788,12 +788,6 @@ html, body {
line-height: 1.3; line-height: 1.3;
} }
.popup-itinerario-header__destination {
margin-left: .3rem;
color: #bae6fd;
font-weight: 600;
}
.popup-parada-body { .popup-parada-body {
font-size: .8rem; font-size: .8rem;
} }
@@ -1374,70 +1368,59 @@ html, body {
gap: .75rem; gap: .75rem;
} }
.history-parameters-toggle { .history-config-mode {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) auto; gap: .35rem;
align-items: center; padding-bottom: .65rem;
gap: .65rem;
min-height: 30px;
padding-bottom: .6rem;
border-bottom: 1px solid rgba(148,163,184,.2); border-bottom: 1px solid rgba(148,163,184,.2);
color: #cbd5e1;
cursor: pointer;
user-select: none;
} }
.history-parameters-toggle__label { .history-config-mode__label {
min-width: 0; color: #cbd5e1;
font-size: .78rem; font-size: .75rem;
font-weight: 700; font-weight: 700;
} }
.history-parameters-toggle input { .history-config-mode__options {
position: absolute; display: grid;
opacity: 0; grid-template-columns: repeat(3, minmax(0, 1fr));
pointer-events: none; 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 { .history-config-mode__option {
position: relative; min-width: 0;
width: 36px; height: 28px;
height: 20px; overflow: hidden;
border: 1px solid rgba(148,163,184,.55); border: 0;
border-radius: 999px; border-radius: 6px;
background: rgba(2,6,23,.72); padding: 0 .35rem;
transition: background .15s ease, border-color .15s ease; background: transparent;
color: #94a3b8;
font-size: .7rem;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
} }
.history-parameters-toggle__thumb { .history-config-mode__option:hover:not(:disabled) {
position: absolute; background: rgba(51,65,85,.72);
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 {
color: #f8fafc; color: #f8fafc;
} }
.history-parameters-toggle--active .history-parameters-toggle__track { .history-config-mode__option--active {
border-color: rgba(59,130,246,.85); background: #2563eb;
background: rgba(37,99,235,.82); color: #fff;
box-shadow: 0 1px 4px rgba(0,0,0,.3);
} }
.history-parameters-toggle--active .history-parameters-toggle__thumb { .history-config-mode__option:disabled {
transform: translateX(16px);
background: #f8fafc;
}
.history-parameters-toggle:has(input:disabled) {
cursor: default; cursor: default;
opacity: .6; opacity: .58;
} }
.history-section { .history-section {

View File

@@ -7,16 +7,22 @@
}; };
window.initAltInspector = function (dotNetHelper) { 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) { 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); }, true);
document.addEventListener("keyup", function (e) { 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); }, true);
window.addEventListener("blur", function () { window.addEventListener("blur", function () {
dotNetHelper.invokeMethodAsync("SetInspectorActivo", false); setInspectorActive(false);
}); });
}; };
@@ -84,6 +90,40 @@ window.initAltInspector = function (dotNetHelper) {
window.rt = window.rt || {}; 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 () { window.rt.ensureRouteLayer = function () {
const map = window.__rtLeafletMap; const map = window.__rtLeafletMap;
if (!map || !window.L) { if (!map || !window.L) {
@@ -290,6 +330,8 @@ window.rt.drawRoute = function (id, coords, options) {
const attachRouteClick = function (line) { const attachRouteClick = function (line) {
line.on("click", function (e) { line.on("click", function (e) {
try { try {
if (window.rt.handleInspectorLayerClick(e)) return;
if (e && e.originalEvent) { if (e && e.originalEvent) {
if (e.originalEvent.preventDefault) e.originalEvent.preventDefault(); if (e.originalEvent.preventDefault) e.originalEvent.preventDefault();
if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation(); if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation();
@@ -422,6 +464,8 @@ window.rt.drawRoute = function (id, coords, options) {
segmentLine.on("mouseover", updateTooltipAtCursor); segmentLine.on("mouseover", updateTooltipAtCursor);
segmentLine.on("mousemove", updateTooltipAtCursor); segmentLine.on("mousemove", updateTooltipAtCursor);
segmentLine.on("click", function (e) { segmentLine.on("click", function (e) {
if (window.rt.handleInspectorLayerClick(e)) return;
window.rt.selectElevationSegment( window.rt.selectElevationSegment(
id, id,
segmentLine, segmentLine,
@@ -732,11 +776,14 @@ window.rt.drawElevPoints = function (id, points, options) {
opacity: 0.92, opacity: 0.92,
className: "rt-elev-tooltip" className: "rt-elev-tooltip"
}); });
}
m.on("click", function (e) { 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; if (!window.rt.isMobileLayout || !window.rt.isMobileLayout()) return;
try { m.openTooltip(e && e.latlng); } catch { } try { m.openTooltip(e && e.latlng); } catch { }
}); });
}
m.addTo(group); m.addTo(group);
} }
@@ -978,6 +1025,8 @@ window.rt.drawPreviewLine = function (id, coords, options) {
// ✅ Hacer la línea clicable y avisar a Blazor // ✅ Hacer la línea clicable y avisar a Blazor
line.on("click", function (e) { line.on("click", function (e) {
try { try {
if (window.rt.handleInspectorLayerClick(e)) return;
if (e && e.originalEvent) { if (e && e.originalEvent) {
if (e.originalEvent.preventDefault) e.originalEvent.preventDefault(); if (e.originalEvent.preventDefault) e.originalEvent.preventDefault();
if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation(); if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation();
@@ -1166,6 +1215,8 @@ window.rt.createHighlightedStopMarker = function (stop, pane, options) {
if (stop.code && window.rt._dotNetHelper) { if (stop.code && window.rt._dotNetHelper) {
marker.on("click", function (event) { marker.on("click", function (event) {
try { try {
if (window.rt.handleInspectorLayerClick(event)) return;
if (event && event.originalEvent) { if (event && event.originalEvent) {
if (event.originalEvent.preventDefault) event.originalEvent.preventDefault(); if (event.originalEvent.preventDefault) event.originalEvent.preventDefault();
if (event.originalEvent.stopPropagation) event.originalEvent.stopPropagation(); 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(); 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("mousedown", stopEvent);
marker.on("dragend", function (e) { marker.on("dragend", function (e) {
@@ -1438,6 +1492,9 @@ window.rt.setInspectorMarkers = function (lat, lon, places) {
opacity: 0.95, opacity: 0.95,
className: "rt-inspector-tooltip" className: "rt-inspector-tooltip"
}); });
puntoZ.on("click", function (event) {
window.rt.handleInspectorLayerClick(event);
});
puntoZ.addTo(window.rt._inspectorLayer); puntoZ.addTo(window.rt._inspectorLayer);
const lugares = Array.isArray(places) ? places.slice(0, 3) : []; const lugares = Array.isArray(places) ? places.slice(0, 3) : [];
@@ -1510,6 +1567,9 @@ window.rt.setInspectorMarkers = function (lat, lon, places) {
opacity: 0.95, opacity: 0.95,
className: "rt-inspector-tooltip" className: "rt-inspector-tooltip"
}); });
marker.on("click", function (event) {
window.rt.handleInspectorLayerClick(event);
});
marker.addTo(window.rt._inspectorLayer); marker.addTo(window.rt._inspectorLayer);
}); });
}; };