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/createCoinPurchasedesaparecieron. El nuevo flujo escreateCoinPurchaseIntent/confirmCoinPurchaseIntent(Stripe PaymentIntent, respalda Apple Pay / Google Pay) ycreatePaypalCoinOrder/capturePaypalCoinOrder(PayPal), que devuelven los nuevos tiposStripeCoinIntent/PaypalCoinOrder. - UI: un nuevo
CoinExpressCheckout.tsx(Stripe Express Checkout Element + botones de PayPal) se renderiza encima deStripeCheckouten el modal de pago de monedas. La selección de paquete de monedas se movió a/settings/get-coins(GetCoinsPage.tsx);/coinsahora redirige ahí. - Modelo: un
CoinPurchasese vincula a unaPaymentTransactioncuyoprovideres una cadena libre ('stripe'o'paypal'), no exclusiva de Stripe. - El enum
CoinTransactionTypecreció de 6 a 16 valores (agregatip_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ón20260721190000-create-platform-wallet, sembrada con una filaplatform) y su manager. - GraphQL (administración): consultas
adminPlatformWallet,adminGetUserCoinBalance; mutacionesadminTopUpWallet,adminTransferCoinsToUser,adminTakeCoinsFromUser. - UI:
UserCoinsCard.tsxen 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.jsy se reemplazaron por equivalentes con espacio de nombres de administrador (adminCreateCoinPackage/adminUpdateCoinPackage/… másadminGetCoinPackages).
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ón20260721170000). - 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 deUserSubscriptiondel 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/disableConversationSubscriptionestá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.jsse movieron bajodata-access-services/payment/.
Publicaciones exclusivas (pagas)
- Vistas previas gratuitas por foto:
PostMedia.isPreview/PostMediaInput.isPreviewpermiten que un creador marque medios específicos en una publicación paga como gratuitos (migración20260721180000-add-is-preview-to-post-media).Post.mediaahora devuelve esos elementos de vista previa a un espectador bloqueado en lugar de resolver anull; la interfaz de alternancia vive enCreatePostModal.tsx. - Registro de descuentos: nuevo
Post.originalCoinPrice(precio anterior tachado cuando un creador bajacoinPriceen una publicación ya paga; migración20260721200000-add-original-coin-price-to-post) yPost.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 deupdatePost),apps/backend/database/models/PostMedia.js.
Analítica de creadores y pagos
- GraphQL: recién documentados
subscriberRetention,myEarningsStatement(year)(EarningsStatement), ymyEarningsTimeSeries(days)(EarningsTimePoint, tendencia diaria rellenada con ceros). - Barrera de identidad: tanto
requestCashout(Stripe) comorequestManualCashoutahora 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.js→identityVerificationManager.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 componentesEarningsTrendChart.tsx/EarningsStatementCard.tsx. - El ciclo de vida de
CoinCashout.statuses específico por proveedor: Stripepending→paid/failed(+ cancelación por administrador→canceled); Manualpending→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 objetosPostdel feed (resuelto por publicación víagift.manager#getStatsForTarget), reemplazando una consultagiftStatsForTargetpor tarjeta. sendGiftahora también crea una notificación de tipogiftpara el destinatario y calcula elnewBalancedel remitente (ambos best-effort, no bloqueantes).- Corrección:
myGiftablePostsno 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/trackPromotionClickahora están en vivo y conectados —PostCard.tsxlos dispara para publicaciones patrocinadas activas, así que las impresiones/clics se incrementan de verdad.estimatePromotionReaches 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:
createPostPromotionrecibeCreatePostPromotionInput;updatePostPromotionrecibeUpdatePostPromotionInput; pausar/reanudar/cancelar y aprobar/rechazar de administrador devuelven todos el objetoPostPromotionactualizado, no un envoltorio{ success }. - UI: la interfaz completa de promociones orientada al creador (
MyPromotionsPage.tsx+CreatePromotionModal.tsxen/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
PostviewerHasLiked/viewerHasSaved/viewerHasReposted/interactionCount/giftStatsagrupan el estado por espectador y los conteos directamente en las consultasfeed/userPosts/post, reemplazando los viajes redondos por tarjeta (PostCard.tsxrecurre 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). PostMediaobtuvofilterCss,objectFit, eisPreview;PostTagInput.mediaIndex/x/yahora son opcionales (etiquetas de personas sin posición).- Ejemplo corregido de
reactToComment: el argumento esreaction(noreactionType) y devuelve unBooleansimple; lee los conteos víapostComment(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
metadataenpost(migración20260722000000-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íaBrowseGate.tsxen/clipspara 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: truepara llamadas anónimas; bajopublicOnly,postAccessService.getByType('clip', …)filtravisibility='public',isPublished=true, y excluye a creadores de cuentas privadas. - Corrección: el flujo dedicado de creación de clips no está conectado —
CreateClipModal.tsxexiste pero ninguna ruta lo monta (el FAB de subida se eliminó deClipsPage.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 enlive-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/storyViewerCountestá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);deleteLiveCommentestá 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
updateScheduledPosty 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. UnDateTimePickercompartido tipo calendario (enpackages/ui) reemplaza el antiguo selector datetime-local. - Colaboradores: la superficie GraphQL de
PostCollaborator(inviteCollaborator,acceptCollaboration,postCollaborators,collaborationStats, …) ahora está completamente conectada y documentada;CollaboratorsModal.tsxahora llama de verdad ainviteCollaborator/removeCollaborator/bulkInviteCollaborators(corregido en un ciclo posterior — ver Colaboradores en publicaciones, referencia técnica), y aceptar/rechazar enCollaborationInvitesPage.tsxsigue 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:
DynamicWatermarkahora 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,hashtagStatsahora están correctamente documentados como consumidos porHashtagPage.tsx.PostCard.tsxcambió alinkifyText(), que convierte en enlaces tanto#hashtagscomo@mencionesen los pies de foto.- Bug documentado: el SDL de
removeTagdeclarauserId/contentType/contentIdpero el resolver desestructura untagIdque nunca se proporciona, así que cada llamada falla; los métodos dementions-tags.manager.jssiguen siendo stubs simulados. La ruta real de eliminación de menciones esremoveMention(post-mention.manager.js#deleteMention). - Correcciones de rutas:
hashtag.access-service.js→data-access-services/post/; resolvers de menciones/etiquetas →user-mentions-tags.resolver.js.
Mensajería
Traducción en el chat (nuevo)
- GraphQL: nuevas consultas
messageTranslations,myTranslationSettingy mutaciónsetMessageTranslation, además de almacenamientoConversationParticipant.translationEnabled/preferredLanguage(migración20260721160000-add-chat-translation). - El backend es agnóstico de proveedor —
message-translation.service.jselige 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.tscambia el estado del mensaje areadyMessageBubble.tsxrenderiza el doble check azul. - Mensajes que desaparecen / expiran (
setMessageExpiration) conectados víauseChatActions.tsy el menú contextual del mensaje;updateConversationSettingsmapea 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@mencionesse analizan en filasMessageMentional crearcreateMessage(aún no hay consumidor en el frontend para las consultas de menciones); flujo de enlace de invitación respaldado porconversation-invite.manager.js. - El ejemplo de código del enum
MessageTypese corrigió para incluirlinkypost. - Corrección de ruta:
message.access-service.jsvive bajodata-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" deSuggestedUsers.tsx. Las tarjetas de usuarios sugeridos ahora muestran una etiqueta "Seguido por [nombre] + N más". - GraphQL: nuevos campos
User.followContext(MutualFollowContext) yUser.isFollowing; nueva mutacióndismissSuggestion(userId)(persistida en la tabladismissed_suggestion) usada por el botón X por tarjeta deRecommendationsSection.tsx;usersYouMayKnow/similarUsersestá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, incluyendonearbyUsers, es alcanzable desde la UI (corregido en un ciclo posterior — esta documentación antes decía que las pestañas estaban ocultas yactiveTabfijado);hideFromSuggestionsse llama desde este componente y desdeAccountPrivacyPage.tsx. /searchsin 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.jsdesapareció — los resolvers de contactos se dividieron enuser-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(noisUsernameAvailable/isEmailAvailable);importContactsrecibeinput: ContactImportInput!;syncContactsno recibe argumentos; la mutación real de invitación esinviteContacts(emails). Las sugerencias de contactos (contactSuggestions) devuelven el tipoContactSuggestionsResponse(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;validateFieldRealtimeahora 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
dateOfBirthes 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.jsya no existen — el resolver se dividió en ~15 archivos por área de responsabilidad fusionados automáticamente porgraphql/resolvers.js; el servicio de acceso se movió adata-access-services/user/.- Grandes correcciones de GraphQL:
Interestsahora es un simpleUser.interests: [String!]actualizado víaupdateInterests; la consulta de permisos de etiquetas estagPermissions(entradaJSON!en bruto); trending/online se renombraron agetTrendingUsers/getOnlineUsers;followUsersiempre sigue de inmediato (el punto de entrada de cuenta privada essendFollowRequest); firmas de apelaciones de restricción corregidas;deleteAccountImmediately/bulkMuteUsersdocumentadas.
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:
getFavoritesStatssolo calculatotalFavorites(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ó adata-access-services/user/. - Publicaciones guardadas ahora se organizan por colección —
mySavedPostsdesapareció, reemplazado porcollectionPosts(collectionId, …). Cada usuario obtiene una colección predeterminada "Guardado" creada automáticamente y no eliminable (SavedCollection.isDefault, migración20260715090000de un ciclo anterior);deleteCollectionrechaza la predeterminada. Nueva UI:SaveToCollectionModal,SavedPage.tsx(/settings/saved),DiscoverCollectionsPage.tsx(/settings/discover-collections). - Ubicación:
nearbyUsersahora 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 sonsetupTwoFactor/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 devuelverequiresTwoFactor/twoFactorToken/twoFactorMethod. regenerateBackupCodes,myPasskeys,deletePasskeyahora están conectados en Settings → Security.TwoFactorSetupResponseobtuvoqrCodeSvg(SVG en línea seguro para CSP) yotpauthUrl.- El puntero de documentación de Apple Sign-In se redirigió del eliminado
APPLE_SIGNIN_IMPLEMENTATION.mda las variablesAPPLE_*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 antiguouser.resolver.jsse dividió en resolversuser-two-factor-auth/user-security-alerts/user-sessions/user-authentication. - Bugs de sesión documentados:
terminateAllSessionses un alias derevokeAllOtherSessions(no cierra sesión en el dispositivo que llama);loginHistorylanza un error (desajuste de forma);sessionsTerminatedsiempre resuelvenull(el manager devuelverevoked_count);getCurrentSessiondevuelve datos simulados fijos. - Gestión de cuenta:
getActivityHeatmapahora se visualiza como un mapa de calor de contribuciones estilo GitHub en Settings → Activity insights.deleteAccountes un finalizador de período de gracia (rechaza a menos que ya esté enpending_deletiony haya pasado la fecha programada);deleteAccountImmediatelyes el bypass real.setAllNotificationsya 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 enumNotificationTypecreció de 8 a 34 valores; las notificaciones push ahora son agnósticas de proveedor (FCM víaservices/push-notification/), con VoIP para llamadas todavía en APNs real (voip-push.service.js). El servicio de acceso a datos se movió adata-access-services/user/. - La exportación de datos ahora está completamente implementada: una tabla
data_export(migración20260719080000, ciclo anterior),myDataExports/downloadDataExport/processDataExport, una suscripcióndataExportStatusChanged, yDownloadDataPage.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ón20260719030000, ciclo anterior) con páginas de configuración reales.InsightsAndToolsPageahora 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_visitsahora está completamente conectado (alternancia de privacidad → barrera derecordVisit), 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/verifyPasswordStepUprespaldado por una ventana de Redis de 15 minutos enstep-up-auth.service.js) y una nueva mutaciónverifyPasswordForModerators(password). - Nuevo registro de auditoría: modelo
AccountModeratorAction+account-moderator-action.access-service.js+ consultamoderatorActivity, y atribuciónMessage.viaModerator(migración20260721150000-expand-account-moderator). - Corrección:
canStoryestá definido pero no se aplica en ningún lugar —createStoryes una mutación separada que nunca aceptaactAsUserId. El flujo "Comentar como" de comentar en nombre del propietario vive enPostModal.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 tablaadmin_passkey, migración20260721140000-create-admin-passkey). - Restablecimiento de contraseña:
adminRequestPasswordReset(sin autenticar, siempre reporta éxito para evitar la enumeración de correos) yadminResetPassword(consume un token de un solo uso) conectados a las nuevas páginas de administrador/forgot-passwordy/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 tablaauto_moderation_rule, aplicado del lado del servidor por un nuevoservices/auto-moderation.service.jsllamado desdepost.manager.jsal crear una publicación. - Correcciones:
adminEscalateReport/adminDismissReportse eliminaron (usaadminReviewReportconESCALATE/REJECT);content-moderation.resolver.js/.type.jsse movieron bajoadmin/;ContentModerationDetails.nsfwScoreahora tiene un resolver real (solo publicaciones);toxicityScoresiempre es null;adminGetContentModerationHistorydevuelve[](faltafindByContent). - 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.retentionRateahora 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;adminGetActivityLogahora combinauser_moderation_logs+content_moderation_logs. - Verificación de identidad (administración):
adminPendingIdentityVerificationsdevuelve una lista plana[IdentityVerificationRequest!]!(paginada por limit/offset), yadminApproveIdentityVerification/adminRejectIdentityVerificationdevuelvenIdentityVerificationStatusResult!(elreasonde rechazo es obligatorio). - Pagos/desembolsos (administración): nuevos
adminGetCashout,adminGetPayoutProfile,adminSetPayoutAccountDisabled;adminGetCashoutscorregido para recibirfilters/limit/offsety devolver[CoinCashout!]!; las mutaciones de cancelar/completar/rechazar recibenidy devuelvenCoinCashout!. - Disputas de tienda:
adminOpenProductDisputes/adminResolveProductDisputeviven en el esquema de administración (/admin/graphql), protegidas porcontext.admin+MODERATE_CONTENT; los valores deProductDisputeResolutionsonresolved_refund(revierte las monedas, no restaura el stock) /resolved_denied. - Moderación de usuarios:
adminGetSuspendedUsers/adminGetBannedUsers/adminGetUserSuspensions/adminGetUserWarningsse 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 unFeedbackPage.tsxreal; las herramientas de revisión de administración (adminGetFeedback/adminGetFeedbackStats/adminUpdateFeedbackStatus/adminSetFeedbackPriority/adminRespondToFeedback, protegidas porMANAGE_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íaUserFeedbackVote(migración20260718140000, 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ón | Agrega | Área |
|---|---|---|
20260721140000-create-admin-passkey | Tabla admin_passkey (credenciales WebAuthn para el inicio de sesión de administrador) | Passkeys de administrador |
20260721150000-expand-account-moderator | can_comment / can_manage_comments / can_story / can_react en account_moderator; nueva tabla de auditoría account_moderator_action | Moderadores de cuenta |
20260721160000-add-chat-translation | translation_enabled / preferred_language en conversation_participant; nueva tabla de caché message_translation | Traducción del chat |
20260721170000-create-subscription-offer | Tablas subscription_offer + subscription_offer_redemption (pruebas / descuentos) | Ofertas de suscripción |
20260721180000-add-is-preview-to-post-media | post_media.is_preview | Vistas previas gratuitas por foto |
20260721190000-create-platform-wallet | Tabla platform_wallet (sembrada con una fila platform) | Billetera de monedas de la plataforma |
20260721200000-add-original-coin-price-to-post | post.original_coin_price | Precios con descuento en publicaciones pagas |
20260722000000-add-metadata-to-post | post.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.
| Variable | Propósito | Predeterminado |
|---|---|---|
LIBRETRANSLATE_URL | URL base de LibreTranslate (su presencia selecciona este proveedor) | — |
LIBRETRANSLATE_API_KEY | Clave de API opcional de LibreTranslate | — |
DEEPL_API_KEY | Clave de autenticación de DeepL (selecciona DeepL) | — |
DEEPL_API_URL | URL base de la API de DeepL | https://api-free.deepl.com |
GOOGLE_TRANSLATE_API_KEY | Clave de API de Google Translate v2 (selecciona Google) | — |
OPENAI_API_KEY | Clave de OpenAI (selecciona OpenAI) | — |
OPENAI_TRANSLATE_MODEL | Modelo de OpenAI para la traducción | gpt-4o-mini |
TRANSLATION_MAX_CHARS | Máximo de caracteres por mensaje traducido | 5000 |
TRANSLATION_MONTHLY_CHAR_LIMIT | Tope mensual de caracteres (0 = sin límite) | 0 |
TRANSLATION_HTTP_TIMEOUT_MS | Tiempo de espera por solicitud al proveedor | 8000 |
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.