Saltar al contenido principal

Gestión de cuenta — Referencia técnica

← Volver a Gestión de cuenta

Dónde vive esto

Backend

Frontend

Checklist de implementación técnica

  • accountStatus — resolver conectado en user-account-management.resolver.js; consultado por AccountManagementPage.tsx
  • resendEmailVerification / verifyEmail — resolvers conectados en user-profile.resolver.js; EmailVerificationPage.tsx llama a resendEmailVerification y procesa un enlace ?token= recibido por correo mediante verifyEmail. sendEmailVerification en sí todavía no tiene ningún llamador en el frontend.
  • deactivateAccount — resolver conectado; handleDeactivate de AccountManagementPage.tsx lo llama
  • reactivateAccount — resolver conectado en user-account-management.resolver.js; el flujo de "Reactivar cuenta" de AccountManagementPage.tsx lo llama (confirmado con contraseña)
  • requestAccountDeletion / cancelAccountDeletion — resolvers conectados; ambos se llaman desde AccountManagementPage.tsx
  • deleteAccount — resolver conectado en user-account-management.resolver.js; a pesar de su nombre, esto solo finaliza una eliminación ya programada por requestAccountDeletion — lanza un error a menos que la cuenta esté en pending_deletion y deletionScheduledAt ya haya pasado. No tiene ningún llamador en el frontend en ninguna parte de la app.
  • deleteAccountImmediately — resolver conectado; el botón "Eliminar permanentemente ahora" de AccountManagementPage.tsx lo llama directamente, omitiendo por completo el período de gracia y el requisito de pending_deletion
  • takeBreak / endBreak (modo de descanso) — resolvers conectados; AccountManagementPage.tsx llama a takeBreak con durationDays
  • autoReplyMessage en el modo de descanso — el flujo de "Tomar un descanso" de AccountManagementPage.tsx tiene un campo de texto opcional para la respuesta automática, enviado como autoReplyMessage en el input de takeBreak
  • resetPrivacySettings (PrivacySettings!, sin argumentos) — reexpuesto en el esquema. Antes se había eliminado por completo (la declaración anterior vivía en graphql/types/privacy-settings.type.js, duplicaba un PrivacySettingsInput con el mismo nombre proveniente de user.type.js, y en el proceso corrompía el input real de updatePrivacySettings — ver el comentario que sigue en ese archivo). Ahora está declarado una sola vez, correctamente, en graphql/types/user.type.js y conectado en user-privacy.resolver.js, que llama al método del manager que siempre existió (userManager.resetPrivacySettingsprivacySettingsManager.resetPrivacySettings) y remapea su resultado en snake_case de la misma forma que privacySettings/updatePrivacySettings. AccountPrivacyPage.tsx ahora tiene un botón "Restablecer valores predeterminados" (con confirmación) conectado a esta mutation.
  • resetNotificationSettings — resolver conectado en user-notifications.resolver.js; el botón "Restablecer configuración de notificaciones a los valores predeterminados" de la página de Notificaciones (NotificationsSettingsPage.tsx) lo llama
  • getUserActivityStats / getActivityHeatmap — resolvers conectados en user-activity-tracking.resolver.js; ActivityInsightsPage.tsx llama a ambos (tarjetas de estadísticas más un mapa de calor de contribuciones estilo GitHub de la actividad de publicación). Este es un conjunto de datos distinto del usageStats de Configuración → Gestión del tiempo (TimeManagementPage.tsx), que está implementado por separado mediante time-management.resolver.js
  • getAccountAnalytics / getDeviceStats — resolvers conectados (user-account-management.resolver.js y user-sessions.resolver.js respectivamente); sin llamador en el frontend
  • getPrivacyRecommendations / refreshRecommendations — resolvers conectados en user-privacy.resolver.js y user-search-discovery.resolver.js respectivamente; PrivacyCheckupPage.tsx llama a ambos
  • Cuentas memorializadas — ahora existe la superficie completa de GraphQL: requestMemorialization + memorializationRequestStatus (graphql/resolvers/memorialization.resolver.js), consumidas por MemorializationPage.tsx; adminGetMemorializationRequests + adminReviewMemorialization (graphql/resolvers/admin/memorialization-admin.resolver.js, restringidas al permiso MODERATE_CONTENT), consumidas por la página /moderation/memorialization del panel de administración. Ambas delegan en memorialized-accounts.manager.js, que persiste la solicitud directamente en la fila del usuario fallecido (memorializationStatus/memorializationRequestedBy/memorializationProof) en lugar de una tabla separada. Los resolvers de campo de solo lectura en User (memorializedBy, memorializationRequestedAt, memorializationProof, legacyContactId, legacyContactAddedAt en user-fields.resolver.js, todos selfOnly()) se mantienen además de las mutaciones.

