Saltar a contenido

ADR-0005 · Precios de competencia: comando único, estados derivados y consumo directo de tablas

  • Estado: aceptado
  • Decisores: hquintero
  • Fecha de la decisión: 2026-08-25

Contexto extenso: RFC 008 (decisiones D1–D28) y definición de requisitos aprobada. Este ADR registra solo las decisiones que se apartan de patrones establecidos del proyecto o que comprometen a futuro.

Contexto y problema

El módulo de precios de competencia (RFC 008) observa diariamente, vía ScrapeOps, el precio de productos propios en las páginas de 2-3 competidores con URL curada a mano, y persiste el último valor para consumo analítico externo. Al diseñarlo hubo que decidir cómo ejecuta el proceso diario, cómo representa el resultado de cada relevamiento y qué superficie de salida ofrece — y en los tres puntos la respuesta se aparta de lo que el proyecto venía haciendo.

Opciones consideradas

  1. Ejecución: comando único con concurrencia interna / job por URL en cola propia (patrón de todas las integraciones de salida, ADR-0003) / cola con Bus::batch.
  2. Estado del relevamiento: derivarlo de dos fechas / columna de estado + contador de fallos + motivo del último error.
  3. Salida: consumo directo de las tablas / vista SQL estable / endpoint API.
  4. Extracción: cadena de reglas configurable en datos por competidor / parser codificado por sitio (patrón del scraping existente de comprasparaguai).

Decisión

1. El proceso diario es un comando único, sin cola. La corrida necesita principio y fin para emitir cabecera y resumen (el resumen es la alerta de cambio de layout); el lote diario es chico por diseño (la frecuencia por competidor reparte el universo entre días); y nada compite por esa ventana. Un job por URL exigiría Bus::batch —patrón que el proyecto no usa— solo para recuperar el cierre de corrida. Los reintentos van inline; los contadores, en memoria del comando.

2. El estado del relevamiento se deriva, nunca se almacena. Cada vínculo producto×competidor guarda dos fechas: último intento y último éxito. Si no coinciden, la última corrida falló; si el éxito es más viejo que el umbral configurado, el vínculo está roto; la distancia entre éxito y hoy es la antigüedad del dato. No existen columna de estado de error, contador de fallos ni motivo persistido — el motivo va al log (una línea por URL, canal propio). Una fecha no puede quedar desincronizada del hecho que representa; un campo de estado sí.

3. No hay superficie de salida: las herramientas externas leen las tablas. Todo lo relacionado a analítica queda fuera del módulo — incluida una vista SQL. Lo que el consumidor necesita saber (solo filas active, precios en PYG, estados derivados) se documenta en RFC 008 §8.5, no se implementa.

4. La extracción es configuración, no código. Cada competidor define, como datos, una cadena ordenada de reglas por campo (datos estructurados schema.org primero, selectores propios como respaldo). Adaptarse a un rediseño del sitio es editar la configuración y probarla desde el ABM, sin despliegue. Es la diferencia deliberada con el scraping de comprasparaguai, que tiene los selectores en el código del servicio.

Consecuencias

Positivas

  • Cero infraestructura nueva de colas, workers o estado compartido; el módulo entero se opera con un comando, dos tablas, un config y un canal de log.
  • Los estados no pueden mentir: no hay sincronización entre columnas que mantener.
  • El mantenimiento ante cambios de sitios queda en manos de operaciones, no de desarrollo.
  • El módulo no toca el pricing interno ni los procesos de sincronización existentes.

Negativas / deuda asumida

  • Las tablas competitors / competitor_prices se vuelven interfaz pública de facto. Cualquier cambio de esquema puede romper las consultas del consumidor externo sin que ningún test lo detecte. Renombrar o alterar columnas exige coordinar con quienes consumen; si esos consumidores se multiplican, este punto es el primer motivo para reabrir la decisión 3 (una vista sería entonces la capa de compatibilidad).
  • Sin cola no hay reintento con backoff por ítem (ADR-0003): un fallo transitorio espera a la próxima corrida diaria. Aceptado: el dato es analítico, no operativo.
  • La regla "solo filas active" y la derivación de estados viven en documentación y en cada consumidor, no en una implementación única.
  • El comando diario debe vigilar su duración: si el universo relevado crece hasta que la corrida no entra en su ventana, la decisión 1 se revisa (el límite por ráfaga previsto en RFC 008/D16 es la primera válvula).