digest-recommendations.js

Overview

Este archivo define el motor determinista de recomendaciones del digest. Su función es generar hasta tres mensajes de acción a partir de datos reales de cada listing, sin usar IA generativa ni sugerencias no verificables.

El módulo trabaja con reglas de prioridad fija. Evalúa listings con periodTotals asociados y devuelve recomendaciones en forma de strings listas para mostrar en el digest. También exporta los key de las reglas para identificación consistente en otras partes del sistema.

[!IMPORTANT] Las recomendaciones dependen solo de señales comprobables del listing y sus totales de período. El módulo no inventa acciones sin respaldo en los datos.

How it works

flowchart TD
    A[buildDigestRecommendations] --> B[RULES]
    B --> C[Rule 1]
    B --> D[Rule 2]
    B --> E[Rule 3]
    B --> F[Rule 4]
    B --> G[Rule 5]
    A --> H[MAX_RECOMMENDATIONS]
    A --> I[Array of listing and periodTotals]
    C --> J[Recommendation strings]
    D --> J
    E --> J
    F --> J
    G --> J
    J --> K[Up to 3 recommendations]

El flujo recorre las reglas en orden y busca el primer listing que cumpla cada una. Si encuentra coincidencia, agrega el texto construido por esa regla. El proceso termina cuando alcanza MAX_RECOMMENDATIONS, que vale 3.

[!NOTE] Una misma regla solo puede aportar una recomendación por digest. El módulo no repite la misma regla para varios listings dentro de la misma ejecución.

Key components

{
    "title": "MAX_RECOMMENDATIONS",
    "content": "Constante p\u00fablica que limita a 3 el n\u00famero total de recomendaciones devueltas por digest.",
    "type": "enum",
    "filePath": "services/api/src/modules/digest/digest-recommendations.js",
    "badges": [
        "public",
        "read-only"
    ]
}
{
    "title": "hasText",
    "content": "Funci\u00f3n auxiliar que verifica si un valor contiene texto \u00fatil despu\u00e9s de convertirlo a string y recortarlo.",
    "type": "function",
    "filePath": "services/api/src/modules/digest/digest-recommendations.js",
    "badges": [
        "public",
        "read-only"
    ]
}
{
    "title": "isOptimizable",
    "content": "Funci\u00f3n auxiliar que permite reglas de optimizaci\u00f3n solo cuando el listing no est\u00e1 archivado.",
    "type": "function",
    "filePath": "services/api/src/modules/digest/digest-recommendations.js",
    "badges": [
        "public",
        "read-only"
    ]
}
{
    "title": "RULES",
    "content": "Lista ordenada de reglas deterministas. Cada regla define una condici\u00f3n de coincidencia y el texto que se mostrar\u00e1 en el digest.",
    "type": "enum",
    "filePath": "services/api/src/modules/digest/digest-recommendations.js",
    "badges": [
        "read-only"
    ]
}
{
    "title": "buildDigestRecommendations",
    "content": "Funci\u00f3n p\u00fablica que recibe listings con totales de per\u00edodo y devuelve hasta 3 recomendaciones en texto plano.",
    "type": "function",
    "filePath": "services/api/src/modules/digest/digest-recommendations.js",
    "badges": [
        "public"
    ]
}
{
    "title": "DIGEST_RECOMMENDATION_RULE_KEYS",
    "content": "Exportaci\u00f3n derivada con los identificadores de todas las reglas en el mismo orden en que se eval\u00faan.",
    "type": "enum",
    "filePath": "services/api/src/modules/digest/digest-recommendations.js",
    "badges": [
        "public",
        "read-only"
    ]
}

Data & fields

[
    {
        "name": "listingsWithPeriodTotals",
        "type": "Array<{ listing, periodTotals }>",
        "description": "Entrada principal de buildDigestRecommendations. Cada elemento combina un listing con sus totales del período."
    },
    {
        "name": "listing",
        "type": "object",
        "required": true,
        "description": "Objeto del listing evaluado por las reglas.",
        "children": [
            {
                "name": "title",
                "type": "string",
                "required": true,
                "description": "Nombre visible del listing. Se usa en el texto de la recomendación."
            },
            {
                "name": "publication.status",
                "type": "string",
                "description": "Estado de publicación usado por varias reglas. Los valores observados en el archivo son draft, published y archived."
            },
            {
                "name": "media",
                "type": "Array",
                "description": "Colección de medios del listing. La regla add_photos la usa para contar fotos."
            },
            {
                "name": "description.short",
                "type": "string",
                "description": "Descripción corta del listing. Se valida con hasText."
            },
            {
                "name": "description.full",
                "type": "string",
                "description": "Descripción completa del listing. Se valida con hasText."
            },
            {
                "name": "contact.phone",
                "type": "string",
                "description": "Teléfono de contacto del listing. Se valida con hasText."
            },
            {
                "name": "contact.link",
                "type": "string",
                "description": "Enlace de contacto del listing. Se valida con hasText."
            }
        ]
    },
    {
        "name": "periodTotals",
        "type": "object",
        "description": "Totales agregados del período para el listing.",
        "children": [
            {
                "name": "detailOpens",
                "type": "number",
                "description": "Cantidad de aperturas de detalle del período. La regla share_listing exige que sea 0."
            }
        ]
    }
]

