services/api/src/models/User.js
Overview
Este archivo define el modelo Mongoose User, que concentra identidad, perfil, roles, suscripción, verificación y ciclo de vida de cuenta en la colección users. También sostiene los campos que otros flujos del backend leen para Google Auth, Twilio Verify, el gate ALTCHA de contacto y la purga diferida.
El modelo funciona como el registro central de cuenta para la plataforma. Otras capas no duplican esa información; la consultan y la actualizan de forma determinista según el estado de la cuenta.
[!NOTE] Las cuentas creadas con Google pueden omitir
phoneypasswordHash. El registro local sigue exigiendo esos datos enauth.validation.js, pero el schema se relaja para ese caso.
How it works
flowchart TD
UserModel[User model]
AuthService[auth.service.js]
PhoneVerificationService[phone-verification.service.js]
ContactGateModule[contact-gate module]
PurgeJob[purgeExpiredAccounts.js]
SendWarningsJob[sendPurgeWarnings.js]
ListingCommentsRoutes[listing-comments.routes.js]
GoogleAuth[Google Auth]
TwilioVerify[Twilio Verify]
AuthService --> UserModel
PhoneVerificationService --> UserModel
ContactGateModule --> UserModel
PurgeJob --> UserModel
SendWarningsJob --> UserModel
ListingCommentsRoutes --> UserModel
GoogleAuth --> UserModel
TwilioVerify --> PhoneVerificationService
PhoneVerificationService -->|phoneVerifiedAt| UserModel
PurgeJob -->|clears account state| UserModel
SendWarningsJob -->|reads accountStatus and purgeScheduledAt| UserModel
El schema define la forma del documento y la validación de sus campos. La lógica de negocio vive fuera del modelo, en servicios y jobs que leen o actualizan campos concretos como phoneVerifiedAt, accountStatus, google.sub o deletionGracePreferences.
Key components
El archivo expone un único componente principal: el modelo Mongoose User. Ese modelo se compila sobre userSchema, usa la colección users y activa timestamps.
{
"title": "User",
"content": "Modelo Mongoose que representa cuentas de usuario en la colecci\u00f3n users. Centraliza identidad, perfil, suscripci\u00f3n, verificaci\u00f3n, preferencias y ciclo de cierre.",
"type": "class",
"filePath": "services/api/src/models/User.js",
"badges": [
"public"
]
}
Data & fields
El schema contiene campos obligatorios, opcionales y subdocumentos. Los tipos y validaciones del schema se aplican antes de persistir cada documento.
[
{
"name": "name",
"type": "string",
"required": true,
"description": "Nombre visible. Se recorta y acepta entre 2 y 120 caracteres."
},
{
"name": "firstName",
"type": "string",
"required": true,
"description": "Nombre de pila. Se recorta y acepta entre 2 y 80 caracteres."
},
{
"name": "lastName",
"type": "string",
"required": true,
"description": "Apellido. Se recorta y acepta entre 2 y 80 caracteres."
},
{
"name": "email",
"type": "string",
"required": true,
"description": "Correo principal de la cuenta. Se recorta y se guarda en minúsculas."
},
{
"name": "phone",
"type": "string",
"description": "Teléfono opcional. Puede faltar en cuentas creadas con Google Auth."
},
{
"name": "phoneVerifiedAt",
"type": "Date",
"description": "Fecha de verificación telefónica. Null significa que no está verificado."
},
{
"name": "username",
"type": "string",
"description": "Nombre público opcional. Se recorta, se guarda en minúsculas y solo acepta letras, números, punto y guion bajo."
},
{
"name": "passwordHash",
"type": "string",
"description": "Hash de contraseña local. Puede faltar en cuentas Google-only."
},
{
"name": "systemRole",
"type": "string",
"required": true,
"description": "Rol del sistema. Valores permitidos: user, admin."
},
{
"name": "accountRole",
"type": "string",
"required": true,
"description": "Rol de cuenta. Valores permitidos: creator, seeker."
},
{
"name": "plan",
"type": "string",
"description": "Plan comercial. Valores permitidos: free, professional, creator, enterprise."
},
{
"name": "stripeCustomerId",
"type": "string",
"description": "Identificador de cliente en Stripe. Se recorta y por defecto queda vacío."
},
{
"name": "stripeSubscriptionId",
"type": "string",
"description": "Identificador de suscripción en Stripe. Se recorta y por defecto queda vacío."
},
{
"name": "subscriptionStatus",
"type": "string",
"description": "Estado de suscripción. Valores permitidos: none, incomplete, incomplete_expired, trialing, active, past_due, canceled, unpaid, paused."
},
{
"name": "currentPeriodEnd",
"type": "Date",
"description": "Fecha de finalización del periodo actual de suscripción. Null por defecto."
},
{
"name": "cancelAtPeriodEnd",
"type": "boolean",
"description": "Marca si la suscripción termina al final del periodo actual."
},
{
"name": "bio",
"type": "string",
"description": "Biografía pública. Se recorta y admite hasta 280 caracteres."
},
{
"name": "photo",
"type": "string",
"description": "URL o referencia de foto de perfil. Se recorta."
},
{
"name": "coverPhoto",
"type": "string",
"description": "URL o referencia de portada. Se recorta."
},
{
"name": "website",
"type": "string",
"description": "Sitio web público. Se recorta."
},
{
"name": "location",
"type": "string",
"description": "Ubicación pública. Se recorta y admite hasta 120 caracteres."
},
{
"name": "seo",
"type": "object",
"description": "Metadatos SEO del perfil.",
"children": [
{
"name": "descriptors",
"type": "string",
"description": "Texto descriptivo para SEO. Se recorta y admite hasta 500 caracteres."
},
{
"name": "aiKeywords",
"type": "string",
"description": "Palabras clave asociadas al perfil. Se recorta y admite hasta 500 caracteres."
},
{
"name": "schemaType",
"type": "string",
"description": "Tipo de schema estructurado asociado al perfil."
}
]
},
{
"name": "accountStatus",
"type": "string",
"required": true,
"description": "Estado de la cuenta. Valores permitidos: active, pending_deletion, anonymized_pending_purge, purged, disabled."
},
{
"name": "deletionRequestedAt",
"type": "Date",
"description": "Fecha en la que se solicitó el cierre de cuenta."
},
{
"name": "anonymizationScheduledAt",
"type": "Date",
"description": "Momento programado para la anonimización dentro del ciclo de cierre."
},
{
"name": "purgeScheduledAt",
"type": "Date",
"description": "Momento programado para la purga definitiva."
},
{
"name": "anonymizedAt",
"type": "Date",
"description": "Fecha real de anonimización. Null por defecto."
},
{
"name": "deletionCompletedAt",
"type": "Date",
"description": "Fecha real de finalización de la eliminación."
},
{
"name": "restorationToken",
"type": "string",
"description": "Token de un solo uso para restaurar una cuenta durante la ventana de restauración."
},
{
"name": "restorationTokenExpiresAt",
"type": "Date",
"description": "Fecha de expiración del token de restauración."
},
{
"name": "googleReauthTokenJti",
"type": "string",
"description": "JTI de la reautenticación con Google usada para flujos de cierre de cuenta."
},
{
"name": "retentionContact",
"type": "string",
"description": "Canal de contacto cifrado conservado temporalmente entre anonimización y purga."
},
{
"name": "deletionGracePreferences",
"type": "object",
"description": "Preferencias del titular durante la ventana de protección del cierre de cuenta.",
"children": [
{
"name": "keepPublishedListingsVisible",
"type": "boolean",
"description": "Controla si los listings publicados siguen visibles."
},
{
"name": "allowLeadCapture",
"type": "boolean",
"description": "Controla si se siguen permitiendo leads."
},
{
"name": "allowComments",
"type": "boolean",
"description": "Controla si se siguen permitiendo comentarios."
}
]
},
{
"name": "tokenVersion",
"type": "number",
"required": true,
"description": "Versión de tokens de la cuenta. No puede ser negativa."
},
{
"name": "passwordResetToken",
"type": "string",
"description": "Token temporal para restablecer contraseña."
},
{
"name": "passwordResetExpires",
"type": "Date",
"description": "Expiración del token de restablecimiento."
},
{
"name": "emailVerifiedAt",
"type": "Date",
"description": "Fecha de verificación de correo. Null significa no verificado."
},
{
"name": "emailVerificationTokenHash",
"type": "string",
"description": "Hash del token de verificación de correo."
},
{
"name": "emailVerificationExpiresAt",
"type": "Date",
"description": "Fecha de expiración de la verificación de correo."
},
{
"name": "emailVerificationRequestedAt",
"type": "Date",
"description": "Marca temporal de la última solicitud de verificación de correo."
},
{
"name": "notificationPreferences",
"type": "object",
"description": "Preferencias mínimas de notificación.",
"children": [
{
"name": "listingPerformanceDigest",
"type": "boolean",
"description": "Preferencia para el digest de rendimiento de listings."
}
]
},
{
"name": "contactGate",
"type": "object",
"description": "Estado del gate ALTCHA de contacto.",
"children": [
{
"name": "validatedAt",
"type": "Date",
"description": "Fecha de la última validación del challenge."
},
{
"name": "count",
"type": "number",
"description": "Conteo de intenciones autorizadas desde la última validación."
}
]
},
{
"name": "google",
"type": "object",
"description": "Identidad federada de Google.",
"children": [
{
"name": "sub",
"type": "string",
"description": "Identificador estable del proveedor Google."
},
{
"name": "linkedAt",
"type": "Date",
"description": "Fecha en que se vinculó la cuenta de Google."
}
]
}
]
Índices y unicidad
El modelo declara un índice único para email y tres índices únicos parciales para phone, username y google.sub. También añade índices compuestos sobre accountStatus con anonymizationScheduledAt y purgeScheduledAt.
emailtiene unicidad global.phonesolo participa en la unicidad cuando existe como cadena.usernamesolo participa en la unicidad cuando existe como cadena.google.subsolo participa en la unicidad cuando existe como cadena.accountStatus + anonymizationScheduledAtacelera consultas del ciclo de anonimización.accountStatus + purgeScheduledAtacelera consultas del ciclo de purga.
[!IMPORTANT]
phone,usernameygoogle.subusan índices parciales. Eso permite cuentas locales sin esos campos y evita colisiones por documentos incompletos.
Behaviors
UsercompilauserSchemacomo modelo Mongoose y lo exporta con el nombreUser.- El modelo persiste documentos en la colección
users. - El schema activa
timestamps, por lo que Mongoose manejacreatedAtyupdatedAt. - No define métodos de instancia, statics ni middleware propios en este archivo.
Gotchas & edge cases
[!WARNING]
phoneVerifiedAtes el único dato persistido para representar verificación telefónica. No existe un booleano paralelo en el schema, y las lecturas públicas deben derivar el estado desde ese campo.
[!WARNING] Las cuentas Google-only pueden omitir
passwordHash.auth.service.jscomprueba su presencia antes de comparar contraseñas, para no revelar el método de alta.
[!IMPORTANT]
accountStatusgobierna el ciclo de cierre de cuenta. Los jobs de restauración, anonimización y purga leen y mutan este estado junto condeletionRequestedAt,anonymizationScheduledAt,purgeScheduledAtyretentionContact.
[!NOTE]
retentionContactes temporal. El flujo de purga lo borra junto conphoneVerifiedAt,contactGatey
[!CAUTION]
phone,usernameygoogle.subdependen de índices de unicidad. Los flujos de alta y verificación deben respetar esas restricciones para evitar duplicados.
Related
services/api/src/modules/auth/auth.service.js consume User para sanitizar perfiles y exponer campos derivados como hasPassword, hasGoogleAccount, publicProfileActive y phoneVerifiedAt.
services/api/src/modules/listing-comments/listing-comments.routes.js carga solo deletionGracePreferences y accountStatus cuando necesita aplicar la política de comentarios del propietario.
services/api/src/scripts/sendPurgeWarnings.js consulta accountStatus y purgeScheduledAt para identificar cuentas próximas a purga.
services/api/src/modules/auth/account-export.service.js usa User para habilitar exportaciones de actividad de cuenta durante el ciclo de cierre.
services/api/src/scripts/purgeExpiredAccounts.js limpia datos sensibles del usuario, incluyendo emailVerifiedAt, phoneVerifiedAt, contactGate y google.
services/api/src/modules/auth/phone-verification.client.js y el servicio de verificación telefónica trabajan con phoneVerifiedAt como señal persistida de aprobación.
services/api/src/modules/auth/google-identity.client.js y los flujos de Google Auth usan google.sub como identidad estable del proveedor.