- Horario tb6 cambiado

- Colores de subida/bajada cambiados
- On defecto en configuracion actual (texto cambiado)
- Coeficiente cambiado a 1.8
- Direccion del bus añadida en el detalle de tramos
- Tooltip del buscador
- Visualizador de lineas añadido en el listado de las paradas
- Palabra RESERVA en mayusculas
- Añadido editor de colores simple
This commit is contained in:
2026-09-01 13:47:08 +02:00
parent 402ce90a95
commit cd3da67a72
18 changed files with 793 additions and 101 deletions

View File

@@ -185,6 +185,16 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
{
var it = GetSelectedIt();
if (it is null) return;
await QuitarItItemAsync(it);
}
/// <summary>
/// Quita del mapa todos los sentidos incluidos en un elemento de itinerario.
/// </summary>
private async Task QuitarItItemAsync(ElementoItinerario it)
{
if (it is null) return;
// claves a borrar:
// - si seleccionas IV => borra I y V
@@ -204,6 +214,8 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
{
return;
}
QuitarSalidasParadaActivasPorClaves(claves);
foreach (var clave in claves)
{
@@ -243,6 +255,7 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
_itPreviewActivos.Clear();
_itPreviewIdsPorClave.Clear();
_salidasParadaItinerariosActivas.Clear();
LimpiarParadasItinerarioVisibles();
_previewLineasActivas.Clear(); // <- esto ya lo usas para los "full_..."

View File

@@ -47,7 +47,7 @@ public partial class PlanificadorRutas : ComponentBase
private int _nivelBrilloMapa = 0;
// CONFIG
private const double FactorPenalizacionDistanciaPiePorDefecto = 1.35;
private const double FactorPenalizacionDistanciaPiePorDefecto = 1.8;
private const double ParametroPenalizacionDistanciaPieMetrosPorDefecto = 200.0;
private const double CoeficienteEsfuerzoAlturaPorDefecto = 5.2;
private const double PendienteMaximaEsfuerzoPorcentaje = 80.0;
@@ -55,8 +55,8 @@ public partial class PlanificadorRutas : ComponentBase
private const double UmbralPendienteSubidaRutaPorDefecto = 2.0;
private const double UmbralPendienteSubidaMediaRutaPorDefecto = 5.0;
private const double UmbralPendienteSubidaFuerteRutaPorDefecto = 10.0;
private const string ColorPendienteSubidaSuaveRutaPorDefecto = "#d946ef";
private const string ColorPendienteSubidaMediaRutaPorDefecto = "#4c1d95";
private const string ColorPendienteSubidaSuaveRutaPorDefecto = "#ec4899";
private const string ColorPendienteSubidaMediaRutaPorDefecto = "#2563eb";
private const string ColorPendienteSubidaFuerteRutaPorDefecto = "#000000";
private const double UmbralPendienteBajadaRutaPorDefecto = 5.0;
private const double UmbralAlturaAlertaPendienteRutaMetrosPorDefecto = 6.0;
@@ -68,7 +68,7 @@ public partial class PlanificadorRutas : ComponentBase
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
private double _pesoTransbordoRankingMinutos = 4.0;
private double _factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.35";
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.8";
private string _parametroPenalizacionDistanciaPieTextoEdicion = "200";
private string _coeficienteEsfuerzoAlturaTextoEdicion = "5.20";
private string _pesoTransbordoRankingTextoEdicion = "4,0";
@@ -1163,7 +1163,10 @@ public partial class PlanificadorRutas : ComponentBase
string? dashArray = null,
bool arrowEnd = false,
string? lineCap = null,
IReadOnlyList<object>? elevationSegments = null)
IReadOnlyList<object>? elevationSegments = null,
string? haloColor = null,
int? haloWeight = null,
double? haloOpacity = null)
{
try
{
@@ -1171,7 +1174,19 @@ public partial class PlanificadorRutas : ComponentBase
"rt.drawRoute",
id,
LineaToLatLng(linea),
new { color, weight, opacity, dashArray, arrowEnd, lineCap, elevationSegments }
new
{
color,
weight,
opacity,
dashArray,
arrowEnd,
lineCap,
elevationSegments,
haloColor,
haloWeight,
haloOpacity
}
);
}
catch { }

