BetterMeter
Documentación
Documentación

Rastree todo.
Comprenda todo.

BetterMeter es una plataforma de analíticas orientada a la privacidad para la era de la IA. Rastree sus sitios web, herramientas CLI, servidores MCP y APIs -- todo desde un solo lugar.

01

Primeros pasos

BetterMeter rastrea cuatro tipos de fuentes. Cada una utiliza una integración ligera que envía eventos al mismo pipeline respetuoso de la privacidad.

Sitios web
<script> tag
~1 KB, sin cookies
Herramientas CLI
Node SDK
trackCommand()
Servidores MCP
Node SDK
wrapMcpServer()
APIs
Node SDK
expressMiddleware()

Todas las fuentes pasan por el mismo pipeline: evento -> procesamiento (análisis de referente, detección de bots, geolocalización, hash de privacidad) -> base de datos. Puede ver todo en el dashboard, CLI o mediante herramientas MCP.

02

Rastreo web

Agregue una sola etiqueta de script a su sitio. Sin cookies, sin configuración CNAME, sin instalación compleja. ~1,3 KB comprimido.

HTML -- add to <head>
<script defer data-site="example.com" src="https://bettermeter.com/api/script"></script>

Opcional: agregue un pixel de rastreo sin JS antes de la etiqueta de cierre </body> para detectar bots y rastreadores que no ejecutan JavaScript:

HTML -- add before </body>
<img src="https://bettermeter.com/api/pixel?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />

El script de rastreo captura automáticamente las vistas de página, la navegación SPA (History API) y los retornos de pestañas. Para eventos personalizados e identificación de usuarios:

JavaScript
// Track custom events
window.bettermeter.track("signup", { plan: "pro" });

// Identify users (optional)
window.bettermeter.identify("user_123");

Atributos

data-siterequerido
string
Su dominio tal como está registrado en BetterMeter
data-api
string
Endpoint API personalizado para configuraciones de proxy (ej.: /api/collect)
data-no-heartbeat
flag
Desactivar el rastreo de visitantes en tiempo real para esta página

Detección de bots del lado del servidor

La mayoría de los bots (Googlebot, GPTBot, ClaudeBot, etc.) no ejecutan JavaScript, por lo que el script de rastreo nunca se activa para ellos. Para detectar tráfico de bots y rastreadores, agregue una línea a su middleware de Next.js:

middleware.ts
import { reportBotVisit } from "@bettermeter/node/middleware";

export function middleware(request) {
  reportBotVisit(request, "example.com");
  // ... rest of your middleware
}

Instalar el SDK:

Terminal
npm install @bettermeter/node

Compatible con edge, no bloqueante, sin impacto en la latencia. Detecta más de 25 bots conocidos, incluyendo rastreadores de IA, motores de búsqueda y servicios de monitoreo. Las visitas de bots aparecen automáticamente en su dashboard de Crawlers.

03

Rastreo de CLI

Rastree cómo los desarrolladores usan su herramienta CLI. Vea qué comandos son populares, cuánto tiempo toman y qué errores ocurren -- sin recopilar datos personales.

Install
npm install @bettermeter/node
Auto-track with Commander.js
import { BetterMeter } from "@bettermeter/node";
import { Command } from "commander";

const bm = new BetterMeter({
  siteId: "my-cli-tool",
  apiKey: "bm_...",
});

const program = new Command();

// Wraps all commands — tracks name, flags, exit code
bm.wrapCommander(program, { version: "1.0.0" });

program.command("deploy").action(() => { /* ... */ });
program.parse();

// Flush on exit
process.on("SIGTERM", () => bm.shutdown());
Manual tracking
bm.trackCommand({
  command: "deploy",
  subcommand: "preview",
  flags: ["--prod", "--verbose"],
  version: "2.1.0",
  durationMs: 4500,
  exitCode: 0,
  isCi: !!process.env.CI,
});

Qué se rastrea

Solo nombres de comandos y de flags. Los valores de flags, argumentos y rutas de archivos nunca se envían. El SDK captura el sistema operativo y la arquitectura para analíticas de entorno.

04

Rastreo de MCP

Rastree qué herramientas llaman los clientes de IA en su servidor MCP, con qué frecuencia, qué clientes (Claude, Cursor, Windsurf) generan más uso y las tasas de error.

Auto-track all tools
import { BetterMeter } from "@bettermeter/node";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const bm = new BetterMeter({
  siteId: "my-mcp-server",
  apiKey: "bm_...",
});

const server = new McpServer({
  name: "my-server",
  version: "1.0.0",
});

// Wraps server.tool() — auto-tracks every invocation
bm.wrapMcpServer(server);

// Tools registered after wrapping are automatically tracked
server.tool("search_docs", { query: z.string() }, async (args) => {
  // ... your tool logic (unchanged)
});
Manual tracking
bm.trackTool({
  tool: "search_docs",
  client: "claude-code",
  protocolVersion: "2024-11-05",
  durationMs: 250,
  success: true,
  inputTokens: 150,
  outputTokens: 800,
});

