v20260917 - actual se sustituya y original metido en el modal de derivadas

This commit is contained in:
2026-09-18 13:43:04 +02:00
parent f63a1b8b08
commit 19377e07c2
16 changed files with 1616 additions and 356 deletions

View File

@@ -5,9 +5,13 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;
using RutasDBUS.Components.Pages;
using RutasDBUS.Modelos.Historial;
using RutasDBUS.Modelos.Importacion;
using RutasDBUS.Modelos.Planificacion;
using RutasDBUS.Servicios;
using RutasDBUS.Servicios.Caminata;
using RutasDBUS.Servicios.Configuracion;
using RutasDBUS.Servicios.Horarios;
using RutasDBUS.Servicios.Importacion;
using RutasDBUS.Servicios.Planificacion;
static void Check(bool result, string message)
@@ -20,6 +24,213 @@ var baseMethod = typeof(PlanificadorRutas).GetMethod("CrearConfiguracionBaseHist
var defaults = (ConfiguracionHistorialRuta)baseMethod.Invoke(null, null)!;
Check(defaults.PesoTransbordoRankingMinutos == 6, "Penalizacion inicial de 6 minutos");
var calendariosTaxibus = new List<FilaCalendarioCsv>
{
new("LAB_BASE", 2, 2, 2, 2, 2, 1, 1, new DateOnly(2026, 6, 1), new DateOnly(2026, 12, 31)),
new("FEST_BASE", 1, 1, 1, 1, 1, 1, 2, new DateOnly(2026, 6, 1), new DateOnly(2026, 12, 31))
};
var excepcionesTaxibus = new List<FilaCalendarioExcepcionCsv>
{
new("FEST_BASE", new DateOnly(2026, 10, 12), 1)
};
var rutasTaxibus = new List<FilaRutaCsv>
{
new("6", "TB6", "Ulia", "Ulia", "4", "", "94AF5D"),
new("7", "TB7", "Atotxaerreka", "Atotxaerreka", "4", "", "63C600")
};
var expedicionesTaxibus = new List<FilaExpedicionCsv>
{
new("TB6_I", "6", "N.A.", 1, "6", "60101", null, "0000"),
new("TB6_V", "6", "N.A.", 2, "6", "60102", null, "0000"),
new("TB7_701_I", "7", "N.A.", 1, "7", "70101", null, "0000"),
new("TB7_701_V", "7", "N.A.", 2, "7", "70102", null, "0000"),
new("TB7_702_I", "7", "N.A.", 1, "7", "70201", null, "0000"),
new("TB7_702_V", "7", "N.A.", 2, "7", "70202", null, "0000")
};
var tiemposTaxibus = new List<FilaTiempoParadaCsv>
{
new("TB6_I", TimeOnly.MinValue, TimeOnly.MinValue, 1000, 372, 1, "", "", ""),
new("TB6_I", TimeOnly.MinValue, TimeOnly.MinValue, 1000, 346, 2, "", "", ""),
new("TB6_I", TimeOnly.MinValue, TimeOnly.MinValue, 0, 521, 3, "", "", ""),
new("TB6_V", TimeOnly.MinValue, TimeOnly.MinValue, 1000, 521, 1, "", "", ""),
new("TB6_V", TimeOnly.MinValue, TimeOnly.MinValue, 1000, 457, 2, "", "", ""),
new("TB6_V", TimeOnly.MinValue, TimeOnly.MinValue, 0, 372, 3, "", "", ""),
new("TB7_701_I", TimeOnly.MinValue, TimeOnly.MinValue, 800, 240, 1, "", "", ""),
new("TB7_701_I", TimeOnly.MinValue, TimeOnly.MinValue, 300, 617, 2, "", "", ""),
new("TB7_701_I", TimeOnly.MinValue, TimeOnly.MinValue, 400, 620, 3, "", "", ""),
new("TB7_701_I", TimeOnly.MinValue, TimeOnly.MinValue, 0, 599, 4, "", "", ""),
new("TB7_701_V", TimeOnly.MinValue, TimeOnly.MinValue, 200, 599, 1, "", "", ""),
new("TB7_701_V", TimeOnly.MinValue, TimeOnly.MinValue, 1300, 343, 2, "", "", ""),
new("TB7_701_V", TimeOnly.MinValue, TimeOnly.MinValue, 0, 42, 3, "", "", ""),
new("TB7_702_I", TimeOnly.MinValue, TimeOnly.MinValue, 2000, 42, 1, "", "", ""),
new("TB7_702_I", TimeOnly.MinValue, TimeOnly.MinValue, 0, 625, 2, "", "", ""),
new("TB7_702_V", TimeOnly.MinValue, TimeOnly.MinValue, 1500, 625, 1, "", "", ""),
new("TB7_702_V", TimeOnly.MinValue, TimeOnly.MinValue, 0, 240, 2, "", "", "")
};
var integrarTaxibus = typeof(TaxibusDbus).Assembly
.GetType("RutasDBUS.Servicios.Importacion.HorariosOficialesTaxibus")!
.GetMethod("AplicarSiFaltanHorarios", BindingFlags.Static | BindingFlags.Public)!;
var rutasTaxibusIntegradas = (IReadOnlySet<string>)integrarTaxibus.Invoke(
null,
new object[]
{
calendariosTaxibus,
excepcionesTaxibus,
expedicionesTaxibus,
rutasTaxibus,
tiemposTaxibus
})!;
Check(rutasTaxibusIntegradas.SetEquals(new[] { "6", "7" }),
"TB6 y TB7 reciben sus horarios oficiales cuando faltan en el ZIP");
Check(expedicionesTaxibus.Any(expedicion =>
expedicion.IdRuta == "6" && expedicion.HSalida == new TimeOnly(7, 0)) &&
expedicionesTaxibus.Any(expedicion =>
expedicion.IdRuta == "6" && expedicion.HSalida == new TimeOnly(7, 40)),
"TB6 sale a las horas oficiales desde Colon y Albergue");
var salidaTb6Colon = expedicionesTaxibus.Single(expedicion =>
expedicion.IdRuta == "6" &&
expedicion.IdPatronExp == "60101" &&
expedicion.HSalida == new TimeOnly(7, 0));
Check(tiemposTaxibus.Any(tiempo =>
tiempo.IdExp == salidaTb6Colon.Id &&
tiempo.IdTiempoPar == 346 &&
tiempo.HoraSalida == new TimeOnly(7, 10)) &&
tiemposTaxibus.Any(tiempo =>
tiempo.IdExp == salidaTb6Colon.Id &&
tiempo.IdTiempoPar == 521 &&
tiempo.HoraSalida == new TimeOnly(7, 20)) &&
expedicionesTaxibus.Any(expedicion =>
expedicion.IdPatronExp == "60101" &&
expedicion.HSalida == new TimeOnly(22, 0)),
"TB6 respeta los tiempos publicados y la salida adicional de verano");
var salidaTb7Laborable = expedicionesTaxibus.Single(expedicion =>
expedicion.IdRuta == "7" &&
expedicion.IdPatronExp == "70101" &&
expedicion.IdCal.EndsWith("LAB", StringComparison.Ordinal) &&
expedicion.HSalida == new TimeOnly(7, 15));
Check(tiemposTaxibus.Any(tiempo =>
tiempo.IdExp == salidaTb7Laborable.Id &&
tiempo.IdTiempoPar == 617 &&
tiempo.HoraSalida == new TimeOnly(7, 23)) &&
tiemposTaxibus.Any(tiempo =>
tiempo.IdExp == salidaTb7Laborable.Id &&
tiempo.IdTiempoPar == 599 &&
tiempo.HoraSalida == new TimeOnly(7, 30)),
"TB7 respeta las horas oficiales de Antiguo, Atotxaerreka y Anorga");
var salidaTb7Anorga = expedicionesTaxibus.Single(expedicion =>
expedicion.IdPatronExp == "70102" &&
expedicion.IdCal.EndsWith("LAB", StringComparison.Ordinal) &&
expedicion.HSalida == new TimeOnly(7, 30));
var salidaTb7Faro = expedicionesTaxibus.Single(expedicion =>
expedicion.IdPatronExp == "70201" &&
expedicion.IdCal.EndsWith("LAB", StringComparison.Ordinal) &&
expedicion.HSalida == new TimeOnly(7, 45));
var vueltaTb7Faro = expedicionesTaxibus.Single(expedicion =>
expedicion.IdPatronExp == "70202" &&
expedicion.IdCal.EndsWith("LAB", StringComparison.Ordinal) &&
expedicion.HSalida == new TimeOnly(8, 5));
Check(tiemposTaxibus.Any(tiempo =>
tiempo.IdExp == salidaTb7Anorga.Id &&
tiempo.IdTiempoPar == 42 &&
tiempo.HoraSalida == new TimeOnly(7, 45)) &&
tiemposTaxibus.Any(tiempo =>
tiempo.IdExp == salidaTb7Faro.Id &&
tiempo.IdTiempoPar == 625 &&
tiempo.HoraSalida == new TimeOnly(8, 5)) &&
tiemposTaxibus.Any(tiempo =>
tiempo.IdExp == vueltaTb7Faro.Id &&
tiempo.IdTiempoPar == 240 &&
tiempo.HoraSalida == new TimeOnly(8, 15)),
"TB7 mantiene encadenado el ciclo Anorga, Antiguo y Paseo del Faro");
Check(expedicionesTaxibus.Any(expedicion =>
expedicion.IdPatronExp == "70101" &&
expedicion.IdCal.EndsWith("SAB", StringComparison.Ordinal) &&
expedicion.HSalida == new TimeOnly(8, 15)) &&
expedicionesTaxibus.Any(expedicion =>
expedicion.IdPatronExp == "70101" &&
expedicion.IdCal.EndsWith("FEST", StringComparison.Ordinal) &&
expedicion.HSalida == new TimeOnly(9, 15)),
"TB7 diferencia laborables, sabados y festivos");
Check(excepcionesTaxibus.Any(excepcion =>
excepcion.Id.EndsWith("LAB", StringComparison.Ordinal) &&
excepcion.FechaEx == new DateOnly(2026, 10, 12) &&
excepcion.TipoExcepcion == 2) &&
excepcionesTaxibus.Any(excepcion =>
excepcion.Id.EndsWith("FEST", StringComparison.Ordinal) &&
excepcion.FechaEx == new DateOnly(2026, 10, 12) &&
excepcion.TipoExcepcion == 1),
"Los festivos entre semana usan el horario festivo de TB7");
var salidaVeranoAtotxaerreka = expedicionesTaxibus.Single(expedicion =>
expedicion.IdPatronExp == "70101" &&
expedicion.HSalida == new TimeOnly(22, 15));
Check(tiemposTaxibus.Where(tiempo => tiempo.IdExp == salidaVeranoAtotxaerreka.Id)
.OrderBy(tiempo => tiempo.SecPar)
.Last().IdTiempoPar == 620 &&
expedicionesTaxibus.Any(expedicion =>
expedicion.IdPatronExp == "70201" &&
expedicion.HSalida == new TimeOnly(21, 45)),
"TB7 incluye las ampliaciones oficiales de verano sin prolongar el viaje de Atotxaerreka");
var expedicionesAnalisisTramos = new List<FilaExpedicionCsv>
{
new("E1", "28", "Amara", 1, "28", "280101", new TimeOnly(8, 0), "LAB"),
new("E2", "28", "Amara", 1, "28", "280101", new TimeOnly(9, 0), "LAB"),
new("E3", "28", "Amara", 1, "28", "280101", new TimeOnly(10, 0), "LAB"),
new("E4", "28", "Amara", 1, "28", "280101", new TimeOnly(11, 0), "LAB"),
new("PLANTILLA", "28", "Amara", 1, "28", "280101", null, "LAB"),
new("OTRA_VARIANTE", "28", "Boulevard", 2, "28", "280102", new TimeOnly(8, 0), "LAB")
};
var tiemposAnalisisTramos = new List<FilaTiempoParadaCsv>
{
new("E1", new TimeOnly(8, 0), new TimeOnly(8, 0), 100, 1, 1, "", "", ""),
new("E1", new TimeOnly(8, 2), new TimeOnly(8, 2), 100, 2, 2, "", "", ""),
new("E1", new TimeOnly(8, 3), new TimeOnly(8, 3), 0, 3, 3, "", "", ""),
new("E2", new TimeOnly(9, 0), new TimeOnly(9, 0), 100, 1, 1, "", "", ""),
new("E2", new TimeOnly(9, 2, 30), new TimeOnly(9, 2, 30), 100, 2, 2, "", "", ""),
new("E2", new TimeOnly(9, 3, 40), new TimeOnly(9, 3, 40), 0, 3, 3, "", "", ""),
new("E3", new TimeOnly(10, 0), new TimeOnly(10, 0), 100, 1, 1, "", "", ""),
new("E3", new TimeOnly(10, 2, 59), new TimeOnly(10, 2, 59), 100, 2, 2, "", "", ""),
new("E3", new TimeOnly(10, 6, 9), new TimeOnly(10, 6, 9), 0, 3, 3, "", "", ""),
new("E4", new TimeOnly(11, 0), new TimeOnly(11, 0), 100, 1, 1, "", "", ""),
new("E4", new TimeOnly(11, 4, 10), new TimeOnly(11, 4, 10), 100, 2, 2, "", "", ""),
new("E4", new TimeOnly(11, 5, 15), new TimeOnly(11, 5, 15), 0, 3, 3, "", "", "")
};
var archivoAnalisisTramos = new ArchivoImportacionDbus
{
Calendarios =
[
new FilaCalendarioCsv(
"LAB", 2, 2, 2, 2, 2, 1, 1,
new DateOnly(2026, 1, 1), new DateOnly(2026, 12, 31))
],
Expediciones = expedicionesAnalisisTramos,
Rutas = [new FilaRutaCsv("28", "28", "Amara-Ospitaleak", "", "4", "", "F39E00")],
Paradas =
[
new FilaParadaCsv(1, "101", "Origen", 0, 0, "", "", "", "", "", ""),
new FilaParadaCsv(2, "102", "Intermedia", 0, 0, "", "", "", "", "", ""),
new FilaParadaCsv(3, "103", "Destino", 0, 0, "", "", "", "", "", "")
],
TiemposParada = tiemposAnalisisTramos,
TiemposParadaPorExpedicion = tiemposAnalisisTramos
.GroupBy(x => x.IdExp, StringComparer.OrdinalIgnoreCase)
.ToDictionary(
g => g.Key,
g => g.OrderBy(x => x.SecPar).ToList(),
StringComparer.OrdinalIgnoreCase)
};
var servicioAnalisisTramos = new ServicioHorariosDbus(
new FakeArchivoImportacionDbus(archivoAnalisisTramos));
await servicioAnalisisTramos.AsegurarCargaAsync("");
var analisisTramos = servicioAnalisisTramos.AnalizarTramosItinerario(
"28", "28", 1, "I", 280101, new DateOnly(2026, 9, 17));
Check(analisisTramos.ExpedicionesAnalizadas == 4 && analisisTramos.Tramos.Count == 2,
"El analisis de tramos usa solo las expediciones activas de la variante exacta");
Check(analisisTramos.Tramos[0].Repeticiones == 3 &&
analisisTramos.Tramos[0].ExpedicionesAnalizadas == 4 &&
analisisTramos.Tramos[0].DuracionHabitualSegundos == 150,
"Los tiempos con hasta un minuto de diferencia forman la frecuencia dominante");
var panelSinCambios = new PlanificadorRutas();
var alternarPanel = typeof(PlanificadorRutas).GetMethod(
"ToggleConfigPanel",
@@ -261,10 +472,6 @@ typeof(PlanificadorRutas).GetField("_mostrarHistorialRutas", flags)!
.SetValue(paginaCierreHistorial, true);
typeof(PlanificadorRutas).GetField("_factorEquilibradoAndarBus", flags)!
.SetValue(paginaCierreHistorial, 99.0);
var configuracionAnteriorHistorial = fabricaHistorial.Clonar();
configuracionAnteriorHistorial.FactorEquilibradoAndarBus = 6;
typeof(PlanificadorRutas).GetField("_configuracionTemporalPreviaHistorial", flags)!
.SetValue(paginaCierreHistorial, configuracionAnteriorHistorial);
var alternarHistorial = typeof(PlanificadorRutas).GetMethod("ToggleHistorialPanel", flags)!;
await (Task)alternarHistorial.Invoke(paginaCierreHistorial, null)!;
Check(
@@ -386,6 +593,29 @@ sealed class FakeValhalla : IClienteValhalla
}
}
sealed class FakeArchivoImportacionDbus(ArchivoImportacionDbus archivo) : IServicioArchivoImportacionDbus
{
public Task<ArchivoImportacionDbus> ObtenerAsync(CancellationToken cancellationToken = default) =>
Task.FromResult(archivo);
public Task<IReadOnlyList<ModeloHorarioDbusInfo>> ListarModelosAsync(
CancellationToken cancellationToken = default) =>
Task.FromResult<IReadOnlyList<ModeloHorarioDbusInfo>>(Array.Empty<ModeloHorarioDbusInfo>());
public Task<ModeloHorarioDbusInfo?> ObtenerModeloActivoAsync(
CancellationToken cancellationToken = default) =>
Task.FromResult<ModeloHorarioDbusInfo?>(null);
public Task SeleccionarModeloAsync(string idModelo, CancellationToken cancellationToken = default) =>
Task.CompletedTask;
public Task<ModeloHorarioDbusInfo> GuardarModeloSubidoAsync(
string nombreArchivo,
Stream contenido,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
}
sealed class OsmWidthHandler : HttpMessageHandler
{
public int Calls { get; private set; }