Temas del Master que cubre este proyecto

Este proyecto integra contenidos de varias areas del Master en Desarrollo Full Stack + Arquitecturas Cloud:

  • Arquitectura: SPA desacoplada Angular + API REST, diseno de APIs RESTful escalables y versionadas, patron Repository en ASP.NET Core
  • Seguridad: Autenticacion y autorizacion con JWT, gestion de roles y politicas en APIs REST, proteccion CORS
  • Computacion en la nube: Base de datos en Azure (Cosmos DB como equivalente a MongoDB Atlas en la nube)
  • Comunicacion y Tiempo Real: SignalR en ASP.NET Core para aplicaciones en tiempo real, arquitectura orientada a eventos

El problema: como sabe el usuario que su pedido se ha actualizado?

Cualquier aplicacion de comercio electronico o logistica se enfrenta al mismo reto: el usuario quiere saber en que estado esta su pedido sin tener que recargar la pagina cada pocos segundos. El enfoque tradicional, el polling, implica que el cliente pregunta al servidor repetidamente si hay novedades. Es ineficiente, consume recursos innecesarios y ofrece una experiencia de usuario mediocre.

La pregunta es: como hacemos que el servidor avise al cliente en el instante en que algo cambia? Ahi entra SignalR.

Dashboard tiempo real - Admin y Viewer lado a lado

Que vamos a construir?

Un sistema completo de seguimiento de pedidos en tiempo real con las siguientes caracteristicas:

  • Dashboard en tiempo real que se actualiza sin recargar la pagina
  • Dos roles diferenciados: Admin (puede crear y gestionar pedidos) y Viewer (solo lectura)
  • Autenticacion con JWT y proteccion de endpoints por rol
  • Base de datos en la nube con Azure Cosmos DB

La arquitectura del sistema

Antes de escribir una sola linea de codigo, es importante entender como se comunican las piezas:

Diagrama de arquitectura del sistema

Frontend (Angular 17): Es la SPA que el usuario ve en el navegador. Se conecta al backend mediante HTTP para autenticarse y obtener datos, y mantiene una conexion persistente con SignalR para recibir eventos en tiempo real.

Backend (ASP.NET Core 8): Expone una API REST securizada con JWT. Contiene un Hub de SignalR que actua como canal de difusion: cuando un pedido cambia de estado, notifica a todos los clientes conectados instantaneamente.

Base de datos (Azure Cosmos DB): Base de datos NoSQL distribuida en Azure donde se persisten todos los pedidos y su historial de estados.


Tecnologias utilizadas

  • Angular 17 (Standalone Components, Signals, HttpClient)
  • ASP.NET Core 8 Web API
  • SignalR (Microsoft.AspNetCore.SignalR)
  • JWT Bearer Authentication
  • Azure Cosmos DB (SDK v3)
  • .NET User Secrets (seguridad de credenciales en local)
  • Python 3 (script simulador de eventos para el demo)

Paso a paso: como construir el sistema

Paso 1 – Configurar Azure Cosmos DB

Accede al portal de Azure (portal.azure.com) con tu cuenta y crea los siguientes recursos:

  1. Crea un grupo de recursos: rg-order-tracking
  2. Crea una cuenta de Azure Cosmos DB for NoSQL en modo Serverless
  3. Crea la base de datos TrackingDB y el contenedor Orders con partition key /id

Cosmos DB Data Explorer - TrackingDB Orders

Una vez creado, ve a la seccion Claves y copia la Cadena de conexion principal. Nunca la pongas en el codigo. Usa .NET User Secrets:

dotnet user-secrets set "CosmosDb:ConnectionString" "AccountEndpoint=...;AccountKey=...;"
dotnet user-secrets set "Jwt:Key" "TuClaveSecretaDe32CaracteresMinimo"

Paso 2 – El backend: ASP.NET Core + SignalR + JWT

2.1 Configurar autenticacion JWT

En Program.cs, registramos el esquema JWT y configuramos los eventos de SignalR para que el token pueda llegar como query parameter (requisito del handshake de WebSockets):

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.Events = new JwtBearerEvents {
            OnMessageReceived = context => {
                var token = context.Request.Query["access_token"];
                if (!string.IsNullOrEmpty(token))
                    context.Token = token;
                return Task.CompletedTask;
            }
        };
    });

2.2 El Hub de SignalR

El Hub es el nucleo del sistema en tiempo real. Cuando un cliente se conecta, lo agrupamos por rol:

[Authorize]
public class OrderHub : Hub {
    public override async Task OnConnectedAsync() {
        await Groups.AddToGroupAsync(Context.ConnectionId, "AllUsers");
        await base.OnConnectedAsync();
    }
}

2.3 Disparar el evento desde el controlador

Cuando el admin actualiza el estado de un pedido, se guarda en Cosmos DB y se emite el evento a todos los clientes conectados:

