Saltar al contenido principal

Dashboard de analítica

El panel de administración expone consultas de analítica granular para estadísticas de plataforma, crecimiento de usuarios, engagement, ingresos, contenido y salud del sistema. No existe una única query compuesta de "dashboard" — cada métrica es su propio campo de GraphQL. Todas estas (excepto adminGetContentStats, adminGetSystemHealth, y adminExportAnalytics) ya están conectadas a la página /dashboard.

Una nota sobre las correcciones de esta sesión: cada método a continuación excepto adminGetPlatformStats y adminGetUserStats (ambos corregidos en un pase anterior) devolvía una forma de objeto que no coincidía en absoluto con su tipo de GraphQL - campos no-nulos faltantes, campos con nombres distintos (canceled_at vs el cancelledAt del modelo, growth_rate vs growthRate), o llamaba a métodos de data-access-service que no existen (findByDateRange, getTopHashtags, findTopByFollowers, getUserPostCounts, y otros - ninguno de estos era un método real en ningún lugar del código). Cada uno fue reescrito contra los modelos reales de Sequelize y el esquema real de GraphQL. En el camino se agregaron algunos métodos nuevos de data-access-service (findByDateRange/countMediaByDateRange de post.access-service.js, countByDateRange de message.access-service.js, findRecent de user-moderation-log.access-service.js) ya que los métodos que el código anterior asumía que existían simplemente no existían.

