Observabilidad
El panel usa dos herramientas. Sentry recoge errores, logs y trazas del frontend. PostHog registra eventos de producto y graba las sesiones. Las dos están enlazadas: cada evento de Sentry se puede relacionar con la sesión de PostHog en la que ocurrió. Esta página explica cuándo está activa cada una, qué se reporta y qué no, cómo se identifica al usuario y cómo pasan los errores de red porapiClient y React Query.
Sentry
Cuándo está activo
Además del entorno, hace falta
VITE_SENTRY_DSN. Sin DSN, initSentry() sale sin hacer nada y todas las llamadas a Sentry son no-ops.
El entorno
prod se reporta como production a propósito. Las reglas de alerta del proyecto de Sentry del panel filtran por production. Con prod, no se disparaba ninguna.Arranque
src/main.tsx llama a initSentry() y a initAnalytics() antes de montar React. La configuración de src/config/sentry/index.ts incluye:
release: __APP_RELEASE__, con la formapanel-front@<sha>. Ver Entornos y despliegue.browserTracingIntegration()para las trazas de navegación y de peticiones.tracePropagationTargets: [VITE_API_BASE_URL]. El panel envía la cabecerasentry-tracea la API para enlazar trazas con el backend.enableLogsmásconsoleLoggingIntegrationcon niveleswarnyerror. Cadaconsole.warnyconsole.errorllega a Sentry Logs. ESLint prohíbe el resto deconsole.*con la reglano-console.sendDefaultPii: false.
Sentry.wrapCreateBrowserRouterV7(createBrowserRouter). Así Sentry nombra las transacciones por ruta.
PortalRoot monta useSentryFeatureTag, que pone la etiqueta feature con el primer segmento de la ruta: homes, bookings, users… En /, la etiqueta es keys, porque ahí vive el dashboard de llaves. Sirve para filtrar incidencias y alertas por sección.
Release y source maps
En builds destaging y prod, vite.config.ts activa @sentry/vite-plugin:
- Crea la release
panel-front@<sha>y sube los source maps conSENTRY_ORG,SENTRY_PROJECTySENTRY_AUTH_TOKEN. - Genera los mapas en modo
hidden, sin referencia en el JS publicado. - Borra los
.mapdel build después de subirlos.
Qué se reporta y qué no
Los códigos silenciados son respuestas esperadas de la API: 41 (token inválido, ya cubierto por el flujo de sesión), 43 (sin permisos), 44 (no encontrado) y 49 (precondición de negocio). El 40 (petición mal formada) sí se reporta, porque suele ser un bug del panel.
Network Error está en la lista de ignorados de beforeSend. Aun así, los fallos de red de la API llegan a Sentry. captureNetworkError los etiqueta con network_error: api, y beforeSend deja pasar cualquier evento con esa etiqueta. Solo se descarta el ruido de red que no viene de apiClient.Etiquetas y agrupación
Los fallos de red se agrupan con el fingerprint
["api-network-error", MÉTODO, ruta]. La ruta se normaliza: se quita el query string y los UUID y segmentos numéricos pasan a :id. Un endpoint caído genera una incidencia, no una por registro.
Sin reportes duplicados
Un mismo error puede pasar porapiClient, por React Query y por el error boundary. Para que llegue una sola vez, src/config/sentry/utils.ts guarda en un WeakSet los errores ya tratados:
captureExceptionycaptureNetworkErrormarcan el error al reportarlo.apiClienttambién marca los errores que decide no reportar.QueryCache,MutationCacheyRouteErrorBoundarycompruebanisErrorProcessed()antes de reportar. Si el error ya está tratado, el boundary muestra el ID conSentry.lastEventId().
Datos sensibles
beforeSend filtra extra y las cabeceras de la petición. beforeBreadcrumb filtra los datos de los breadcrumbs xhr y fetch. En los tres casos, cualquier clave que contenga uno de estos textos pasa a valer [FILTERED]: password, token, access_token, refresh_token, authorization, api_key, apikey, secret o credential. El filtro es recursivo.
Cada petición de apiClient deja además un breadcrumb http con el método y la URL.
Identidad del usuario
useAuth asocia el usuario cuando hay token. En Sentry llama a setSentryUser({ id, email }), donde id es el sub del token. Al cerrar sesión, o si el token desaparece, llama a clearSentryUser(). PostHog sigue el mismo ciclo, descrito más abajo.
Pantalla de error
Los dos boundaries muestranErrorFallback (src/components/ErrorBoundary/index.tsx):
- Un aviso de que el error se ha reportado automáticamente.
- El enlace Comprueba si es un problema conocido, que abre la página de estado de VIVLA.
- El ID del evento de Sentry con el botón Copiar. Con ese ID, cualquiera de tech encuentra la incidencia exacta.
- El botón Volver a intentar. En el boundary del router recarga la página. En el raíz, resetea el boundary.
RouteErrorBoundary reporta dentro de un useEffect, no en el render. Así no se repite el reporte en cada re-render.
Errores de red
Todas las llamadas a la API pasan porapiClient (src/lib/apiClient.ts), una instancia de Axios con baseURL de VITE_API_BASE_URL y timeout de 30 segundos. React Query (src/config/queryClient.ts) hace de segunda red.
code propio: 20 es éxito. apiClient convierte cualquier otro código en un ApiError con code, msg y data. Así las mutaciones y queries reciben un error normal.
El QueryClient usa staleTime de 60 segundos y refetchOnWindowFocus: false.
Última petición fallida
src/lib/lastFailedRequest.ts guarda en memoria la última petición de apiClient que falló:
Solo se guarda la última, en una variable de módulo: una recarga la borra. El diálogo de reportes la adjunta al contexto y el hilo de Slack la muestra como Última petición fallida.
El envío de un reporte usa
Axios directamente, no apiClient. Si falla, no pisa la última petición fallida. Sí llega a Sentry por MutationCache.PostHog
Cuándo está activo
Además hace falta
VITE_PUBLIC_POSTHOG_TOKEN. El host sale de VITE_PUBLIC_POSTHOG_HOST. Si PostHog no está activo, initAnalytics() instala un proveedor vacío (providers/noop.ts). Todas las llamadas funcionan sin hacer nada y getSessionReplayUrl() devuelve null.
El resto del código no importa posthog-js. Todo pasa por la abstracción de src/lib/analytics/: trackEvent, trackPageView, setAnalyticsUser, clearAnalyticsUser y getSessionReplayUrl.
Configuración
providers/posthog.ts inicializa PostHog así:
capture_pageview: false. Las páginas vistas se envían a mano.capture_pageleave: true.persistence: "localStorage+cookie".startSessionRecording()al arrancar: la grabación está siempre activa en staging y producción.- Propiedades globales en todos los eventos:
app: "vivla-panel"yenvironment. posthog.sentryIntegrationse añade a Sentry consendExceptionsToPostHog: false. Enlaza los eventos de Sentry con la sesión de PostHog sin duplicar las excepciones en PostHog.
Páginas vistas
PortalRoot monta useTrackPageView. En cada cambio de pathname o search, envía un $pageview con pageName (la ruta), search y $current_url. Solo se registran páginas del área autenticada.
Identificación
Cuando hay token,useAuth llama a setAnalyticsUser({ id, email }), que hace posthog.identify(id, { email }). El id es el sub del token de Auth0. Al cerrar sesión, clearAnalyticsUser() hace posthog.reset().
El proveedor admite también role, pero ahora mismo no se envía.
Grabaciones de sesión
getSessionReplayUrl() devuelve el enlace a la grabación actual, posicionado 30 segundos antes del momento de la llamada. Lo usa el diálogo de reportes: lo enseña como Grabación y lo adjunta al reporte.
Catálogo de eventos
Los eventos están tipados ensrc/lib/analytics/events.ts (AnalyticsEventMap). trackEvent solo acepta nombres de ese mapa, con sus propiedades exactas.
Reservas
Reservas
Usuarios
Usuarios
Los eventos de usuarios conservan el prefijo
owner_* para no romper los dashboards de PostHog.Casas
Casas
Llaves
Llaves
Documento de check-in
Documento de check-in
Calendarios
Calendarios
Reportes y sesión
Reportes y sesión
auth_login, auth_logout, booking_create_started y booking_create_step_viewed están en el mapa, pero ningún componente los emite. Si un dashboard depende de ellos, estará vacío.