View File

@@ -22,6 +22,9 @@ public partial class PlanificadorRutas
public string Fuente { get; init; } = "";
public double Latitud { get; init; }
public double Longitud { get; init; }
public string TextoCompleto => string.IsNullOrWhiteSpace(Subtitulo)
? Titulo
: $"{Titulo} - {Subtitulo}";
}
private readonly List<SugerenciaBusquedaMapa> _sugerenciasBusquedaOrigen = new();

View File

@@ -41,7 +41,7 @@ public partial class PlanificadorRutas
private bool _refrescandoHistorialCompartido = false;
private bool _omitirRegistroHistorialEnSiguienteCalculo = false;
private bool _aplicandoRegistroHistorial = false;
private bool _usarParametrosActualesHistorial = false;
private bool _usarParametrosActualesHistorial = true;
private bool _cambiandoParametrosHistorial = false;
private string? _historialCompartidoPendienteEliminarId;
private ConfiguracionLocalPlanificador? _configuracionTemporalPreviaHistorial;
@@ -1488,7 +1488,7 @@ public partial class PlanificadorRutas
var configuracionPrevia = _configuracionTemporalPreviaHistorial;
_configuracionTemporalPreviaHistorial = null;
_configuracionParametrosActualesHistorial = null;
_usarParametrosActualesHistorial = false;
_usarParametrosActualesHistorial = true;
if (configuracionPrevia is null)
return;

View File

