feat(ui): modernize home dashboard and welcome experience close #72
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 9m29s

This commit is contained in:
Leandro Hernan Rojas 2026-06-11 00:10:43 -03:00
parent 525953d981
commit ed9de1b45e
4 changed files with 715 additions and 36 deletions

View File

@ -0,0 +1,276 @@
# PhronCare — Story #72: Modernización de la Página de Inicio
## Objetivo
Modernizar la página principal posterior al login para reemplazar el contenido demo actual por una Home profesional, moderna y visualmente atractiva, alineada con el resto de PhronCare.
La nueva Home debe funcionar como punto de entrada al sistema y ofrecer una visión rápida de los módulos principales.
---
## Contexto funcional
Actualmente la página principal del BackOffice muestra contenido de demostración utilizado durante el desarrollo inicial.
El contenido existente no representa información funcional del producto y genera una percepción poco profesional para usuarios finales.
PhronCare ya cuenta con múltiples módulos funcionales:
- Pacientes
- Presupuestos
- Ventas
- Remitos
- Sales Documents
- Stock
- Tickets
- Configuración
La Home debe evolucionar hacia un dashboard de bienvenida que sirva como punto de navegación principal.
Esta Story se enfoca exclusivamente en la capa UI.
No debe incorporar métricas reales ni nuevas integraciones.
---
## Alcance
Analizar la implementación actual de la Home.
Identificar archivos involucrados.
Reemplazar el contenido demo actual por un dashboard institucional.
Implementar:
### 1. Hero de bienvenida
Bloque superior con:
- nombre del sistema
- mensaje de bienvenida
- descripción breve
Ejemplo:
```text
Bienvenido a PhronCare
Gestión integral para empresas de internación domiciliaria,
ortopedia y prestaciones médicas.
```
### 2. Accesos rápidos
Mostrar tarjetas de navegación hacia módulos principales.
- Pacientes
- Presupuestos
- Ventas
- Remitos
- Sales Documents
- Stock
- Tickets
Cada tarjeta debe permitir navegación directa.
### 3. Tarjetas resumen
Agregar sección de KPIs visuales mock.
Ejemplos:
- Presupuestos Activos
- Remitos Emitidos
- Facturas Pendientes
- Tickets Abiertos
Valores estáticos temporales.
No consumir API.
No generar endpoints.
### 4. Actividad reciente
Agregar listado visual de actividad simulada.
Ejemplo:
- Presupuesto aprobado
- Remito emitido
- Factura generada
- Ticket cerrado
Contenido mock.
### 5. Responsive
La Home debe visualizarse correctamente en:
- Desktop
- Tablet
- Notebook
Utilizar Bootstrap existente.
### 6. Compatibilidad visual
Mantener compatibilidad con:
- tema claro
- tema oscuro
No romper estilos existentes.
---
## Fuera de alcance
No modificar:
- Data
- Models
- Domain
- Core
- API
- Base de datos
- Stored Procedures
- Repositorios
- DTOs
- Endpoints
- Swagger
- Autenticación
- Permisos
No incorporar:
- métricas reales
- integraciones externas
- reportes
- charts dinámicos
---
## Criterios de aceptación
- La página principal deja de mostrar contenido demo.
- Existe una sección institucional de bienvenida.
- Existen accesos rápidos a módulos principales.
- Existen tarjetas resumen visuales.
- Existe sección de actividad reciente mock.
- La página es responsive.
- Funciona correctamente en modo claro y oscuro.
- No se generan cambios fuera de UI.
- El proyecto compila correctamente.
---
## Decisiones de diseño
### Dashboard estático inicial
La Home funcionará inicialmente como dashboard visual.
Las métricas serán simuladas.
Esto evita dependencias innecesarias con API y permite evolucionar posteriormente hacia métricas reales.
### Navegación rápida
La Home debe priorizar velocidad de acceso a funcionalidades frecuentes.
No reemplaza menús laterales existentes.
Los complementa.
### Implementación exclusivamente UI
Toda la Story queda limitada a:
```text
UI (Blazor)
```
Sin impacto en capas inferiores.
---
## Wireframe funcional esperado
```text
+--------------------------------------------------+
| Bienvenido a PhronCare |
| Gestión integral de prestaciones médicas |
+--------------------------------------------------+
+-----------+-----------+-----------+-------------+
| Pacientes | Ventas | Stock | Tickets |
+-----------+-----------+-----------+-------------+
+-----------+-----------+-----------+-------------+
| Presup. | Remitos | Facturas | Config |
+-----------+-----------+-----------+-------------+
+-----------+-----------+-----------+-------------+
| 128 | 42 | 15 | 6 |
| Presup. | Remitos | Facturas | Tickets |
+-----------+-----------+-----------+-------------+
Actividad reciente
• Presupuesto aprobado
• Remito emitido
• Factura generada
• Ticket cerrado
```
---
## Entregable esperado
Archivos potencialmente involucrados:
- phronCare.UIBlazor/Pages/Index.razor
- phronCare.UIBlazor/wwwroot/css/app.css
Pueden agregarse componentes UI auxiliares si Codex considera necesario reutilizar patrones existentes.
---
## Branch sugerida
```text
feature/leandro/72-home-dashboard
```
---
## Commit sugerido
```text
feat(ui): modernize home dashboard and welcome experience close #72
```
---
## Instrucción para Codex
```text
Analizar primero el repositorio completo.
Identificar cómo está implementada actualmente la Home.
Reutilizar componentes, estilos y patrones ya existentes.
No generar APIs.
No generar Core.
No generar Domain.
No generar Models.
Limitar todos los cambios a UI.
Implementar la Home descrita en esta Story.
Generar patch final validado mediante compilación exitosa.
```

