Saltar al contenido principal

Actualizaciones Recientes de Funciones

Esta página es un changelog fechado y de solo-anexado (append-only) de los ciclos de desarrollo: qué se lanzó, cómo funciona cada cambio, los archivos fuente clave y — lo más importante — qué necesitas hacer para desplegarlo (migraciones de base de datos, nuevas variables de entorno).

Cada ciclo tiene su propia sección ## AAAA-MM-DD — Título, con el más reciente primero. Cuando llega un nuevo ciclo, agrega una nueva sección fechada arriba de la anterior — nunca reescribas ni elimines el contenido de una sección anterior. Eso es lo que convierte esta página en un registro duradero de qué se lanzó y cuándo, en lugar de una instantánea de "el último ciclo" que una futura reescritura borra en silencio.


2026-07-21 — Reconciliación completa de la documentación de funciones contra el código

Esta página resume lo que se lanzó en este ciclo de desarrollo, cómo funciona cada cambio, los archivos fuente clave y — lo más importante — qué necesitas hacer para desplegarlos (migraciones de base de datos, nuevas variables de entorno).

Este ciclo fue una reconciliación completa de la documentación de funciones contra el código: una flota de agentes auditó cada documento de funciones contra el código en ejecución en toda la aplicación — cuentas, autenticación y seguridad, mensajería, feed y contenido, monetización, descubrimiento, y la superficie de administración/moderación. En el camino se lanzaron varias capacidades genuinamente nuevas (moderadores de cuenta ampliados con un registro de auditoría, traducción en el chat, ofertas de prueba gratuita/descuento en suscripciones, compras de monedas con PayPal y Apple Pay / Google Pay, una billetera de monedas de la plataforma, vistas previas gratuitas por foto en publicaciones pagas, y passkeys de administrador), mientras que muchas firmas de GraphQL obsoletas, fabricadas o desactualizadas fueron corregidas para reflejar la realidad. 8 nuevas migraciones de base de datos llegaron junto con este trabajo, y la traducción del chat introduce nuevas variables de entorno (opcionales).

Resumen de configuración: ejecuta las 8 nuevas migraciones de base de datos después de hacer pull. Hay nuevas variables de entorno opcionales para la traducción del chat (la función permanece deshabilitada hasta que se configure un proveedor de traducción). Ninguna otra función de este ciclo requiere configuración. Consulta Despliegue y configuración abajo.


Aspectos destacados — genuinamente nuevo en este ciclo

  • Moderadores de cuenta, ampliados — el conjunto de permisos creció de 5 a 9, con una ventana de reautenticación reforzada y un registro completo de auditoría de acciones (migración 20260721150000).
  • Traducción en el chat — traducción de mensajes por conversación con un backend agnóstico de proveedor (migración 20260721160000, nuevas variables de entorno).
  • Ofertas de suscripción — enlaces de prueba gratuita / descuento para creadores (migración 20260721170000).
  • Más formas de comprar monedas — Stripe Express Checkout (Apple Pay / Google Pay) y PayPal, además de una billetera de monedas de la plataforma administrada por administradores (migraciones 20260721190000).
  • Vistas previas gratuitas por foto en publicaciones pagas, además de un precio original tachado cuando un creador aplica un descuento a una publicación ya paga (migraciones 20260721180000, 20260721200000).
  • Passkeys de administrador — credenciales WebAuthn para el inicio de sesión de administrador (migración 20260721140000).
  • Navegación sin sesión iniciada — grilla pública de Clips y búsqueda pública para visitantes no autenticados, además de una página dedicada de Descubrir Personas.

Monetización y monedas

Más formas de comprar monedas (PayPal, Apple Pay, Google Pay)

  • GraphQL: las mutaciones antiguas completeCoinPurchase/createCoinPurchase desaparecieron. El nuevo flujo es createCoinPurchaseIntent/confirmCoinPurchaseIntent (Stripe PaymentIntent, respalda Apple Pay / Google Pay) y createPaypalCoinOrder/capturePaypalCoinOrder (PayPal), que devuelven los nuevos tipos StripeCoinIntent / PaypalCoinOrder.
  • UI: un nuevo CoinExpressCheckout.tsx (Stripe Express Checkout Element + botones de PayPal) se renderiza encima de StripeCheckout en el modal de pago de monedas. La selección de paquete de monedas se movió a /settings/get-coins (GetCoinsPage.tsx); /coins ahora redirige ahí.
  • Modelo: un CoinPurchase se vincula a una PaymentTransaction cuyo provider es una cadena libre ('stripe' o 'paypal'), no exclusiva de Stripe.
  • El enum CoinTransactionType creció de 6 a 16 valores (agrega tip_sent, tip_received, post_purchase, conversation_subscription, post_sale, subscription_revenue, product_sale, cashout_requested, cashout_reversed, cashout_canceled).
  • Fuente: apps/backend/graphql/types/coin-purchase.type.js, apps/backend/graphql/resolvers/coin-purchase.resolver.js, apps/backend/managers/coin-managers/coin-purchase.manager.js, apps/frontend-nextjs/src/components/payment/CoinExpressCheckout.tsx, apps/frontend-nextjs/src/page-components/CoinsPage.tsx.

