Dashboard web construido con Blazor Server (.NET 8) que consume la API REST StockFlow-API (ASP.NET Core 8 + JWT) para administrar el inventario y el punto de venta de una PyME: KPIs del negocio, gráficas de ventas, catálogo con alertas de stock, registro de ventas con carrito e historial con exportación a CSV.
Proyecto de portafolio full-stack .NET: API REST + cliente Blazor Server, autenticación JWT, componentes reutilizables y diseño responsive en español.
Login contra POST /api/auth/login. El token JWT nunca se guarda en texto
plano: vive en memoria del circuito y se respalda cifrado con
ProtectedSessionStorage (Data Protection), por lo que la sesión sobrevive a
un F5 pero no es legible desde el navegador.
Cuatro KPIs (ventas del día, ventas del mes, productos con stock bajo y valor del inventario) más dos gráficas con Blazor-ApexCharts: ventas por mes de los últimos 12 meses y top 10 de productos más vendidos.
Tabla genérica reutilizable con búsqueda por nombre/SKU, filtro por categoría, filtro "solo stock bajo" y paginación. Los productos en o por debajo de su stock mínimo se resaltan en rojo.
Carrito simple: busca en el catálogo, agrega productos (respetando el stock disponible), ajusta cantidades y confirma. La API descuenta el stock de forma transaccional y regresa el folio de la venta.
Filtro por rango de fechas con resumen del periodo (ventas, artículos y monto) y exportación a CSV (UTF-8 con BOM, compatible con Excel).
Toda llamada a la API pasa por un cliente genérico que traduce fallas de red, timeouts, sesiones expiradas (401) y errores de negocio a mensajes amigables con botón de reintento. El layout se adapta a móvil con menú lateral colapsable.
| API no disponible | Vista móvil |
|---|---|
![]() |
![]() |
| Capa | Tecnología |
|---|---|
| UI | Blazor Server (.NET 8), render interactivo sin prerender |
| Gráficas | Blazor-ApexCharts (MIT, sin JS manual) |
| Autenticación | JWT contra la API + AuthenticationStateProvider personalizado + ProtectedSessionStorage |
| Estilos | CSS propio (paleta pizarra/índigo, fuente Inter, Bootstrap Icons) |
| Backend | StockFlow-API: ASP.NET Core 8, EF Core, SQL Server LocalDB |
src/PanelVentas.Web/
├── Auth/ # SesionUsuario (token cifrado) y JwtAuthStateProvider
├── Models/ # DTOs espejo de la API (RespuestaApi<T>, ProductoDto...)
├── Services/ # ApiClient genérico (ApiResultado<T>) y servicios por dominio
├── State/ # CarritoState: carrito de venta a nivel circuito
└── Components/
├── Layout/ # MainLayout (sidebar) y LoginLayout
├── Shared/ # Componentes reutilizables:
│ ├── KpiCard.razor # tarjeta KPI con ícono, tono y esqueleto de carga
│ ├── TablaDatos.razor # tabla genérica <TItem>: búsqueda, filtros,
│ │ # paginación, estados de carga/error/vacío
│ ├── FiltroFechas.razor # rango de fechas con validación
│ ├── AlertaError.razor # error amigable con botón reintentar
│ ├── EstadoCarga.razor # spinner accesible
│ └── EstadoVacio.razor # estado sin datos
└── Pages/ # Login, Dashboard, Productos, NuevaVenta, HistorialVentas
ApiClient+ApiResultado<T>: ninguna página recibe excepciones crudas; todo error (red, timeout, 401, validación de la API) llega como un resultado con mensaje en español. Un 401 cierra la sesión y redirige al login.TablaDatos<TItem>: una sola tabla genérica da búsqueda, paginación, filtros por slot (FiltrosExtra), resaltado por fila y los tres estados (cargando / error / vacío) a Productos e Historial.- Fechas en hora local: la API persiste en UTC;
ReportesServiceconvierte a hora local y recalcula los totales del rango para que "ventas de hoy" signifique el día del usuario. - Autorización en el router: las páginas usan
[Authorize]evaluado porAuthorizeRouteView; el endpoint HTTP es anónimo porque la sesión JWT vive en el circuito de Blazor, no en cookies del servidor.
- SDK de .NET 8 (o superior)
- SQL Server LocalDB (incluido con Visual Studio; la API aplica migraciones y datos semilla automáticamente al arrancar)
cd StockFlow-API
dotnet run --project src/StockFlow.Api --launch-profile httpLa API queda en http://localhost:5299 (Swagger en http://localhost:5299/swagger).
En otra terminal:
cd PanelVentas
dotnet run --project src/PanelVentas.Web --launch-profile httpEl panel queda en http://localhost:5237.
Si la API corre en otra URL, ajusta
StockFlowApi:BaseUrlen src/PanelVentas.Web/appsettings.json.
| Usuario | Contraseña | Rol |
|---|---|---|
admin |
Admin123! |
Admin |
vendedor |
Vendedor123! |
Vendedor |
Las imágenes de este README se generan con un script de Puppeteer que recorre la aplicación real (requiere Node.js y Google Chrome, con API y panel corriendo):
cd tools/capturas
npm install
node capturar.js







