Métricas del dashboard y seguimiento de visitantes
Qué cuenta cada gráfica de /admin, de dónde salen sus números, cómo agregar una y cómo el seguimiento de visitantes alimenta a Facebook.
El dashboard de administración dibuja una gráfica a la vez sobre el rango de fechas que elijas. Esta guía lista cada métrica que puede mostrar y la tabla que lee cada una, explica el trabajo diario detrás del conteo de usuarios activos, muestra cómo agregar una métrica propia y cubre el seguimiento opcional de visitantes que registra sesiones y eventos y los reenvía a la Conversions API de Facebook.
El dashboard
/admin es el componente Livewire App\Livewire\Admin\Dashboard. Tiene tres controles: Desde, Hasta y Métrica. El rango parte de los últimos 30 días, terminando hoy; un rango escrito al revés se intercambia, y una fecha que no se puede interpretar vuelve al valor por omisión. La métrica se guarda en la URL, así que /admin?metric=daily_revenue abre directo en esa gráfica y se puede guardar como marcador; la métrica por omisión, new_users, se deja fuera de la URL. Una métrica que la URL nombra pero el dashboard no ofrece vuelve a new_users.
Las barras o puntos de la gráfica se agrupan según la longitud del rango:
| Rango | Agrupación | Etiqueta |
|---|---|---|
| Menos de 50 días | Una por día | 2026-09-03 |
| Menos de 6 meses | Una por semana, empezando en lunes | La fecha del lunes |
| Menos de 3 años | Una por mes | 2026-09 |
| Más largo | Una por año | 2026 |
Las métricas
| Clave | Etiqueta | Gráfica | Qué cuenta | Se muestra cuando |
|---|---|---|---|---|
new_users |
Nuevos Usuarios Registrados | Columnas | Usuarios por created_at. |
Siempre |
total_users |
Usuarios Acumulados Totales | Línea | Usuarios existentes al final de cada periodo, partiendo del conteo anterior a Desde. | Siempre |
activity_log |
Eventos del registro de actividad | Columnas | Filas de activity_log por created_at. |
Siempre |
logged_users |
Usuarios activos por día | Columnas | Las filas daily_logged_users de la tabla stats, sumadas por periodo. Mira más abajo. |
Siempre |
users_by_country |
Usuarios por país | Una columna por país | Cada usuario agrupado por country_code, en mayúsculas; las cuentas sin país van en Desconocido. Ignora el rango de fechas. |
Siempre |
tracking_events |
Seguimiento de Eventos | Líneas: todos los eventos, más una por tipo de evento | Filas de tracking_events por created_at. |
FEATURE_TRACKING_ENABLED=true |
referral_subscriptions |
Suscripciones por referencias | Dos series de columnas | Referidos por registered_at, y recompensas de referido de tipo subscription por created_at. |
FEATURE_REFERRALS_ENABLED=true |
daily_revenue |
Ingresos diarios (moneda) | Columnas | Eventos de webhook invoice.paid exitosos de Stripe que pertenecen a una suscripción y pagaron más de cero, en la moneda principal. |
El paquete de facturación está instalado |
new_subscriptions |
Suscripciones creadas | Líneas: todas las suscripciones pagadas, más una por plan | Suscripciones cuyo precio es mayor que cero, por created_at. |
Los planes están activos |
total_subscriptions |
Suscripciones Totales Acumuladas | Líneas acumuladas, mismas series | Las mismas suscripciones, acumuladas. | Los planes están activos |
"Los planes están activos" significa FEATURE_PLANS_ENABLED=true, un STRIPE_SECRET y al menos un plan en la base de datos. Ingresos diarios convierte cada factura de su moneda a pricing.primary_currency con el tipo de cambio guardado para ese día; una factura en una moneda sin tipo de cambio para su día cuenta como cero. Las filas de webhook que lee están descritas en /help/audit-trails-and-webhook-events, y los tipos de cambio en /help/currencies-intervals-and-exchange-rates.
Las gráficas las construye el trait App\Livewire\Traits\HasCharts sobre asantibanez/livewire-charts. Ofrece una gráfica de columnas, una de columnas con una columna por categoría, una de columnas con varias series, una de línea acumulada, una de varias líneas y una de varias líneas acumuladas, todas recibiendo el rango y el tamaño de agrupación y llenando con cero los periodos en los que nadie escribió. El paquete de facturación reutiliza el mismo trait, así que sus gráficas se ven como las del kit.
Usuarios activos por día
Es la única métrica que no se calcula desde una columna de fecha al cargar la página. La producen tres piezas:
App\Http\Middleware\UpdateLastLoggedAtcorre en cada petición web. En unGETde página completa de un usuario con sesión (no AJAX, no JSON, no una actualización de Livewire) llama aUser::markLoggedIn(), que escribeusers.last_logged_atuna vez al día: sólo cuando la columna está vacía o guarda otra fecha. La escritura es silenciosa, así que no deja fila en el registro de actividad.php artisan stats:compute-daily(App\Console\Commands\ComputeDailyStats) cuenta los usuarios cuyolast_logged_atcae en ayer e inserta o actualiza una fila enstats: clavedaily_logged_users,dateayer,valueel conteo. La tabla tiene un índice único sobre clave y fecha, así que correrlo dos veces para el mismo día actualiza la fila.routes/console.phpprograma ese comando condailyAt('00:00'), en la zona horaria de la aplicación. Sólo corre cuando corre el scheduler, así que el servidor necesitaphp artisan schedule:runcada minuto en su cron. Mira /help/queues-and-scheduled-work.
Un día que el scheduler se saltó aparece en cero, y el comando siempre cuenta ayer: no se le puede apuntar a una fecha anterior. La migración 2026_05_05_000001_backfill_daily_logged_users_stats llena el historial una vez, desde el registro de actividad: por cada día, el número de usuarios distintos que causaron una fila de actividad, saltando los días que ya tienen estadística.
Agrega una métrica propia
Todo vive en app/Livewire/Admin/Dashboard.php. El ejemplo cuenta facturas abiertas; cámbialo por tu modelo.
- Ofrécela en el selector:
#[Computed]
public function metricOptions()
{
$options = [
'new_users' => __('New Registered Users'),
// ...
'open_invoices' => __('Open Invoices'),
];
- Dirígela a un constructor en
chartModel():
return match ($this->metric) {
'new_users' => $this->newUsersChart($from, $to, $granularity),
// ...
'open_invoices' => $this->openInvoicesChart($from, $to, $granularity),
default => $this->newUsersChart($from, $to, $granularity),
};
- Escribe el constructor. Agrupa las filas con
chartBucketKey()para que caigan en los mismos periodos que dibuja la gráfica, y entrega la colección a uno de los constructores del trait:
private function openInvoicesChart(Carbon $from, Carbon $to, string $granularity)
{
$data = Invoice::where('status', 'open')
->whereBetween('created_at', [$from, $to])
->get(['created_at'])
->groupBy(function ($invoice) use ($granularity) {
return $this->chartBucketKey($invoice->created_at, $granularity);
})
->map(function ($invoices) {
return $invoices->count();
});
return $this->buildColumnChart($from, $to, $data, __('Open Invoices'), $granularity);
}
- Si es una gráfica de línea, agrega su clave a
isLineChart(); la vista elige el componente de línea o el de columnas a partir de eso.
La etiqueta pasa por __(), así que agrégala a lang/en.json y lang/es.json. Mira /help/languages-and-translations. Envuelve la opción en una condición cuando la métrica pertenezca a una bandera, como lo hacen las métricas de seguimiento y de referidos.
Seguimiento de visitantes
El seguimiento viene apagado. Es una sola bandera:
| Variable de entorno | Valor por omisión | Qué se activa |
|---|---|---|
FEATURE_TRACKING_ENABLED |
false |
Se abren sesiones para los visitantes, trackingEvent() registra eventos, aparecen las tres pantallas de Tracking en el panel de administración y Seguimiento de Eventos aparece en el dashboard. |
La variable no está en .env.example; agrega la línea tú mismo. Con la bandera apagada, el middleware deja pasar las peticiones, el helper regresa sin hacer nada y las políticas de seguimiento niegan todas las pantallas.
Sesiones
App\Http\Middleware\TrackingMiddleware corre en cada petición web. En un GET de página completa (no AJAX, no JSON, no una actualización de Livewire) resuelve una sesión de seguimiento cuando el visitante todavía no tiene una, o de nuevo cuando la URL trae fbclid o utm_campaign, para que un regreso por un enlace de campaña nuevo quede atribuido. TrackingService busca la sesión en este orden: el id guardado en la sesión de Laravel bajo new_tracking_session_id, la cookie del mismo nombre, y luego cualquier sesión con la misma dirección IP; si las tres fallan, crea una. El id se escribe en la sesión, y en la cookie por 365 días cuando la sesión es nueva.
Una fila de sesión (tracking_sessions) guarda ip_address, user_agent, fbclid, fbp (la cookie _fbp), fbc (la cookie _fbc, o construida desde fbclid), referrer_url, current_url, campaign (utm_campaign) y user_id. Los campos de atribución — fbp, fbc, fbclid, referrer_url, campaign — se escriben la primera vez que llegan y nunca se sobrescriben. user_id se llena cuando se ve a un visitante con sesión iniciada y la sesión no tiene uno.
Eventos
Dispara un evento desde cualquier lugar de la aplicación con el helper global:
trackingEvent('Checkout Started', ['plan' => 'pro', 'amount' => 29]);
El helper no hace nada cuando la bandera está apagada. Si no, toma la sesión actual (el id en la sesión de Laravel, o la primera sesión del usuario con sesión iniciada) y registra el evento en ella; sin sesión — un job en cola, un comando — no se registra nada. Una clave user_id en el payload asocia a ese usuario con la sesión. El nombre del evento es un TrackingEventType, creado en el primer uso con el nombre como título. Una sesión registra cada tipo de evento una sola vez: un segundo Register Started en la misma sesión se ignora.
El kit dispara cuatro eventos:
| Evento | Cuándo | Payload |
|---|---|---|
Register Started |
Se abre la página de registro. | — |
Register Completed |
Se crea un usuario. | user_id |
Subscribed |
Un usuario toma un plan o lo cambia. | user_id, variation_id |
Subscription Paid |
Stripe confirma una factura de suscripción. Se registra en la sesión del usuario, creando una cuando el usuario no tiene ninguna. | invoice_id, subscription_id, amount, currency |
Reenvío a Facebook
App\Observers\TrackingEventObserver envía cada evento nuevo a la Conversions API de Facebook, dentro de la misma petición que lo registró, a través de TrackingService::sendToFacebook():
| Variable de entorno | Clave de configuración | Qué hace |
|---|---|---|
FACEBOOK_ACCESS_TOKEN |
services.facebook.access_token |
El token de usuario del sistema de la Conversions API. |
FACEBOOK_PIXEL_ID |
services.facebook.pixel_id |
El píxel al que se publican los eventos, en graph.facebook.com/v18.0/{pixel}/events. |
FACEBOOK_TEST_EVENT_CODE |
services.facebook.test_event_code |
Opcional. Cuando está definido, cada envío lleva test_event_code, así que los eventos aparecen en la pestaña Test Events del Events Manager en lugar de en producción. |
Sin el token y el id del píxel, los eventos se guardan y no se envía nada. Cada envío lleva el nombre del tipo de evento como event_name, la hora, las URL actual y de referencia de la sesión, action_source website, el payload como custom_data, y user_data con fbc, fbp, IP y user agent, más el SHA-256 del correo y el nombre del usuario en minúsculas cuando la sesión tiene usuario. Un envío fallido se escribe en el log como advertencia y nunca rompe la petición.
Las pantallas de administración
Tres recursos bajo Tracking en la barra lateral, todos detrás de la bandera:
| URL | Qué muestra |
|---|---|
/admin/tracking-event-types |
Los nombres de evento, con un título y una descripción que puedes editar. El dashboard y la lista de eventos muestran el título, así que renombra aquí sin tocar el código que dispara el evento. Un tipo creado sin nombre toma su título como nombre. |
/admin/tracking-sessions |
Sólo índice y detalle: usuario, número de eventos, IP, campaña, identificadores de Facebook, URL, user agent y los eventos de la sesión. |
/admin/tracking-events |
Cada evento con su tipo, sesión, usuario y payload. Filtra por tipo, por rango de fechas y por id de sesión. |
Leerlas requiere los permisos retrieve tracking_event_type, retrieve tracking_session y retrieve tracking_event. Mira /help/roles-and-permissions.