Qué se rastrea

Nombres de herramientas, nombres de clientes, duración, éxito/fallo y conteos opcionales de tokens. Los parámetros de entrada y el contenido de salida de las herramientas nunca se envían.

05

Rastreo de API

Rastree el uso de endpoints API, latencia, tasas de error y patrones de uso con middleware Express o llamadas manuales.

Express middleware
import { BetterMeter } from "@bettermeter/node";
import express from "express";

const bm = new BetterMeter({
  siteId: "my-api",
  apiKey: "bm_...",
});

const app = express();

// Auto-track all requests
app.use(bm.expressMiddleware());
Manual tracking
bm.trackApi({
  method: "POST",
  endpoint: "/api/users",    // Use patterns, not actual paths with IDs
  statusCode: 201,
  durationMs: 45,
});

Qué se rastrea

Método HTTP, patrón de endpoint, código de estado y duración. Los cuerpos de solicitud/respuesta, encabezados, parámetros de consulta y parámetros de ruta nunca se envían. Use patrones de endpoint (/api/users/:id) no rutas reales (/api/users/abc123).

06

Referencia del SDK

El SDK @bettermeter/node no tiene dependencias (usa fetch y crypto integrados). Requiere Node.js 18+.

Constructor

const bm = new BetterMeter(config);
siteIdrequerido
string
Dominio o identificador registrado en BetterMeter
apiKeyrequerido
string
Clave API (token Bearer) desde la configuración del dashboard
apiUrl
string
URL de la API de BetterMeter. Por defecto: "https://bettermeter.com"
disabled
boolean
Desactivar todo el rastreo. Por defecto: false
batch
boolean
Poner eventos en cola y vaciar a intervalo. Por defecto: false
batchInterval
number
Intervalo de vaciado en ms. Por defecto: 5000

trackCommand(options)

commandrequerido
string
Nombre del comando
subcommand
string
Subcomando (ej.: "deploy preview")
flags
string[]
Nombres de flags usados (valores eliminados)
version
string
Versión del CLI
durationMs
number
Tiempo de ejecución en milisegundos
exitCode
number
Código de salida del proceso (0 = éxito)
isCi
boolean
Ejecutando en entorno CI
userId
string
Identificador de usuario personalizado
properties
object
Propiedades personalizadas adicionales

trackTool(options)

toolrequerido
string
Nombre de la herramienta MCP
client
string
Nombre del cliente IA (ej.: "claude-code", "cursor")
protocolVersion
string
Versión del protocolo MCP
durationMs
number
Tiempo de ejecución en milisegundos
success
boolean
Si la llamada tuvo éxito
errorType
string
Clasificación del error (ej.: "validation_error")
inputTokens
number
Conteo de tokens de entrada
outputTokens
number
Conteo de tokens de salida
userId
string
Identificador de usuario personalizado
properties
object
Propiedades personalizadas adicionales

trackApi(options)

methodrequerido
string
Método HTTP (GET, POST, etc.)
endpointrequerido
string
Patrón de endpoint (use :param para segmentos dinámicos)
statusCode
number
Código de estado de la respuesta HTTP
durationMs
number
Tiempo de respuesta en milisegundos
userId
string
Identificador de usuario personalizado
properties
object
Propiedades personalizadas adicionales

Encapsuladores automáticos

wrapCommander(program, options?)

Se conecta al postAction de Commander.js para rastrear automáticamente todos los comandos.

wrapMcpServer(server)

Parche dinámico de server.tool() para encapsular todos los manejadores con cronometraje y rastreo de errores.

expressMiddleware()

Devuelve un middleware Express/Connect que rastrea cada solicitud en res.end.

Detección de bots del lado del servidor

La mayoría de los bots no ejecutan JavaScript, por lo que el script de rastreo nunca se activa para ellos. Use reportBotVisit() en su middleware de servidor para detectar bots a nivel de solicitud.

middleware.ts
// Next.js middleware
import { reportBotVisit } from "@bettermeter/node/middleware";

export function middleware(request) {
  reportBotVisit(request, "my-site.com");
  // ... rest of your middleware
}
requestrequerido
Request
Solicitud entrante (NextRequest, Request, etc.)
siteIdrequerido
string
Su ID de sitio BetterMeter
options.apiUrl
string
URL de la API de BetterMeter. Por defecto: https://bettermeter.com

Compatible con edge (sin dependencias de Node.js). No bloqueante -- no agrega latencia a las respuestas. Omite automáticamente recursos estáticos, rutas de API e internos de Next.js.

Ciclo de vida

flush(): Promise<void>

Enviar todos los eventos en cola inmediatamente.

shutdown(): Promise<void>

Detener el temporizador de lotes y vaciar los eventos restantes. Llamar antes de que finalice el proceso.

07