Billetera de monedas de la plataforma (administración)

  • Modelo: una única billetera de monedas a nivel de plataforma (modelo PlatformWallet + migración 20260721190000-create-platform-wallet, sembrada con una fila platform) y su manager.
  • GraphQL (administración): consultas adminPlatformWallet, adminGetUserCoinBalance; mutaciones adminTopUpWallet, adminTransferCoinsToUser, adminTakeCoinsFromUser.
  • UI: UserCoinsCard.tsx en la página de detalle de usuario de administración.
  • Fuente: apps/backend/database/models/PlatformWallet.js, apps/backend/managers/coin-managers/platform-wallet.manager.js, apps/backend/graphql/types/admin/platform-wallet.type.js, apps/backend/graphql/resolvers/admin/platform-wallet.resolver.js, apps/frontend-admin/src/components/UserCoinsCard.tsx.
  • Nota: las mutaciones CRUD de paquetes de monedas no administrativas se eliminaron de coin-package.resolver.js y se reemplazaron por equivalentes con espacio de nombres de administrador (adminCreateCoinPackage/adminUpdateCoinPackage/… más adminGetCoinPackages).

Suscripciones de creadores y ofertas de suscripción

  • GraphQL: un sistema de ofertas de suscripción completamente nuevo — createSubscriptionOffer/updateSubscriptionOffer/deactivateSubscriptionOffer/deleteSubscriptionOffer, myOffers, offerByToken, applicableDiscount, redeemSubscriptionTrial — un mecanismo de enlace de prueba gratuita/descuento que no existía en la última sincronización (migración 20260721170000).
  • GraphQL: nueva consulta subscriberRetention(months) (SubscriberRetentionPoint) — analítica de retención/abandono agrupada por mes, calculada en el código de la aplicación a partir del historial completo de UserSubscription del creador.
  • UI: ahora existe una pantalla CRUD completa de niveles en Settings → Subscriptions (SubscriptionsSettingsPage.tsx) y un CTA real de Suscribirse con un selector de nivel se renderiza en los perfiles públicos de los creadores; renewSubscription, enableConversationSubscription/disableConversationSubscription están todos conectados a botones reales. Nuevas superficies: SubscriptionOffersPage.tsx, la ruta pública /offer/[token] (OfferRedemptionPage.tsx), useApplicableDiscount.ts + DiscountedPrice.tsx.
  • Fuente: apps/backend/graphql/resolvers/subscription-offer.resolver.js, apps/backend/graphql/types/subscription-offer.type.js, apps/backend/managers/payment-managers/subscription-offer.manager.js, apps/backend/managers/payment-managers/user-subscription.manager.js, apps/frontend-nextjs/src/page-components/settings/SubscriptionsSettingsPage.tsx.
  • Correcciones de rutas: subscription-tier.access-service.js / user-subscription.access-service.js se movieron bajo data-access-services/payment/.

Publicaciones exclusivas (pagas)

  • Vistas previas gratuitas por foto: PostMedia.isPreview / PostMediaInput.isPreview permiten que un creador marque medios específicos en una publicación paga como gratuitos (migración 20260721180000-add-is-preview-to-post-media). Post.media ahora devuelve esos elementos de vista previa a un espectador bloqueado en lugar de resolver a null; la interfaz de alternancia vive en CreatePostModal.tsx.
  • Registro de descuentos: nuevo Post.originalCoinPrice (precio anterior tachado cuando un creador baja coinPrice en una publicación ya paga; migración 20260721200000-add-original-coin-price-to-post) y Post.hiddenMediaCount (conteo de medios aún bloqueados para un espectador sin acceso).
  • Corrección de UI: no existe una pestaña de perfil dedicada de "Contenido pago" — las publicaciones exclusivas se renderizan dentro del feed/grilla de perfil normal con una insignia de precio en monedas; el flujo de superposición de bloqueo + "Desbloquear por N monedas" vive en PostCard.tsx/PostModal.tsx.
  • Fuente: apps/backend/graphql/types/post.type.js, apps/backend/managers/post-managers/post.manager.js (registro de descuentos de updatePost), apps/backend/database/models/PostMedia.js.

Analítica de creadores y pagos

  • GraphQL: recién documentados subscriberRetention, myEarningsStatement(year) (EarningsStatement), y myEarningsTimeSeries(days) (EarningsTimePoint, tendencia diaria rellenada con ceros).
  • Barrera de identidad: tanto requestCashout (Stripe) como requestManualCashout ahora requieren una verificación de identidad aprobada por un administrador antes de que se mueva cualquier moneda — una barrera estricta del lado del servidor (coin-cashout.manager.jsidentityVerificationManager.requireApprovedForCashout), presentada mediante un banner de barrera en la pantalla de pagos.
  • Mapa de rutas de UI corregido: la UI de pagos ahora vive en /settings/payouts (PayoutsPage.tsx), ganancias/estados de cuenta en /settings/insights-tools (InsightsAndToolsPage.tsx), y ventas por comprador en /settings/sales (CreatorSalesPage.tsx). Nueva exportación CSV del lado del cliente (handleExportCsv) y componentes EarningsTrendChart.tsx/EarningsStatementCard.tsx.
  • El ciclo de vida de CoinCashout.status es específico por proveedor: Stripe pending→paid/failed (+ cancelación por administrador→canceled); Manual pending→processing→completed/rejected.
  • Fuente: apps/backend/managers/coin-managers/coin-cashout.manager.js, apps/backend/graphql/types/coin-transaction.type.js, apps/frontend-nextjs/src/page-components/settings/PayoutsPage.tsx.

