Saltar al contenido principal

Configuración — Referencia técnica

← Volver a Configuración

Dónde vive esto

Backend

Frontend

Checklist de implementación técnica

  • privacySettings/updatePrivacySettings/resetPrivacySettings + extendedPrivacySettings/updateExtendedPrivacySettings — ambos pares de resolvers conectados; AccountPrivacyPage.tsx totalmente conectada a ambos
  • storyHiddenUsers/updateStoryHiddenUsers — conectado; UI dentro de StoryLivePage.tsx
  • mediaPreferences/updateMediaPreferences — conectado; reemplaza el antiguo enum de cadena único mediaQuality por 3 booleanos
  • sharingSettings, hiddenWords, followersInvitesSettings + mutations — cada uno conectado de forma independiente; los canales de invitación de FollowersAndInvitesPage.tsx más allá de "compartir enlace" son filas solo de UI marcadas como "próximamente"
  • accountStandingStatus — conectado, lee los valores reales de user.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 en i18next
  • timeManagementSettings/usageStats/updateTimeManagementSettings/recordUsageHeartbeat — conectado de extremo a extremo, incluido el cliente de heartbeat
  • archivedPosts/recentlyDeletedPosts/archivePost/unarchivePost/restorePost/permanentlyDeletePost — conectado; depende de Post.isArchived + el soft-delete paranoid de 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 par approveTag/rejectTag antiguo, no relacionado e inconcluso en el mismo tipo Mutation, 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.configured refleja 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 modelo UserRestriction (tabla user_restriction, restrictor_id/restricted_id). Aplicado en post-comment.manager.js junto con Limitar interacciones.
  • Limitar interacciones — completamente conectado (interaction-limit.type.js / interaction-limit.resolver.js / interaction-limit.manager.js): myInteractionLimit, setInteractionLimit; persistido en user.limit_interactions_enabled/limit_interactions_scope/limit_interactions_until (migración 20260719030000-add-interaction-limits-to-user.js).
  • Información y herramientas — InsightsAndToolsPage.tsx está 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 en post-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 codeconnectCrosspostAccount(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

RutaComponenteDescripción
/settings/profileEditProfilePageNombre, nombre de usuario, biografía, avatar, foto de portada
/settings/account-privacyAccountPrivacyPageCuenta privada, visibilidad, menciones, actividad, interruptores de privacidad extendida
/settings/tags-and-mentionsTagsAndMentionsPageQuién puede mencionarte/etiquetarte, requisito de aprobación de etiquetas
/settings/tags-and-mentions/reviewTagReviewPageAprobar/rechazar etiquetas pendientes de tu revisión
/settings/tagsTagsScreenPageCuadrícula de Etiquetado (publicaciones en las que estás etiquetado) + pestaña de Etiquetas pendientes
/settings/friends-activityFriendsActivityPageVisibilidad de tu actividad en el feed de Amigos
/settings/followers-and-invitesFollowersAndInvitesPageConfirmar seguidores automáticamente, invitar amigos
/settings/close-friendsCloseFriendsPageGestión de la lista de Amigos cercanos
/settings/favoritesFavoritesPageCuentas favoritas — ver Favoritos
/settings/blockedBlockedPageCuentas bloqueadas
/settings/restricted-accountsRestrictedAccountsPageLista de cuentas restringidas con opción de quitar restricción por fila — conectada a restrictedAccounts/unrestrictAccount (restrict.resolver.js)
/settings/limit-interactionsLimitInteractionsPageActivar/desactivar la limitación de interacciones con expiración automática opcional — conectada a myInteractionLimit/setInteractionLimit (interaction-limit.resolver.js)
/settings/mutedMutedPageCuentas silenciadas
/settings/hidden-wordsHiddenWordsPagePalabras/frases ocultas automáticamente de comentarios y solicitudes de mensaje
/settings/story-liveStoryLivePageAudiencia predeterminada de Historias/En vivo, compartir ubicación, lista "Ocultar historia a"
/settings/account-statusAccountStatusPageResumen de situación de moderación (distinto del estado del ciclo de vida de la cuenta)
/settings/accountAccountManagementPageDesactivar/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

RutaComponenteDescripción
/settings/messages-and-story-repliesMessagesAndStoryRepliesPageCentro que enlaza a las cuatro subpáginas siguientes
/settings/messages-and-story-replies/message-requestsMessageRequestsPageQuién puede enviarte mensajes, invitaciones grupales, ocultar solicitudes no deseadas
/settings/messages-and-story-replies/story-repliesStoryRepliesPageQuién puede responder a tus historias
/settings/messages-and-story-replies/read-receiptsReadReceiptsPageInterruptor para mostrar confirmaciones de lectura
/settings/messages-and-story-replies/nudity-protectionNudityProtectionPageInterruptor de protección contra desnudos en mensajes directos
/settings/notificationsNotificationsSettingsPageCentro de notificaciones — pausar todas, modo de descanso, lista por categoría — ver Notificaciones
/settings/notifications/[category]NotificationCategoryPageInterruptores granulares para una categoría de notificación
/settings/notifications/sleep-modeSleepModePageVentana programada de no molestar
/settings/time-managementTimeManagementPageGráfica de uso diario, límite diario, acceso directo al modo de descanso
/settings/archiveArchivePagePublicaciones/historias/instants archivados
/settings/recently-deletedRecentlyDeletedPagePublicaciones eliminadas de forma suave, recuperables durante 30 días
/settings/activityYourActivityPageCentro "Tu actividad"
/settings/activity/interactionsInteractionsPageMe gusta, comentarios, republicaciones
/settings/activity/watch-historyWatchHistoryPagePublicaciones/clips vistos recientemente
/settings/activity/recent-searchesRecentSearchesPageHistorial de búsqueda
/settings/activity/link-historyLinkHistoryPageEnlaces externos en los que hiciste clic recientemente
/settings/activity/suggested-contentSuggestedContentPagePreferencias de contenido "No me interesa"

Descubrimiento, contenido y soporte

RutaComponenteDescripción
/settings/languageLanguagePageIdioma de la app + preferencias de traducción de Reels
/settings/content-preferencesContentPreferencesPageCentro que enlaza a Contenido sugerido, Accesibilidad, Calidad de medios, Idioma
/settings/accessibilityAccessibilityPageReducir movimiento / reproducción automática de videos (local al dispositivo, localStorage)
/settings/commentsCommentsPageInterruptor para permitir comentarios (refleja el campo que también está en Privacidad de la cuenta)
/settings/sharingShareSettingsPagePermitir volver a compartir tus publicaciones en las Historias de otros
/settings/crosspostingCrosspostingPageConectar/desconectar/alternar X, Facebook, TikTok — conectada a crosspost.resolver.js (condicionada por proveedor a variables de entorno OAuth del servidor)
/settings/download-dataDownloadDataPageExportación de datos RGPD — ver Exportación de datos
/settings/device-permissionsDevicePermissionsPageCámara/Micrófono/Ubicación/Notificaciones vía Permissions API; Contactos/Fotos solo informativo
/settings/app-website-permissionsAppAndWebsitePermissionsPageApps/sitios web conectados
/settings/family-centerFamilyCenterPage⚠️ Stub de UI
/settings/insights-toolsInsightsAndToolsPageGanancias 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/sessionsSessionsSettingsPageSesiones activas — ver Sesiones
/settings/securitySecuritySettingsPage2FA, contraseña — ver Seguridad
/settings/verificationVerificationRequestPageSolicitar una insignia de verificación — ver Verificación
/settings/subscriptionsSubscriptionsSettingsPageGestionar suscriptores/suscripciones
/payments(PaymentsPage)Enlazada desde el centro como "Tus pedidos y recaudaciones de fondos"
/settings/more-info-supportMoreInfoSupportPageAyuda, Centro de privacidad, Términos, enlaces a Acerca de y Estado de la cuenta
/settings/aboutAboutPageVersió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 previewsEnabled que 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ó a suggestPostsToNonFollowers. El interruptor "Previews" real (mostrar vistas previas del contenido de los mensajes en las notificaciones) es ExtendedNotificationSettings.previewsEnabled, respaldado por un campo notification_preview que 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:

PantallaRuta
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.

ComponenteBackendNotas
EditProfilePage
AccountPrivacyPageConfiguración de privacidad base + extendida
TagsAndMentionsPage
TagReviewPage
TagsScreenPagePestañ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
FavoritesPageConectado 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
RestrictedAccountsPageConectado a restrictedAccounts/unrestrictAccount
LimitInteractionsPageConectado a myInteractionLimit/setInteractionLimit
MutedPage
HiddenWordsPage
StoryLivePageIncluye la lista "Ocultar historia a" (storyHiddenUsers)
AccountStatusPage
AccountManagementPage
MessagesAndStoryRepliesPageCentro, sin query propia
MessageRequestsPage
StoryRepliesPage
ReadReceiptsPage
NudityProtectionPage
NotificationsSettingsPage
NotificationCategoryPage
SleepModePage
TimeManagementPage
ArchivePage
RecentlyDeletedPage
YourActivityPageCentro
InteractionsPage
WatchHistoryPage
RecentSearchesPage
LinkHistoryPage
SuggestedContentPage
LanguagePageIncluye traducciones de Reels
ContentPreferencesPageCentro, enlaza a otras páginas
AccessibilityPagelocal al dispositivolocalStorage, no sincronizado con el backend
CommentsPage
ShareSettingsPage
CrosspostingPageConectada a la API de publicación cruzada; conexión por proveedor condicionada a variables de entorno OAuth del servidor
DownloadDataPage
DevicePermissionsPageAPIs del navegadorCámara/Micrófono/Ubicación/Notificaciones vía Permissions API; Contactos/Fotos solo informativo
AppAndWebsitePermissionsPage
FamilyCenterPage❌ stub
InsightsAndToolsPageGanancias del creador/analítica de suscriptores, no herramientas para cuentas supervisadas — conectada a myPostPurchaseEarnings/subscriberCount/subscriberRetention
SessionsSettingsPage
SecuritySettingsPage
VerificationRequestPage
SubscriptionsSettingsPage
MoreInfoSupportPageEnlaces estáticos
AboutPageEstática
components/LanguageSelector.tsxSelector de idioma
components/ThemeToggle.tsxlocal al dispositivoModo oscuro / claro
components/UsageHeartbeat.tsxImpulsa 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 }
}