Resumen de decisiones
- «Casi real», no «tiempo real». Los precios se refrescan por demanda (lo que está en listas activas primero) con vigencia por categoría: perecederos cada 24 h, abarrotes cada 3 a 7 días. Cada precio muestra tienda, fuente y hora. Prometer tiempo real obligaría a golpear los sitios de las cadenas a un ritmo que ninguna aceptaría.
- Tres capas de fuentes, en este orden de preferencia: convenio o API oficial con la cadena; datos públicos (SIMPAH de la FHIA y boletines semanales de la SDE) como precio de referencia; lectura respetuosa de páginas públicas de producto sólo donde robots.txt y términos lo permitan, con identificación, límite de ritmo y botón de apagado por tienda. Los precios reportados por usuarios cubren tiendas sin presencia en línea.
- Una PWA liviana (Preact + TypeScript + Vite, Workbox, IndexedDB con Dexie) contra una API NestJS con Postgres y Redis. Los conectores corren como workers en cola (BullMQ), nunca desde el teléfono del usuario.
- Todo el cálculo de lista corre en el cliente sobre un paquete de precios por ciudad descargado y cacheado. Escalar porciones, convertir unidades, redondear a empaques y comparar tiendas funciona sin señal.
- Dividir entre tiendas es una recomendación con umbral, no un valor por defecto: sólo se sugiere cuando el ahorro neto supera el costo del viaje o envío extra y un mínimo configurable (L 25 o 5 %).
- Primer cliente: Tegucigalpa y San Pedro Sula, que es donde La Colonia y Walmart entregan en línea. El resto del país arranca con precios de referencia y reportes de usuarios.
Contexto hondureño que condiciona el diseño
Presupuesto de JavaScript inicial menor a 150 KB comprimidos y cero dependencias pesadas. iOS existe pero es minoría; sus limitaciones de PWA se diseñan, no se ignoran.
Cada megabyte cuesta. Descargas grandes sólo con Wi-Fi y consentimiento; catálogo por ciudad, no nacional; respeto al encabezado Save-Data.
La gente compra frijoles por libra, huevos por cartón de 30 (o docena), culantro por manojo. La app habla en esas unidades y convierte a gramos sólo por dentro.
La Colonia, Walmart y Paiz venden en línea en Tegucigalpa y San Pedro Sula. PriceSmart tiene catálogo público para Honduras. Maxi Despensa y Despensa Familiar no tienen tienda en línea propia; PedidosYa revende con recargo.
La FHIA publica precios diarios de mercados mayoristas y minoristas desde 1996. La SDE publica cada semana precios de referencia de la canasta básica. Sirven como piso de comparación y para tiendas sin dato.
La lista se comparte como texto plano por WhatsApp desde el primer día. Es más útil que cualquier función de «invitar a la familia» dentro de la app.
Alcance funcional
| Módulo | Qué hace | Corre en |
|---|---|---|
| Recetario | Catálogo curado de platos hondureños y de uso diario, con porciones base, tiempos, pasos e ingredientes ligados al catálogo canónico. Búsqueda y filtros por ocasión, tiempo y costo estimado. | Cliente, con datos sincronizados |
| Escalado de porciones | Ajusta cantidades de 1 a 50 personas con reglas por tipo de ingrediente y redondeo «de cocina» (½ taza, 1 cartón, 2 libras). | Cliente |
| Conversión de unidades | Masa, volumen y unidades discretas con densidad por ingrediente. Traduce «2 tazas de frijol» a gramos y luego a «1 libra». | Cliente |
| Lista de compras | Combina varias recetas, consolida ingredientes repetidos, descuenta la despensa del usuario, agrupa por categoría y permite ítems manuales. | Cliente |
| Precios y disponibilidad | Precio actual, promoción, disponibilidad y última verificación por tienda, más precio de referencia público cuando no hay dato de tienda. | Servidor (conectores) y cliente (caché) |
| Sustitutos | Alternativas por ingrediente con proporción y contexto («queso seco por queso duro 1:1; crema por mantequilla sólo para untar»). Se ofrecen cuando falta el producto o cuando el sustituto ahorra más de un umbral. | Cliente |
| Comparador | Costo total de la lista por tienda, cobertura, faltantes, diferencia contra la más barata y ahorro. | Cliente |
| Repartir compra | Asignación de cada ítem a la tienda más conveniente considerando costo fijo por tienda extra, con recomendación explícita de «una sola» o «dividir». | Cliente |
| Reporte de precios | El usuario registra un precio visto en góndola (foto opcional). Se valida contra el histórico y votos de otros usuarios. | Cliente y servidor |
Arquitectura propuesta
Stack y por qué
| Capa | Elección | Motivo |
|---|---|---|
| Cliente | Preact + TypeScript + Vite, enrutado por hash o preact-iso | Misma ergonomía que React con un runtime de unos 4 KB. Importa en Android de gama baja. |
| Estado y persistencia | Dexie (IndexedDB) + store liviano (Zustand o señales de Preact) | Consultas indexadas sobre miles de productos sin cargar todo en memoria. |
| Service Worker | Workbox con precache del shell y estrategias por ruta | Instalable, offline por defecto, actualización controlada. |
| API | NestJS + Postgres + Redis, desplegado en la VM de GCP existente o en Cloud Run | Coincide con lo que ya se opera en otros proyectos; un solo binario, fácil de vigilar. |
| Colas | BullMQ sobre Redis | Reintentos, prioridad por demanda, límite de ritmo por host, un worker por conector. |
| Conectores | HTTP simple con undici; Playwright sólo si la página exige JavaScript | Un navegador sin cabeza es pesado y más agresivo con el sitio origen; se usa como último recurso. |
| Estáticos | Cloudflare delante del origen; paquetes JSON gzip por ciudad | Un precio consultado por mil usuarios se sirve desde el borde, no desde Postgres. |
| Observabilidad | Logs estructurados, métrica por conector (páginas, errores, cambios), alerta si un conector cae a cero | Un scraper que se rompe en silencio produce precios viejos con cara de nuevos. |
Fuentes de datos y estrategia de integración ética
El principio operativo: la app compara precios para ayudar a comprar, no copia catálogos. Sólo se guarda lo que hace falta para las listas de los usuarios, siempre con enlace a la tienda y atribución de fuente. Cada conector es un módulo con una ficha de cumplimiento revisada antes de encenderse y un interruptor para apagarlo en un minuto si una cadena lo pide.
Tres niveles, en orden de preferencia
reference_prices y sirven para tres cosas: estimar faltantes, detectar precios reportados absurdos y mostrar «precio de mercado» en productos frescos.Estado de cada fuente al 9 de septiembre de 2026
| Fuente | Qué se verificó | Decisión |
|---|---|---|
| La Colonia (lacolonia.com) | robots.txt permite páginas de producto vía sitemap y prohíbe /api*, /busca*, /quick-view* y el subdominio móvil. Los términos prohíben violar seguridad o acceder a datos no destinados al usuario; no mencionan scraping. Entrega en Tegucigalpa y San Pedro Sula. | Nivel 3 con carta previa Sólo URLs del sitemap. Nunca la API interna ni el buscador. |
| Walmart / Paiz (walmart.com.hn, paiz.com.hn) | robots.txt sólo bloquea cuenta, login, checkout y vistas rápidas; publica sitemaps de ambas marcas. Tienda en línea con entrega a domicilio. | Nivel 3 con carta previa Revisar términos de uso completos antes de activar. |
| PriceSmart (pricesmart.com/es-hn) | robots.txt bloquea explícitamente bots de IA y permite buscadores; nuestro conector no es un buscador. Catálogo público, membresía para comprar. | Apagado hasta tener permiso Se muestra como tienda con precios reportados por socios. |
| Maxi Despensa / Despensa Familiar | Sin tienda en línea propia; catálogo por volantes y redes sociales. | Nivel 4 (reportes) Más ingestión manual de volantes semanales como promociones. |
| PedidosYa (supermercados) | Precios con recargo del canal; términos de plataforma restrictivos. | No se lee Si se integra, será como canal «a domicilio» separado y por convenio. |
| SIMPAH (FHIA) | Reportes públicos diarios y series históricas desde 1996, por mercado y producto, en lempiras. | Nivel 2, activo desde el MVP Citar a la FHIA como fuente en pantalla. |
| SDE (Protección al Consumidor) | Boletín semanal de precios de referencia de la canasta básica; publicado como imagen y PDF mensual, sin licencia explícita. | Nivel 2, ingestión semi-manual OCR con revisión humana; pedir formato tabular por escrito. |
Si para leer un precio hace falta iniciar sesión, resolver un desafío, cambiar de agente de usuario o llamar a una ruta que robots.txt prohíbe, ese precio no se lee. Se muestra el precio de referencia público y se invita al usuario a reportarlo. Ninguna funcionalidad de la app depende de que una cadena específica esté encendida.
Ficha de cumplimiento por conector
connectors/la-colonia/compliance.yaml
host: www.lacolonia.com
discovery: sitemap # nunca búsqueda interna
robots_checked_at: 2026-09-09
disallowed_paths_respected: [/api, /busca, /quick-view, /account, /checkout]
user_agent: "CucharadaBot/0.1 (+https://cucharada.hn/bot; contacto@cucharada.hn)"
rate: { min_interval_s: 4, window: "01:00-05:00 America/Tegucigalpa" }
max_pages_per_run: 1500
cache_ttl_h: 24
stores_full_catalog: false # sólo productos ligados a ingredientes
auto_pause_on: [429, 403, challenge_page, error_rate > 5%]
legal_review: { by: "pendiente", date: null }
contact_letter_sent: null
kill_switch: env CONNECTOR_LA_COLONIA=off
Caché y actualización de precios
Cuatro capas, cada una con su vigencia. La regla es que un precio nunca aparece sin su edad, y una edad mayor a la vigencia de su categoría se marca visiblemente como «por confirmar».
| Capa | Dónde | Contenido | Vigencia |
|---|---|---|---|
| L1 cliente | IndexedDB | Catálogo de la ciudad, precios actuales, listas y recetas del usuario | Sincronización delta al abrir (?desde=) y al conectarse a Wi-Fi |
| L2 borde | Cloudflare | /paquetes/{ciudad}/{tienda}.json.gz firmado, de 60 a 200 KB | Regenerado cuando cambia más del 2 % de precios o cada 6 h |
| L3 servidor | Redis | current_price por producto-tienda, cola de refresco, contadores de ritmo | Escrito por el normalizador, leído por la API |
| L4 histórico | Postgres | Toda observación con fuente, hora y confianza | Permanente; alimenta tendencias y validación |
Vigencia por categoría
| Categoría | Refresco objetivo | Se marca «por confirmar» a las | Motivo |
|---|---|---|---|
| Frutas, verduras, carnes, lácteos frescos | 24 h | 48 h | Precio y disponibilidad cambian a diario; SIMPAH cubre el hueco. |
| Granos, aceites, harinas, enlatados | 72 h | 7 d | Cambian con promociones semanales. |
| Limpieza, higiene, bebidas | 7 d | 14 d | Baja rotación de precio. |
| Promociones detectadas | hasta fecha fin | al vencer | Si no trae fecha, se asume domingo de esa semana. |
Refresco por demanda
- Prioridad alta: productos presentes en listas activas (modificadas en los últimos 7 días). Se refrescan primero dentro de la ventana permitida.
- Prioridad media: productos ligados a recetas populares de la semana.
- Prioridad baja: el resto del catálogo ligado a ingredientes, en rotación.
- Detección de cambio: se guarda un hash del bloque de precio de cada página; si no cambió, no se crea observación nueva, sólo se actualiza
last_seen_at. Reduce escritura y ruido. - Presupuesto de ritmo: con 4 s entre peticiones y una ventana de 4 h, un conector lee unas 3,600 páginas por noche. Alcanza para un catálogo de ingredientes de 1,500 a 2,500 productos por cadena. Si no alcanza, se reduce alcance, nunca se acelera.
Sincronización con el cliente
- Al abrir, el cliente pide
/precios?ciudad=tgu&desde=<marca>. La respuesta trae sólo cambios y suele pesar menos de 10 KB. - Si el paquete completo de la ciudad tiene una versión nueva y el usuario está en Wi-Fi (o aceptó usar datos), se descarga entero y reemplaza la tabla local en una transacción.
- Android con soporte usa Periodic Background Sync para hacer esto en segundo plano; iOS no lo permite, así que se hace al abrir y se muestra un aviso discreto de «precios de hace 2 días» mientras llega.
- Los reportes de precio del usuario se encolan localmente y se envían con Background Sync donde exista, o en la siguiente apertura.
Modelo de datos
La separación clave es entre ingrediente (lo que pide una receta: «frijol rojo»), producto (algo con marca y contenido neto: «Frijol rojo Natura, bolsa 1 lb») y producto en tienda (ese producto en una cadena, con su SKU, URL y precio). Las recetas se ligan a ingredientes; los precios, a productos en tienda. El emparejador conecta ambos mundos.
| Tabla | Campos principales | Notas |
|---|---|---|
| stores | id, chain, name, city, lat, lng, channel (tienda | en_linea | domicilio), delivery_fee, min_order, pickup, connector_id | Una cadena puede tener varias sucursales; el precio en línea aplica a la ciudad. |
| categories | id, parent_id, name, slug, ttl_hours, sort | Taxonomía propia de dos niveles; la vigencia de caché cuelga de aquí. |
| ingredients | id, name, aliases[], category_id, base_unit (g | ml | unidad), density_g_ml, typical_packs[], is_pantry_staple | Ingrediente canónico. «Culantro», «cilantro» y «culantro ancho» son alias o ingredientes distintos según la cocina. |
| products | id, ingredient_id, brand, net_qty, net_unit, barcode, image_url, pack_kind (bolsa | lata | cartón | botella | unidad) | Un producto puede servir a un ingrediente; el contenido neto es lo que permite el precio por unidad base. |
| store_products | id, store_id, product_id, external_sku, url, availability (disponible | agotado | desconocido), availability_at, last_seen_at, page_hash | Disponibilidad se separa de precio: un producto puede tener precio y estar agotado. |
| price_observations | id, store_product_id, price, promo_price, promo_until, observed_at, source (conector | crowd | oficial), confidence, unit_price_base | Serie temporal. unit_price_base es L por gramo, mililitro o unidad, calculado al insertar. |
| current_prices | vista materializada: store_product_id, price, promo_price, observed_at, source, confidence, is_stale | Lo que se empaqueta para el cliente. |
| reference_prices | ingredient_id, source (SIMPAH | SDE), market, city, price, unit, observed_on | Precio público de referencia; nunca se presenta como precio de una tienda. |
| recipes | id, slug, title, region, base_servings, prep_min, cook_min, tags[], steps[], cost_band, published | cost_band se recalcula cada noche con precios medios de la ciudad principal. |
| recipe_ingredients | recipe_id, ingredient_id, qty, unit, prep_note, optional, group, scaling (lineal | sublineal | fijo), sort | scaling gobierna el escalado: sal y especias son sublineales; «1 hoja de laurel» es fijo. |
| substitutes | ingredient_id, substitute_id, ratio, context, quality (1 a 3), note | Dirigido: A puede sustituir a B sin que B sustituya a A. |
| unit_defs | code, dimension (masa | volumen | unidad), to_base, names_hn[], plural | Incluye libra, onza, taza, cucharada, cucharadita, cartón, docena, manojo. |
| users | id, device_key, city_id, household_size, prefs (tiendas preferidas, radio, usa_datos_moviles), created_at | Cuenta anónima por dispositivo; correo opcional sólo para respaldar listas. |
| pantry_items | user_id, ingredient_id, qty, unit, updated_at | Lo que el usuario ya tiene; se descuenta de la lista. |
| shopping_lists | id, user_id, name, recipes[{recipe_id, servings}], mode (una_tienda | dividida), max_stores, status | Guarda la decisión del usuario, no sólo el cálculo. |
| list_items | list_id, ingredient_id, qty_needed_base, display_qty, display_unit, from_recipes[], chosen_product_id, manual, checked | Consolidado por ingrediente; el producto elegido puede cambiar por tienda. |
| list_allocations | list_id, item_id, store_id, store_product_id, packs, unit_price, line_cost, computed_at | Resultado del reparto. Se recalcula al cambiar precios o modo. |
| price_reports | id, user_id, store_id, product_id, price, photo_url, created_at, votes_up, votes_down, status | Entra a price_observations con source = crowd cuando pasa validación. |
| connector_runs | connector_id, started_at, finished_at, pages, changed, errors, status, paused_reason | Auditoría de cada corrida; alimenta alertas. |
En el cliente, Dexie replica ingredients, products, store_products, current_prices, reference_prices, unit_defs, substitutes y las recetas publicadas para la ciudad elegida, más las tablas propias del usuario. Cada tabla lleva updated_at para la sincronización delta.
Lógica de negocio
Conversión de unidades
Toda cantidad se normaliza a una unidad base por dimensión: gramos para masa, mililitros para volumen, piezas para unidades discretas. Cruzar de volumen a masa requiere la densidad del ingrediente; cruzar de piezas a masa requiere un peso típico por pieza (un huevo mediano, 50 g; un tomate, 120 g). Si falta el dato, la conversión se rechaza y el ítem se muestra en su unidad original con una nota, nunca con una cifra inventada.
| Unidad (como se dice en Honduras) | Dimensión | Equivalencia | Uso |
|---|---|---|---|
| libra (lb) | masa | 453.6 g | Unidad de compra dominante en carne, granos, queso, verduras. |
| onza (oz) | masa | 28.35 g | Empaques importados, quesos. |
| kilogramo | masa | 1,000 g | Harinas y azúcar en empaques grandes. |
| taza | volumen | 240 ml | Recetas caseras. |
| cucharada / cucharadita | volumen | 15 ml / 5 ml | Condimentos y grasas. |
| litro, botella de 750 ml | volumen | 1,000 ml / 750 ml | Aceite, leche, bebidas. |
| docena / cartón | unidad | 12 / 30 huevos | «Cartón» sin más significa 30; se confirma en la pantalla de producto. |
| manojo | unidad → masa | ≈ 30 g (culantro) | Peso típico por ingrediente; editable. |
| unidad / pieza | unidad | peso típico por ingrediente | Plátano, tomate, cebolla, aguacate. |
Escalado de porciones
Factor f = porciones deseadas / porciones base. Se aplica por ingrediente según su modo:
- Lineal (la mayoría):
qty × f. - Sublineal (sal, especias, ajo, consomé, levadura):
qty × f^0.85cuandof > 1, lineal cuandof < 1. Al duplicar la receta la sal sube 80 %, no 100 %; es lo que hacen las cocineras con experiencia. - Fijo (una hoja de laurel, una tapa de olla): no cambia, salvo que
fsupere 3, donde se duplica.
Después del cálculo entra el redondeo de cocina: volúmenes a fracciones de taza y cucharada (¼, ⅓, ½, ⅔, ¾), masas a múltiplos de 5 g bajo 100 g y de 25 g arriba, piezas al entero superior. La cantidad exacta queda guardada; sólo la presentación se redondea. Tiempos de cocción no escalan; se muestra una nota cuando f ≥ 2.5 («usá dos ollas o alargá el tiempo»).
Redondeo a empaques
Una lista pide 900 g de harina, pero la tienda vende bolsas de 1 lb y de 2 kg. El motor elige, por tienda, la combinación de empaques que cubre la necesidad al menor costo, con desempate por menor sobrante. La pantalla muestra las tres cifras: necesitás, comprás, te sobra. El sobrante alimenta la despensa si el usuario lo acepta.
Sustitutos
Se ofrecen en dos momentos: cuando el ingrediente no está disponible en la tienda evaluada y cuando el sustituto de calidad 1 o 2 cuesta al menos 15 % menos. El sustituto entra con su ratio (1.0 para queso duro por queso seco; 1.25 para margarina por mantequilla en repostería) y su contexto, para que nunca se proponga crema en lugar de mantequilla al hornear.
Comparación por tienda
Para cada tienda de la ciudad se calcula:
- Total de los ítems con precio y disponibilidad conocidas, ya redondeados a empaque.
- Cobertura: ítems con dato entre el total de ítems.
- Faltantes: lista de lo que no tiene o no se conoce, con sustituto si existe.
- Total comparable: total más los faltantes valorados a precio de referencia público, marcado como estimado. Sólo así se pueden ordenar tiendas con cobertura distinta sin favorecer a la que tiene menos datos.
- Diferencia contra la tienda más barata, en lempiras y porcentaje, y frescura mínima de los precios usados.
Repartir entre tiendas
Con hasta cinco tiendas por ciudad, la búsqueda exhaustiva es trivial: se evalúan los 31 subconjuntos no vacíos, en cada uno cada ítem va a la tienda más barata del subconjunto, y al total se suma un costo fijo por tienda extra (viaje, o envío mínimo si es en línea) que el usuario configura y por defecto vale L 15 por tienda adicional. Gana el subconjunto de menor costo total. La recomendación de dividir sólo aparece si el ahorro neto contra la mejor tienda única supera L 25 o 5 % del total, lo que sea mayor. Cuando la lista supera 60 ítems, o el usuario permite más de cinco tiendas, se cambia a asignación voraz por ítem con el mismo costo fijo, que en la práctica da el mismo resultado.
| Baleadas sencillas, 8 personas | Necesita → compra | Tienda 1 | Tienda 2 | Tienda 3 | Mínimo |
|---|---|---|---|---|---|
| Harina de trigo | 900 g → 2 × 1 lb | 44.00 | 41.00 | sin dato | 41.00 (T2) |
| Frijol rojo | 1 lb → 1 lb | 32.00 | 34.00 | 29.00 | 29.00 (T3) |
| Queso seco | ½ lb → 1 lb | 105.00 | 98.00 | 112.00 | 98.00 (T2) |
| Mantequilla (crema) | ½ lb → 1 lb | 62.00 | 65.00 | 58.00 | 58.00 (T3) |
| Huevos | 8 → docena | 58.00 | 55.00 | 60.00 | 55.00 (T2) |
| Aceite | 120 ml → 750 ml | 48.00 | 52.00 | 46.00 | 46.00 (T3) |
| Margarina | 150 g → 400 g | 38.00 | 36.00 | 41.00 | 36.00 (T2) |
| Total | 387.00 | 381.00 | 346.00 (6 de 7) | 363.00 | |
| Total comparable | faltante a referencia | 387.00 | 381.00 | 389.00 (est.) |
// Núcleo del reparto (TypeScript, corre en el cliente)
function repartir(items: Item[], tiendas: Tienda[], costoExtra: number, maxTiendas = 5) {
let mejor = { costo: Infinity, tiendas: [] as Tienda[], asign: new Map- () };
for (const sub of subconjuntos(tiendas, maxTiendas)) {
const asign = new Map
- ();
let costo = costoExtra * (sub.length - 1);
for (const it of items) {
const opciones = sub.map(t => ({ t, c: costoEnTienda(it, t) })).filter(o => o.c !== null);
if (opciones.length === 0) { costo += precioReferencia(it); continue; } // faltante estimado
const min = opciones.reduce((a, b) => (b.c! < a.c! ? b : a));
asign.set(it, min.t); costo += min.c!;
}
if (costo < mejor.costo) mejor = { costo, tiendas: sub, asign };
}
return mejor;
}
Flujos de UI
Cinco pantallas cargan con el trabajo real; el resto son detalles. Lenguaje directo en segundo persona del voseo hondureño («elegí», «agregá»), cifras siempre en lempiras con dos decimales, y ninguna acción de dinero escondida detrás de un ícono. Tema claro en blanco y tema oscuro en negro, con el acento sólo en botones primarios, ahorros y estado de frescura.
Recorridos principales
Offline y rendimiento para Honduras
Presupuestos
Preact, enrutador, Dexie y la pantalla de inicio. El motor de comparación y el recetario completo se cargan en segundo plano.
Medido con Lighthouse en perfil Moto G4 y red «Slow 4G». Se rompe el build si se pasa.
Sólo productos ligados a ingredientes. Descarga completa sólo en Wi-Fi o con permiso.
Sincronización delta más una o dos imágenes de receta en WebP a 480 px.
Estrategias del Service Worker
| Recurso | Estrategia | Nota |
|---|---|---|
| Shell (HTML, JS, CSS, fuentes) | Precache con versión | Actualización silenciosa; se aplica al siguiente arranque con aviso «hay versión nueva». |
| Paquete de precios por ciudad | Stale-while-revalidate | Se usa el local de inmediato y se reemplaza al llegar el nuevo. |
| API de listas y reportes | Network-first con cola offline | Escrituras se guardan en IndexedDB y se reenvían. |
| Imágenes de recetas y productos | Cache-first con tope de 60 MB y expiración | WebP con fallback; nunca se descargan imágenes de producto en modo ahorro de datos. |
| Fuentes tipográficas | Precache, subconjunto latino | Con font-display: swap para no bloquear texto. |
iOS y Android, distinto por diseño
- Sin Background Sync ni Periodic Sync: todo refresco ocurre al abrir la app. La interfaz muestra la edad de los precios sin drama.
- Almacenamiento puede evacuarse tras días sin uso si la app no está instalada. Se pide
navigator.storage.persist()y se insiste en «agregar a inicio» con instrucciones visuales, porque la app instalada conserva datos. - Notificaciones push sólo con la app instalada y fuera de la Unión Europea; para Honduras aplica. Se usan con moderación: «bajó el precio de algo en tu lista».
- Cuota de caché más ajustada: tope de imágenes en 60 MB y limpieza por antigüedad.
- Modo compra usa Screen Wake Lock, disponible en apps instaladas desde Safari 18.4.
- Background Sync para reenviar reportes y Periodic Background Sync para el paquete de ciudad cuando la app se usa con frecuencia.
- Instalación con
beforeinstallprompttras la segunda lista creada, no en la primera visita. - Web Share Target opcional: recibir una foto de volante desde la galería y proponer un reporte de precio.
- Se respeta
navigator.connection.saveDatay el tipo de red para decidir si descargar el paquete completo.
Rendimiento en el dispositivo
- Consultas a IndexedDB por índice compuesto
[ingredient_id+store_id]; la comparación de una lista de 30 ítems contra 5 tiendas se resuelve en menos de 50 ms en un Android de gama baja. - Listas largas con virtualización sólo cuando superan 100 filas; antes no vale el peso.
- Nada de mapas embebidos: la ubicación de tiendas se muestra como lista con distancia y un enlace externo a Google Maps.
- Fuentes del sistema como respaldo real: si Google Fonts no responde en 3 s, el texto ya está en pantalla con la fuente local.
Seguridad y privacidad
- Mínimo dato personal. La cuenta es una clave de dispositivo; correo sólo si el usuario quiere respaldar listas. No se pide teléfono ni ubicación precisa; la ciudad se elige a mano.
- Honduras no tiene aún una ley general de protección de datos vigente, así que se diseña como si la tuviera: propósito declarado, borrado a pedido desde la app, retención de reportes de precio anonimizada a los 90 días.
- Las fotos de reportes se recortan del lado del cliente al bloque de precio y se borran del servidor una vez validado el reporte.
- API con límite de ritmo por dispositivo, paquetes firmados para que un CDN o un proxy no puedan alterar precios, y sin secretos en el cliente.
- Marcas de las cadenas: se usan sus nombres como texto, no sus logotipos, hasta tener permiso escrito.
- Los conectores no guardan cookies, no usan credenciales y registran cada corrida, para poder demostrar ante cualquier cadena exactamente qué se leyó y cuándo.
Fases y riesgos
| Fase | Entrega | Criterio de salida |
|---|---|---|
| 0 · Permisos (semanas 1 a 2, en paralelo) | Cartas a La Colonia, Walmart Centroamérica y PriceSmart; solicitud de formato tabular a la SDE; alta como usuario de SIMPAH. | Al menos una respuesta o un silencio de 15 días documentado antes de encender cualquier conector de nivel 3. |
| 1 · Motor sin precios (semanas 1 a 4) | PWA con 40 recetas hondureñas, escalado, conversión, sustitutos, lista consolidada y despensa. Sin tiendas. | Usable offline en iPhone y Android de gama baja; 10 hogares reales lo usan una semana. |
| 2 · Referencia pública (semanas 4 a 6) | Ingestión de SIMPAH y SDE; costo estimado por receta; reportes de usuarios con validación. | Toda receta muestra un costo estimado con fuente y fecha. |
| 3 · Primer conector (semanas 6 a 9) | Un conector de nivel 3 o API oficial, emparejador ingrediente–producto revisado a mano para los 400 productos más usados, comparador y reparto. | Cobertura mayor a 85 % de los ingredientes de las 40 recetas en Tegucigalpa y San Pedro Sula. |
| 4 · Segunda y tercera tienda (semanas 9 a 12) | Más conectores según permisos; alertas de baja de precio; compartir por WhatsApp; modo compra. | Un usuario puede decidir dónde comprar sin abrir ningún otro sitio. |
Riesgos abiertos
- Que ninguna cadena responda. La app sigue siendo útil con referencia pública y reportes, pero la promesa de «comparar súpers» se cumple a medias. Mitigación: fase 1 y 2 no dependen de ellas.
- Emparejamiento ingrediente–producto. Es el trabajo más manual y el que más errores visibles produce («queso seco» emparejado con queso crema). Mitigación: revisión humana de los 400 productos más frecuentes y botón «esto no es lo que pedí» en cada línea.
- Precios en línea distintos a los de sucursal. Algunas cadenas cobran distinto en línea. Mitigación: el canal se muestra siempre («precio en línea») y los reportes de góndola se guardan como canal aparte.
- Desgaste de conectores. Los sitios cambian su HTML sin aviso. Mitigación: pruebas de contrato por conector, alerta cuando una corrida produce cero cambios o más de 5 % de errores, y precios marcados «por confirmar» automáticamente al vencer su vigencia.
- Evacuación de datos en iOS. Un usuario que no instala la app puede perder su lista. Mitigación: respaldo opcional por correo y aviso claro de instalar.
Fuentes consultadas
- Términos y condiciones de Supermercados La Colonia y su robots.txt, revisados el 9 de septiembre de 2026.
- Walmart Honduras, tienda en línea y su robots.txt, con sitemaps de Walmart y Paiz.
- PriceSmart Honduras y su robots.txt, que bloquea bots de IA y permite buscadores.
- SIMPAH, Sistema de Información de Mercados de Productos Agrícolas de Honduras, administrado por la FHIA; ejemplo de reporte diario de hortalizas.
- Boletines de la canasta básica alimentaria, Secretaría de Desarrollo Económico; nota de la TNH sobre los precios de referencia semanales.
- OBSAN de la UNAH, monitoreo de precios de granos básicos.
- PedidosYa Honduras, supermercados.
- Maxi Despensa y Despensa Familiar, ubicaciones.
- Prizio, guía de ofertas de supermercados en Honduras 2026 (app comparadora de volantes ya existente en el mercado).
- Limitaciones de PWA en iOS y soporte de Safari, 2026 y PWA en 2026: qué funciona en iOS y Android.