Regalos

  • GraphQL: nuevo campo Post.giftStats: GiftTargetStats! agrupa los totales de regalos en los objetos Post del feed (resuelto por publicación vía gift.manager#getStatsForTarget), reemplazando una consulta giftStatsForTarget por tarjeta.
  • sendGift ahora también crea una notificación de tipo gift para el destinatario y calcula el newBalance del remitente (ambos best-effort, no bloqueantes).
  • Corrección: myGiftablePosts no forma parte del sistema de regalos — pertenece al flujo de recompensas de top-fans (top-fans.*).
  • Fuente: apps/backend/graphql/types/post.type.js, apps/backend/managers/coin-managers/gift.manager.js, apps/backend/data-access-services/gift/gift.access-service.js.

Promociones de publicaciones

  • GraphQL: trackPromotionImpression/trackPromotionClick ahora están en vivo y conectados — PostCard.tsx los dispara para publicaciones patrocinadas activas, así que las impresiones/clics se incrementan de verdad. estimatePromotionReach es una estimación real derivada del número de seguidores + interacción reciente + presupuesto/duración (aún ignora la segmentación).
  • Formas de entrada/salida corregidas: createPostPromotion recibe CreatePostPromotionInput; updatePostPromotion recibe UpdatePostPromotionInput; pausar/reanudar/cancelar y aprobar/rechazar de administrador devuelven todos el objeto PostPromotion actualizado, no un envoltorio { success }.
  • UI: la interfaz completa de promociones orientada al creador (MyPromotionsPage.tsx + CreatePromotionModal.tsx en /settings/promotions) ya estaba conectada — la afirmación previa de "sin interfaz frontend en absoluto" era incorrecta.
  • Fuente: apps/backend/graphql/resolvers/post-promotion.resolver.js, apps/backend/graphql/types/post-promotion.type.js, apps/backend/managers/post-managers/post-promotion.manager.js.

Feed, contenido y medios

Feed principal — estado del espectador en línea

  • GraphQL: nuevos resolvers de campo de Post viewerHasLiked/viewerHasSaved/viewerHasReposted/interactionCount/giftStats agrupan el estado por espectador y los conteos directamente en las consultas feed/userPosts/post, reemplazando los viajes redondos por tarjeta (PostCard.tsx recurre a las antiguas consultas separadas solo cuando el estado en línea está ausente).
  • Personalización del feed: una publicación que el espectador acaba de publicar (últimos 5 minutos) se fija en la parte superior de su propio feed (post.access-service.js#getFeed, FRESH_OWN_MS).
  • PostMedia obtuvo filterCss, objectFit, e isPreview; PostTagInput.mediaIndex/x/y ahora son opcionales (etiquetas de personas sin posición).
  • Ejemplo corregido de reactToComment: el argumento es reaction (no reactionType) y devuelve un Boolean simple; lee los conteos vía postComment(id).reactionCounts/.viewerReaction.
  • Fuente: apps/backend/graphql/types/post.type.js, apps/backend/graphql/resolvers/post.resolver.js, apps/frontend-nextjs/src/components/PostCard.tsx, apps/frontend-nextjs/src/page-components/HomePage.tsx.

Fondos de publicaciones de texto (nuevo post.metadata)

  • Modelo: una columna JSONB libre metadata en post (migración 20260722000000-add-metadata-to-post) almacena el fondo estilo Facebook para publicaciones de solo texto, por ejemplo { background: 'linear-gradient(...)', textColor: '#fff' }.
  • Fuente: apps/backend/database/migrations/20260722000000-add-metadata-to-post.js, apps/backend/database/models/Post.js.

Clips

  • UI: navegación sin sesión iniciada — una grilla pública de Clips (PublicClipsPage.tsx) se sirve vía BrowseGate.tsx en /clips para visitantes no autenticados; el enlace de Clips ahora se ubica inmediatamente después de Explorar en la barra lateral de escritorio y es el elemento central de la navegación inferior móvil.
  • GraphQL: el resolver de clips pasa publicOnly: true para llamadas anónimas; bajo publicOnly, postAccessService.getByType('clip', …) filtra visibility='public', isPublished=true, y excluye a creadores de cuentas privadas.
  • Corrección: el flujo dedicado de creación de clips no está conectado — CreateClipModal.tsx existe pero ninguna ruta lo monta (el FAB de subida se eliminó de ClipsPage.tsx).
  • Fuente: apps/frontend-nextjs/src/page-components/PublicClipsPage.tsx, apps/frontend-nextjs/src/components/public/BrowseGate.tsx, apps/backend/data-access-services/post/post.access-service.js.

Historias y en vivo

  • Regalos de monedas en vivo (sendLiveGift/liveGiftCatalog/liveStreamGifts) y solicitudes de orador en transmisiones en vivo (requestLiveSpeaker/liveStreamSpeakerRequests/myLiveSpeakerStatus/respondToLiveSpeakerRequest) están completamente construidos y conectados — pasaron de "no construido" a terminado. Las solicitudes de orador en vivo son una implementación separada en live-stream.manager.js, independiente del manager de solicitudes de orador exclusivo de Llamadas.
  • Nueva mutación promoteLiveStream: el propietario crea/reutiliza una publicación de anuncio complementaria y la promociona a través del flujo normal de promoción de publicaciones (botón "Promocionar" en la sala en vivo).
  • Correcciones: la audiencia de historias (close_friends) se almacena pero no se aplica al leer; storyViewers/storyViewerCount están declarados y son llamados por la UI pero lanzan un error en tiempo de ejecución (no hay métodos del manager que los respalden); deleteLiveComment está restringido al autor del comentario o al anfitrión de la transmisión (sin bypass de administrador).
  • Fuente: apps/backend/graphql/types/live-stream-gift.type.js, apps/backend/managers/live-stream-managers/live-stream.manager.js, apps/frontend-nextjs/src/page-components/LiveRoomPage.tsx.

Programación de publicaciones y colaboradores

  • Programación: nueva mutación updateScheduledPost y programación de historias (StoryCreateInput.scheduledAt); el propietario ahora ve sus propias publicaciones programadas en su propia grilla de perfil (marcadas con la hora de publicación), mientras que otros espectadores nunca las ven. Un DateTimePicker compartido tipo calendario (en packages/ui) reemplaza el antiguo selector datetime-local.
  • Colaboradores: la superficie GraphQL de PostCollaborator (inviteCollaborator, acceptCollaboration, postCollaborators, collaborationStats, …) ahora está completamente conectada y documentada; CollaboratorsModal.tsx ahora llama de verdad a inviteCollaborator/removeCollaborator/bulkInviteCollaborators (corregido en un ciclo posterior — ver Colaboradores en publicaciones, referencia técnica), y aceptar/rechazar en CollaborationInvitesPage.tsx sigue funcionando. Las publicaciones de colaboradores aceptados sí se propagan a los perfiles/feeds de los seguidores.
  • Fuente: apps/backend/graphql/types/post.type.js, apps/backend/graphql/resolvers/post-collaborator.resolver.js, apps/backend/managers/post-managers/post.manager.js.

Protección de contenido (marca de agua)

  • Corrección: DynamicWatermark ahora está conectado únicamente en la superficie de transmisión en vivo. Se eliminó de las vistas de publicaciones exclusivas (PublicProfilePage.tsx/PostModal.tsx) y de las burbujas de mensajes de chat pagos (MessageBubble.tsx) — los dos elementos de la lista de verificación "medios de publicación exclusiva" y "imagen de mensaje pago" se desmarcaron. El componente en sí no cambió; dos de sus tres puntos de invocación se eliminaron.
  • Fuente: apps/frontend-nextjs/src/page-components/LiveRoomPage.tsx, apps/frontend-nextjs/src/components/common/DynamicWatermark.tsx.

Hashtags y menciones

  • trendingHashtags, hashtagByName, hashtagStats ahora están correctamente documentados como consumidos por HashtagPage.tsx. PostCard.tsx cambió a linkifyText(), que convierte en enlaces tanto #hashtags como @menciones en los pies de foto.
  • Bug documentado: el SDL de removeTag declara userId/contentType/contentId pero el resolver desestructura un tagId que nunca se proporciona, así que cada llamada falla; los métodos de mentions-tags.manager.js siguen siendo stubs simulados. La ruta real de eliminación de menciones es removeMention (post-mention.manager.js#deleteMention).
  • Correcciones de rutas: hashtag.access-service.jsdata-access-services/post/; resolvers de menciones/etiquetas → user-mentions-tags.resolver.js.

Mensajería

Traducción en el chat (nuevo)

  • GraphQL: nuevas consultas messageTranslations, myTranslationSetting y mutación setMessageTranslation, además de almacenamiento ConversationParticipant.translationEnabled/preferredLanguage (migración 20260721160000-add-chat-translation).
  • El backend es agnóstico de proveedormessage-translation.service.js elige el primer proveedor configurado (LibreTranslate → DeepL → Google → OpenAI) y no hace nada si ninguno está configurado. Consulta las nuevas variables de entorno en Despliegue.
  • Fuente: apps/backend/graphql/types/message-translation.type.js, apps/backend/graphql/resolvers/message-translation.resolver.js, apps/backend/managers/message-managers/message-translation.manager.js, apps/backend/database/models/ConversationParticipant.js.

Funciones de chat ahora conectadas

  • Confirmaciones de lectura (suscripción messageRead) están completamente conectadas — useChatMessages.ts cambia el estado del mensaje a read y MessageBubble.tsx renderiza el doble check azul.
  • Mensajes que desaparecen / expiran (setMessageExpiration) conectados vía useChatActions.ts y el menú contextual del mensaje; updateConversationSettings mapea las alternancias de mensajes que desaparecen (predeterminado 86400s).
  • Compartir ubicación estática (shareLocation) está conectado desde el compositor y se renderiza con un enlace de Maps; la ubicación en vivo (updateLiveLocation/stopLiveLocation) permanece sin conectar.
  • Nuevo: suscripción messageReactionAdded (actualiza las reacciones en vivo); las @menciones se analizan en filas MessageMention al crear createMessage (aún no hay consumidor en el frontend para las consultas de menciones); flujo de enlace de invitación respaldado por conversation-invite.manager.js.
  • El ejemplo de código del enum MessageType se corrigió para incluir link y post.
  • Corrección de ruta: message.access-service.js vive bajo data-access-services/message/.

Social, descubrimiento y perfil

Descubrir personas y recomendaciones

  • UI: una página dedicada de Descubrir Personas en /discover-people (DiscoverPeoplePage.tsx) con secciones de recomendados / similares / personas que quizás conozcas / tendencias / cercanos, alcanzable desde "Ver todo" de SuggestedUsers.tsx. Las tarjetas de usuarios sugeridos ahora muestran una etiqueta "Seguido por [nombre] + N más".
  • GraphQL: nuevos campos User.followContext (MutualFollowContext) y User.isFollowing; nueva mutación dismissSuggestion(userId) (persistida en la tabla dismissed_suggestion) usada por el botón X por tarjeta de RecommendationsSection.tsx; usersYouMayKnow/similarUsers están conectados.
  • La barra de pestañas de categoría de RecommendationsSection.tsx (quizás-conozcas/similares/tendencias/recomendados/cercanos) es real y se puede pulsar (onClick={() => setActiveTab(tab.id)}), así que cada pestaña, incluyendo nearbyUsers, es alcanzable desde la UI (corregido en un ciclo posterior — esta documentación antes decía que las pestañas estaban ocultas y activeTab fijado); hideFromSuggestions se llama desde este componente y desde AccountPrivacyPage.tsx.
  • /search sin sesión iniciada ahora se bifurca: autenticado → ExplorePage (hideRecommendations), sin sesión iniciada → PublicSearchPage.tsx (searchUsers + searchHashtags).
  • Fuente: apps/frontend-nextjs/src/page-components/DiscoverPeoplePage.tsx, apps/backend/graphql/resolvers/user-fields.resolver.js, apps/backend/graphql/types/user-recommendations.type.js.

Contactos y validación

  • El antiguo user.resolver.js desapareció — los resolvers de contactos se dividieron en user-contact-import.resolver.js, user-recommendations.resolver.js, user-mentions-tags.resolver.js, user-validation.resolver.js.
  • Correcciones de GraphQL: las consultas de validación en tiempo real son validateUsername/validateEmail (no isUsernameAvailable/isEmailAvailable); importContacts recibe input: ContactImportInput!; syncContacts no recibe argumentos; la mutación real de invitación es inviteContacts(emails). Las sugerencias de contactos (contactSuggestions) devuelven el tipo ContactSuggestionsResponse (corregido en un ciclo posterior — ver Contactos, referencia técnica).
  • Bugs corregidos en un ciclo posterior (ver Contactos, referencia técnica): el autocompletado de menciones con @ (mentionSuggestions) ahora ejecuta una búsqueda real y devuelve el [MentionSuggestion!]! con la forma del esquema; validateFieldRealtime ahora reenvía los argumentos correctos al manager.
  • Cambio de regla de validación: la edad mínima de registro ahora es 18 (antes 16/13) y dateOfBirth es obligatorio en el registro, dado que la plataforma vende contenido íntimo pago (user.validator.js, validateDateOfBirth).
  • Ahora existe UI para: RecommendationsSection.tsx, PostModal.tsx (sugerencias de menciones), Login.tsx (validación).

Perfil

  • Enlaces sociales ahora son una función de GraphQL en vivo y completamente conectada (mySocialLinks/userSocialLinks/socialLinkStats; addSocialLink/updateSocialLink/deleteSocialLink/reorderSocialLinks/trackSocialLinkClick) presentada en Settings → Social Links.
  • user.resolver.js / user.access-service.js ya no existen — el resolver se dividió en ~15 archivos por área de responsabilidad fusionados automáticamente por graphql/resolvers.js; el servicio de acceso se movió a data-access-services/user/.
  • Grandes correcciones de GraphQL: Interests ahora es un simple User.interests: [String!] actualizado vía updateInterests; la consulta de permisos de etiquetas es tagPermissions (entrada JSON! en bruto); trending/online se renombraron a getTrendingUsers/getOnlineUsers; followUser siempre sigue de inmediato (el punto de entrada de cuenta privada es sendFollowRequest); firmas de apelaciones de restricción corregidas; deleteAccountImmediately/bulkMuteUsers documentadas.

Favoritos, publicaciones guardadas, y otros cabos sueltos de descubrimiento

  • Favoritos ahora se pueden agregar desde el menú "⋯ Más opciones" de un perfil (no solo eliminarse desde Settings). Se aplica un tope de 50 favoritos del lado del servidor. Correcciones: getFavoritesStats solo calcula totalFavorites (mutual/recent/mostFavorited están fijados en 0/null); el manager es persistencia real con Sequelize (no "andamiaje simulado"); el servicio de acceso a datos se movió a data-access-services/user/.
  • Publicaciones guardadas ahora se organizan por colección — mySavedPosts desapareció, reemplazado por collectionPosts(collectionId, …). Cada usuario obtiene una colección predeterminada "Guardado" creada automáticamente y no eliminable (SavedCollection.isDefault, migración 20260715090000 de un ciclo anterior); deleteCollection rechaza la predeterminada. Nueva UI: SaveToCollectionModal, SavedPage.tsx (/settings/saved), DiscoverCollectionsPage.tsx (/settings/discover-collections).
  • Ubicación: nearbyUsers ahora se presenta en la sección "Cerca de ti" de /discover-people; el compartir de ubicación estática en el chat está conectado (ver Mensajería).
  • Referidos: el referidor ahora recibe una notificación referral_bonus; la página de referidos tiene botones para compartir nativo/WhatsApp/Facebook/X.

Cuentas, autenticación y seguridad

Autenticación y 2FA

  • Las mutaciones de autoinscripción 2FA por SMS/correo (enableTwoFactor, verifyTwoFactorCode, generateBackupCodes, verifyBackupCode) fueron eliminadas del esquema — ahora solo el 2FA de aplicación autenticadora puede autoinscribirse. Las mutaciones de configuración en vivo son setupTwoFactor/verifyTwoFactorSetup/disableTwoFactor/regenerateBackupCodes.
  • Nueva documentación para verifyLoginTwoFactor(twoFactorToken, code) — la mutación que completa un inicio de sesión una vez que se requiere 2FA (acepta un TOTP o un código de respaldo). El inicio de sesión devuelve requiresTwoFactor/twoFactorToken/twoFactorMethod.
  • regenerateBackupCodes, myPasskeys, deletePasskey ahora están conectados en Settings → Security. TwoFactorSetupResponse obtuvo qrCodeSvg (SVG en línea seguro para CSP) y otpauthUrl.
  • El puntero de documentación de Apple Sign-In se redirigió del eliminado APPLE_SIGNIN_IMPLEMENTATION.md a las variables APPLE_* en environment-setup.
  • Fuente: apps/backend/graphql/types/two-factor-auth.type.js, apps/backend/graphql/resolvers/user-authentication.resolver.js, apps/frontend-nextjs/src/components/Login.tsx, apps/frontend-nextjs/src/page-components/settings/SecuritySettingsPage.tsx.

Seguridad, sesiones y gestión de cuenta

  • Ahora existe UI de seguridad en frontend-nextjs: SecuritySettingsPage.tsx (/settings/security), SecurityAlertsPage.tsx, SessionsSettingsPage.tsx, VerificationRequestPage.tsx. El antiguo user.resolver.js se dividió en resolvers user-two-factor-auth/user-security-alerts/user-sessions/user-authentication.
  • Bugs de sesión documentados: terminateAllSessions es un alias de revokeAllOtherSessions (no cierra sesión en el dispositivo que llama); loginHistory lanza un error (desajuste de forma); sessionsTerminated siempre resuelve null (el manager devuelve revoked_count); getCurrentSession devuelve datos simulados fijos.
  • Gestión de cuenta: getActivityHeatmap ahora se visualiza como un mapa de calor de contribuciones estilo GitHub en Settings → Activity insights. deleteAccount es un finalizador de período de gracia (rechaza a menos que ya esté en pending_deletion y haya pasado la fecha programada); deleteAccountImmediately es el bypass real. setAllNotifications ya no existe. La pila de cuentas conmemorativas (GraphQL de usuario + administrador) ahora está completamente implementada. El servicio de correo es una fachada multiproveedor (services/email/, EMAIL_PROVIDER).
  • Notificaciones: nueva suscripción notificationAdded(userId) (el frontend se suscribe en lugar de hacer polling); el enum NotificationType creció de 8 a 34 valores; las notificaciones push ahora son agnósticas de proveedor (FCM vía services/push-notification/), con VoIP para llamadas todavía en APNs real (voip-push.service.js). El servicio de acceso a datos se movió a data-access-services/user/.
  • La exportación de datos ahora está completamente implementada: una tabla data_export (migración 20260719080000, ciclo anterior), myDataExports/downloadDataExport/processDataExport, una suscripción dataExportStatusChanged, y DownloadDataPage.tsx (/settings/download-data). Varios campos están documentados como parciales/rotos (getDataPortabilityInfo, downloadToken, estimatedCompletion).

Configuración, visitantes de perfil, verificación de edad

  • Restringir (restrictAccount/unrestrictAccount) y Limitar interacciones (setInteractionLimit) están completamente conectados de extremo a extremo (migración 20260719030000, ciclo anterior) con páginas de configuración reales. InsightsAndToolsPage ahora muestra analítica real de monetización del creador. Family Center — construido en un ciclo posterior, ya no es un stub — está documentado por separado en Centro de familias.
  • hide_profile_visits ahora está completamente conectado (alternancia de privacidad → barrera de recordVisit), no código muerto.
  • Edad/identidad: el banner de barrera de verificación de identidad ahora también aparece en una página dedicada /settings/payouts.

Moderación y administración

Moderadores de cuenta (ampliados)

  • Los permisos crecieron de 5 a 9 (se agregaron Publicar historias, Comentar, Reaccionar, Gestionar comentarios), con un mecanismo de reautenticación reforzada (assertRecentAuth/verifyPasswordStepUp respaldado por una ventana de Redis de 15 minutos en step-up-auth.service.js) y una nueva mutación verifyPasswordForModerators(password).
  • Nuevo registro de auditoría: modelo AccountModeratorAction + account-moderator-action.access-service.js + consulta moderatorActivity, y atribución Message.viaModerator (migración 20260721150000-expand-account-moderator).
  • Corrección: canStory está definido pero no se aplica en ningún lugar — createStory es una mutación separada que nunca acepta actAsUserId. El flujo "Comentar como" de comentar en nombre del propietario vive en PostModal.tsx + post-comment.resolver.js.
  • Fuente: apps/backend/graphql/types/account-moderator.type.js, apps/backend/managers/user-managers/account-moderator.manager.js, apps/backend/services/step-up-auth.service.js, apps/backend/database/models/AccountModeratorAction.js.

Passkeys de administrador y restablecimiento de contraseña

  • Passkeys de administrador: credenciales WebAuthn con alcance limitado a admin_user (nueva tabla admin_passkey, migración 20260721140000-create-admin-passkey).
  • Restablecimiento de contraseña: adminRequestPasswordReset (sin autenticar, siempre reporta éxito para evitar la enumeración de correos) y adminResetPassword (consume un token de un solo uso) conectados a las nuevas páginas de administrador /forgot-password y /reset-password.
  • Fuente: apps/backend/graphql/resolvers/admin/admin-user.resolver.js, apps/backend/managers/admin-managers/admin-user.manager.js, apps/frontend-admin/src/app/forgot-password/page.tsx.

Moderación de contenido

  • Ahora existe un panel completo de moderación de administración en frontend-admin: /moderation (cola de reportes), /moderation/flagged (cola de contenido marcado + acciones), /moderation/rules (CRUD de reglas de auto-moderación) — las afirmaciones previas de "no hay panel de administración" fueron corregidas.
  • El motor de reglas de auto-moderación es real y se aplica: adminGetAutoModerationRules/Create/Update/Delete contra una tabla auto_moderation_rule, aplicado del lado del servidor por un nuevo services/auto-moderation.service.js llamado desde post.manager.js al crear una publicación.
  • Correcciones: adminEscalateReport/adminDismissReport se eliminaron (usa adminReviewReport con ESCALATE/REJECT); content-moderation.resolver.js/.type.js se movieron bajo admin/; ContentModerationDetails.nsfwScore ahora tiene un resolver real (solo publicaciones); toxicityScore siempre es null; adminGetContentModerationHistory devuelve [] (falta findByContent).
  • Nuevos archivos de backend: auto-moderation.service.js, content-preview.service.js, content-moderation-log.access-service.js.

Analítica del panel, identidad, pagos, disputas, moderación de usuarios

  • Panel: UserGrowthData.retentionRate ahora prefiere datos reales de cohortes de registro (user_cohort_snapshots, agregado en un ciclo posterior — ver Analítica del panel), y recae en el proxy original de usuario recurrente (null solo cuando no hay base previa) para los buckets que una instantánea aún no cubre; adminGetActivityLog ahora combina user_moderation_logs + content_moderation_logs.
  • Verificación de identidad (administración): adminPendingIdentityVerifications devuelve una lista plana [IdentityVerificationRequest!]! (paginada por limit/offset), y adminApproveIdentityVerification/adminRejectIdentityVerification devuelven IdentityVerificationStatusResult! (el reason de rechazo es obligatorio).
  • Pagos/desembolsos (administración): nuevos adminGetCashout, adminGetPayoutProfile, adminSetPayoutAccountDisabled; adminGetCashouts corregido para recibir filters/limit/offset y devolver [CoinCashout!]!; las mutaciones de cancelar/completar/rechazar reciben id y devuelven CoinCashout!.
  • Disputas de tienda: adminOpenProductDisputes/adminResolveProductDispute viven en el esquema de administración (/admin/graphql), protegidas por context.admin + MODERATE_CONTENT; los valores de ProductDisputeResolution son resolved_refund (revierte las monedas, no restaura el stock) / resolved_denied.
  • Moderación de usuarios: adminGetSuspendedUsers/adminGetBannedUsers/adminGetUserSuspensions/adminGetUserWarnings se eliminaron; adminBulkBanUsers (solo super-administrador) existe pero aún no tiene UI de administración.
  • Apelaciones: las notas de denegación son opcionales (sin verificación de presencia en el resolver o la UI).
  • Comentarios de usuarios: la superficie de GraphQL (myFeedback, publicFeedback, submitFeedback, upvoteFeedback/downvoteFeedback) está en vivo con un FeedbackPage.tsx real; las herramientas de revisión de administración (adminGetFeedback/adminGetFeedbackStats/adminUpdateFeedbackStatus/adminSetFeedbackPriority/adminRespondToFeedback, protegidas por MANAGE_FEEDBACK, con una UI de administración real en /feedback) se corrigieron en un ciclo posterior — ver Comentarios de usuarios (administración). Deduplicación de votos vía UserFeedbackVote (migración 20260718140000, ciclo anterior).

Despliegue y configuración

Ejecuta las nuevas migraciones de base de datos

8 nuevas migraciones llegaron en este ciclo. Desde apps/backend:

npx sequelize-cli db:migrate
# luego reinicia el backend
MigraciónAgregaÁrea
20260721140000-create-admin-passkeyTabla admin_passkey (credenciales WebAuthn para el inicio de sesión de administrador)Passkeys de administrador
20260721150000-expand-account-moderatorcan_comment / can_manage_comments / can_story / can_react en account_moderator; nueva tabla de auditoría account_moderator_actionModeradores de cuenta
20260721160000-add-chat-translationtranslation_enabled / preferred_language en conversation_participant; nueva tabla de caché message_translationTraducción del chat
20260721170000-create-subscription-offerTablas subscription_offer + subscription_offer_redemption (pruebas / descuentos)Ofertas de suscripción
20260721180000-add-is-preview-to-post-mediapost_media.is_previewVistas previas gratuitas por foto
20260721190000-create-platform-walletTabla platform_wallet (sembrada con una fila platform)Billetera de monedas de la plataforma
20260721200000-add-original-coin-price-to-postpost.original_coin_pricePrecios con descuento en publicaciones pagas
20260722000000-add-metadata-to-postpost.metadata (JSONB — fondos de publicaciones de texto)Metadatos de publicación

Todas las migraciones son idempotentes (protegidas por describeTable/showAllTables), así que volver a ejecutarlas es seguro.

Los numerosos cambios de reconciliación de documentación anteriores (mutaciones agregadas, eliminadas o corregidas, o correcciones de ruta de archivo) reutilizan el esquema existente y no requieren ninguna migración propia — solo un reinicio del backend para captar los cambios de resolver. Las tablas mencionadas arriba que no están en esta tabla (por ejemplo, content_moderation_logs vía 20260717060000, data_export vía 20260719080000, saved_collection.is_default vía 20260715090000, límites de interacción vía 20260719030000) se crearon en ciclos anteriores.

Nuevas variables de entorno — traducción del chat (todas opcionales)

La traducción del chat es agnóstica de proveedor y está deshabilitada por defecto: message-translation.service.js elige el primer proveedor configurado, y si ninguno está configurado no hace nada (registra "chat translation disabled"). Para habilitarla, configura las credenciales de exactamente un proveedor.

Orden de selección de proveedor (gana el primero configurado): LibreTranslate → DeepL → Google → OpenAI.

VariablePropósitoPredeterminado
LIBRETRANSLATE_URLURL base de LibreTranslate (su presencia selecciona este proveedor)
LIBRETRANSLATE_API_KEYClave de API opcional de LibreTranslate
DEEPL_API_KEYClave de autenticación de DeepL (selecciona DeepL)
DEEPL_API_URLURL base de la API de DeepLhttps://api-free.deepl.com
GOOGLE_TRANSLATE_API_KEYClave de API de Google Translate v2 (selecciona Google)
OPENAI_API_KEYClave de OpenAI (selecciona OpenAI)
OPENAI_TRANSLATE_MODELModelo de OpenAI para la traduccióngpt-4o-mini
TRANSLATION_MAX_CHARSMáximo de caracteres por mensaje traducido5000
TRANSLATION_MONTHLY_CHAR_LIMITTope mensual de caracteres (0 = sin límite)0
TRANSLATION_HTTP_TIMEOUT_MSTiempo de espera por solicitud al proveedor8000

Ninguna de estas es necesaria para iniciar el backend — dejarlas sin configurar simplemente mantiene desactivada la traducción del chat.

Las fachadas multiproveedor de correo (EMAIL_PROVIDER) y notificaciones push (PUSH_NOTIFICATION_PROVIDER, DISABLE_PUSH_NOTIFICATIONS, FIREBASE_*, APNS_*) se documentaron en este ciclo como correcciones — el código y esas variables ya existían antes de este ciclo, así que no son nuevos requisitos de configuración. Consulta Configuración del entorno para la referencia completa.