Colaboradores de Publicaciones — Referencia Técnica
← Volver a Colaboradores de Publicaciones
Dónde vive esto
Backend
apps/backend/managers/post-managers/post-collaborator.manager.js— lógica de negocio para invitar/aceptar/rechazar/eliminar colaborador y estadísticas de colaboraciónapps/backend/data-access-services/post/post-collaborator.access-service.js— consultas a la base de datos dePostCollaboratorapps/backend/graphql/types/post-collaborator.type.js/apps/backend/graphql/resolvers/post-collaborator.resolver.js— el esquema de GraphQL y los resolvers que conectan el manager anterior con la API en vivo (se carga automáticamente en el esquema de la misma manera que cualquier otro archivographql/types/graphql/resolvers, medianteloadFilesSyncengraphql/typeDefs.js/graphql/resolvers.js)apps/backend/managers/post-managers/post.manager.js—getUserPosts/getFeedincorporan las publicaciones en las que quien llama/sus seguidos son coautores como colaborador aceptado (mediantepostCollaboratorAccessService.getAcceptedPostIdsForUsers), por lo que una publicación colaborativa aparece en la cuadrícula del perfil del colaborador y en los feeds de sus seguidores
La API descrita a continuación sí es accesible — inviteCollaborator, bulkInviteCollaborators, acceptCollaboration, rejectCollaboration, removeCollaborator, postCollaborators, myPendingCollaborations, collaborationStats e isCollaborator son todos campos de GraphQL en vivo.
Frontend
apps/frontend-nextjs/src/page-components/settings/CollaborationInvitesPage.tsx(montado en/settings/collaborations) — lista las invitaciones pendientes de quien llama (myPendingCollaborations), botones Aceptar/Rechazar (acceptCollaboration/rejectCollaboration), y un encabezado con 3 estadísticas (collaborationStats)apps/frontend-nextjs/src/components/PostOptionsMenu.tsxtiene una entrada "Manage collaborators" (solo para el propietario) que abreCollaboratorsModalconisOwneractivado. Corregido en esta sesión: el modal antes ya no se comunicaba en absoluto con la API de post-collaborator — consultabapost(id).taggedUsers(la función independiente de etiquetado por toque, respaldada porPostTag/PostMention) y simplemente listaba a esas personas, de solo lectura. Ahora está reconectado a la query realpostCollaborators(modo de gestión, activado cuando se pasa unpostIdsin una propusers), mostrando a cada colaborador con una etiqueta de estado pendiente/aceptado/rechazado. CuandoisOwnerestá activado, también renderiza un formulario de invitación (búsqueda de nombre de usuario reutilizando la querysearchUsersde etiquetar personas, que invocainviteCollaboratorpara una sola selección obulkInviteCollaboratorscuando se seleccionan varias) y una acción "Eliminar" por fila (removeCollaborator) — ambas restringidas al propietario, ya que solo la entrada exclusiva del propietario dePostOptionsMenupasaisOwner. La línea de solo lectura "Con @x y N más" dePostCard.tsxsigue pasandousersdirectamente (nopostId), así que esa ruta de visualización simple no se ve afectada y sigue sin consultarpostCollaborators.
Checklist de implementación técnica
- mutación
inviteCollaborator— implementada (post-collaborator.manager.js), conectada al esquema (post-collaborator.resolver.js), y ahora invocada desde el formulario de invitación deCollaboratorsModal(solo propietario). Corregido en esta sesión — antes solo accesible mediante GraphQL, sin llamador en el frontend - mutaciones
acceptCollaboration/rejectCollaboration— implementadas, conectadas, y usadas porCollaborationInvitesPage.tsx - mutación
removeCollaborator— implementada, conectada, y ahora invocada desde la acción "Eliminar" por fila deCollaboratorsModal(solo propietario). Corregido en esta sesión — antes sin llamador en el frontend - mutación
bulkInviteCollaborators— implementada, conectada, y ahora invocada desde el formulario de invitación deCollaboratorsModalcuando se selecciona más de un usuario. Corregido en esta sesión — antes sin llamador en el frontend - consultas
postCollaborators/myPendingCollaborations— ambas implementadas y conectadas;postCollaboratorsahora es invocada porCollaboratorsModalen modo de gestión (corregido en esta sesión — antes consultabapost(id).taggedUsersen su lugar), ymyPendingCollaborationssigue siendo usada porCollaborationInvitesPage.tsx - consulta
collaborationStats— implementada, conectada, y usada porCollaborationInvitesPage.tsx(estas son las propias estadísticas de quien llama a través de todas sus colaboraciones, no limitadas a una sola publicación) - consulta
isCollaborator— implementada y conectada; todavía sin llamador en el frontend - La publicación colaborativa aparece en los perfiles y feeds de todos los colaboradores —
getUserPosts/getFeeddepost.manager.jsincorporan las publicaciones de colaboradores aceptados mediantegetAcceptedPostIdsForUsers
Cómo funciona
El creador original de la publicación invita a uno o más colaboradores mediante inviteCollaborator/bulkInviteCollaborators, desde el formulario de invitación de CollaboratorsModal (solo propietario, abierto mediante la entrada "Manage collaborators" de PostOptionsMenu). Cada usuario invitado puede aceptar o rechazar la invitación desde Configuración → Invitaciones de colaboración. Una vez aceptada, la publicación aparece en la propia cuadrícula del perfil del colaborador y en los feeds de sus seguidores, tal como si él mismo la hubiera publicado. Eliminar a un colaborador (removeCollaborator, también solo propietario desde CollaboratorsModal) borra directamente la fila de colaboración en lugar de cambiarla a un estado "eliminado". Corregido en esta sesión: invitar, invitar en masa, eliminar y explorar la nómina ahora tienen interfaz de frontend real en CollaboratorsModal; antes solo la mitad de aceptar/rechazar de este flujo (CollaborationInvitesPage.tsx) la tenía, y el modal mismo mostraba datos de personas etiquetadas no relacionados en lugar de registros reales de colaboración.
Modelo de datos
PostCollaborator (apps/backend/database/models/PostCollaborator.js, tabla post_collaborator):
| Campo | Tipo | Descripción |
|---|---|---|
id | UUID | ID del registro de colaborador |
postId | UUID (columna post_id) | La publicación en la que se colabora |
userId | UUID (columna user_id) | El ID del usuario invitado/colaborador |
invitedBy | UUID (columna invited_by) | ID de usuario del propietario de la publicación que envió la invitación |
status | String(50) | pending, accepted, o rejected (por defecto pending; no existe un estado removed — eliminar borra la fila) |
createdAt / updatedAt | DateTime (columnas created_at/updated_at) | Marcas de tiempo estándar |
No existe una columna sharedToFeed, invitedAt, ni respondedAt — la visibilidad en el feed/perfil se deriva en el momento de la lectura a partir de status === 'accepted', y createdAt/updatedAt cubren el momento de la invitación/respuesta.
API de GraphQL
Invitar a un colaborador
inviteCollaborator envía una invitación a un usuario específico. Quien llama debe ser el propietario de la publicación (impuesto en el manager); invitarse a uno mismo, una publicación inexistente, o un usuario que ya está invitado/colaborando generan todos un error. La invitación entra en estado pending y crea una notificación collaboration_invite para el invitado.
mutation InviteCollaborator($postId: ID!, $userId: ID!) {
inviteCollaborator(postId: $postId, userId: $userId) {
id
status
createdAt
user { id username profilePicture }
}
}
Invitación masiva
bulkInviteCollaborators llama a inviteCollaborator una vez por cada userId; un fallo para un usuario (ya invitado, autoinvitación, etc.) se recopila en errors en lugar de abortar todo el lote, y success es false si alguna invitación falló.
mutation BulkInviteCollaborators($postId: ID!, $userIds: [ID!]!) {
bulkInviteCollaborators(postId: $postId, userIds: $userIds) {
success
invited
failed
errors { userId error }
}
}
Responder a una invitación
acceptCollaboration/rejectCollaboration permiten que el invitado (solo él) responda a su propia invitación pendiente. Aceptar establece status: accepted y notifica a quien invitó (collaboration_accepted); rechazar establece status: rejected y notifica a quien invitó (collaboration_rejected). Cualquiera de las dos llamadas falla si la invitación no está pendiente o si quien llama no es el invitado.
mutation AcceptCollaboration($collaborationId: ID!) {
acceptCollaboration(collaborationId: $collaborationId) { id status }
}
mutation RejectCollaboration($collaborationId: ID!) {
rejectCollaboration(collaborationId: $collaborationId) { id status }
}
Eliminar a un colaborador
removeCollaborator borra directamente la fila de colaboración. Solo el usuario que envió la invitación (invitedBy, es decir, el propietario de la publicación) puede hacer esto; notifica al colaborador eliminado (collaboration_removed).
mutation RemoveCollaborator($collaborationId: ID!) {
removeCollaborator(collaborationId: $collaborationId)
}
Consultar colaboradores / invitaciones
postCollaborators devuelve las filas de colaboración de una publicación (cualquier estado por defecto, o filtrado mediante status) — no hay verificación de propiedad en el resolver, por lo que cualquier llamador autenticado puede leerlo. myPendingCollaborations devuelve las propias invitaciones pendientes de quien llama. isCollaborator informa si quien llama es un colaborador aceptado en una publicación.
query PostCollaborators($postId: ID!) {
postCollaborators(postId: $postId, status: "accepted", limit: 20, offset: 0) {
id status createdAt
user { id username profilePicture }
}
}
query MyPendingCollaborations {
myPendingCollaborations {
id status createdAt
inviter { id username profilePicture }
post { id text }
}
}
query IsCollaborator($postId: ID!) {
isCollaborator(postId: $postId)
}
Estadísticas de colaboración
collaborationStats devuelve los propios recuentos de colaboración de quien llama a través de cada publicación a la que ha sido invitado — no limitados a una sola publicación.
query CollaborationStats {
collaborationStats {
totalCollaborations
accepted
pending
rejected
}
}
Comportamiento del feed
Cuando un usuario acepta una invitación de colaboración, getUserPosts de post.manager.js incluye la publicación en su propia cuadrícula de perfil, y getFeed la incluye en los feeds de cualquiera que lo siga — ambas se derivan por solicitud desde postCollaboratorAccessService.getAcceptedPostIdsForUsers. Ambas búsquedas son "best-effort": un fallo ahí se registra y se descarta en lugar de romper la carga del perfil/feed.