@@ -293,6 +293,113 @@ public partial class PlanificadorRutas : ComponentBase
}
}
/// <summary>
/// Alterna desde el popup de parada el itinerario asociado a una salida.
/// </summary>
private readonly Dictionary<string, string> _salidasParadaItinerariosActivas =
new(StringComparer.OrdinalIgnoreCase);
private async Task ToggleItinerarioSalidaParadaAsync(ProximaSalidaVm salida)
{
var itinerario = ObtenerItinerarioSalidaParada(salida);
if (itinerario is null)
return;
var salidaId = CrearIdSalidaParada(salida);
var claveItinerario = ItClave(itinerario.Variantes[0]);
var esSalidaActiva = _salidasParadaItinerariosActivas.TryGetValue(salidaId, out var claveActiva) &&
string.Equals(claveActiva, claveItinerario, StringComparison.OrdinalIgnoreCase);
var estaPintado = EstaItinerarioPintado(itinerario);
if (esSalidaActiva && estaPintado)
{
await QuitarItItemAsync(itinerario);
}
else
{
if (!estaPintado)
{
await PintarItItemAsync(itinerario);
await FitRoutesJsAsync(ConstruirCoordenadasItinerario(itinerario));
}
QuitarSalidasParadaActivasPorClaves([claveItinerario]);
_salidasParadaItinerariosActivas[salidaId] = claveItinerario;
}
if (_itItemsFiltrados.Any(x => x.Id.Equals(itinerario.Id, StringComparison.OrdinalIgnoreCase)))
{
_itSeleccionadoId = itinerario.Id;
_selectItKey++;
}
StateHasChanged();
}
private bool EstaItinerarioSalidaParadaPintado(ProximaSalidaVm salida)
{
var itinerario = ObtenerItinerarioSalidaParada(salida);
return itinerario is not null &&
_salidasParadaItinerariosActivas.TryGetValue(CrearIdSalidaParada(salida), out var claveActiva) &&
itinerario.Variantes.Any(variante =>
string.Equals(claveActiva, ItClave(variante), StringComparison.OrdinalIgnoreCase)) &&
EstaItinerarioPintado(itinerario);
}
private void QuitarSalidasParadaActivasPorClaves(IEnumerable<string> claves)
{
var clavesAEliminar = new HashSet<string>(claves, StringComparer.OrdinalIgnoreCase);
foreach (var salidaId in _salidasParadaItinerariosActivas
.Where(x => clavesAEliminar.Contains(x.Value))
.Select(x => x.Key)
.ToList())
{
_salidasParadaItinerariosActivas.Remove(salidaId);
}
}
private static string CrearIdSalidaParada(ProximaSalidaVm salida)
=> string.Join('|',
salida.ExpId,
salida.RutaId,
salida.Hora,
salida.ItinerarioClave);
private bool EstaItinerarioPintado(ElementoItinerario itinerario)
=> itinerario.Variantes.Any(variante =>
_itPreviewIdsPorClave.TryGetValue(ItClave(variante), out var ids) && ids.Count > 0);
private ElementoItinerario? ObtenerItinerarioSalidaParada(ProximaSalidaVm salida)
{
var claves = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
NormalizarClaveItinerarioPopup(salida.ItinerarioClave),
NormalizarClaveItinerarioPopup(salida.LineaTxt)
};
claves.RemoveWhere(string.IsNullOrWhiteSpace);
return _itItems.FirstOrDefault(item =>
item.Variantes.Count == 1 &&
claves.Contains(NormalizarClaveItinerarioPopup(ItClave(item.Variantes[0]))));
}
private static string NormalizarClaveItinerarioPopup(string? texto)
{
if (string.IsNullOrWhiteSpace(texto))
return "";
var clave = texto.Trim();
var separadorNombre = clave.IndexOf(" - ", StringComparison.Ordinal);
if (separadorNombre >= 0)
clave = clave[..separadorNombre];
const string prefijoLinea = "Línea ";
if (clave.StartsWith(prefijoLinea, StringComparison.OrdinalIgnoreCase))
clave = clave[prefijoLinea.Length..];
return clave.Replace(" ", "", StringComparison.Ordinal).ToUpperInvariant();
}
/// <summary>
/// Reúne la geometría de todos los sentidos incluidos en un elemento de itinerario.
/// </summary>
@@ -345,7 +452,7 @@ public partial class PlanificadorRutas : ComponentBase
private async Task MostrarPopupParadaAsync(Parada paradaDetectada)
{
var myToken = Interlocked.Increment(ref _paradaPopupToken);
_paradaClicada = null;
_paradaElevIdee = null;
_paradaElevLocal5 = null;
@@ -1202,7 +1309,7 @@ public partial class PlanificadorRutas : ComponentBase
private bool _elevPopupMinimizado = false;
private const string _versionApp = "(v-20260831a)";
private const string _versionApp = "(v-20260901a)";
private double? _paradaElevIdee;

View File

@@ -8,8 +8,10 @@ namespace RutasDBUS.Components.Pages;
public partial class PlanificadorRutas
{
private const string ClaveConfiguracionLocal = "rutasdbus.planificador.config.v1";
private const int VersionPersistenciaFactorPenalizacionPie = 1;
private const int VersionPersistenciaFactorEquilibrado = 2;
private const int VersionPersistenciaPendientes = 2;
private const int VersionPersistenciaColoresPendientes = 1;
private const int VersionPersistenciaCoeficienteEsfuerzo = 1;
private const int VersionPersistenciaFuenteElevacion = 1;
private bool _configuracionLocalCargada = false;
@@ -38,6 +40,7 @@ public partial class PlanificadorRutas
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; }
@@ -48,6 +51,7 @@ public partial class PlanificadorRutas
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; }
@@ -161,6 +165,7 @@ public partial class PlanificadorRutas
FechaSimulada = _fechaSimulada.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
HoraSimulada = _horaSimulada.ToString("HH:mm:ss", CultureInfo.InvariantCulture),
FactorPenalizacionDistanciaPie = _factorPenalizacionDistanciaPie,
VersionFactorPenalizacionPie = VersionPersistenciaFactorPenalizacionPie,
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros,
CoeficienteEsfuerzoAltura = _coeficienteEsfuerzoAltura,
VersionCoeficienteEsfuerzo = VersionPersistenciaCoeficienteEsfuerzo,
@@ -171,6 +176,7 @@ public partial class PlanificadorRutas
FuenteElevacionCalculo = _fuenteElevacionCalculo,
VersionFuenteElevacion = VersionPersistenciaFuenteElevacion,
VersionPendientes = VersionPersistenciaPendientes,
VersionColoresPendientes = VersionPersistenciaColoresPendientes,
UmbralPendienteSubidaRuta = _umbralPendienteSubidaRuta,
UmbralPendienteSubidaMediaRuta = _umbralPendienteSubidaMediaRuta,
UmbralPendienteSubidaFuerteRuta = _umbralPendienteSubidaFuerteRuta,
@@ -224,9 +230,14 @@ public partial class PlanificadorRutas
if (TimeOnly.TryParse(config.HoraSimulada, out var horaSimulada))
_horaSimulada = horaSimulada;
var migrarFactorPenalizacionPie =
(config.VersionFactorPenalizacionPie ?? 0) < VersionPersistenciaFactorPenalizacionPie;
if (config.FactorPenalizacionDistanciaPie is { } factor && double.IsFinite(factor))
_factorPenalizacionDistanciaPie =
Math.Abs(factor - 1.3) < 0.0001 || Math.Abs(factor - 1.6) < 0.0001
migrarFactorPenalizacionPie &&
(Math.Abs(factor - 1.3) < 0.0001 ||
Math.Abs(factor - 1.35) < 0.0001 ||
Math.Abs(factor - 1.6) < 0.0001)
? FactorPenalizacionDistanciaPiePorDefecto
: factor;
if (config.ParametroPenalizacionDistanciaPieMetros is { } parametro && double.IsFinite(parametro))
@@ -282,15 +293,23 @@ public partial class PlanificadorRutas
double.IsFinite(umbralSubidaFuerte)
? NormalizarUmbralPendienteRuta(umbralSubidaFuerte)
: UmbralPendienteSubidaFuerteRutaPorDefecto;
_colorPendienteSubidaSuaveRuta = NormalizarColorHexRuta(
config.ColorPendienteSubidaSuaveRuta,
ColorPendienteSubidaSuaveRutaPorDefecto);
_colorPendienteSubidaMediaRuta = NormalizarColorHexRuta(
config.ColorPendienteSubidaMediaRuta,
ColorPendienteSubidaMediaRutaPorDefecto);
_colorPendienteSubidaFuerteRuta = NormalizarColorHexRuta(
config.ColorPendienteSubidaFuerteRuta,
ColorPendienteSubidaFuerteRutaPorDefecto);
var migrarColoresPendientes =
(config.VersionColoresPendientes ?? 0) < VersionPersistenciaColoresPendientes;
_colorPendienteSubidaSuaveRuta = migrarColoresPendientes
? ColorPendienteSubidaSuaveRutaPorDefecto
: NormalizarColorHexRuta(
config.ColorPendienteSubidaSuaveRuta,
ColorPendienteSubidaSuaveRutaPorDefecto);
_colorPendienteSubidaMediaRuta = migrarColoresPendientes
? ColorPendienteSubidaMediaRutaPorDefecto
: NormalizarColorHexRuta(
config.ColorPendienteSubidaMediaRuta,
ColorPendienteSubidaMediaRutaPorDefecto);
_colorPendienteSubidaFuerteRuta = migrarColoresPendientes
? ColorPendienteSubidaFuerteRutaPorDefecto
: NormalizarColorHexRuta(
config.ColorPendienteSubidaFuerteRuta,
ColorPendienteSubidaFuerteRutaPorDefecto);
NormalizarOrdenUmbralesColorSubidaRuta();
if (config.UmbralPendienteBajadaRuta is { } umbralBajada && double.IsFinite(umbralBajada))
_umbralPendienteBajadaRuta =

