Configuración — Referencia técnica
Dónde vive esto
Backend
apps/backend/graphql/types/extended-privacy-settings.type.js+resolvers/extended-privacy-settings.resolver.js— interruptores de privacidad extendida, story-hidden-usersapps/backend/graphql/types/extended-notification-settings.type.js+ resolver — interruptores de notificaciones extendidasapps/backend/graphql/types/media-preferences.type.js,sharing-settings.type.js,hidden-words.type.js,followers-invites.type.js,account-standing.type.js,reels-translation.type.js,time-management.type.js,content-archive.type.js,activity-log.type.js,content-preference.type.js,search-history.type.js,link-history.type.js,watch-history.type.js,tag-review.type.js— cada uno es un par de tipo/resolver dedicado y completamente nuevo (ver Arquitectura de GraphQL para entender por qué este código base evita extender nombres de tipos compartidos)apps/backend/managers/user-managers/privacy-settings.manager.js— respalda casi todos los campos extendidos de privacidad/medios/compartir/palabras ocultas/seguidores-invitaciones mediante un único blob JSONBsettings.privacyapps/backend/managers/user-managers/notification-settings.manager.js,usage-tracking.manager.js,reels-translation.manager.js,content-preference.manager.js,activity-log.manager.js,search-history.manager.js,link-tracking.manager.js;managers/post-managers/content-archive.manager.js,watch-history.manager.jsapps/backend/database/models/PostMention.js— llevastatus(approved/pending/rejected) para el flujo de Etiquetas/Revisión pendiente
Frontend
apps/frontend-nextjs/src/page-components/SettingsPage.tsx— estructura de configuración,menuItemsde la barra lateralapps/frontend-nextjs/src/page-components/settings/— cada pantalla de configuración, un componente por archivo (lista completa en Componentes de frontend más abajo)apps/frontend-nextjs/src/app/settings/— un envoltorio de ruta delgado por cada pantalla, en paralelo a la tabla de rutas de abajoapps/frontend-nextjs/src/components/UsageHeartbeat.tsx— montado dentro deNavigation.tsx, impulsa las estadísticas de uso de Gestión del tiempo
Checklist de implementación técnica
-
privacySettings/updatePrivacySettings/resetPrivacySettings+extendedPrivacySettings/updateExtendedPrivacySettings— ambos pares de resolvers conectados;AccountPrivacyPage.tsxtotalmente conectada a ambos -
storyHiddenUsers/updateStoryHiddenUsers— conectado; UI dentro deStoryLivePage.tsx -
mediaPreferences/updateMediaPreferences— conectado; reemplaza el antiguo enum de cadena únicomediaQualitypor 3 booleanos -
sharingSettings,hiddenWords,followersInvitesSettings+ mutations — cada uno conectado de forma independiente; los canales de invitación deFollowersAndInvitesPage.tsxmás allá de "compartir enlace" son filas solo de UI marcadas como "próximamente" -
accountStandingStatus— conectado, lee los valores reales deuser.isRestricted/user_warnings/user_moderation_logs -
notificationSettings/updateNotificationSettings+extendedNotificationSettings/updateExtendedNotificationSettings— ambos conectados -
reelsTranslationSettings/updateReelsTranslationSettings— conectado; clave JSONB separada (settings.reels_translation) de la configuración de idioma de UI eni18next -
timeManagementSettings/usageStats/updateTimeManagementSettings/recordUsageHeartbeat— conectado de extremo a extremo, incluido el cliente de heartbeat -
archivedPosts/recentlyDeletedPosts/archivePost/unarchivePost/restorePost/permanentlyDeletePost— conectado; depende dePost.isArchived+ el soft-deleteparanoidde Sequelize -
myLikedPosts/myComments/myReposts/watchHistory/searchHistory/linkHistory/notInterestedContent+ mutations — conectado; "Respuestas con stickers"/"Reseñas" quedan intencionalmente sin exponer (no tienen modelo que las respalde) -
taggedPosts/pendingTagReviews/approvePostTag/rejectPostTag— conectado; con nombre distinto de un parapproveTag/rejectTagantiguo, no relacionado e inconcluso en el mismo tipoMutation, para evitar una colisión de nombre de campo - Publicación cruzada — completamente conectada (
crosspost.type.js/crosspost.resolver.js):myCrosspostConnections,crosspostAuthorizeUrl,connectCrosspostAccount,disconnectCrosspostAccount,setCrosspostEnabled. El OAuth hacia cada proveedor está condicionado a variables de entorno del servidor (X_CLIENT_ID/FACEBOOK_APP_ID/TIKTOK_CLIENT_KEY…);CrosspostConnection.configuredrefleja si la app del proveedor está configurada. Compartición automática al publicar. - Restringir — completamente conectado (
restrict.type.js/restrict.resolver.js/restrict.manager.js):restrictedAccounts,isAccountRestricted,restrictAccount,unrestrictAccount; persistido mediante el modeloUserRestriction(tablauser_restriction,restrictor_id/restricted_id). Aplicado enpost-comment.manager.jsjunto con Limitar interacciones. - Limitar interacciones — completamente conectado (
interaction-limit.type.js/interaction-limit.resolver.js/interaction-limit.manager.js):myInteractionLimit,setInteractionLimit; persistido enuser.limit_interactions_enabled/limit_interactions_scope/limit_interactions_until(migración20260719030000-add-interaction-limits-to-user.js). - Información y herramientas —
InsightsAndToolsPage.tsxestá conectada, pero a la analítica de monetización para creadores en lugar de las herramientas para cuentas supervisadas que sugiere el nombre:myPostPurchaseEarnings,subscriberCount,subscriberRetention(campos preexistentes enpost-purchase.resolver.js/user-subscription.resolver.js). - Centro familiar — stub solo de frontend, no existe resolver/tipo/manager alguno
API de publicación cruzada
Permite a un usuario vincular cuentas sociales externas (X, Facebook, TikTok) y compartir automáticamente las publicaciones nuevas en ellas. Las apps de OAuth se configuran del lado del servidor; un proveedor solo aparece como conectable cuando sus credenciales están presentes (configured: true).
type CrosspostConnection {
provider: String! # x | facebook | tiktok
connected: Boolean! # a token is stored (account linked)
enabled: Boolean! # auto-share to this provider is on
providerUsername: String
configured: Boolean! # the server has this provider's OAuth app set up
}
extend type Query {
myCrosspostConnections: [CrosspostConnection!]!
crosspostAuthorizeUrl(provider: String!, redirectUri: String!): String # null if not configured
}
extend type Mutation {
connectCrosspostAccount(provider: String!, code: String!, redirectUri: String!): Boolean! # finish OAuth
disconnectCrosspostAccount(provider: String!): Boolean!
setCrosspostEnabled(provider: String!, enabled: Boolean!): Boolean! # toggle auto-share
}
Flujo: llamar a crosspostAuthorizeUrl → enviar al usuario al consentimiento OAuth del proveedor → el callback devuelve un code → connectCrosspostAccount(provider, code, redirectUri) guarda el token → setCrosspostEnabled alterna la compartición automática. Variables de entorno: X_CLIENT_ID/X_CLIENT_SECRET, FACEBOOK_APP_ID/FACEBOOK_APP_SECRET, TIKTOK_CLIENT_KEY/TIKTOK_CLIENT_SECRET (ver configuración de entorno).
Restringir y Limitar interacciones
Dos herramientas distintas de "moderación suave" controladas por el usuario, ambas aplicadas en post-comment.manager.js (un comentario se oculta al dueño de la publicación si quien comenta está restringido o limitado por el dueño). Ninguna está relacionada con bloquear/silenciar ni con la moderación de administración.
# Restrict — persisted via the UserRestriction model (user_restriction table)
type RestrictedAccount { user: User! restrictedAt: DateTime! }
query RestrictedAccounts($limit: Int, $offset: Int) { restrictedAccounts(limit: $limit, offset: $offset) { user { id username } restrictedAt } }
query IsAccountRestricted($userId: ID!) { isAccountRestricted(userId: $userId) }
mutation RestrictAccount($userId: ID!) { restrictAccount(userId: $userId) }
mutation UnrestrictAccount($userId: ID!) { unrestrictAccount(userId: $userId) }
# Limit interactions — persisted on user.limit_interactions_enabled/_scope/_until
type InteractionLimit { enabled: Boolean! scope: String! until: DateTime active: Boolean! }
query MyInteractionLimit { myInteractionLimit { enabled scope until active } }
mutation SetInteractionLimit($enabled: Boolean!, $scope: String, $durationDays: Int) {
setInteractionLimit(enabled: $enabled, scope: $scope, durationDays: $durationDays) { enabled until active }
}
durationDays establece until en ahora + N días (omítelo para que sea indefinido); myInteractionLimit.active es true solo mientras enabled esté activo y aún no haya expirado. El único scope aplicado hoy es non_followers.
Información y herramientas
InsightsAndToolsPage.tsx (ruta /settings/insights-tools) no es una herramienta para cuentas supervisadas pese a su nombre — es analítica de monetización para creadores, que reutiliza campos preexistentes en lugar de agregar otros nuevos:
query MyPostPurchaseEarnings { myPostPurchaseEarnings { totalSales totalRevenue totalEarnings platformFees } }
query SubscriberCount($creatorId: ID!) { subscriberCount(creatorId: $creatorId) }
query SubscriberRetention($months: Int) {
subscriberRetention(months: $months) { month newSubscribers churned activeAtEnd churnRate retentionRate }
}
myPostPurchaseEarnings proviene de post-purchase.resolver.js; subscriberCount/subscriberRetention provienen de user-subscription.resolver.js. La página renderiza tiles de estadísticas de ventas/ganancias de publicaciones exclusivas, un conteo de suscriptores activos y una gráfica de barras mensual de retención/abandono, más un botón de exportación a CSV.
Rutas
Cuenta y privacidad
| Ruta | Componente | Descripción |
|---|---|---|
/settings/profile | EditProfilePage | Nombre, nombre de usuario, biografía, avatar, foto de portada |
/settings/account-privacy | AccountPrivacyPage | Cuenta privada, visibilidad, menciones, actividad, interruptores de privacidad extendida |
/settings/tags-and-mentions | TagsAndMentionsPage | Quién puede mencionarte/etiquetarte, requisito de aprobación de etiquetas |
/settings/tags-and-mentions/review | TagReviewPage | Aprobar/rechazar etiquetas pendientes de tu revisión |
/settings/tags | TagsScreenPage | Cuadrícula de Etiquetado (publicaciones en las que estás etiquetado) + pestaña de Etiquetas pendientes |
/settings/friends-activity | FriendsActivityPage | Visibilidad de tu actividad en el feed de Amigos |
/settings/followers-and-invites | FollowersAndInvitesPage | Confirmar seguidores automáticamente, invitar amigos |
/settings/close-friends | CloseFriendsPage | Gestión de la lista de Amigos cercanos |
/settings/favorites | FavoritesPage | Cuentas favoritas — ver Favoritos |
/settings/blocked | BlockedPage | Cuentas bloqueadas |
/settings/restricted-accounts | RestrictedAccountsPage | Lista de cuentas restringidas con opción de quitar restricción por fila — conectada a restrictedAccounts/unrestrictAccount (restrict.resolver.js) |
/settings/limit-interactions | LimitInteractionsPage | Activar/desactivar la limitación de interacciones con expiración automática opcional — conectada a myInteractionLimit/setInteractionLimit (interaction-limit.resolver.js) |
/settings/muted | MutedPage | Cuentas silenciadas |
/settings/hidden-words | HiddenWordsPage | Palabras/frases ocultas automáticamente de comentarios y solicitudes de mensaje |
/settings/story-live | StoryLivePage | Audiencia predeterminada de Historias/En vivo, compartir ubicación, lista "Ocultar historia a" |
/settings/account-status | AccountStatusPage | Resumen de situación de moderación (distinto del estado del ciclo de vida de la cuenta) |
/settings/account | AccountManagementPage | Desactivar/eliminar la cuenta — ver Administración de la cuenta |
/profile?tab=saved | (ProfilePage, pestaña saved) | Publicaciones guardadas — el centro de Configuración enlaza aquí en lugar de duplicar la funcionalidad |
Mensajes y actividad
| Ruta | Componente | Descripción |
|---|---|---|
/settings/messages-and-story-replies | MessagesAndStoryRepliesPage | Centro que enlaza a las cuatro subpáginas siguientes |
/settings/messages-and-story-replies/message-requests | MessageRequestsPage | Quién puede enviarte mensajes, invitaciones grupales, ocultar solicitudes no deseadas |
/settings/messages-and-story-replies/story-replies | StoryRepliesPage | Quién puede responder a tus historias |
/settings/messages-and-story-replies/read-receipts | ReadReceiptsPage | Interruptor para mostrar confirmaciones de lectura |
/settings/messages-and-story-replies/nudity-protection | NudityProtectionPage | Interruptor de protección contra desnudos en mensajes directos |
/settings/notifications | NotificationsSettingsPage | Centro de notificaciones — pausar todas, modo de descanso, lista por categoría — ver Notificaciones |
/settings/notifications/[category] | NotificationCategoryPage | Interruptores granulares para una categoría de notificación |
/settings/notifications/sleep-mode | SleepModePage | Ventana programada de no molestar |
/settings/time-management | TimeManagementPage | Gráfica de uso diario, límite diario, acceso directo al modo de descanso |
/settings/archive | ArchivePage | Publicaciones/historias/instants archivados |
/settings/recently-deleted | RecentlyDeletedPage | Publicaciones eliminadas de forma suave, recuperables durante 30 días |
/settings/activity | YourActivityPage | Centro "Tu actividad" |
/settings/activity/interactions | InteractionsPage | Me gusta, comentarios, republicaciones |
/settings/activity/watch-history | WatchHistoryPage | Publicaciones/clips vistos recientemente |
/settings/activity/recent-searches | RecentSearchesPage | Historial de búsqueda |
/settings/activity/link-history | LinkHistoryPage | Enlaces externos en los que hiciste clic recientemente |
/settings/activity/suggested-content | SuggestedContentPage | Preferencias de contenido "No me interesa" |
Descubrimiento, contenido y soporte
| Ruta | Componente | Descripción |
|---|---|---|
/settings/language | LanguagePage | Idioma de la app + preferencias de traducción de Reels |
/settings/content-preferences | ContentPreferencesPage | Centro que enlaza a Contenido sugerido, Accesibilidad, Calidad de medios, Idioma |
/settings/accessibility | AccessibilityPage | Reducir movimiento / reproducción automática de videos (local al dispositivo, localStorage) |
/settings/comments | CommentsPage | Interruptor para permitir comentarios (refleja el campo que también está en Privacidad de la cuenta) |
/settings/sharing | ShareSettingsPage | Permitir volver a compartir tus publicaciones en las Historias de otros |
/settings/crossposting | CrosspostingPage | Conectar/desconectar/alternar X, Facebook, TikTok — conectada a crosspost.resolver.js (condicionada por proveedor a variables de entorno OAuth del servidor) |
/settings/download-data | DownloadDataPage | Exportación de datos RGPD — ver Exportación de datos |
/settings/device-permissions | DevicePermissionsPage | Cámara/Micrófono/Ubicación/Notificaciones vía Permissions API; Contactos/Fotos solo informativo |
/settings/app-website-permissions | AppAndWebsitePermissionsPage | Apps/sitios web conectados |
/settings/family-center | FamilyCenterPage | ⚠️ Stub de UI |
/settings/insights-tools | InsightsAndToolsPage | Ganancias del creador + analítica de suscriptores (ventas de publicaciones exclusivas, conteo de suscriptores, retención/abandono) — pese al nombre de la ruta, no es una herramienta para cuentas supervisadas |
/settings/sessions | SessionsSettingsPage | Sesiones activas — ver Sesiones |
/settings/security | SecuritySettingsPage | 2FA, contraseña — ver Seguridad |
/settings/verification | VerificationRequestPage | Solicitar una insignia de verificación — ver Verificación |
/settings/subscriptions | SubscriptionsSettingsPage | Gestionar suscriptores/suscripciones |
/payments | (PaymentsPage) | Enlazada desde el centro como "Tus pedidos y recaudaciones de fondos" |
/settings/more-info-support | MoreInfoSupportPage | Ayuda, Centro de privacidad, Términos, enlaces a Acerca de y Estado de la cuenta |
/settings/about | AboutPage | Versión de la app, enlaces legales |
Configuración de privacidad
El tipo base PrivacySettings (declarado en user.type.js) cubre los campos de privacidad originales:
type PrivacySettings {
isPrivate: Boolean!
showActivityStatus: Boolean!
allowMessagesFrom: String! # "everyone" | "followers" | "nobody"
allowTagsFrom: String!
showFollowers: Boolean!
showFollowing: Boolean!
allowComments: Boolean!
showInExplore: Boolean!
showInSearch: Boolean!
allowStorySharing: Boolean!
saveStoryToArchive: Boolean!
shareLocation: Boolean!
allowGroupInvitesFrom: String!
}
query PrivacySettings {
privacySettings {
isPrivate showActivityStatus allowMessagesFrom allowTagsFrom
showFollowers showFollowing allowComments showInExplore
}
}
mutation UpdatePrivacySettings($input: PrivacySettingsInput!) {
updatePrivacySettings(input: $input) { isPrivate showActivityStatus }
}
mutation ResetPrivacySettings { resetPrivacySettings { settings { settingName value } } }
Todo interruptor de paridad con Instagram más reciente vive en un tipo separado ExtendedPrivacySettings en lugar de fusionarse en PrivacySettings — dos tipos de GraphQL con el mismo nombre antes se fusionaban silenciosamente por campo mediante @graphql-tools/merge cuando existía un PrivacySettingsInput duplicado, corrompiendo el esquema (ver Arquitectura de GraphQL). Todo tipo nuevo introducido sigue la regla de "nombre de tipo propio y completamente nuevo" para evitar repetir ese error.
type ExtendedPrivacySettings {
whoCanMention: String!
requireApprovalForTags: Boolean!
friendsActivityVisibility: String! # "followers_you_follow_back" | "no_one"
hideLikeAndShareCounts: Boolean!
suggestPostsToNonFollowers: Boolean! # renamed from a mislabeled "previewsEnabled" — see note below
locationHistory: Boolean!
allowPersonalization: Boolean!
hideNotificationPreviews: Boolean!
autoDeleteMessages: Boolean!
messageDeletionDays: Int!
hideUnwantedMessageRequests: Boolean!
storyRepliesFrom: String! # "followers_you_follow_back" | "no_one"
readReceiptsEnabled: Boolean!
nudityProtectionEnabled: Boolean!
}
query ExtendedPrivacySettings { extendedPrivacySettings { whoCanMention requireApprovalForTags suggestPostsToNonFollowers storyRepliesFrom readReceiptsEnabled nudityProtectionEnabled } }
mutation UpdateExtendedPrivacySettings($input: ExtendedPrivacySettingsInput!) { updateExtendedPrivacySettings(input: $input) { suggestPostsToNonFollowers } }
Corrección de nombre de "Previews": un pase anterior agregó un campo
previewsEnabledque significaba "sugerir mis publicaciones a cuentas que no me siguen" y lo etiquetó como "Previews" — eso no es lo que significa el ajuste real "Previews" de Instagram. Se renombró asuggestPostsToNonFollowers. El interruptor "Previews" real (mostrar vistas previas del contenido de los mensajes en las notificaciones) esExtendedNotificationSettings.previewsEnabled, respaldado por un camponotification_previewque ya existía en el manager de notificaciones pero que nunca se había expuesto hasta ahora.
Privacidad de historias — usuarios ocultos
query StoryHiddenUsers { storyHiddenUsers { id username profilePicture } }
mutation UpdateStoryHiddenUsers($userIds: [ID!]!) { updateStoryHiddenUsers(userIds: $userIds) { id username } }
Calidad de medios
Reemplaza el antiguo enum de cadena único mediaQuality por tres booleanos independientes:
type MediaPreferences {
dataSaverCellular: Boolean! # reduce quality on cellular
uploadQualityHigh: Boolean! # upload in high vs. standard quality
hdrEnabled: Boolean! # upload/display HDR where supported
}
query MediaPreferences { mediaPreferences { dataSaverCellular uploadQualityHigh hdrEnabled } }
mutation UpdateMediaPreferences($input: MediaPreferencesInput!) { updateMediaPreferences(input: $input) { dataSaverCellular } }
Compartir, Palabras ocultas, Seguidores e invitaciones
# Sharing — resharing this account's *posts* into other users' Stories
# (distinct from PrivacySettings.allowStorySharing, which governs resharing this account's *Stories*)
query SharingSettings { sharingSettings { allowResharingToStories } }
mutation UpdateSharingSettings($input: SharingSettingsInput!) { updateSharingSettings(input: $input) { allowResharingToStories } }
# Hidden Words — free-form word/phrase list, filters comments and message requests
query HiddenWords { hiddenWords }
mutation UpdateHiddenWords($words: [String!]!) { updateHiddenWords(words: $words) }
# Followers and invites
query FollowersInvitesSettings { followersInvitesSettings { autoConfirmFollowers } }
mutation UpdateFollowersInvitesSettings($input: FollowersInvitesSettingsInput!) { updateFollowersInvitesSettings(input: $input) { autoConfirmFollowers } }
Situación de la cuenta
Un resumen de situación de moderación, deliberadamente una query distinta de la ya existente accountStatus (que reporta el estado del ciclo de vida isActive/isDeactivated/isDeleted, usado por Administración de la cuenta). restrictionLevel se deriva de dos fuentes: suspended/banned provienen del campo real user.accountStatus (establecido por suspendUser/banUser), mientras que limited proviene de las columnas separadas y en su mayoría inactivas user.isRestricted/restrictedUntil — este documento antes atribuía toda la query únicamente a isRestricted/restrictedUntil; corregido. reason y suspendedUntil se agregaron en este pase (solo se completan mientras la cuenta está realmente suspendida/baneada) para impulsar el flujo de apelación de restricción — ver Verificación → Apelaciones de restricción.
type AccountStandingStatus {
hasViolations: Boolean!
restrictionLevel: String! # "none" | "limited" | "suspended" | "banned"
isEligibleForMonetization: Boolean! # moderation-standing signal only, not full payout eligibility
recentStrikes: Int!
lastActionAt: DateTime
reason: String # suspension/ban reason - only populated while suspended/banned
suspendedUntil: DateTime # only populated while suspended/banned
}
query AccountStandingStatus {
accountStandingStatus { hasViolations restrictionLevel recentStrikes lastActionAt reason suspendedUntil }
}
AccountStatusPage.tsx también consulta myLatestAppeal junto con accountStandingStatus en la misma solicitud, para poder renderizar el estado de apelación correcto (formulario / pendiente / denegada) sin un segundo viaje de ida y vuelta — ver Verificación → Apelaciones de restricción para esa query y el lado de revisión de administración.
Notificaciones
Ver Notificaciones para la API base de notificationSettings. El centro de Configuración también expone una superficie mucho más amplia, ExtendedNotificationSettings, que cubre granularidad por categoría (historias, en vivo, seguimientos, menciones, etiquetas, mensajes, grupos, reacciones, actividad de amigos, cumpleaños, sugerencias, tendencias, interacciones de publicaciones/historias), horas silenciosas, un modo de un toque "Pausar todas" / "Solo mensajes", y el interruptor "Previews" real:
query ExtendedNotificationSettings {
extendedNotificationSettings {
pauseAll messagesOnlyMode
quietHoursEnabled quietHoursStart quietHoursEnd
notifyFollows notifyMentions notifyTags notifyMessages notifyGroupMessages
notifyStoryViews notifyStoryLikes notifyFriendActivity notifyLiveVideos
likesNotificationScope taggedPhotoNotificationScope
previewsEnabled
}
}
mutation UpdateExtendedNotificationSettings($input: ExtendedNotificationSettingsInput!) {
updateExtendedNotificationSettings(input: $input) { pauseAll previewsEnabled }
}
Idioma y traducciones de Reels
El selector de idioma (i18next, EN/ES) no cambió. Las preferencias de traducción de Reels son nuevas y se guardan por separado (JSONB settings.reels_translation, no ligado al idioma de UI de la app):
type ReelsTranslationSettings {
reelsAutoTranslateText: Boolean!
reelsAutoTranslateVoice: Boolean!
reelsPreferredLanguages: [String!]!
}
query ReelsTranslationSettings { reelsTranslationSettings { reelsAutoTranslateText reelsAutoTranslateVoice reelsPreferredLanguages } }
mutation UpdateReelsTranslationSettings($input: ReelsTranslationSettingsInput!) { updateReelsTranslationSettings(input: $input) { reelsAutoTranslateText } }
El selector de idioma guarda la elección del idioma de la UI de la app en localStorage y la aplica al instante. Idiomas admitidos: inglés (en), español (es).
Gestión del tiempo
type TimeManagementSettings { dailyLimitMinutes: Int sleepModeEnabled: Boolean! sleepModeStart: String! sleepModeEnd: String! }
type UsageStats { days: Int! dailyAverageSeconds: Int! todaySeconds: Int! series: [UsageDayEntry!]! }
query TimeManagementSettings { timeManagementSettings { dailyLimitMinutes sleepModeEnabled sleepModeStart sleepModeEnd } }
query UsageStats($days: Int) { usageStats(days: $days) { dailyAverageSeconds todaySeconds series { date totalSeconds } } }
mutation UpdateTimeManagementSettings($input: TimeManagementSettingsInput!) { updateTimeManagementSettings(input: $input) { dailyLimitMinutes } }
mutation RecordUsageHeartbeat($seconds: Int!) { recordUsageHeartbeat(seconds: $seconds) }
recordUsageHeartbeat es llamado cada 30 segundos por components/UsageHeartbeat.tsx, montado dentro de Navigation.tsx mientras la pestaña está visible y el usuario está autenticado — esto es lo que impulsa la gráfica de uso diario (renderizada con Recharts).
Archivo y Eliminados recientemente
Reutiliza el tipo Post de GraphQL existente — las Historias y los Instants son simplemente filas de Post con type: "story" / type: "clip", y archivar es una bandera booleana (isArchived) en lugar de un modelo separado.
query ArchivedPosts($type: String, $limit: Int, $offset: Int) { archivedPosts(type: $type, limit: $limit, offset: $offset) { id type createdAt } }
query RecentlyDeletedPosts($limit: Int, $offset: Int) { recentlyDeletedPosts(limit: $limit, offset: $offset) { id deletedAt } }
mutation ArchivePost($postId: ID!) { archivePost(postId: $postId) { id isArchived } }
mutation UnarchivePost($postId: ID!) { unarchivePost(postId: $postId) { id isArchived } }
mutation RestorePost($postId: ID!) { restorePost(postId: $postId) { id deletedAt } }
mutation PermanentlyDeletePost($postId: ID!){ permanentlyDeletePost(postId: $postId) }
recentlyDeletedPosts depende del soft-delete paranoid: true de Sequelize en Post — las publicaciones permanecen recuperables durante 30 días antes de la eliminación permanente. Post.deletedAt se expone en el tipo de GraphQL específicamente para impulsar la cuenta regresiva de "N días restantes" en esta pantalla.
Tu actividad
query MyLikedPosts($limit: Int, $offset: Int) { myLikedPosts(limit: $limit, offset: $offset) { id } }
query MyComments($limit: Int, $offset: Int) { myComments(limit: $limit, offset: $offset) { id text } }
query MyReposts($limit: Int, $offset: Int) { myReposts(limit: $limit, offset: $offset) { id } }
query WatchHistory($limit: Int, $offset: Int) { watchHistory(limit: $limit, offset: $offset) { id post { id } viewedAt } }
mutation RecordPostView($postId: ID!) { recordPostView(postId: $postId) }
mutation ClearWatchHistory { clearWatchHistory }
query SearchHistory($limit: Int) { searchHistory(limit: $limit) { id query createdAt } }
mutation RecordSearch($query: String!, $targetType: String, $targetId: ID) { recordSearch(query: $query, targetType: $targetType, targetId: $targetId) { id } }
mutation DeleteSearchHistoryItem($id: ID!) { deleteSearchHistoryItem(id: $id) }
mutation ClearSearchHistory { clearSearchHistory }
query LinkHistory($limit: Int) { linkHistory(limit: $limit) { id url createdAt } }
mutation RecordLinkClick($url: String!, $sourceType: String, $sourceId: ID) { recordLinkClick(url: $url, sourceType: $sourceType, sourceId: $sourceId) { id } }
mutation ClearLinkHistory { clearLinkHistory }
query NotInterestedContent($limit: Int, $offset: Int) { notInterestedContent(limit: $limit, offset: $offset) { id targetType targetId } }
mutation SetContentPreference($targetType: String!, $targetId: ID!, $preference: String!) { setContentPreference(targetType: $targetType, targetId: $targetId, preference: $preference) { id } }
mutation RemoveContentPreference($targetType: String!, $targetId: ID!) { removeContentPreference(targetType: $targetType, targetId: $targetId) }
"Respuestas con stickers" y "Reseñas" (dos filas que Instagram muestra dentro de Interacciones) no tienen ningún modelo que las respalde en el código base y deliberadamente no están expuestas — el frontend muestra un estado honesto de "no disponible" en lugar de simular datos.
Etiquetas
Distinto de Etiquetas y menciones (controles de privacidad sobre quién puede etiquetarte). Esta pantalla muestra la cuadrícula real de contenido etiquetado:
query PendingTagReviews { pendingTagReviews { id user { username } media { url } positionX positionY } }
mutation ApprovePostTag($mentionId: ID!) { approvePostTag(mentionId: $mentionId) }
mutation RejectPostTag($mentionId: ID!) { rejectPostTag(mentionId: $mentionId) }
query TaggedPosts($userId: ID!, $limit: Int, $offset: Int) { taggedPosts(userId: $userId, limit: $limit, offset: $offset) { id } }
approvePostTag/rejectPostTag deliberadamente no se llaman approveTag/rejectTag — esos nombres ya están ocupados por un par de mutations de "permisos de etiquetas genéricos" más antiguo, no relacionado y nunca terminado, en el mismo tipo Mutation. Las filas de PostMention llevan un status (approved | pending | rejected); solo las menciones approved aparecen en taggedPosts.
Pantallas stub
Estas pantallas son solo de UI — no existe tipo, resolver ni manager de GraphQL alguno para ellas en el backend. Se renderizan de forma honesta (visibles pero deshabilitadas, o claramente etiquetadas como "próximamente") en lugar de simular un estado funcional:
| Pantalla | Ruta |
|---|---|
| Centro familiar | /settings/family-center |
Cuentas restringidas, Limitar interacciones, Publicación cruzada e Información y herramientas antes eran stubs, pero ahora están completamente conectadas — ver el checklist de arriba.
Componentes de frontend
Todos los componentes viven bajo page-components/settings/ salvo que se indique lo contrario.
| Componente | Backend | Notas |
|---|---|---|
EditProfilePage | ✅ | |
AccountPrivacyPage | ✅ | Configuración de privacidad base + extendida |
TagsAndMentionsPage | ✅ | |
TagReviewPage | ✅ | |
TagsScreenPage | ✅ | Pestañas de cuadrícula de Etiquetado + Etiquetas pendientes |
FriendsActivityPage | ✅ | |
FollowersAndInvitesPage | ✅ (parcial) | La confirmación automática es real; los canales de invitación más allá de "compartir enlace" son "próximamente" |
CloseFriendsPage | ✅ | |
FavoritesPage | ✅ | Conectado a favoriteUsers/addToFavorites/etc., respaldado por el modelo real UserFavorite (tabla user_favorites) — mutualFavorites/recentAdditions/mostFavoritedUser de getFavoritesStats siguen siendo marcadores fijos, ver Favoritos |
BlockedPage | ✅ | |
RestrictedAccountsPage | ✅ | Conectado a restrictedAccounts/unrestrictAccount |
LimitInteractionsPage | ✅ | Conectado a myInteractionLimit/setInteractionLimit |
MutedPage | ✅ | |
HiddenWordsPage | ✅ | |
StoryLivePage | ✅ | Incluye la lista "Ocultar historia a" (storyHiddenUsers) |
AccountStatusPage | ✅ | |
AccountManagementPage | ✅ | |
MessagesAndStoryRepliesPage | — | Centro, sin query propia |
MessageRequestsPage | ✅ | |
StoryRepliesPage | ✅ | |
ReadReceiptsPage | ✅ | |
NudityProtectionPage | ✅ | |
NotificationsSettingsPage | ✅ | |
NotificationCategoryPage | ✅ | |
SleepModePage | ✅ | |
TimeManagementPage | ✅ | |
ArchivePage | ✅ | |
RecentlyDeletedPage | ✅ | |
YourActivityPage | — | Centro |
InteractionsPage | ✅ | |
WatchHistoryPage | ✅ | |
RecentSearchesPage | ✅ | |
LinkHistoryPage | ✅ | |
SuggestedContentPage | ✅ | |
LanguagePage | ✅ | Incluye traducciones de Reels |
ContentPreferencesPage | — | Centro, enlaza a otras páginas |
AccessibilityPage | local al dispositivo | localStorage, no sincronizado con el backend |
CommentsPage | ✅ | |
ShareSettingsPage | ✅ | |
CrosspostingPage | ✅ | Conectada a la API de publicación cruzada; conexión por proveedor condicionada a variables de entorno OAuth del servidor |
DownloadDataPage | ✅ | |
DevicePermissionsPage | APIs del navegador | Cámara/Micrófono/Ubicación/Notificaciones vía Permissions API; Contactos/Fotos solo informativo |
AppAndWebsitePermissionsPage | ✅ | |
FamilyCenterPage | ❌ stub | |
InsightsAndToolsPage | ✅ | Ganancias del creador/analítica de suscriptores, no herramientas para cuentas supervisadas — conectada a myPostPurchaseEarnings/subscriberCount/subscriberRetention |
SessionsSettingsPage | ✅ | |
SecuritySettingsPage | ✅ | |
VerificationRequestPage | ✅ | |
SubscriptionsSettingsPage | ✅ | |
MoreInfoSupportPage | — | Enlaces estáticos |
AboutPage | — | Estática |
components/LanguageSelector.tsx | ✅ | Selector de idioma |
components/ThemeToggle.tsx | local al dispositivo | Modo oscuro / claro |
components/UsageHeartbeat.tsx | ✅ | Impulsa las estadísticas de uso de Gestión del tiempo |
Restricciones de descubrimiento
hideFromSuggestions elimina al usuario de las listas de "Personas que quizás conozcas" / "Usuarios sugeridos".
mutation HideFromSuggestions($enabled: Boolean!) { hideFromSuggestions(enabled: $enabled) }
Recomendaciones de privacidad
getPrivacyRecommendations devuelve una lista clasificada de mejoras sugeridas (p. ej., "Activar 2FA", "Establecer la cuenta como privada"), cada una con un indicador isApplied.
query PrivacyRecommendations {
getPrivacyRecommendations { id recommendationType title description priority isApplied }
}