Lead model

Overview

services/api/src/models/Lead.js define el modelo Lead con Mongoose. Este modelo guarda leads en MongoDB, aplica validación básica sobre datos de contacto y conserva metadatos de origen y ciclo de vida.

También conecta cada lead con un Listing y un User mediante referencias de Mongoose. El esquema está pensado para consultas por propietario y por listado, gracias a sus índices explícitos.

[!NOTE] El modelo usa timestamps: true, por lo que Mongoose añade createdAt y updatedAt automáticamente.

How it works

flowchart TB
    Schema[Lead schema] --> Model[Lead model]
    Schema --> Validation[Field validation and defaults]
    Schema --> Refs[Listing and User references]
    Schema --> Indexes[Index ownerId and listingId]
    Model --> Collection[leads collection]

El archivo crea primero leadSchema con reglas de formato, valores por defecto y restricciones de enumeración. Después registra el modelo con mongoose.model('Lead', leadSchema) y lo exporta como Lead.

La colección usada es leads. El esquema también activa dos índices para acelerar búsquedas por ownerId y por listingId.

Key components

{
    "title": "Lead",
    "content": "Modelo de Mongoose que representa un lead persistido en la colecci\u00f3n leads. Define campos de contacto, referencias a Listing y User, estados de negocio y metadatos de origen.",
    "type": "class",
    "filePath": "services/api/src/models/Lead.js",
    "badges": [
        "public"
    ]
}

Data & fields

[
    {
        "name": "name",
        "type": "String",
        "required": true,
        "description": "Nombre del lead. Se recorta con trim y admite hasta 120 caracteres."
    },
    {
        "name": "email",
        "type": "String",
        "description": "Correo electrónico. Se recorta, se guarda en minúsculas y usa cadena vacía como valor por defecto."
    },
    {
        "name": "phone",
        "type": "String",
        "description": "Teléfono de contacto. Se recorta y usa cadena vacía por defecto."
    },
    {
        "name": "message",
        "type": "String",
        "description": "Mensaje asociado al lead. Se recorta, admite hasta 1000 caracteres y usa cadena vacía por defecto."
    },
    {
        "name": "notes",
        "type": "String",
        "description": "Notas internas o adicionales. Se recorta, admite hasta 2000 caracteres y usa cadena vacía por defecto."
    },
    {
        "name": "listingId",
        "type": "ObjectId ref Listing",
        "description": "Referencia al Listing relacionado. El valor por defecto es null."
    },
    {
        "name": "listingTitle",
        "type": "String",
        "description": "Título del listing asociado. Se recorta y usa cadena vacía por defecto."
    },
    {
        "name": "ownerId",
        "type": "ObjectId ref User",
        "required": true,
        "description": "Referencia al usuario propietario del lead."
    },
    {
        "name": "status",
        "type": "String",
        "description": "Estado operativo del lead. Valores permitidos: new, follow_up, closed, discarded. El valor por defecto es new."
    },
    {
        "name": "source",
        "type": "String",
        "description": "Origen del lead. Valores permitidos: whatsapp, form, phone, email, manual, activity. El valor por defecto es manual."
    },
    {
        "name": "createdDuringProtection",
        "type": "Boolean",
        "description": "Marca si el lead se creó durante el ciclo de cierre de cuenta de 60 días del titular del listing. El valor por defecto es false."
    },
    {
        "name": "createdDuringAnonymization",
        "type": "Boolean",
        "description": "Marca si el lead se creó durante una fase de anonimización. El valor por defecto es false."
    },
    {
        "name": "ownerStatusAtCreation",
        "type": "String",
        "description": "Estado del titular del listing en el momento de creación. Valores permitidos: active, pending_deletion, anonymized_pending_purge, purged, disabled. El valor por defecto es active."
    },
    {
        "name": "deliveryStatus",
        "type": "String",
        "description": "Estado de entrega o notificación del lead. Valores permitidos: pending, notified, held, expired, purged. El valor por defecto es pending."
    },
    {
        "name": "timestamps",
        "type": "System metadata",
        "description": "Mongoose añade createdAt y updatedAt automáticamente porque el esquema usa timestamps: true."
    }
]

Field groups

Estos campos capturan el estado funcional del lead y el contexto en el que fue creado.

  • status controla el ciclo comercial del lead.
  • source clasifica el canal de origen.
  • createdDuringProtection y createdDuringAnonymization preservan contexto histórico.
  • ownerStatusAtCreation guarda el estado del titular al momento de creación.
  • deliveryStatus describe la entrega o retención del lead.

Estos campos enlazan el lead con otras entidades del sistema.

  • listingId referencia a Listing.
  • ownerId referencia a User.
  • listingTitle guarda una copia textual del título del listing.

Behaviors

  • new mongoose.Schema(...) — Define validaciones, transformaciones y valores por defecto para cada campo del documento.
  • leadSchema.index({ ownerId: 1, createdAt: -1 }) — Crea un índice para consultas por propietario ordenadas por recencia.
  • leadSchema.index({ listingId: 1 }) — Crea un índice para búsquedas por listing.
  • mongoose.model('Lead', leadSchema) — Registra el modelo Lead y lo deja listo para persistencia y consultas.
  • export const Lead — Exporta el modelo para que otros módulos lo importen y lo usen.

Gotchas & edge cases

[!WARNING] El comentario del esquema indica que, cuando source es activity, listingId debe ser obligatorio e inmutable. Esa regla no vive en este archivo; se aplica en leads.routes.js para la operación PATCH.

[!NOTE] email se normaliza a minúsculas al guardar. Esto afecta la forma en que se comparan y persisten direcciones de correo.

[!CAUTION] ownerId es obligatorio. Un documento sin esa referencia no cumple el esquema.

Este modelo se relaciona directamente con Listing y User mediante referencias de Mongoose. También depende de la lógica de negocio que valida cambios sobre leads, según el comentario que apunta a leads.routes.js.

  • Listing — entidad referenciada por listingId.
  • User — entidad referenciada por ownerId.
  • leads.routes.js — contiene una regla de negocio mencionada en el comentario del esquema para el caso source = activity.

[!IMPORTANT] El esquema no define lógica de negocio ejecutable por sí mismo. Solo declara estructura, validación, valores por defecto e índices para la colección leads.