Inicio/Docs/Métricas personalizadas
Contrato de métricas personalizadas
Las métricas personalizadas permiten que el propio servicio de tu extensión envíe los números que le importan (conversiones, ingresos, estado del backend, embudos) al mismo panel que ya registra las instalaciones y el estado en Chrome Web Store. El contrato está basado en metadatos y tiene doble superficie: cada métrica declara dónde y cómo aparece tanto en el panel como en el estado de Telegram, de modo que el panel sigue siendo un renderizador genérico sin código específico de ningún producto.
1. Cómo funciona (doble superficie)
Tu servicio expone un endpoint de métricas. Ext Ops Panel es el cliente: consulta ese endpoint (nunca al revés) y renderiza la respuesta. La respuesta alimenta dos superficies a la vez:
- Pestañas del panel en la vista Detalle de la extensión: pestañas, secciones y tipos de renderizado por métrica.
- Estado de Telegram: líneas lógicas compuestas a partir de las mismas métricas, filtradas y formateadas por métrica.
El panel combina tus métricas con las métricas base propias (instalaciones / desinstalaciones / reinstalaciones / comentarios / Chrome Web Store / salud), que viven en la pestaña reservada base y en la línea de Telegram del mismo nombre.
2. El endpoint y la autenticación
El panel llama a tu endpoint con GET y espera el payload v2 como respuesta 200. Los dos modos de consulta comparten la misma estructura:
- Bajo demanda: se activa cuando un usuario abre Detalle. Limitado a 1 solicitud por minuto por fuente como máximo, en todos los planes, incluido Free.
- Monitorización: según un calendario, en los planes que incluyen monitorización; compone el estado de Telegram y las alertas de cambio de estado.
La autenticación es una de none, bearer o api_key_header. Los secretos se almacenan cifrados en reposo en el backend del panel y nunca se entregan a la extensión del panel. Si una consulta falla, el panel se degrada de forma controlada (última instantánea o bloque oculto) sin romper las métricas base.
3. Estructura del payload
El payload de nivel superior contiene el diseño (pestañas, secciones, líneas de Telegram) y el array de métricas.
| Campo | Tipo | Notas |
|---|---|---|
schema_version | string | Obligatorio. Debe ser "2.0". |
generated_at | string (date-time) | Momento en que se compuso el payload (frescura / detección de inactividad). |
tabs | Tab[] | Pestañas del panel. Véase la sección 4. |
sections | Section[] | Grupos de métricas dentro de una pestaña (tab, id, title, order). |
telegram | TelegramMeta | Plantilla header + lines[]. Véase la sección 7. |
metrics | Metric[] | Las propias métricas. Véanse las secciones 5 y 6. |
4. Pestañas e iconos de pestañas
Una pestaña es { id, title, order, icon? }. La pestaña reservada
base pertenece al propio panel (icon="layout-dashboard"); tu servicio añade el resto. Cuando hay muchas pestañas, la activa muestra icono + título y las demás se reducen al icono (el título queda en el tooltip y en aria-label).
Dos ids de pestaña están reservados para el panel: base (la pestaña Basic) y monitoring (la pestaña Monitoring integrada que llenan los informes de heartbeat, consulte el
contrato de monitorización). No los declare en tabs: una declaración se ignora y el panel fija su título, icono y orden. Las métricas siguen apuntando a ellos con dashboard.tab.
El valor icon es una de dos cosas:
- A Nombre del icono de Lucide en kebab-case ASCII (debe coincidir con
^[a-z][a-z0-9-]*$), p. ej.trending-up. Un nombre bien formado pero no admitido se degrada al icono predeterminado (square), así que la barra nunca se rompe. - A un solo emoji, p. ej. 💰 o 🚀, que se muestra como texto.
El panel incluye un catálogo seleccionado de 60 nombres de Lucide (ligero para el paquete MV3). El mismo catálogo sirve para los iconos de pestañas y los iconos de objetivos (sección 5.3):
- layout-dashboard
- activity
- gauge
- layers
- plug
- dollar-sign
- banknote
- wallet
- credit-card
- receipt
- bar-chart-3
- line-chart
- pie-chart
- trending-up
- trending-down
- users
- user
- alert-triangle
- bell
- clock
- git-branch
- package
- box
- database
- server
- cpu
- zap
- globe
- pencil
- puzzle
- funnel
- filter
- square
- circle
- hash
- percent
- tag
- flag
- user-plus
- log-in
- plus-square
- shopping-cart
- check-circle
- send
- download
- upload
- star
- heart
- eye
- mouse-pointer-click
- share-2
- message-square
- target
- rocket
- file-text
- bookmark
- calendar
- key
- play
5. Métrica: valor + dos bloques de ubicación
Una métrica lleva un valor tipado más dos bloques independientes: dashboard (dónde y cómo en la extensión) y telegram (dónde y cómo en el mensaje). Cualquiera de ellos se puede omitir u ocultar mediante show:false, de modo que una métrica puede existir en una sola superficie.
| Campo | Notas |
|---|---|
id | Id de métrica estable, único en el payload. |
kind | Tipo de datos (apartado 5.1). Selecciona qué campos de valor se aplican. |
label | Etiqueta canónica legible. |
value y campos del tipo | Los campos del payload dependen de kind (sección 5.1). |
severity | Uno de ok, info, warn, crit (se asigna a color / emoji). |
shareable | Booleano opcional, por defecto true. Con false la métrica es solo del propietario: en un proyecto compartido en modo de solo lectura se elimina en el servidor y nunca llega a un observador ni a la vista pública (úsalo para métricas financieras). Regula el acceso, no la ubicación. |
dashboard | { show, tab, section, order, name, render, options } (sección 5.2). |
telegram | { show, line, pos, format, sep, when } (apartados 6 y 8). |
5.1. Tipos (tipo de datos + payload)
Los tipos son un conjunto abierto: el renderizador ignora un tipo desconocido (compatibilidad hacia delante).
| kind | Campos del payload | Uso de ejemplo |
|---|---|---|
number | value (+ opcional delta, unit) | recuentos de instalaciones / comentarios |
delta | value, delta (renderiza 1200 (+72)) | total (+hoy) |
percent | value (0 a 100) | tasa de éxito del motor |
duration | seconds o minutes | p50/p95/media, frescura |
bytes | bytes o kb | tamaño de salida (~522 KB) |
rating | value, count | 5.0★ (1) |
money | value, currency, opcional delta | MRR, gasto |
status | state + severity | semáforo de salud |
series | points:[{date,count}], series:[{name,points}] opcional (múltiple), apiladas con stack: true | instalaciones diarias, usuarios activos |
intraday | series:[{name,intraday:[{minute,value}]}], opcional step_min (predeterminado 15), tz | hoy en intervalos de 15 minutos |
hour_histogram | { weekdays:[...], start, end, tz } | conversiones por hora |
funnel | steps:[{key,label,value}] | de la página de precios al pago |
breakdown | items:[{label,value,unit?,sub?/money?,country?,parts?}], opcional ranges:{today,week,month,all} | suscripciones por plan, por motor, por superficie; visitas por origen o país |
list | items:[{text,count?,severity?}] en el nivel superior de la métrica, no dentro de value | errores recientes |
table | items:[{…}], un objeto por fila; columnas de dashboard.options.columns, si no, las claves de la primera fila | colas, envíos recientes |
text | value (string) | texto libre |
Una métrica table:
{ "id": "queues", "kind": "table", "label": "Mail queues",
"items": [ { "queue": "outbound", "size": 3, "oldest": "2m" },
{ "queue": "deferred", "size": 0, "oldest": "-" } ],
"dashboard": { "show": true, "tab": "backend", "section": "engines", "order": 5, "render": "table",
"options": { "columns": ["queue", "size", "oldest"] } } }5.2. Render (cómo se muestra en el panel)
dashboard.render elige la representación visual (normalmente coincide con kind, pero puede diferir, p. ej. mostrar un percent como badge). También es un conjunto abierto.
| render | Aspecto | Opciones (dashboard.options) |
|---|---|---|
kpi | Tarjeta: nombre + valor grande + delta; con stack, las partes en una fila debajo del valor | tone, stack |
badge | Píldora de estado / pictograma según la gravedad | – |
chart | Gráfico de líneas (Mes/Todo el periodo, media móvil de 7 días, hover); con stack: true, columnas apiladas por día con una fila Total | series, defaultMode, stack |
intraday | Hoy por intervalos (96 por día UTC): barras apiladas o líneas suavizadas, eje de tiempo HH:MM, lectura de todas las series al pasar el cursor | mode (bars | lines, predeterminado lines) |
hour_histogram | Barras por hora, agrupadas en Laborables / Fin de semana / Por día de la semana | series |
funnel | Pasos con la conversión entre ellos | – |
breakdown | Barras categóricas horizontales con valor + porcentaje del total. Con ranges, un selector hoy / 7 días / 30 días / todo; con map: countries, un mapa del mundo sobre la lista; con stack, barras divididas en partes conmutables | items, defaultRange, map, stack |
table | Tabla (por ejemplo, comentarios) | columns |
text | Párrafo / en línea | – |
status | Estado del semáforo por gravedad | – |
list | Lista de elementos con recuento/gravedad opcional | – |
intraday frente a chart. El eje de chart es diario (points[].date = YYYY-MM-DD) y su vista mensual se recorta a los últimos 30 puntos, por lo que los 96 intervalos de hoy no caben en él. intraday es el gráfico Hoy propio del panel expuesto como tipo del contrato: la misma convención en la que minute cuenta los minutos desde el inicio del día UTC (0, 15, …, 1425), pero las series y los datos los declara la fuente. Envía solo los intervalos no vacíos; el panel completa el resto.
Un intervalo vacío es null, no 0. Para un promedio (p. ej., el tiempo medio de conversión) un cero sería una mentira, así que la línea se interrumpe
en lugar de caer a cero. Un intervalo ausente y value: null son equivalentes: la línea se interrumpe, no se dibuja ninguna barra y la lectura al pasar el cursor muestra un guion.
Apilado: el total y sus partes en un gráfico y una lista. Cuando una métrica se compone de partes (por ejemplo, las páginas vistas de dos sitios), envía el total y las partes como UNA métrica: el panel dibuja las partes una sobre otra y la leyenda activa y desactiva cada una, sin pestañas adicionales ni filtros desplegables. Ambas extensiones son aditivas: un renderizador que no las conoce dibuja la misma métrica que antes.
Gráfico: kind: "series" con dashboard.options.stack: true. Las series se apilan en el orden del array: la primera es la capa inferior. El panel dibuja una columna por día (en todo el periodo, por ventana de 7 días) con un segmento por serie visible, así que la parte superior de una columna es la suma exacta de las series visibles; un día en que todas valen cero no tiene columna. Al hacer clic en una entrada de la leyenda se oculta una capa (la última visible sigue activa); el resto se redibuja desde cero y el eje de valores sigue al total visible. Al pasar el cursor por una columna, o al llegar a ella con las flechas, la lectura muestra cada capa visible y una fila Total. Todo el periodo apila la media móvil de 7 días de cada serie, marcada con ~. Cualquier otro valor de stack mantiene las líneas superpuestas.
Lista: kind: "breakdown" con dashboard.options.stack como lista ordenada de partes [{key, label}], y parts: {"key": number} en cada elemento, en items y en cada ventana de ranges. value debe ser igual a la suma de todas las partes. El panel muestra un chip por parte que la activa o desactiva para toda la tarjeta, en todas las ventanas. Una fila muestra la suma de sus partes visibles, las filas en cero se ocultan, la lista se reordena por esa suma y la barra se divide en un segmento por cada parte visible. Con map: "countries", los círculos siguen las sumas visibles. Un elemento sin parts cuenta entero, se desactive lo que se desactive.
{ "id": "site_sources", "kind": "breakdown", "label": "Sources",
"items": [ { "label": "Search engines", "value": 812, "parts": { "makcards": 790, "journalingapp": 22 } } ],
"ranges": { "today": [ { "label": "Search engines", "value": 30, "parts": { "makcards": 29, "journalingapp": 1 } } ],
"month": [ { "label": "Search engines", "value": 812, "parts": { "makcards": 790, "journalingapp": 22 } } ] },
"dashboard": { "show": true, "tab": "site", "section": "site_sources", "render": "breakdown",
"options": { "defaultRange": "month",
"stack": [ { "key": "makcards", "label": "makcards.online" },
{ "key": "journalingapp", "label": "journalingapp.app" } ] } } }
Tarjeta KPI: una tarjeta kpi de tipo number o delta acepta la misma lista dashboard.options.stack y parts: {"key": number} en la propia métrica. El valor grande sigue siendo value, que debe ser igual a la suma de todas las partes. Debajo, el panel muestra las partes en una fila en el orden de la pila (en una tarjeta estrecha la fila se ajusta), con el color que ese mismo nombre tiene en un gráfico apilado de la pestaña; una parte en cero se atenúa. La tarjeta no tiene interruptores. Los titulares de la lista de proyectos y Telegram siguen leyendo value.
{ "id": "site_views_today", "kind": "number", "label": "Page views", "value": 42,
"parts": { "portal": 30, "cabinet": 4, "backoffice": 8 },
"dashboard": { "show": true, "tab": "site", "section": "site_today", "render": "kpi",
"options": { "stack": [ { "key": "portal", "label": "zakazindia.ru" },
{ "key": "cabinet", "label": "Client cabinet" },
{ "key": "backoffice", "label": "Backoffice" } ] } } }5.3. La lista de proyectos: usuarios y objetivos de la pestaña Básica
La lista de proyectos muestra algunos números de un servicio web sin abrirlo. El panel los lee solo de la pestaña base y solo cuando la métrica tiene un value numérico. Gana la primera coincidencia.
| Métrica | Cómo la encuentra el panel | Dónde se muestra |
|---|---|---|
| DAU, WAU, MAU | El id, el label o el nombre en el dashboard es exactamente dau, wau o mau (sin distinguir mayúsculas). | WAU y MAU en la columna Usuarios, DAU en Hoy. |
| Objetivos 1 a 3 | El id es exactamente goal_1, goal_2 o goal_3. | Hoy, en la misma línea que DAU: un icono y un número. |
Un objetivo es una acción objetivo de tus usuarios: registros, compras, formularios enviados. Eliges la ventana del número y el panel lo muestra tal cual. Recomendamos contar el día de hoy y nombrar la ventana en el label (por ejemplo, Registros hoy). El label es el tooltip, hasta 80 caracteres. El campo icon admite un nombre del catálogo de la sección 4; los emoji no se aceptan para objetivos. Sin icono, o con un nombre desconocido, el panel dibuja target. Un cero se muestra como un guion atenuado.
{ "id": "goal_1", "kind": "number", "label": "Sign-ups today", "value": 12, "icon": "user-plus",
"dashboard": { "show": true, "tab": "base", "section": "goals", "render": "kpi" } }El panel guarda solo los últimos valores, como con DAU. Cada consulta correcta los reemplaza, y un objetivo que ya no está en la respuesta desaparece de la lista.
Varias superficies
Una fuente de métricas personalizadas pertenece a una superficie de tu servicio, y el panel guarda los números por superficie. La lista de proyectos muestra su suma; la página del proyecto muestra cada superficie por separado.
- Varias fuentes de una superficie cuentan como la última consultada. Las fuentes sin superficie forman un grupo más.
- Solo cuentan las superficies de producción. Una superficie en un entorno de staging, pruebas o desarrollo queda fuera.
- Una superficie cuyos últimos números van más de 48 horas por detrás de los más recientes queda fuera.
- Cada ventana se suma sobre las superficies que la envían, y los objetivos se suman por id, así que un objetivo debe significar lo mismo en todas las superficies.
- La suma es un límite superior: una persona que usa dos superficies se cuenta dos veces.
Extensiones de Chrome
Una extensión usa la misma convención. Cuando su feed envía dau y objetivos en la pestaña base, la lista de proyectos los muestra en una línea más bajo los contadores de instalaciones, desinstalaciones, comentarios y reinstalaciones, con el mismo formato que la línea Hoy de un servicio web.
- El WAU y el MAU de una extensión no se muestran en la lista; su columna Usuarios es el número de usuarios de Chrome Web Store.
- Cuenta el DAU y los objetivos del día UTC actual: los contadores de la misma fila usan días UTC.
- Varias fuentes de una extensión se suman según las reglas anteriores. No hay variación respecto a ayer.
- Solo el autor ve la línea. El botón de actualizar de la fila también consulta las métricas personalizadas.
5.4. Umbrales (warn y crit)
Una métrica numérica puede llevar thresholds. Entonces el panel calcula por sí mismo la severidad de la métrica, en cada consulta y en cada recepción de heartbeat, y muestra los límites junto al valor: 41 (threshold 20/100), amarillo entre warn y crit, rojo a partir de crit. La severidad que su servicio envía para esa métrica se sustituye por la calculada.
| Campo | Significado |
|---|---|
direction | above: peor cuanto más alto (cola, CPU). below: peor cuanto más bajo (disco libre, días restantes). |
warn, crit | Números. Con above, un valor igual o superior a warn es amarillo y igual o superior a crit es rojo; below lo invierte. Con above warn no debe superar a crit, con below no debe quedar por debajo. |
unit | Etiqueta opcional de hasta 16 bytes para el texto: threshold 80/95%. |
recover | Histéresis opcional: una métrica que estaba en warn o crit vuelve a ok solo cuando el valor ha pasado recover; entre ambos sigue en warn. |
{
"id": "queue",
"kind": "number",
"label": "Mail queue",
"value": 41,
"thresholds": { "direction": "above", "warn": 20, "crit": 100, "recover": 15 },
"dashboard": { "show": true, "tab": "backend", "section": "mail", "order": 1, "render": "kpi" },
"telegram": { "show": true, "line": "mail", "pos": 10, "format": "queue {value}", "when": "severity>=warn" }
}- Los umbrales comparan
value; endurationcomparanseconds(si no,minutes), enbytescomparanbytes(si no,kb). Los kinds no numéricos comostatus,listotableno pueden llevar umbrales. - Sin número esta vez significa
idle: gris, y no cuenta para ninguna luz. - Un bloque mal formado se rechaza en un informe de heartbeat (
422 invalid_report). En una respuesta consultada se descarta, la métrica se dibuja tal como llegó y el motivo aparece como último error de la fuente. - En un informe de heartbeat, la luz del heartbeat nunca queda por debajo de un umbral superado (consulte el contrato de monitorización).
Consulta programada. En un servicio web puede activar la consulta programada de una fuente en su configuración. El panel consulta entonces el endpoint con un intervalo que va desde el mínimo de su plan (5 minutos en Trial e Indie, 1 minuto en Studio y Fleet) hasta 24 horas, con la misma petición que cualquier consulta: un User-Agent honesto y como máximo una petición por minuto. Ocupa una unidad de monitorización, por lo que no está disponible en Free. Un umbral superado abre un incidente para esa métrica y le avisa; una superación repetida no vuelve a avisar. El incidente se cierra tras una consulta sin superación si el intervalo es de 1 hora o más, y tras dos consultas seguidas sin superación si es más corto. Una consulta fallida no cambia nada y muestra su motivo como último error. Hasta 20 métricas con umbrales por fuente generan incidentes.
6. La paleta la controla el panel
Los colores de los gráficos y de las series los asigna el panel a partir de los tokens de marca, no la fuente. options.series[] solo lleva la identidad ({key,label,order}); un campo color en el payload es orientativo y se ignora. Las claves semánticas se asignan a tokens fijos (paid al color de acento, free al neutro atenuado, failed/error a peligro, ready/success a éxito); las demás claves toman la paleta categórica de la marca por posición. Los rellenos y las líneas de las series mantienen un contraste de al menos 3:1 en ambos temas (WCAG 1.4.11), y el texto y las etiquetas de al menos 4,5:1. Así la marca queda protegida de colores hex externos arbitrarios.
7. Plantillas de Telegram
telegram.header es el encabezado del mensaje; telegram.lines[] son las líneas lógicas ({ id, order, prefix, when }). El compositor reúne todas las métricas con un telegram.line dado, filtra por when, ordena por pos, renderiza cada una con su plantilla format y las une con el sep de cada métrica. La línea prefix se imprime primero. Todo es texto plano.
| Marcador de posición | Resultado |
|---|---|
{value} | Por tipo (número 1,200; porcentaje 99%) |
{value:k} | 1.2k / 3.0M (compacto) |
{delta:+} | +72 / -3 |
{value:pct} | 99% |
{seconds:dur} | 28s / 5m 47s |
{minutes:age} | 45m / 2h 30m / never |
{bytes:size} | ~522KB / 1.4MB |
{value}★ ({count}) | 5.0★ (1) |
{sev} | Emoji de gravedad: 🟢 ok, 🟡 warn, 🔴 crit, ⚪ idle |
{threshold} | El umbral de la métrica: threshold 20/100. Si no está en el formato, a una métrica con umbrales se le añade (threshold 20/100) al final. |
{trend} | ↗ / ↘ (±5pp vs 7d) |
8. Condiciones de visibilidad (cuándo)
when controla si se muestra un campo de métrica (o una línea de Telegram completa). Valores:
| when | Muestra el campo |
|---|---|
always | Siempre (predeterminado). |
nonzero | Solo cuando el valor no es 0 / vacío. |
severity>=warn | Solo cuando la gravedad es warn o crit. |
changed | Solo cuando cambió desde la instantánea anterior. |
Los valores when desconocidos se comportan como always (compatibilidad hacia delante).
9. Ejemplo de payload
{
"schema_version": "2.0",
"generated_at": "2026-07-02T23:17:30Z",
"tabs": [
{ "id": "conv", "title": "Conversions", "order": 1, "icon": "trending-up" }
],
"sections": [
{ "tab": "conv", "id": "by_hour", "title": "By hour", "order": 0 }
],
"telegram": {
"header": "{name} • {cws.users} • {cws.rating}★ ({cws.rating_count})",
"lines": [
{ "id": "base", "order": 0 },
{ "id": "conv", "order": 1, "prefix": "{sev} Conv " }
]
},
"metrics": [
{
"id": "conv_today",
"kind": "number",
"label": "Conversions today",
"value": 254,
"severity": "ok",
"shareable": true,
"dashboard": {
"show": true,
"tab": "conv", "section": "by_hour", "order": 10,
"name": "Today", "render": "kpi", "options": {}
},
"telegram": {
"show": true,
"line": "conv", "pos": 20,
"format": "today {value}", "sep": " • ", "when": "always"
}
}
]
}10. El contrato legible por máquina
La descripción formal e importable de este endpoint es el archivo OpenAPI 3.1. Lleva la misma versión de contrato (2.1.0) y es el único artefacto canónico legible por máquina, común a todos los idiomas.