Referencia de CLI

El CLI de BetterMeter le permite consultar analíticas desde la terminal con una salida visual elegante. Todos los comandos aceptan -s/--site, -r/--range, -l/--limit y --json.

Install & authenticate
npm install -g bettermeter
bettermeter login -t <apiKey> -u <dashboardUrl>

Formatos de salida

Por defecto, el CLI muestra una salida visual enriquecida con gráficos ASCII, texto con color, sparklines y tarjetas de estadísticas. Toda la salida usa caracteres Unicode compatibles con todas las terminales modernas. Los colores detectan automáticamente las capacidades de la terminal y respetan la variable de entorno NO_COLOR.

Por defecto (visual)Gráficos de líneas, barras horizontales, sparklines, tablas estilizadas con color
--jsonDatos JSON sin procesar -- ideal para scripts, piping a jq o uso programático

La salida visual incluye:

  • Gráficos de líneas para datos de series temporales (visitantes diarios, invocaciones)
  • Barras horizontales para listas ordenadas (páginas, fuentes, países)
  • Sparklines junto a las estadísticas generales para una visualización rápida de tendencias
  • Tarjetas de estadísticas con indicadores de cambio a color para resúmenes
  • Tablas estilizadas con bordes para datos detallados
Example: visual vs JSON
# Visual output (default)
bettermeter stats -s example.com

# JSON output for scripting
bettermeter stats -s example.com --json | jq '.visitors'

Autenticación

login -t <key> -u <url>Autenticarse con clave API y URL del dashboard
logoutEliminar credenciales almacenadas
whoamiMostrar el usuario autenticado actual

Tiempo real

live -s <siteId>Conteo de visitantes en vivo (--json para salida sin procesar)

Analíticas web

statsResumen: visitantes, vistas de página, sesiones + % de cambio
pagesPáginas más visitadas por número de visitantes
sourcesFuentes de tráfico (--filter all|ai|traditional)
ai-trafficDesglose de referencias de IA por plataforma
botsTráfico de bots/rastreadores (--category all|ai-crawler|search|monitoring|scraper)
timeseriesTendencia diaria de visitantes/vistas de página
countriesVisitantes por país
devicesDesglose por dispositivo
browsersDesglose por navegador
visitorsVisitantes recientes con resumen de actividad
eventsEventos personalizados con conteos
campaignsAtribución de URL de campaña (UTM + IDs de clic)
channelsDesglose por canal (Directo, Búsqueda pagada, Orgánico, etc.)
exportReporte completo (--format json|csv|md)

Analíticas de CLI

cli-overviewInvocaciones, usuarios, tasa de éxito, duración promedio
cli-commandsComandos más usados por número de invocaciones
cli-timeseriesActividad diaria de CLI

Analíticas de MCP

mcp-overviewInvocaciones, usuarios, tasa de éxito, duración promedio
mcp-toolsHerramientas MCP más usadas por número de invocaciones
mcp-clientsDesglose por cliente (Claude, Cursor, etc.)
mcp-timeseriesActividad diaria de MCP

Analíticas de API

api-overviewInvocaciones, usuarios, tasa de error, duración promedio
api-endpointsEndpoints más usados por número de invocaciones
api-timeseriesActividad diaria de API

Pulse AI

pulse insightsAnomalías, tendencias, oportunidades, hitos
pulse healthPuntuación de salud del producto (0-100, nota A-F)
pulse briefingBriefing diario/semanal (-p/--period daily|weekly)
pulse forecastPronóstico de tráfico (-m/--metric, -d/--days)
pulse compareComparar dos períodos (-r/--range, --from2, --to2)
pulse alertsListar reglas de alerta de monitoreo
pulse alerts:createCrear alerta (-t/--type, -n/--name, -c/--condition)
pulse alerts:deleteEliminar una alerta (-i/--id)
pulse notificationsNotificaciones recientes (--unread solo para no leídas)

Posicionamiento en búsqueda y visibilidad IA

brand-report <domain>Generar reporte de posicionamiento en búsqueda (-q/--queries)
brand-config <domain>Ver/actualizar configuración de monitoreo de marca
brand-compare <domain>Comparar posicionamiento con competidores (-q, -c)
brand-alerts <domain>Gestionar alertas de posicionamiento (-a list|create|delete)
ai-mentions <domain>Menciones de marca por chatbots de IA (-q/--queries, -p/--providers)
backlinks <domain>Perfil de backlinks: rango del dominio, dominios referentes

Gestión de sitios

sites listListar todos los sitios
sites add <domain>Agregar un nuevo sitio (-n/--name para nombre de visualización)
sites remove <siteId>Eliminar un sitio
sites info <siteId>Mostrar detalles del sitio y código de rastreo
install <siteId>Obtener código de rastreo para un sitio

Gestión de equipo

