Analítica de creador — Referencia técnica
← Volver a Analítica de creador
El panel es en su mayoría una composición de analíticas que ya existían en el backend (resumen de ganancias, ganancias por fuente, ganancias de posts exclusivos, conteo de suscriptores), más varias queries de backend genuinamente nuevas agregadas a lo largo de distintas pasadas: una serie temporal diaria de ganancias (Fase D.4 / roadmap 4.12), retención/abandono de suscriptores mensual, y un estado de ganancias anual por año calendario. La página que reemplaza era un stub honesto que declaraba que la analítica de creador "no tenía soporte de backend"; eso ya estaba desactualizado.
Dónde vive esto
Backend
apps/backend/graphql/types/coin-transaction.type.js— nuevo tipoEarningsTimePointy querymyEarningsTimeSeries(days: Int): [EarningsTimePoint!]!, junto almyEarningsBySourcepreexistente.apps/backend/graphql/resolvers/coin-transaction.resolver.js— resolvermyEarningsTimeSeries(autenticado).apps/backend/managers/coin-managers/coin-transaction.manager.js—getEarningsTimeSeries(userId, days=30): limita la ventana a ≤365 días y rellena con ceros cada día del rango para que el frontend grafique una serie continua en vez de solo los días con ventas.apps/backend/data-access-services/coin/coin-transaction.access-service.js—getEarningsTimeSeries(userId, since): un agregado de PostgresGROUP BY DATE(created_at)SUM(amount), acotado a tipos de transacción de ganancia genuinos (tip_received,post_sale,subscription_revenue,product_sale, más elreward/message_purchasede mensajes de pago) en lugar de unamount > 0a secas — así las compras de monedas del propio creador no inflan la tendencia, igual que comogetEarningsBySourcedefine las ganancias.apps/backend/graphql/types/user-subscription.type.js— tipoSubscriberRetentionPointy querysubscriberRetention(months: Int): [SubscriberRetentionPoint!]!.apps/backend/graphql/resolvers/user-subscription.resolver.js— resolversubscriberRetention(autenticado; absorbe los errores del manager y devuelve[]).apps/backend/managers/payment-managers/user-subscription.manager.js—getSubscriberRetention(creatorId, { months }, context): limitamonthsa un rango de 1–24 (6 por defecto), agrupa todo el historial deUserSubscriptiondel creador por mes calendario en código de aplicación, y calcula los conteos de nuevos / dados de baja / activos-al-final, además de la tasa de abandono y de retención por mes.apps/backend/graphql/types/coin-transaction.type.js— tipoEarningsStatementy querymyEarningsStatement(year: Int): EarningsStatement!(también documentada bajo Retiros, ya que se construyó junto con la lógica de cashout/retiro).apps/backend/managers/coin-managers/coin-transaction.manager.js—getEarningsStatement(userId, year, context): desglose de ganancias por fuente del año calendario mástotalCashedOut; un resumen autogestionado, no un formato fiscal oficial.- Preexistentes, reutilizados tal cual:
myEarningsSummary(coin-payout.type.js),myEarningsBySource(coin-transaction.type.js),myPostPurchaseEarnings/myPostPurchaseSales(post-purchase.type.js),subscriberCount(user-subscription.type.js).
Frontend
apps/frontend-nextjs/src/page-components/settings/InsightsAndToolsPage.tsx(ruta/settings/insights-tools) — el panel. Era un stub honesto; ahora compone las tarjetas de abajo, consultamyPostPurchaseEarnings,subscriberCountysubscriberRetention(months: 6), y tiene una exportación "Exportar CSV" del lado del cliente (handleExportCsv) que arma un CSV a partir de los números del resumen de ganancias y de la tabla de retención de 6 meses ya presentes en memoria — sin una query de exportación dedicada.apps/frontend-nextjs/src/page-components/settings/CreatorSalesPage.tsx(ruta/settings/sales, "Ventas y ganancias") — pestañas de ventas/compras sobremyPostPurchaseSales/myPurchasedPosts, más una lista "Mejores compradores" calculada del lado del cliente (byBuyer) que agrupa la página de ventas actual porbuyer.idcon conteos de venta por comprador y totales desellerEarningsCoins.apps/frontend-nextjs/src/components/payouts/EarningsTrendChart.tsx— la gráfica de barras de tendencia diaria (recharts), consultandomyEarningsTimeSeriescon un selector de rango 7d/30d/90d.apps/frontend-nextjs/src/components/payouts/EarningsSummaryCard.tsx,EarningsBySourceChart.tsxyEarningsStatementCard.tsx— preexistentes, reutilizados sin cambios (EarningsStatementCardconsultamyEarningsStatementpara un estado de cuenta descargable por año y por lo demás está documentada bajo Retiros). Las dos primeras también solían aparecer en/settings/get-coins(la página de compra de monedas) — se quitaron de ahí: duplicaban este dashboard y, peor aún, comprar tus propias monedas subía visiblemente "ganado de por vida" en esa tarjeta (ver la nota sobre la corrección delifetimeEarneden Retiros).
Checklist de implementación técnica
-
myEarningsTimeSeries(days)— query/resolver/manager/access-service, serie diaria rellenada con ceros;EarningsTrendChart.tsx - Ventas de posts exclusivos — expone
myPostPurchaseEarnings(el backend ya existía; sin consumidor de frontend antes) - Conteo de suscriptores activos — expone
subscriberCount(creatorId) - Tarjetas de resumen y por-fuente reutilizadas
- Serie temporal de retención de suscriptores — query/resolver/manager
subscriberRetention(months), gráfica de barras de 6 meses más abandono enInsightsAndToolsPage.tsx - Vista de ventas por comprador —
myPostPurchaseSalesexpuesto mediante una lista "Mejores compradores" agrupada del lado del cliente enCreatorSalesPage.tsx(/settings/sales) - Exportación CSV — solo del lado del cliente (
handleExportCsvenInsightsAndToolsPage.tsx); sin query de exportación dedicada - Estado de ganancias anual —
myEarningsStatement(year)víaEarningsStatementCard.tsx, también renderizado en esta página (documentación principal bajo Retiros)
API de GraphQL
# Daily earnings trend, zero-filled, last N days (default 30, max 365)
query MyEarningsTimeSeries($days: Int) {
myEarningsTimeSeries(days: $days) {
date # YYYY-MM-DD
earnings # coins earned that day
}
}
# Monthly subscriber retention/churn, oldest -> newest (default 6 months, max 24)
query SubscriberRetention($months: Int) {
subscriberRetention(months: $months) {
month # YYYY-MM
newSubscribers
churned
activeAtEnd
churnRate # percent
retentionRate # percent
}
}
# Reused on the dashboard
query DashboardExtras($creatorId: ID!) {
myEarningsSummary { availableForCashout unmaturedRecentEarnings }
myEarningsBySource(days: 30) { tips contentSales profileSubscriptions groupSubscriptions marketplace other }
myPostPurchaseEarnings { totalSales totalRevenue totalEarnings platformFees }
myPostPurchaseSales(limit: 20, offset: 0) { id coinPrice sellerEarningsCoins status createdAt buyer { id username } }
subscriberCount(creatorId: $creatorId)
myEarningsStatement(year: 2026) { totalEarnings tips contentSales profileSubscriptions groupSubscriptions marketplace other totalCashedOut }
}
Notas
- La definición de ganancias es compartida. Tanto
myEarningsBySourcecomomyEarningsTimeSeriescuentan los mismos tipos de transacción como "ganancias", así que el total de la tendencia y el total por fuente coinciden para una ventana dada. Las compras de monedas y los bonos otorgados por admin se excluyen de la tendencia (si no, aparecerían como picos espurios). - El relleno con ceros ocurre en el manager, no en SQL — el access-service devuelve solo los días con ganancias, y
getEarningsTimeSeriesen el manager lo expande en un arreglo continuo día por día. Esto mantiene la query barata mientras le da a la gráfica un eje limpio. - Los buckets de retención se calculan en código de aplicación, no en SQL —
getSubscriberRetentionobtiene una sola vez todo el historial deUserSubscriptiondel creador (getAllByCreatorForAnalytics) y lo agrupa en meses calendario en JS. "Activos al final" de un mes cuenta las suscripciones creadas antes del fin del bucket y no canceladas antes de ese punto; la tasa de abandono es el número de bajas en el mes sobre los activos al inicio. - Las exportaciones de CSV/estado de cuenta son del lado del cliente. Tanto el botón "Exportar CSV" en Insights & Tools como la descarga del estado de cuenta en
EarningsStatementCardconstruyen su archivo (CSV / texto plano) a partir de datos de query ya obtenidos en el navegador — no hay un endpoint de exportación del lado del servidor ni almacenamiento de archivos involucrado.