Estado de la cuenta

accountStatus devuelve el estado actual de la cuenta del usuario autenticado de un vistazo. Úsalo en la pantalla principal de configuración para avisar al usuario si su cuenta está suspendida (isSuspended: true) y mostrar el motivo y la fecha de vencimiento.

query AccountStatus {
accountStatus {
isActive isDeactivated isSuspended isDeleted
suspensionReason suspensionExpires
lastActivity accountType
}
}

Valores de accountType: personal, creator, business.

Verificación de email

sendEmailVerification envía un correo de verificación mediante AWS SES a la dirección registrada del usuario. resendEmailVerification hace lo mismo pero aplica un período de espera para evitar spam de correos. verifyEmail valida el token de un solo uso del enlace del correo y marca la cuenta como verificada.

mutation SendEmailVerification { sendEmailVerification { success message } }
mutation ResendEmailVerification { resendEmailVerification { success message } }
mutation VerifyEmail($token: String!) { verifyEmail(token: $token) { success message } }

Desactivar y reactivar

La desactivación oculta la cuenta a otros usuarios y la elimina de los resultados de búsqueda sin borrar ningún dato. El contenido del usuario se conserva. Se requiere password como medida de confirmación.

reactivateAccount restaura la visibilidad de inmediato — no se necesita aprobación de un administrador.

# Ocultar la cuenta sin eliminar datos (reversible)
mutation Deactivate($password: String!) {
deactivateAccount(password: $password) { success message }
}

# Restaurar una cuenta desactivada
mutation Reactivate($password: String!) {
reactivateAccount(password: $password) { success message }
}

Eliminación de cuenta

La eliminación es un proceso de dos pasos para dar a los usuarios la oportunidad de reconsiderarlo. requestAccountDeletion programa la eliminación para una fecha futura (normalmente 30 días después) y devuelve un timestamp scheduledDeletion. Durante esta ventana, cancelAccountDeletion puede abortar el proceso. Una vez que el período de gracia ha transcurrido, deleteAccount la finaliza — el resolver rechaza la llamada a menos que la cuenta ya esté en pending_deletion y su deletionScheduledAt ya haya pasado, así que a pesar del nombre no es un mecanismo manual para omitir el proceso. deleteAccountImmediately es el verdadero mecanismo para omitirlo: se salta por completo el período de gracia y el requisito de pending_deletion, verificando solo la contraseña antes de anonimizar la cuenta de inmediato — esto es lo que impulsa la opción "Eliminar permanentemente ahora" de Configuración → Gestión de cuenta.

# Paso 1 — programar la eliminación (el período de gracia permite cancelar)
mutation RequestDeletion($password: String!) {
requestAccountDeletion(password: $password) { success message }
}

# Cancelar antes de la fecha de eliminación programada
mutation CancelDeletion {
cancelAccountDeletion { success message }
}

# Finalizar una eliminación después de que el período de gracia haya transcurrido (falla en caso contrario)
mutation DeleteAccount($password: String!) {
deleteAccount(password: $password) { success message }
}

# Omitir el período de gracia por completo — eliminación inmediata y permanente
mutation DeleteAccountImmediately($password: String!) {
deleteAccountImmediately(password: $password) { success message }
}

AccountDeletionRequest (devuelto por las consultas de estado) incluye scheduledDeletion, reason y canCancel.

Modo de descanso

Los usuarios pueden poner su cuenta en un descanso temporal sin desactivarla. Consulta Perfil de usuario → Modo de descanso para la API completa. breakStatus devuelve si hay un descanso activo, su ventana de inicio/fin, y el autoReplyMessage que se envía a cualquiera que le escriba al usuario durante el descanso.

query BreakStatus {
breakStatus {
isOnBreak breakStart breakEnd breakType autoReplyMessage
}
}

Configuración de privacidad

