Hashtags y Tendencias — Referencia técnica
← Volver a Hashtags y Tendencias
Dónde vive esto
Backend
apps/backend/graphql/resolvers/hashtag.resolver.js— resuelvehashtag,hashtagByName,searchHashtags,trendingHashtags,hashtagStatsapps/backend/graphql/types/hashtag.type.js— tipo SDLHashtagy esquema de consultasapps/backend/managers/post-managers/hashtag.manager.js— cálculo del puntaje de tendencia, lógica de búsqueda y consultaapps/backend/data-access-services/post/hashtag.access-service.js— acceso a la base de datos de hashtags (trasladado adata-access-services/post/junto con los demás servicios de acceso relacionados con publicaciones)apps/backend/graphql/resolvers/user-mentions-tags.resolver.js— implementausersMentionedBy,usersIMentioned,removeTag,removeMention(eluser.resolver.jsmonolítico al que este documento apuntaba antes ya no existe — los resolvers de usuario se dividieron en muchos archivos)- Verificado pero vale la pena señalarlo:
post-hashtag.access-service.js(ahoradata-access-services/post/post-hashtag.access-service.js) yconversation-hashtag.access-service.js(ahoradata-access-services/message/conversation-hashtag.access-service.js) están conectados —post.manager.js#createPostenlaza cada hashtag extraído mediante el primero (este documento decía antes que ninguno era llamado por ningún manager — corregido). Por separado, los métodos subyacentes deremoveTagenmentions-tags.manager.js—getUsersMentionedBy/getUsersIMentioned— siguen siendo stubs TODO/simulados que devuelven datos vacíos sin importar la entrada (removeTagen sí fue corregido en esta sesión — ver la checklist abajo).myMentions,mentionsInContent,mentionUserytagUserya no existen en absoluto en el esquema (fueron eliminados — ver la checklist abajo), así que ignora cualquier referencia a ellos a continuación como algo histórico.
Frontend
apps/frontend-nextjs/src/page-components/HomePage.tsx— la superposición de búsqueda móvil consultasearchHashtagsjunto con la búsqueda de usuariosapps/frontend-nextjs/src/app/hashtag/[tag]/page.tsx+HashtagPage.tsx— consultapostsByHashtagpara renderizar la cuadrícula de publicaciones de un hashtag, además detrendingHashtags(la fila de chips "Tendencias") yhashtagByName/hashtagStats(el total de publicaciones / personas distintas del encabezado)apps/frontend-nextjs/src/components/PostCard.tsx— renderiza los pies de foto mediantelinkifyText()(enlib/linkifyHashtags.tsx), que enlaza tanto los segmentos#taga/hashtag/[tag]como las menciones@usernameal perfil del usuario; la exportación anteriorlinkifyHashtags(), exclusiva para hashtags, del mismo archivo todavía la usanPublicPostCard.tsx,PublicPostViewer.tsxyClipsPage.tsx
Checklist de implementación técnica
-
trendingHashtags— conectado enhashtag.resolver.js, llama ahashtag.manager.js#getTrendingHashtags; consumido por la fila de chips "Tendencias" deHashtagPage.tsx(este documento decía antes que no había ningún consumidor en el frontend — corregido) -
searchHashtags— conectado enhashtag.resolver.js; consumido por la superposición de búsqueda móvil deHomePage.tsx - Extracción automática de hashtags al crear una publicación —
post.manager.js:217llama ahashtagManager.extractAndCreateHashtags; combinado congetByHashtag/postsByHashtag, esto sustenta el feed real de/hashtag/[tag](HashtagPage.tsx) y los enlaces de hashtag en los que se puede hacer clic quePostCard.tsxrenderiza mediantelinkifyText— ver Publicaciones y Feed -
hashtagByName/hashtagStats— conectado enhashtag.resolver.js; consumido por el encabezado deHashtagPage.tsx(total de publicaciones / personas distintas) — la propia cuadrícula de publicaciones del feed del hashtag sigue usandopostsByHashtag, no estos dos (este documento decía antes que ninguno tenía un consumidor en el frontend — corregido) -
PostHashtag(publicaciones) yConversationHashtag(dentro del chat) están ambos conectados —post.manager.js#createPostenlaza cada hashtag extraído mediantepost-hashtag.access-service.js;message.manager.js#createMessageextrae los#hashtagsdel texto del mensaje, los enlaza a la conversación medianteconversation-hashtag.access-service.js(sin duplicados) e incrementa el uso;hashtag.access-service.js#getTrendingcuenta los enlaces recientes de hashtags de conversación junto con los de publicaciones, de modo que los hashtags dentro del chat también aparecen en las tendencias. -
usersMentionedBy/usersIMentioned— conectado enuser-mentions-tags.resolver.js; consumido porAjustes → Historial de menciones(MentionHistoryPage.tsx, en/settings/mentions) — pero los métodos subyacentes dementions-tags.manager.jstodavía son stubs TODO/simulados que siempre devuelven un resultado vacío, así que las pestañas se renderizan y consultan correctamente pero nunca muestran datos reales de menciones -
removeTag— Corregido en esta sesión, dos errores: (1) el resolver antes desestructuraba un argumentotagIdinexistente, por lo que toda llamada fallaba sin importar la entrada — ahora lee los argumentos realesuserId/contentType/contentIdque declara el SDL y devuelve el booleano real del manager. (2)mentions-tags.manager.js#removeTagera un stub (console.log, sin escritura en la base de datos) — ahora realiza una eliminación real dePostMention, restringida a contenido de tipoPOST, y autorizada solo al usuario etiquetado o al propietario de la publicación (obtenido mediantepostAccessService.findById); cualquier otro usuario recibepermissions.cannot_remove_tag.removeMention(mentionId)sigue siendo una mutación separada y más simple para la misma fila dePostMention, respaldada porpost-mention.manager.js#deleteMention. -
myMentions/mentionsInContent/mentionUser/tagUser— eliminados del SDL dementions-tags.type.js. Eran campos muertos sin resolver y sin consumidor en el frontend, que duplicaban conceptos ya cubiertos por el sistema real de etiquetado basado enPostMention(tocar para etiquetar +@mencionesen el pie de foto, registradas mediantepost-mention.access-service.js), así que se eliminaron en lugar de implementarse, para mantener el esquema honesto.
Modelo Hashtag
| Campo | Descripción |
|---|---|
name | Hashtag original (mayúsculas/minúsculas mixtas) |
normalizedName | Versión normalizada en minúsculas |
usageCount | Conteo total de uso |
trendingScore | Puntaje de tendencia calculado |
lastUsedAt | Timestamp del uso más reciente |
Queries
trendingHashtags devuelve los N hashtags principales ordenados por trendingScore, que combina la velocidad reciente con el uso general. Úsalo para la fila de tendencias de la página Explorar.
searchHashtags autocompleta una cadena parcial de hashtag — llámalo en cada pulsación de tecla mientras el usuario escribe # en el compositor de publicaciones. Los resultados se ordenan por usageCount.
hashtagByName obtiene un solo hashtag por su nombre exacto — útil al navegar a una página de hashtag (p. ej. /hashtag/photography).
hashtagStats devuelve el número total de publicaciones y usuarios únicos que han usado un hashtag — se muestra en la página de detalle del hashtag.
query TrendingHashtags($limit: Int) {
trendingHashtags(limit: $limit) {
id name usageCount trendingScore lastUsedAt
}
}
query SearchHashtags($query: String!, $limit: Int) {
searchHashtags(query: $query, limit: $limit) { id name usageCount }
}
query HashtagByName($name: String!) {
hashtagByName(name: $name) { id name usageCount trendingScore }
}
query HashtagStats($hashtagId: ID!) {
hashtagStats(hashtagId: $hashtagId) { totalPosts totalUsers }
}
Hashtags en conversaciones
Los mensajes también pueden llevar hashtags. La tabla ConversationHashtag los indexa para la búsqueda dentro del chat.
Menciones de usuarios
myMentions, mentionsInContent, mentionUser y tagUser no existen en el esquema — fueron eliminados (ver la checklist arriba); ignora cualquier referencia anterior a ellos.
removeMention elimina una fila específica de PostMention y es la mutación que sí funciona de extremo a extremo — puede llamarla tanto el propietario de la publicación como el usuario mencionado (post-mention.manager.js#deleteMention). removeTag (argumentos userId/contentType/contentId — contentType debe ser POST, el único tipo de contenido para el que hoy están modeladas las etiquetas) ahora también funciona de extremo a extremo: elimina la fila PostMention correspondiente, restringido al usuario etiquetado o al propietario de la publicación — ver la checklist arriba para los dos errores corregidos en esta sesión.
# Remove a specific mention (post owner or the mentioned user can call this)
mutation RemoveMention($mentionId: ID!) {
removeMention(mentionId: $mentionId)
}
# Tagged user or the post's owner can call this
mutation RemoveTag($userId: ID!, $contentType: String!, $contentId: ID!) {
removeTag(userId: $userId, contentType: $contentType, contentId: $contentId)
}
Etiquetas de contenido
ContentTag es una etiqueta gestionada por el sistema, aplicada automáticamente mediante clasificación por IA — distinta de los hashtags creados por los usuarios. Las etiquetas describen la categoría temática de publicaciones y conversaciones.
| Campo | Descripción |
|---|---|
name | Etiqueta (p. ej. photography, sports) |
category | Grupo de categoría de la etiqueta |
usageCount | Número de elementos etiquetados |
confidenceAvg | Puntaje promedio de confianza de la IA (0–1) |
Las etiquetas se aplican a publicaciones mediante PostContentTag y a conversaciones mediante ConversationContentTag. Impulsan el motor de intereses de contenido — consulta Publicaciones y Feed para ver cómo se rastrea la afinidad del usuario por cada etiqueta.
Historial de menciones
usersMentionedBy muestra a qué usuarios suele mencionar una cuenta dada — útil para una vista analítica o de relaciones. usersIMentioned muestra a quién ha mencionado recientemente el usuario actual. Ambas son consumidas por Ajustes → Historial de menciones (MentionHistoryPage.tsx), pero su implementación subyacente en mentions-tags.manager.js sigue siendo un stub TODO/simulado que siempre devuelve una lista vacía — las pestañas están completamente conectadas de extremo a extremo pero no mostrarán datos reales de menciones hasta que ese manager se implemente contra PostMention/MessageMention.
query UsersMentionedBy($userId: ID!, $limit: Int) {
usersMentionedBy(userId: $userId, limit: $limit) { id username }
}
query UsersIMentioned($limit: Int) {
usersIMentioned(limit: $limit) { id username }
}