Checklist de implementación

  • Estadísticas generales de la plataforma (adminGetPlatformStats - conectado a la página del dashboard)
  • Crecimiento de usuarios a lo largo del tiempo (adminGetUserGrowth - conectado a la página del dashboard. retentionRate ahora prefiere datos reales de cohortes de registro, corregido en esta sesión: user_cohort_snapshots (escrita a diario por services/analytics-snapshot.service.js#runCohortSnapshot, ver más abajo) entrega una lectura real de retención Day-N - sum(active_count) / sum(cohort_size) entre las cohortes cuyo día de registro cae dentro de un bucket determinado - en lugar de usar siempre el proxy anterior. Bootstrap con degradación elegante: un bucket sin instantánea de cohorte que lo cubra todavía (instalación nueva, o el cron aún no lleva suficiente tiempo corriendo para cubrirlo - aproximadamente los primeros ~30 días tras este lanzamiento) recae en el proxy en vivo original sin cambios: la fracción de usuarios que existían antes del bucket y que volvieron a estar activos durante este (mediante createdAt/lastActiveAt), que sigue sin ser una retención real por cohortes y es null únicamente cuando no hay una base de usuarios preexistente contra la cual medir.)
  • Estadísticas de engagement (adminGetEngagementStats - corregido en esta sesión, conectado a la página del dashboard)
  • Estadísticas de contenido (adminGetContentStats - resolver en admin/admin-dashboard.resolver.js protegido por el permiso VIEW_ANALYTICS → adminDashboardManager.getContentStats, que devuelve conteos reales de posts/medios/hashtags que coinciden con el tipo ContentStats)
  • Estadísticas de creación de contenido por período (adminGetContentCreationStats - conectado a la página del dashboard. Corregido en esta sesión: period/startDate/endDate ahora sí se respetan - totalPosts/totalMedia/totalHashtags/trendingHashtags quedan delimitados a la ventana [startDate, endDate] resultante (las fechas explícitas tienen prioridad, si no una ventana retroactiva dimensionada según period), vía postAccessService.findByDateRange() y countMediaByDateRange(), ambos añadidos en esta sesión ya que ninguno existía antes. postsToday/mediaToday/postsWeek mantienen intencionalmente su significado literal de "hoy"/"últimos 7 días desde ahora" sin importar la ventana solicitada, coincidiendo con lo que esos nombres de campo significan según el esquema - no forman parte de la delimitación por período. Antes estos argumentos se aceptaban pero se ignoraban silenciosamente, y la consulta siempre devolvía la misma instantánea de todo-el-tiempo que adminGetContentStats, sin importar lo que se pidiera.)
  • Estadísticas de ingresos (adminGetRevenueStats - corregido en esta sesión, conectado a la página del dashboard. Los ingresos son solo filas completadas de PaymentTransaction, coincidiendo con la definición de adminGetPlatformStats - las compras de monedas y las propinas no están incluidas.)
  • Estadísticas de la cola de moderación (adminGetModerationQueueStats - corregido en esta sesión, conectado a la página del dashboard. flaggedContent/reportedContent reutilizan ambos el mismo conteo de reportes pendientes (ver la nota sobre contenido marcado en Moderación de contenido). autoFlagged ahora refleja una señal real, también corregido en esta sesión: antes devolvía un 0 fijo detrás de un comentario obsoleto que afirmaba que no existía ningún sistema de auto-moderación - eso ya no es cierto (ver la sección Reglas de auto-moderación), así que ahora cuenta las filas de content_moderation_logs escritas con action: 'flag' y moderator_id: 'system' - el rastro que deja services/auto-moderation.service.js cada vez que una regla coincide y llama a contentModerationManager.flagContent() con el actor 'system'. Igual que el resto de los conteos de este método, es una instantánea de la cola de todo-el-tiempo, no delimitada a una ventana temporal.)
  • Mejor contenido por métrica (adminGetTopContent - corregido en esta sesión, conectado a la página del dashboard. Su argumento contentType: String ahora también respeta COMMENT, no solo posts: antes el método nunca desestructuraba contentType en absoluto, así que toda llamada devolvía posts sin importar lo que se pidiera. PostComment solo registra likesCount/repliesCount (no existen contadores de vistas/compartidos para comentarios), así que LIKES se mapea a likesCount, COMMENTS (más respondidos) se mapea a repliesCount, y cualquier otro valor recae en likesCount. Todavía no hay una ruta de consulta equivalente aquí para otros tipos de contenido (blasts, tales, articles, etc).)
  • Mejores usuarios por métrica (adminGetTopUsers - corregido en esta sesión, conectado a la página del dashboard. Su rama ENGAGEMENT tenía un segundo bug encontrado en una revisión posterior: el literal SQL crudo referenciaba nombres de columna camelCase entre comillas ("likesCount", etc.) que no existen como columnas de base de datos - Post usa underscored: true con mapeos explícitos estilo field: 'likes_count', así que el literal necesitaba los nombres reales en snake_case. Actualmente no es alcanzable desde la página del dashboard, que solo solicita la métrica FOLLOWERS.)
  • Estadísticas de suscripciones (adminGetSubscriptionStats - corregido en esta sesión, conectado a la página del dashboard)
  • Estadísticas de hashtags/tendencias (adminGetHashtagStats - conectado a la página del dashboard. growthRate ahora es real, corregido en esta sesión: compara el usageCount actual de un hashtag contra la fila más antigua disponible en hashtag_usage_snapshots (escrita a diario por services/analytics-snapshot.service.js#runHashtagSnapshot, ver más abajo), una línea base que naturalmente se acerca a ~7+ días de antigüedad a medida que se acumulan instantáneas diarias. trendingScore ahora sí usa ese crecimiento: usageCount * (1 + growthRate / 100), con un piso de 0 para que un hashtag en declive no pueda quedar negativo. Bootstrap con degradación elegante: un hashtag sin instantánea todavía mantiene growthRate: 0 tal como antes, lo cual también mantiene trendingScore idéntico al respaldo anterior basado solo en el conteo de uso - el comportamiento no cambia hasta que se acumule historial.)
  • Relleno histórico de instantáneas (services/analytics-snapshot.service.js - nuevo en esta sesión, no es en sí una query de GraphQL. Dos tareas cron diarias - uso de hashtags a las 02:00, retención por cohortes de registro a las 02:30, ambas hora del servidor - escriben en dos tablas nuevas del almacén de analítica de las cuales leen las correcciones de growthRate/retentionRate anteriores: hashtag_usage_snapshots (una fila por hashtag por día) y user_cohort_snapshots (una fila por cohorte de día de registro por día de medición; ver services/analytics/migrations/006_create_hashtag_usage_snapshots_table.sql y 007_create_user_cohort_snapshots_table.sql). Cada tarea es best-effort por elemento - que un hashtag o un día de cohorte falle al escribirse no aborta el resto de la corrida - y ambas se degradan sin error cuando el sumidero de analítica no está conectado.)
  • Registro de actividad de administradores (adminGetActivityLog - corregido en esta sesión, conectado a la página del dashboard. Combina dos fuentes: la tabla user_moderation_logs y la tabla content_moderation_logs (a través de content-moderation-log.access-service.js, por la cual content-moderation.manager.js#logContentModerationAction ahora persiste). Todavía no existe una tabla dedicada de registro de actividad de administradores - esto es una combinación de los dos registros de moderación existentes, ordenados por fecha. Las entradas cuyo actor no se puede resolver a un AdminUser real se descartan, ya que el campo admin del esquema es no-nulo.)
  • Métricas de salud del sistema (adminGetSystemHealth) — devuelve la forma plana SystemHealthMetrics con valores REALES: tiempo de ida y vuelta a la DB + tiempo de respuesta promedio del plugin de Apollo (services/metrics.service), estado de Redis/almacenamiento, % de memoria del SO, % de carga de CPU, disco mediante statfs, conexiones activas del pool de DB, y tasa de error. El plugin de métricas está conectado en api/server.js. Conectado a las pastillas de estado y las tarjetas de estadísticas de la página /system.
  • Exportación de analítica (adminExportAnalytics) — construye filas reales a partir de los métodos de analítica (usuarios/contenido/engagement/ingresos/moderación), las serializa a CSV o JSON, las sube a S3 (o escribe un archivo local como respaldo), y devuelve un downloadUrl + fileSize reales. Conectado al formulario de exportación de la página /system (selector de tipo de reporte + formato).

La página /system/environment (qué integraciones están configuradas, pruebas de conectividad por grupo) es una funcionalidad distinta — ver Estado del entorno.

Referencia técnica

Ver Panel de administración → Consultas de analítica granular y Exportación de analítica para la API completa de GraphQL.