members list -s <siteId>Listar miembros del sitio
members add <email>Agregar un miembro (-s, -r viewer|editor|admin, --all-sites)
members remove <id>Eliminar un miembro (-s)
members update-role <id>Actualizar rol de miembro (-s, -r)

Facturación

billingMostrar plan actual, uso e información de facturación (--json)

Opciones

-s, --siterequerido
string
ID del sitio (dominio)
-r, --range
string
Rango de fechas: today, 7d, 30d, 90d, 12m. Por defecto: 30d
-l, --limit
number
Máximo de resultados. Por defecto: 10
--json
flag
Salida JSON sin procesar en lugar de gráficos y tablas visuales
08

Referencia de herramientas MCP

BetterMeter funciona como un servidor MCP para asistentes de IA. Cada comando CLI tiene una herramienta MCP correspondiente con el prefijo bettermeter_.

.mcp.json
{
  "mcpServers": {
    "bettermeter": {
      "command": "npx",
      "args": ["bettermeter"]
    }
  }
}

Todas las herramientas de analíticas aceptan site_id (string), range (today|7d|30d|90d|12m) y limit (number). La herramienta MCP bettermeter_export soporta los formatos json y md (el comando CLI export también soporta csv). Ejemplos de uso para asistentes de IA:

"How many live visitors right now?"bettermeter_live_visitors
"How's my traffic?"bettermeter_stats
"Show AI referral breakdown"bettermeter_ai_traffic
"Which CLI commands are most used?"bettermeter_cli_commands
"What AI clients call my MCP server?"bettermeter_mcp_clients
"Top API endpoints this week"bettermeter_api_endpoints with range=7d
"How healthy is my product?"bettermeter_pulse_health
"Any anomalies I should know about?"bettermeter_pulse_insights
"Give me a weekly briefing"bettermeter_pulse_briefing with period=weekly

Lista completa de herramientas

Tiempo real

bettermeter_live_visitors

Analíticas web

bettermeter_statsbettermeter_pagesbettermeter_sourcesbettermeter_ai_trafficbettermeter_botsbettermeter_timeseriesbettermeter_countriesbettermeter_devicesbettermeter_browsersbettermeter_visitorsbettermeter_visitorbettermeter_eventsbettermeter_campaignsbettermeter_channelsbettermeter_export

Analíticas de CLI

bettermeter_cli_overviewbettermeter_cli_commandsbettermeter_cli_timeseries

Analíticas de MCP

bettermeter_mcp_overviewbettermeter_mcp_toolsbettermeter_mcp_clientsbettermeter_mcp_timeseries

Analíticas de API

bettermeter_api_overviewbettermeter_api_endpointsbettermeter_api_timeseries

Gestión de sitios

bettermeter_sites_listbettermeter_sites_addbettermeter_sites_removebettermeter_sites_infobettermeter_install

Gestión de equipo

bettermeter_members_listbettermeter_members_addbettermeter_members_removebettermeter_members_update_role

Posicionamiento en búsqueda y visibilidad IA

bettermeter_brand_reportbettermeter_brand_configbettermeter_brand_comparebettermeter_brand_alertsbettermeter_ai_mentionsbettermeter_backlinks

Facturación

bettermeter_billing

Pulse AI

bettermeter_pulse_insightsbettermeter_pulse_healthbettermeter_pulse_briefingbettermeter_pulse_forecastbettermeter_pulse_comparebettermeter_pulse_alerts_listbettermeter_pulse_alerts_createbettermeter_pulse_alerts_deletebettermeter_pulse_notifications
09

Referencia de API

Todos los endpoints de analíticas aceptan solicitudes GET con parámetros de consulta. Autentíquese con Authorization: Bearer <api_key>.

Ingestión de eventos

POST /api/eventIngerir un evento (web, CLI, MCP o API). Devuelve 202.
Event payload
{
  "site_id": "example.com",
  "event_name": "pageview",        // or "cli.command", "mcp.tool", "api.request"
  "event_source": "web",           // "web" | "cli" | "mcp" | "api"
  "url": "https://example.com/page",
  "pathname": "/page",
  "hostname": "example.com",
  "referrer": "https://google.com",
  "screen_width": 1920,
  "timezone": "America/New_York",
  "user_id": "optional_user_id",
  "properties": { "key": "value" }
}

Tiempo real y heartbeat

GET /api/analytics/liveConteo de visitantes en vivo. Devuelve { live: number }. Acepta ?siteId=...
POST /api/heartbeatRecibir heartbeats del navegador para rastreo de visitantes en vivo
POST /api/hAlias sigiloso para /api/heartbeat (resistente a bloqueadores de anuncios)

Endpoints de consulta

Todos aceptan ?siteId=...&from=YYYY-MM-DD&to=YYYY-MM-DD.

Analíticas web

