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

75
Resources/NavStyles.xaml Normal file
View File

@@ -0,0 +1,75 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:HC_APTBS.Converters">
<!-- Shared converter: enum ↔ int for TabControl.SelectedIndex -->
<conv:EnumToIntConverter x:Key="EnumToInt"/>
<!-- Nav rail ListBoxItem: 56px tall, left accent bar on selection -->
<Style x:Key="NavItem" TargetType="ListBoxItem">
<Setter Property="Height" Value="56"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="Root" Background="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Accent" Grid.Column="0" Background="Transparent"/>
<ContentPresenter Grid.Column="1"
VerticalAlignment="Center"
Margin="16,0,0,0"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Root" Property="Background" Value="#FF3A4050"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Root" Property="Background" Value="#FF404860"/>
<Setter TargetName="Accent" Property="Background" Value="#FF2196F3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Nav rail ListBox -->
<Style x:Key="NavRail" TargetType="ListBox">
<Setter Property="Background" Value="#FF2F3440"/>
<Setter Property="Foreground" Value="#FFE6E6E6"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource NavItem}"/>
</Style>
<!-- TabControl that hides its tab strip so the nav rail is the only selector -->
<Style x:Key="HiddenTabsTabControl" TargetType="TabControl">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabControl">
<Grid>
<!-- TabPanel renders the headers; we collapse it entirely -->
<TabPanel x:Name="HeaderPanel" Visibility="Collapsed"
IsItemsHost="True"/>
<ContentPresenter x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -8,10 +8,39 @@
<!-- ── App ──────────────────────────────────────────────────────────── -->
<sys:String x:Key="App.Title">HC_APTBS — Herlic Test Bench</sys:String>
<sys:String x:Key="App.LanguageLabel">ESP</sys:String>
<!-- ── Menu ─────────────────────────────────────────────────────────── -->
<sys:String x:Key="Menu.Settings">Settings</sys:String>
<!-- ── Navigation rail ──────────────────────────────────────────────── -->
<sys:String x:Key="Nav.Dashboard">Dashboard</sys:String>
<sys:String x:Key="Nav.Bench">Bench</sys:String>
<sys:String x:Key="Nav.Pump">Pump</sys:String>
<sys:String x:Key="Nav.Tests">Tests</sys:String>
<sys:String x:Key="Nav.Results">Results</sys:String>
<sys:String x:Key="Nav.Settings">Settings</sys:String>
<!-- ── Dashboard page ───────────────────────────────────────────────── -->
<sys:String x:Key="Dashboard.Readings">Bench readings</sys:String>
<sys:String x:Key="Dashboard.Connections">Connections</sys:String>
<sys:String x:Key="Dashboard.Conn.Can">CAN bus</sys:String>
<sys:String x:Key="Dashboard.Conn.Bench">Bench controller</sys:String>
<sys:String x:Key="Dashboard.Conn.Pump">Pump ECU</sys:String>
<sys:String x:Key="Dashboard.Conn.KLine">K-Line session</sys:String>
<sys:String x:Key="Dashboard.StateOnline">ONLINE</sys:String>
<sys:String x:Key="Dashboard.StateOffline">OFFLINE</sys:String>
<sys:String x:Key="Dashboard.StateOpen">OPEN</sys:String>
<sys:String x:Key="Dashboard.StateClosed">CLOSED</sys:String>
<sys:String x:Key="Dashboard.StateFailed">FAILED</sys:String>
<sys:String x:Key="Dashboard.Alarms">Active alarms</sys:String>
<sys:String x:Key="Dashboard.AlarmsNone">System OK — no active alarms</sys:String>
<sys:String x:Key="Dashboard.TestSummary">Test summary</sys:String>
<sys:String x:Key="Dashboard.TestActive">Active:</sys:String>
<sys:String x:Key="Dashboard.TestPhase">Phase:</sys:String>
<sys:String x:Key="Dashboard.NoTestRunning">No test is currently running.</sys:String>
<sys:String x:Key="Dashboard.LastTestPass">Last test: PASS</sys:String>
<sys:String x:Key="Dashboard.LastTestFail">Last test: FAIL</sys:String>
<sys:String x:Key="Dashboard.Action.StartTest">Start Test</sys:String>
<sys:String x:Key="Dashboard.Action.StartTest.Tip">Requires a selected pump and an open CAN connection.</sys:String>
<sys:String x:Key="Dashboard.Action.Stop">Stop</sys:String>
<sys:String x:Key="Dashboard.Action.EmergencyStop">EMERGENCY STOP</sys:String>
<!-- ── Status bar / connection indicators ───────────────────────────── -->
<sys:String x:Key="Status.Label">Status:</sys:String>
@@ -24,7 +53,6 @@
<sys:String x:Key="Status.Disconnected">Disconnected</sys:String>
<!-- ── Bench section ────────────────────────────────────────────────── -->
<sys:String x:Key="Bench.Header">Bench</sys:String>
<sys:String x:Key="Bench.ConnectCan">Connect CAN</sys:String>
<sys:String x:Key="Bench.DisconnectCan">Disconnect CAN</sys:String>
<sys:String x:Key="Bench.Rpm">rpm</sys:String>
@@ -64,6 +92,40 @@
<sys:String x:Key="Bench.Electronic">Electronic</sys:String>
<sys:String x:Key="Bench.DepositCooler">Deposit Cooler</sys:String>
<sys:String x:Key="Bench.DepositHeater">Deposit Heater</sys:String>
<sys:String x:Key="Bench.TinCooler">T-in Cooler</sys:String>
<sys:String x:Key="Bench.Flasher">Flasher</sys:String>
<sys:String x:Key="Bench.Pulse4Signal">4-Pulse Signal</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">Temperature</sys:String>
<sys:String x:Key="Bench.Setpoint">Setpoint</sys:String>
<sys:String x:Key="Bench.Tolerance">Tolerance</sys:String>
<sys:String x:Key="Bench.ApplySetpoint">Apply setpoint</sys:String>
<!-- ── Pump page sub-nav / banners ──────────────────────────────────── -->
<sys:String x:Key="PumpSub.Identification">Identification</sys:String>
<sys:String x:Key="PumpSub.Dtcs">Fault Codes</sys:String>
<sys:String x:Key="PumpSub.LiveData">Live Data</sys:String>
<sys:String x:Key="PumpSub.Adaptation">Adaptation</sys:String>
<sys:String x:Key="PumpSub.Unlock">Immobilizer Unlock</sys:String>
<sys:String x:Key="Pump.KLineClosed">K-Line session is not open. Identify a pump to begin diagnostics.</sys:String>
<sys:String x:Key="Pump.NoPumpSelected">Select a pump on the Identification tab to enable diagnostics.</sys:String>
<sys:String x:Key="Pump.NoUnlockActive">No immobilizer unlock is currently in progress for this pump.</sys:String>
<sys:String x:Key="PumpLive.Engineering">Engineering values (raw)</sys:String>
<!-- ── DTC list ─────────────────────────────────────────────────────── -->
<sys:String x:Key="Dtc.Read">Read DTCs</sys:String>
<sys:String x:Key="Dtc.Clear">Clear DTCs</sys:String>
<sys:String x:Key="Dtc.NoFaults">No fault codes reported by the ECU.</sys:String>
<sys:String x:Key="Dtc.LastRead">Last read:</sys:String>
<sys:String x:Key="Dtc.Cleared">Fault codes cleared.</sys:String>
<!-- ── Auth gate ────────────────────────────────────────────────────── -->
<sys:String x:Key="AuthGate.LockedTitle">Authentication required</sys:String>
<sys:String x:Key="AuthGate.LockedMessage">This section can modify pump parameters. Sign in to unlock.</sys:String>
<sys:String x:Key="AuthGate.Authenticate">Authenticate…</sys:String>
<sys:String x:Key="AuthGate.UnlockedAs">Unlocked as</sys:String>
<sys:String x:Key="AuthGate.Lock">Lock</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 started...</sys:String>
<sys:String x:Key="Test.Stopped">Test stopped.</sys:String>
<!-- ── Tests-page wizard ──────────────────────────────────────────────── -->
<sys:String x:Key="Test.Wizard.Plan">1. Plan</sys:String>
<sys:String x:Key="Test.Wizard.Preconditions">2. Preconditions</sys:String>
<sys:String x:Key="Test.Wizard.Running">3. Running</sys:String>
<sys:String x:Key="Test.Wizard.Done">4. Done</sys:String>
<sys:String x:Key="Test.Wizard.Next">Next ▶</sys:String>
<sys:String x:Key="Test.Wizard.Back">◀ Back</sys:String>
<!-- ── Preconditions checklist ────────────────────────────────────────── -->
<sys:String x:Key="Test.Precheck.Title">Preconditions</sys:String>
<sys:String x:Key="Test.Precheck.PumpSelected">Pump selected</sys:String>
<sys:String x:Key="Test.Precheck.CanLive">CAN bus live</sys:String>
<sys:String x:Key="Test.Precheck.KLineOpen">K-Line session open</sys:String>
<sys:String x:Key="Test.Precheck.RpmZero">Bench RPM at zero</sys:String>
<sys:String x:Key="Test.Precheck.OilPumpOn">Oil pump running</sys:String>
<sys:String x:Key="Test.Precheck.NoCriticalAlarms">No critical alarms</sys:String>
<sys:String x:Key="Test.Precheck.UserAuth">User authenticated</sys:String>
<sys:String x:Key="Test.Precheck.FixButton">Fix</sys:String>
<sys:String x:Key="Test.Precheck.Ready">All preconditions met. Ready to start.</sys:String>
<sys:String x:Key="Test.Precheck.NotReady">Resolve the items above before starting.</sys:String>
<sys:String x:Key="Test.Precheck.AuthBanner">This test requires operator authentication.</sys:String>
<sys:String x:Key="Test.Precheck.AuthButton">Authenticate…</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.SelectPump">Go to Pump page to select a pump.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.CheckCan">Open the Dashboard to check CAN connection.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.OpenKLine">Open a K-Line session on the Pump page.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.StopBench">Stop the bench from the Bench page.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.StartOilPump">Start the oil pump from the Bench page.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.ClearAlarms">Clear critical alarms on the Dashboard.</sys:String>
<sys:String x:Key="Test.Precheck.Remediation.Authenticate">Authenticate the operator above.</sys:String>
<!-- ── Running step controls ──────────────────────────────────────────── -->
<sys:String x:Key="Test.Running.Abort">■ Abort</sys:String>
<sys:String x:Key="Test.Running.Pause">Pause</sys:String>
<sys:String x:Key="Test.Running.Resume">Resume</sys:String>
<sys:String x:Key="Test.Running.Retry">Retry phase</sys:String>
<sys:String x:Key="Test.Running.Skip">Skip phase</sys:String>
<sys:String x:Key="Test.Running.ComingSoon">Coming soon</sys:String>
<!-- ── Done step ──────────────────────────────────────────────────────── -->
<sys:String x:Key="Test.Done.Passed">PASSED</sys:String>
<sys:String x:Key="Test.Done.Failed">FAILED</sys:String>
<sys:String x:Key="Test.Done.ViewFullResults">View full results</sys:String>
<sys:String x:Key="Test.Done.RunAgain">Run again</sys:String>
<!-- ── Abort confirmation ─────────────────────────────────────────────── -->
<sys:String x:Key="Test.Abort.Title">Abort test?</sys:String>
<sys:String x:Key="Test.Abort.Message">Stopping the test now will end the current phase and return partial results. Continue?</sys:String>
<sys:String x:Key="Test.Abort.Confirm">Abort</sys:String>
<sys:String x:Key="Test.Abort.Cancel">Keep running</sys:String>
<!-- ── Test types ───────────────────────────────────────────────────── -->
<sys:String x:Key="TestType.Warmup">Warm-up</sys:String>
<sys:String x:Key="TestType.Adjustment">Adjustment</sys:String>
@@ -152,6 +264,19 @@
<sys:String x:Key="Result.ResultHeader">Result</sys:String>
<sys:String x:Key="Result.AllTests">All Tests</sys:String>
<!-- ── Results page (§5) ────────────────────────────────────────────── -->
<sys:String x:Key="Results.PageTitle">Results</sys:String>
<sys:String x:Key="Results.HistoryHeader">Test history (session)</sys:String>
<sys:String x:Key="Results.DetailHeader">Run detail</sys:String>
<sys:String x:Key="Results.ObservationsLabel">Observations / Notes</sys:String>
<sys:String x:Key="Results.ExportButton">Export PDF…</sys:String>
<sys:String x:Key="Results.EmptyState">No completed tests yet. Finish a test on the Tests page to see it here.</sys:String>
<sys:String x:Key="Results.NoSelection">Select a run on the left to view its details.</sys:String>
<sys:String x:Key="Results.InterruptedLabel">INTERRUPTED</sys:String>
<sys:String x:Key="Results.DeleteTooltip">Remove this entry</sys:String>
<sys:String x:Key="Results.ClearSessionButton">Clear session</sys:String>
<sys:String x:Key="Results.ClearSessionConfirm">Clear all history entries for this session?</sys:String>
<!-- ── Common strings ───────────────────────────────────────────────── -->
<sys:String x:Key="Common.Pass">PASS</sys:String>
<sys:String x:Key="Common.Fail">FAIL</sys:String>
@@ -225,7 +350,7 @@
<sys:String x:Key="Dialog.Settings.Tab.Safety">Safety</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">Company</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.Report">Report</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.KLine">K-Line</sys:String>
<sys:String x:Key="Dialog.Settings.Tab.Advanced">Advanced</sys:String>
@@ -272,6 +397,29 @@
<sys:String x:Key="Dialog.Settings.BlinkInterval">Blink interval (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.FlasherInterval">Flasher interval (ms):</sys:String>
<sys:String x:Key="Dialog.Settings.UsersHeader">Users</sys:String>
<sys:String x:Key="Dialog.Settings.ManageUsers">Manage Users...</sys:String>
<!-- ── Dialog: User management ──────────────────────────────────────── -->
<sys:String x:Key="Dialog.UserManage.Title">User Management</sys:String>
<sys:String x:Key="Dialog.UserManage.ColumnUsername">Username</sys:String>
<sys:String x:Key="Dialog.UserManage.Add">Add...</sys:String>
<sys:String x:Key="Dialog.UserManage.Remove">Remove</sys:String>
<sys:String x:Key="Dialog.UserManage.ChangePassword">Change Password...</sys:String>
<sys:String x:Key="Dialog.UserManage.Close">Close</sys:String>
<sys:String x:Key="Dialog.UserManage.Prompt.AddTitle">Add User</sys:String>
<sys:String x:Key="Dialog.UserManage.Prompt.ChangeTitle">Change password for '{0}'</sys:String>
<sys:String x:Key="Dialog.UserManage.Confirm.RemoveTitle">Remove User</sys:String>
<sys:String x:Key="Dialog.UserManage.Confirm.Remove">Remove user '{0}'?</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.EmptyTitle">Invalid Input</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.Empty">Username and password cannot be empty.</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.InvalidCharsTitle">Invalid Input</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.InvalidChars">Username must not contain ':' or ','.</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.DuplicateTitle">Duplicate User</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.Duplicate">A user with that name already exists.</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.LastUserTitle">Cannot Remove</sys:String>
<sys:String x:Key="Dialog.UserManage.Error.LastUser">Cannot remove the last remaining user.</sys:String>
<!-- ── Error messages ───────────────────────────────────────────────── -->
<sys:String x:Key="Error.ReportGeneration">Failed to generate report:\n{0}</sys:String>
<sys:String x:Key="Error.ReportTitle">Report Error</sys:String>
@@ -318,5 +466,6 @@
<sys:String x:Key="Pdf.ErrorBits"> ⚠ Error bits: {0}</sys:String>
<sys:String x:Key="Pdf.NoSampleData">No sample data available for graphical display.</sys:String>
<sys:String x:Key="Pdf.ChartSamples">Samples: {0} | Target: {1} ± {2} | Average: {3} | Result: {4}</sys:String>
<sys:String x:Key="Pdf.Observations">OBSERVATIONS</sys:String>
</ResourceDictionary>

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>

52
Resources/Styles.xaml Normal file
View File

@@ -0,0 +1,52 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Boolean → Visibility converter (shared across views) -->
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<!-- LCD blue gradient border -->
<Style x:Key="LcdBlue" TargetType="Border">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="4"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0.3,0" EndPoint="0.5,1.3">
<GradientStop Color="#0040ff" Offset="0"/>
<GradientStop Color="#0031c2" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
<!-- LCD amber gradient border -->
<Style x:Key="LcdAmber" TargetType="Border">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="3"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0.3,0" EndPoint="0.5,1.3">
<GradientStop Color="#ffae00" Offset="0"/>
<GradientStop Color="#91670a" Offset="2"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
<!-- Connection indicator style (green/gray) -->
<Style x:Key="ConnIndicator" TargetType="Border">
<Setter Property="Background" Value="Gray"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="2,4"/>
</Style>
<!-- Relay toggle button style -->
<Style x:Key="RelayButton" TargetType="Button">
<Setter Property="Padding" Value="6,3"/>
<Setter Property="Margin" Value="3,2"/>
<Setter Property="FontSize" Value="11"/>
</Style>
</ResourceDictionary>