Saltar al contenido
Proyectos

Radar de Precios

En vivo usable

Emparejo 296 medicamentos entre cadenas de farmacias peruanas leyendo sus propias APIs no documentadas.

Compara el precio del mismo medicamento entre cadenas de farmacias peruanas. Resuelve el problema difícil: decidir que dos SKU distintos son el mismo producto, por código y por principio activo.

Tecnología

  • Scraping
  • Visualización

Sector

  • Salud
  • Retail & negocio

Problema

El mismo medicamento puede costar entre 20% y 70% más en una cadena de farmacias peruanas que en otra, y dos de las tres cadenas más grandes del país pertenecen al mismo holding: nada obliga a la transparencia de precios y no existe una herramienta pública que permita comparar antes de entrar a la tienda.

Enfoque

Adaptadores por cadena sobre APIs no documentadas (Algolia, SFCC) y un matcher de tres capas que decide, sin catálogo de producto compartido, cuándo dos listados son el mismo medicamento.

Inkafarma y Mifarma pertenecen al mismo holding y corren sobre el mismo Algolia del holding, así que sus listados comparten un ID interno de producto y cruzan gratis. Boticas Perú, en cambio, corre sobre Salesforce Commerce Cloud sin ningún ID compartido: su adaptador combina la grilla HTML pública con el JSON de QuickView de la propia tienda. Ninguna de estas es una API oficial: cada adaptador lee las mismas llaves y endpoints públicos de solo-búsqueda que las propias tiendas de cada cadena llaman desde el navegador, con un ritmo muy por debajo de cualquier cosa que pareciera abuso.

Cruzar entre cadenas sin ID compartido es el problema real que resuelve el proyecto. El matcher corre en tres capas: ID exacto donde existe, texto difuso sobre nombre, principio activo y presentación (con guardas contra combos y variantes casi idénticas como distintas dosis de vitaminas) donde no existe, y hash perceptual de imagen como verificación final sobre cruces difusos dudosos. Cada corrida se guarda como su propio snapshot en vez de sobrescribirse, así que el historial de precios y la detección de promociones son posibles después sin volver a scrapear el pasado.

El sitio publicado es deliberadamente simple: HTML/CSS/JS estático sin build, que lee un JSON en runtime y arma la tabla comparativa en el cliente, así que no cuesta nada de hosting y funciona en cualquier subcarpeta sin cambios.

Resultado

productos cruzados entre cadenas
296
web/data.json
cruzados con precio de Boticas Perú
100
web/data.json
cruzados en las tres cadenas
64
web/data.json

Estándares

Calidad de software (ISO/IEC 25010) No se corrió un checklist formal de ISO/IEC 25010. El riesgo de calidad más duro -falsos cruces de producto- se ataca directo: matcher de tres capas (ID exacto, texto difuso, hash perceptual de imagen) con guardas documentadas contra combos y variantes casi idénticas.
Rendimiento web (Core Web Vitals) no medido
Accesibilidad (WCAG 2.2) no medido
Software de investigación (FAIR4RS) n/a — no es software de investigación
Higiene de seguridad Solo se usan llaves públicas de solo-búsqueda de Algolia (visibles en el bundle JS de cada cadena); el README confirma que nunca se commiteó ninguna en los 34 commits del repo.
Reproducibilidad requirements.txt fijado; .env.example documenta las credenciales necesarias; un módulo de pytest cubre el matcher. Sin contenedor todavía.
Metodología de benchmark sin benchmark declarado
Documentación de datos / modelo n/a
Versionado y registro de cambios Sin SemVer; historial de commits plano (34 commits) en un repo público.

Qué haría después

  • Sumar historial de precios a partir de los snapshots ya guardados por corrida, en vez de mostrar solo la última.
  • Automatizar la corrida programada (hoy manual) y alertar cambios de precio por promociones.
  • Extender el matcher a una cuarta cadena cuando exista un adaptador para su tienda.

Arquitectura

Comparador de precios de medicamentos: una corrida diaria graba cuatro catálogos públicos, cruza productos equivalentes y publica un JSON a una web estática.

  • PC Windows
  • Hostinger
  • Navegador del usuario
run --todocapturar()Algolia + detalleAlgolia + detalleHTML + QuickViewcatalog search JSONgraba respuestasreproduce sin redexportar()rclone copyrclone copystagingFTPS / SFTPHTTPS GET data.jsonprocesar()exportar()pc windows · tarea diariaTarea programadaschtasks · 02:00 ·corrida_diaria.cmdpython · orquestadorpipeline.runcapturar · procesar · validar· exportar · sincronizarpython · motor genéricoAdaptadores y sesión HTTPhttpx · pausas por dominio ·backoff ante 429 y 503 ·SesionHttp graba, reanuda oreproduceexterno · catálogo públicoCadena A (Algolia)búsqueda y detalle por presentaciónexterno · catálogo públicoCadena B (Algolia)mismo esquema que la cadena Aexterno · catálogo públicoCadena C (SFCC)HTML de listado + QuickView JSONexterno · catálogo públicoCadena D (VTEX)catalog search legacy · trae EANarchivos · disco del pcCrudo comprimidorespuestas por cadena ycorrida · manifiesto · fotospython · núcleo de cruceNormalizador y matcherid/EAN · texto difuso + reglasduras · pHasharchivos · disco del pcStaging e históricodata.json · snapshots ·eventos CSV · Parquetexterno · almacén en la nubeAlmacén en la nuberclone copy · crudo y Parquetpython · a manoPublicadorcomparar · confirmar · subirhostinger · archivos estáticosHosting estáticoweb/ · data.jsonpersona · navegadorVisitantecálculo en el clienteC4 · nivel 2 (contenedores) · tráfico ambiente = llamadas reales · clic en una caja para el detallelínea continua = HTTP/SQL · punteada = adaptador elegido por configuración · caja punteada = existe fuera del proyecto
abrir en pestaña ↗

Flujos que se pueden reproducir

  1. Corrida diaria
  2. Reproceso sin red
  3. Publicar a mano
  4. Una visita