Saltar al contenido principal

Colaboradores de Publicaciones — Referencia Técnica

← Volver a Colaboradores de Publicaciones

Dónde vive esto

Backend

La API descrita a continuación 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.tsx tiene una entrada "Manage collaborators" (solo para el propietario) que abre CollaboratorsModal con isOwner activado. Corregido en esta sesión: el modal antes ya no se comunicaba en absoluto con la API de post-collaborator — consultaba post(id).taggedUsers (la función independiente de etiquetado por toque, respaldada por PostTag/PostMention) y simplemente listaba a esas personas, de solo lectura. Ahora está reconectado a la query real postCollaborators (modo de gestión, activado cuando se pasa un postId sin una prop users), mostrando a cada colaborador con una etiqueta de estado pendiente/aceptado/rechazado. Cuando isOwner está activado, también renderiza un formulario de invitación (búsqueda de nombre de usuario reutilizando la query searchUsers de etiquetar personas, que invoca inviteCollaborator para una sola selección o bulkInviteCollaborators cuando se seleccionan varias) y una acción "Eliminar" por fila (removeCollaborator) — ambas restringidas al propietario, ya que solo la entrada exclusiva del propietario de PostOptionsMenu pasa isOwner. La línea de solo lectura "Con @x y N más" de PostCard.tsx sigue pasando users directamente (no postId), así que esa ruta de visualización simple no se ve afectada y sigue sin consultar postCollaborators.

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 de CollaboratorsModal (solo propietario). Corregido en esta sesión — antes solo accesible mediante GraphQL, sin llamador en el frontend
  • mutaciones acceptCollaboration / rejectCollaboration — implementadas, conectadas, y usadas por CollaborationInvitesPage.tsx
  • mutación removeCollaborator — implementada, conectada, y ahora invocada desde la acción "Eliminar" por fila de CollaboratorsModal (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 de CollaboratorsModal cuando 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; postCollaborators ahora es invocada por CollaboratorsModal en modo de gestión (corregido en esta sesión — antes consultaba post(id).taggedUsers en su lugar), y myPendingCollaborations sigue siendo usada por CollaborationInvitesPage.tsx
  • consulta collaborationStats — implementada, conectada, y usada por CollaborationInvitesPage.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/getFeed de post.manager.js incorporan las publicaciones de colaboradores aceptados mediante getAcceptedPostIdsForUsers

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):

CampoTipoDescripción
idUUIDID del registro de colaborador
postIdUUID (columna post_id)La publicación en la que se colabora
userIdUUID (columna user_id)El ID del usuario invitado/colaborador
invitedByUUID (columna invited_by)ID de usuario del propietario de la publicación que envió la invitación
statusString(50)pending, accepted, o rejected (por defecto pending; no existe un estado removed — eliminar borra la fila)
createdAt / updatedAtDateTime (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.