Saltar al contenido principal

Búsqueda y Descubrimiento — Referencia técnica

← Volver a Búsqueda y Descubrimiento

Dónde vive esto

Backend

Frontend

Un bug preexistente del backend corregido al conectar esta página

Las mutations de user-bulk-operations.resolver.js estaban escritas para desestructurar { input } de sus argumentos de GraphQL y leer input.userIds / input.requestIds / input.action. Pero el schema real (user.type.js / social-actions.type.js) declara cada mutation en lote con argumentos planos (userIds: [ID!]!, followerIds: [ID!]!, requestIds: [ID!]!, action: String!) — no hay ningún envoltorio input en ninguna parte del schema. Llamar a cualquiera de estas mutations tal como estaban escritas originalmente habría lanzado Cannot read properties of undefined (reading 'userIds') de inmediato. Por separado, la forma de retorno del manager ({ success, message, results: { success, failed, skipped, errors } }) no coincidía ni con BulkOperationResponse ni con BulkOperationResult, cuyos campos planos y no-nulos (successCount!, failedCount!, total!, processedCount!) habrían lanzado un error de GraphQL "Cannot return null for non-nullable field". Ambos problemas se corrigieron directamente en el resolver (ver los helpers mapToBulkOperationResponse / mapToBulkOperationResult en el archivo) como parte de construir el frontend de esta página — no era una función preexistente "terminada" como asumían notas anteriores.

getBulkOperationHistory y getBulkOperationStatus llaman a userManager.getBulkOperationHistory / userManager.getBulkOperationStatus, pero ninguno de los dos métodos existe en bulk-operations.manager.js (solo existe getBulkOperationLimits) — no hay ninguna capa de persistencia de seguimiento de operaciones detrás de estos dos campos ni de cancelBulkOperation. Se dejaron tal cual (siguen lanzando un error limpio de GraphQL si alguna vez se llaman) pero el frontend deliberadamente no los llama; las acciones en lote se ejecutan de forma síncrona y reportan su resultado en línea en vez de con una barra de progreso.

hideFromSuggestions ahora está conectada al interruptor "Mostrar en Explorar"

Corregido en esta sesión: el schema (user-presence.type.js) declara desde hace tiempo la única mutation hideFromSuggestions(enabled: Boolean!): Boolean!, resuelta (user-search-discovery.resolver.js) hacia search-discovery.manager.js#hideFromSuggestions/#showInSuggestions según el valor de enabled — pero ninguna pantalla del frontend la llamaba. El interruptor Configuración → Privacidad → Mostrar en Explorar de AccountPrivacyPage.tsx antes persistía mediante la mutation genérica updatePrivacySettings, que cambia la misma bandera subyacente appear_in_suggestions pero no invalida los resultados de recomendaciones en caché de quien llama. El manejador del interruptor (handleToggleShowInExplore) ahora llama en su lugar a la mutation dedicada hideFromSuggestions (enabled: !nextValue, ya que el enabled de la mutation significa "ocúltame" mientras que el propio valor del interruptor significa "muéstrame"), así que desactivarlo también invalida la caché de inmediato en lugar de esperar a que expire de forma natural.

Checklist de implementación técnica

  • searchUsers — conectado; tres puntos de llamada distintos, la barra de búsqueda de Explorar, y PublicSearchPage.tsx para visitantes sin sesión
  • interestSuggestions(limit, interests) + updateInterests(interests) — sugerencias basadas en intereses; user.interests text[] (atributo del modelo interestTags), coincidencia por superposición con getUsersByInterests (migración 20260721120000)
  • suggestedUsers — conectado; SuggestedUsers.tsx, ahora con datos de prueba social isFollowing + followContext
  • usersYouMayKnow / similarUsers — conectado; RecommendationsSection.tsx y DiscoverPeoplePage.tsx
  • getRecommendedUsers / getTrendingUsers / nearbyUsers — conectado; RecommendationsSection.tsx (las cinco pestañas — Recomendados/Quizás conozcas/Similares/Tendencia/Cercanos — ahora se renderizan mediante una barra de pestañas real, corregido en esta sesión, antes solo se renderizaba getRecommendedUsers porque activeTab estaba fijado) y DiscoverPeoplePage.tsx (todas se renderizan)
  • dismissSuggestion(userId) — conectado; el botón de descartar (X) por tarjeta de RecommendationsSection.tsx
  • hideFromSuggestionsCorregido en esta sesión: el interruptor Configuración → Privacidad → Mostrar en Explorar de AccountPrivacyPage.tsx ahora llama a la mutation dedicada hideFromSuggestions en lugar del campo genérico updatePrivacySettings — ver la nota más arriba
  • bulkFollowUsers / bulkUnfollowUsers / bulkBlockUsers / bulkRemoveFollowers / bulkProcessFollowRequests — conectado; BulkFollowerActionsPage.tsx (bug del resolver del backend corregido como parte de este trabajo — ver arriba)
  • getBulkOperationHistory / getBulkOperationStatus / cancelBulkOperation — ahora implementado: una tabla bulk_operation + modelo + servicio de acceso; cada acción en lote registra conteos/estado reales, el estado/historial leen las filas reales, y cancelar devuelve false para operaciones (síncronas) ya finalizadas