View File

@ -17,7 +17,7 @@
<div class="@($"{NavMenuCssClass} nav-scrollable bg-dark text-white")" @onclick="ToggleNavMenu" style="padding-top:4px;"> <div class="@($"{NavMenuCssClass} nav-scrollable bg-dark text-white")" @onclick="ToggleNavMenu" style="padding-top:4px;">
<nav class="flex-column text-center"> <nav class="flex-column text-center">
<div class="nav-item px-2 py-0"> <div class="nav-item px-2 py-0">
<NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" href="/DashboardPanel" Match="NavLinkMatch.All" activeClass="bg-secondary text-white"> <NavLink class="nav-link text-white d-flex align-items-center gap-2 py-0 px-2" href="/" Match="NavLinkMatch.All" activeClass="bg-secondary text-white">
<span class="oi oi-home" aria-hidden="true"></span> <span class="oi oi-home" aria-hidden="true"></span>
@if (!navMenuService.Minimized) @if (!navMenuService.Minimized)
{ {

View File

@ -1,50 +1,135 @@
@page "/" @page "/"
@using phronCare.UIBlazor.Pages.Authorization @using phronCare.UIBlazor.Pages.Authorization
@inject NavigationManager Navigation
<PageTitle>PhronCare</PageTitle> <PageTitle>PhronCare</PageTitle>
<AuthorizeView> <AuthorizeView>
<Authorized> <Authorized>
<h1>Hola, usuario!</h1> <section class="home-dashboard">
Bievenido a PhronCare. <div class="home-hero">
Estamos en etapa de desarrollo.- <div>
<br/> <span class="home-eyebrow">BackOffice operativo</span>
<h1>Bienvenido a PhronCare</h1>
<p>
Gestion integral para empresas de internacion domiciliaria,
ortopedia y prestaciones medicas.
</p>
</div>
<div class="home-hero-status" aria-label="Estado general">
<span class="home-status-dot"></span>
Plataforma operativa
</div>
</div>
<ul class="fa-ul"> <div class="home-section-header">
<li><span class="fa-li"><i class="fa-solid fa-check-square"></i></span>List icons can</li> <h2>Accesos rapidos</h2>
<li><span class="fa-li"><i class="fa-solid fa-check-square"></i></span>be used to</li> <span>Modulos principales</span>
<li><span class="fa-li"><i class="fa-solid fa-spinner fa-pulse"></i></span>replace bullets</li> </div>
<li><span class="fa-li"><i class="fa-regular fa-square"></i></span>in lists</li>
</ul>
<br />
<div>
<span style="border: 1px solid silver; border-radius: 0.25em; padding: 0.5em;"><i class="fa-solid fa-arrow-left fa-fw" title="Back"></i></span>
<span style="border: 1px solid silver; border-radius: 0.25em; padding: 0.5em;"><i class="fa-solid fa-arrow-right fa-fw" title="Forward"></i></span>
<span style="border: 1px solid silver; border-radius: 0.25em; padding: 0.5em;"><i class="fa-solid fa-arrows-rotate fa-fw" title="Refresh"></i></span>
<span style="border: 1px solid silver; border-radius: 0.25em; padding: 0.5em;"><i class="fa-solid fa-house fa-fw" title="Home"></i></span>
<span style="border: 1px solid silver; border-radius: 0.25em; padding: 0.5em;"><i class="fa-solid fa-info fa-fw" title="Info"></i></span>
<span style="border: 1px solid silver; border-radius: 0.25em; padding: 0.5em;"><i class="fa-solid fa-download fa-fw" title="Download"></i></span>
</div>
<br />
<div class="fa-3x">
<i class="fa-solid fa-sync fa-spin"></i>
<i class="fa-solid fa-circle-notch fa-spin"></i>
<i class="fa-solid fa-cog fa-spin"></i>
<i class="fa-solid fa-cog fa-spin fa-spin-reverse"></i>
<i class="fa-solid fa-spinner fa-spin-pulse"></i>
<i class="fa-solid fa-spinner fa-spin-pulse fa-spin-reverse"></i>
</div>
<br/>
<div class="quick-access-grid">
@foreach (var item in QuickAccessItems)
{
<button type="button" class="quick-access-card" @onclick="() => NavigateTo(item.Url)">
<span class="quick-access-icon @item.AccentClass">
<i class="@item.Icon" aria-hidden="true"></i>
</span>
<span class="quick-access-content">
<strong>@item.Title</strong>
<small>@item.Description</small>
</span>
<i class="fas fa-chevron-right quick-access-arrow" aria-hidden="true"></i>
</button>
}
</div>
<div class="home-content-grid">
<section>
<div class="home-section-header">
<h2>Resumen operativo</h2>
<span>Valores temporales</span>
</div>
<div class="kpi-grid">
@foreach (var item in SummaryItems)
{
<div class="kpi-card">
<div class="kpi-icon @item.AccentClass">
<i class="@item.Icon" aria-hidden="true"></i>
</div>
<div>
<strong>@item.Value</strong>
<span>@item.Title</span>
</div>
</div>
}
</div>
</section>
<section>
<div class="home-section-header">
<h2>Actividad reciente</h2>
<span>Simulada</span>
</div>
<div class="activity-list">
@foreach (var item in RecentActivity)
{
<div class="activity-item">
<span class="activity-icon @item.AccentClass">
<i class="@item.Icon" aria-hidden="true"></i>
</span>
<div>
<strong>@item.Title</strong>
<small>@item.Description</small>
</div>
</div>
}
</div>
</section>
</div>
</section>
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
<LoginPage/> <LoginPage />
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
@code @code
{ {
public bool state; private readonly List<QuickAccessItem> QuickAccessItems =
private void OnChangeToggleSwitchState(bool value) [
new("Pacientes", "Gestion de pacientes y datos asistenciales.", "/sales/patients", "fas fa-user-injured", "accent-blue"),
new("Presupuestos", "Consulta y seguimiento de presupuestos.", "/quotes/dashboard", "fas fa-file-invoice-dollar", "accent-green"),
new("Ventas", "Clientes, productos y circuito comercial.", "/sales/customers", "fas fa-briefcase", "accent-indigo"),
new("Remitos", "Emision y control de remitos.", "/deliverynotes", "fas fa-truck", "accent-orange"),
new("Sales Documents", "Documentos comerciales y facturacion.", "/salesdocuments", "fas fa-receipt", "accent-teal"),
new("Stock", "Productos medicos y trazabilidad.", "/stock/products", "fas fa-boxes-stacked", "accent-violet"),
new("Tickets", "Alta y seguimiento de tickets.", "/dashboardpanel", "fas fa-ticket-alt", "accent-red")
];
private readonly List<SummaryItem> SummaryItems =
[
new("Presupuestos Activos", "128", "fas fa-file-signature", "accent-green"),
new("Remitos Emitidos", "42", "fas fa-truck-fast", "accent-orange"),
new("Facturas Pendientes", "15", "fas fa-file-invoice", "accent-teal"),
new("Tickets Abiertos", "6", "fas fa-headset", "accent-red")
];
private readonly List<ActivityItem> RecentActivity =
[
new("Presupuesto aprobado", "Hace 12 minutos", "fas fa-check-circle", "accent-green"),
new("Remito emitido", "Hace 38 minutos", "fas fa-truck", "accent-orange"),
new("Factura generada", "Hoy, 10:45", "fas fa-receipt", "accent-teal"),
new("Ticket cerrado", "Hoy, 09:20", "fas fa-circle-check", "accent-red")
];
private void NavigateTo(string url)
{ {
state = value; Navigation.NavigateTo(url);
} }
private sealed record QuickAccessItem(string Title, string Description, string Url, string Icon, string AccentClass);
private sealed record SummaryItem(string Title, string Value, string Icon, string AccentClass);
private sealed record ActivityItem(string Title, string Description, string Icon, string AccentClass);
} }

View File

@ -0,0 +1,318 @@
.home-dashboard {
display: flex;
flex-direction: column;
gap: 1.5rem;
padding-bottom: 2rem;
}
.home-hero {
align-items: flex-start;
background: var(--background-highlight);
border: 1px solid var(--background-highlight-light);
border-radius: 8px;
display: flex;
gap: 1rem;
justify-content: space-between;
padding: 1.75rem;
}
.home-eyebrow {
color: var(--link-color);
display: block;
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0;
margin-bottom: 0.45rem;
text-transform: uppercase;
}
.home-hero h1 {
color: var(--text-background);
font-size: 2rem;
font-weight: 700;
line-height: 1.2;
margin: 0;
}
.home-hero p {
color: var(--text-background);
font-size: 1rem;
line-height: 1.55;
margin: 0.75rem 0 0;
max-width: 760px;
opacity: 0.78;
}
.home-hero-status {
align-items: center;
background: var(--background);
border: 1px solid var(--background-highlight-light);
border-radius: 999px;
color: var(--text-background);
display: inline-flex;
flex: 0 0 auto;
font-size: 0.85rem;
font-weight: 600;
gap: 0.5rem;
padding: 0.45rem 0.75rem;
white-space: nowrap;
}
.home-status-dot {
background: #16a085;
border-radius: 999px;
box-shadow: 0 0 0 4px rgba(22, 160, 133, 0.14);
height: 0.6rem;
width: 0.6rem;
}
.home-section-header {
align-items: baseline;
display: flex;
gap: 0.75rem;
justify-content: space-between;
}
.home-section-header h2 {
color: var(--text-background);
font-size: 1.1rem;
font-weight: 700;
margin: 0;
}
.home-section-header span {
color: var(--text-background);
font-size: 0.82rem;
opacity: 0.62;
}
.quick-access-grid {
display: grid;
gap: 0.9rem;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.quick-access-card {
align-items: center;
background: var(--background-highlight);
border: 1px solid var(--background-highlight-light);
border-radius: 8px;
color: var(--text-background);
cursor: pointer;
display: grid;
gap: 0.75rem;
grid-template-columns: auto minmax(0, 1fr) auto;
min-height: 86px;
padding: 1rem;
text-align: left;
transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
width: 100%;
}
.quick-access-card:hover,
.quick-access-card:focus {
border-color: var(--link-color);
box-shadow: 0 0.35rem 1rem rgba(0, 0, 0, 0.12);
outline: none;
transform: translateY(-1px);
}
.quick-access-icon,
.kpi-icon,
.activity-icon {
align-items: center;
border-radius: 8px;
color: #fff;
display: inline-flex;
flex: 0 0 auto;
justify-content: center;
}
.quick-access-icon {
font-size: 1rem;
height: 2.5rem;
width: 2.5rem;
}
.quick-access-content {
display: flex;
flex-direction: column;
gap: 0.2rem;
min-width: 0;
}
.quick-access-content strong {
color: var(--text-background);
font-size: 0.95rem;
line-height: 1.2;
}
.quick-access-content small {
color: var(--text-background);
font-size: 0.78rem;
line-height: 1.35;
opacity: 0.66;
}
.quick-access-arrow {
color: var(--text-background);
font-size: 0.8rem;
opacity: 0.48;
}
.home-content-grid {
display: grid;
gap: 1.5rem;
grid-template-columns: minmax(0, 1.3fr) minmax(280px, 0.7fr);
}
.home-content-grid section {
display: flex;
flex-direction: column;
gap: 0.9rem;
}
.kpi-grid {
display: grid;
gap: 0.9rem;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.kpi-card,
.activity-list {
background: var(--background-highlight);
border: 1px solid var(--background-highlight-light);
border-radius: 8px;
}
.kpi-card {
align-items: center;
display: flex;
gap: 0.9rem;
min-height: 92px;
padding: 1rem;
}
.kpi-icon {
font-size: 1rem;
height: 2.65rem;
width: 2.65rem;
}
.kpi-card strong {
color: var(--text-background);
display: block;
font-size: 1.75rem;
line-height: 1;
}
.kpi-card span {
color: var(--text-background);
display: block;
font-size: 0.85rem;
margin-top: 0.35rem;
opacity: 0.68;
}
.activity-list {
display: flex;
flex-direction: column;
overflow: hidden;
}
.activity-item {
align-items: center;
display: flex;
gap: 0.85rem;
min-height: 72px;
padding: 0.95rem 1rem;
}
.activity-item + .activity-item {
border-top: 1px solid var(--background-highlight-light);
}
.activity-icon {
font-size: 0.9rem;
height: 2.25rem;
width: 2.25rem;
}
.activity-item strong {
color: var(--text-background);
display: block;
font-size: 0.92rem;
line-height: 1.25;
}
.activity-item small {
color: var(--text-background);
display: block;
font-size: 0.78rem;
margin-top: 0.18rem;
opacity: 0.62;
}
.accent-blue {
background: #2473a6;
}
.accent-green {
background: #138f77;
}
.accent-indigo {
background: #34495e;
}
.accent-orange {
background: #da8c10;
}
.accent-teal {
background: #2c8f9e;
}
.accent-violet {
background: #7f3d9b;
}
.accent-red {
background: #cf4435;
}
@media (max-width: 1199.98px) {
.quick-access-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 991.98px) {
.home-content-grid {
grid-template-columns: 1fr;
}
.quick-access-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 767.98px) {
.home-hero {
flex-direction: column;
padding: 1.25rem;
}
.home-hero h1 {
font-size: 1.6rem;
}
.home-hero-status {
white-space: normal;
}
.kpi-grid,
.quick-access-grid {
grid-template-columns: 1fr;
}
}