Campos usados por las reglas

  • listing.publication.status [badge:optional] string — Determina si el listing está en draft, published o archived.
  • listing.media [badge:optional] array — Se cuenta para detectar listings con menos de 3 fotos.
  • listing.description.short [badge:optional] string — Se usa para evaluar si la descripción está incompleta.
  • listing.description.full [badge:optional] string — Se usa junto con description.short para validar el texto descriptivo.
  • listing.contact.phone [badge:optional] string — Se valida para detectar contacto incompleto.
  • listing.contact.link [badge:optional] string — Se valida junto con contact.phone.
  • listing.title [badge:required] string — Se inserta en el texto final de cada recomendación.
  • periodTotals.detailOpens [badge:optional] number — Se usa para detectar listings publicados sin aperturas recientes.

Behaviors

  • hasText(value) — Convierte cualquier valor a texto, lo recorta y devuelve true si queda contenido.
  • isOptimizable(listing) — Devuelve true si el listing no está archivado.
  • RULES — Define cinco reglas evaluadas en orden de prioridad.
  • buildDigestRecommendations(listingsWithPeriodTotals) — Recorre las reglas y devuelve hasta tres mensajes.
  • DIGEST_RECOMMENDATION_RULE_KEYS — Expone los identificadores de las reglas en orden estable.

Reglas de recomendación

La lista RULES contiene cinco entradas con key, matches y build. Cada una cubre una situación concreta del listing.

  • publish_draft

    • Matchea cuando listing.publication.status === 'draft'.
    • Genera el mensaje para publicar el borrador.
    • Tiene la mayor prioridad.
  • add_photos

    • Matchea solo si el listing es optimizable.
    • Exige que no sea draft.
    • Se activa con menos de 3 elementos en listing.media.
    • Sugiere agregar más fotos.
  • complete_description

    • Matchea solo si el listing es optimizable.
    • Exige que description.short y description.full estén vacíos.
    • Sugiere completar la descripción.
  • review_contact

    • Matchea solo si el listing es optimizable.
    • Exige que contact.phone y contact.link estén vacíos.
    • Sugiere revisar los datos de contacto.
  • share_listing

    • Matchea solo si el listing está publicado.
    • Exige periodTotals.detailOpens === 0.
    • Sugiere compartir el listing por falta de aperturas.

How the selection logic behaves

buildDigestRecommendations recibe un array de objetos con listing y periodTotals. Luego evalúa cada regla en el mismo orden en que aparece en RULES.

Si una regla coincide, toma el primer listing que la cumple y agrega su mensaje. Después pasa a la siguiente regla. El algoritmo no agrega dos veces la misma regla en un digest.

[!WARNING] buildDigestRecommendations no agrega recomendaciones por listing, sino por regla. Eso limita el resultado a una selección global de hasta tres mensajes.

Workflows

1. Recibir listings | La función recibe un array de objetos con `listing` y `periodTotals`.
2. Evaluar reglas | Recorre `RULES` en orden de prioridad.
3. Buscar primera coincidencia | Para cada regla, toma el primer listing que cumple `matches`.
4. Construir mensaje | La regla ejecuta `build` y produce un string final.
5. Limitar salida | El proceso se detiene al llegar a `MAX_RECOMMENDATIONS`.

Gotchas & edge cases

[!WARNING] Los listings archivados quedan excluidos de todas las recomendaciones de optimización y publicación, salvo la regla publish_draft, que ya no puede matchear un archivado porque exige draft.

[!NOTE] share_listing depende de periodTotals.detailOpens. Si ese valor falta, se interpreta como 0 por el uso de Number(... || 0).

[!CAUTION] La función hasText considera vacío cualquier valor nulo, indefinido, numérico convertido a cadena vacía o texto con solo espacios.

Este módulo pertenece a services/api/src/modules/digest/digest-recommendations.js y trabaja sobre la estructura de digest agregada que incluye listing y periodTotals. Su diseño encaja con un digest determinista, donde las recomendaciones salen de reglas verificables y no de un proveedor externo.

Las reglas se apoyan en campos de publicación, medios, descripción, contacto y actividad del período. Eso conecta este archivo con la capa de agregación previa que prepara los listings para recomendación.