feat: page-based navigation shell + Tests page wizard

Replace the monolithic MainWindow with a SelectedPage-driven shell
(Dashboard / Pump / Bench / Tests / Results / Settings). The Tests
page gets the Plan -> Preconditions -> Running -> Done wizard from
ui-structure.md \u00a74, backed by a 7-item precondition gate and
shared sub-views (PhaseCardView / TestSectionView / GraphicIndicatorView)
extracted from the now-deleted monolithic TestPanelView.

New VMs / views:
- Tests wizard: TestPreconditions, PhaseCard, GraphicIndicator,
  TestSection, TestPlan, TestRunning, TestDone
- Dashboard panels: DashboardConnection, DashboardReadings,
  DashboardAlarms, InterlockBanner, ResultHistory
- Pump / bench panels: PumpIdentificationPanel, PumpLiveData,
  UnlockPanel, BenchDriveControl, BenchReadings, RelayBank,
  TemperatureControl, DtcList, AuthGate
- Dialogs: generic ConfirmDialog, UserManageDialog, UserPromptDialog

Supporting changes:
- IsOilPumpOn exposed on MainViewModel for precondition evaluation
- RequiresAuth added to TestDefinition (XML round-trip)
- BipStatusDefinition + CompletedTestRun models
- ~35 new Test.* localization keys (en + es)
- Settings moved from modal dialog to full page
- Pause / Retry / Skip stubs in TestRunningView; full spec in
  docs/gap-test-running-controls.md for follow-up implementation
- docs/ui-structure.md captures the wizard design

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 13:11:34 +02:00
parent 37d099cdbd
commit 0280a2fad1
110 changed files with 8008 additions and 1115 deletions

View File