GET /api/analytics/overviewVisitantes, vistas de página, sesiones + % de cambio
GET /api/analytics/pagesPáginas más visitadas por número de visitantes
GET /api/analytics/sourcesFuentes de tráfico con detección de IA
GET /api/analytics/timeseriesTendencia diaria de visitantes/vistas de página
GET /api/analytics/ai-trafficDesglose de referencias de IA por plataforma
GET /api/analytics/botsTráfico de bots/rastreadores
GET /api/analytics/countriesVisitantes por país
GET /api/analytics/devicesDesglose por tipo de dispositivo
GET /api/analytics/browsersDesglose por navegador
GET /api/analytics/visitorsLista de visitantes con actividad
GET /api/analytics/visitors/[visitorId]Perfil del visitante con cronología de eventos
GET /api/analytics/eventsEventos personalizados
GET /api/analytics/campaignsAtribución de URL de campaña (UTM + IDs de clic)
GET /api/analytics/campaigns/[campaign]Detalle de una campaña individual
GET /api/analytics/channelsDesglose por canal
GET /api/analytics/session-statsEstadísticas de sesiones
GET /api/analytics/sessionsLista de sesiones

Analíticas de CLI

GET /api/analytics/cli-overviewInvocaciones, usuarios, tasa de éxito
GET /api/analytics/cli-commandsComandos más usados
GET /api/analytics/cli-timeseriesActividad diaria de CLI

Analíticas de MCP

GET /api/analytics/mcp-overviewInvocaciones, usuarios, tasa de éxito
GET /api/analytics/mcp-toolsHerramientas más usadas
GET /api/analytics/mcp-clientsDesglose por cliente
GET /api/analytics/mcp-timeseriesActividad diaria de MCP

Analíticas de API

GET /api/analytics/api-overviewInvocaciones, usuarios, tasa de error
GET /api/analytics/api-endpointsEndpoints más usados
GET /api/analytics/api-timeseriesActividad diaria de API

Posicionamiento en búsqueda y visibilidad IA

GET /api/analytics/brandReporte de visibilidad en posicionamiento de búsqueda
GET /api/analytics/brand/historyDatos históricos de posicionamiento
GET /api/analytics/brand/compareComparación con competidores
GET /api/analytics/brand/alertsReglas de alerta de posicionamiento
GET /api/analytics/brand/exportExportar datos de posicionamiento
GET /api/analytics/backlinksPerfil de backlinks
GET /api/analytics/ai-mentionsMenciones de marca por chatbots de IA
GET /api/analytics/ai-mentions/historyHistorial de menciones de IA

Pulse AI

GET /api/pulse/insightsAnomalías, tendencias, oportunidades
GET /api/pulse/healthPuntuación de salud del producto (0-100)
GET /api/pulse/briefingBriefing diario o semanal
GET /api/pulse/forecastPronóstico de tráfico/uso
GET /api/pulse/compareComparación de períodos
GET /api/pulse/alertsListar alertas de monitoreo
POST /api/pulse/alertsCrear regla de alerta
DELETE /api/pulse/alertsEliminar regla de alerta
GET /api/pulse/notificationsObtener notificaciones
PATCH /api/pulse/notificationsMarcar notificaciones como leídas
POST /api/pulse/chatChat Pulse AI (streaming)
10

Pulse AI

Pulse AI es la capa de inteligencia analítica proactiva de BetterMeter. En lugar de esperar a que revise los dashboards, Pulse detecta automáticamente anomalías, muestra insights, evalúa la salud del producto, pronostica tendencias y entrega briefings diarios o semanales.

Capacidades

Detección de anomalías
Detecta picos de tráfico, caídas, cambios en tasas de error, oleadas de bots y nuevas fuentes de referencia
Insights proactivos
Muestra tendencias, oportunidades de crecimiento e hitos antes de que los solicite
Puntuación de salud
Puntuación unificada de 0 a 100 (nota A-F) que cubre tráfico, interacción, errores y crecimiento
Pronósticos
Extrapola tendencias de tráfico y uso con intervalos de confianza
Comparación de períodos
Análisis detallado lado a lado de dos períodos con métricas diferenciales
Briefings
Resúmenes diarios o semanales con métricas clave, aspectos destacados y acciones recomendadas

Tipos de alertas

Cree alertas de monitoreo que generan notificaciones cuando se cumplen las condiciones:

traffic_spikeAlerta cuando el tráfico supera un umbral
traffic_dropAlerta cuando el tráfico cae por debajo de un umbral
error_rateAlerta cuando la tasa de error supera un umbral
bot_surgeAlerta por picos inusuales de actividad de bots/rastreadores
new_ai_crawlerAlerta cuando se detecta un nuevo rastreador de IA
performanceAlerta por degradación del rendimiento

Endpoints de API

