listing-match-eligibility.js
Overview
Este archivo define el contrato de elegibilidad para Match en services/api/src/modules/listings/listing-match-eligibility.js. No implementa Match, ni scoring, ni ranking, ni digest. Su función es decidir qué Listing puede participar en un futuro Match y qué par de Listing tiene sentido comparar.
El archivo trabaja con reglas puras y deterministas. No consulta Mongo, no muta argumentos, y no crea Lead ni ListingEvent. También conserva compatibilidad con valores heredados, como intent, lifecycle.status y ownerAccountStatus ausentes.
[!IMPORTANT] Este archivo no implementa Match. Solo define elegibilidad y compatibilidad previa para un motor futuro.
How it works
flowchart TD
Caller[Caller] --> Single[getListingMatchEligibility]
Caller --> Pair[getListingPairMatchCompatibility]
Single --> Intent[Resolve intent]
Single --> Life[Resolve lifecycle status]
Single --> Pub[Resolve publication status]
Single --> Owner[Resolve owner status]
Intent --> SingleResult[Eligibility result]
Life --> SingleResult
Pub --> SingleResult
Owner --> SingleResult
Pair --> OfferCheck[Check offer candidate]
Pair --> ReqCheck[Check requirement candidate]
OfferCheck --> OfferSingle[getListingMatchEligibility]
ReqCheck --> ReqSingle[getListingMatchEligibility]
OfferSingle --> PairRules[Pair rules]
ReqSingle --> PairRules
PairRules --> PairResult[Compatible result]
ReqCheck --> Map[getCompatibleOfferOperationTypes]
Map --> PairRules
El flujo empieza con un Listing individual. Primero se resuelven sus valores efectivos, incluso cuando faltan campos. Luego, el archivo puede comparar dos Listing entre sí. Esa comparación añade reglas de par, como intent opuesto, entityType igual y compatibilidad de operationType.
Key components
{
"title": "getListingMatchEligibility",
"content": "Eval\u00faa si un Listing puede participar, en principio, en un futuro Match. Devuelve banderas derivadas, razones de rechazo y estado normalizado, sin efectos secundarios.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-match-eligibility.js",
"badges": [
"public"
]
}
{
"title": "getCompatibleOfferOperationTypes",
"content": "Devuelve los operationType v\u00e1lidos para un Offer candidato, a partir del operationType de un Requirement. La salida deriva de OPERATION_TYPE_EQUIVALENCE.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-match-eligibility.js",
"badges": [
"public"
]
}
{
"title": "getListingPairMatchCompatibility",
"content": "Compara dos Listings y decide si forman un par compatible para un futuro Match. Reutiliza la elegibilidad individual y agrega reglas de comparaci\u00f3n entre ambos candidatos.",
"type": "function",
"filePath": "services/api/src/modules/listings/listing-match-eligibility.js",
"badges": [
"public"
]
}
Data & fields
OPERATION_TYPE_EQUIVALENCE[badge:read-only]object— Mapa congelado de equivalencias entreoperationTypede oferta y requerimiento.listing[badge:required]object— Documento deListing. Puede serleanu objeto plano.ownerAccountStatus[badge:optional]string— Estado deUser.accountStatusya resuelto por el caller. Si falta, se asumeactive.meta.entityType[badge:optional]string— Tipo de entidad delListing. En comparación de pares, falta por defecto se interpreta comoproperty.meta.operationType[badge:optional]string— Tipo operativo delListing. Se usa para compatibilidad entre oferta y requerimiento.intent[badge:optional]string— Si no esrequirement, se resuelve comooffer.lifecycle.status[badge:optional]string— Si falta, se resuelve comonormal.publication.status[badge:optional]string— Si falta, se resuelve comodraft.
[
{
"name": "eligible",
"type": "boolean",
"required": true,
"description": "Indica si el Listing puede participar en principio en un futuro Match."
},
{
"name": "reasons",
"type": "string[]",
"required": true,
"description": "Lista de códigos de rechazo acumulados."
},
{
"name": "isPublished",
"type": "boolean",
"required": true,
"description": "Indica si publication.status es published."
},
{
"name": "isPubliclyVisible",
"type": "boolean",
"required": true,
"description": "Indica si el Listing está publicado y en lifecycle normal."
},
{
"name": "lifecycleStatus",
"type": "string | null",
"required": true,
"description": "Estado normalizado del lifecycle del Listing."
},
{
"name": "intent",
"type": "\"offer\" | \"requirement\" | null",
"required": true,
"description": "Intent normalizado del Listing."
},
{
"name": "ownerAvailable",
"type": "boolean",
"required": true,
"description": "Indica si el owner está disponible según ownerAccountStatus."
}
]
[
{
"name": "compatible",
"type": "boolean",
"required": true,
"description": "Indica si ambos Listings pueden compararse en un futuro Match."
},
{
"name": "reasons",
"type": "string[]",
"required": true,
"description": "Lista de códigos de rechazo acumulados por incompatibilidad de par."
},
{
"name": "offerEligibility",
"type": "object",
"required": true,
"description": "Resultado de getListingMatchEligibility para el candidato offer."
},
{
"name": "requirementEligibility",
"type": "object",
"required": true,
"description": "Resultado de getListingMatchEligibility para el candidato requirement."
}
]
LISTING_MISSING— No se recibiólisting.NOT_PUBLISHED—publication.statusno espublished.LIFECYCLE_NOT_NORMAL—lifecycle.statusno esnormal.OWNER_UNAVAILABLE—ownerAccountStatusno esactive.OFFER_NOT_ELIGIBLE— El candidato offer no pasó su elegibilidad individual.REQUIREMENT_NOT_ELIGIBLE— El candidato requirement no pasó su elegibilidad individual.OFFER_CANDIDATE_IS_NOT_OFFER— El offer candidato no tieneintentresuelto comooffer.REQUIREMENT_CANDIDATE_IS_NOT_REQUIREMENT— El requirement candidato no tieneintentresuelto comorequirement.ENTITY_TYPE_MISMATCH— Losmeta.entityTypeno coinciden.OPERATION_TYPE_MISMATCH— Losmeta.operationTypeno son compatibles.
Behaviors
resolveListingIntent— Normalizalisting.intent. Devuelverequirementsolo cuando el valor es exactamenterequirement. En cualquier otro caso, devuelveoffer.resolveLifecycleStatus— Normalizalisting.lifecycle.status. Si no existe, devuelvenormal.resolvePublicationStatus— Normalizalisting.publication.status. Si no existe, devuelvedraft.operationTypesAreCompatible— Compara dosoperationType. Devuelvefalsesi falta alguno. Devuelvetruesi son iguales. También acepta la equivalencia definida porOPERATION_TYPE_EQUIVALENCE.getListingMatchEligibility— Evalúa un soloListing. Calculaeligible,reasons,isPublished,isPubliclyVisible,lifecycleStatus,intentyownerAvailable.getCompatibleOfferOperationTypes— Devuelve un arreglo con losoperationTypecompatibles para un Offer candidato, a partir deloperationTypede un Requirement.getListingPairMatchCompatibility— Evalúa dosListingy decide si forman un par compatible. Reusa la elegibilidad individual y agrega validaciones de par.
Workflows
Evaluación de un solo Listing
sequenceDiagram
participant Caller
participant Eligibility as getListingMatchEligibility
Caller->>Eligibility: listing y ownerAccountStatus
Eligibility-->>Caller: eligible, reasons y banderas derivadas
La evaluación individual solo mira el estado del Listing y el estado del owner. La función calcula valores normalizados y agrega razones de rechazo cuando corresponda. No consulta persistencia, no hace escrituras y no depende de servicios externos.
Comparación de un par
sequenceDiagram
participant Caller
participant Pair as getListingPairMatchCompatibility
participant Single as getListingMatchEligibility
Caller->>Pair: offerCandidate y requirementCandidate
Pair->>Single: offerCandidate
Single-->>Pair: offerEligibility
Pair->>Single: requirementCandidate
Single-->>Pair: requirementEligibility
Pair-->>Caller: compatible y reasons
La comparación de par aplica primero la elegibilidad individual. Luego exige intent opuesto, mismo meta.entityType y compatibilidad de meta.operationType. Si cualquiera falla, el resultado final acumula razones.
Gotchas & edge cases
[!WARNING]
getListingMatchEligibilitytrata unListingsinintentcomooffer. Ese comportamiento es intencional y legado-safe.
[!WARNING]
getListingMatchEligibilitytrata unListingsinlifecycle.statuscomonormal. Ese default mantiene compatibilidad con datos antiguos.
[!IMPORTANT]
ownerAccountStatusno se resuelve dentro del archivo. El caller debe pasarlo ya calculado.
[!NOTE]
isPubliclyVisibleno significa “visible para Match”. Solo exigepublishedylifecycle normal.
[!CAUTION] El archivo no usa
publication.verified,phoneVerifiedAt,plannirolecomo evidencia de elegibilidad. Esos campos quedan fuera por diseño.
[!WARNING]
getListingPairMatchCompatibilityexige que el caller haya separado correctamenteofferCandidateyrequirementCandidate. Si no, devuelve razones comoOFFER_CANDIDATE_IS_NOT_OFFER.
Related
Este archivo comparte criterio con getListingInteractionPolicy en listing-interaction-policy.js. El comentario del archivo también lo alinea con DEFAULT_OWNER_PREFERENCES para el default permisivo de owner activo. Además, getCompatibleOfferOperationTypes deriva su lógica de OPERATION_TYPE_EQUIVALENCE y del patrón descrito en listings.validators.js.
También hace referencia a docs/01-product/future-roadmap.md, donde se sitúa Match como una capacidad futura todavía no diseñada.