Una tarjeta de Azure Monitor para una métrica concreta en tu dashboard
Daniel J. Saldaña- 24 de octubre de 2026
- Puntuación de feedback

Un dashboard de infraestructura no tiene por qué arrancar con veinte gráficas. Una tarjeta bien elegida ya puede responder algo valioso:
¿Cómo ha evolucionado la CPU de esta máquina virtual durante la última hora?
Ese es el tamaño de la integración de hoy. Partimos de una sola métrica de Azure Monitor, la consultamos desde el backend y devolvemos al frontend una serie temporal pequeña y fácil de representar.
Tarjeta del dashboard ↓API privada autorizada ↓Azure Monitor Metrics API ↓Último valor, agregación y puntos para la gráficaEl mismo patrón sirve para solicitudes por segundo, latencia, disponibilidad, transacciones de base de datos o mensajes en cola. Lo que cambia es la métrica permitida para cada tipo de recurso.
Escoge una pregunta antes de escoger la métrica
Una tarjeta útil combina tres decisiones explícitas:
| Decisión | Ejemplo |
|---|---|
| Recurso | Una máquina virtual concreta |
| Métrica | Percentage CPU |
| Ventana y agregación | Última hora, media cada cinco minutos |
No todas las métricas existen para todos los proveedores, y los nombres exactos importan. Por eso no es buena idea aceptar metricName libremente desde la URL. Configura las métricas que tu producto soporta o recupéralas de las definiciones de Azure Monitor en una acción administrativa controlada.
El límite de seguridad
El navegador solicita «la gráfica de esta tarjeta», no un identificador ARM arbitrario. El backend resuelve el recurso desde su inventario, comprueba que el usuario puede verlo y usa una identidad administrada con permisos de lectura de monitorización, por ejemplo el rol Monitoring Reader en el alcance adecuado.
Browser ── sesión ──► API de la aplicación ── identidad administrada ──► Azure Monitor │ └── resourceId obtenido de un registro autorizadoAdemás de proteger los tokens, este diseño evita que un usuario use el dashboard para explorar métricas de recursos que pertenecen a otro equipo o suscripción.
Consulta una serie temporal desde TypeScript
El endpoint de métricas se encuentra bajo el resourceId del recurso. Este ejemplo encapsula la llamada y transforma la respuesta de Azure en un contrato mínimo:
import { DefaultAzureCredential } from '@azure/identity';
const credential = new DefaultAzureCredential();const scope = 'https://management.azure.com/.default';
export type MetricPoint = { timestamp: string; value: number | null;};
export type MetricSeries = { name: string; aggregation: string; unit?: string; points: MetricPoint[];};
export async function queryMetric(input: { resourceId: string; metricName: string; aggregation: 'Average' | 'Total' | 'Maximum' | 'Minimum' | 'Count'; from: Date; to: Date; interval?: string;}): Promise<MetricSeries> { if (!input.resourceId.startsWith('/subscriptions/')) { throw new Error('Azure resource id no válido'); }
const token = await credential.getToken(scope); if (!token) throw new Error('No se pudo obtener token de Azure');
const url = new URL(`https://management.azure.com${input.resourceId}/providers/Microsoft.Insights/metrics`); url.searchParams.set('api-version', '2023-10-01'); url.searchParams.set('metricnames', input.metricName); url.searchParams.set('aggregation', input.aggregation); url.searchParams.set('timespan', `${input.from.toISOString()}/${input.to.toISOString()}`); url.searchParams.set('interval', input.interval ?? 'PT5M');
const response = await fetch(url, { headers: { Authorization: `Bearer ${token.token}` }, signal: AbortSignal.timeout(8_000), });
if (!response.ok) { throw new Error(`Azure Monitor devolvió ${response.status}`); }
const body = await response.json(); const metric = body.value?.[0]; const data = metric?.timeseries?.[0]?.data ?? []; const aggregationKey = input.aggregation.toLowerCase();
return { name: metric?.name?.value ?? input.metricName, aggregation: input.aggregation, unit: metric?.unit, points: data.map((point: Record<string, unknown>) => ({ timestamp: String(point.timeStamp), value: typeof point[aggregationKey] === 'number' ? point[aggregationKey] : null, })), };}La respuesta de Azure puede contener varios timeseries si aplicas dimensiones. Para la primera tarjeta es mejor elegir una sola serie o añadir una dimensión explícita desde el servidor. Devolver todas sin una decisión de producto suele acabar en una gráfica ilegible.
Convierte una tarjeta de dashboard en una consulta limitada
La interfaz puede mandar el identificador de una tarjeta, no los parámetros de ARM:
const metricCards = { vmCpu: { metricName: 'Percentage CPU', aggregation: 'Average' as const, interval: 'PT5M', },};
export async function GET({ locals, params }: ApiContext) { const user = await requireUser(locals); const card = await dashboardCards.findById(params.cardId);
if (!card || !(await canViewCard(user, card))) { return Response.json({ error: 'No encontrado' }, { status: 404 }); }
const definition = metricCards[card.metricKey as keyof typeof metricCards]; if (!definition) { return Response.json({ error: 'Métrica no soportada' }, { status: 422 }); }
const now = new Date(); const result = await metricsCache.getOrSet( `metric:${card.id}:${Math.floor(now.getTime() / 60_000)}`, () => queryMetric({ resourceId: card.azureResourceId, ...definition, from: new Date(now.getTime() - 60 * 60_000), to: now, }), 60_000 );
return Response.json(result);}La caché de corta duración amortigua una navegación con varias tarjetas abiertas y reduce el riesgo de llegar a límites de servicio. Nunca cachees un token; cachea únicamente el resultado normalizado y durante un intervalo visible para la persona usuaria.
Una interfaz que no exagera el dato
En el frontend, enseña el último valor junto con la agregación y la ventana:
const last = series.points.at(-1)?.value;
<section aria-label={`${series.name}, última hora`}> <p>{series.name}</p> <strong>{last == null ? 'Sin datos' : `${last.toFixed(1)} ${series.unit ?? ''}`}</strong> <small>{series.aggregation} · últimos 60 minutos</small> <Sparkline points={series.points} /></section>;Hay tres detalles que suelen mejorar mucho la lectura:
- Muestra
Sin datossi el último punto es nulo; no lo conviertas en cero. - Indica unidad, agregación y periodo. Un
48sin contexto no comunica nada. - Diferencia la visualización de una alerta. Una gráfica roja no sustituye una regla de alerta operativa.
Trata los errores de Azure como estados de producto
| Respuesta | Qué mostrar |
|---|---|
| Sin datos en la ventana | Tarjeta vacía con periodo y opción de ampliar |
403 |
«No hay permiso para consultar la métrica», con registro técnico interno |
429 o timeout |
Último dato cacheado y reintento con espera |
| Métrica no soportada | No ofrecer la tarjeta para ese tipo de recurso |
| Error de tu backend | Un error técnico, no una alerta de Azure |
Los límites, la granularidad disponible y las dimensiones dependen del tipo de recurso. Empieza con una ventana corta y una agregación conocida; cuando el caso esté validado, añade selector de periodo, comparación y filtros de dimensión.
Preparado para crecer
Una vez resuelta una tarjeta, conservar el contrato evita que cada gráfica sea una integración distinta:
type MetricSeries = { name: string; aggregation: string; unit?: string; points: Array<{ timestamp: string; value: number | null }>;};Puedes alimentar este mismo DTO desde Azure Monitor, desde una caché de series o desde un servicio de agregación. El componente sigue siendo el mismo.
La referencia de Metrics - List de Azure Monitor documenta los parámetros de la llamada. Con una sola métrica bien protegida, un dashboard ya gana una señal de operación accionable sin convertirse en una réplica completa del portal de Azure.