GET /api/pulse/insightsAnomalías, tendencias, oportunidades, hitos
GET /api/pulse/healthPuntuación de salud del producto (0-100) con desglose por categoría
GET /api/pulse/briefingResumen de briefing diario o semanal
GET /api/pulse/forecastPronóstico de tráfico/uso con intervalos de confianza
GET /api/pulse/compareComparación detallada período por período
GET /api/pulse/alertsListar reglas de alerta de monitoreo
POST /api/pulse/alertsCrear una nueva regla de alerta
DELETE /api/pulse/alertsEliminar una regla de alerta
GET /api/pulse/notificationsObtener notificaciones recientes
PATCH /api/pulse/notificationsMarcar notificaciones como leídas

Comandos CLI

pulse insightsObtener insights proactivos y anomalías
pulse healthPuntuación de salud del producto con nota
pulse briefingBriefing diario o semanal (-p/--period daily|weekly)
pulse forecastPronóstico de tráfico (-m/--metric, -d/--days)
pulse compareComparar dos períodos (-r/--range, --from2, --to2)
pulse alertsListar alertas de monitoreo
pulse alerts:createCrear alerta (-t/--type, -n/--name, -c/--condition)
pulse alerts:deleteEliminar una alerta (-i/--id)
pulse notificationsNotificaciones recientes (--unread solo para no leídas)

Herramientas MCP

bettermeter_pulse_insightsbettermeter_pulse_healthbettermeter_pulse_briefingbettermeter_pulse_forecastbettermeter_pulse_comparebettermeter_pulse_alerts_listbettermeter_pulse_alerts_createbettermeter_pulse_alerts_deletebettermeter_pulse_notifications
11

Configuración de proxy

Los bloqueadores de anuncios a veces bloquean scripts de analíticas de terceros. Al hacer proxy de BetterMeter a través de su propio dominio, el script y los eventos aparecen como solicitudes de primera parte -- haciéndolos invisibles para los bloqueadores. Esto funciona porque el navegador ve solicitudes a yourdomain.com/bm/... en lugar de bettermeter.com/api/....

Cómo funciona

Configure reescrituras de URL en su servidor para que tres rutas de su dominio redirijan a BetterMeter:

/bm/s→ https://bettermeter.com/api/sScript de rastreo
/bm/e→ https://bettermeter.com/api/eEndpoint de eventos
/bm/p→ https://bettermeter.com/api/pPixel noscript

Luego actualice su código de rastreo para usar las rutas del proxy:

Proxied snippet
<script defer data-site="example.com" data-api="/bm/e" src="/bm/s"></script>
<img src="/bm/p?s=example.com" alt="" style="position:absolute;width:0;height:0;overflow:hidden" />

El atributo data-api le indica al script que envíe eventos a su endpoint proxy en lugar de directamente a BetterMeter. El src carga el script desde su proxy. Para el navegador (y los bloqueadores de anuncios), todo es una solicitud del mismo origen.

Guías por plataforma

Next.js

next.config.js
module.exports = {
  async rewrites() {
    return [
      { source: "/bm/s", destination: "https://bettermeter.com/api/s" },
      { source: "/bm/e", destination: "https://bettermeter.com/api/e" },
      { source: "/bm/p", destination: "https://bettermeter.com/api/p" },
    ];
  },
};

Nuxt

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/bm/s": { proxy: "https://bettermeter.com/api/s" },
    "/bm/e": { proxy: "https://bettermeter.com/api/e" },
    "/bm/p": { proxy: "https://bettermeter.com/api/p" },
  },
});

SvelteKit

svelte.config.js
const config = {
  kit: {
    // SvelteKit doesn't have built-in rewrites.
    // Use a server hook instead:
  },
};
src/hooks.server.ts
import type { Handle } from "@sveltejs/kit";

const PROXY_MAP: Record<string, string> = {
  "/bm/s": "https://bettermeter.com/api/s",
  "/bm/e": "https://bettermeter.com/api/e",
  "/bm/p": "https://bettermeter.com/api/p",
};

export const handle: Handle = async ({ event, resolve }) => {
  const target = PROXY_MAP[event.url.pathname];
  if (target) {
    const url = new URL(target);
    url.search = event.url.search;
    const res = await fetch(url, {
      method: event.request.method,
      headers: event.request.headers,
      body: event.request.method !== "GET" ? event.request.body : undefined,
      // @ts-expect-error — needed for streaming
      duplex: "half",
    });
    return new Response(res.body, {
      status: res.status,
      headers: res.headers,
    });
  }
  return resolve(event);
};

Astro

src/pages/bm/[...proxy].ts
import type { APIRoute } from "astro";

const PROXY_MAP: Record<string, string> = {
  s: "https://bettermeter.com/api/s",
  e: "https://bettermeter.com/api/e",
  p: "https://bettermeter.com/api/p",
};

export const ALL: APIRoute = async ({ params, request }) => {
  const target = PROXY_MAP[params.proxy ?? ""];
  if (!target) return new Response("Not found", { status: 404 });

  const url = new URL(target);
  url.search = new URL(request.url).search;
  return fetch(url, {
    method: request.method,
    headers: request.headers,
    body: request.method !== "GET" ? request.body : undefined,
    // @ts-expect-error — needed for streaming
    duplex: "half",
  });
};

