listing-referral
Overview
Este módulo gestiona la relación entre un anuncio principal y un anuncio relacionado dentro de services/api/src/modules/listings/listing-referral.js. Su foco está en validar enlaces entre anuncios, sincronizar un periodo de gracia cuando cambia la publicación, y enriquecer listados con un resumen seguro del anuncio relacionado.
También aplica reglas deterministas sobre propiedad, publicación, tipo de anuncio y caducidad. Eso permite mantener la relación sin depender de lógica externa o de un modelo de IA.
[!NOTE] El módulo trabaja sobre la estructura
requirement.referraly usaListingcomo fuente de verdad para validar y limpiar relaciones.
How it works
sequenceDiagram
participant Caller as Caller
participant Module as listing-referral.js
participant Listing as Listing model
participant Mongo as mongoose
Caller->>Module: validateRelatedOffer referral ownerId
alt relatedListingId missing
Module-->>Caller: null
else invalid ObjectId
Module-->>Caller: INVALID_RELATED_LISTING_ID
else valid ObjectId
Module->>Mongo: Types ObjectId isValid
Module->>Listing: findById select fields lean
alt related missing or purged
Module-->>Caller: RELATED_LISTING_UNAVAILABLE
else owner differs
Module-->>Caller: RELATED_LISTING_NOT_OWNED
else intent is requirement
Module-->>Caller: RELATED_LISTING_MUST_BE_OFFER
else not published
Module-->>Caller: RELATED_LISTING_NOT_PUBLISHED
else valid relation
Module-->>Caller: null
end
end
La validación primero descarta relaciones vacías. Después verifica que el identificador sea válido y que el anuncio exista. Luego aplica reglas de propiedad, tipo e ինտención, y por último confirma que esté publicado.
Key components
{
"title": "REFERRAL_RELATED_OFFER_GRACE_DAYS",
"content": "Constante p\u00fablica que define el periodo de gracia para relaciones con un anuncio relacionado no publicado. Su valor es 10 d\u00edas.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-referral.js",
"badges": [
"public"
]
}
{
"title": "REFERRAL_RELATED_OFFER_GRACE_MS",
"content": "Constante p\u00fablica que convierte el periodo de gracia de 10 d\u00edas a milisegundos. Se usa para calcular cortes de expiraci\u00f3n.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-referral.js",
"badges": [
"public"
]
}
{
"title": "validateRelatedOffer",
"content": "Funci\u00f3n p\u00fablica as\u00edncrona que valida si un anuncio relacionado puede aceptarse para un referral. Devuelve null o un objeto de error con code y message.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-referral.js",
"badges": [
"public"
]
}
{
"title": "cleanupExpiredReferralRelations",
"content": "Funci\u00f3n p\u00fablica as\u00edncrona que limpia relaciones caducadas en Listing. Borra relatedListingId y relatedListingUnpublishedAt cuando superan el corte calculado.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-referral.js",
"badges": [
"public"
]
}
{
"title": "syncReferralGraceForOffer",
"content": "Funci\u00f3n p\u00fablica as\u00edncrona que sincroniza el periodo de gracia cuando cambia el estado de publicaci\u00f3n de una oferta. Marca o limpia relatedListingUnpublishedAt seg\u00fan el cambio de estado.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-referral.js",
"badges": [
"public"
]
}
{
"title": "attachRelatedOfferSummaries",
"content": "Funci\u00f3n p\u00fablica as\u00edncrona que adjunta un resumen seguro del anuncio relacionado a una lista de listings. Tambi\u00e9n limpia relaciones expiradas y oculta datos privados cuando corresponde.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-referral.js",
"badges": [
"public"
]
}
{
"title": "safeRelatedOfferSummary",
"content": "Funci\u00f3n interna que construye un resumen reducido del anuncio relacionado. Expone t\u00edtulo, metadatos, estado de publicaci\u00f3n y ubicaci\u00f3n, con control sobre la visibilidad de la ubicaci\u00f3n.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-referral.js",
"badges": []
}
Data & fields
[
{
"name": "referral.relatedListingId",
"type": "string | null",
"description": "Identificador del anuncio relacionado. Si no existe, la validación no aplica."
},
{
"name": "referral.relatedListingUnpublishedAt",
"type": "Date | string | null",
"description": "Marca temporal de cuándo el anuncio relacionado dejó de estar publicado."
},
{
"name": "listing.createdBy",
"type": "string | object",
"required": true,
"description": "Autor del anuncio. Se compara con ownerId y con viewerUserId."
},
{
"name": "listing.intent",
"type": "string",
"required": true,
"description": "Tipo de anuncio. La validación exige que el relacionado no tenga intent igual a requirement."
},
{
"name": "listing.publication.status",
"type": "string",
"description": "Estado de publicación. La validación exige published."
},
{
"name": "listing.lifecycle.status",
"type": "string",
"description": "Estado del ciclo de vida. Si vale purged, el anuncio se considera no disponible."
},
{
"name": "listing.requirement.referral.relatedListing",
"type": "object | null",
"description": "Resumen adjunto del anuncio relacionado para consumo de lectura.",
"children": [
{
"name": "_id",
"type": "string",
"required": true,
"description": "Identificador del anuncio relacionado."
},
{
"name": "title",
"type": "string",
"description": "Título del anuncio relacionado."
},
{
"name": "meta",
"type": "object",
"required": true,
"description": "Metadatos resumidos del anuncio.",
"children": [
{
"name": "entityType",
"type": "string",
"description": "Tipo de entidad, con valor por defecto property."
},
{
"name": "operationType",
"type": "string",
"description": "Tipo de operación del anuncio."
},
{
"name": "propertyType",
"type": "string",
"description": "Tipo de propiedad."
},
{
"name": "serviceType",
"type": "string",
"description": "Tipo de servicio."
},
{
"name": "investmentType",
"type": "string",
"description": "Tipo de inversión."
}
]
},
{
"name": "publication",
"type": "object",
"required": true,
"description": "Estado de publicación resumido.",
"children": [
{
"name": "status",
"type": "string",
"required": true,
"description": "Estado de publicación. Usa published como valor principal."
}
]
},
{
"name": "location",
"type": "object",
"required": true,
"description": "Ubicación resumida del anuncio.",
"children": [
{
"name": "country",
"type": "string",
"description": "País visible según las reglas de exposición."
},
{
"name": "state",
"type": "string",
"description": "Estado o provincia visible según las reglas de exposición."
},
{
"name": "city",
"type": "string",
"description": "Ciudad visible según las reglas de exposición."
},
{
"name": "neighborhood",
"type": "string",
"description": "Barrio visible según las reglas de exposición."
}
]
}
]
}
]
Constants
REFERRAL_RELATED_OFFER_GRACE_DAYS[badge:read-only]number— Define una ventana de 10 días para mantener referencias a una oferta no publicada.REFERRAL_RELATED_OFFER_GRACE_MS[badge:read-only]number— Convierte esa ventana a milisegundos para cálculos de corte.
Behaviors
validateRelatedOffer(referral, ownerId)— Valida la relación propuesta y devuelvenullo un error estructurado concodeymessage.cleanupExpiredReferralRelations(now)— Limpia en base de datos las relaciones cuya marcarelatedListingUnpublishedAtya caducó.syncReferralGraceForOffer({ offerId, previousStatus, nextStatus, now })— Actualiza marcas de gracia cuando una oferta cambia de publicada a no publicada, o al revés.attachRelatedOfferSummaries(listings, { viewerUserId, now })— Devuelve una lista enriquecida conrelatedListing, o elimina la referencia cuando ya no puede mostrarse.safeRelatedOfferSummary(offer, options)— Construye un resumen de lectura con campos controlados por el estado de publicación y el contexto del visor.
[!IMPORTANT]
validateRelatedOfferno lanza errores. En su lugar, devuelve un objeto concodeymessagecuando falla la validación.
Workflows
1. Validar relación propuesta | `validateRelatedOffer` revisa el `relatedListingId`, el propietario y el estado del anuncio.
2. Sincronizar cambio de publicación | `syncReferralGraceForOffer` marca o limpia `relatedListingUnpublishedAt` cuando cambia `publication.status`.
3. Limpiar relaciones vencidas | `cleanupExpiredReferralRelations` elimina vínculos fuera del periodo de gracia.
4. Enriquecer listings de lectura | `attachRelatedOfferSummaries` añade `relatedListing` y oculta datos privados cuando corresponde.
Gotchas & edge cases
[!WARNING] Si
relatedListingIdestá vacío, la validación devuelvenull. Eso significa que la relación queda sin comprobación.
[!WARNING]
cleanupExpiredReferralRelationsactúa sobre registros cuyo corte ya fue superado. El cálculo usa la hora actual menosREFERRAL_RELATED_OFFER_GRACE_MS.
[!CAUTION]
attachRelatedOfferSummariesborra la relación si la oferta no es visible para el visor. Esto ocurre cuando no hay propiedad compartida o la oferta no está publicada y el visor no es el dueño.
[!NOTE]
safeRelatedOfferSummarysolo expone ubicación completa cuando la oferta está publicada. También la expone siincludePrivateLocationes verdadero.
Related
Este módulo depende directamente de mongoose y del modelo Listing importado desde ../../models/Listing.js. También se integra con los campos publication.status, lifecycle.status, intent y requirement.referral de los documentos de anuncios.
Listing.findByIdse usa para validar el anuncio relacionado.Listing.findse usa para recuperar ofertas válidas y construir resúmenes.Listing.updateManyse usa para limpiar o sincronizar relaciones de referral.
El módulo usa dos niveles de visibilidad para el anuncio relacionado.
- Si el anuncio está publicado, el resumen puede incluir ubicación.
- Si no está publicado, solo el dueño puede conservar el vínculo visible.
- Si la relación no cumple la propiedad o visibilidad, el módulo la elimina del resultado.