var updated = await _cosmos.UpdateOrderStatusAsync(id, request.NewStatus, request.Note);
await _hub.Clients.Group("AllUsers").SendAsync("OrderStatusUpdated", new {
    orderId = updated.OrderId,
    status = updated.Status.ToString(),
    timestamp = updated.UpdatedAt
});

Paso 3 – El frontend: Angular 17

3.1 El interceptor JWT

Un interceptor funcional de Angular anade el token JWT a cada peticion HTTP automaticamente:

export const authInterceptor: HttpInterceptorFn = (req, next) => {
    const token = inject(AuthService).getToken();
    if (token) {
        req = req.clone({ setHeaders: { Authorization: 'Bearer ' + token } });
    }
    return next(req);
};

3.2 El servicio de SignalR

El servicio gestiona la conexion persistente con el Hub y emite los eventos via RxJS Subjects:

this.hub = new HubConnectionBuilder()
    .withUrl(environment.signalRUrl, {
        accessTokenFactory: () => this.auth.getToken() ?? ''
    })
    .withAutomaticReconnect()
    .build();
this.hub.on('OrderStatusUpdated', (event) => {
    this.orderStatusUpdated$.next(event);
});

3.3 El dashboard en tiempo real

El componente escucha los eventos y actualiza el listado reactivamente con Angular Signals:

this.signalR.orderStatusUpdated$.subscribe(event => {
    this.orders.update(orders =>
        orders.map(o => o.orderId === event.orderId
            ? { ...o, status: event.status }
            : o)
    );
});

Dashboard Admin con botones de cambio de estado

Dashboard Viewer solo lectura

Paso 4 – Roles: Admin vs Viewer

La diferencia entre roles es funcional, no solo visual:

  • Admin (fondo azul): puede crear pedidos, cambiar estados y ver el historial completo
  • Viewer (fondo purpura): ve los mismos datos en tiempo real pero sin ningun control de modificacion
[HttpPost]
[Authorize(Policy = "AdminOnly")]
public async Task<IActionResult> Create([FromBody] Order order) { ... }

Paso 5 – Probar el sistema

Necesitas tres terminales abiertas:

# Terminal 1 - Backend
cd backend/TrackingApi
$env:ASPNETCORE_ENVIRONMENT='Development'
dotnet run
# Terminal 2 - Frontend
cd frontend
ng serve
# Terminal 3 - Simulador (opcional)
cd scripts
python simulate_orders.py --url http://localhost:5000 --orders 5 --delay 1.5

Abre http://localhost:4200 en dos ventanas del navegador. En una entra como admin/admin123 y en la otra como viewer/viewer123.

Demo tiempo real - admin y viewer sincronizados


Problemas encontrados y como resolverlos

1. JWT en la negociacion de SignalR: SignalR no puede enviar cabeceras HTTP durante el handshake de WebSockets. La solucion es pasar el token como query parameter y leerlo en el evento OnMessageReceived del middleware JWT.

2. Enum serializado como numero: Por defecto, System.Text.Json serializa los enums como enteros. El frontend esperaba strings como «Pending» pero recibia 0. Solucion: anadir JsonStringEnumConverter en Program.cs.

3. User Secrets no se cargan en modo Production: .NET solo carga User Secrets en entorno Development. Hay que establecer $env:ASPNETCORE_ENVIRONMENT=’Development’ antes de lanzar el backend.

4. CORS y SignalR: SignalR requiere AllowCredentials() en la politica CORS, incompatible con AllowAnyOrigin(). Hay que especificar el origen exacto del frontend.


Recomendaciones

  • Nunca pongas credenciales en el codigo. Usa User Secrets en local y variables de entorno en produccion.
  • Activa withAutomaticReconnect() en el cliente SignalR para recuperar la conexion automaticamente.
  • En produccion, considera Azure SignalR Service para escalar horizontalmente sin configuracion adicional.
  • El modo Serverless de Cosmos DB es ideal para demos. Para produccion con carga constante evalua Provisioned Throughput.
  • Serializa siempre los enums como strings en las APIs – hace el contrato mas legible y menos fragil.

Preguntas para el debate

WebSockets o Server-Sent Events? SignalR usa WebSockets con fallback a long polling. Para comunicacion unidireccional servidor a cliente, los SSE son una alternativa mas ligera. Cuando elegirias cada uno?

Hub propio o Azure SignalR Service? El Hub de ASP.NET Core funciona en un unico servidor. Si necesitas escalar horizontalmente, multiples instancias no comparten el estado de las conexiones. Donde esta el punto de equilibrio?

Roles estaticos o RBAC dinamico? En este proyecto los roles estan en codigo. En produccion deberian venir de un proveedor de identidad como Azure Active Directory. Que implica ese cambio?

Actualizacion manual o automatica? Los estados se cambian manualmente desde el panel de admin. En produccion vendrian de sistemas externos como GPS del transportista. Como diseñarias esa integracion?


Recursos


Autor/a: Sofia Rojas
Master: Desarrollo Full Stack + Arquitecturas Cloud
Centro: Tajamar Tech
Ano academico: 2025-2026
Codigo / recursos: Enlace a GitHub

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.