View File

@@ -141,22 +141,36 @@ public partial class PlanificadorRutas : ComponentBase
Fuerte
}
private async Task CambiarColorPendienteSubidaRutaAsync(
ChangeEventArgs e,
private NivelColorSubidaRuta? _selectorColorPendienteRuta;
private void AbrirConfiguracionPendientesRuta()
{
_selectorColorPendienteRuta = null;
_mostrarConfiguracionPendientesRuta = true;
}
private void CerrarConfiguracionPendientesRuta()
{
_selectorColorPendienteRuta = null;
_mostrarConfiguracionPendientesRuta = false;
}
private void AlternarSelectorColorPendienteRuta(NivelColorSubidaRuta nivel)
=> _selectorColorPendienteRuta = _selectorColorPendienteRuta == nivel ? null : nivel;
private string ObtenerColorPendienteSubidaRuta(NivelColorSubidaRuta nivel)
=> nivel switch
{
NivelColorSubidaRuta.Media => _colorPendienteSubidaMediaRuta,
NivelColorSubidaRuta.Fuerte => _colorPendienteSubidaFuerteRuta,
_ => _colorPendienteSubidaSuaveRuta
};
private async Task AplicarColorPendienteSubidaRutaAsync(
string color,
NivelColorSubidaRuta nivel)
{
var (actual, porDefecto) = nivel switch
{
NivelColorSubidaRuta.Media =>
(_colorPendienteSubidaMediaRuta, ColorPendienteSubidaMediaRutaPorDefecto),
NivelColorSubidaRuta.Fuerte =>
(_colorPendienteSubidaFuerteRuta, ColorPendienteSubidaFuerteRutaPorDefecto),
_ =>
(_colorPendienteSubidaSuaveRuta, ColorPendienteSubidaSuaveRutaPorDefecto)
};
var nuevo = NormalizarColorHexRuta(
Convert.ToString(e.Value, CultureInfo.InvariantCulture),
NormalizarColorHexRuta(actual, porDefecto));
var nuevo = NormalizarColorHexRuta(color, ObtenerColorPendienteSubidaRuta(nivel));
switch (nivel)
{
@@ -171,6 +185,7 @@ public partial class PlanificadorRutas : ComponentBase
break;
}
_selectorColorPendienteRuta = null;
await PersistirConfiguracionAsync();
StateHasChanged();
}
@@ -355,7 +370,10 @@ public partial class PlanificadorRutas : ComponentBase
dashArray: DashArrayPiePunteado,
arrowEnd: arrowEnd,
lineCap: "round",
elevationSegments: resultadoElevacion.Segmentos);
elevationSegments: resultadoElevacion.Segmentos,
haloColor: "#ffffff",
haloWeight: weight + 3,
haloOpacity: 0.9);
}
private async Task<(
@@ -520,6 +538,15 @@ public partial class PlanificadorRutas : ComponentBase
_distanciaPendientePositivaSignificativaRutaMetros = 0.0;
}
private string ObtenerNombreDestinoItinerario(InfoVariante variante)
{
if (variante.CodigosParadas.Count == 0)
return string.Empty;
var ultimaParada = ResolverParadaFisica(variante.CodigosParadas[^1]);
return ultimaParada.Nombre?.Trim() ?? string.Empty;
}
private double? ElevacionPreferidaMapa(MuestraElevacion muestra)
=> ObtenerElevacionFuenteCalculo(muestra);
@@ -561,7 +588,7 @@ public partial class PlanificadorRutas : ComponentBase
ColorPendienteSubidaSuaveRutaPorDefecto));
if (pendientePorcentaje.Value <= -umbralBajada)
return ("bajada", "#2563eb");
return ("bajada", "#16a34a");
return ("llano", "#dc2626");
}
@@ -829,6 +856,10 @@ public partial class PlanificadorRutas : ComponentBase
var tramo = tramosBus[i];
var esTransbordoReal = i > 0 && UtilidadesTransitoDbus.EsTransbordoReal(tramosBus[i - 1], tramo);
var infoVariante = tramo.Variante;
var nombreDestinoItinerario = ObtenerNombreDestinoItinerario(infoVariante);
var textoDestinoItinerario = string.IsNullOrWhiteSpace(nombreDestinoItinerario)
? string.Empty
: $" → {nombreDestinoItinerario}";
var lineaGeom = lineasBus[i];
string codParadaIni = infoVariante.CodigosParadas[tramo.IndiceInicio];
@@ -1100,7 +1131,7 @@ public partial class PlanificadorRutas : ComponentBase
{
Tipo = "Bus",
Descripcion = CapitalizarPalabrasClave(
$"{HHmm(depT.Value)} -> {HHmm(arrT.Value)} - Linea {codLineaItinSentidoMostrado} ({infoVariante.Linea.Nombre})."
$"{HHmm(depT.Value)} -> {HHmm(arrT.Value)} - Linea {codLineaItinSentidoMostrado} ({infoVariante.Linea.Nombre}){textoDestinoItinerario}."
),
DistanciaMetros = distanciaTramo,
DuracionSegundos = durBus,
@@ -1120,7 +1151,7 @@ public partial class PlanificadorRutas : ComponentBase
{
Tipo = "Bus",
Descripcion = CapitalizarPalabrasClave(
$"Linea {codLineaItinSentidoMostrado} ({infoVariante.Linea.Nombre})."
$"Linea {codLineaItinSentidoMostrado} ({infoVariante.Linea.Nombre}){textoDestinoItinerario}."
),
DistanciaMetros = distanciaTramo,
DuracionSegundos = duracionBusSegundos,

View File

@@ -222,6 +222,12 @@ public partial class PlanificadorRutas : ComponentBase
private bool HorariosTaxibusSimuladosVisibles
=> _nextDeps.Any(salida => TaxibusDbus.EsExpedicionSimulada(salida.ExpId));
private string MensajeHorarioTaxibusSimuladoVisible
=> TaxibusDbus.ObtenerMensajeHorarioSimulado(
_nextDeps
.Where(salida => TaxibusDbus.EsExpedicionSimulada(salida.ExpId))
.SelectMany(salida => new[] { salida.RutaId, salida.LineaTxt }));
private static string ObtenerAvisoTaxibus(AlternativaRuta alternativa)
{
var lineasTaxibus = alternativa.AlternativaBus?.Tramos
@@ -233,8 +239,13 @@ public partial class PlanificadorRutas : ComponentBase
if (lineasTaxibus is not { Count: > 0 })
return string.Empty;
return lineasTaxibus.Any(linea => linea.HorarioSimulado)
? TaxibusDbus.MensajeHorarioSimulado
var lineasSimuladas = lineasTaxibus
.Where(linea => linea.HorarioSimulado)
.Select(linea => linea.Codigo)
.ToList();
return lineasSimuladas.Count > 0
? TaxibusDbus.ObtenerMensajeHorarioSimulado(lineasSimuladas)
: TaxibusDbus.MensajeReserva;
}

View File

@@ -161,6 +161,7 @@
{
<button type="button"
class="gm-suggestion"
title="@sugerencia.TextoCompleto"
@onclick="() => SeleccionarSugerenciaBusquedaAsync(CampoBusquedaMapa.Origen, sugerencia)">
<span class="gm-suggestion__type">@sugerencia.Tipo</span>
<span class="gm-suggestion__text">
@@ -196,6 +197,7 @@
{
<button type="button"
class="gm-suggestion"
title="@sugerencia.TextoCompleto"
@onclick="() => SeleccionarSugerenciaBusquedaAsync(CampoBusquedaMapa.Destino, sugerencia)">
<span class="gm-suggestion__type">@sugerencia.Tipo</span>
<span class="gm-suggestion__text">
@@ -302,17 +304,28 @@
@if (HorariosTaxibusSimuladosVisibles)
{
<div class="taxibus-notice taxibus-notice--schedule" role="status">
<strong>Taxibús:</strong> @TaxibusDbus.MensajeHorarioSimulado
<strong>Taxibús:</strong> @MensajeHorarioTaxibusSimuladoVisible
</div>
}
<div style="max-height: 220px; overflow-y: auto; border: 1px solid rgba(255,255,255,.12); border-radius: 6px; padding: .35rem .5rem; scroll-behavior: smooth;">
<ul style="margin:0; padding-left:1.1rem;">
<div class="stop-schedule-list">
<ul>
@foreach (var x in NextDepsOrdenados)
{
var itinerarioPintado = EstaItinerarioSalidaParadaPintado(x);
<li id="@x.AnchorId"
style="margin:.15rem 0; @(x.EsProximo ? "background: rgba(255,255,255,.12); border-radius:6px; padding:2px 6px;" : "")">
<b>@x.Hora</b> - @x.LineaTxt
class="stop-schedule-row @(x.EsProximo ? "stop-schedule-row--next" : "")">
<span class="stop-schedule-row__text">
<b>@x.Hora</b> - @x.LineaTxt
</span>
<button type="button"
class="stop-itinerary-toggle @(itinerarioPintado ? "is-active" : "")"
title="@(itinerarioPintado ? "Ocultar itinerario" : "Ver itinerario")"
aria-label="@(itinerarioPintado ? "Ocultar itinerario" : "Ver itinerario")"
aria-pressed="@itinerarioPintado"
@onclick="() => ToggleItinerarioSalidaParadaAsync(x)">
<span aria-hidden="true">👁</span>
</button>
</li>
}
</ul>
@@ -1085,7 +1098,7 @@
<div class="history-panel-body">
<label class="history-parameters-toggle @(_usarParametrosActualesHistorial ? "history-parameters-toggle--active" : "")"
title="Recalcular las rutas del historial con la configuración actual">
<span class="history-parameters-toggle__label">Usar parámetros actuales</span>
<span class="history-parameters-toggle__label">Usar configuración actual</span>
<input type="checkbox"
checked="@_usarParametrosActualesHistorial"
disabled="@(_aplicandoRegistroHistorial || _cambiandoParametrosHistorial)"
@@ -1769,7 +1782,7 @@
title="Configurar Pendientes"
aria-label="Configurar Pendientes"
disabled="@_pintandoElevacionesRuta"
@onclick="() => _mostrarConfiguracionPendientesRuta = true">
@onclick="AbrirConfiguracionPendientesRuta">
<span aria-hidden="true">⚙</span>
</button>
</div>
@@ -1808,7 +1821,7 @@
@if (_mostrarConfiguracionPendientesRuta)
{
<div class="custom-modal-backdrop pendientes-config-modal-backdrop"
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
@onclick="CerrarConfiguracionPendientesRuta">
<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>
@@ -1816,7 +1829,7 @@
class="config-close-btn pendientes-config-modal__close"
title="Cerrar"
aria-label="Cerrar"
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
@onclick="CerrarConfiguracionPendientesRuta">
×
</button>
</div>
@@ -1844,14 +1857,21 @@
<div class="pendientes-config-modal__field">
<span class="pendientes-config-modal__threshold-label">
<input type="color"
class="pendientes-config-modal__swatch"
value="@_colorPendienteSubidaSuaveRuta"
title="Elegir color de subida"
aria-label="Elegir color de subida"
disabled="@_pintandoElevacionesRuta"
@onchange="@(e => CambiarColorPendienteSubidaRutaAsync(e, NivelColorSubidaRuta.Suave))" />
<button type="button"
class="pendientes-config-modal__swatch"
style="background-color: @_colorPendienteSubidaSuaveRuta"
title="Cambiar color de subida"
aria-label="Cambiar color de subida"
disabled="@_pintandoElevacionesRuta"
@onclick="() => AlternarSelectorColorPendienteRuta(NivelColorSubidaRuta.Suave)">
</button>
<span>Umbral Subida (%)</span>
@if (_selectorColorPendienteRuta == NivelColorSubidaRuta.Suave)
{
<SelectorColorSimple Color="@_colorPendienteSubidaSuaveRuta"
Disabled="@_pintandoElevacionesRuta"
ColorChanged="@(color => AplicarColorPendienteSubidaRutaAsync(color, NivelColorSubidaRuta.Suave))" />
}
</span>
<input type="text"
class="config-input"
@@ -1864,14 +1884,21 @@
<div class="pendientes-config-modal__field">
<span class="pendientes-config-modal__threshold-label">
<input type="color"
class="pendientes-config-modal__swatch"
value="@_colorPendienteSubidaMediaRuta"
title="Elegir color de subida media"
aria-label="Elegir color de subida media"
disabled="@_pintandoElevacionesRuta"
@onchange="@(e => CambiarColorPendienteSubidaRutaAsync(e, NivelColorSubidaRuta.Media))" />
<button type="button"
class="pendientes-config-modal__swatch"
style="background-color: @_colorPendienteSubidaMediaRuta"
title="Cambiar color de subida media"
aria-label="Cambiar color de subida media"
disabled="@_pintandoElevacionesRuta"
@onclick="() => AlternarSelectorColorPendienteRuta(NivelColorSubidaRuta.Media)">
</button>
<span>Subida Media (%)</span>
@if (_selectorColorPendienteRuta == NivelColorSubidaRuta.Media)
{
<SelectorColorSimple Color="@_colorPendienteSubidaMediaRuta"
Disabled="@_pintandoElevacionesRuta"
ColorChanged="@(color => AplicarColorPendienteSubidaRutaAsync(color, NivelColorSubidaRuta.Media))" />
}
</span>
<input type="text"
class="config-input"
@@ -1884,14 +1911,21 @@
<div class="pendientes-config-modal__field">
<span class="pendientes-config-modal__threshold-label">
<input type="color"
class="pendientes-config-modal__swatch"
value="@_colorPendienteSubidaFuerteRuta"
title="Elegir color de subida fuerte"
aria-label="Elegir color de subida fuerte"
disabled="@_pintandoElevacionesRuta"
@onchange="@(e => CambiarColorPendienteSubidaRutaAsync(e, NivelColorSubidaRuta.Fuerte))" />
<button type="button"
class="pendientes-config-modal__swatch"
style="background-color: @_colorPendienteSubidaFuerteRuta"
title="Cambiar color de subida fuerte"
aria-label="Cambiar color de subida fuerte"
disabled="@_pintandoElevacionesRuta"
@onclick="() => AlternarSelectorColorPendienteRuta(NivelColorSubidaRuta.Fuerte)">
</button>
<span>Subida Fuerte (%)</span>
@if (_selectorColorPendienteRuta == NivelColorSubidaRuta.Fuerte)
{
<SelectorColorSimple Color="@_colorPendienteSubidaFuerteRuta"
Disabled="@_pintandoElevacionesRuta"
ColorChanged="@(color => AplicarColorPendienteSubidaRutaAsync(color, NivelColorSubidaRuta.Fuerte))" />
}
</span>
<input type="text"
class="config-input"

View File

@@ -0,0 +1,58 @@
<div class="pendientes-color-picker" @onclick:stopPropagation="true">
<div class="pendientes-color-picker__title">Colores básicos</div>
<div class="pendientes-color-picker__presets">
@foreach (var color in ColoresPredefinidos)
{
var seleccionado = string.Equals(Color, color.Hex, StringComparison.OrdinalIgnoreCase);
<button type="button"
class="pendientes-color-picker__preset @(seleccionado ? "is-selected" : null)"
style="background-color: @color.Hex"
title="@color.Nombre"
aria-label="Usar color @color.Nombre"
disabled="@Disabled"
@onclick="() => SeleccionarColorAsync(color.Hex)">
</button>
}
</div>
<label class="pendientes-color-picker__advanced">
<span>Color exacto</span>
<input type="color"
value="@Color"
aria-label="Elegir color exacto"
disabled="@Disabled"
@onchange="CambiarColorExactoAsync" />
</label>
</div>
@code {
private static readonly (string Nombre, string Hex)[] ColoresPredefinidos =
[
("Verde", "#16a34a"),
("Amarillo", "#eab308"),
("Naranja", "#f97316"),
("Rojo", "#dc2626"),
("Rosa", "#ec4899"),
("Violeta", "#7c3aed"),
("Azul", "#2563eb"),
("Cian", "#0891b2"),
("Gris", "#64748b"),
("Negro", "#000000")
];
[Parameter]
public string Color { get; set; } = "#000000";
[Parameter]
public bool Disabled { get; set; }
[Parameter]
public EventCallback<string> ColorChanged { get; set; }
private Task SeleccionarColorAsync(string color)
=> ColorChanged.InvokeAsync(color);
private Task CambiarColorExactoAsync(ChangeEventArgs e)
=> ColorChanged.InvokeAsync(Convert.ToString(e.Value) ?? Color);
}