Búsqueda

query SearchUsers($query: String!, $limit: Int, $offset: Int, $filters: JSON) {
searchUsers(query: $query, limit: $limit, offset: $offset, filters: $filters) {
id username profilePicture isVerified
}
}

query SuggestedUsers($limit: Int, $offset: Int, $category: String) {
suggestedUsers(limit: $limit, offset: $offset, category: $category) {
id username profilePicture
}
}

Recomendaciones

query UsersYouMayKnow($limit: Int, $offset: Int) {
usersYouMayKnow(limit: $limit, offset: $offset) {
peopleYouMayKnow {
user { id username }
matchScore
reasons { type mutualConnectionsCount confidence }
}
total
hasMore
}
}

query SimilarUsers($limit: Int, $offset: Int) {
similarUsers(limit: $limit, offset: $offset) {
similarUsers { user { id username } similarityScore sharedInterestScore activitySimilarity }
total
hasMore
}
}

query GetTrendingUsers($limit: Int, $timeframe: String) {
getTrendingUsers(limit: $limit, timeframe: $timeframe) {
user { username }
trendingReason
}
}

query GetRecommendedUsers($limit: Int, $offset: Int, $algorithm: String) {
getRecommendedUsers(limit: $limit, offset: $offset, algorithm: $algorithm) { username }
}

query NearbyUsers($radiusKm: Float, $limit: Int) {
nearbyUsers(radiusKm: $radiusKm, limit: $limit) { user { id username } city distanceKm }
}

mutation DismissSuggestion($userId: ID!) { dismissSuggestion(userId: $userId) }

mutation HideFromSuggestions($enabled: Boolean!) { hideFromSuggestions(enabled: $enabled) }

dismissSuggestion(userId) (declarada en user-recommendations.type.js, resuelta por user-recommendations.resolver.js hacia search-discovery.manager.js#dismissSuggestion) elimina a ese usuario de las sugerencias de "personas que quizás conozcas" de quien llama, de ahí en adelante; RecommendationsSection.tsx también oculta la tarjeta del lado del cliente de inmediato, antes de que la mutation se resuelva.

hideFromSuggestions(enabled: true) es un interruptor sobre uno mismo — oculta a quien lo llama de las listas de sugerencias de otros usuarios (persiste appear_in_suggestions: false); no oculta a un usuario específico de las propias sugerencias de quien lo llama, y no afecta a searchUsers (la búsqueda directa no se filtra por esta bandera hoy). Corregido en esta sesión: el interruptor Configuración → Privacidad → Mostrar en Explorar de AccountPrivacyPage.tsx ahora llama a esta mutation (ver la nota más arriba) — antes ninguna pantalla del frontend lo hacía.

suggestedUsers y las tarjetas de sugerencias en otros lugares también pueden seleccionar User.isFollowing y User.followContext { count sample { id username } }followContext devuelve datos de prueba social ("Seguido por...") sobre cuántas cuentas que sigue quien mira también siguen a este usuario, más un usuario de muestra para la etiqueta. Ambos campos se resuelven como null para quienes llaman de forma anónima o al ver el propio perfil.

Acciones en lote sobre relaciones

mutation BulkFollowUsers($userIds: [ID!]!) {
bulkFollowUsers(userIds: $userIds) { success message successCount failedCount total errors { userId reason } }
}
mutation BulkUnfollowUsers($userIds: [ID!]!) {
bulkUnfollowUsers(userIds: $userIds) { success message successCount failedCount total errors { userId reason } }
}
mutation BulkBlockUsers($userIds: [ID!]!) {
bulkBlockUsers(userIds: $userIds) { success message processedCount failedCount }
}
mutation BulkRemoveFollowers($followerIds: [ID!]!) {
bulkRemoveFollowers(followerIds: $followerIds) { success message processedCount failedCount }
}
mutation BulkProcessFollowRequests($requestIds: [ID!]!, $action: String!) {
bulkProcessFollowRequests(requestIds: $requestIds, action: $action) { success message processedCount failedCount }
}

action para bulkProcessFollowRequests es 'accept' o 'reject', aplicado a cada id en requestIds — el manager en sí soporta acciones mixtas por solicitud ({requestId, action}[]), pero el campo de GraphQL solo expone la forma de una sola acción para muchos ids, así que el resolver expande requestIds a ese arreglo del lado del servidor.