services/api/src/modules/listings/requirement-refinement.js
Overview
Este módulo define la autoridad estructurada para refinar un Requirement propio ya existente. Su función es distinguir entre cambios que refinan la misma búsqueda y cambios que pivotan la intención, y luego aplicar el cambio con validación reusada del dominio. El archivo se presenta como la única fuente de verdad para esa clasificación, y evita que otros módulos repliquen la lógica de decisión.
También mantiene el refinamiento dentro de un alcance conservador. Solo expone campos cuya validación ya existe en listings.validators.js, y reserva algunos campos como pivotes de intención o como no soportados. El flujo está pensado para ser usado por conversations.routes.js hoy, y por un futuro RequirementInterpreter o agente autorizado después.
[!IMPORTANT] Este módulo no traduce el resultado a HTTP. Devuelve un
outcomeestructurado y deja esa traducción al caller.
How it works
El proceso sigue una secuencia corta y determinista. Primero clasifica todos los campos propuestos. Después consulta Mongo solo si no hay pivotes ni campos desconocidos. Luego valida el nuevo requirement con validateListingPayload, y finalmente persiste el cambio si el documento sigue siendo elegible para edición.
sequenceDiagram
participant C as Caller
participant R as Requirement refinement
participant L as Listing model
participant V as validateListingPayload
C->>R: applyRequirementRefinement
R->>R: classify all fields
alt no changes
R-->>C: empty
else pivot field
R-->>C: pivot
else unsupported field
R-->>C: unsupported
else continue
R->>L: findOne
L-->>R: requirement or none
alt not found
R-->>C: not_found
else deletion pending
R-->>C: deletion_pending
else valid candidate
R->>V: validateListingPayload
V-->>R: valid or invalid
alt invalid
R-->>C: invalid
else write update
R->>L: findOneAndUpdate
L-->>R: updated or none
alt updated
R-->>C: applied
else conflict
R->>L: findOne
L-->>R: deletion status
R-->>C: deletion_pending or not_found
end
end
end
end
El diagrama refleja el orden real del código. La clasificación ocurre antes de tocar Mongo. La lectura usa un filtro de propiedad y vida editable. La escritura repite el filtro y exige que deletion siga en null. Si esa condición falla, el módulo reconsulta el estado actual para decidir entre deletion_pending y not_found.
Key components
Este archivo exporta dos funciones públicas. También define tres conjuntos de apoyo para clasificar campos y filtrar documentos editables. La lógica está orientada a listas de campos planas, no a rutas anidadas.
{
"title": "classifyRefinementField",
"content": "Clasifica un campo como supported, pivot o unsupported seg\u00fan listas fijas del m\u00f3dulo.",
"type": "function",
"filePath": "services/api/src/modules/listings/requirement-refinement.js",
"badges": [
"public"
]
}
{
"title": "applyRequirementRefinement",
"content": "Aplica un refinamiento estructurado sobre un Requirement propio, validando, leyendo y escribiendo el documento en una sola ruta de dominio.",
"type": "function",
"filePath": "services/api/src/modules/listings/requirement-refinement.js",
"badges": [
"public"
]
}
Constantes de clasificación
Estas constantes no se exportan como API pública adicional, pero sí definen el comportamiento del módulo. Su contenido está fijado con Object.freeze, así que no cambia en tiempo de ejecución.
-
REFINEMENT_SUPPORTED_FIELDS[badge:public]readonly string[]— Campos que refinan la misma búsqueda.budgetdesiredAmenitiespreferenceshasPetspetFriendlyRequiredmaximumAcceptedTermValuemaximumAcceptedTermUnitsearchZonessearchZonesse añadió en M3.3b y se reemplaza completo, no por índice. La validación sigue siendo la misma devalidateListingPayload.
-
REFINEMENT_PIVOT_FIELDS[badge:read-only]readonly string[]— Campos que cambian qué se busca.intententityTypeoperationTypepropertyTypeserviceTypeinvestmentTypeEstos campos viven fuera derequirement.*porque afectan el universo de ofertas comparables.
-
EDITABLE_LIFECYCLE_FILTER[badge:read-only]object— Filtro de vida editable.- Excluye
protection - Excluye
anonymized - Excluye
purgedEl módulo permite refinar requisitos en borrador o archivados, siempre que no estén en esos estados.
- Excluye
Data & fields
Este módulo trabaja con un objeto de cambios plano. Cada llave entra en una clasificación previa, y luego se aplica como reemplazo de un solo nivel sobre requirement. Los campos no mencionados en changes se conservan intactos.
[
{
"name": "requirementId",
"type": "string",
"required": true,
"description": "ID del Requirement propio que se quiere refinar."
},
{
"name": "ownerId",
"type": "string",
"required": true,
"description": "ID del propietario. Debe coincidir con createdBy."
},
{
"name": "changes",
"type": "object",
"required": true,
"description": "Mapa plano de campos de refinamiento. Cada llave se clasifica antes de leer o escribir Mongo.",
"children": [
{
"name": "budget",
"type": "object",
"description": "Reemplaza el objeto completo de budget."
},
{
"name": "desiredAmenities",
"type": "array",
"description": "Reemplaza el arreglo completo de amenidades deseadas."
},
{
"name": "preferences",
"type": "object",
"description": "Reemplaza el objeto completo de preferencias."
},
{
"name": "hasPets",
"type": "boolean|null",
"description": "Actualiza el indicador de mascotas permitido."
},
{
"name": "petFriendlyRequired",
"type": "boolean|null",
"description": "Actualiza la preferencia de que el inmueble sea pet friendly."
},
{
"name": "maximumAcceptedTermValue",
"type": "number|null",
"description": "Actualiza el valor máximo aceptado del plazo."
},
{
"name": "maximumAcceptedTermUnit",
"type": "string|null",
"description": "Actualiza la unidad del plazo aceptado."
},
{
"name": "searchZones",
"type": "array",
"description": "Reemplaza el arreglo completo de zonas de búsqueda. Usa la validación existente de listings.validators.js."
}
]
}
]
La función devuelve un objeto con una de estas formas. No lanza para casos de negocio esperados. Solo puede lanzar por errores reales de infraestructura, como fallos de Mongo.
{ outcome: "empty" }— No había cambios útiles.{ outcome: "pivot", field }— El conjunto incluía un campo de intención.{ outcome: "unsupported", field }— El conjunto incluía un campo no soportado.{ outcome: "invalid", errors }— La validación del payload falló.{ outcome: "not_found" }— El requisito no era editable o no pertenecía al usuario.{ outcome: "deletion_pending" }— El requisito tenía eliminación pendiente.{ outcome: "applied", requirement, changedFields }— El cambio se aplicó con éxito.changedFieldsconserva las llaves recibidas enchanges.
Behaviors
Estas son las responsabilidades observables del archivo. Cada comportamiento está respaldado por el código o por su cobertura de prueba.
classifyRefinementField(field)— Devuelvesupported,pivotounsupported.applyRequirementRefinement({ requirementId, ownerId, changes })— Orquesta lectura, clasificación, validación y escritura del refinamiento.- Clasificación total antes de persistencia — Evalúa todas las llaves antes de tocar Mongo.
- Prioridad de pivote — Un campo de pivote gana aunque también exista un desconocido.
- Prioridad de rechazo — Un campo desconocido devuelve
unsupportedsin aplicar cambios. - Lectura con propiedad y vida editable — Busca solo requisitos del dueño y con estado editable.
- Rechazo de eliminación pendiente — Devuelve
deletion_pendingsi el documento ya está marcado. - Merge superficial de un nivel — Sustituye el subobjeto o arreglo completo del campo afectado.
- Validación reusada — Pasa el payload final por
validateListingPayload. - Escritura condicional — Actualiza solo si el documento sigue sin
deletion. - Resolución de carreras — Si la escritura no coincide, reconsulta para decidir el resultado.
[!NOTE]
preferencesconserva su semántica real de triestado por campo. No se colapsa a un booleano en este módulo.
[!WARNING]
searchZonesse reemplaza completo. No existe mutación parcial por índice en este archivo.
Workflows
Este flujo resume la secuencia de ejecución que implementa el módulo. El comportamiento es conservador y no intenta resolver interpretación libre.
1. Recibir cambios | El caller envía `requirementId`, `ownerId` y un objeto `changes`.
2. Clasificar llaves | Cada llave se evalúa como `supported`, `pivot` o `unsupported`.
3. Cortar temprano | Si hay pivote, campo desconocido o cambios vacíos, el módulo responde sin tocar Mongo.
4. Leer el Requirement | Se busca el documento propio con filtro de propietario, intención y vida editable.
5. Verificar estado | Si no existe o tiene eliminación pendiente, se devuelve un outcome negativo.
6. Fusionar y validar | Se aplica un merge superficial y se reusa `validateListingPayload`.
7. Escribir condicionalmente | Se actualiza solo si el documento sigue vigente y sin eliminación.
8. Resolver conflicto | Si la actualización no coincide, se reconsulta el estado actual y se decide el outcome final.
Gotchas & edge cases
Este módulo evita varios errores de integración comunes. Las condiciones esperadas se transforman en outcome, no en excepciones.
[!WARNING]
intent,entityType,operationType,propertyType,serviceTypeeinvestmentTypenunca son refinamiento. Siempre se tratan como pivote.
[!CAUTION] La validación de
requirement.*no se reimplementa aquí. La autoridad sigue siendovalidateListingPayload.
[!IMPORTANT] El módulo no aplica cambios parciales cuando el objeto mezcla un campo soportado con un pivote o desconocido. La clasificación previa bloquea toda la operación.
[!NOTE] Un
Requirementen borrador o archivado puede seguir siendo refinable. No se exigepublication.status = published.
[!WARNING] Si el documento cambia entre lectura y escritura, el resultado puede degradarse a
not_foundodeletion_pending. Esa lógica está prevista por el módulo.
Related
Este módulo se conecta con varias piezas del dominio de listings y conversaciones. Las relaciones están explícitas en los comentarios del archivo y en la cobertura de pruebas.
services/api/src/models/Listing.js— Fuente del schema que distinguerequirement.*demeta.*.services/api/src/modules/listings/listings.validators.js— Validador reutilizado para todos los cambios.services/api/src/modules/conversations/conversations.routes.js— Orquestación actual del refinamiento.services/api/src/modules/listings/candidate-retrieval.js— Usameta.*para definir el universo de ofertas.services/api/src/modules/listings/listing-match-eligibility.js— Participa en la decisión de comparación inicial.services/api/test/requirement-refinement.test.js— Verifica clasificación, outcomes y reuso de validación.
[!TIP] El contrato del módulo está alineado con la fase M3.3 y su extensión M3.3b para
searchZones.