@@ -8,10 +8,39 @@
<!-- ── App ──────────────────────────────────────────────────────────── -->
<sys:String x:Key="App.Title">HC_APTBS — Herlic Banco de Pruebas</sys:String>
<sys:String x:Key="App.LanguageLabel">ENG</sys:String>
<!-- ── Menu ─────────────────────────────────────────────────────────── -->
<sys:String x:Key="Menu.Settings">Configuración</sys:String>
<!-- ── Navigation rail ──────────────────────────────────────────────── -->
<sys:String x:Key="Nav.Dashboard">Panel</sys:String>
<sys:String x:Key="Nav.Bench">Banco</sys:String>
<sys:String x:Key="Nav.Pump">Bomba</sys:String>
<sys:String x:Key="Nav.Tests">Pruebas</sys:String>
<sys:String x:Key="Nav.Results">Resultados</sys:String>
<sys:String x:Key="Nav.Settings">Configuración</sys:String>
<!-- ── Página de panel ──────────────────────────────────────────────── -->
<sys:String x:Key="Dashboard.Readings">Lecturas del banco</sys:String>
<sys:String x:Key="Dashboard.Connections">Conexiones</sys:String>
<sys:String x:Key="Dashboard.Conn.Can">Bus CAN</sys:String>
<sys:String x:Key="Dashboard.Conn.Bench">Controlador del banco</sys:String>
<sys:String x:Key="Dashboard.Conn.Pump">ECU de la bomba</sys:String>
<sys:String x:Key="Dashboard.Conn.KLine">Sesión K-Line</sys:String>
<sys:String x:Key="Dashboard.StateOnline">EN LÍNEA</sys:String>
<sys:String x:Key="Dashboard.StateOffline">FUERA DE LÍNEA</sys:String>
<sys:String x:Key="Dashboard.StateOpen">ABIERTA</sys:String>
<sys:String x:Key="Dashboard.StateClosed">CERRADA</sys:String>
<sys:String x:Key="Dashboard.StateFailed">FALLO</sys:String>
<sys:String x:Key="Dashboard.Alarms">Alarmas activas</sys:String>
<sys:String x:Key="Dashboard.AlarmsNone">Sistema OK — sin alarmas activas</sys:String>
<sys:String x:Key="Dashboard.TestSummary">Resumen de prueba</sys:String>
<sys:String x:Key="Dashboard.TestActive">Activa:</sys:String>
<sys:String x:Key="Dashboard.TestPhase">Fase:</sys:String>
<sys:String x:Key="Dashboard.NoTestRunning">No hay ninguna prueba en curso.</sys:String>
<sys:String x:Key="Dashboard.LastTestPass">Última prueba: APROBADA</sys:String>
<sys:String x:Key="Dashboard.LastTestFail">Última prueba: FALLIDA</sys:String>
<sys:String x:Key="Dashboard.Action.StartTest">Iniciar prueba</sys:String>
<sys:String x:Key="Dashboard.Action.StartTest.Tip">Requiere una bomba seleccionada y conexión CAN abierta.</sys:String>
<sys:String x:Key="Dashboard.Action.Stop">Detener</sys:String>
<sys:String x:Key="Dashboard.Action.EmergencyStop">PARADA DE EMERGENCIA</sys:String>
<!-- ── Status bar / connection indicators ───────────────────────────── -->
<sys:String x:Key="Status.Label">Estado:</sys:String>
@@ -24,7 +53,6 @@
<sys:String x:Key="Status.Disconnected">Desconectado</sys:String>
<!-- ── Bench section ────────────────────────────────────────────────── -->
<sys:String x:Key="Bench.Header">Banco</sys:String>
<sys:String x:Key="Bench.ConnectCan">Conectar CAN</sys:String>
<sys:String x:Key="Bench.DisconnectCan">Desconectar CAN</sys:String>
<sys:String x:Key="Bench.Rpm">rpm</sys:String>
@@ -64,6 +92,40 @@
<sys:String x:Key="Bench.Electronic">Electrónico</sys:String>
<sys:String x:Key="Bench.DepositCooler">Enfriador Depósito</sys:String>
<sys:String x:Key="Bench.DepositHeater">Calentador Depósito</sys:String>
<sys:String x:Key="Bench.TinCooler">Enfriador T-in</sys:String>
<sys:String x:Key="Bench.Flasher">Intermitente</sys:String>
<sys:String x:Key="Bench.Pulse4Signal">Señal 4-Pulsos</sys:String>
<sys:String x:Key="Bench.RelayOn">ON</sys:String>
<sys:String x:Key="Bench.RelayOff">OFF</sys:String>
<sys:String x:Key="Bench.TempControl">Temperatura</sys:String>
<sys:String x:Key="Bench.Setpoint">Consigna</sys:String>
<sys:String x:Key="Bench.Tolerance">Tolerancia</sys:String>
<sys:String x:Key="Bench.ApplySetpoint">Aplicar consigna</sys:String>
<!-- ── Pump page sub-nav / banners ──────────────────────────────────── -->
<sys:String x:Key="PumpSub.Identification">Identificación</sys:String>
<sys:String x:Key="PumpSub.Dtcs">Códigos de Falla</sys:String>
<sys:String x:Key="PumpSub.LiveData">Datos en Vivo</sys:String>
<sys:String x:Key="PumpSub.Adaptation">Adaptación</sys:String>
<sys:String x:Key="PumpSub.Unlock">Desbloqueo Immo</sys:String>
<sys:String x:Key="Pump.KLineClosed">La sesión K-Line no está abierta. Identifique una bomba para iniciar el diagnóstico.</sys:String>
<sys:String x:Key="Pump.NoPumpSelected">Seleccione una bomba en la pestaña Identificación para habilitar el diagnóstico.</sys:String>
<sys:String x:Key="Pump.NoUnlockActive">No hay ningún desbloqueo de inmovilizador en curso para esta bomba.</sys:String>
<sys:String x:Key="PumpLive.Engineering">Valores de ingeniería (en bruto)</sys:String>
<!-- ── DTC list ─────────────────────────────────────────────────────── -->
<sys:String x:Key="Dtc.Read">Leer DTC</sys:String>
<sys:String x:Key="Dtc.Clear">Borrar DTC</sys:String>
<sys:String x:Key="Dtc.NoFaults">La ECU no reportó códigos de falla.</sys:String>
<sys:String x:Key="Dtc.LastRead">Última lectura:</sys:String>
<sys:String x:Key="Dtc.Cleared">Códigos de falla borrados.</sys:String>
<!-- ── Auth gate ────────────────────────────────────────────────────── -->
<sys:String x:Key="AuthGate.LockedTitle">Se requiere autenticación</sys:String>
<sys:String x:Key="AuthGate.LockedMessage">Esta sección puede modificar parámetros de la bomba. Inicie sesión para desbloquearla.</sys:String>
<sys:String x:Key="AuthGate.Authenticate">Autenticar…</sys:String>
<sys:String x:Key="AuthGate.UnlockedAs">Desbloqueado como</sys:String>
<sys:String x:Key="AuthGate.Lock">Bloquear</sys:String>
<!-- ── Pump live data ───────────────────────────────────────────────── -->
<sys:String x:Key="Pump.THyb">T-hyb</sys:String>
@@ -135,6 +197,56 @@
<sys:String x:Key="Test.Started">Test iniciado...</sys:String>
<sys:String x:Key="Test.Stopped">Test detenido.</sys:String>
<!-- ── Asistente de la página de tests ────────────────────────────────── -->
<sys:String x:Key="Test.Wizard.Plan">1. Planificar</sys:String>
<sys:String x:Key="Test.Wizard.Preconditions">2. Precondiciones</sys:String>
<sys:String x:Key="Test.Wizard.Running">3. En ejecución</sys:String>
<sys:String x:Key="Test.Wizard.Done">4. Finalizado</sys:String>
<sys:String x:Key="Test.Wizard.Next">Siguiente ▶</sys:String>
<sys:String x:Key="Test.Wizard.Back">◀ Atrás</sys:String>
<!-- ── Checklist de precondiciones ────────────────────────────────────── -->
<sys:String x:Key="Test.Precheck.Title">Precondiciones</sys:String>
<sys:String x:Key="Test.Precheck.PumpSelected">Bomba seleccionada</sys:String>
<sys:String x:Key="Test.Precheck.CanLive">Bus CAN activo</sys:String>
<sys:String x:Key="Test.Precheck.KLineOpen">Sesión K-Line abierta</sys:String>
<sys:String x:Key="Test.Precheck.RpmZero">RPM del banco a cero</sys:String>
<sys:String x:Key="Test.Precheck.OilPumpOn">Bomba de aceite en marcha</sys:String>
<sys:String x:Key="Test.Precheck.NoCriticalAlarms">Sin alarmas críticas</sys:String>
<sys:String x:Key="Test.Precheck.UserAuth">Usuario autenticado</sys:String>
<sys:String x:Key="Test.Precheck.FixButton">Corregir</sys:String>
<sys:String x:Key="Test.Precheck.Ready">Todas las precondiciones cumplidas. Listo para comenzar.</sys:String>
<sys:String x:Key="Test.Precheck.NotReady">Resuelva los elementos anteriores antes de comenzar.</sys:String>
<sys:String x:Key="Test.Precheck.AuthBanner">Este test requiere autenticación del operador.</sys:String>
<sys:String x:Key="Test.Precheck.AuthButton">Autenticar…</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.SelectPump">Vaya a la página de Bomba para seleccionar una.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.CheckCan">Abra el Dashboard para verificar la conexión CAN.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.OpenKLine">Abra una sesión K-Line en la página Bomba.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.StopBench">Detenga el banco desde la página Banco.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.StartOilPump">Arranque la bomba de aceite desde la página Banco.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.ClearAlarms">Elimine las alarmas críticas en el Dashboard.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.Authenticate">Autentique al operador arriba.</sys:String>
<!-- ── Controles del paso En ejecución ────────────────────────────────── -->
<sys:String x:Key="Test.Running.Abort">■ Abortar</sys:String>
<sys:String x:Key="Test.Running.Pause">Pausar</sys:String>
<sys:String x:Key="Test.Running.Resume">Reanudar</sys:String>
<sys:String x:Key="Test.Running.Retry">Reintentar fase</sys:String>
<sys:String x:Key="Test.Running.Skip">Saltar fase</sys:String>
<sys:String x:Key="Test.Running.ComingSoon">Próximamente</sys:String>
<!-- ── Paso Finalizado ────────────────────────────────────────────────── -->
<sys:String x:Key="Test.Done.Passed">APROBADO</sys:String>
<sys:String x:Key="Test.Done.Failed">FALLIDO</sys:String>
<sys:String x:Key="Test.Done.ViewFullResults">Ver resultados completos</sys:String>
<sys:String x:Key="Test.Done.RunAgain">Ejecutar de nuevo</sys:String>
<!-- ── Confirmación de aborto ─────────────────────────────────────────── -->
<sys:String x:Key="Test.Abort.Title">¿Abortar el test?</sys:String>
<sys:String x:Key="Test.Abort.Message">Detener el test ahora finalizará la fase actual y devolverá resultados parciales. ¿Continuar?</sys:String>
<sys:String x:Key="Test.Abort.Confirm">Abortar</sys:String>
<sys:String x:Key="Test.Abort.Cancel">Seguir ejecutando</sys:String>
<!-- ── Test types ───────────────────────────────────────────────────── -->
<sys:String x:Key="TestType.Warmup">Calentamiento</sys:String>
<sys:String x:Key="TestType.Adjustment">Ajuste</sys:String>
@@ -152,6 +264,19 @@
<sys:String x:Key="Result.ResultHeader">Resultado</sys:String>
<sys:String x:Key="Result.AllTests">Todos los Tests</sys:String>
<!-- ── Página de resultados (§5) ────────────────────────────────────── -->
<sys:String x:Key="Results.PageTitle">Resultados</sys:String>
<sys:String x:Key="Results.HistoryHeader">Historial de pruebas (sesión)</sys:String>
<sys:String x:Key="Results.DetailHeader">Detalle de la prueba</sys:String>
<sys:String x:Key="Results.ObservationsLabel">Observaciones / Notas</sys:String>
<sys:String x:Key="Results.ExportButton">Exportar PDF…</sys:String>
<sys:String x:Key="Results.EmptyState">Aún no hay pruebas completadas. Finalice una prueba para verla aquí.</sys:String>
<sys:String x:Key="Results.NoSelection">Seleccione una prueba en la lista para ver sus detalles.</sys:String>
<sys:String x:Key="Results.InterruptedLabel">INTERRUMPIDA</sys:String>
<sys:String x:Key="Results.DeleteTooltip">Eliminar esta entrada</sys:String>
<sys:String x:Key="Results.ClearSessionButton">Limpiar sesión</sys:String>
<sys:String x:Key="Results.ClearSessionConfirm">¿Eliminar todas las entradas del historial de esta sesión?</sys:String>
<!-- ── Common strings ───────────────────────────────────────────────── -->
<sys:String x:Key="Common.Pass">APROBADO</sys:String>
<sys:String x:Key="Common.Fail">REPROBADO</sys:String>
@@ -225,7 +350,7 @@
<sys:String x:Key="Dialog.Settings.Tab.Safety">Seguridad</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.Pid">PID</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.Motor">Motor</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.Company">Empresa</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.Report">Reporte</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.KLine">K-Line</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.Advanced">Avanzado</sys:String>
@@ -272,6 +397,29 @@
<sys:String x:Key="Dialog.Settings.BlinkInterval">Intervalo parpadeo (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.FlasherInterval">Intervalo flasher (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.UsersHeader">Usuarios</sys:String>
<sys:String x:Key="Dialog.Settings.ManageUsers">Administrar Usuarios...</sys:String>
<!-- ── Dialog: Gestión de usuarios ──────────────────────────────────── -->
<sys:String x:Key="Dialog.UserManage.Title">Gestión de Usuarios</sys:String>
<sys:String x:Key="Dialog.UserManage.ColumnUsername">Usuario</sys:String>
<sys:String x:Key="Dialog.UserManage.Add">Añadir...</sys:String>
<sys:String x:Key="Dialog.UserManage.Remove">Eliminar</sys:String>
<sys:String x:Key="Dialog.UserManage.ChangePassword">Cambiar Contraseña...</sys:String>
<sys:String x:Key="Dialog.UserManage.Close">Cerrar</sys:String>
<sys:String x:Key="Dialog.UserManage.Prompt.AddTitle">Añadir Usuario</sys:String>
<sys:String x:Key="Dialog.UserManage.Prompt.ChangeTitle">Cambiar contraseña de '{0}'</sys:String>
<sys:String x:Key="Dialog.UserManage.Confirm.RemoveTitle">Eliminar Usuario</sys:String>
<sys:String x:Key="Dialog.UserManage.Confirm.Remove">¿Eliminar el usuario '{0}'?</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.EmptyTitle">Entrada Inválida</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.Empty">El usuario y la contraseña no pueden estar vacíos.</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.InvalidCharsTitle">Entrada Inválida</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.InvalidChars">El nombre de usuario no puede contener ':' o ','.</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.DuplicateTitle">Usuario Duplicado</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.Duplicate">Ya existe un usuario con ese nombre.</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.LastUserTitle">No Se Puede Eliminar</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.LastUser">No se puede eliminar el último usuario restante.</sys:String>
<!-- ── Error messages ───────────────────────────────────────────────── -->
<sys:String x:Key="Error.ReportGeneration">Error al generar informe:\n{0}</sys:String>
<sys:String x:Key="Error.ReportTitle">Error de Informe</sys:String>
@@ -318,5 +466,6 @@
<sys:String x:Key="Pdf.ErrorBits"> ⚠ Bits de error: {0}</sys:String>
<sys:String x:Key="Pdf.NoSampleData">No hay datos de muestra disponibles para visualización gráfica.</sys:String>
<sys:String x:Key="Pdf.ChartSamples">Muestras: {0} | Objetivo: {1} ± {2} | Promedio: {3} | Resultado: {4}</sys:String>
<sys:String x:Key="Pdf.Observations">OBSERVACIONES</sys:String>
</ResourceDictionary>