resetNotificationSettings revierte todas las preferencias de notificaciones a los valores predeterminados de la plataforma en una sola llamada — respalda el botón "Restablecer configuración de notificaciones a los valores predeterminados" de la página de Notificaciones. resetPrivacySettings (PrivacySettings!, sin argumentos) hace lo equivalente para la configuración de privacidad; está reexpuesto en el esquema (user.type.js) y conectado en user-privacy.resolver.js, llamando a la lógica de privacySettingsManager.resetPrivacySettings que había existido inalcanzable desde que se eliminó la mutation — ver el checklist anterior. El botón "Restablecer valores predeterminados" de AccountPrivacyPage.tsx ya lo llama. setAllNotifications sigue eliminado del schema/resolvers (solo queda un método huérfano del manager, inalcanzable vía GraphQL); markAllNotificationsAsRead y deleteAllNotifications (graphql/types/notification.type.js) son las operaciones masivas de notificaciones más cercanas que sobreviven, pero actúan sobre notificaciones existentes en lugar de alternar configuraciones.

# Reset all notification preferences to defaults
mutation ResetNotifications { resetNotificationSettings }

# Reset all privacy settings to defaults
mutation ResetPrivacy { resetPrivacySettings { isPrivate showActivityStatus allowMessagesFrom } }

Actividad y presencia

getUserActivityStats devuelve el resumen de uso de la plataforma del usuario para un período de tiempo dado (day, week, month). totalTime está en segundos; mostActiveHour va de 0 a 23. activityStreak es el número de días consecutivos con actividad. userId es obligatorio y debe ser el id del propio llamador, a menos que el llamador sea un administrador.

getActivityHeatmap devuelve una cuadrícula de series de tiempo ({ timeframe, days, total, maxCount, cells: [{date, count}] }, calculada a partir de las publicaciones del usuario por día) apta para renderizar un mapa de calor de contribuciones estilo GitHub — ActivityInsightsPage.tsx lo renderiza exactamente así. getAccountAnalytics ofrece una visión más amplia del engagement y getDeviceStats desglosa el uso por tipo de dispositivo; ninguno de los dos tiene todavía un llamador en el frontend.

query ActivityStats($userId: ID!, $timeframe: String) {
getUserActivityStats(userId: $userId, timeframe: $timeframe) {
totalTime # seconds
activeDays
mostActiveHour # 0-23
activityStreak # consecutive days
lastActivity
}
}

query ActivityHeatmap($timeframe: String) { getActivityHeatmap(timeframe: $timeframe) }
query AccountAnalytics($timeframe: String) { getAccountAnalytics(timeframe: $timeframe) }
query DeviceStats($timeframe: String) { getDeviceStats(timeframe: $timeframe) }

Recomendaciones de privacidad

getPrivacyRecommendations devuelve una lista clasificada de mejoras de privacidad sugeridas según la configuración actual del usuario. Cada recomendación tiene una priority (high, medium, low) y un indicador isApplied que muestra si el usuario ya la adoptó. refreshRecommendations (resolver en user-search-discovery.resolver.js, devuelve Boolean!) las vuelve a calcular. PrivacyCheckupPage.tsx llama a ambos.

query PrivacyRecommendations {
getPrivacyRecommendations {
id recommendationType title description priority isApplied
}
}

mutation RefreshRecommendations { refreshRecommendations }

Cuentas memorializadas

Cualquier usuario autenticado puede enviar una solicitud para memorializar la cuenta de un usuario fallecido; la aprobación es una acción exclusiva de administradores en un schema de administración separado. La solicitud se almacena directamente en la fila del usuario fallecido en lugar de en su propia tabla — memorializationStatus (none/pending/approved/rejected), memorializationRequestedBy, memorializationRequestedAt, y un blob JSON memorializationProof (prueba de fallecimiento, identificación del solicitante, relación, etc.). Las cuentas aprobadas obtienen isMemorialized: true y, a menos que el administrador lo desactive, showMemorialBanner: true, que PublicProfilePage.tsx usa para renderizar un banner "En memoria".

# User-facing: submit a request
mutation RequestMemorialization($input: MemorializationRequestInput!) {
requestMemorialization(input: $input) {
success message requestId estimatedReviewTime submittedAt
}
}

query MemorializationRequestStatus($deceasedUserId: ID!) {
memorializationRequestStatus(deceasedUserId: $deceasedUserId) {
hasRequest canSubmitNew
request { status submittedAt reviewedAt rejectionReason }
}
}

# Admin-only: review queue (requires the MODERATE_CONTENT permission)
query AdminMemorializationQueue($limit: Int, $offset: Int) {
adminGetMemorializationRequests(limit: $limit, offset: $offset) {
totalCount
requests { deceasedUserId deceasedUser { username } status requestedBy requestedAt relationship proof }
}
}

mutation AdminReviewMemorialization($deceasedUserId: ID!, $decision: String!) {
adminReviewMemorialization(deceasedUserId: $deceasedUserId, decision: $decision) {
success message status reviewedAt
}
}