Remix / React Router

app/routes/bm.$.tsx
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";

const PROXY_MAP: Record<string, string> = {
  s: "https://bettermeter.com/api/s",
  e: "https://bettermeter.com/api/e",
  p: "https://bettermeter.com/api/p",
};

async function proxy({ request, params }: LoaderFunctionArgs | ActionFunctionArgs) {
  const target = PROXY_MAP[params["*"] ?? ""];
  if (!target) return new Response("Not found", { status: 404 });

  const url = new URL(target);
  url.search = new URL(request.url).search;
  return fetch(url, {
    method: request.method,
    headers: request.headers,
    body: request.method !== "GET" ? request.body : undefined,
    // @ts-expect-error — needed for streaming
    duplex: "half",
  });
}

export const loader = proxy;
export const action = proxy;

Nginx

nginx.conf
location /bm/s {
    proxy_pass https://bettermeter.com/api/s;
    proxy_ssl_server_name on;
    proxy_set_header Host bettermeter.com;
}

location /bm/e {
    proxy_pass https://bettermeter.com/api/e;
    proxy_ssl_server_name on;
    proxy_set_header Host bettermeter.com;
}

location /bm/p {
    proxy_pass https://bettermeter.com/api/p;
    proxy_ssl_server_name on;
    proxy_set_header Host bettermeter.com;
}

Apache

.htaccess or httpd.conf
RewriteEngine On
RewriteRule ^bm/s$ https://bettermeter.com/api/s [P,L]
RewriteRule ^bm/e$ https://bettermeter.com/api/e [P,L]
RewriteRule ^bm/p$ https://bettermeter.com/api/p [P,L]

# Requires mod_proxy and mod_proxy_http enabled:
# a2enmod proxy proxy_http

Caddy

Caddyfile
example.com {
    handle_path /bm/s {
        reverse_proxy https://bettermeter.com/api/s {
            header_up Host bettermeter.com
        }
    }
    handle_path /bm/e {
        reverse_proxy https://bettermeter.com/api/e {
            header_up Host bettermeter.com
        }
    }
    handle_path /bm/p {
        reverse_proxy https://bettermeter.com/api/p {
            header_up Host bettermeter.com
        }
    }
}

Cloudflare Workers

worker.js
export default {
  async fetch(request) {
    const url = new URL(request.url);
    const map = {
      "/bm/s": "https://bettermeter.com/api/s",
      "/bm/e": "https://bettermeter.com/api/e",
      "/bm/p": "https://bettermeter.com/api/p",
    };
    const target = map[url.pathname];
    if (!target) return fetch(request);

    const dest = new URL(target);
    dest.search = url.search;
    return fetch(dest.toString(), {
      method: request.method,
      headers: { ...Object.fromEntries(request.headers), Host: "bettermeter.com" },
      body: request.method !== "GET" ? request.body : undefined,
    });
  },
};

Vercel (without Next.js)

vercel.json
{
  "rewrites": [
    { "source": "/bm/s", "destination": "https://bettermeter.com/api/s" },
    { "source": "/bm/e", "destination": "https://bettermeter.com/api/e" },
    { "source": "/bm/p", "destination": "https://bettermeter.com/api/p" }
  ]
}

Netlify

netlify.toml
[[redirects]]
  from = "/bm/s"
  to = "https://bettermeter.com/api/s"
  status = 200
  force = true

[[redirects]]
  from = "/bm/e"
  to = "https://bettermeter.com/api/e"
  status = 200
  force = true

[[redirects]]
  from = "/bm/p"
  to = "https://bettermeter.com/api/p"
  status = 200
  force = true

WordPress

Agregue estas reglas de reescritura al functions.php de su tema o a un plugin personalizado:

functions.php
add_action('init', function() {
    add_rewrite_rule('^bm/s$', 'index.php?bm_proxy=s', 'top');
    add_rewrite_rule('^bm/e$', 'index.php?bm_proxy=e', 'top');
    add_rewrite_rule('^bm/p$', 'index.php?bm_proxy=p', 'top');
});

add_filter('query_vars', function($vars) {
    $vars[] = 'bm_proxy';
    return $vars;
});

add_action('template_redirect', function() {
    $proxy = get_query_var('bm_proxy');
    if (!$proxy) return;

    $map = [
        's' => 'https://bettermeter.com/api/s',
        'e' => 'https://bettermeter.com/api/e',
        'p' => 'https://bettermeter.com/api/p',
    ];
    if (!isset($map[$proxy])) return;

    $url = $map[$proxy];
    if ($_SERVER['QUERY_STRING']) $url .= '?' . $_SERVER['QUERY_STRING'];

    $args = ['method' => $_SERVER['REQUEST_METHOD'], 'timeout' => 10];
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $args['body'] = file_get_contents('php://input');
        $args['headers'] = ['Content-Type' => $_SERVER['CONTENT_TYPE'] ?? 'application/json'];
    }

    $response = wp_remote_request($url, $args);
    if (is_wp_error($response)) { status_header(502); exit; }

    status_header(wp_remote_retrieve_response_code($response));
    foreach (wp_remote_retrieve_headers($response) as $k => $v) {
        if (in_array(strtolower($k), ['content-type', 'cache-control'])) header("$k: $v");
    }
    echo wp_remote_retrieve_body($response);
    exit;
});

