![]() |
VOOZH | about |
dotnet add package Cayaqui.MPS.Components --version 0.65.9
NuGet\Install-Package Cayaqui.MPS.Components -Version 0.65.9
<PackageReference Include="Cayaqui.MPS.Components" Version="0.65.9" />
<PackageVersion Include="Cayaqui.MPS.Components" Version="0.65.9" />Directory.Packages.props
<PackageReference Include="Cayaqui.MPS.Components" />Project file
paket add Cayaqui.MPS.Components --version 0.65.9
#r "nuget: Cayaqui.MPS.Components, 0.65.9"
#:package Cayaqui.MPS.Components@0.65.9
#addin nuget:?package=Cayaqui.MPS.Components&version=0.65.9Install as a Cake Addin
#tool nuget:?package=Cayaqui.MPS.Components&version=0.65.9Install as a Cake Tool
Design system + componentes EVM/EPC reutilizables para .NET 10 Blazor WebApp (Interactive Server) construidos sobre Syncfusion.Blazor 33.x y Blazor-ApexCharts.
New accent based on Codelco's 2019 Manual de Normas Gráficas:
#E55302 — Pantone 166C (Marca Maestra brand color)opts.DefaultAccent = ThemeAccent.Codelco;
// or at runtime:
await ThemeService.SetAsync(accent: ThemeAccent.Codelco);
Additive — no breaking changes.
.mps-table, .e-grid y .e-treegrid alineados visualmente a YNEX:
| Propiedad | Antes | Ahora |
|---|---|---|
| Header text-transform | uppercase |
normal (sin transform) |
| Header letter-spacing | 0.04em |
none |
| Header font-size | var(--text-xs) |
var(--text-sm) |
| Header font-weight | 500 |
600 |
| Header color | var(--color-text-tertiary) |
var(--color-text) |
| Header background | var(--color-surface-alt) |
var(--color-surface) |
| Row cell color | var(--color-text-secondary) |
var(--color-text) |
| Border-radius | var(--radius-xl) = 12px |
var(--radius-md) = 8px |
| Sticky first-col bg | var(--color-surface-alt) |
var(--color-surface) |
Preset Untitled reintroduce su estilo original vía [data-mps-theme="untitled"] overrides: border-radius: var(--radius-xl), uppercase + letter-spacing: 0.04em, font-size: var(--text-xs), font-weight: 500, color: var(--color-text-tertiary), background: var(--color-surface-alt).
.mps-card ahora tiene margin-block: 1.5rem por defecto (espaciado vertical entre cards, igual que YNEX). El footer elimina el background var(--color-surface-alt) — footer ahora transparente como YNEX.
.mps-card-group > * y .mps-kpi-tile .mps-card zerean el margen — cuando el spacing lo maneja el contenedor (grid gap), el margen no se duplica.
Inputs, labels, checkboxes, radios y botones alineados al estilo YNEX:
| Elemento | Antes | Ahora |
|---|---|---|
| Input border | 1px solid var(--color-border-strong) |
1px solid var(--color-border) (más ligero) |
| Input radius | var(--radius-md) = 8px |
var(--radius-sm) = 6px |
| Input shadow | var(--shadow-xs) |
none |
| Input hover | border-color: var(--color-text-tertiary) |
border-color: var(--color-border-strong) |
| Input focus | border-color + shadow-focus (ring 4px) |
solo border-color: var(--color-accent-500) |
| Label font | var(--text-sm) / weight 500 |
0.8rem / weight 600 |
| Placeholder | default | font-weight: 500 |
| Checkbox/radio border | 1.5px solid var(--color-border-strong) |
1px solid var(--color-border) |
| Btn radius | var(--radius-md) = 8px |
var(--radius-sm) = 6px |
Preset Untitled preserva estilo original: border-radius: var(--radius-md) + box-shadow: var(--shadow-xs) en inputs, label var(--text-sm) / 500, btn var(--radius-md).
Base card ahora coincide con YNEX en todos los presets:
| Propiedad | Antes (default) | Ahora (YNEX-global) |
|---|---|---|
border |
1px solid var(--color-border) |
none |
border-radius |
var(--radius-xl) = 12px |
var(--radius-md) = 8px |
box-shadow |
var(--shadow-sm) |
0 0.125rem 0 rgba(10,10,10,.06) |
| Header padding | 20px 24px |
16px 20px |
| Body padding | 20px 24px |
20px |
| Footer padding | 16px 24px |
16px 20px |
| Title | var(--text-lg) / 600 |
0.9375rem / 700 |
Preset Untitled preserva su estilo propio vía [data-mps-theme="untitled"] (borde, radius-xl, shadow-sm, padding original).
--mps-ph-bleed-xMpsPageHeader implementa el patrón full-bleed: el fondo y sombra se extienden de borde a borde del contenedor padre, mientras el contenido interno alinea con el resto de la página.
El consumer establece --mps-ph-bleed-x en el contenedor padre igual a su padding horizontal:
/* app.css del consumidor */
.app-main {
padding: 24px 32px;
--mps-ph-bleed-x: 32px; /* igual al padding horizontal */
}
--mps-ph-bleed-x: 32px → margin-inline: -32px, padding-inline: 32px → borde a borde, título alineado con contenido--mps-ph-bleed-x (default 0px) → sin bleed, sin márgenes negativos (comportamiento neutro)MpsPageHeader ahora ocupa el 100% del ancho con padding-left: 0 y padding-right: 0. El layout del consumidor es responsable del padding horizontal (e.g., el <main> o contenedor padre). Sin breaking changes de API.
@* Layout del consumidor *@
<div class="app-shell">
<MpsSidebar @ref="_sidebar"
Sticky="true"
Collapsible="true"
CollapseStorageKey="myapp.sidebar.collapsed"
... />
<div class="app-content">
<MpsAppHeader Sidebar="_sidebar" ShowHamburger="true" ... />
<main>@Body</main>
</div>
</div>
/* site.css del consumidor */
.app-content { transition: margin-left 0.05s ease; }
.app-shell:has(.mps-sidebar.is-sticky.is-app:not(.is-collapsed)) .app-content { margin-left: 280px; }
.app-shell:has(.mps-sidebar.is-sticky.is-app.is-collapsed) .app-content,
.app-shell:has(.mps-sidebar.is-sticky.is-slim) .app-content { margin-left: 72px; }
@media (max-width: 1023px) { .app-shell .app-content { margin-left: 0; } }
El hamburguer de MpsAppHeader detecta el viewport automáticamente:
⚠ Breaking: eliminados Card, Tinted, CardBackground, CardBorderColor.
Nuevo look: banda full-width blanca, shadow borrosa inferior, breadcrumbs bajo subtítulo, acciones a la derecha del título.
Todos los componentes renderizan correctamente en un teléfono (390px) sin configuración del consumidor.
Mecanismo híbrido:
@container queries para componentes embebibles (tablas, charts, cards, forms) — se adaptan al ancho de su celda, no solo del teléfono. Convención de breakpoints: sm=640px, lg=1024px.@media viewport solo para layout global: modals full-screen bajo 640px, drawers a 100%, sidebar off-canvas bajo 1024px.@media (pointer: coarse) para targets táctiles: 44px (WCAG 2.5.8) en botones/inputs/rows, 40px en items densos (menús, crumbs).Tablas: wrapper .mps-table-scroll — scroll horizontal dentro del card (la página nunca scrollea), primera columna sticky en contenedores angostos, hint visual de scroll. Ajustable por tabla vía --mps-table-min-width. Composición manual de filas R9C: envolver en .mps-table-scroll.
Charts: MpsChartResponsive centraliza la config móvil de ApexCharts (leyenda abajo, menos labels, toolbar oculto bajo 640px). Gantt/Timeline conservan ancho mínimo legible con scroll.
Sidebar off-canvas (nuevo API aditivo):
<MpsSidebar @bind-MobileOpen="_menuAbierto" ... />
<button class="ah-icon-btn" aria-label="Abrir menú"
@onclick="() => _menuAbierto = !_menuAbierto">☰</button>
Bajo 1024px la sidebar se oculta off-canvas; MobileOpen=true la desliza sobre un backdrop clickeable que dispara MobileOpenChanged(false). El rail collapse de desktop (Collapsible/IsCollapsed) no cambia.
Forms: el EditForm de MpsAutoForm lleva la clase .mps-autoform (query container) — submit a ancho completo en contenedores angostos.
Sin breaking changes. Auditoría componente a componente: docs/components/2026-06-11-mobile-audit.md del repo.
Bug: MpsBarChart con orientación por defecto (Vertical = barras horizontales) mostraba NaN
en todas las etiquetas del eje Y. Causa: Yaxis.Labels.Formatter aplicaba Intl.NumberFormat().format(v)
a las strings de categoría (eje Y = labels en barras horizontales), retornando NaN. Adicionalmente,
Xaxis.Type estaba en Category cuando debería ser Numeric para el eje de valores.
Fix: Xaxis.Type es ahora Numeric para Orientation=Vertical (barras horizontales) y Category
para Orientation=Horizontal (columnas). Yaxis.Labels.Formatter usa función identidad
(function(v){return v}) para barras horizontales (eje Y = strings) y el formateador numérico para
columnas (eje Y = números). Sin breaking changes.
MpsGridColumn Kind="MpsColumnKind.AutoBadge" usado en columnas manuales (con slot <Columns>,
sin DtoType/PropertyName) ahora resuelve el texto del badge desde [Display(Name)] del miembro
del enum y el color desde [BadgeColor] del miembro — antes mostraba enum.ToString() (inglés) y
color gris. MpsAutoBadge standalone también lee [Display(Name)] para el texto.
Fallback a ToString() cuando el miembro no tiene [Display]. El modo AUTO (sin <Columns>,
resolver-driven) ya resolvía [Display] y queda igual. Aditivo, sin breaking.
El consumidor sólo anota el enum:
public enum Severity
{
[Display(Name = "Información")] [BadgeColor(BadgeKind.Neutral)] Info,
[Display(Name = "Advertencia")] [BadgeColor(BadgeKind.Warning)] Warning,
[Display(Name = "Crítica")] [BadgeColor(BadgeKind.Error)] Critical,
}
<MpsGrid TItem="AlertDto" DataSource="_alerts">
<Columns>
<MpsGridColumn TItem="AlertDto" Field="Level" Header="Severidad"
Kind="MpsColumnKind.AutoBadge" Width="120px" />
</Columns>
</MpsGrid>
@* Badge: texto "Crítica" (no "Critical"), color error — sin DtoType/PropertyName. *@
Cuatro action items opt-in en el lado derecho del header, más estado unificado de dropdown (un solo menú abierto a la vez + backdrop click-outside, que ahora también cubre el user-menu).
ShowNotifications, Notifications (lista de MpsHeaderNotification),
badge de no-leídos derivado o explícito (NotificationBadge, cap 9+), PulseBadge,
OnNotificationClick, footer "Ver todas" (OnViewAllNotifications), NotificationsTitle/NotificationsEmptyText.ShowFullscreen (reusa MpsFullscreenToggle).ShowAppsGrid, AppShortcuts (lista de MpsHeaderAppShortcut),
OnAppShortcutClick (fallback: navega al Url), AppsGridTitle.ShowLanguageSelector, Languages (lista de MpsHeaderLanguage),
CurrentLanguageCode, OnLanguageChanged.Nuevos records: MpsHeaderNotification, MpsHeaderAppShortcut, MpsHeaderLanguage.
<MpsAppHeader Sidebar="_sidebar" ShowHamburger="true"
ShowNotifications="true" Notifications="_notifs" PulseBadge="true"
ShowFullscreen="true"
ShowAppsGrid="true" AppShortcuts="_apps"
ShowLanguageSelector="true" Languages="_langs" CurrentLanguageCode="es"
UserName="Demo User" UserSubtitle="demo@cayaqui.com">
<Right><MpsColorModeToggle /></Right>
</MpsAppHeader>
Aditivo, sin breaking changes.
Nuevo valor ThemeAccent.AndesIron (minera Andes Iron SpA): rust óxido de hierro #ED6F51,
escala completa 50–800 en light y dark. Aparece automáticamente en MpsThemeSwitcher.
builder.Services.AddMpsComponents(opts => opts.DefaultAccent = ThemeAccent.AndesIron);
// o en runtime:
await ThemeService.SetAsync(accent: ThemeAccent.AndesIron);
Aditivo, sin breaking changes.
Cierra la migración de gráficos fuera de Syncfusion. API pública sin dependencia de ApexCharts.
Nuevos componentes (Design.Charts / Design.DataDisplay):
MpsLineChart<T> — curva de línea genérica. Serie única (YField) o multi-serie
vía Series (MpsChartSeries<T>(Name, YField, Color?)). Marcador de fecha de control
(ControlDate + XAxis="MpsAxisType.DateTime").MpsColumnChart<T> — columnas verticales. Multi-serie agrupada (default) o
apilada (Stacked). Caso MPS: Plan / Real / EAT por categoría.MpsPieChart — composición sólida; reutiliza el motor de MpsDonutChart
(InnerRadius="0%") y hereda toda la interactividad.Interactividad (mejora) — MpsDonutChart / MpsPieChart:
OnItemClick : EventCallback<DonutItem> — click en slice (cross-filter / toggle en el consumidor).SelectedLabel : string? + DimUnselected (default true) — atenúa los slices no seleccionados.DonutItem(Label, Value, Color?).Interactividad (mejora) — MpsScatterChart<T>:
OnPointClick : EventCallback<T> — devuelve el item (drill-down).XMin/XMax/XInterval + YMin/YMax/YInterval (CPI×SPI encuadrado en 1.0).XPlotLine/YPlotLine. Bubble vía SizeField/ColorField.@* Multi-serie EVM *@
<MpsLineChart TItem="EvmPoint" Data="@evm" Series="@series"
XField="@(x => (object)x.Date)" XAxis="MpsAxisType.DateTime" />
@* Pie cross-filter *@
<MpsPieChart Items="@status" SelectedLabel="@selected" OnItemClick="OnSliceClick" />
@* Scatter CPI×SPI con drill-down y ejes fijos *@
<MpsScatterChart TItem="Proj" Data="@perf"
XField="@(x => (decimal?)x.Cpi)" YField="@(x => (decimal?)x.Spi)"
XMin="0.5m" XMax="1.5m" XInterval="0.1m"
YMin="0.5m" YMax="1.5m" YInterval="0.1m"
XPlotLine="1.0m" YPlotLine="1.0m" DecimalPlaces="2"
OnPointClick="@(p => Nav.NavigateTo($"/proyectos/{p.Id}"))" />
⚠️ BREAKING menor:
DonutItempasó arecord (Label, Value, Color?). La construcción de 2 args sigue válida; sólo se rompe la deconstrucción posicional de 2 elementos (is DonutItem(var l, var v)) — poco usada.
MpsAutoGrid fusionado en MpsGrid ⚠️ BREAKINGMpsAutoGrid fue eliminado. Su funcionalidad (columnas auto-generadas desde
Cayaqui.MPS.Metadata) ahora vive dentro de MpsGrid como modo AUTO:
<Columns> con <MpsGridColumn> explícitos.MpsGrid) — omití <Columns> y las columnas se infieren
desde los atributos del DTO. Acotá con Include / Exclude.MpsGrid absorbió todos los parámetros del viejo MpsAutoGrid: Include, Exclude,
RowActions, RowActionsHeader, RowActionsWidth.
Renombrá la etiqueta. Los parámetros son idénticos — no requiere otros cambios:
- <MpsAutoGrid TItem="InvoiceDto" DataSource="@rows"
- Exclude="@(new[] { nameof(InvoiceDto.Id) })">
- <RowActions Context="row">
- <MpsIconButton Icon="e-icons e-edit" OnClick="@(() => Edit(row))" />
- </RowActions>
- </MpsAutoGrid>
+ <MpsGrid TItem="InvoiceDto" DataSource="@rows"
+ Exclude="@(new[] { nameof(InvoiceDto.Id) })">
+ <RowActions Context="row">
+ <MpsIconButton Icon="e-icons e-edit" OnClick="@(() => Edit(row))" />
+ </RowActions>
+ </MpsGrid>
Regla rápida: MpsGrid sin <Columns> = lo que antes hacía MpsAutoGrid.
MpsGrid con <Columns> = modo manual de siempre. Búsqueda-reemplazo global
MpsAutoGrid → MpsGrid resuelve la migración completa.
Five bugs in mps-theme.js and mps-components.css affecting dark-mode accent rendering:
opts.mode mismatch fixed — undefined/null now treated as 'system' in apply(), consistent with applyMode(). Previously resolved to 'light' while applyMode set dark attribute → dark UI with light accent palette.init() matchMedia listener now calls apply(_lastOpts) on OS dark/light switch (was calling only applyMode()). Inline accent palette no longer stale after system preference change.generateCustomPalette generates inverted lightness scale when isDark=true (50=12%L → 800=93%L). Previously always generated light-mode values.ACCENT_PALETTES_DARK.cobalt (not light ACCENT_PALETTES.cobalt) when accent key is unrecognized in dark mode.0,6,0 not 0,5,0.YNEX preset now matches the original YNEX admin template visual language:
#F0F1F7 (warm light gray), card no-border, 8px radius, bottom shadowMontserrat font, z-index: 103, slim/collapsed → 72pxz-index: 100ThemeAccent.Violet, #845BDF) — matches YNEX primaryFixes:
Accent="Bar" cards now work under YNEX (border:none shorthand was overriding border-inline-start)rgba(255,255,255,0.08) (was near-white, jarring on dark surface)--color-text, --color-text-secondary, etc.) now defined — were falling through to dark-blue light-mode value, rendering illegible@* Enable YNEX preset with Violet accent *@
await ThemeService.SetAsync(new ThemeOptions
{
Preset = ThemePreset.Ynex,
Direction = ThemeDirection.A,
Accent = ThemeAccent.Violet,
Mode = ColorMode.System
});
MpsBadge — VariantString parameter removed (was [Obsolete] since v0.40.0). Use Variant (enum MpsBadgeVariant) instead.MpsAlert — VariantString parameter removed (was [Obsolete] since v0.40.0). Use Variant (enum MpsAlertVariant) instead.Migration:
@* Before (broken in 0.57.0) *@
<MpsBadge VariantString="success">Activo</MpsBadge>
<MpsAlert VariantString="warning" Title="Atención">...</MpsAlert>
@* After *@
<MpsBadge Variant="MpsBadgeVariant.Success">Activo</MpsBadge>
<MpsAlert Variant="MpsAlertVariant.Warning" Title="Atención">...</MpsAlert>
Source= + child overrides)Source= auto-generates all items; MpsDetailItem For= children replace individual properties; MpsDetailItem without For= append at end.
<MpsDetailList Source="@dto" Columns="3">
@* Override Status with custom badge *@
<MpsDetailItem For="@(() => dto.Status)">
<MpsBadge Variant="MpsBadgeVariant.Success">Activo (custom)</MpsBadge>
</MpsDetailItem>
@* Manual item appended after auto-generated ones *@
<MpsDetailItem Label="Días desde inicio">
@((DateTime.Today - (dto.StartDate ?? DateTime.Today)).Days) días
</MpsDetailItem>
</MpsDetailList>
ChildContent field overrides)MpsAutoForm<TModel> auto-generates fields from DTO metadata; MpsAutoFormField<T> For= with ChildContent replaces auto-generated field; MpsAutoFormField<T> without For= adds a manual field.
<MpsAutoForm Model="@dto" OnValidSubmit="HandleSubmit">
@* Override Status field with custom select *@
<MpsAutoFormField For="@(() => dto.Status)" @bind-Value="dto.Status">
<select ...>...</select>
</MpsAutoFormField>
@* Manual field appended at end *@
<MpsAutoFormField Label="Notas internas">
<MpsTextBox @bind-Value="_notes" />
</MpsAutoFormField>
</MpsAutoForm>
Cayaqui.MPS.Metadata 0.5.0 required)New [GridWidth(string)] and [GridFormat(string)] attributes for MpsAutoGrid column control:
public class BudgetDto
{
[Label("Código WBS"), GridWidth("120")] public string WbsCode { get; set; }
[Label("CPI"), GridWidth("80"), GridFormat("N2")] public decimal Cpi { get; set; }
[Label("Descripción"), GridWidth("25%")] public string? Name { get; set; }
}
MpsDetailItem and MpsDetailList now auto-derive labels, formatting, and badges from DTO attributes via IExtendedPropertyResolver.
For= parameter@* Auto label + format from [Label], [Currency], [Date], [Badge] *@
<MpsDetailItem For="@(() => dto.Amount)" />
@* Explicit label override *@
<MpsDetailItem For="@(() => dto.Amount)" Label="Monto Total" />
@* Manual ChildContent override *@
<MpsDetailItem For="@(() => dto.Status)">
<MpsBadge Variant="MpsBadgeVariant.Success">Activo</MpsBadge>
</MpsDetailItem>
Source= parameter@* Auto-generate all visible properties from DTO *@
<MpsDetailList Source="@dto" Columns="3" />
DTO example:
public class ContractDto
{
[Label("Código"), Display(Order = 1)] public string? Code { get; set; }
[Label("Monto"), Display(Order = 2), Currency("USD", 0)] public decimal Amount { get; set; }
[Label("Estado"), Display(Order = 3), Badge] public ContractStatus Status { get; set; }
[Label("Inicio"), Display(Order = 4), Date("dd-MMM-yy")] public DateTime? StartDate { get; set; }
[Hidden] public string? InternalId { get; set; }
}
public enum ContractStatus
{
[BadgeColor(BadgeKind.Success)] Active,
[BadgeColor(BadgeKind.Warning)] OnHold,
[BadgeColor(BadgeKind.Error)] Closed
}
Requires DTO attributes from Cayaqui.MPS.Metadata: [Label], [Currency], [Date], [Badge], [BadgeColor], [Display(Order=n, GroupName="X")], [Hidden].
ThemeService.SetAsync(preset: ...) now auto-applies canonical accent/density/radius when switching presets. Explicit parameters always override defaults.
| Preset | Accent | Density | RadiusScale |
|---|---|---|---|
ThemePreset.Ynex |
Emerald | Comfort | 1.0 |
ThemePreset.Untitled |
Cobalt | Comfort | 1.0 |
await Theme.SetAsync(preset: ThemePreset.Untitled);
// → Accent=Cobalt, Density=Comfort, RadiusScale=1.0
await Theme.SetAsync(preset: ThemePreset.Untitled, accent: ThemeAccent.Indigo);
// → Accent=Indigo (caller wins), Density=Comfort, RadiusScale=1.0
| Component | Namespace | Description |
|---|---|---|
MpsDivider |
DataDisplay | Horizontal/vertical separator with optional label |
MpsFeatureCard |
DataDisplay | Clickeable card: icon + title + description |
MpsDetailList + MpsDetailItem |
DataDisplay | Grid label→value ficha with RenderFragment values |
MpsStepIndicator + MpsStep |
Navigation | Clickeable multi-step progress indicator |
<MpsDivider Label="Información del contrato" />
<MpsFeatureCard Title="EVM" Icon="@MpsIcons.ChartBar" OnClick="@Navigate" />
<MpsDetailList Columns="2">
<MpsDetailItem Label="Contrato">CNT-001</MpsDetailItem>
<MpsDetailItem Label="Estado"><MpsBadge Variant="MpsBadgeVariant.Success">Activo</MpsBadge></MpsDetailItem>
</MpsDetailList>
<MpsStepIndicator @bind-ActiveStep="_step">
<MpsStep Label="Datos" />
<MpsStep Label="Revisión" />
<MpsStep Label="Confirmar" />
</MpsStepIndicator>
4 nuevas paletas de acento (Navy · Teal · Rose · Orange) + Custom accent picker con generador HSL en JS. ThemeSidebarTone global (Default/Tinted/Branded/Gradient/Transparent) vía data-mps-sidebar. ThemeHeaderStyle global (Default/Color/Gradient) vía data-mps-header. MpsSidebar.SidebarTone ampliado con Gradient + Transparent. MpsThemeSwitcher renovado con secciones de Sidebar Tone, Header Style y Custom Color Picker.
// Custom accent — genera rampa HSL completa desde cualquier hex
await Theme.SetAsync(accent: ThemeAccent.Custom, customAccentHex: "#7c3aed");
// Sidebar branded + header gradient en una sola llamada
await Theme.SetAsync(
accent: ThemeAccent.Navy,
sidebarTone: ThemeSidebarTone.Branded,
headerStyle: ThemeHeaderStyle.Gradient);
Nuevo tema visual paralelo al sistema YNEX, inspirado en UntitledUI v8. Se activa con data-mps-theme="untitled" en <html> y coexiste con todos los acentos y el dark mode existentes.
| ThemePreset | ThemeGray | data-mps-theme |
data-mps-gray |
Visual |
|---|---|---|---|---|
Ynex (default) |
— | — | — | Direction A/B tradicional |
Untitled |
Neutral (default) |
untitled |
— | Gray puro sin tinte (#FCFCFD bg) |
Untitled |
Cool |
untitled |
cool |
Gray con tinte azul-frío (#F5F6FA bg) |
// Activar tema Untitled neutral
await Theme.SetAsync(preset: ThemePreset.Untitled, gray: ThemeGray.Neutral);
// Activar tema Untitled cool + acento cobalt
await Theme.SetAsync(preset: ThemePreset.Untitled, gray: ThemeGray.Cool, accent: ThemeAccent.Cobalt);
// Volver al sistema YNEX direction A
await Theme.SetAsync(preset: ThemePreset.Ynex, direction: ThemeDirection.A);
builder.Services.AddMpsComponents(o =>
{
o.DefaultPreset = ThemePreset.Untitled; // nuevo: default al iniciar
o.DefaultGray = ThemeGray.Cool; // nuevo: variante de gray para Untitled
// ... resto de opciones sin cambios
});
<script>
(function () {
try {
var s = localStorage.getItem('mps.theme');
if (s) {
var t = JSON.parse(s);
if (t.Mode === 1) document.documentElement.setAttribute('data-mps-mode', 'dark');
if (t.Preset === 1) { // 1 = ThemePreset.Untitled
document.documentElement.setAttribute('data-mps-theme', 'untitled');
if (t.Gray === 1) // 1 = ThemeGray.Cool
document.documentElement.setAttribute('data-mps-gray', 'cool');
}
}
} catch (_) {}
})();
</script>
Preset y Gray se agregaron como parámetros opcionales con defaults al final del record. Implementaciones existentes de IThemePersistence que construyen ThemeState posicionalmente no requieren cambios — los nuevos campos usan sus defaults (Ynex / Neutral).
Removes a stray } that caused the browser CSS parser to discard the MpsSidebar accordion base rule (max-height: 0; overflow: hidden). Submenus now correctly collapse/expand.
MpsSidebarItem ahora soporta submenús declarativos y data-driven con transición CSS max-height.
@* Declarativo *@
<MpsSidebarItem Text="Control de Proyecto" Icon="@_icon">
<MpsSidebarItem Text="Dashboard EVM" Href="/evm" />
<MpsSidebarItem Text="Curva S" Href="/scurve" />
<MpsSidebarItem Text="Flujo de Caja" Href="/cashflow" />
</MpsSidebarItem>
@* Data-driven *@
<MpsSidebarItem Text="Contratos"
Icon="@_icon"
Children="@_contractItems" />
En modo Slim / collapsed, los submenús aparecen como flyout popovers al hover. El sidebar aplica acordeón exclusivo (abrir uno cierra los demás al mismo nivel).
Esta versión incorpora componentes y layouts avanzados basados en el framework de diseño YNEX para la suite de controles interactivos corporativos de MPS.
MpsChat — Centro de colaboración y mensajería interna (Design.DataDisplay)Estructura de chat interactiva de dos paneles (sidebar de contactos con filtros/búsqueda y área de mensajes). Incluye un script JS aislado para auto-scroll dinámico.
<MpsChat Contacts="@_contacts"
Messages="@_messages"
CurrentUserId="@_myId"
ActiveContactId="@_activeContactId"
OnSendMessage="HandleSendMessage"
OnActiveContactChanged="HandleContactChanged" />
MpsTimeline — Layouts avanzados (Design.DataDisplay)Se expande el componente existente para soportar layouts Simple, Detailed (con avatares, sub-badges semánticos y detalles en cascada) y TwoColumns de alta densidad. Incluye botón diferido "Cargar más".
<MpsTimeline Items="@_detailedItems" Layout="MpsTimelineLayout.Detailed" EnableLoadMore="true" OnLoadMore="HandleLoadMore" />
MpsTaskCard — Tarjetas de tareas y entregables (Design.DataDisplay)Tarjeta semáforo configurable por prioridad (Low, Medium, High, Critical), con soporte de estrella de favorito interactiva y responsables apilados (MpsAvatarGroup).
<MpsTaskCard Task="@_task" OnStarredChanged="HandleStarred" />
MpsTeamSparklineList — Lista de rendimiento con Sparklines (Design.DataDisplay)Lista vertical compacta de colaboradores que muestra su rol, estado de conexión y tendencia de desempeño utilizando mini-gráficos Sparklines de ApexCharts.
<MpsTeamSparklineList Members="@_members" />
MpsThemeSwitcher — Panel lateral de configuración offcanvas (Design.Navigation)Panel lateral offcanvas que expone selectores interactivos para personalizar el tema (Modo de color claro/oscuro, densidad de espaciado, radio de bordes y acento de color) en tiempo real con ThemeService. Incluye un botón flotante de engranaje autocontenido.
<MpsThemeSwitcher />
MpsWorkflowEditor + MpsWorkflowMini — Pipeline de aprobación genérico (Engineering.Workflow)Visualización y edición de pipelines de aprobación. Nodos configurables vía parámetros — no hay roles hardcodeados. MpsWorkflowEditor es interactivo (toggle por click + presets); MpsWorkflowMini es compacto y read-only.
Tipos: MpsWorkflowEditor.NodeDef(Key, Label, Title, IsFixed, IsTerminal) · MpsWorkflowEditor.Preset(Label, ActiveKeys)
@* Define nodos y presets específicos de la app *@
@code {
private static readonly IReadOnlyList<MpsWorkflowEditor.NodeDef> _nodes =
[
new("start", "Ini", "Inicio", IsFixed: true),
new("review", "Rev", "Revisión", IsFixed: false),
new("approval", "Apr", "Aprobación", IsFixed: false),
new("end", "✓", "Completado", IsFixed: true, IsTerminal: true),
];
private static readonly IReadOnlyList<MpsWorkflowEditor.Preset> _presets =
[
new("Mínimo", []),
new("Completo", ["review", "approval"]),
];
private IReadOnlyList<string> _activeKeys = ["review", "approval"];
}
@* Editor interactivo *@
<MpsWorkflowEditor Nodes="_nodes"
Presets="_presets"
@bind-ActiveKeys="_activeKeys" />
@* Vista compacta (ej. en una tabla) *@
<MpsWorkflowMini Nodes="_nodes" ActiveKeys="_activeKeys" />
Namespace: @using MPS.Components.Engineering.Workflow (incluido en _Imports.razor global de 0.51.0+).
MpsFullscreenToggle — Botón fullscreen browser (Design.Navigation)Botón que alterna document.documentElement.requestFullscreen() / document.exitFullscreen(). Sincroniza estado con el evento fullscreenchange.
Setup requerido — agregar en App.razor antes de </body>:
<script src="_content/MPS.Components/js/mps-fullscreen.js"></script>
@* Uso básico — colocar donde quieras el botón (ej. AppHeader) *@
<MpsFullscreenToggle />
@* Con labels en inglés *@
<MpsFullscreenToggle EnterLabel="Full screen"
ExitLabel="Exit full screen"
Class="my-fullscreen-btn" />
El JS expone window.mpsFullscreen (distinto de cualquier fullscreenToggle legacy). Si el browser bloquea la API (ej. iframe sin allow-fullscreen), el botón es silencioso — no rompe la UI.
MpsReconnectModal — Diálogo de reconexión Blazor Server (Design.Feedback)Diálogo <dialog> nativo que gestiona los estados de reconexión del circuito SignalR de Blazor Server. Todos los textos son [Parameter] para i18n.
Setup requerido — en App.razor, DESPUÉS de blazor.web.js:
<script src="@Assets["_framework/blazor.web.js"]"></script>
<script type="module" src="_content/MPS.Components/js/mps-reconnect.js"></script>
Uso en App.razor (reemplaza <ReconnectModal /> custom):
<body>
<Routes @rendermode="InteractiveServer" />
<MpsReconnectModal /> @* ← aquí *@
<script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js"></script>
<script src="_content/MPS.Components/js/mps-theme.js"></script>
<script src="_content/MPS.Components/js/mps-fullscreen.js"></script> @* si usas MpsFullscreenToggle *@
<script src="@Assets["_framework/blazor.web.js"]"></script>
<script type="module" src="_content/MPS.Components/js/mps-reconnect.js"></script>
</body>
@* Textos personalizados (todos tienen defaults en español) *@
<MpsReconnectModal FirstAttemptText="Reconnecting to server…"
FailedText="Connection lost. Please reload."
RetryLabel="Retry"
ResumeLabel="Resume" />
MpsCardGroup gana parámetro MaxCardWidth (default "1fr"). Permite MinCardWidth="280px" MaxCardWidth="400px" para grids con ancho acotado.MpsGauge gana parámetro Preset = GaugePreset.Cpi | Spi | None. Elimina necesidad de MpsCpiGauge y MpsSpiGauge.MpsDeltaChip nuevo componente unificado (Variant = DeltaVariant.Variance | Trend). Elimina MpsVariancePill y MpsTrendDelta.<MpsCpiGauge> → <MpsGauge Preset="MpsGauge.GaugePreset.Cpi">. <MpsTrendDelta Period="WoW"> → <MpsDeltaChip Variant="MpsDeltaChip.DeltaVariant.Trend" Period="WoW">.MpsGridColumn kind Number usaba CultureInfo.InvariantCulture → separadores incorrectos en es-CL y otros locales.CultureInfo.CurrentCulture — valores numéricos ahora usan los separadores del sistema (ej. es-CL: 1.234.567).MpsPhysicalProgressCurve eliminado — su funcionalidad (curva S clásica, solo líneas) vive ahora en MpsProgressSCurve con ShowPartialBars="false".ProgressSCurvePoint gana BaselineCumulative? (antes BaselinePct? del componente eliminado).<MpsPhysicalProgressCurve> → <MpsProgressSCurve ShowPartialBars="false"> y renombrar propiedades: BaselinePct→BaselineCumulative, PlanPct→PlanCumulative, ActualPct→ActualCumulative, ForecastPct→ForecastCumulative.GenerateDocumentationFile habilitado — el .xml de IntelliSense se empaqueta junto al DLL.[Parameter] ahora tienen /// <summary> en los 181 componentes (Design, Evm, Engineering).MpsCardGroup: contenedor grid que iguala la altura de todos los cards de cada fila. Auto-fill responsive por defecto; parámetro Columns para columnas fijas.ConfirmDialog: cerrar con X button o overlay ahora dispara OnCancel (antes se ignoraba).CloseOnOverlayClick default cambiado a false (evita dismiss accidental en acciones destructivas).mps-indicators.css → mps-syncfusion-overrides.css (sección dialog).Warning: color ámbar aplicado en estado normal + hover; eliminados fallback hex.Intl.NumberFormat('es-CL') sin sufijo kUSD (el título del eje ya lo indica). Ej. "25.000" en vez de "25000.0 kUSD".Curve.Straight reemplaza Curve.Smooth — elimina el overshoot visual que extendía la línea "Real acum." más allá del mes de control.CashflowChart: ambos ejes Y usan (v/1000).toFixed(1) + ' kUSD' — etiquetas y títulos muestran "Acumulado (kUSD)" / "Mensual (kUSD)".GanttChart: columnas Real% y Plan% con Format="N1".Breaking changes — ver BREAKING_CHANGES_v0.48.md.
WbsKanban legacy (display:grid en .mps-kanban, .mps-kanban-body, .phase-*) que nunca matcheaban el DOM real.KpiCard: inline styles → clases CSS; nuevo parámetro Size (KpiSize.Md/Sm).GaugeTile: corregido token --color-text-primary (no existía) → --color-text; marker visible en dark mode.RiskHeatmap level-critical: contraste 3.3:1 → 6.1:1 (WCAG AA) con error-700 + texto blanco.--color-hover definido en tokens (causaba hover invisible en ChangeOrderLog).prefers-reduced-motion activo en toda la librería.MpsDomainHeader dark mode adaptativo.EvmCashflowCurve, PhysicalProgressCurve, MpsProgressSCurve, ContingencyDrawdown, CashflowChart y MpsTimelineChart lanzaban ArgumentException en DateTimeOffset..ctor(DateTime, TimeSpan.Zero) cuando el parámetro de fecha (p.ej. ControlDate, StartField, EndField) tenía DateTimeKind.Local — que es el Kind que devuelve DateTime.Today y DateTime.Now. Todos los componentes ahora usan DateTime.SpecifyKind(x.Date, DateTimeKind.Utc) internamente: acepta cualquier Kind sin excepción. Sin breaking changes.
Nuevos componentes:
MpsListGroup — Lista estilizada model-driven con items accionables. Soporte para icono leading, subtítulo, badge trailing (MpsBadgeVariant), selección (@bind-SelectedItemId), estado Disabled, variante Flush e ItemTemplate custom.<MpsListGroup Items="_items" @bind-SelectedItemId="_selectedId" />
@code {
private readonly MpsListGroupItem[] _items =
[
new("1", "Planos estructurales", Subtitle: "Ingeniería Civil",
Icon: "e-icons e-folder", BadgeText: "12", BadgeVariant: MpsBadgeVariant.Brand),
new("2", "Especificaciones técnicas"),
new("3", "Bloqueado", Disabled: true),
];
private string? _selectedId;
}
MpsFileManager — Browser de archivos/documentos con vista Grid (tarjetas) y Lista (tabla). Toggle de vista integrado (@bind-ViewMode). Auto-detección de icono por extensión (.pdf, .dwg, .docx, .xlsx, imágenes, .zip). Callbacks OnOpen y OnDelete. El consumer provee IReadOnlyList<MpsFileItem> — sin dependencia de sistema de archivos.<MpsFileManager Items="_files"
Title="Documentos del Proyecto"
@bind-ViewMode="_view"
OnOpen="f => NavigateTo(f.Id)"
OnDelete="f => RemoveFile(f.Id)" />
@code {
private MpsFileItem[] _files =
[
new("f1", "Plano-Estructura.pdf", Category: "Planos",
SizeBytes: 1_420_000, UpdatedAt: DateTime.UtcNow.AddDays(-3)),
new("f2", "Especificaciones.docx", Category: "Specs", SizeBytes: 85_000),
new("d1", "Carpeta Ingeniería", IsFolder: true),
];
private MpsFileViewMode _view = MpsFileViewMode.Grid;
}
Migración Syncfusion Charts → ApexCharts completada:
Todos los componentes de chart han sido migrados a ApexCharts. Los paquetes Syncfusion.Blazor.Charts y Syncfusion.Blazor.Sparkline han sido eliminados de las dependencias del paquete. MpsSparkline reescrito como SVG puro (sin dependencia JS).
Componentes migrados (17 total):
BarChart, StackedBarChart, DonutChart, MonteCarloHistogram, BurndownChart, Waterfall, CommodityPriceChart, ManagementReserveTracker, ProductionCurve, ResourceHistogram, ContingencyDrawdown, EvmCashflowCurve, PhysicalProgressCurve, EvmControlChart, CashflowChart, CpiIndicator, SpiIndicator.
Sin breaking changes en la API pública.
Acción requerida para consumers: Si en tu propio
.csprojtenías una referencia aSyncfusion.Blazor.ChartsoSyncfusion.Blazor.Sparklineúnicamente porque era dependencia transitiva de este paquete, puedes eliminarla con seguridad. Si la usas directamente, consérvala.
EvmCashflowCurve — Curva S EVM monetaria4 series EVM sobre un gráfico ApexCharts: Plan (PV) azul, Earned Value (EV) área púrpura, Actual Cost (AC) rojo, Forecast (EAC) verde dashed. Anotación horizontal BAC + anotación vertical de fecha de control con etiqueta rotada −90°. Pie con título y fecha.
<EvmCashflowCurve Points="@_points"
Bac="10_000_000m"
ControlDate="@_controlDate"
Title="Talara U3"
Currency="USD" />
@code {
private readonly DateTime _controlDate = new DateTime(2026, 4, 1);
private readonly EvmCashflowCurve.EvmCashflowPoint[] _points =
[
new() { Date = new DateTime(2026,1,1), Pv = 1_200_000m },
new() { Date = new DateTime(2026,2,1), Pv = 2_800_000m, Ev = 2_600_000m, Ac = 2_750_000m },
new() { Date = new DateTime(2026,3,1), Pv = 4_500_000m, Ev = 4_100_000m, Ac = 4_300_000m },
new() { Date = new DateTime(2026,4,1), Pv = 6_200_000m, Ev = 5_600_000m, Ac = 5_900_000m },
new() { Date = new DateTime(2026,5,1), Pv = 7_800_000m, Forecast = 8_400_000m },
new() { Date = new DateTime(2026,6,1), Pv = 9_100_000m, Forecast = 9_800_000m },
new() { Date = new DateTime(2026,7,1), Pv = 10_000_000m, Forecast = 10_800_000m },
];
}
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
Points ⚠️ |
IList<EvmCashflowPoint> |
— | Requerido. |
Bac |
decimal? |
null |
Budget At Completion — línea horizontal de referencia. |
ControlDate |
DateTime? |
null |
Data date — línea vertical dashed + etiqueta. Acepta cualquier DateTimeKind. |
Title |
string |
"" |
Título en el pie del chart. |
ControlDateLabel |
string |
"Control al" |
Prefijo de la etiqueta de fecha de control. |
Currency |
string |
"USD" |
Código ISO 4217 — se incluye en los nombres de serie. |
Height |
string |
"360" |
Alto en px. |
YAxisTitle |
string |
"" |
Título del eje Y. |
Class |
string? |
null |
Clase CSS adicional para el contenedor raíz. |
EvmCashflowPoint (clase anidada pública):
| Propiedad | Tipo | Descripción |
|---|---|---|
Date |
DateTime |
Fecha del punto (eje X). |
Pv |
decimal? |
Planned Value. |
Ev |
decimal? |
Earned Value. null para meses futuros. |
Ac |
decimal? |
Actual Cost. null para meses futuros. |
Forecast |
decimal? |
EAC proyectado. Solo en meses > ControlDate. |
PhysicalProgressCurve — Curva S de avance físico (%)4 series en porcentaje: Baseline (gris dashed, opcional), Plan (azul), Real (rojo con markers) y Forecast (verde dashed). Meta al 100% como línea horizontal. Anotación vertical de fecha de control.
<PhysicalProgressCurve Points="@_curve"
ControlDate="@_controlDate"
Title="Avance Físico · Talara U3" />
@code {
private readonly DateTime _controlDate = new DateTime(2026, 4, 1);
private readonly PhysicalProgressCurve.ProgressCurvePoint[] _curve =
[
new() { Date = new DateTime(2026,1,1), PlanPct=8m, ActualPct=7m },
new() { Date = new DateTime(2026,2,1), PlanPct=18m, ActualPct=17m },
new() { Date = new DateTime(2026,3,1), PlanPct=30m, ActualPct=27m },
new() { Date = new DateTime(2026,4,1), PlanPct=43m, ActualPct=38m },
new() { Date = new DateTime(2026,5,1), PlanPct=57m, ForecastPct=52m },
new() { Date = new DateTime(2026,6,1), PlanPct=72m, ForecastPct=68m },
new() { Date = new DateTime(2026,7,1), PlanPct=85m, ForecastPct=82m },
new() { Date = new DateTime(2026,8,1), PlanPct=100m, ForecastPct=100m },
];
}
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
Points ⚠️ |
IList<ProgressCurvePoint> |
— | Requerido. |
ControlDate |
DateTime? |
null |
Data date — línea vertical dashed + etiqueta. Acepta cualquier DateTimeKind. |
Title |
string |
"" |
Título en el pie del chart. |
ControlDateLabel |
string |
"Control al" |
Prefijo de la etiqueta de fecha de control. |
YMax |
decimal |
100 |
Máximo del eje Y. Cambiar si la escala no es 0–100. |
ShowGoalLine |
bool |
true |
Muestra/oculta la línea horizontal al 100% (o YMax). |
Height |
string |
"360" |
Alto en px. |
YAxisTitle |
string |
"" |
Título del eje Y. |
Class |
string? |
null |
Clase CSS adicional para el contenedor raíz. |
ProgressCurvePoint (clase anidada pública):
| Propiedad | Tipo | Descripción |
|---|---|---|
Date |
DateTime |
Fecha del punto (eje X). |
BaselinePct |
decimal? |
Baseline original (gris dashed). Omitir si no aplica. |
PlanPct |
decimal? |
Plan vigente. |
ActualPct |
decimal? |
Avance real. Solo para puntos ≤ ControlDate. |
ForecastPct |
decimal? |
Pronóstico. Solo para puntos > ControlDate. |
Curva S de avance físico con barras periódicas + líneas acumuladas:
MpsProgressSCurve — Gráfico mixto ApexCharts (Bar + Line) para Plan, Real y Forecast. Paleta EVM canónica. Anotaciones de ControlDate y meta 100%. El consumer provee parciales y acumulados de forma explícita.<MpsProgressSCurve Points="@_scurve"
ControlDate="@_controlDate"
Title="Avance Físico · Talara U3" />
@code {
private static readonly DateTime _controlDate = new DateTime(2025, 5, 1);
private MpsProgressSCurve.ProgressSCurvePoint[] _scurve =
[
new() { Date = new DateTime(2025,1,1), PlanPartial=8m, PlanCumulative=8m, ActualPartial=7m, ActualCumulative=7m },
new() { Date = new DateTime(2025,5,1), PlanPartial=14m, PlanCumulative=59m, ActualPartial=12m, ActualCumulative=52m },
new() { Date = new DateTime(2025,6,1), PlanPartial=13m, PlanCumulative=72m, ForecastPartial=14m, ForecastCumulative=66m },
new() { Date = new DateTime(2025,8,1), PlanPartial=13m, PlanCumulative=100m, ForecastPartial=18m, ForecastCumulative=100m },
];
}
Tabla de control de avance para ingeniería de proyectos EPC:
MpsEngProgressTable — Tabla Plan/Forecast/Actual/Var por documento con columnas de hitos configurables. Cálculo automático de avance ponderado y reglas de color por fecha de control.<MpsEngProgressTable Documents="@_docs"
Milestones="@_milestones"
ControlDate="@_controlDate"
Title="Control de Avance de Ingeniería" />
Tres componentes atómicos para gestión de entregables de ingeniería:
MpsDocStatusBadge — Badge de estado libre. El consumer mapea sus códigos (IFC, IFA, IFR…) a 5 variantes de color.MpsDocRevisionList — Lista de revisiones con activa destacada, fecha, razón de emisión y link de descarga.MpsDocCard — Card completa: número, disciplina, tipo, revisión activa, HH presupuestado vs real, revisiones colapsables, upload drag & drop opcional.<MpsDocCard Document="@_doc"
StatusVariantResolver="@(s => s switch {
"IFC" => MpsDocStatusBadge.BadgeVariant.Success,
"IFA" => MpsDocStatusBadge.BadgeVariant.Warning,
"IFR" => MpsDocStatusBadge.BadgeVariant.Info,
_ => MpsDocStatusBadge.BadgeVariant.Neutral })"
ShowUploader="true"
OnFilesSelected="@HandleFiles" />
Calendario interactivo completo basado en FullCalendar 6.x (bundle incluido en el paquete):
MpsCalendar — Calendario con vistas Mes, Semana, Día y Agenda. Soporte drag-drop, resize, click en eventos y click en fechas.MpsCalendarEvent — DTO para eventos (Id, Title, Start, End, AllDay, Color, Category).MpsCalendarEventMoveArgs — DTO para callbacks de drag/resize (EventId, NewStart, NewEnd).@* Registro del script en App.razor *@
<script src="_content/MPS.Components/js/mps-calendar.js"></script>
@* Uso básico en la página *@
<MpsCalendar Events="@_events"
View="MpsCalendar.CalendarView.Month"
Editable="true"
OnEventClick="@HandleEventClick"
OnDateClick="@HandleDateClick"
OnEventDrop="@HandleEventDrop" />
@code {
private List<MpsCalendarEvent> _events = new()
{
new MpsCalendarEvent { Id = "1", Title = "Reunión", Start = DateTime.Today.AddDays(1), AllDay = true },
new MpsCalendarEvent { Id = "2", Title = "Visita", Start = DateTime.Today.AddHours(10), End = DateTime.Today.AddHours(12) },
};
private void HandleEventClick(MpsCalendarEvent ev) => Console.WriteLine($"Click: {ev.Title}");
private void HandleDateClick(DateTime date) => Console.WriteLine($"Fecha: {date:dd-MMM-yy}");
private void HandleEventDrop(MpsCalendarEventMoveArgs a) => Console.WriteLine($"Movido a: {a.NewStart:dd-MMM-yy}");
}
Demo vivo: /catalog/calendar.
Dos nuevos componentes de gráficos basados en Blazor-ApexCharts 6.1.0:
MpsHeatmapChart<TItem> — Agrupa datos planos por RowField en series y renderiza un heatmap. Params: Data, RowField, ColField, ValueField, Title, Height.MpsTimelineChart<TItem> — Gráfico de barras horizontales tipo Gantt (range-bar). Agrupa por GroupField en series y convierte DateTime a Unix ms. Params: Data, NameField, GroupField, StartField, EndField, Title, Height.@* Heatmap: avance % por disciplina y semana *@
<MpsHeatmapChart TItem="HeatCell"
Data="@_heatmap"
RowField="@(x => x.Discipline)"
ColField="@(x => x.Week)"
ValueField="@(x => (decimal?)x.Progress)" />
@* Timeline (Gantt-style): fases de proyecto *@
<MpsTimelineChart TItem="Phase"
Data="@_timeline"
NameField="@(x => x.Name)"
GroupField="@(x => x.Discipline)"
StartField="@(x => x.Start)"
EndField="@(x => x.End)" />
Demo vivo: /catalog/charts (incluye los 6 chart components: Area, Scatter, Funnel, Treemap, Heatmap, Timeline).
MpsSpinner, MpsAvatar, MpsAvatarGroup, MpsInputGroup, MpsFloatingLabel, MpsFormWizard.
@* Spinner de carga — 4 tamaños, 6 colores *@
<MpsSpinner Size="MpsSpinner.SpinnerSize.Md" Color="MpsSpinner.SpinnerColor.Primary" />
@* Avatar con iniciales determinísticas o imagen *@
<MpsAvatar Name="Pedro Vera" Size="MpsAvatar.AvatarSize.Md" />
<MpsAvatar Name="María López" Src="@user.AvatarUrl" Shape="MpsAvatar.AvatarShape.Circle" />
@* Stack de avatares con badge de overflow *@
<MpsAvatarGroup Avatars="@_equipo" Max="4" />
@* Input con addons de texto *@
<MpsInputGroup Prefix="$" Suffix="USD">
<MpsTextBox Placeholder="Costo estimado" />
</MpsInputGroup>
@* Floating label — el input DEBE tener Placeholder=" " (espacio) *@
<MpsFloatingLabel Label="Nombre del proyecto" InputId="fl-nombre">
<MpsTextBox Placeholder=" " @bind-Value="@_nombre" />
</MpsFloatingLabel>
@* Wizard multi-paso con validación async por step *@
<MpsFormWizard OnFinish="HandleFinish" FinishLabel="Crear Proyecto">
<MpsFormWizardStep Title="Datos generales">
</MpsFormWizardStep>
<MpsFormWizardStep Title="Presupuesto" OnValidate="ValidarPresupuesto">
</MpsFormWizardStep>
</MpsFormWizard>
MpsSpinner: tamaños Xs/Sm/Md/Lg, colores Primary/Muted/White/Success/Warning/Danger. Respeta prefers-reduced-motion.
MpsAvatar: tamaños Xs/Sm/Md/Lg/Xl, formas Circle/Square. Color de iniciales determinístico (paleta de 8 colores semánticos).
MpsAvatarGroup: parámetros Avatars (IReadOnlyList<MpsAvatarSpec>) y Max (default 4).
MpsInputGroup: clase raíz mps-input-addon-group. Props Prefix, Suffix, PrefixIcon, SuffixIcon.
MpsFloatingLabel: requiere Placeholder=" " en el input hijo. InputId para asociación accesible.
MpsFormWizardStep: parámetros Title, OnValidate (Func<Task<bool>>?). Sin parámetro Icon.
4 componentes nuevos para el flujo EPC de fotos de campo (iPhone → procesado → galería + lightbox). Integración callback-based — MPS.Components no depende de MPS.Images ni MPS.Storage.
@* Galería con lightbox EXIF — pure Blazor, sin JS *@
<MpsPhotoGallery Items="_fotos" MaxVisible="5" OnDelete="HandleDelete" />
@* Uploader con preview inmediato y progreso por foto *@
<MpsPhotoUploader OnUpload="HandleUpload" OnUploaded="HandleUploaded"
MaxPhotos="10" MaxParallel="3" />
@* Avatar circular con spinner y rollback en error *@
<MpsAvatarUploader OnUpload="HandleUpload" CurrentName="@_user.FullName"
Size="MpsAvatarUploaderSize.Lg" />
AttachmentList actualizado: nuevo parámetro ShowImagePreview (default false) — muestra thumbnail 40×40 inline para extensiones de imagen.
MpsSegmented<TValue> rediseñado con estilo Pill/Float y API completa. Corrige el CSS class mismatch que impedía que el componente tuviera estilos visuales.
<MpsSegmented TValue="string"
Value="@_view"
ValueChanged="v => _view = v"
Options="@_opts"
Variant="MpsSegmentedVariant.Accent"
Size="MpsSegmentedSize.Sm"
FullWidth="true" />
@code {
private string _view = "kanban";
private static readonly IReadOnlyList<MpsSegmented<string>.Option> _opts =
[
new("lista", "Lista", "e-icons e-list"),
new("kanban", "Kanban", "e-icons e-kanban"),
new("gantt", "Gantt", "e-icons e-gantt", Disabled: true),
];
}
Variantes: Default · Accent · Success · Warning · Danger
Tamaños: Sm · Md (default) · Lg
FullWidth: false por default — true estira al 100% del contenedor
Disabled por ítem: new Option(value, label, icon, Disabled: true) — grisado, no clickeable
Cinco composites nuevos sobre MpsSkeleton cubren los arquetipos comunes de loading-state — eligen el correcto y reducís layout shift sin reinventar el primitivo:
<MpsSkeletonStack /> @* N líneas (forms, sidebars, párrafos) *@
<MpsSkeletonCard Height="280px" /> @* header + rect (chart placeholder) *@
<MpsSkeletonRow /> @* avatar + título + subtítulo *@
<MpsSkeletonGrid /> @* 1×4 KPI strip por default *@
<MpsSkeletonTable Rows="15" /> @* toolbar + 15 filas *@
Composición típica de dashboard:
@if (_loading)
{
<MpsSkeletonGrid Rows="1" Columns="4" /> @* KPI strip *@
<MpsSkeletonCard Height="280px" /> @* chart *@
<MpsSkeletonTable Rows="6" ShowToolbar="false" /> @* tabla resumen *@
}
Todos consumen internamente <MpsSkeleton/> (heredan shimmer + prefers-reduced-motion). +37 tests bUnit (798 total). Sin breaking changes — son aditivos sobre la v0.33.0. Ver scripts/migrate-to-components-0.34.0.md para recetas por arquetipo.
MpsSkeleton runtime fixBug crítico runtime (presente desde la primera versión): MpsSkeleton.razor rendereaba <div class="mps-skeleton ..."> pero la regla CSS empacada era solo .skeleton. El componente nunca aplicó shimmer ni estilos en consumidores. En 0.33.0 la clase y el markup quedan alineados (.mps-skeleton), @keyframes shimmer se namespacea como mps-skeleton-shimmer, y se agregan defaults dimensionales por shape:
<MpsSkeleton /> @* line · 100% × 0.875rem *@
<MpsSkeleton Shape="rect" /> @* rect · 100% × 6rem *@
<MpsSkeleton Shape="circle" /> @* circle · 2.5rem × 2.5rem *@
<MpsSkeleton /> sin parámetros ahora es visible. Cualquier llamada que ya pasaba Width/Height mantiene el override. Soporte prefers-reduced-motion: reduce (WCAG 2.3.3). +7 tests bUnit (761 total).
Acción opcional para consumers: si tu app aplicó el hotfix consumer-side (regla .mps-skeleton en wwwroot/app.css), podés removerlo — el paquete ya la incluye. Ver scripts/migrate-to-components-0.33.0.md. Sin breaking changes.
MpsPageHeader browser tab + Double/TripleProgress 90% opacidadFix bug: la versión anterior tenía <PageTitle>@Title - MOVES</PageTitle> hardcodeado en MpsPageHeader — toda app no-MOVES recibía "MOVES" en sus pestañas. Ahora el sufijo es configurable vía ThemeOptions.AppName:
// Program.cs
builder.Services.AddMpsComponents(o => o.AppName = "MOVES");
MpsPageHeader emite automáticamente <PageTitle>{Title} · {AppName}</PageTitle>. Overrides:
SetBrowserTab="false" → no emite (tu page maneja su propio <PageTitle>).BrowserTabTitle="Corto" → título distinto al del header visual.BrowserTabSuffix="Demo" → sufijo por instancia (override de AppName).BrowserTabSuffix="" → fuerza sin sufijo.DoubleProgress / TripleProgress: barras a 90% opacidad (antes 50%) para mejor legibilidad EVM.
CashflowChart leyenda en CutOffDateLa leyenda del chart ahora se renderiza como HTML propio debajo del gráfico, con dos grupos:
Si CutOffDate es null, la leyenda muestra los totales del rango. Cada entrada usa MoneyDisplay (formato consistente con el resto del design system) y un swatch que replica el estilo visual del chart (columnas 65% opacidad, líneas sólidas, Forecast acum. dashed verde).
Sin cambios de API. Helper estático CashflowChart.ComputeLegendValues(raw, cutOff) para tests. Migración: si testeás contra .cf-summary (header anterior), migrá a .cf-legend.
CashflowChart con acumuladosCashflowChart ahora muestra columnas mensuales + líneas acumuladas en el mismo gráfico (eje Y secundario):
CutOffDate, Forecast > CutOffDate (filtrado automático).<CashflowChart Points="@cashflow"
Currency="USD"
CutOffDate="@DateTime.Today"
Title="Cashflow mensual · Talara U3" />
ShowCumulative="false" para volver al aspecto 0.29.x (sólo columnas). El parámetro ControlDate queda como alias retro-compat de CutOffDate. Sin breaking de API; +10 tests; demo en /catalogo/evm#CashflowChart.
Convención:
--font-mono / .kbd) → atajos de teclado, snippets de código.Tokens nuevos:
:root {
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-numeric: 'Roboto Condensed', 'Inter', system-ui, sans-serif;
--font-table: var(--font-numeric);
--font-chart: var(--font-numeric);
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
}
Recomendación de loading (LCP óptimo). mps-tokens.css ya trae un @import con las fuentes/pesos, pero @import bloquea el render. Para tu host page preferí <link> en App.razor:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;500;700&family=Inter:wght@300;400;500;600;700&display=swap">
Aplicado automáticamente a:
text SVG)..mps-table, .mps-po-table, .mps-deliv-table, .mps-risk-table, .mps-svt-table).Override por consumer (mantener apariencia 0.28.x con JetBrains Mono en valores numéricos):
:root { --font-numeric: var(--font-mono); }
Sin breaking changes.
Si tu app usa un DefaultAccent distinto a Emerald, el cold load mostraba un flash visible de Emerald → tu accent (~300ms). Causa: la paleta vivía como CSS estático Emerald + JS interop que la reescribía post-paint. Fix: nuevos selectors :root[data-mps-accent="..."] + helper MpsThemeAttributes para SSR.
Patrón canónico (sin más workarounds inline). Dos piezas en App.razor:
MpsThemeAttributes.For(...) en el <html>
(emite los data-mps-* en el HTML SSR desde tus defaults de AddMpsComponents):@* App.razor *@
@inject IOptions<ThemeOptions> ThemeOpts
<!DOCTYPE html>
<html lang="es" @attributes="MpsThemeAttributes.For(ThemeOpts.Value)">
<head>
@* 2. Dark MODE (System/per-user) — dinámico, ANTES de cualquier stylesheet *@
<script>
(function(){
try {
var m = localStorage.getItem('mps-color-mode') || 'system';
var dark = m==='dark' || (m==='system' && matchMedia('(prefers-color-scheme: dark)').matches);
if (dark) document.documentElement.setAttribute('data-mps-mode','dark');
} catch(_){}
})();
</script>
@* Syncfusion dual-link (dark arranca disabled) + swap según el mode resuelto arriba *@
<link id="sf-theme-light" rel="stylesheet" href="_content/Syncfusion.Blazor.Themes/tailwind3.css" />
<link id="sf-theme-dark" rel="stylesheet" href="_content/Syncfusion.Blazor.Themes/tailwind3-dark.css" disabled />
<script>
(function(){
if (document.documentElement.getAttribute('data-mps-mode')==='dark') {
document.getElementById('sf-theme-light').disabled = true;
document.getElementById('sf-theme-dark').disabled = false;
}
})();
</script>
<link rel="stylesheet" href="_content/MPS.Components/css/mps-bundle.css" />
<HeadOutlet />
</head>
// Program.cs
builder.Services.AddMpsComponents(opts => opts.DefaultAccent = ThemeAccent.Cobalt);
Resultado: el HTML SSR-rendered ya tiene <html data-mps-accent="cobalt"> antes del primer paint → cero flicker. El ThemeService.SetAsync runtime sigue funcionando para overrides per-user (CSS specificity garantiza que el setProperty inline gane sobre el attribute selector).
¿Por qué dos piezas? El accent/density son estáticos (vienen de
ThemeOptions→ SSR vía@attributes). El dark modeSystemes per-user (depende delocalStorage+ OS del cliente), no se puede resolver en el servidor → va por el script inline. Guía completa:docs/themes/theme-setup-consumer.md.
5 accents soportados con paleta completa (50→800 stops): Cobalt, Indigo, Emerald (default), Amber, Slate. 3 densities (Compact, Comfort, Spacious) y 2 directions (A warm/sobria, B cool/expresiva) también soportan SSR via data-mps-density y data-mps-direction.
Sin breaking changes — los consumers que no adoptan el patrón siguen funcionando con Emerald como default.
4 componentes core que internamente wrappeaban Syncfusion ahora son HTML nativo:
MpsButton → <button> con CSS existente (mps-btn primary/secondary/tertiary/link/destructive).MpsIconButton → <button class="mps-btn icon-only">.MpsRadio → <input type="radio"> con role="radiogroup" y keyboard handling del browser.MpsTabs → <div role="tablist"> con <button role="tab"> y keyboard nav WAI-ARIA (← → / ↑ ↓ / Home / End).API pública conservada — drop-in upgrade. Las clases CSS internas cambiaron: Ghost ahora emite class="mps-btn tertiary" y Danger emite destructive. Si tu CSS custom tenía selectores .mps-btn.ghost/.danger, hay que adaptarlos.
Por qué la migración: independencia de runtime de Syncfusion para los componentes más usados, control completo del CSS, menor superficie de licencia para estos cuatro. Tier 2 (MpsDialog → <dialog>, MpsDrawer, MpsToastHost) en una release futura.
🐛 Bug fix — MpsRangeSlider tooltip: Syncfusion 33.x no interpola C# format strings en SliderTooltip.Format — el tooltip mostraba "USD {600000:N0} - USD {30000000:N0}" literal. Fix: usar OnTooltipChange callback que construye el texto en C#.
💰 Smart compact money — MpsMoneyScope + nueva regla MPS:
@* Antes: cada MoneyDisplay decidía su escala individualmente *@
<MoneyDisplay Value="450_000m" Compact="true" /> @* "USD 450K" *@
<MoneyDisplay Value="3_500_000m" Compact="true" /> @* "USD 3.5M" — distinto criterio *@
@* v0.26.0: scope unifica escala basado en max value *@
<MpsMoneyScope Values="@(new [] { 450_000m, 3_500_000m })">
<MoneyDisplay Value="450_000m" Compact="true" /> @* "MUSD 0,5" *@
<MoneyDisplay Value="3_500_000m" Compact="true" /> @* "MUSD 3,5" *@
</MpsMoneyScope>
Regla MPS nueva: kUSD con 0 decimales (kUSD 450); MUSD con 1 decimal (MUSD 4.500,1 en es-CL). Una página entera usa la escala del monto mayor — coherencia visual.
Breaking en MoneyDisplay compact: el output cambió de "USD 10.5M" (suffix) a "MUSD 10,5" (prefix). Añadidos params Scale: MoneyScale?, Culture: CultureInfo?, y CompactDecimals ahora es int? con default por-escala (override solo si querés saltarte la regla).
🌐 MpsCounter cultura: ahora hereda CultureInfo.CurrentCulture (era InvariantCulture). Con DefaultThreadCurrentCulture = es-CL en MPS.Web, los números se formatean automáticamente con punto thousands y coma decimal.
💬 MpsButton + MpsIconButton — Tooltip param:
<MpsButton Tooltip="Guardar cambios pendientes">Guardar</MpsButton>
<MpsIconButton Icon="e-icons e-edit" Tooltip="Editar fila" />
Renderiza title (browser tooltip nativo) + aria-label (a11y) si AriaLabel no se especificó explícitamente.
🎨 MpsCard — header bg + line toggle:
@* Header con tint del Variant + sin línea inferior *@
<MpsCard Title="Resumen"
Variant="MpsCardVariant.Success"
HeaderTinted="true"
HeaderBorder="false">
...
</MpsCard>
HeaderTinted (default false) aplica --mps-card-tint al fondo del header. HeaderBorder (default true) controla la línea inferior. Combinados generan un header con look cohesionado al body.
MpsEnumSelect<TEnum> (introducido en v0.23) requiere Value: TEnum? (nullable
struct) para soportar el placeholder "Seleccionar...". Esto NO compila con
forms que tienen propiedades non-nullable:
@* NO COMPILA si _form.Status es 'Status' (non-nullable) *@
<MpsEnumSelect TEnum="Status" @bind-Value="_form.Status" />
MpsEnumSelectRequired<TEnum> es una sister component aditiva con
Value: TEnum para el caso non-nullable:
@* OK con _form.Status: Status non-nullable *@
<MpsEnumSelectRequired TEnum="Status" @bind-Value="_form.Status" />
Cuándo usar cuál:
| Escenario | Componente |
|---|---|
Form con [Required] + non-nullable enum prop |
<MpsEnumSelectRequired> |
| Filtro que permite "todas las opciones" (sin selección) | <MpsEnumSelect> (nullable) |
Bump transitivo: Cayaqui.MPS.Metadata 0.4.0+ — los enums con [Display(Name)]
ahora se renderizan con la etiqueta localizada en MpsAutoGrid,
MpsAutoBadge, MpsAutoForm y los renderers de Cayaqui.MPS.Reports.
MpsAutoGrid ahora rinde automáticamente celdas con componentes ricos del design system según attributes/types del DTO:
<StatusChip> automático cuando una propiedad del DTO es de tipo StatusChip.WorkflowStatus (auto-detect, sin atributo).<UserChip> automático cuando se decora con [UserColumn(AvatarSource, RoleSource)] (atributo nuevo en Cayaqui.MPS.Metadata 0.3.0). Apunta a propiedades hermanas del DTO.<MpsBadge> componente real (con MpsBadgeVariant enum tipado v0.20+) en lugar de <span class="mps-badge"> inline.public class ChangeOrderDto
{
public string Code { get; set; } = "";
public StatusChip.WorkflowStatus Status { get; set; } // → automático <StatusChip>
[UserColumn(AvatarSource = nameof(OwnerAvatar), RoleSource = nameof(OwnerRole))]
public string OwnerName { get; set; } = ""; // → automático <UserChip>
public string? OwnerAvatar { get; set; }
public string? OwnerRole { get; set; }
}
Sin breaking changes. Bumpea dependencia de Cayaqui.MPS.Metadata a 0.3.0. Migración en scripts/migrate-to-components-0.24.0.md.
MpsNumeric ahora formatea + parsea con culture es-CL (. thousands, , decimal) en lugar de Invariant. Default Format=N2 con 1234.5m rinde "1.234,50". Sin breaking change.
Los 6 componentes de formulario simples (MpsTextBox, MpsNumeric, MpsSelect, MpsInputMask, MpsCheckBox, MpsToggle) migran de wrappers Syncfusion a HTML-native:
[Collection("Syncfusion")] — no necesitan serializaciónSyncfusion.Blazor.DropDownsNuevo: MpsEnumSelect<TEnum> con auto-projection desde Enum.GetValues y labels desde [Display(Name="…")]:
public enum Status { [Display(Name="Aprobado")] Approved, ... }
<MpsFormField Label="Estado">
<MpsEnumSelect TEnum="Status" @bind-Value="_status" />
</MpsFormField>
Breaking changes (2, mecánicos): MpsTextBox.InputType enum cambia (era SF, ahora propio); MpsSelect TextField/ValueField → TextSelector/ValueSelector. Ver scripts/migrate-to-components-0.23.0.md.
Suite EPC completo + design system con 70 componentes activos del roadmap publicado (v0.20.0 — 5 nuevos: Accordion, Timeline, FileUploader, InputMask, AvatarGroup + improvements Button.Loading/Badge enum/Alert enum/Avatar.Status; v0.19.0 — Cards suite: variants/accents/behaviors + 4 nuevos cards especializados + token --color-info-*; v0.18.4 — fix MpsSelect popup + docs sidebar accent override; v0.18.3 — MpsPageHeader breadcrumbs auto-generados + acento izquierdo del tema en el card + tipografía refinada; v0.18.2 — fix 19 constantes MpsIcons con nombres CSS inválidos en Syncfusion; v0.18.1 — fix MpsDialog compatible con Syncfusion 33.2.3; v0.18.0 — MpsIcons ~150 constantes, IRouteLabelsProvider para breadcrumbs automáticos con label+icono, MpsPageHeader.Card mode; v0.17.0 — RiskHeatmap v2 chips/tooltip/filtro; v0.16.0 — UserProfileCard + UserChip.PopoverContent). Agrupados por sección:
MpsAutoGrid<T>, MpsAutoForm<TModel>, MpsAutoFormField<T>, MpsAutoBadge que leen attrs del DTO desde Cayaqui.MPS.MetadataDistribución propietaria — requiere contrato comercial con Cayaqui. Ver
LICENSE.txt.
dotnet add package Cayaqui.MPS.Components
// Program.cs
using MPS.Components;
builder.Services.AddMpsComponents(); // ThemeService + ToastService (scoped)
// Override de defaults:
builder.Services.AddMpsComponents(opts =>
{
opts.DefaultAccent = ThemeAccent.Emerald;
opts.DefaultDensity = ThemeDensity.Comfort;
opts.DefaultRadiusScale = 10m / 6m; // "10 · Redondeado"
});
La persistencia por usuario del tema (guardar accent/density/radius en BD) no forma parte de este paquete — se implementa con IThemePersistence (interfaz expuesta aquí) y un módulo de administración propio del consumidor.
En _Imports.razor:
@using MPS.Components.Design ← MpsIcons y otros tipos raíz
@using MPS.Components.Design.Buttons
@using MPS.Components.Design.Forms
@using MPS.Components.Design.DataDisplay
@using MPS.Components.Design.Navigation
@using MPS.Components.Design.Overlays
@using MPS.Components.Design.Feedback
@using MPS.Components.Evm
@using MPS.Components.Theming
MpsIconsMpsIcons es una clase estática de constantes (MPS.Components.Design) que centraliza las clases CSS Syncfusion (e-icons e-*) usadas en toda la suite. Evita strings hardcodeados dispersos y hace los usos refactorizables.
// Namespace — ya incluido si seguís los imports recomendados:
@using MPS.Components.Design
<MpsButton IconLeft="@MpsIcons.Add">Nuevo CA</MpsButton>
<MpsButton IconLeft="@MpsIcons.ExportExcel" Variant="MpsButton.ButtonVariant.Ghost">Exportar</MpsButton>
<MpsIconButton Icon="@MpsIcons.Edit" AriaLabel="Editar" Size="MpsButton.ButtonSize.Sm" />
<MpsIconButton Icon="@MpsIcons.Delete" AriaLabel="Eliminar" Size="MpsButton.ButtonSize.Sm" />
new NavItem("Dashboard", "/", MpsIcons.Home, IsActive: true)
new NavItem("Control Accounts", "/ca", MpsIcons.GridView)
new NavItem("WBS", "/wbs", MpsIcons.Folder, Badge: "16")
new NavItem("Cronograma", "/schedule", MpsIcons.Schedule)
new NavItem("Riesgos", "/risk", MpsIcons.Warning)
| Grupo | Ejemplos |
|---|---|
| Actions | Add, Edit, Delete, Save, Download, Upload, Filter, Search, Undo, Redo, … |
| Export | ExportExcel, ExportCsv, ExportPdf, ExportWord, ExportPng, … |
| Navigation | Home, Folder, FolderOpen, Menu, Expand, Collapse, Pin, OpenLink, … |
| Arrows & Chevrons | ArrowDown/Left/Up/Right, ChevronDown/Up/Left/Right + fills |
| Files & Attachments | File, FilePdf, FileDocument, Attachment, Image, Link, … |
| People & Identity | User, People, Password, Location |
| Status / Feedback | Check, CircleCheck, CircleInfo, Warning, Clock, History, Star, … |
| Data & Tables | Table, GrandTotal, SubTotal, Calculation, Level1–Level5, … |
| Charts | Chart, ChartLine, ChartDonut, ChartScatter, ChartColumn, … |
| EVM / Project Controls | Kpi, CriticalPath, GanttGripper, TimelineDay/Week/Month, … |
La galería completa con búsqueda y preview está en /catalogo/icons del proyecto MPS.Web de referencia.
Los CSS/JS se sirven automáticamente bajo _content/MPS.Components/. Snippet recomendado en App.razor — un único bundle más el theme de Syncfusion (que viaja como dependencia transitiva):
<link rel="stylesheet" href="_content/Syncfusion.Blazor.Themes/tailwind3.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-bundle.css" />
<script src="_content/MPS.Components/js/mps-theme.js"></script>
El theme Syncfusion debe ir antes que
mps-bundle.csspara quemps-syncfusion-overrides.css(incluido en el bundle) tenga la última palabra.
Si prefieres tree-shake manual o cargar sólo un subset, sustituye mps-bundle.css por los archivos puntuales en este orden:
<link rel="stylesheet" href="_content/MPS.Components/css/mps-tokens.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-syncfusion-overrides.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-components.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-indicators.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-evm-extras.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-wbs-treegrid.css" />
<link rel="stylesheet" href="_content/MPS.Components/css/mps-gantt.css" />
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("YOUR_LICENSE_KEY");
| Propiedad | Default |
|---|---|
| Accent | Emerald |
| Density | Comfort |
| RadiusScale | 10 / 6 (opción "10 · Redondeado") |
Configurable vía ThemeOptions al registrar AddMpsComponents(opts => { ... }).
Con un DTO decorado usando atributos de Cayaqui.MPS.Metadata, los componentes MpsAutoGrid<T> y MpsAutoForm<TModel> generan columnas y campos automáticamente sin ceremonia manual.
public sealed class ControlAccountRow
{
[Label("Código"), Wbs] public string Code { get; set; } = "";
[Label("Nombre"), Display(Order = 1)] public string Name { get; set; } = "";
[Currency("USD")] public decimal Bac { get; set; }
[Percentage(0)] public decimal PctComplete { get; set; }
[EvmIndicator(EvmKind.Cpi), EvmBand] public decimal Cpi { get; set; }
[Badge] public CaPhase Phase { get; set; }
[Hidden] public Guid Id { get; set; }
}
public enum CaPhase
{
[BadgeColor(BadgeKind.Brand)] Engineering,
[BadgeColor(BadgeKind.Purple)] Procurement,
[BadgeColor(BadgeKind.Warning)] Construction
}
<MpsAutoGrid TItem="ControlAccountRow" DataSource="@rows" Title="Control Accounts" />
<MpsAutoForm TModel="ControlAccountRow" Model="@row" OnValidSubmit="SaveAsync" />
La dependencia Cayaqui.MPS.Metadata se auto-instala como transitive y AddMpsComponents() también llama internamente a AddMpsMetadata().
MpsSidebar + MpsAppHeaderAmbos componentes siguen Untitled UI Pro v8. Patrón canónico para una app:
@* MainLayout.razor *@
@inherits LayoutComponentBase
@inject ThemeService Theme
<div class="app-shell">
<MpsSidebar @ref="_sidebar"
Tone="MpsSidebar.SidebarTone.Branded"
BrandLogo="@(new MpsSidebar.SidebarLogo(
Src: \"/img/brand-app.svg\", @* 200×40 lockup horizontal *@
SlimSrc: \"/img/brand-app-slim.svg\", @* 36×36 icon/iso *@
Alt: \"Mi App\"))"
CompanyLogo="@(new MpsSidebar.SidebarLogo(
Src: \"/img/owner-logo.svg\", @* 160×28 lockup horizontal *@
SlimSrc: \"/img/owner-logo-slim.svg\", @* 28×28 icon/iso *@
Alt: \"Owner Inc\"))"
Collapsible="true"
CollapseStorageKey="myapp.sidebar.collapsed">
<ChildContent>
<MpsSidebarSection>
<MpsSidebarItem Text="Inicio" Href="/" Match="MpsSidebar.ItemMatch.Exact">
<Icon><svg width="18" height="18" ...></svg></Icon>
</MpsSidebarItem>
</MpsSidebarSection>
<MpsSidebarSection Label="Control">
<MpsSidebarItem Text="Proyectos" Href="/projects" Badge="12">
<Icon><svg ...></svg></Icon>
</MpsSidebarItem>
</MpsSidebarSection>
</ChildContent>
<Footer>
<MpsSidebarUser Name="Ana Pérez" Subtitle="ana@cayaqui.com" />
</Footer>
</MpsSidebar>
<div class="app-content">
<MpsAppHeader Sidebar="_sidebar"
ShowHamburger="true"
ShowSearch="true"
SearchPlaceholder="Buscar…"
OnSearch="@HandleSearch"
UserName="Ana Pérez"
UserSubtitle="ana@cayaqui.com">
<UserMenu>
<a href="/perfil">Mi perfil</a>
<a href="/config">Configuración</a>
<button @onclick="SignOut">Cerrar sesión</button>
</UserMenu>
</MpsAppHeader>
<main class="app-main">
@Body
</main>
</div>
</div>
@code {
private MpsSidebar? _sidebar;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) await Theme.InitializeAsync();
}
private void HandleSearch(string q) { /* navegar / filtrar */ }
private async Task SignOut() { /* ... */ }
}
CSS mínimo del shell (en app.css del consumer — el bundle no incluye estos selectores porque varían según el layout):
.app-shell { display: flex; min-height: 100vh; background: var(--color-bg); }
.app-content { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.app-main { flex: 1; min-width: 0; padding: 32px; }
MpsSidebar — opciones| Parámetro | Default | Notas |
|---|---|---|
Variant |
Application (280px) |
Slim (72px solo iconos) |
Tone |
Neutral |
Tinted (lavado accent-50) o Branded (accent-700 con texto claro) |
BrandLogo (v0.15.0) |
null |
SidebarLogo(Src, SlimSrc?, Alt?). Logo del producto en el header. App: 200×40 px. Slim: 36×36 px. |
CompanyLogo (v0.15.0) |
null |
SidebarLogo(Src, SlimSrc?, Alt?). Logo del owner en franja inferior. App: 160×28 px. Slim: 28×28 px. |
Sticky |
true |
Sticky al top del viewport (position: sticky; top: 0; height: 100vh). Setear false si la sidebar va embebida en un layout que no quiere ese comportamiento. |
Collapsible |
false |
Toggle Application↔Slim en el header. |
CollapseStorageKey |
null |
Si seteás un key, el estado collapsed persiste por usuario en localStorage. |
AutoActive |
true |
Infiere item activo desde NavigationManager.Uri. |
ChildContent |
null |
Modo declarativo (<MpsSidebarSection> + <MpsSidebarItem>). |
Sections |
[] |
Modo data-driven legacy con NavSection/NavItem records. |
Sticky requiere viewport scroll. Si tu shell tiene un ancestor con
overflow: autoooverflow: hiddenpropio,position: stickyse ancla a ese ancestor — usualmente no es lo que querés. Mantené el shell sin overflow propio (el body scrollea) y el sticky funciona como espera el patrón Untitled UI v8.
MpsSidebar soporta de forma tipada dos logos parametrizables: del producto (header) y de la compañía/owner (franja inferior, opcional). Cada logo se declara con dos variantes — lockup horizontal para Application mode y icon/iso square para Slim/Collapsed.
<MpsSidebar Tone="MpsSidebar.SidebarTone.Branded"
BrandLogo="@(new MpsSidebar.SidebarLogo(
Src: "/img/brand-myapp.svg",
SlimSrc: "/img/brand-myapp-slim.svg",
Alt: "MyApp Suite"))"
CompanyLogo="@(new MpsSidebar.SidebarLogo(
Src: "/img/owner-logo.svg",
SlimSrc: "/img/owner-logo-slim.svg",
Alt: "Owner Inc"))"
Collapsible="true"
CollapseStorageKey="myapp.sidebar.collapsed">
@* ChildContent / Footer slots ... *@
</MpsSidebar>
public sealed record SidebarLogo(string Src, string? SlimSrc = null, string? Alt = null);
Src — URL del lockup horizontal (Application mode).SlimSrc — URL del icon/iso 1:1 (Slim mode o Collapsed=true). Opcional pero recomendado. Si es null, se reusa Src y puede verse apretado/deformado.Alt — texto alt para accesibilidad.| Slot | Application (sidebar 280px) | Slim/Collapsed (sidebar 72px) |
|---|---|---|
| Brand (producto) | 200×40 px | 36×36 px |
| Company (owner) | 160×28 px | 28×28 px |
.sb-header, .sb-company).CompanyLogo se renderiza con opacity: 0.75 (Neutral/Tinted) ó 0.85 (Branded) — visualmente subordinado al brand del producto.En modo Branded (sidebar pintado con accent-700), los logos a color sobre el fondo oscuro pueden no leer bien. Dos opciones:
Src apuntando a la versión blanca/monocroma del logo (ej. logo-white.svg).fill="#000" o sin fill explícito), agregar la utility class invert-on-branded al <img> del logo invierte automáticamente a blanco vía filter: brightness(0) invert(1). La utility está disponible globalmente — no requiere parámetro adicional.Application (280px) Slim (72px)
┌──────────────────────────┐ ┌──────┐
│ [logo MyApp lockup] ⇄ │ │ [Mi] │ ← BrandLogo.SlimSrc
├──────────────────────────┤ ├──────┤
│ ▸ Dashboard │ │ ▦ │
│ ▸ Proyectos 12 │ │ □ │
│ ▸ Riesgos │ │ ⚠ │
├──────────────────────────┤ ├──────┤
│ [Footer slot — user] │ │ [Av] │ ← Footer slot (opcional, MpsSidebarUser)
├──────────────────────────┤ ├──────┤
│ [logo Owner Inc] │ │ [Ow] │ ← CompanyLogo.SlimSrc
└──────────────────────────┘ └──────┘
Coexistencia con
Footerslot: si declarásFooter(típicamenteMpsSidebarUser) yCompanyLogo, ambos se apilan (Footer arriba, CompanyLogo abajo). El divisor entre ambos se suprime automáticamente para evitar doble línea (regla CSS> .sb-footer + .sb-company).
Precedencia en el header: si pasás un
HeaderRenderFragment custom, ignoraBrandLogoyBrandMark. Si pasásBrandLogo, ignoraBrandMark. La intención es que cada nivel de override sea opt-in claro.
MpsAppHeader — opciones| Parámetro | Default | Notas |
|---|---|---|
Sticky |
true |
Sticky al top del area de contenido. |
ShowHamburger |
false |
Muestra el botón menú a la izquierda. Si querés que aparezca solo en mobile, condicionalo en el consumer (no se fuerza por CSS). |
Sidebar |
null |
Ref al MpsSidebar. Si está seteado, el hamburger toggle-a Collapsed automáticamente. |
OnHamburgerClick |
— | Override manual; toma precedencia sobre Sidebar. |
ShowSearch |
false |
Renderiza un <input type="search"> con icono. OnSearch se dispara al apretar Enter. |
UserName / UserSubtitle / UserImage |
— | Si UserName no es null, renderiza el dropdown de usuario. |
UserMenu |
— | Slot con los items del dropdown (links, botones). Click cierra el menú. |
Left / Center / Right |
— | Slots libres. Center se oculta en <768px. |
MpsAppHeader y MpsSidebar por shell. Para shells anidados (admin / contractor portal) duplicá el shell completo.Branded para shell global (la marca visible). Si tenés un sub-shell (ej. configuración), usar Tinted o Neutral para diferenciar jerarquía.Collapsed=true y dejar al usuario expandir.stroke="currentColor" heredan el color del item según estado (hover/active/branded). Evitá íconos rasterizados.<MpsSidebarItem> con <AuthorizeView Roles="..."> cuando necesites filtrar por rol.MpsFormField ahora cascadea un MpsFieldContext a sus inputs hijos. Los inputs (MpsTextBox, MpsNumeric, MpsSelect, MpsDatePicker, MpsCheckBox) leen el cascade y propagan id, aria-describedby (apunta a hint o error) y aria-invalid automáticamente al input subyacente — sin código extra del consumer.
<MpsFormField Label="Email" Hint="Correo de trabajo" Error="@_emailError" Required="true">
<MpsTextBox @bind-Value="_email" />
</MpsFormField>
Resultado renderizado (simplificado):
<div class="mps-field has-error">
<label class="mps-label" for="mps-fld-abc123">Email <span aria-hidden="true">*</span></label>
<div class="mps-field-control">
<input id="mps-fld-abc123" aria-describedby="mps-fld-abc123-error" aria-invalid="true" ... />
</div>
<div class="mps-field-error" id="mps-fld-abc123-error" role="alert" aria-live="polite">
@_emailError
</div>
</div>
El error se anuncia automáticamente a screen readers vía role="alert" + aria-live="polite".
AriaLabel para controles sin label visibleCuando un input vive fuera de un MpsFormField (ej. checkbox dentro de tabla, button icon-only, dialog con HeaderTemplate sin texto), pasá AriaLabel explícito:
<MpsCheckBox @bind-Checked="@row.Selected" AriaLabel="Seleccionar fila" />
<MpsButton AriaLabel="Cerrar" IconLeft="e-icons e-close" />
<MpsDialog @bind-Visible="_open" AriaLabel="Detalle del proyecto">
<ChildContent>...</ChildContent>
</MpsDialog>
MpsDialog deriva aria-label desde Title automáticamente si no pasás AriaLabel.
0.4.1 retirado: tenía un bug donde el id auto-generado de
MpsFormFieldse regeneraba en cada rerender del padre, rompiendo la conexiónlabel[for]/aria-describedby. Si estás en 0.4.1, subí a 0.4.2 (fix incluido).
Para que el accent del tema (default Emerald) se aplique desde el primer frame, el consumer debe inicializar ThemeService una vez en su layout principal:
@inject ThemeService Theme
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) await Theme.InitializeAsync();
}
}
Sin esto, los tokens accent quedan en su fallback estático (Direction A = emerald) hasta que el usuario cambie el tema desde la UI.
Add two inline scripts and the Syncfusion dual-link to your App.razor <head>, in this exact order:
<script>
(function(){
try {
var m = localStorage.getItem('mps-color-mode') || 'system';
var dark = m==='dark' || (m==='system' && matchMedia('(prefers-color-scheme: dark)').matches);
if (dark) document.documentElement.setAttribute('data-mps-mode','dark');
} catch(_){}
})();
</script>
<link id="sf-theme-light" rel="stylesheet"
href="_content/Syncfusion.Blazor.Themes/tailwind3.css" />
<link id="sf-theme-dark" rel="stylesheet"
href="_content/Syncfusion.Blazor.Themes/tailwind3-dark.css"
disabled />
<script>
(function(){
if (document.documentElement.getAttribute('data-mps-mode')==='dark') {
var l = document.getElementById('sf-theme-light');
var d = document.getElementById('sf-theme-dark');
if (l) l.disabled = true;
if (d) d.disabled = false;
}
})();
</script>
<link rel="stylesheet" href="_content/Cayaqui.MPS.Components/css/mps-bundle.css" />
Then place <MpsColorModeToggle /> anywhere in your layout:
@using MPS.Components.Design.Navigation
<MpsColorModeToggle />
Or switch programmatically:
@inject ThemeService Theme
<button @onclick="() => Theme.SetAsync(mode: ThemeMode.Dark)">Dark</button>
Configure the default in Program.cs:
builder.Services.AddMpsComponents(o =>
{
o.DefaultMode = ThemeMode.System; // Light, Dark, or System (default)
});
Known limitation: In dark mode on first load a brief (~50–150 ms) Syncfusion theme flash may occur because tailwind3.css starts downloading before Script 1 can swap to tailwind3-dark.css. This is the tradeoff of localStorage-based detection.
ThemeService permite cambiar el accent (y density / direction / radiusScale / fontFamily) en tiempo real desde cualquier página. La aplicación re-pinta instantáneamente — sidebar branded, KPI gauges, botones primary, focus rings, etc. — porque internamente el servicio reescribe las CSS custom properties (--color-accent-50 … --color-accent-700) en :root vía JS interop.
public enum ThemeAccent { Cobalt, Indigo, Emerald, Amber, Slate }
5 paletas built-in. Cada acento expone una rampa completa de 9 stops (50/100/200/300/400/500/600/700/800/900) ya validada para contraste WCAG AA en texto y backgrounds. Emerald es el default si no override-as DefaultAccent.
Patrón recomendado: MpsSegmented con un option por accent + binding al ThemeService.Accent.
@page "/configuracion"
@inject ThemeService Theme
@implements IDisposable
<MpsCard Title="Apariencia">
<MpsFormField Label="Color del tema">
<MpsSegmented TValue="ThemeAccent"
Value="@Theme.Accent"
ValueChanged="@(async v => await Theme.SetAsync(accent: v))"
Options="@_accentOpts" />
</MpsFormField>
<MpsFormField Label="Densidad">
<MpsSegmented TValue="ThemeDensity"
Value="@Theme.Density"
ValueChanged="@(async v => await Theme.SetAsync(density: v))"
Options="@_densityOpts" />
</MpsFormField>
</MpsCard>
@code {
MpsSegmented<ThemeAccent>.Option[] _accentOpts = new[]
{
new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Cobalt, "Cobalt"),
new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Indigo, "Indigo"),
new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Emerald, "Emerald"),
new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Amber, "Amber"),
new MpsSegmented<ThemeAccent>.Option(ThemeAccent.Slate, "Slate"),
};
MpsSegmented<ThemeDensity>.Option[] _densityOpts = new[]
{
new MpsSegmented<ThemeDensity>.Option(ThemeDensity.Compact, "Compact"),
new MpsSegmented<ThemeDensity>.Option(ThemeDensity.Comfort, "Comfort"),
new MpsSegmented<ThemeDensity>.Option(ThemeDensity.Spacious, "Spacious"),
};
protected override void OnInitialized() => Theme.Changed += StateHasChanged;
public void Dispose() => Theme.Changed -= StateHasChanged;
}
Theme.Changed es un event Action? que se dispara después de aplicar el cambio. Suscribirse + StateHasChanged permite que la página refleje el Theme.Accent actualizado en bindings (Value= del segmented).
// Cambiar solo accent:
await Theme.SetAsync(accent: ThemeAccent.Cobalt);
// Cambiar varios atributos en un solo apply:
await Theme.SetAsync(accent: ThemeAccent.Indigo, density: ThemeDensity.Compact);
// Volver al default del tenant:
await Theme.SetAsync(accent: ThemeAccent.Emerald, density: ThemeDensity.Comfort);
SetAsync es debounced de facto — múltiples await consecutivos son seguros, pero en UIs reactivas (sliders, color pickers continuos) sumá tu propio debounce client-side para evitar flicker.
Para que la elección del usuario sobreviva F5 y logout/login, registrá una implementación de IThemePersistence:
public interface IThemePersistence
{
Task<ThemeState?> LoadAsync(CancellationToken ct);
Task SaveAsync(ThemeState state, CancellationToken ct);
}
ProtectedLocalStorage (Blazor Server, default)public sealed class LocalStorageThemePersistence : IThemePersistence
{
private readonly ProtectedLocalStorage _store;
private const string Key = "mps.theme";
public LocalStorageThemePersistence(ProtectedLocalStorage store) => _store = store;
public async Task<ThemeState?> LoadAsync(CancellationToken ct)
{
try
{
var r = await _store.GetAsync<ThemeState>(Key);
return r.Success ? r.Value : null;
}
catch { return null; }
}
public async Task SaveAsync(ThemeState state, CancellationToken ct)
=> await _store.SetAsync(Key, state);
}
// Program.cs
services.AddScoped<IThemePersistence, LocalStorageThemePersistence>();
Si tu app necesita que el tema viaje entre dispositivos, persistí ThemeState en una columna JSON sobre la tabla de usuarios:
public sealed class DbThemePersistence : IThemePersistence
{
private readonly ICurrentUser _user;
private readonly AppDbContext _db;
public async Task<ThemeState?> LoadAsync(CancellationToken ct)
{
var u = await _db.Users.FindAsync([_user.Id], ct);
return u?.ThemeJson is not null
? JsonSerializer.Deserialize<ThemeState>(u.ThemeJson)
: null;
}
public async Task SaveAsync(ThemeState state, CancellationToken ct)
{
var u = await _db.Users.FindAsync([_user.Id], ct);
if (u is null) return;
u.ThemeJson = JsonSerializer.Serialize(state);
await _db.SaveChangesAsync(ct);
}
}
ThemeState es un record público con todos los atributos del tema:
public sealed record ThemeState(
ThemeDirection Direction,
ThemeAccent Accent,
ThemeDensity Density,
decimal RadiusScale,
string FontFamily,
ThemeMode Mode);
If you implement IThemePersistence directly and persist theme state to a database, ThemeState now has a 6th required parameter Mode (added in v0.46.0):
// Before (0.45.x):
new ThemeState(Direction, Accent, Density, RadiusScale, FontFamily)
// After (0.46.0):
new ThemeState(Direction, Accent, Density, RadiusScale, FontFamily, Mode)
// When loading from DB with no stored mode yet, use System as default:
Mode: Enum.TryParse<ThemeMode>(savedModeString, true, out var m) ? m : ThemeMode.System
If you store theme columns individually (not as JSON), add a nullable ThemeMode string column to your preferences table:
ALTER TABLE UserPreferences ADD ThemeMode nvarchar(16) NULL;
Para componentes custom que no usen CSS variables (ej. canvas/SVG con colores hardcodeados), suscribirse al evento Changed y recolorear:
@inject ThemeService Theme
@implements IDisposable
<canvas @ref="_canvas" width="400" height="200"></canvas>
@code {
private ElementReference _canvas;
protected override void OnInitialized() => Theme.Changed += OnThemeChanged;
public void Dispose() => Theme.Changed -= OnThemeChanged;
private async void OnThemeChanged()
{
// Theme.Accent cambió — recolorear el canvas custom.
await JS.InvokeVoidAsync("myChart.recolor", _canvas, Theme.Accent.ToString());
StateHasChanged();
}
}
Tip: la mayoría de componentes MPS y tu UI consumer nativa no requieren esto — heredan el accent automáticamente vía CSS variables. Solo te preocupa esto si tenés rendering custom fuera del flujo CSS (canvas/WebGL/charts third-party que reciben colores como argumento, no como CSS).
ThemeService.SetAsync(accent: ...) reescribe los --color-accent-* en :root — el cambio afecta toda la app. Si necesitás que solo el sidebar (o cualquier componente puntual) use un acento distinto al global, sobrescribí las custom properties en un selector más específico — las variables CSS cascadean y solo afectan al sub-árbol donde se redefinen.
<MpsSidebar Class="sb-corporate" Sections="@_sections" />
<style>
.mps-sidebar.sb-corporate {
--color-accent-50: #EEF2FF;
--color-accent-100: #E0E7FF;
--color-accent-700: #4338CA;
}
</style>
Botones, KPIs, focus rings y resto de la UI conservan el acento global. Solo cambia el highlight del item activo del sidebar marcado con Class="sb-corporate".
Variables que consume MpsSidebar (por impacto visual):
| Variable | Dónde se usa |
|---|---|
--color-accent-50 |
Fondo del item activo (Tone Neutral/Tinted) |
--color-accent-100 |
Borde del item activo + tinte de fondo Tone="Tinted" |
--color-accent-700 |
Texto + iconos del item activo |
--color-accent-600 |
Fondo de la sidebar en Tone="Branded" |
Si solo querés cambiar el highlight sin migrar toda la rampa, podés ir más directo:
.mps-sidebar.sb-corporate .sb-item.active { background: #1F2937; color: #FFFFFF; }
Program.csSi tu app es multi-tenant y cada tenant tiene un default distinto:
services.AddMpsComponents(opts =>
{
opts.DefaultAccent = currentTenant.BrandAccent; // ej. ThemeAccent.Cobalt
opts.DefaultDensity = ThemeDensity.Comfort;
opts.DefaultFontFamily = "'Inter', system-ui, sans-serif";
});
El usuario siempre puede sobrescribir vía Theme.SetAsync — el default solo aplica en la primera carga si no hay state persistido.
public enum ThemeDirection { A, B }
| Valor | Efecto visual |
|---|---|
A |
Sidebar neutral (fondo --color-bg), acentos sólo en items activos. Estética corporativa/sobria. Default. |
B |
Sidebar usa el acento como color de fondo. Estética más expresiva/colorida. |
// Cambiar a dirección B:
await Theme.SetAsync(direction: ThemeDirection.B);
MpsColorModeToggle — props| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
Size |
MpsSegmentedSize |
Md |
Tamaño del toggle: Sm · Md · Lg |
Class |
string? |
null |
Clase CSS adicional |
@using MPS.Components.Design.Navigation
<MpsColorModeToggle /> @* Md (default) *@
<MpsColorModeToggle Size="MpsSegmentedSize.Sm" /> @* compacto para toolbar *@
Para que componentes custom del consumer reaccionen automáticamente al dark mode, usar exclusivamente estos tokens (no hardcodear colores):
| Token | Descripción | Uso típico |
|---|---|---|
--color-bg |
Fondo de app/página | body, main, page wrapper |
--color-bg-subtle |
Fondo alternativo | Table rows alternadas, cards secundarias |
--color-surface |
Superficies elevadas | Cards, dropdowns, modals |
--color-surface-alt |
Superficies más elevadas | Popups sobre cards |
--color-border |
Borde estándar | Inputs, tablas, divisores |
--color-border-strong |
Borde enfatizado | Focus rings, separadores importantes |
--color-text |
Texto primario | Títulos, labels, body copy |
--color-text-secondary |
Texto secundario | Subtítulos, descripciones |
--color-text-tertiary |
Texto apagado | Placeholders, metadata |
--color-accent-600 |
Acento principal activo | Botones primary, links, highlights |
Ejemplo de componente custom dark-compatible:
.mi-tarjeta {
background: var(--color-surface);
border: 1px solid var(--color-border);
color: var(--color-text);
border-radius: calc(var(--radius-base) * 1px);
}
.mi-tarjeta .descripcion {
color: var(--color-text-secondary);
}
.mi-tarjeta .accion {
color: var(--color-accent-600);
}
El selector dark activo es [data-mps-mode="dark"] en <html> — los tokens se redefinen automáticamente bajo ese selector en mps-tokens.css.
Lista resumida — para sintaxis + props + casos completos ver /docs/components en el repo.
MPS.Components.Design.Buttons)| Componente | Versión | Propósito |
|---|---|---|
MpsButton |
0.1.0 | Botón base — wrapper de SfButton. Variants: Primary / Secondary / Ghost / Danger / Link. Sizes Sm / Md / Lg. Slots IconLeft / IconRight (clase CSS, ej. e-icons e-plus). AriaLabel para botones icon-only o texto no descriptivo. HtmlType="button" (default), "submit", "reset". |
MpsIconButton |
0.1.0 | Botón cuadrado solo con icono. Required: Icon (clase CSS). Required best-practice: AriaLabel. |
MpsButtonGroup |
0.21.0 | Toggle data-driven entre opciones (Items: IReadOnlyList<MpsButtonGroupItem> + @bind-Value). Reusa MpsButton.ButtonSize. Selected item recibe ButtonVariant.Primary. |
v0.15.0 fix: tanto
MpsButtoncomoMpsIconButtonenrutabanTypeyaria-labelcomo atributos sueltos alSfButtoninterno. En Syncfusion 33.2.3,SfButtondeclaraHtmlAttributescon[Parameter(CaptureUnmatchedValues=true)]— cualquier atributo no reconocido choca con la asignación explícita y disparaInvalidOperationExceptional renderizar. Ahora ambos componentes consolidan los atributos HTML en el diccionarioHtmlAttributesinterno (incluyendotypeyaria-label). Cero impacto en tu API —HtmlTypeyAriaLabelsiguen siendo los mismos parámetros del consumer.
<MpsButton Variant="MpsButton.ButtonVariant.Primary"
IconLeft="e-icons e-plus">Nuevo CA</MpsButton>
<MpsButton Variant="MpsButton.ButtonVariant.Ghost"
IconLeft="e-icons e-export-excel">Exportar</MpsButton>
<MpsIconButton Icon="e-icons e-edit"
AriaLabel="Editar fila"
Size="MpsButton.ButtonSize.Sm" />
<MpsButton HtmlType="submit"
Variant="MpsButton.ButtonVariant.Primary"
Block="true">Guardar cambios</MpsButton>
MPS.Components.Design.Forms)| Componente | Versión | Propósito |
|---|---|---|
MpsFormField |
0.1.0 | Wrapper label + hint + error. |
MpsTextBox |
0.1.0 | Input texto (single/multi/password). |
MpsNumeric |
0.1.0 | Numérico con stepper, min/max, format. |
MpsSelect<TValue, TItem> |
0.1.0 | DropDown genérico. |
MpsDatePicker |
0.1.0 | Selector de fecha. |
MpsCheckBox |
0.1.0 | Checkbox 2-state / tristate. |
MpsRadio<TValue> |
0.1.0 | Grupo radio genérico. |
MpsToggle |
0.1.0 | Switch on/off. |
WbsPicker |
0.12.0 | Selector jerárquico CA/WP con búsqueda + chip. |
PeriodNavigator |
0.12.0 | < Apr-2026 > Day/Week/Month/Quarter/Year. |
DateRangePicker |
0.12.0 | Rango con 8 presets + Custom + ExtraPresets. |
CurrencyInput |
0.12.0 | Numeric + currency selector, default 0 decimales. |
CodedTextbox |
0.12.0 | Auto-formato WBS/CBS uppercase + grouping. |
SearchCombo<TItem> |
0.12.0 | Autocomplete genérico con debounce + match highlight. |
MpsFileUploader |
0.20.0 | Drag-drop dropzone; emite IBrowserFile[]; validación MIME + tamaño; OnRejected. |
MpsInputMask |
0.20.0 | Wrapper SfMaskedTextBox con presets RutChile/RucPeru/PhoneIntl. |
MpsRangeSlider |
0.22.0 | Slider dual-handle numérico (wrapper SfSlider<double[]> Range). API decimal. Format/Prefix/Suffix en tooltips. |
MpsColorPicker |
0.22.0 | Color picker free-form (Picker) o restringido a paleta (Palette). Wrapper SfColorPicker. |
MPS.Components.Design.DataDisplay)MpsAvatar · MpsBadge · StatusChip · MpsCard · MpsStatCard · MpsMediaCard · MpsReviewCard · MpsActionCard · MpsEmpty · MpsProgress · MpsGrid<T> · MpsGridColumn · MpsGridActionColumn · MpsAutoGrid<T> · MpsAutoBadge + nuevos en 0.11.0/0.13.0/0.19.0/0.20.0:
| Componente | Versión | Propósito |
|---|---|---|
| AttachmentList | 0.11.0 | Lista archivos + extension icons + preview/download/delete. |
| BarChart | 0.13.0 | Wrapper SfChart Vertical/Horizontal con paleta MPS. |
| DonutChart | 0.13.0 | SfAccumulationChart con InnerRadius + center label. |
| StackedBarChart | 0.13.0 | N series stacked, modo Percent (StackingColumn100). |
| UserChip | 0.13.0 | Avatar + nombre + rol compacto inline, 3 tamaños. |
| MpsStatCard | 0.19.0 | Card de métricas inline con trend indicator. |
| MpsMediaCard | 0.19.0 | Card con imagen (top cap o side). |
| MpsReviewCard | 0.19.0 | Rating estrellas + autor. |
| MpsActionCard | 0.19.0 | Header con persona + dropdown de acciones. |
| MpsTimeline | 0.20.0 | Eventos cronológicos con dots por MpsTimelineStatus; orientación Vertical/Horizontal. |
| MpsAvatarGroup | 0.20.0 | Stack horizontal de N avatares con overlap -8px; excedente colapsa a chip +N. |
| MpsKanban<TItem> | 0.21.0 | Kanban genérico con selector functions (IdSelector, ColumnSelector) y drag-drop HTML5 native. OnItemMoved reporta MpsKanbanMove<TItem>. |
| MpsRating | 0.21.0 | Rating estrellas standalone. Interactive (@bind-Value + hover preview) o read-only. 3 sizes (Sm/Md/Lg). |
| MpsCounter | 0.21.0 | Número animado con easing cubic-out. Format/Prefix/Suffix/Culture configurables. Duration=0 = set inmediato. |
| MpsTreeView<TNode> | 0.22.0 | Tree view genérico con selector functions (IdSelector, ParentSelector, LabelSelector, IconSelector). Wrapper SfTreeView. Single-select + ExpandAll. |
| MpsListGroup | 0.44.0 | Lista estilizada model-driven. Icono, subtítulo, badge, @bind-SelectedItemId, Disabled, Flush, ItemTemplate. |
| MpsFileManager | 0.44.0 | Browser Grid/Lista de archivos. Auto-icono por extensión. @bind-ViewMode, OnOpen, OnDelete. |
MPS.Components.Design.Navigation)MpsBreadcrumbs · MpsPageHeader · MpsSegmented<TValue> · MpsSidebar (+ MpsSidebarSection + MpsSidebarItem + MpsSidebarUser) · MpsTabs · MpsAppHeader + nuevo:
| Componente | Versión | Propósito |
|---|---|---|
| MpsStepper | 0.11.0 | Wizard multi-paso con CanAdvance/CanFinish + AllowJumpBack. |
| MpsPagination | 0.22.0 | Page nav hand-rolled con ellipsis + SortedSet dedup. ShowSummary, ShowSizeSelector opcionales. |
| MpsColorModeToggle | 0.46.0 | Segmented Light/System/Dark toggle. Requiere ThemeService. |
| MpsFullscreenToggle | 0.51.0 | Botón toggle fullscreen browser. Requiere mps-fullscreen.js en App.razor. EnterLabel, ExitLabel, Class. |
IRouteLabelsProviderRegistrar una única vez en DI y todos los breadcrumbs con AutoGenerate="true" resuelven label + icono automáticamente sin parámetros por página.
1 — Implementar en la app:
using MPS.Components.Design.Navigation;
public sealed class AppRouteLabelsProvider : IRouteLabelsProvider
{
private static readonly Dictionary<string, RouteInfo> Routes =
new(StringComparer.OrdinalIgnoreCase)
{
["/"] = new("Inicio", "e-icons e-home"),
["/proyectos"] = new("Proyectos", "e-icons e-gantt-chart"),
["/proyectos/overview"] = new("Vista General", "e-icons e-eye"),
["/control-accounts"] = new("Control Accounts", "e-icons e-hierarchy"),
["/riesgos"] = new("Riesgos", "e-icons e-warning"),
["/schedule"] = new("Cronograma", "e-icons e-schedule"),
// Segmentos sueltos (fallback cuando el path completo no matchea)
["overview"] = new("Vista General", "e-icons e-eye"),
};
public RouteInfo? GetRoute(string pathOrSegment) =>
Routes.TryGetValue(pathOrSegment, out var info) ? info : null;
}
2 — Registrar en Program.cs (una sola vez):
builder.Services.AddSingleton<IRouteLabelsProvider, AppRouteLabelsProvider>();
3 — Usar en cualquier página sin parámetros extra:
<MpsBreadcrumbs AutoGenerate="true" />
Prioridad de resolución por segmento: Items explícito → RouteLabels param → IRouteLabelsProvider DI → title-case del segmento.
RouteInfo(string Label, string? Icon) — Icon acepta cualquier clase CSS de Syncfusion (ej. "e-icons e-home").
Si IRouteLabelsProvider no está registrado en DI, el componente lo ignora silenciosamente (sin excepción).
MPS.Components.Design.Overlays)MpsDialog · MpsDrawer · MpsPopover · MpsTooltip · MpsDropdownMenu · MpsCommandMenu + nuevo:
| Componente | Versión | Propósito |
|---|---|---|
| ConfirmDialog | 0.11.0 | Wrapper Default/Danger/Warning sobre MpsDialog + IsBusy. |
MPS.Components.Design.Feedback)MpsAlert · MpsSkeleton · MpsToastHost (con ToastService) + nuevos en 0.13.0/0.20.0/0.51.0:
| Componente | Versión | Propósito |
|---|---|---|
| MpsBanner | 0.13.0 | Sticky top con 5 variants — Env purple gradient para QA/STAGING/DEV. |
| MpsEmptyStateIllustrated | 0.13.0 | 6 illustration kinds inline SVG + acción opcional. |
| MpsAccordion | 0.20.0 | Contenedor colapsable template-based vía CascadingValue. Multiple permite varios abiertos. |
| MpsAccordionItem | 0.20.0 | Sección hija de MpsAccordion. Disabled suprime click. Animación chevron + body via CSS. |
| MpsSpinner | 0.38.0 | Spinner circular CSS puro. Size (Xs/Sm/Md/Lg), Color (Primary/Muted/White/Success/Warning/Danger). |
| MpsReconnectModal | 0.51.0 | Diálogo reconexión Blazor Server. Requiere mps-reconnect.js (type="module") después de blazor.web.js. Todos los textos son [Parameter]. |
MPS.Components.Engineering.Workflow)| Componente | Versión | Propósito |
|---|---|---|
MpsWorkflowEditor |
0.51.0 | Pipeline editor interactivo. Nodes (IReadOnlyList<NodeDef>), @bind-ActiveKeys, Presets opcionales, Disabled. Nodos fijos siempre activos; opcionales toggle por click. Nested types: NodeDef(Key, Label, Title, IsFixed, IsTerminal), Preset(Label, ActiveKeys). |
MpsWorkflowMini |
0.51.0 | Vista compacta read-only del pipeline. Mismos Nodes (tipo MpsWorkflowEditor.NodeDef) + ActiveKeys. Badge con conteo de pasos activos. |
MPS.Components.Engineering.Documents)| Componente | Versión | Propósito |
|---|---|---|
MpsDocCard |
0.41.0 | Card de documento con estado, revisión y acciones. |
MpsDocRevisionList |
0.41.0 | Lista revisiones de un documento. |
MpsDocStatusBadge |
0.41.0 | Badge de estado de documento. |
MpsEngProgressTable |
0.42.0 | Tabla Plan/Forecast/Actual/Var por documento con hitos configurables. |
MPS.Components.Evm) — 42 componentesP1 EVM/AACE (8): EvmKpiStrip · KpiCard · MpsGauge · MpsBulletGauge · CpiGauge · SpiGauge · CpiIndicator · SpiIndicator · VarianceIndicator · ForecastIndicator · EvmSemaforo · TrendChip · DoubleProgress · TripleProgress · EvmCashflowCurve · PhysicalProgressCurve · TcpiIndicator · EarnedScheduleIndicator · HealthDot · VariancePill · TrendDelta · MpsSparkline · CsiIndicator · EvmControlChart
P2 Cost Controls (7): MoneyDisplay · QuantityDisplay · CommitmentGauge · Waterfall · CashflowChart · ContingencyDrawdown · ManagementReserveTracker
Otros pre-roadmap: GanttChart · WbsTreeGrid · WbsKanban · R9cRow · R9cTable · RiskHeatmap · ChangeOrderLog · PurchaseOrderRegister · DeliverablesProgressTable · LookaheadGrid
| Componente | Versión | Sección | Propósito |
|---|---|---|---|
MilestoneStrip |
0.10.0 | P3 Schedule | Barra horizontal hitos + health + Hoy + critical rombo. |
ResourceHistogram |
0.10.0 | P3 Schedule | StackingColumn por categoría + capacity stripline. |
CriticalPathSummary |
0.10.0 | P3 Schedule | Widget ejecutivo float + critical + slip + next milestone. |
ScheduleVarianceTable |
0.10.0 | P3 Schedule | Tabla baseline/current/forecast + slip pill + indent WBS. |
BurndownChart |
0.10.0 | P3 Schedule | Plan dashed + Actual con markers + status burn rate. |
RiskRegisterTable |
0.9.0 | P4 Risk | Tabla PMI completa con filtros + búsqueda + drill-down. |
TornadoChart |
0.9.0 | P4 Risk | Sensitivity ranking low/high simétricas. |
MonteCarloHistogram |
0.9.0 | P4 Risk | Distribución QRA + percentile striplines (P50/P80/P90). |
RiskBoard |
0.9.0 | P4 Risk | Kanban PMI lifecycle. |
RiskTrendLine |
0.9.0 | P4 Risk | Sparkline burden total + TrendDelta LowerIsBetter. |
ApprovalWorkflow |
0.11.0 | P5 Scope | Cadena aprobadores con states + comments + overall computed. |
RaciMatrix |
0.11.0 | P5 Scope | Role × actividad + validación PMBoK 9.1.2.1 automática. |
CommodityPriceChart |
0.14.0 | P8 Mining | Cu/Au/Li temporal + baseline stripline + Δ% pill. |
StockpileLevel |
0.14.0 | P8 Mining | Barra horizontal + bandas operacionales + status auto. |
ProductionCurve |
0.14.0 | P8 Mining | Plan vs Actual + nameplate + status achievement %. |
ShiftSchedule |
0.14.0 | P8 Mining | Calendario semanal turnos Day/Night/Maint/Off/Custom. |
4 componentes nuevos:
MpsRangeSlider — slider dual-handle numérico (wrapper SfSlider Range).MpsColorPicker — color picker free-form + palette (wrapper SfColorPicker).MpsTreeView<TNode> — tree view genérico con selector functions (wrapper SfTreeView).MpsPagination — page nav hand-rolled con ellipsis dedup.Sin breaking changes.
4 componentes nuevos:
MpsKanban<TItem> — kanban genérico con selector functions y drag-drop HTML5 native.MpsRating — rating estrellas standalone (interactive / read-only).MpsCounter — número animado con easing cubic-out + cancellation correcta.MpsButtonGroup — toggle data-driven entre opciones.3 improvements opt-in:
MpsProgress.Shape="Circular" con SVG ring.MpsTabs.Orientation="Vertical" (mapea a Syncfusion).MpsTooltip.ContentTemplate para markup rico.Refactor interno: WbsKanban ahora wrappea MpsKanban<KanbanPackage> — API pública idéntica. Sin breaking changes.
5 componentes nuevos:
MpsAccordion + MpsAccordionItem — secciones colapsables template-based con CascadingValue.MpsTimeline — eventos cronológicos, vertical/horizontal con status-colored dots.MpsFileUploader — drag-drop dropzone (sin HTTP); emite IBrowserFile[].MpsInputMask — wrapper sobre SfMaskedTextBox con presets RUT/RUC/Phone.MpsAvatarGroup — stack de N avatares con chip +N.4 improvements:
MpsButton.Loading con spinner inline.MpsBadge enum + Pill + Outline modifiers.MpsAlert enum + Dismissible + OnDismiss.MpsAvatar.Status overlay dot.Breaking minor: MpsBadge.Variant y MpsAlert.Variant pasaron de string a enum (MpsBadgeVariant/MpsAlertVariant). Migración mecánica vía find/replace; VariantString [Obsolete] provee escape hatch hasta v1.0. Detalles en scripts/migrate-to-components-0.20.0.md.
MpsCard refactored — 6 variants semánticas (Default | Primary | Success | Warning | Danger | Info), 4 modos de acento (Bar | Border | Tinted | None), 2 densidades (Comfortable | Compact). Todos los params opt-in; defaults 100% backward-compat. Cards con Title muestran una barra vertical gris muy suave por default (Accent="Bar"); suprimir con Accent="MpsCardAccent.None".Collapsible (@bind-Open), Closable (OnClose), Fullscreen (ESC sin JS interop), Tabs (@bind-ActiveTabId + slot TabbedContent).MpsStatCard (métrica + trend inline), MpsMediaCard (image cap top/side), MpsReviewCard (rating estrellas + autor), MpsActionCard (header persona + dropdown).--color-info-* en mps-tokens.css (Direction A + B). Resuelve referencias huérfanas pre-existentes en mps-evm-extras.css y mps-wbs-treegrid.css.UserProfileCard refactorizado internamente para envolver <MpsCard NoPadding> — API pública idéntica.MpsIcons — clase estática (~150 constantes) con iconos Syncfusion agrupados por categoría semántica (Actions, Navigation, Export, Files, People, Status, Data, Charts, EVM, Arrows). Namespace MPS.Components.Design. Elimina strings hardcodeados y hace los usos refactorizables.IRouteLabelsProvider + RouteInfo — interfaz DI para resolución automática de label + icono en MpsBreadcrumbs. Registrar AddSingleton<IRouteLabelsProvider, MyProvider>() una sola vez en Program.cs; todos los breadcrumbs con AutoGenerate="true" lo consumen automáticamente.MpsPageHeader.Card — nuevo parámetro bool Card = true que envuelve el header en una card blanca con borde gris. Personalizable con CardBackground y CardBorderColor (valores CSS arbitrarios).MpsDialog fix — DialogEvents.OnOverlayClick renombrado a OnOverlayModalClick en Syncfusion 33.2.3. Corrige InvalidOperationException en runtime al renderizar cualquier modal.e-icons de Syncfusion — bundle más pequeño, consistencia visual con el design system.RiskHeatmap fue completamente rediseñado. Las celdas ahora muestran chips con el ID del riesgo (R-01, O-02) en lugar de un contador, tooltip rico con título/responsable/categoría/score al hover, overflow "+N más" con popover de lista completa, y un filtro segmentado Inherente / Residual / Objetivo. Las amenazas se distinguen en rojo y las oportunidades en verde.
Nuevos campos en RiskItem (todos nullable, backwards-compatible):
ProbabilityResidual, ImpactResidual — coordenadas de la vista Residual.ProbabilityTarget, ImpactTarget — coordenadas de la vista Objetivo.IsOpportunity — true para oportunidades (chip verde).Nuevos parámetros en RiskHeatmap:
View + ViewChanged — binding bidireccional (@bind-View).ShowViewFilter — muestra/oculta el selector de vista (default true).Cada versión tiene su script de migración en scripts/migrate-to-components-X.Y.Z.md del repo. Para upgrades multi-versión (e.g. 0.4.x → 0.14.0) usar scripts/consumer-upgrade-to-0.10.0.md + el script de la versión target.
Para greenfield (proyecto Blazor WebApp nuevo): scripts/consumer-setup.md configura todo desde cero (Program.cs + App.razor + MainLayout).
El catálogo vivo (demos + props detallados + ejemplos) está en github.com/Cayaqui/MPS_DESIGN:
/docs/components/*.md — sintaxis, ejemplos y tabla de props para cada componente./docs/roadmap-componentes.md — estado del roadmap (47 items, P1–P8 cerrado)./CHANGELOG.md — historial de versiones con breaking changes y migration notes./scripts/migrate-to-components-*.md — scripts de migración por versión (0.5.0 → 0.14.0)./docs/infrastructure/metadata.md — integración con Cayaqui.MPS.Metadata.| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.65.9 | 89 | 6/15/2026 |
| 0.65.8 | 95 | 6/13/2026 |
| 0.65.7 | 93 | 6/13/2026 |
| 0.65.6 | 88 | 6/13/2026 |
| 0.65.5 | 90 | 6/13/2026 |
| 0.65.4 | 95 | 6/13/2026 |
| 0.65.3 | 99 | 6/13/2026 |
| 0.65.2 | 103 | 6/12/2026 |
| 0.65.1 | 91 | 6/12/2026 |
| 0.65.0 | 94 | 6/12/2026 |
| 0.63.0 | 90 | 6/12/2026 |
| 0.62.1 | 100 | 6/8/2026 |
| 0.62.0 | 98 | 6/7/2026 |
| 0.61.1 | 100 | 6/4/2026 |
| 0.60.3 | 133 | 6/3/2026 |
| 0.60.2 | 151 | 6/1/2026 |
| 0.60.1 | 99 | 6/1/2026 |
| 0.60.0 | 104 | 6/1/2026 |
| 0.59.0 | 120 | 5/30/2026 |
0.65.9 — ThemeAccent.Codelco: nuevo accent basado en Pantone 166C (copper-orange #E55302, Marca Maestra Codelco MNG2019). Paleta completa 8-stop light + dark. ThemeSwitcher swatch incluido. Sin breaking changes.
0.65.8 — YNEX table fidelity: .mps-table y Syncfusion .e-grid/.e-treegrid alineados a YNEX. Headers sin uppercase ni letter-spacing, font-size var(--text-sm), font-weight 600, color var(--color-text), background var(--color-surface). Border-radius var(--radius-md). .mps-table td color var(--color-text). Sticky first-col background corregido a var(--color-surface). Untitled preset reintroduce estilo original: radius-xl, uppercase + letter-spacing, font-size xs, weight 500, color tertiary, bg surface-alt. Sin breaking changes.
0.65.7 — YNEX card fidelity: .mps-card margin-block: 1.5rem (espaciado vertical entre cards). .mps-card-footer sin background (era var(--color-surface-alt), YNEX usa transparent). .mps-card-group y .mps-kpi-tile zerean el margen (el gap del grid maneja el espaciado). Sin breaking changes de API.
0.65.6 — YNEX form fidelity global: inputs/textareas usan border ligero (--color-border), border-radius 6px (radius-sm), sin shadow en reposo, hover sutil (border-strong), focus solo cambia border-color (sin ring 4px). Labels 0.8rem weight 600. Placeholders weight 500. Checkboxes/radios border 1px ligero. Botones radius 6px (radius-sm). Untitled preset preserva estilo original: radius-md + shadow-xs para inputs, label var(--text-sm) weight 500, btn radius-md. Sin breaking changes de API.
0.65.5 — Fix: .mps-kpi-tile--info .mps-card usaba border-color (ignorado con border:none base de 0.65.4); corregido a border: 1px solid. 0.65.4 — MpsCard YNEX fidelity global: base card ahora coincide con YNEX por defecto (sin border, border-radius 8px/radius-md, bottom shadow 0 0.125rem 0 rgba(10,10,10,.06)); dark mode override explícito (rgba 30%). Padding base alineado a YNEX: header/footer 16px/20px, body 20px. Title 0.9375rem weight 700. Untitled preset mantiene su estilo propio via [data-mps-theme="untitled"] overrides (border, radius-xl, shadow-sm, padding original, title text-lg 600). Sin breaking changes de API. 0.65.3 — MpsPageHeader full-bleed: patrón margin negativo + padding simétrico. El consumer establece --mps-ph-bleed-x en el contenedor padre (igual al padding horizontal del contenedor). El header extiende su fondo/sombra de borde a borde mientras el contenido interno alinea con el resto de la página. Sin bleed (default 0px): comportamiento actual sin márgenes negativos. 0.65.2 — MpsPageHeader: margin-left e margin-right explícitamente 0 (era margin-bottom solamente; header HTML puede heredar márgenes de resets). 0.65.1 — MpsPageHeader: padding horizontal 0 (era var(--sp-6)), width: 100%. El consuming layout provee el padding horizontal. 0.65.0 — MpsSidebar YNEX behavior completo: sidebar fixed (position: fixed, full-height), mobile off-canvas drawer con backdrop (transform slide-in <1024px), flyout submenus animados opacity/visibility/transform (era display:none/block), ToggleSidebarAsync() viewport-aware (desktop=collapse/expand, mobile=drawer), auto-close drawer on navigation, CSS layout utilities mps-sb-shell/mps-sb-content con :has() auto margin-left. Sin breaking changes. 1166 tests. 0.64.0 — MpsPageHeader YNEX redesign: full-width white band, dual-layer blurred shadow, breadcrumbs below subtitle. Eliminados parámetros obsoletos: Card/Tinted/CardBackground/CardBorderColor. 0.63.0 — Responsive móvil por defecto. Los 184 componentes renderizan correctamente a 390px sin configuración: mecanismo híbrido container queries (componentes embebibles se adaptan a su celda) + media queries viewport (solo layout global) + pointer:coarse (touch targets 44px WCAG 2.5.8, 40px en items densos). Tablas: patrón .mps-table-scroll (scroll-x con hint de sombras + primera columna sticky bajo 640px de contenedor + min-width configurable vía --mps-table-min-width) aplicado a R9C, RiskRegister, PurchaseOrder, Deliverables, ScheduleVariance, ChangeOrderLog, Lookahead, EngProgress, RACI; SfGrid/SfTreeGrid usan scroll interno Syncfusion con min-width override (sticky vía FrozenColumns = follow-up). Charts: nuevo MpsChartResponsive (public static, idempotente) aplicado a los 24 charts ApexCharts — bajo 640px viewport: leyenda abajo, dataLabels off, labels 10px, toolbar oculto; Timeline/Gantt con scroll-x y ancho mínimo 700px. Layout: modals/dialogs full-screen bajo 640px (incl. SfDialog), drawers 100%, toasts stretch, AppHeader alineado a convención 640/1024, sidebar off-canvas bajo 1024px con nuevos parámetros MpsSidebar.MobileOpen/MobileOpenChanged (@bind) y backdrop. Forms: EditForm de MpsAutoForm con clase .mps-autoform (container), submit full-width en contenedor angosto. Fixes colaterales: .mps-radio .box tenía tamaño 0x0 (invisible desde siempre) — ahora 18px base; botón delete de MpsFileManager era hover-only (inaccesible touch) — siempre visible; MpsCommandMenu/popovers/dropdowns con cap calc(100vw-16px). Sin breaking changes: API solo aditiva (MobileOpen/MobileOpenChanged, MpsChartResponsive). +47 tests (1158 total). Auditoría completa en docs/components/2026-06-11-mobile-audit.md. 0.62.1 — Fix MpsBarChart NaN labels en barras horizontales. Xaxis.Type ahora Numeric (no Category) para Orientation=Vertical (barras horizontales); Yaxis.Labels.Formatter usa función identidad para categorías string en vez del formateador numérico — evita que Intl.NumberFormat() devuelva NaN al recibir strings de etiqueta.
0.62.0 — Badge i18n en columnas manuales (issue #3). MpsGridColumn Kind=AutoBadge sin DtoType/PropertyName (columnas manuales con <Columns>) ahora resuelve el texto del badge vía [Display(Name)] del miembro del enum y el color vía [BadgeColor] del miembro — antes caía a enum.ToString() (inglés) y color gris. MpsAutoBadge standalone también lee [Display(Name)] para el texto (el color por [BadgeColor] vía EnumBadgeMap se mantiene, indexado por nombre del miembro). Fallback a ToString() cuando no hay [Display]. Aditivo, sin breaking — el modo AUTO (resolver-driven) queda igual. +4 tests (1135 total).
0.61.1 — Fix dependencia: requiere Cayaqui.MPS.Metadata 0.20.0. La 0.61.0 declaraba dep en Metadata 0.19.0, cuyo binario publicado tenía `IExtendedPropertyResolver` en el namespace raíz `MPS.Metadata` mientras el source ya lo había movido a `MPS.Metadata.Resolver` — provocaba `TypeLoadException` en runtime al renderizar grids/componentes metadata-driven. Sin otros cambios respecto a 0.61.0.
0.61.0 — MpsAppHeader: action items estilo YNEX (opt-in). Nuevos params: ShowNotifications + Notifications/NotificationBadge/PulseBadge/OnNotificationClick/OnViewAllNotifications (bell + badge unread con cap 9+ + dropdown), ShowFullscreen (reusa MpsFullscreenToggle), ShowAppsGrid + AppShortcuts/OnAppShortcutClick (apps-grid e-grid-view), ShowLanguageSelector + Languages/CurrentLanguageCode/OnLanguageChanged. Nuevos records MpsHeaderNotification/MpsHeaderAppShortcut/MpsHeaderLanguage. Estado unificado: un solo dropdown abierto a la vez + backdrop click-outside (incluye el user-menu existente). Aditivo, sin breaking.
0.60.3 — Nuevo accent de marca ThemeAccent.AndesIron (Andes Iron SpA, minera): rust óxido de hierro #ED6F51, escala 50–800 light+dark en mps-tokens.css + mps-theme.js, swatch en MpsThemeSwitcher. Aditivo, sin breaking.
0.60.2 — MpsColumnChart<T>: nuevo ColorField (Func<TItem,string?>) — color por columna (distributed mode, serie única) para color semántico / atenuación de cross-filter. Aditivo.
0.60.1 — MpsLineChart<T> y MpsColumnChart<T>: nuevo OnPointClick (EventCallback<TItem>) vía OnDataPointSelection — habilita cross-filter por columna/punto (ej. distribución por programa en dashboards Portfolio). Aditivo, sin breaking.
0.60.0 — Charts: 3 nuevos + 2 mejoras de interactividad (cierra la migración fuera de Syncfusion). Nuevos: MpsLineChart<T> (serie única o multi-serie vía Series=MpsChartSeries<T>, marcador ControlDate con XAxis=DateTime), MpsColumnChart<T> (agrupado/apilado, Plan-Real-EAT), MpsPieChart (motor Donut con InnerRadius=0, hereda interactividad). MpsDonutChart/MpsPieChart: OnItemClick (EventCallback<DonutItem>), SelectedLabel + DimUnselected (atenúa no seleccionados para cross-filter), color semántico por item. MpsScatterChart<T>: OnPointClick (drill-down), ejes fijos XMin/XMax/XInterval + YMin/YMax/YInterval, plotlines XPlotLine/YPlotLine, bubble vía SizeField/ColorField, DecimalPlaces. Nuevos tipos públicos: MpsChartSeries<T>, MpsAxisType, MpsChartPalette. BREAKING menor: DonutItem ahora es record (Label, Value, Color?) — la deconstrucción posicional de 2 elementos (`is DonutItem(var l, var v)`) deja de compilar; la construcción de 2 args sigue válida. +17 tests (1111 total). API pública sin dependencia de ApexCharts.
0.59.0 — BREAKING: MpsAutoGrid eliminado, fusionado en MpsGrid. MpsGrid ahora soporta modo AUTO: omití <Columns> y las columnas se generan desde los atributos de Cayaqui.MPS.Metadata (igual que el viejo MpsAutoGrid). Params absorbidos: Include, Exclude, RowActions, RowActionsHeader, RowActionsWidth. Migración: renombrá la etiqueta <MpsAutoGrid> → <MpsGrid> — los parámetros son idénticos, no requiere otros cambios. El modo MANUAL (con <Columns>) queda sin cambios. 0.58.1 — Dark-mode theme engine fixes. Fix: opts.mode=undefined treated as 'system' (was defaulting to 'light', causing dark-attr/light-palette mismatch). Fix: OS dark/light toggle now refreshes inline accent palette (init() listener now calls apply(_lastOpts) instead of only applyMode()). Fix: custom accent generates correct dark-mode palette when isDark=true (was always generating light values). Fix: unknown accent key in dark mode falls back to dark cobalt (not light cobalt). Fix: CSS comment corrects specificity annotation on branded/gradient dark override (0,6,0).
0.58.0 — YNEX fidelity + Violet accent. YNEX preset now matches original YNEX admin template (body #F0F1F7, Montserrat sidebar font, 240px sidebar, 60px header, no-border 8px-radius cards with bottom shadow). New accent: Violet (#845BDF). Fix: Accent=Bar restored under YNEX (border:none shorthand was killing border-inline-start). Fix: dark YNEX sidebar hover uses rgba(255,255,255,0.08) instead of near-white. Fix: dark Direction B text tokens now set correctly.
0.57.0 — Mixed Mode (DetailList + AutoForm) + Grid Attributes. MpsDetailList: Source= + MpsDetailItem children como overrides/appends. MpsAutoForm: ChildContent con MpsAutoFormField For= como overrides de campo. Nuevos attrs de grid (requiere Cayaqui.MPS.Metadata 0.19.0): [GridWidth] y [GridFormat] para MpsAutoGrid. BREAKING: MpsBadge y MpsAlert eliminan param obsoleto VariantString — usar Variant enum. Fix: MpsProgressBars/MpsProgress usan InvariantCulture en CSS widths. Fix: MpsSidebarItem.ToggleOpen usa StateHasChanged. MpsThemeSwitcher: 4 botones de preset (Ynex A/B, Untitled, Untitled Cool).
0.56.0 — Metadata-driven MpsDetailItem + MpsDetailList. MpsDetailItem: nuevo parámetro For= (LambdaExpression) — auto-label, formato y badge desde atributos DTO ([Label], [Currency], [Date], [Badge]/[BadgeColor]). MpsDetailList: nuevo parámetro Source= (object?) — genera automáticamente todos los ítems visibles del DTO vía IExtendedPropertyResolver; group headers cuando GroupName cambia. Ambos backward-compatible — sin cambios en API existente.
0.55.0 — Preset auto-defaults + 4 nuevos componentes. ThemeService.SetAsync detecta cambio de preset y aplica accent/density/radius fijos (Ynex→Emerald, Untitled→Cobalt). Nuevos: MpsDivider, MpsFeatureCard, MpsDetailList+MpsDetailItem, MpsStepIndicator+MpsStep.
0.54.0 — UntitledUI v8-Inspired Theme: nuevo ThemePreset (Ynex/Untitled) + ThemeGray (Neutral/Cool). Tema paralelo al sistema YNEX, activado vía data-mps-theme="untitled". Grays UntitledUI exactos (Gray-25 #FCFCFD como bg principal). Variante Cool gray con tinte azul-frío. Dark mode completo para ambas variantes. MpsThemeSwitcher actualizado: sección "Tema Visual" con 4 opciones (Ynex A · Ynex B · Untitled · Untitled Cool). ThemeState extendido con Preset + Gray (defaults backwards-compat). Sin breaking changes para consumidores existentes.
0.53.0 — YNEX Accent System: 4 nuevas paletas (Navy/Teal/Rose/Orange) + Custom accent picker (HSL palette generator en JS). ThemeSidebarTone global (Default/Tinted/Branded/Gradient/Transparent) vía data-mps-sidebar. ThemeHeaderStyle global (Default/Color/Gradient) vía data-mps-header. MpsSidebar.SidebarTone ampliado con Gradient + Transparent. MpsThemeSwitcher renovado con secciones de Sidebar Tone, Header Style y Custom Color Picker. Fix: MPS.Licensing.dll ahora está incluido en lib/net10.0/ del .nupkg (mismo bug de packaging que 0.45.0/0.45.1 y 0.52.6 — pack sin build previo).
0.52.6 — Fix: accordion CSS base rule (.mps-sidebar .sb-submenu max-height:0) was silently dropped by browser CSS parser due to a stray closing brace after the MpsChat media query block. Accordion visually open/close now works correctly in all browsers.
0.52.5 — MpsSidebar accordion multinivel YNEX-style: MpsSidebarItem completely rewritten. Supports ChildContent nesting (declarative) and Children parameter (data-driven). Submenu slides open/closed with CSS max-height transition. In collapsed/slim mode, submenus appear as hover flyout popovers. MpsSidebar: RegisterRootItem / OnItemExpanded for exclusive accordion behaviour.
0.52.4 — MpsBulletGauge, MpsGauge, MpsSparkline: ShouldRender() optimization — skips DOM diff when no parameter has changed. Prevents unnecessary re-renders on parent rerender in dashboard layouts.
0.52.3 — MpsAutoGrid, MpsGrid: EnableVirtualization parameter (bool, default false). When Height is set and DataSource exceeds 50 items, virtualization auto-activates. Explicit EnableVirtualization="true" forces virtual rendering regardless of count.
0.52.2 — MpsTimeline: Simple, Detailed, TwoColumns layout support; LoadMore button; sub-badges. MpsThemeSwitcher: ThemeService-synchronized offcanvas panel.
0.52.1 — PackageReadme documentation update and minor build fixes.
0.52.0 — YNEX Premium components integration: MpsChat (collaboration center with contact list, sidebar filtering and JS auto-scroll via mps-chat.js), MpsTimeline detailed layouts (Simple, Detailed, TwoColumns layouts, custom avatars, sub-badges and deferred loading), MpsTaskCard (priority cards with assignee avatars and starring), MpsTeamSparklineList (ApexCharts performance sparkline for team members) and MpsThemeSwitcher (offcanvas style control panel synchronized with ThemeService).
0.51.0 — MpsWorkflowEditor + MpsWorkflowMini (Engineering.Workflow): generic pipeline editor and compact read-only variant — parameter-driven nodes/presets, ActiveKeys two-way binding. MpsFullscreenToggle (Design.Navigation): browser fullscreen API button with JS interop via mps-fullscreen.js. MpsReconnectModal (Design.Feedback): Blazor Server circuit reconnect dialog with i18n parameters via mps-reconnect.js.
0.50.0 — MpsDomainHeader: Collapsible parameter (bool). MpsScheduleTimeline (Evm): new visual timeline bar — PlannedStart/End, PhysicalProgress, Spi, DaysDeviation, ProjectedEnd.
0.49.8 — Fix: MpsCardGroup reverted to minmax(min, max) — MaxCardWidth="Xpx" is a hard column cap; use default 1fr for full-width rows. App.razor link to {App}.styles.css documented as required.
0.49.7 — Fix: MpsCardGroup grid CSS moved to mps-bundle.css (mps-components.css) — previously only in Blazor scoped bundle, causing layout to break when styles.css was not linked.
0.49.6 — MpsCardGroup: new MaxCardWidth parameter (default "1fr"). Use MinCardWidth="280px" MaxCardWidth="400px" to cap card size in auto-fill grids.
0.49.5 — Merge: MpsCpiGauge + MpsSpiGauge absorbed into MpsGauge via GaugePreset.Cpi/Spi. Merge: MpsVariancePill + MpsTrendDelta absorbed into MpsDeltaChip (Variant=Variance/Trend). Migration: MpsCpiGauge → MpsGauge Preset="GaugePreset.Cpi"; MpsTrendDelta → MpsDeltaChip Variant="DeltaVariant.Trend".
0.49.4 — Fix: MpsGrid Number column formatting now uses CultureInfo.CurrentCulture instead of InvariantCulture — numeric values now display with correct locale separators (e.g. es-CL: 1.234.567 instead of 1,234,567).
0.49.3 — Merge: MpsPhysicalProgressCurve absorbed into MpsProgressSCurve (ShowPartialBars=false). ProgressSCurvePoint gains BaselineCumulative?. Migration: use MpsProgressSCurve ShowPartialBars="false"; rename BaselinePct→BaselineCumulative, PlanPct→PlanCumulative, ActualPct→ActualCumulative, ForecastPct→ForecastCumulative.
0.49.2 — Docs: GenerateDocumentationFile enabled; 876 [Parameter] properties now carry /// <summary> XML IntelliSense docs across all 181 components (Design, Evm, Engineering). No API or binary changes.
0.49.1 — MpsCardGroup: equal-height card grid container (auto-fill responsive + optional Columns override).
0.47.2 — Fix: MpsGrid Height defaults to "auto" when not set — prevents NullReferenceException in SfGrid.GetStyle() during SSR. No breaking changes.
0.47.1 — Fix: MpsGrid now accepts Height parameter (string?, e.g. "280px") and forwards it to SfGrid — fixes InvalidOperationException "does not have a property matching the name 'Height'". No breaking changes.
0.47.0 — R9cRow.R9cEntry: breaking change — Ac→Incurred, ToCommit→Paid, nuevo Trends, eliminado Variance. PctExec renderiza correctamente en escala 0..1 (fix: era 0.9% en vez de 85%). 0.46.1 — Docs: theming guide expandida en PackageReadme. Secciones nuevas: ThemeDirection A vs B, MpsColorModeToggle props, tabla de CSS tokens semánticos (--color-bg, --color-text, --color-surface, --color-accent-*) para dark mode compatibility en componentes custom del consumer. Sin cambios de API ni binarios.
0.46.0 — Dark / Light / System color mode. New: ThemeMode enum, ThemeOptions.DefaultMode (default=System), ThemeService.Mode + SetAsync(mode:) + InitializeAsync with Mode, MpsThemeAttributes dark support (emits data-mps-mode=dark), MpsColorModeToggle component (Light/System/Dark segmented toggle with SVG icons). CSS: dark palette in mps-tokens.css for Direction A+B and all 5 accents. JS: mps-theme.js applyMode() + OS prefers-color-scheme change listener. Consumer setup: add dual Syncfusion <link> + 2 anti-FOUC scripts to App.razor (see PackageReadme). Breaking: ThemeState record gains Mode as last parameter — update any ThemeState(...) direct constructors to add ThemeMode.System as final arg.
v0.45.2 — Fix: MPS.Licensing.dll ahora se incluye en lib/net10.0/ del .nupkg. En 0.45.0/0.45.1 el DLL faltaba en el deploy del consumer (502.5 en IIS). Sin cambios de API.
v0.45.1 — Fix: re-publicación de 0.45.0 con binarios correctos (0.45.0 empaquetó binarios sin las adiciones de licensing por flag --no-build). Sin diferencia de API.
v0.45.0 — Licensing: AddMpsComponents acepta opts.LicenseKey (ECDSA P-256). Sin clave válida se muestra banner en app header y marca de agua en output. ComponentsLicenseState registrado en DI. Sin breaking changes — licenseKey es opcional y todos los comportamientos existentes se mantienen cuando no se provee.
v0.44.4 — Docs: secciones completas de uso (parámetros + ejemplos) para EvmCashflowCurve y PhysicalProgressCurve en PackageReadme. Nota de los fixes 0.44.2–0.44.3 (ArgumentException DateTimeOffset). Sin cambios de código.
v0.44.3 — Fix: mismo ArgumentException en DateTimeOffset..ctor(DateTime, TimeSpan.Zero) corregido en los 5 componentes restantes con el patrón: PhysicalProgressCurve, MpsProgressSCurve, ContingencyDrawdown, CashflowChart, MpsTimelineChart. Todos usan DateTime.SpecifyKind(x.Date, DateTimeKind.Utc). Sin breaking changes.
v0.44.2 — Fix: EvmCashflowCurve lanzaba ArgumentException al recibir un ControlDate con DateTimeKind.Local (ej. DateTime.Today). DateTimeOffset..ctor(DateTime, TimeSpan.Zero) requiere Kind.Utc o Kind.Unspecified. Corregido con DateTime.SpecifyKind(cd.Date, DateTimeKind.Utc). Sin breaking changes.
v0.44.1 — Fix: Tooltip.Intersect=false en 7 componentes con Shared=true (StackedBarChart, ContingencyDrawdown, ResourceHistogram, EvmControlChart, EvmCashflowCurve, PhysicalProgressCurve, ProductionCurve) — ApexCharts lanza excepción si ambos son true. Sin breaking changes.
v0.44.0 — MpsListGroup: lista estilizada model-driven (icono, badge, selección, Flush, ItemTemplate). MpsFileManager: browser Grid/Lista para documentos — auto-icono por extensión, OnOpen/OnDelete, @bind-ViewMode. Migración Syncfusion.Blazor.Charts → ApexCharts (17 componentes). Sin breaking changes en API pública. Consumers deben quitar Syncfusion.Blazor.Charts de sus propios csproj si no lo usan en otro lugar. 999 tests totales.
0.43.8 — MpsProgressSCurve: etiqueta de Fecha de Control vertical sobre línea punteada; leyendas simplificadas (Plan/Real/Forecast, sin " acum."). v0.43.7 — MpsProgressSCurve: títulos de ejes Y — "Avance Acumulado" en Y1 izquierdo y "Avance Parcial" en Y2 derecho, ambos con escala %. v0.43.6 — Fix MpsProgressSCurve leyenda: selector CSS corregido de data-series-index a rel (1-based) para ocultar barras parciales — ApexCharts usa rel en ítems de leyenda. v0.43.5 — Fix MpsProgressSCurve: eje Y secundario para barras parciales implementado con 6 entradas YAxis + SeriesName binding (elimina InvalidOperationException por YAxisIndex no disponible como parámetro Blazor). 956 tests. v0.43.4 — MpsProgressSCurve: barras parciales en eje Y2, leyenda limpia (series bar ocultas via CSS), Forecast acum. anclado al último punto Real acum., Plan acum. línea continua. v0.43.3 — Fix tooltip JS error (shared+intersect). v0.43.2 — MpsEngProgressTable totales y bordes. v0.43.1/0.43.0 — MpsProgressSCurve: Curva S de avance físico, gráfico mixto ApexCharts. Fix MpsTimelineChart eje X. v0.43.0 — MpsProgressSCurve: Curva S de avance físico con gráfico mixto ApexCharts (barras periódicas + líneas acumuladas) para Plan/Real/Forecast. Paleta EVM canónica (#2E5BFF/#D92D20/#039855). Anotación ControlDate vertical y goal line 100%. Consumer provee parciales y acumulados explícitamente (ProgressSCurvePoint). Fix: MpsTimelineChart eje X corregido a XAxisType.Datetime. 956 tests totales. v0.42.0 — MpsEngProgressTable: tabla Plan/Forecast/Actual/Var por documento con hitos configurables. v0.41.0 — MpsDocCard + MpsDocRevisionList + MpsDocStatusBadge. v0.40.0 — MpsCalendar (FullCalendar 6). v0.39.0 — MpsHeatmapChart + MpsTimelineChart (ApexCharts). v0.34.0 — Skeleton composites para arquetipos de loading-state. Cinco componentes nuevos sobre MpsSkeleton cubren los arquetipos comunes: MpsSkeletonStack (N líneas para forms/párrafos), MpsSkeletonCard (header + body línea/rect para detail/charts), MpsSkeletonRow (avatar + título + subtítulo para listas), MpsSkeletonGrid (grilla de tiles para KPI strips, default 1x4), MpsSkeletonTable (toolbar + N filas para index pages, RowHeight default 2.5rem para match con MpsAutoGrid). Composición típica de dashboard reemplaza un <MpsSkeleton /> plano por <MpsSkeletonGrid /> + <MpsSkeletonCard Height="280px" /> + <MpsSkeletonTable /> — reduce layout shift y comunica visualmente qué tipo de contenido viene. Todos los composites consumen internamente <MpsSkeleton/> — heredan shimmer y prefers-reduced-motion. +37 tests bUnit (798 total). Sin breaking changes — aditivos sobre v0.33.0. Ver scripts/migrate-to-components-0.34.0.md para recetas. v0.33.0 — MpsSkeleton runtime fix. El componente rendereaba `<div class="mps-skeleton ...">` pero la regla CSS empacada era solo `.skeleton` — desde la primera versión. En 0.33.0 la clase se alinea (`.mps-skeleton`), `@keyframes shimmer` se namespacea como `mps-skeleton-shimmer`, se agregan defaults dimensionales por shape (line 100% × 0.875rem, rect 100% × 6rem, circle 2.5rem × 2.5rem) — `<MpsSkeleton />` sin parámetros ahora es visible. Soporte `prefers-reduced-motion: reduce` (WCAG 2.3.3). +7 tests bUnit (761 total). Sin breaking changes; consumers que aplicaron hotfix consumer-side (regla `.mps-skeleton` en `app.css`) pueden removerlo. v0.32.0 — MpsPageHeader sincroniza el browser tab title automáticamente. Nuevo `ThemeOptions.AppName` (configurable en AddMpsComponents) sirve como sufijo. Formato `{Title} · {AppName}` (middot). Override por instancia con `BrowserTabSuffix` (sufijo) y `BrowserTabTitle` (título distinto al header). Opt-out con `SetBrowserTab=false`. Helper estático `MpsPageHeader.ComputeBrowserTabTitle(...)` testeable. Fix de bug: la versión anterior tenía `<PageTitle>@Title - MOVES</PageTitle>` hardcodeado en una librería genérica — rompía consumers no-MOVES y filtraba el nombre del proyecto. DoubleProgress / TripleProgress: barras a 90% opacidad (antes 50%). +12 tests (754 total). Sin breaking de API; el browser tab default sigue activo (ahora correcto). v0.31.0 — CashflowChart leyenda con valores en CutOffDate. Reemplaza el header "totales" por una leyenda HTML con dos grupos: "Mes de cutoff" (Plan/Real/Forecast columnas — valor del mes de control, Forecast=total restante) y "Acumulado al cutoff" (Plan acum./Real acum./Forecast acum.=EAC). Si no hay CutOffDate, los valores son los totales del rango. Cada entrada con swatch que replica el estilo del chart (columnas opacidad 0.65, líneas sólidas, Forecast acum. dashed verde). Legend Syncfusion built-in se oculta — la nueva leyenda HTML usa MoneyDisplay para formato consistente con el resto del design system. Nuevo helper estático `ComputeLegendValues(raw, cutOffDate)` testeable. +3 tests (742 total). Sin breaking de API. v0.30.1 — CashflowChart palette EVM canónica. Plan=azul (#2E5BFF), Real=rojo (#D92D20), Forecast=verde (#039855) con la línea acumulada de Forecast en verde segmentado (DashArray 6,3). Las columnas mensuales usan los mismos hex al 75% de opacidad para diferenciarse visualmente de las líneas acumuladas (100%). Reemplaza la paleta inicial 0.30.0 (Plan azul, Real verde, Forecast naranja).
v0.30.0 — CashflowChart 2.0: extiende el componente con capa acumulada (3 series tipo línea sobre eje Y secundario) además de las columnas mensuales. Plan acum. cubre todo el rango; Real acum. ≤ CutOffDate; Forecast acum. arranca en el Real acum. del cutoff (continuidad visual con la línea Real) y luego suma los Forecast period-by-period. Las columnas Forecast quedan filtradas automáticamente a meses > CutOffDate (antes el caller debía nullear los pre-cutoff manualmente). Nuevo parámetro `ShowCumulative` (default `true`) controla la capa de líneas. Nuevo parámetro `CutOffDate` (alias retro-compat de `ControlDate`). Anchor robusto cuando CutOffDate no coincide con un punto exacto del dataset (cae en el último ≤ cutoff). +10 tests (739 total). Sin breaking changes; consumers que pasen `ShowCumulative="false"` mantienen el aspecto 0.29.x.
v0.29.0 — Tipografía dual MPS. Roboto Condensed para charts, tablas, valores numéricos, KPIs y códigos tabulares (Money/Counter/Range/Stat-card/Pagination/R9C/PO/Risk/Sensitivity/Lookahead). Inter para cuerpo, labels, títulos, botones — "el resto del contenido". Nuevos tokens CSS: --font-numeric, --font-table, --font-chart. Charts Syncfusion (SfChart, AccumulationChart, StockChart, RangeNavigator, Sparkline, Gauges, Gantt) y custom (mps-gauge, mps-bullet) reciben Roboto Condensed via SVG <text> rule. SfGrid + TreeGrid + .mps-table + .mps-po-table + .mps-deliv-table + .mps-risk-table + .mps-svt-table heredan font-table. Utility `.mono` re-apuntada a --font-numeric (los 30+ usos en razor — MoneyDisplay/MpsCounter/CurrencyInput/MpsGridColumn/etc — quedan correctos sin migración). Nuevo `.kbd` para mono real (JetBrains Mono kept). El @import del Google Fonts ahora trae Inter 300/400/500/600/700 + Roboto Condensed 300/400/500/700 + JetBrains Mono. Recomendado: el consumer precarga las fuentes con <link rel="preconnect"> en App.razor para LCP óptimo (ver PackageReadme). v0.28.1 — Patch fix de IOptions<ThemeOptions> en AddMpsComponents. v0.28.0 — Theme SSR support, elimina FOUC. Sin breaking changes en v0.29.0; sólo cambia la familia tipográfica visual.