Saltar al contenido principal

Comentarios de usuarios

Los administradores pueden revisar, priorizar y responder a los comentarios enviados por los usuarios (reportes de errores, solicitudes de funciones, quejas, sugerencias y comentarios generales) desde /feedback. Los usuarios ya contaban con una superficie completa de envío y votación (submitFeedback, publicFeedback, upvoteFeedback/downvoteFeedback — ver Comentarios de usuarios), pero no existía previamente ninguna forma, desde el lado de administración, de ver, priorizar o responder a ninguno de ellos.

Esto se construyó casi por completo sobre la infraestructura existente: database/models/UserFeedback.js ya tenía todas las columnas necesarias para la priorización (status, priority, adminResponse, reviewedBy, reviewedAt), y managers/user-managers/user-feedback.manager.js ya tenía la mayor parte de la lógica de negocio (getAllFeedback, updateStatus, setPriority, getFeedbackStats). Lo que faltaba era la superficie de GraphQL de administración en sí, una acción respondToFeedback que establece adminResponse/reviewedBy/reviewedAt juntos, y soporte de filtrado en getAll.

Checklist de implementación

Priorización de administrador (/feedback, requiere MANAGE_FEEDBACK)

  • Listar comentarios con filtros opcionales de status/priority/feedbackType, del más reciente al más antiguo (adminGetFeedback)
  • Conteos agregados por tipo/estado/prioridad, además del total de votos a favor/en contra (adminGetFeedbackStats)
  • Actualizar el estado de un elemento — pending | in_review | in_progress | resolved | rejected | duplicate (adminUpdateFeedbackStatus)
  • Establecer la prioridad de un elemento — low | medium | high | critical (adminSetFeedbackPriority)
  • Escribir una respuesta de administrador, que también registra quién revisó el elemento y cuándo, en una sola acción (adminRespondToFeedback)

Dos brechas reales cerradas en el camino

  • data-access-services/user/user-feedback.access-service.js#getAll(options) solo admitía {limit, offset} — sin filtrado. Ahora acepta status/priority/feedbackType opcionales y los combina en el where de Sequelize. Los métodos anteriores del manager getByStatus/getByPriority llamaban a métodos del access-service (getByStatus/getByPriority) que nunca existieron — un error de tipo "is not a function" si alguna vez se invocaban en la práctica. Se dejan como código muerto sin referencias (nada los llama) en lugar de agregar tres métodos de listado casi duplicados; getAllFeedback/getAll con filtros cumple el propósito que tenían.
  • UserFeedback.reviewedBy está pensado para contener el id de un administrador, pero la migración original (20220122175043-create-user-feedback.js) apuntaba su llave foránea a user (copiado de las otras columnas de esa tabla, que sí referencian correctamente a quien envía el comentario). La migración 20260802140000-fix-user-feedback-reviewed-by-references-admin-user.js corrige la FK para que referencie admin_user (la misma distinción que appeal.reviewed_by ya tenía correcta), poniendo en NULL primero las pocas filas existentes cuyo reviewed_by contenía un id de user (datos de prueba preexistentes — nunca fue una acción real de un administrador, ya que no existía ninguna superficie de revisión de administrador hasta ahora). La asociación del modelo (UserFeedback.js) se corrigió para que coincida (belongsTo(models.AdminUser, ...)).

Una compensación deliberada en el schema

status/priority/adminResponse/reviewedBy/reviewedAt ya estaban declarados en el tipo compartido UserFeedback de GraphQL (el schema completo se ensambla una sola vez antes de dividirse en los ámbitos de cliente/administración, así que un tipo declarado una vez se resuelve en ambos). Eso significa que las consultas myFeedback/feedbackById de un usuario regular también pueden leer esos campos sobre sus propios comentarios — no es una fuga de datos (ambas consultas ya están verificadas por propiedad), y hasta podría considerarse útil (un usuario puede ver la respuesta del administrador a su propio reporte). Ver el comentario en la parte superior de graphql/types/user-feedback.type.js.

Dónde vive esto

Backend

Frontendapps/frontend-admin/src/app/feedback/page.tsx + FeedbackContent.tsx. Entrada de navegación protegida por el permiso MANAGE_FEEDBACK (o super_admin), el mismo patrón que la sección de Control de versiones de la app protegida por MANAGE_APP_VERSIONS.

PermisoMANAGE_FEEDBACK, agregado al catálogo real de permisos en admin-user.manager.js#getAdminPermissions (categoría content_moderation, junto a MODERATE_CONTENT/REMOVE_CONTENT).

Referencia técnica de GraphQL

# Administrador (requiere MANAGE_FEEDBACK)
query AdminGetFeedback($status: String, $priority: String, $feedbackType: String, $limit: Int, $offset: Int) {
adminGetFeedback(status: $status, priority: $priority, feedbackType: $feedbackType, limit: $limit, offset: $offset) {
id title description feedbackType status priority adminResponse
reviewedBy reviewedAt upvotes downvotes createdAt
user { id username profilePicture }
}
}

query AdminGetFeedbackStats {
adminGetFeedbackStats { total byType byStatus byPriority totalUpvotes totalDownvotes }
}

mutation AdminUpdateFeedbackStatus($id: ID!, $status: String!) {
adminUpdateFeedbackStatus(id: $id, status: $status) { id status }
}

mutation AdminSetFeedbackPriority($id: ID!, $priority: String!) {
adminSetFeedbackPriority(id: $id, priority: $priority) { id priority }
}

mutation AdminRespondToFeedback($id: ID!, $response: String!) {
adminRespondToFeedback(id: $id, response: $response) { id adminResponse reviewedBy reviewedAt }
}

# Cliente (autenticado - sin cambios por este trabajo)
query MyFeedback { myFeedback { id title status priority adminResponse } }
mutation SubmitFeedback($input: SubmitFeedbackInput!) { submitFeedback(input: $input) { id } }