// After adding, flush rewrite rules: Settings → Permalinks → Save

Django

urls.py + views.py
# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("bm/<slug:endpoint>", views.bm_proxy),
]

# views.py
import requests
from django.http import HttpResponse

PROXY_MAP = {
    "s": "https://bettermeter.com/api/s",
    "e": "https://bettermeter.com/api/e",
    "p": "https://bettermeter.com/api/p",
}

def bm_proxy(request, endpoint):
    target = PROXY_MAP.get(endpoint)
    if not target:
        return HttpResponse("Not found", status=404)
    url = target + ("?" + request.META["QUERY_STRING"] if request.META.get("QUERY_STRING") else "")
    resp = requests.request(
        method=request.method, url=url,
        headers={"Host": "bettermeter.com", "Content-Type": request.content_type},
        data=request.body if request.method == "POST" else None,
        timeout=10,
    )
    return HttpResponse(resp.content, status=resp.status_code, content_type=resp.headers.get("Content-Type"))

Rails

config/routes.rb + controller
# config/routes.rb
match "/bm/:endpoint", to: "bm_proxy#proxy", via: [:get, :post], constraints: { endpoint: /[sep]/ }

# app/controllers/bm_proxy_controller.rb
class BmProxyController < ApplicationController
  skip_before_action :verify_authenticity_token

  PROXY_MAP = { "s" => "/api/s", "e" => "/api/e", "p" => "/api/p" }.freeze

  def proxy
    path = PROXY_MAP[params[:endpoint]]
    return head(:not_found) unless path

    uri = URI("https://bettermeter.com#{path}")
    uri.query = request.query_string.presence

    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    req = (request.post? ? Net::HTTP::Post : Net::HTTP::Get).new(uri)
    req["Host"] = "bettermeter.com"
    if request.post?
      req.body = request.body.read
      req["Content-Type"] = request.content_type
    end

    resp = http.request(req)
    render body: resp.body, status: resp.code.to_i, content_type: resp["Content-Type"]
  end
end

Verificación

Después de configurar el proxy, verifique que funcione:

Test
# Should return the tracker JavaScript
curl -s https://yourdomain.com/bm/s | head -5

# Should return 202 (event accepted)
curl -s -o /dev/null -w "%{http_code}" -X POST https://yourdomain.com/bm/e \
  -H "Content-Type: application/json" \
  -d '{"site_id":"yourdomain.com","event_name":"test"}'
12

Modelo de datos

Las cuatro fuentes de eventos comparten una sola tabla Event. La columna eventSource las distingue. Los metadatos específicos de cada fuente están en la columna JSON properties.

Mapeo de campos

Campo
Web
CLI
MCP
API
eventName
pageview
cli.command
mcp.tool
api.request
eventSource
web
cli
mcp
api
pathname
Ruta URL
/command
/tool_name
/endpoint
referrer
document.referrer
(empty)
mcp://client
(empty)
properties
propiedades personalizadas
command, flags, duration, exit_code, os
tool, client, duration, success
method, endpoint, status_code, duration
13

Privacidad

BetterMeter está diseñado desde cero para respetar la privacidad del usuario. No se necesitan banners de consentimiento. Cumple con GDPR por defecto.

01

Sin cookies

Cero cookies, cero almacenamiento local, cero huellas digitales. El script de rastreo no tiene estado.

02

Identificadores hasheados

Las direcciones IP se hashean con SHA-256 con la fecha actual incluida en la entrada del hash. La IP sin procesar nunca se almacena. Los hashes de visitantes diarios rotan cada 24 horas. Un ID de visitante estable (hasheado sin fecha) permite perfiles de visitantes dentro de un sitio pero no puede revertirse para revelar la IP original.

03

Sin recopilación de datos personales

El SDK rastrea nombres de comandos, nombres de herramientas y patrones de endpoints -- nunca argumentos, rutas de archivos, cuerpos de solicitud ni datos personales.

04

Enviar y olvidar

Las llamadas de analíticas son asíncronas y nunca lanzan excepciones. Un fallo de red descarta silenciosamente el evento. Las analíticas nunca deben interrumpir la aplicación principal.

05

Desactivación

Configure disabled: true en la configuración del SDK o BETTERMETER_DISABLED=1 como variable de entorno.

06

Pipeline abierto

Cada evento pasa por la misma función processEvent(). Puede auditar exactamente qué se recopila y almacena.

BetterMeter Analytics -- Creado para la nueva internet