- historial arreglado
- solucion 1/2 - quitar segundo score y colocar el score bien calculado - score equilibrado a - no usar pendientes - flechas arregaldas con el zoom - intinerario permitidos hasta el 99 - añadir peso a los transbordos factor equilibrado a 6 visualizar pendientes en la confi avanzada cambiar pisos por metros
This commit is contained in:
@@ -50,9 +50,10 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private const double FactorPenalizacionDistanciaPiePorDefecto = 1.35;
|
||||
private const double ParametroPenalizacionDistanciaPieMetrosPorDefecto = 200.0;
|
||||
private const double CoeficienteEsfuerzoAlturaPorDefecto = 8.2;
|
||||
private const double FactorEquilibradoAndarBusPorDefecto = 6.0;
|
||||
private const double UmbralPendienteSubidaRutaPorDefecto = 2.0;
|
||||
private const double UmbralPendienteBajadaRutaPorDefecto = 5.0;
|
||||
private const double UmbralPisosPendienteRutaPorDefecto = 2.0;
|
||||
private const double UmbralAlturaAlertaPendienteRutaMetrosPorDefecto = 6.0;
|
||||
private const double MetrosPorPiso = 3.0;
|
||||
|
||||
private double _velocidadPieKmH = 3.6;
|
||||
@@ -60,12 +61,12 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private double _parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||
private double _pesoTransbordoRankingMinutos = 4.0;
|
||||
private double _factorEquilibradoAndarBus = 12.0;
|
||||
private double _factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.35";
|
||||
private string _parametroPenalizacionDistanciaPieTextoEdicion = "200";
|
||||
private string _coeficienteEsfuerzoAlturaTextoEdicion = "8.20";
|
||||
private string _pesoTransbordoRankingTextoEdicion = "4,0";
|
||||
private string _factorEquilibradoAndarBusTextoEdicion = "12";
|
||||
private string _factorEquilibradoAndarBusTextoEdicion = "6";
|
||||
private double _velocidadBusKmH = 16.0;
|
||||
private int _minutosTransbordo = 5;
|
||||
private int _margenTransbordoSegundos = 120;
|
||||
@@ -275,7 +276,11 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
{
|
||||
SincronizarTextosPenalizacionPie();
|
||||
await PersistirConfiguracionAsync();
|
||||
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
||||
|
||||
if (_mostrarElevacionesRuta)
|
||||
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
||||
else
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -285,6 +290,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
{
|
||||
SincronizarTextosPenalizacionPie();
|
||||
await PersistirConfiguracionAsync();
|
||||
await RecalcularRutaEquilibradaTrasCambioFactorAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -364,14 +370,14 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _panelRutasMovilContraido = false;
|
||||
private double _umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||
private double _umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||
private double _umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
||||
private double _umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||
private double _alturaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||
private double _distanciaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||
|
||||
private bool MostrarAvisoPendientesPositivasSignificativas =>
|
||||
private bool MostrarAlertaPendientesPositivasSignificativas =>
|
||||
_mostrarElevacionesRuta &&
|
||||
_alturaPendientePositivaSignificativaRutaMetros >
|
||||
(NormalizarUmbralPisosPendienteRuta(_umbralPisosPendienteRuta) * MetrosPorPiso);
|
||||
NormalizarUmbralAlturaAlertaPendienteRutaMetros(_umbralAlturaAlertaPendienteRutaMetros);
|
||||
private readonly Dictionary<string, List<Parada>> _paradasItinerarioVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, List<Parada>> _paradasRutaVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -1351,15 +1357,18 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construye los parametros actuales que afectan a la penalizacion peatonal.
|
||||
/// Construye los parametros actuales que afectan al ranking de rutas.
|
||||
/// </summary>
|
||||
private ParametrosPlanificadorRuta CrearParametrosPenalizacionActuales()
|
||||
{
|
||||
return new ParametrosPlanificadorRuta
|
||||
{
|
||||
VelocidadPieKmH = _velocidadPieKmH,
|
||||
VelocidadBusKmH = _velocidadBusKmH,
|
||||
FactorPenalizacionDistanciaPie = _factorPenalizacionDistanciaPie,
|
||||
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros
|
||||
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros,
|
||||
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
||||
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1386,7 +1395,8 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
var distanciaCorregidaBase = UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaTotalMetros(
|
||||
distanciasPiePorTramo,
|
||||
parametros);
|
||||
var esfuerzoExtra = double.IsFinite(alternativa.EsfuerzoExtraPendienteMetros)
|
||||
var esfuerzoExtra = _mostrarElevacionesRuta &&
|
||||
double.IsFinite(alternativa.EsfuerzoExtraPendienteMetros)
|
||||
? Math.Max(0, alternativa.EsfuerzoExtraPendienteMetros)
|
||||
: 0;
|
||||
|
||||
@@ -1416,7 +1426,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
var distanciaAndando = CalcularDistanciaCorregidaAlternativa(alternativa);
|
||||
var factor = double.IsFinite(_factorEquilibradoAndarBus)
|
||||
? Math.Max(0, _factorEquilibradoAndarBus)
|
||||
: 12.0;
|
||||
: FactorEquilibradoAndarBusPorDefecto;
|
||||
var distanciaEquilibrada = Math.Max(0, distanciaAndando) * factor;
|
||||
|
||||
return double.IsFinite(distanciaEquilibrada)
|
||||
@@ -1431,7 +1441,10 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
|
||||
var distanciaEquilibradaAndando = CalcularDistanciaEquilibradaAndandoAlternativa(alternativa);
|
||||
var score = distanciaBus + distanciaEquilibradaAndando;
|
||||
var penalizacionTransbordos = UtilidadesTransitoDbus.CalcularPenalizacionTransbordosRankingMetros(
|
||||
alternativa.NumeroTransbordosReales,
|
||||
CrearParametrosPenalizacionActuales());
|
||||
var score = distanciaBus + distanciaEquilibradaAndando + penalizacionTransbordos;
|
||||
return double.IsFinite(score) && score >= 0
|
||||
? score
|
||||
: double.PositiveInfinity;
|
||||
@@ -1443,7 +1456,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
if (!double.IsFinite(score))
|
||||
return string.Empty;
|
||||
|
||||
return $"Score {FormatearDistanciaTarjeta(score)}";
|
||||
return FormatearDistanciaTarjeta(score);
|
||||
}
|
||||
|
||||
private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync()
|
||||
|
||||
@@ -12,6 +12,7 @@ public partial class PlanificadorRutas
|
||||
private const string ClaveRespaldoHistorialLocal = "rutasdbus.planificador.routeHistory.recovery.v1";
|
||||
private const int MaxItemsHistorialLocal = 100;
|
||||
private const int IntervaloRefrescoHistorialCompartidoSegundos = 3;
|
||||
private const int TiempoMaxOperacionHistorialLocalSegundos = 8;
|
||||
private static readonly JsonSerializerOptions OpcionesLecturaHistorialLocal = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
@@ -36,6 +37,27 @@ public partial class PlanificadorRutas
|
||||
private bool _historialLocalCargado = false;
|
||||
private bool _historialLocalCargando = false;
|
||||
private int _versionAperturaHistorial;
|
||||
private int _numeroIntentoCargaHistorialLocal;
|
||||
private string _resumenErrorHistorialLocal = string.Empty;
|
||||
private string _detalleErrorHistorialLocal = string.Empty;
|
||||
|
||||
private sealed class ResultadoStorageNavegador
|
||||
{
|
||||
[JsonPropertyName("ok")]
|
||||
public bool Ok { get; set; }
|
||||
|
||||
[JsonPropertyName("found")]
|
||||
public bool Found { get; set; }
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
public string? Value { get; set; }
|
||||
|
||||
[JsonPropertyName("errorName")]
|
||||
public string? ErrorName { get; set; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
private async Task ToggleHistorialPanel()
|
||||
{
|
||||
@@ -80,8 +102,9 @@ public partial class PlanificadorRutas
|
||||
{
|
||||
return await tareaCarga;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
RegistrarErrorHistorialLocal("coordinacion de la carga", ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
@@ -93,62 +116,159 @@ public partial class PlanificadorRutas
|
||||
|
||||
private async Task<bool> CargarHistorialLocalDesdeNavegadorAsync()
|
||||
{
|
||||
string? json;
|
||||
var numeroIntento = ++_numeroIntentoCargaHistorialLocal;
|
||||
ResultadoStorageNavegador? resultadoLectura;
|
||||
try
|
||||
{
|
||||
json = await JS.InvokeAsync<string?>("rt.loadLocalValue", ClaveHistorialLocal);
|
||||
using var cts = new CancellationTokenSource(
|
||||
TimeSpan.FromSeconds(TiempoMaxOperacionHistorialLocalSegundos));
|
||||
resultadoLectura = await JS.InvokeAsync<ResultadoStorageNavegador?>(
|
||||
"rt.loadLocalValueResult",
|
||||
cts.Token,
|
||||
ClaveHistorialLocal);
|
||||
}
|
||||
catch (JSException)
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
RegistrarErrorHistorialLocal(
|
||||
"lectura de localStorage",
|
||||
new TimeoutException(
|
||||
$"La lectura no respondio en {TiempoMaxOperacionHistorialLocalSegundos} segundos.",
|
||||
ex),
|
||||
$"Intento de carga: {numeroIntento}.");
|
||||
return false;
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
RegistrarErrorHistorialLocal(
|
||||
"lectura de localStorage",
|
||||
ex,
|
||||
$"Intento de carga: {numeroIntento}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
if (resultadoLectura is null)
|
||||
{
|
||||
RegistrarErrorHistorialLocal(
|
||||
"lectura de localStorage",
|
||||
"RespuestaVacia",
|
||||
"JavaScript no devolvio ningun resultado de lectura.",
|
||||
$"Intento de carga: {numeroIntento}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!resultadoLectura.Ok)
|
||||
{
|
||||
RegistrarErrorHistorialLocal(
|
||||
"lectura de localStorage",
|
||||
resultadoLectura.ErrorName ?? "StorageError",
|
||||
resultadoLectura.ErrorMessage ?? "El navegador no ha indicado el motivo del error.",
|
||||
$"Intento de carga: {numeroIntento}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var json = resultadoLectura.Value;
|
||||
if (!resultadoLectura.Found || string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
|
||||
LimpiarErrorHistorialLocal();
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var items = DeserializarHistorialLocal(json, out var registrosOmitidos);
|
||||
var items = DeserializarHistorialLocal(
|
||||
json,
|
||||
out var registrosOmitidos,
|
||||
out var primerErrorRegistro);
|
||||
FinalizarCargaHistorialLocal(items);
|
||||
|
||||
if (registrosOmitidos > 0)
|
||||
{
|
||||
await GuardarHistorialLocalRecuperadoAsync(json);
|
||||
var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync(json);
|
||||
var detalleFalloRecuperacion = recuperacionGuardada
|
||||
? null
|
||||
: _detalleErrorHistorialLocal;
|
||||
if (!recuperacionGuardada)
|
||||
_historialLocalCargado = false;
|
||||
|
||||
RegistrarErrorHistorialLocal(
|
||||
"validacion de registros",
|
||||
"RegistrosDaniados",
|
||||
$"Se omitieron {registrosOmitidos} registros que no se podian utilizar.",
|
||||
$"Primer fallo: {primerErrorRegistro ?? "sin detalle"}. " +
|
||||
$"Registros recuperados: {_historialLocal.Count}. " +
|
||||
$"Tamano leido: {json.Length} caracteres. " +
|
||||
$"Copia de recuperacion: {(recuperacionGuardada ? "guardada" : "no guardada")}." +
|
||||
(string.IsNullOrWhiteSpace(detalleFalloRecuperacion)
|
||||
? string.Empty
|
||||
: $"{Environment.NewLine}Fallo al recuperar: {detalleFalloRecuperacion}"));
|
||||
MostrarMensajeHistorial(
|
||||
$"Se han recuperado {_historialLocal.Count} registros del historial; {registrosOmitidos} estaban dañados.",
|
||||
esError: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LimpiarErrorHistorialLocal();
|
||||
}
|
||||
|
||||
return true;
|
||||
return _historialLocalCargado;
|
||||
}
|
||||
catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException)
|
||||
catch (Exception ex) when (
|
||||
ex is JsonException or
|
||||
NotSupportedException or
|
||||
InvalidOperationException or
|
||||
ArgumentException)
|
||||
{
|
||||
FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
|
||||
await GuardarHistorialLocalRecuperadoAsync(json);
|
||||
var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync(json);
|
||||
var detalleFalloRecuperacion = recuperacionGuardada
|
||||
? null
|
||||
: _detalleErrorHistorialLocal;
|
||||
if (!recuperacionGuardada)
|
||||
_historialLocalCargado = false;
|
||||
|
||||
RegistrarErrorHistorialLocal(
|
||||
"deserializacion del historial",
|
||||
ex,
|
||||
$"Tamano leido: {json.Length} caracteres. " +
|
||||
$"Copia de recuperacion: {(recuperacionGuardada ? "guardada" : "no guardada")}." +
|
||||
(string.IsNullOrWhiteSpace(detalleFalloRecuperacion)
|
||||
? string.Empty
|
||||
: $"{Environment.NewLine}Fallo al recuperar: {detalleFalloRecuperacion}"));
|
||||
MostrarMensajeHistorial(
|
||||
"El historial personal guardado estaba dañado. Se ha conservado una copia y se ha iniciado uno nuevo.",
|
||||
recuperacionGuardada
|
||||
? "El historial personal guardado estaba dañado. Se ha conservado una copia y se ha iniciado uno nuevo."
|
||||
: "El historial personal esta dañado, pero no se ha modificado porque no se pudo guardar la copia. Reintenta la carga.",
|
||||
esError: true);
|
||||
return true;
|
||||
return _historialLocalCargado;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_historialLocalCargado = false;
|
||||
RegistrarErrorHistorialLocal(
|
||||
"fallo inesperado al procesar el historial",
|
||||
ex,
|
||||
$"Tamano leido: {json.Length} caracteres. El contenido original no se ha modificado.");
|
||||
MostrarMensajeHistorial(
|
||||
"El codigo ha producido un error inesperado al cargar el historial. Consulta el diagnostico; los datos originales no se han modificado.",
|
||||
esError: true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<RegistroHistorialRuta> DeserializarHistorialLocal(
|
||||
string json,
|
||||
out int registrosOmitidos)
|
||||
out int registrosOmitidos,
|
||||
out string? primerErrorRegistro)
|
||||
{
|
||||
using var documento = JsonDocument.Parse(json);
|
||||
if (documento.RootElement.ValueKind != JsonValueKind.Array)
|
||||
throw new JsonException("El historial local no contiene una lista.");
|
||||
|
||||
registrosOmitidos = 0;
|
||||
primerErrorRegistro = null;
|
||||
var recuperados = new List<RegistroHistorialRuta>();
|
||||
var idsVistos = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var elemento in documento.RootElement.EnumerateArray())
|
||||
{
|
||||
try
|
||||
@@ -157,18 +277,27 @@ public partial class PlanificadorRutas
|
||||
if (item is null)
|
||||
{
|
||||
registrosOmitidos++;
|
||||
primerErrorRegistro ??= "El registro deserializado era nulo.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.Id))
|
||||
if (string.IsNullOrWhiteSpace(item.Id) || !idsVistos.Add(item.Id))
|
||||
{
|
||||
item.Id = Guid.NewGuid().ToString("N");
|
||||
idsVistos.Add(item.Id);
|
||||
}
|
||||
|
||||
item.LineasPermitidas ??= new List<int>();
|
||||
NormalizarRegistroHistorialLocal(item);
|
||||
recuperados.Add(item);
|
||||
}
|
||||
catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException)
|
||||
catch (Exception ex) when (
|
||||
ex is JsonException or
|
||||
NotSupportedException or
|
||||
InvalidOperationException or
|
||||
ArgumentException)
|
||||
{
|
||||
registrosOmitidos++;
|
||||
primerErrorRegistro ??= $"{ex.GetType().Name}: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +307,33 @@ public partial class PlanificadorRutas
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static void NormalizarRegistroHistorialLocal(RegistroHistorialRuta item)
|
||||
{
|
||||
if (!CoordenadaHistorialValida(item.OrigenLatitud, item.OrigenLongitud))
|
||||
throw new JsonException("Las coordenadas de origen no son validas.");
|
||||
if (!CoordenadaHistorialValida(item.DestinoLatitud, item.DestinoLongitud))
|
||||
throw new JsonException("Las coordenadas de destino no son validas.");
|
||||
|
||||
item.OrigenTexto = string.IsNullOrWhiteSpace(item.OrigenTexto)
|
||||
? $"{item.OrigenLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.OrigenLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}"
|
||||
: item.OrigenTexto.Trim();
|
||||
item.DestinoTexto = string.IsNullOrWhiteSpace(item.DestinoTexto)
|
||||
? $"{item.DestinoLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.DestinoLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}"
|
||||
: item.DestinoTexto.Trim();
|
||||
item.LineasPermitidas = NormalizarLineasHistorial(item.LineasPermitidas).ToList();
|
||||
|
||||
if (item.FechaCreacionUtc == default)
|
||||
item.FechaCreacionUtc = item.FechaActualizacionUtc == default ? DateTime.UtcNow : item.FechaActualizacionUtc;
|
||||
if (item.FechaActualizacionUtc == default)
|
||||
item.FechaActualizacionUtc = item.FechaCreacionUtc;
|
||||
}
|
||||
|
||||
private static bool CoordenadaHistorialValida(double latitud, double longitud)
|
||||
=> double.IsFinite(latitud) &&
|
||||
double.IsFinite(longitud) &&
|
||||
latitud is >= -90 and <= 90 &&
|
||||
longitud is >= -180 and <= 180;
|
||||
|
||||
private void FinalizarCargaHistorialLocal(IEnumerable<RegistroHistorialRuta> items)
|
||||
{
|
||||
_historialLocal.Clear();
|
||||
@@ -187,17 +343,19 @@ public partial class PlanificadorRutas
|
||||
_historialLocalCargado = true;
|
||||
}
|
||||
|
||||
private async Task GuardarHistorialLocalRecuperadoAsync(string jsonOriginal)
|
||||
private async Task<bool> 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)
|
||||
{
|
||||
}
|
||||
if (!await GuardarValorHistorialLocalAsync(
|
||||
ClaveRespaldoHistorialLocal,
|
||||
jsonOriginal,
|
||||
"guardado de la copia de recuperacion"))
|
||||
return false;
|
||||
|
||||
var jsonRecuperado = JsonSerializer.Serialize(_historialLocal);
|
||||
return await GuardarValorHistorialLocalAsync(
|
||||
ClaveHistorialLocal,
|
||||
jsonRecuperado,
|
||||
"guardado del historial recuperado");
|
||||
}
|
||||
|
||||
private async Task PersistirHistorialLocalAsync()
|
||||
@@ -205,14 +363,143 @@ public partial class PlanificadorRutas
|
||||
if (!_historialLocalCargado)
|
||||
return;
|
||||
|
||||
var json = JsonSerializer.Serialize(_historialLocal);
|
||||
if (!await GuardarValorHistorialLocalAsync(
|
||||
ClaveHistorialLocal,
|
||||
json,
|
||||
"persistencia del historial"))
|
||||
{
|
||||
MostrarMensajeHistorial(
|
||||
"No se ha podido guardar el historial personal. Consulta el diagnostico.",
|
||||
esError: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> GuardarValorHistorialLocalAsync(string clave, string valor, string fase)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(_historialLocal);
|
||||
await JS.InvokeVoidAsync("rt.saveLocalValue", ClaveHistorialLocal, json);
|
||||
using var cts = new CancellationTokenSource(
|
||||
TimeSpan.FromSeconds(TiempoMaxOperacionHistorialLocalSegundos));
|
||||
var resultado = await JS.InvokeAsync<ResultadoStorageNavegador?>(
|
||||
"rt.saveLocalValueResult",
|
||||
cts.Token,
|
||||
clave,
|
||||
valor);
|
||||
|
||||
if (resultado?.Ok == true)
|
||||
return true;
|
||||
|
||||
RegistrarErrorHistorialLocal(
|
||||
fase,
|
||||
resultado?.ErrorName ?? "RespuestaVacia",
|
||||
resultado?.ErrorMessage ?? "JavaScript no devolvio un resultado valido al guardar.",
|
||||
$"Tamano que se intentaba guardar: {valor.Length} caracteres.");
|
||||
return false;
|
||||
}
|
||||
catch (JSException)
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
RegistrarErrorHistorialLocal(
|
||||
fase,
|
||||
new TimeoutException(
|
||||
$"La escritura no respondio en {TiempoMaxOperacionHistorialLocalSegundos} segundos.",
|
||||
ex),
|
||||
$"Tamano que se intentaba guardar: {valor.Length} caracteres.");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RegistrarErrorHistorialLocal(
|
||||
fase,
|
||||
ex,
|
||||
$"Tamano que se intentaba guardar: {valor.Length} caracteres.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReintentarCargaHistorialLocalAsync()
|
||||
{
|
||||
if (_historialLocalCargando)
|
||||
return;
|
||||
|
||||
_historialLocalCargado = false;
|
||||
_tareaCargaHistorialLocal = null;
|
||||
_historialLocalCargando = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var cargado = await AsegurarHistorialLocalCargadoAsync();
|
||||
MostrarMensajeHistorial(
|
||||
cargado
|
||||
? $"Historial personal cargado: {_historialLocal.Count} registro(s)."
|
||||
: "El historial personal sigue sin poder cargarse. Consulta el diagnostico.",
|
||||
esError: !cargado);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_historialLocalCargando = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void RegistrarErrorHistorialLocal(
|
||||
string fase,
|
||||
Exception excepcion,
|
||||
string? contexto = null)
|
||||
{
|
||||
var causa = excepcion.GetBaseException();
|
||||
RegistrarErrorHistorialLocal(
|
||||
fase,
|
||||
causa.GetType().Name,
|
||||
causa.Message,
|
||||
CombinarDetalle(contexto, excepcion.ToString()));
|
||||
}
|
||||
|
||||
private void RegistrarErrorHistorialLocal(
|
||||
string fase,
|
||||
string tipo,
|
||||
string mensaje,
|
||||
string? contexto = null)
|
||||
{
|
||||
var marcaTiempo = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss", CultureInfo.CurrentCulture);
|
||||
_resumenErrorHistorialLocal = $"{tipo}: {LimitarTextoDiagnostico(mensaje, 240)}";
|
||||
_detalleErrorHistorialLocal = string.Join(
|
||||
Environment.NewLine,
|
||||
new[]
|
||||
{
|
||||
$"Fecha: {marcaTiempo}",
|
||||
$"Fase: {fase}",
|
||||
$"Tipo: {tipo}",
|
||||
$"Mensaje: {mensaje}",
|
||||
$"Estado: cargado={_historialLocalCargado}, cargando={_historialLocalCargando}, registrosEnMemoria={_historialLocal.Count}",
|
||||
string.IsNullOrWhiteSpace(contexto) ? null : $"Contexto: {contexto}"
|
||||
}.Where(linea => linea is not null));
|
||||
|
||||
Console.Error.WriteLine($"[Historial personal] {_detalleErrorHistorialLocal}");
|
||||
}
|
||||
|
||||
private void LimpiarErrorHistorialLocal()
|
||||
{
|
||||
_resumenErrorHistorialLocal = string.Empty;
|
||||
_detalleErrorHistorialLocal = string.Empty;
|
||||
}
|
||||
|
||||
private static string? CombinarDetalle(string? contexto, string? detalle)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(contexto))
|
||||
return detalle;
|
||||
if (string.IsNullOrWhiteSpace(detalle))
|
||||
return contexto;
|
||||
return $"{contexto}{Environment.NewLine}{detalle}";
|
||||
}
|
||||
|
||||
private static string LimitarTextoDiagnostico(string texto, int maximo)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(texto) || texto.Length <= maximo)
|
||||
return texto;
|
||||
|
||||
return texto[..maximo] + "...";
|
||||
}
|
||||
|
||||
private async Task CargarHistorialCompartidoAsync(bool mostrarError = true)
|
||||
@@ -339,16 +626,29 @@ public partial class PlanificadorRutas
|
||||
|
||||
private async Task CargarHistorialLocalAsync(string id)
|
||||
{
|
||||
var item = _historialLocal.FirstOrDefault(x => x.Id == id);
|
||||
if (item is null)
|
||||
try
|
||||
{
|
||||
MostrarMensajeHistorial("No encuentro esa combinacion en tu historial personal.", esError: true);
|
||||
return;
|
||||
}
|
||||
var item = _historialLocal.FirstOrDefault(x => x.Id == id);
|
||||
if (item is null)
|
||||
{
|
||||
MostrarMensajeHistorial("No encuentro esa combinacion en tu historial personal.", esError: true);
|
||||
return;
|
||||
}
|
||||
|
||||
await AplicarRegistroHistorialAsync(item);
|
||||
SeleccionarHistorialLocal(id);
|
||||
MostrarMensajeHistorial("Combinacion personal cargada.");
|
||||
await AplicarRegistroHistorialAsync(item);
|
||||
SeleccionarHistorialLocal(id);
|
||||
MostrarMensajeHistorial("Combinacion personal cargada.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RegistrarErrorHistorialLocal(
|
||||
"aplicacion de un registro personal",
|
||||
ex,
|
||||
$"Id del registro: {id}.");
|
||||
MostrarMensajeHistorial(
|
||||
"La combinacion personal ha producido un error. Consulta el diagnostico.",
|
||||
esError: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CargarHistorialCompartidoAsync(string id)
|
||||
@@ -429,33 +729,46 @@ public partial class PlanificadorRutas
|
||||
|
||||
private async Task EliminarHistorialLocalAsync(string id)
|
||||
{
|
||||
if (!await AsegurarHistorialLocalCargadoAsync())
|
||||
return;
|
||||
|
||||
var indiceEliminado = _historialLocal.FindIndex(x => x.Id == id);
|
||||
if (indiceEliminado < 0)
|
||||
return;
|
||||
|
||||
var estabaSeleccionado = _historialLocalSeleccionadoId == id;
|
||||
_historialLocal.RemoveAt(indiceEliminado);
|
||||
|
||||
await PersistirHistorialLocalAsync();
|
||||
|
||||
if (estabaSeleccionado)
|
||||
try
|
||||
{
|
||||
if (_historialLocal.Count == 0)
|
||||
{
|
||||
_historialLocalSeleccionadoId = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
var siguiente = _historialLocal[Math.Min(indiceEliminado, _historialLocal.Count - 1)];
|
||||
await AplicarRegistroHistorialAsync(siguiente);
|
||||
SeleccionarHistorialLocal(siguiente.Id);
|
||||
}
|
||||
}
|
||||
if (!await AsegurarHistorialLocalCargadoAsync())
|
||||
return;
|
||||
|
||||
MostrarMensajeHistorial("Combinacion eliminada del historial personal.");
|
||||
var indiceEliminado = _historialLocal.FindIndex(x => x.Id == id);
|
||||
if (indiceEliminado < 0)
|
||||
return;
|
||||
|
||||
var estabaSeleccionado = _historialLocalSeleccionadoId == id;
|
||||
_historialLocal.RemoveAt(indiceEliminado);
|
||||
|
||||
await PersistirHistorialLocalAsync();
|
||||
|
||||
if (estabaSeleccionado)
|
||||
{
|
||||
if (_historialLocal.Count == 0)
|
||||
{
|
||||
_historialLocalSeleccionadoId = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
var siguiente = _historialLocal[Math.Min(indiceEliminado, _historialLocal.Count - 1)];
|
||||
await AplicarRegistroHistorialAsync(siguiente);
|
||||
SeleccionarHistorialLocal(siguiente.Id);
|
||||
}
|
||||
}
|
||||
|
||||
MostrarMensajeHistorial("Combinacion eliminada del historial personal.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
RegistrarErrorHistorialLocal(
|
||||
"eliminacion de un registro personal",
|
||||
ex,
|
||||
$"Id del registro: {id}.");
|
||||
MostrarMensajeHistorial(
|
||||
"No se ha podido completar la eliminacion. Consulta el diagnostico.",
|
||||
esError: true);
|
||||
}
|
||||
}
|
||||
|
||||
private void PedirConfirmacionEliminarHistorialCompartido(string id)
|
||||
@@ -638,7 +951,7 @@ public partial class PlanificadorRutas
|
||||
PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta,
|
||||
FactorEquilibradoAndarBus = double.IsFinite(_factorEquilibradoAndarBus)
|
||||
? Math.Max(0, _factorEquilibradoAndarBus)
|
||||
: 12.0,
|
||||
: FactorEquilibradoAndarBusPorDefecto,
|
||||
ActivarFiltroLineas = _activarRangoLineas,
|
||||
LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(),
|
||||
FechaCreacionUtc = DateTime.UtcNow,
|
||||
|
||||
@@ -1204,7 +1204,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _elevPopupMinimizado = false;
|
||||
|
||||
|
||||
private const string _versionApp = "(v-20260728a)";
|
||||
private const string _versionApp = "(v-20260731a)";
|
||||
|
||||
|
||||
private double? _paradaElevG;
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace RutasDBUS.Components.Pages;
|
||||
public partial class PlanificadorRutas
|
||||
{
|
||||
private const string ClaveConfiguracionLocal = "rutasdbus.planificador.config.v1";
|
||||
private const int VersionPersistenciaFactorEquilibrado = 2;
|
||||
private bool _configuracionLocalCargada = false;
|
||||
private bool _mostrarConfirmacionRestablecer = false;
|
||||
|
||||
@@ -36,8 +37,12 @@ public partial class PlanificadorRutas
|
||||
public double? CoeficienteEsfuerzoAltura { get; set; }
|
||||
public double? PesoTransbordoRankingMinutos { get; set; }
|
||||
public double? FactorEquilibradoAndarBus { get; set; }
|
||||
public int? VersionFactorEquilibrado { get; set; }
|
||||
public bool? MostrarElevacionesRuta { get; set; }
|
||||
public double? UmbralPendienteSubidaRuta { 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; }
|
||||
@@ -139,9 +144,11 @@ public partial class PlanificadorRutas
|
||||
CoeficienteEsfuerzoAltura = _coeficienteEsfuerzoAltura,
|
||||
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
||||
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
||||
VersionFactorEquilibrado = VersionPersistenciaFactorEquilibrado,
|
||||
MostrarElevacionesRuta = _mostrarElevacionesRuta,
|
||||
UmbralPendienteSubidaRuta = _umbralPendienteSubidaRuta,
|
||||
UmbralPendienteBajadaRuta = _umbralPendienteBajadaRuta,
|
||||
UmbralPisosPendienteRuta = _umbralPisosPendienteRuta,
|
||||
UmbralAlturaAlertaPendienteRutaMetros = _umbralAlturaAlertaPendienteRutaMetros,
|
||||
VelocidadPieKmH = _velocidadPieKmH,
|
||||
VelocidadBusKmH = _velocidadBusKmH,
|
||||
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
|
||||
@@ -206,7 +213,11 @@ public partial class PlanificadorRutas
|
||||
if (config.PesoTransbordoRankingMinutos is { } pesoTransbordo && double.IsFinite(pesoTransbordo))
|
||||
_pesoTransbordoRankingMinutos = Math.Abs(pesoTransbordo - 8.0) < 0.0001 ? 4.0 : Math.Max(0, pesoTransbordo);
|
||||
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
|
||||
_factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
|
||||
_factorEquilibradoAndarBus = (config.VersionFactorEquilibrado ?? 0) < VersionPersistenciaFactorEquilibrado &&
|
||||
Math.Abs(factorEquilibrado - 12.0) < 0.0001
|
||||
? FactorEquilibradoAndarBusPorDefecto
|
||||
: Math.Max(0, factorEquilibrado);
|
||||
_mostrarElevacionesRuta = config.MostrarElevacionesRuta ?? false;
|
||||
if (config.UmbralPendienteSubidaRuta is { } umbralSubida && double.IsFinite(umbralSubida))
|
||||
_umbralPendienteSubidaRuta = Math.Abs(umbralSubida - 3.0) < 0.0001
|
||||
? UmbralPendienteSubidaRutaPorDefecto
|
||||
@@ -217,8 +228,16 @@ public partial class PlanificadorRutas
|
||||
Math.Abs(umbralBajada - 6.0) < 0.0001
|
||||
? UmbralPendienteBajadaRutaPorDefecto
|
||||
: NormalizarUmbralPendienteRuta(umbralBajada);
|
||||
if (config.UmbralPisosPendienteRuta is { } umbralPisos && double.IsFinite(umbralPisos))
|
||||
_umbralPisosPendienteRuta = NormalizarUmbralPisosPendienteRuta(umbralPisos);
|
||||
if (config.UmbralAlturaAlertaPendienteRutaMetros is { } umbralAltura && double.IsFinite(umbralAltura))
|
||||
{
|
||||
_umbralAlturaAlertaPendienteRutaMetros =
|
||||
NormalizarUmbralAlturaAlertaPendienteRutaMetros(umbralAltura);
|
||||
}
|
||||
else if (config.UmbralPisosPendienteRuta is { } umbralPisos && double.IsFinite(umbralPisos))
|
||||
{
|
||||
_umbralAlturaAlertaPendienteRutaMetros =
|
||||
NormalizarUmbralAlturaAlertaPendienteRutaMetros(umbralPisos * MetrosPorPiso);
|
||||
}
|
||||
_velocidadPieKmH = Math.Clamp(config.VelocidadPieKmH, 0.5, 25.0);
|
||||
_velocidadBusKmH = Math.Clamp(
|
||||
Math.Abs(config.VelocidadBusKmH - 15.0) < 0.0001 ||
|
||||
@@ -321,10 +340,11 @@ public partial class PlanificadorRutas
|
||||
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||
_pesoTransbordoRankingMinutos = 4.0;
|
||||
_factorEquilibradoAndarBus = 12.0;
|
||||
_factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||
_mostrarElevacionesRuta = false;
|
||||
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||
_umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
||||
_umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||
_velocidadPieKmH = 3.6;
|
||||
_velocidadBusKmH = 16.0;
|
||||
_minutosTransbordo = 5;
|
||||
@@ -371,10 +391,11 @@ public partial class PlanificadorRutas
|
||||
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||
_pesoTransbordoRankingMinutos = 4.0;
|
||||
_factorEquilibradoAndarBus = 12.0;
|
||||
_factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||
_mostrarElevacionesRuta = false;
|
||||
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||
_umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
||||
_umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||
_velocidadPieKmH = 3.6;
|
||||
_velocidadBusKmH = 16.0;
|
||||
_minutosTransbordo = 5;
|
||||
|
||||
@@ -384,6 +384,12 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
IReadOnlyList<AlternativaRuta> alternativas,
|
||||
CancellationToken ct)
|
||||
{
|
||||
foreach (var alternativa in alternativas)
|
||||
alternativa.EsfuerzoExtraPendienteMetros = 0;
|
||||
|
||||
if (!_mostrarElevacionesRuta || alternativas.Count == 0)
|
||||
return;
|
||||
|
||||
var lineasPorAlternativa = alternativas
|
||||
.Select(alternativa => new
|
||||
{
|
||||
|
||||
@@ -81,16 +81,40 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
private async Task ToggleElevacionesRutaAsync(ChangeEventArgs e)
|
||||
{
|
||||
_mostrarElevacionesRuta = e.Value switch
|
||||
var mostrarElevaciones = e.Value switch
|
||||
{
|
||||
bool valor => valor,
|
||||
string texto when bool.TryParse(texto, out var valor) => valor,
|
||||
_ => !_mostrarElevacionesRuta
|
||||
};
|
||||
|
||||
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
||||
if (_mostrarElevacionesRuta == mostrarElevaciones)
|
||||
return;
|
||||
|
||||
_mostrarElevacionesRuta = mostrarElevaciones;
|
||||
await PersistirConfiguracionAsync();
|
||||
if (_opcionesRuta.Count == 0)
|
||||
{
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
_pintandoElevacionesRuta = true;
|
||||
_mensajeRuta = _mostrarElevacionesRuta
|
||||
? "Aplicando pendientes..."
|
||||
: "Representando ruta seleccionada...");
|
||||
: "Recalculando sin pendientes...";
|
||||
StateHasChanged();
|
||||
await Task.Yield();
|
||||
|
||||
try
|
||||
{
|
||||
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync(forzarRepintado: true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pintandoElevacionesRuta = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CambiarUmbralElevacionRutaAsync(ChangeEventArgs e, bool esSubida)
|
||||
@@ -107,7 +131,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
await PersistirConfiguracionAsync();
|
||||
|
||||
if (esSubida)
|
||||
if (esSubida && _mostrarElevacionesRuta)
|
||||
{
|
||||
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
||||
}
|
||||
@@ -117,7 +141,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task RecalcularRankingTrasCambioEsfuerzoPendienteAsync()
|
||||
private async Task RecalcularRankingTrasCambioEsfuerzoPendienteAsync(bool forzarRepintado = false)
|
||||
{
|
||||
if (_opcionesRuta.Count == 0)
|
||||
{
|
||||
@@ -143,7 +167,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
!ReferenceEquals(opcionSeleccionadaAnterior, _opcionesRuta[nuevoIndice]);
|
||||
_indiceAlternativaSeleccionada = nuevoIndice;
|
||||
|
||||
if (_mostrarElevacionesRuta || cambioSeleccion)
|
||||
if (forzarRepintado || _mostrarElevacionesRuta || cambioSeleccion)
|
||||
{
|
||||
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
||||
? "Aplicando pendientes..."
|
||||
@@ -155,11 +179,11 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CambiarUmbralPisosPendienteRutaAsync(ChangeEventArgs e)
|
||||
private async Task CambiarUmbralAlturaAlertaPendienteRutaAsync(ChangeEventArgs e)
|
||||
{
|
||||
_umbralPisosPendienteRuta = ParseUmbralPisosPendienteRuta(
|
||||
_umbralAlturaAlertaPendienteRutaMetros = ParseUmbralAlturaAlertaPendienteRutaMetros(
|
||||
e.Value,
|
||||
_umbralPisosPendienteRuta);
|
||||
_umbralAlturaAlertaPendienteRutaMetros);
|
||||
|
||||
await PersistirConfiguracionAsync();
|
||||
StateHasChanged();
|
||||
@@ -210,22 +234,22 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
? Math.Clamp(Math.Abs(valor), 0.0, 100.0)
|
||||
: UmbralPendienteSubidaRutaPorDefecto;
|
||||
|
||||
private static double ParseUmbralPisosPendienteRuta(object? valor, double actual)
|
||||
private static double ParseUmbralAlturaAlertaPendienteRutaMetros(object? valor, double actual)
|
||||
{
|
||||
var texto = Convert.ToString(valor, CultureInfo.InvariantCulture)?.Replace(',', '.');
|
||||
if (!double.TryParse(texto, NumberStyles.Float, CultureInfo.InvariantCulture, out var nuevo) ||
|
||||
!double.IsFinite(nuevo))
|
||||
{
|
||||
return NormalizarUmbralPisosPendienteRuta(actual);
|
||||
return NormalizarUmbralAlturaAlertaPendienteRutaMetros(actual);
|
||||
}
|
||||
|
||||
return NormalizarUmbralPisosPendienteRuta(nuevo);
|
||||
return NormalizarUmbralAlturaAlertaPendienteRutaMetros(nuevo);
|
||||
}
|
||||
|
||||
private static double NormalizarUmbralPisosPendienteRuta(double valor)
|
||||
private static double NormalizarUmbralAlturaAlertaPendienteRutaMetros(double valor)
|
||||
=> double.IsFinite(valor)
|
||||
? Math.Clamp(Math.Abs(valor), 0.0, 100.0)
|
||||
: UmbralPisosPendienteRutaPorDefecto;
|
||||
? Math.Clamp(Math.Abs(valor), 0.0, 1000.0)
|
||||
: UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||
|
||||
private async Task DrawWalkingRouteJsAsync(
|
||||
string id,
|
||||
|
||||
@@ -613,88 +613,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="route-visual-options">
|
||||
<label class="route-elevation-switch @(_mostrarElevacionesRuta ? "route-elevation-switch--active" : "")"
|
||||
title="Colorear tramos a pie segun pendiente">
|
||||
<input type="checkbox"
|
||||
checked="@_mostrarElevacionesRuta"
|
||||
disabled="@(_pintandoElevacionesRuta || _opcionesRuta.Count == 0)"
|
||||
@onchange="ToggleElevacionesRutaAsync" />
|
||||
<span class="route-elevation-switch-track">
|
||||
<span class="route-elevation-switch-thumb"></span>
|
||||
</span>
|
||||
<span class="route-elevation-switch-text">
|
||||
Visualizar Pendientes
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div class="route-elevation-settings">
|
||||
<button type="button"
|
||||
class="route-elevation-settings-trigger @(_mostrarConfiguracionPendientesRuta ? "route-elevation-settings-trigger--active" : "")"
|
||||
title="Configurar pendientes"
|
||||
aria-label="Configurar pendientes"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="@_mostrarConfiguracionPendientesRuta"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onclick="() => _mostrarConfiguracionPendientesRuta = !_mostrarConfiguracionPendientesRuta">
|
||||
<span aria-hidden="true">⚙</span>
|
||||
</button>
|
||||
|
||||
@if (_mostrarConfiguracionPendientesRuta)
|
||||
{
|
||||
<div class="route-elevation-settings-popover"
|
||||
role="dialog"
|
||||
aria-label="Umbrales de pendientes"
|
||||
@onclick:stopPropagation="true">
|
||||
<div class="route-elevation-settings-header">
|
||||
<span>Configurar Pendientes</span>
|
||||
<button type="button"
|
||||
class="route-elevation-settings-close"
|
||||
title="Cerrar"
|
||||
aria-label="Cerrar configuración de pendientes"
|
||||
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="route-elevation-settings-fields">
|
||||
<label class="route-elevation-setting-row">
|
||||
<span>Subida</span>
|
||||
<input type="text"
|
||||
inputmode="decimal"
|
||||
aria-label="Porcentaje de subida"
|
||||
value="@_umbralPendienteSubidaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, true))" />
|
||||
<span>%</span>
|
||||
</label>
|
||||
<label class="route-elevation-setting-row">
|
||||
<span>Bajada</span>
|
||||
<input type="text"
|
||||
inputmode="decimal"
|
||||
aria-label="Porcentaje de bajada"
|
||||
value="@_umbralPendienteBajadaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, false))" />
|
||||
<span>%</span>
|
||||
</label>
|
||||
<label class="route-elevation-setting-row"
|
||||
title="Pisos acumulados para mostrar el aviso de pendientes positivas">
|
||||
<span>Aviso</span>
|
||||
<input type="text"
|
||||
inputmode="decimal"
|
||||
aria-label="Pisos para mostrar el aviso"
|
||||
value="@_umbralPisosPendienteRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onchange="CambiarUmbralPisosPendienteRutaAsync" />
|
||||
<span>pisos</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (_opcionesRuta.Count > 0)
|
||||
{
|
||||
<div class="route-alt-list">
|
||||
@@ -763,16 +681,13 @@
|
||||
{
|
||||
var alt = _opcionesRuta[_indiceAlternativaSeleccionada];
|
||||
|
||||
@if (MostrarAvisoPendientesPositivasSignificativas)
|
||||
@if (MostrarAlertaPendientesPositivasSignificativas)
|
||||
{
|
||||
<div class="route-slope-warning" role="status">
|
||||
<div>Pendientes positivas significativas.</div>
|
||||
<div style="margin-top:.2rem; padding-left:.55rem;">
|
||||
<div>- Distancia con subidas: @FormatearDistanciaResumenElevacion(_distanciaPendientePositivaSignificativaRutaMetros)</div>
|
||||
<div>
|
||||
- Subidas acumuladas: @_alturaPendientePositivaSignificativaRutaMetros.ToString("0.00", CultureInfo.InvariantCulture) m
|
||||
(@((_alturaPendientePositivaSignificativaRutaMetros / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture)) pisos)
|
||||
</div>
|
||||
<div>- Subidas acumuladas: @_alturaPendientePositivaSignificativaRutaMetros.ToString("0.00", CultureInfo.InvariantCulture) m</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -975,7 +890,7 @@
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(scoreEquilibradoSeleccionado))
|
||||
{
|
||||
<div>Score equilibrado: @scoreEquilibradoSeleccionado</div>
|
||||
<div>Score equilibrado A: @scoreEquilibradoSeleccionado</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(xyzOrigen))
|
||||
{
|
||||
@@ -1113,6 +1028,27 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_resumenErrorHistorialLocal))
|
||||
{
|
||||
<div class="history-local-diagnostic" role="alert">
|
||||
<div class="history-local-diagnostic__title">Diagnóstico del historial personal</div>
|
||||
<div class="history-local-diagnostic__summary">@_resumenErrorHistorialLocal</div>
|
||||
<details class="history-local-diagnostic__details">
|
||||
<summary>Detalle técnico</summary>
|
||||
<pre>@_detalleErrorHistorialLocal</pre>
|
||||
</details>
|
||||
|
||||
@if (!_historialLocalCargado && !_historialLocalCargando)
|
||||
{
|
||||
<button type="button"
|
||||
class="history-local-diagnostic__retry"
|
||||
@onclick="ReintentarCargaHistorialLocalAsync">
|
||||
Reintentar carga
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="history-section">
|
||||
@@ -1262,10 +1198,10 @@
|
||||
</div>
|
||||
|
||||
<div class="config-row">
|
||||
<label>Bus/paradas:</label>
|
||||
<label>Paradas:</label>
|
||||
<div class="config-dual-labeled">
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Seg. parada</span>
|
||||
<span>Seg./parada</span>
|
||||
@if (_usarHorariosTeoricos)
|
||||
{
|
||||
<input type="number" class="config-input" step="1" min="0" value="0" disabled readonly />
|
||||
@@ -1348,7 +1284,7 @@
|
||||
@bind="_itinerarioMin"
|
||||
@bind:event="onchange"
|
||||
@bind:after="PersistirConfiguracionAsync">
|
||||
@foreach (var n in Range(1, 150, 1))
|
||||
@foreach (var n in Range(1, 99, 1))
|
||||
{
|
||||
<option value="@n">@n</option>
|
||||
}
|
||||
@@ -1361,7 +1297,7 @@
|
||||
@bind="_itinerarioMax"
|
||||
@bind:event="onchange"
|
||||
@bind:after="PersistirConfiguracionAsync">
|
||||
@foreach (var n in Range(1, 150, 1))
|
||||
@foreach (var n in Range(1, 99, 1))
|
||||
{
|
||||
<option value="@n">@n</option>
|
||||
}
|
||||
@@ -1441,19 +1377,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-row config-row--radio-legacy">
|
||||
<label>Coef. / Param.(m) andando:</label>
|
||||
<div class="config-dual-inline config-dual-inline--bare">
|
||||
<input type="number" class="config-input" step="any"
|
||||
placeholder="Coef."
|
||||
title="Coeficiente de penalizacion"
|
||||
@bind="FactorPenalizacionDistanciaPieTexto" @bind:event="oninput"
|
||||
@onblur="OnBlurFactorPenalizacionDistanciaPie" />
|
||||
<input type="number" class="config-input" step="any"
|
||||
placeholder="Param."
|
||||
title="Parametro base en metros"
|
||||
@bind="ParametroPenalizacionDistanciaPieTexto" @bind:event="oninput"
|
||||
@onblur="OnBlurParametroPenalizacionDistanciaPie" />
|
||||
<div class="config-row">
|
||||
<label>Andando:</label>
|
||||
<div class="config-dual-labeled">
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Coef.</span>
|
||||
<input type="number" class="config-input" step="any"
|
||||
title="Coeficiente de penalización andando"
|
||||
@bind="FactorPenalizacionDistanciaPieTexto" @bind:event="oninput"
|
||||
@onblur="OnBlurFactorPenalizacionDistanciaPie" />
|
||||
</label>
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Base (m)</span>
|
||||
<input type="number" class="config-input" step="any"
|
||||
title="Parámetro base en metros"
|
||||
@bind="ParametroPenalizacionDistanciaPieTexto" @bind:event="oninput"
|
||||
@onblur="OnBlurParametroPenalizacionDistanciaPie" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1464,7 +1404,7 @@
|
||||
<span>Peso rank.</span>
|
||||
<input type="text" class="config-input"
|
||||
placeholder="4,0"
|
||||
title="Penalizacion interna por cada transbordo real en minutos decimales"
|
||||
title="Penalización temporal y distancia equivalente por cada transbordo real"
|
||||
@bind="PesoTransbordoRankingTexto" @bind:event="oninput"
|
||||
@onblur="OnBlurPesoTransbordoRanking" />
|
||||
</label>
|
||||
@@ -1478,59 +1418,90 @@
|
||||
</div>
|
||||
|
||||
<div class="config-row">
|
||||
<label>Equilibrado:</label>
|
||||
<div class="config-dual-labeled config-dual-labeled--equilibrado">
|
||||
<label>Factor / Método:</label>
|
||||
<div class="config-dual-labeled">
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Factor</span>
|
||||
<span>Equilib. A</span>
|
||||
<input type="number" class="config-input" step="0.1" min="0"
|
||||
placeholder="12"
|
||||
placeholder="6"
|
||||
title="Factor del modo equilibrado"
|
||||
@bind="FactorEquilibradoAndarBusTexto" @bind:event="oninput"
|
||||
@onblur="OnBlurFactorEquilibradoAndarBus" />
|
||||
</label>
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Andando</span>
|
||||
<select class="config-input"
|
||||
title="Método de cálculo andando"
|
||||
@bind="_walkProvider" @bind:after="PersistirConfiguracionAsync">
|
||||
<option value="@ProveedorCaminata.Osrm">v1</option>
|
||||
<option value="@ProveedorCaminata.Valhalla">v2</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-row">
|
||||
<label>Vel. Andando/Bus (km/h):</label>
|
||||
<div class="config-dual-inline config-dual-inline--bare">
|
||||
<input type="number" class="config-input" step="0.1"
|
||||
placeholder="Andar"
|
||||
title="Velocidad andando (km/h)"
|
||||
@bind="_velocidadPieKmH" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||
<input type="number" class="config-input" step="0.1"
|
||||
placeholder="Bus"
|
||||
title="Velocidad bus (km/h)"
|
||||
@bind="_velocidadBusKmH" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||
<label>Velocidad:</label>
|
||||
<div class="config-dual-labeled">
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Andar (km/h)</span>
|
||||
<input type="number" class="config-input" step="0.1"
|
||||
title="Velocidad andando (km/h)"
|
||||
@bind="_velocidadPieKmH" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||
</label>
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Bus (km/h)</span>
|
||||
<input type="number" class="config-input" step="0.1"
|
||||
title="Velocidad bus (km/h)"
|
||||
@bind="_velocidadBusKmH" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-row">
|
||||
<label>Altitud: Max Puntos / Coef</label>
|
||||
<div class="config-dual-inline config-dual-inline--bare">
|
||||
<input type="number" class="config-input" min="2" max="999" step="1"
|
||||
placeholder="Puntos"
|
||||
title="Máximo de puntos de altitud por tramo"
|
||||
@bind="_elevMaxPuntosPorTramo" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||
<input type="number" class="config-input" step="any"
|
||||
placeholder="Coef."
|
||||
title="Coeficiente de esfuerzo por altura"
|
||||
@bind="CoeficienteEsfuerzoAlturaTexto" @bind:event="oninput"
|
||||
@onblur="OnBlurCoeficienteEsfuerzoAltura" />
|
||||
<label>Altitud:</label>
|
||||
<div class="config-dual-labeled">
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Máx. puntos</span>
|
||||
<input type="number" class="config-input" min="2" max="999" step="1"
|
||||
title="Máximo de puntos de altitud por tramo"
|
||||
@bind="_elevMaxPuntosPorTramo" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||
</label>
|
||||
<label class="config-dual-labeled__field">
|
||||
<span>Coef.</span>
|
||||
<input type="number" class="config-input" step="any"
|
||||
title="Coeficiente de esfuerzo por altura"
|
||||
@bind="CoeficienteEsfuerzoAlturaTexto" @bind:event="oninput"
|
||||
@onblur="OnBlurCoeficienteEsfuerzoAltura" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-row">
|
||||
<label>Método cálculo andando:</label>
|
||||
|
||||
<div class="num-inline">
|
||||
<select class="config-input"
|
||||
style="width:100%; min-width:0;"
|
||||
@bind="_walkProvider" @bind:after="PersistirConfiguracionAsync">
|
||||
<option value="@ProveedorCaminata.Osrm">v1</option>
|
||||
<option value="@ProveedorCaminata.Valhalla">v2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-row config-row-pendientes">
|
||||
<label>Pendientes:</label>
|
||||
<div class="config-pendientes-inline">
|
||||
<label class="route-elevation-switch @(_mostrarElevacionesRuta ? "route-elevation-switch--active" : "")"
|
||||
title="Activar o desactivar las pendientes en el cálculo">
|
||||
<input type="checkbox"
|
||||
checked="@_mostrarElevacionesRuta"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onchange="ToggleElevacionesRutaAsync" />
|
||||
<span class="route-elevation-switch-track" aria-hidden="true">
|
||||
<span class="route-elevation-switch-thumb"></span>
|
||||
</span>
|
||||
<span class="config-pendientes-inline__label">Visualizar</span>
|
||||
</label>
|
||||
|
||||
<button type="button"
|
||||
class="config-pendientes-settings-button"
|
||||
title="Configurar Pendientes"
|
||||
aria-label="Configurar Pendientes"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onclick="() => _mostrarConfiguracionPendientesRuta = true">
|
||||
<span aria-hidden="true">⚙</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="config-actions-row">
|
||||
<button class="btn-ghost" @onclick="RecalcularConParametros">
|
||||
@@ -1562,6 +1533,57 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (_mostrarConfiguracionPendientesRuta)
|
||||
{
|
||||
<div class="custom-modal-backdrop pendientes-config-modal-backdrop"
|
||||
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
|
||||
<div class="custom-modal-card pendientes-config-modal" @onclick:stopPropagation="true">
|
||||
<div class="pendientes-config-modal__header">
|
||||
<div class="custom-modal-title">Configurar Pendientes</div>
|
||||
<button type="button"
|
||||
class="config-close-btn pendientes-config-modal__close"
|
||||
title="Cerrar"
|
||||
aria-label="Cerrar"
|
||||
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pendientes-config-modal__fields">
|
||||
<label class="pendientes-config-modal__field">
|
||||
<span>Subida (%)</span>
|
||||
<input type="text"
|
||||
class="config-input"
|
||||
inputmode="decimal"
|
||||
value="@_umbralPendienteSubidaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, true))" />
|
||||
</label>
|
||||
|
||||
<label class="pendientes-config-modal__field">
|
||||
<span>Bajada (%)</span>
|
||||
<input type="text"
|
||||
class="config-input"
|
||||
inputmode="decimal"
|
||||
value="@_umbralPendienteBajadaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, false))" />
|
||||
</label>
|
||||
|
||||
<label class="pendientes-config-modal__field">
|
||||
<span>Alerta (m)</span>
|
||||
<input type="text"
|
||||
class="config-input"
|
||||
inputmode="decimal"
|
||||
value="@_umbralAlturaAlertaPendienteRutaMetros.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@onchange="CambiarUmbralAlturaAlertaPendienteRutaAsync" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (_mostrarModelosHorariosPanel)
|
||||
{
|
||||
<div class="custom-modal-backdrop modelos-horarios-modal-backdrop" @onclick="CerrarModelosHorariosPanel">
|
||||
|
||||
Reference in New Issue
Block a user