# Parafarmacia + Productos de Bebé — Design Spec **Date:** 2026-07-13 **Status:** Approved **Author:** MiMoCode ## Problem FarmaFinder currently only searches medications via the CIMA API. Users need to find parapharmacy products (OTC medications, vitamins, topical treatments) and baby products (formula milk, baby food, cereals) — categories that pharmacies sell but CIMA doesn't cover comprehensively. ## Goal Extend FarmaFinder's search to include: 1. **OTC medications** from CIMA (filtered by `cpresc = "Sin Receta"`) 2. **Baby products** (formula milk, baby food, cereals) from Open Food Facts API Results should appear in a unified search alongside existing prescription medication results. ## Data Sources ### CIMA API (existing, extended) - **Base URL:** `https://cima.aemps.es/cima/rest` - **No authentication required** - **New filter:** `cpresc=Sin+Receta` to get only OTC products - **Endpoints used:** - `GET /medicamentos?nombre={query}&cpresc=Sin+Receta` — search OTC products - `GET /medicamento/{nregistro}` — get OTC product details - **Rate limits:** None documented; existing 5s timeout per request - **Data fields:** nregistro, nombre, labtitular, cpresc, formaFarmaceutica, vtm, dosis, fotos, docs ### Open Food Facts API (new) - **Base URL:** `https://world.openfoodfacts.org/api/v2` - **No authentication required** - **Endpoints used:** - `GET /search?categories_tags=baby-food&search_terms={query}&json=true` — search baby products - `GET /product/{barcode}.json` — get product details - **Rate limits:** Be polite (< 10 req/s) - **Data fields:** product_name, brands, image_url, nutriscore, ingredients_text, categories_tags - **Product categories to search:** - `baby-foods` — general baby food - `baby-milks` — formula milk - `cereals-for-babies` — baby cereals - `snacks-and-desserts-for-babies` — baby snacks ## Architecture ### Data Flow ``` User searches "leche" → Frontend: GET /api/products/search?q=leche → Backend orchestrator: 1. cimaService.searchOTC('leche') — CIMA OTC search 2. offService.searchBaby('leche') — Open Food Facts search 3. Merge results with source tag 4. Cache in Redis (1h TTL) → Return unified result list ``` ### Unified Product Model ```typescript interface Product { id: string; // nregistro (CIMA) or _id (OFF) source: 'cima' | 'openfoodfacts'; name: string; brand: string; // labtitular (CIMA) or brands (OFF) category: 'otc' | 'baby_food' | 'baby_milk' | 'baby_cereal'; image_url: string | null; // fotos[0].url (CIMA) or image_url (OFF) // CIMA-specific fields active_ingredient?: string; // vtm.nombre dosage?: string; // dosis form?: string; // formaFarmaceutica.nombre prescription?: string; // cpresc commercialized?: boolean; // comerc photos?: string[]; docs?: { tipo: number; url: string }[]; // OFF-specific fields nutriscore?: string; // nutriscore_grade ingredients?: string; // ingredients_text nova_group?: number; // nova_group eco_score?: string; // ecoscore_grade } ``` ## Backend Changes ### 1. New file: `apps/backend/off-service.js` Open Food Facts API client: ```javascript const OFF_API_BASE = 'https://world.openfoodfacts.org/api/v2'; async function searchBabyProducts(query) { // Search in baby-food categories const url = `${OFF_API_BASE}/search?categories_tags=baby-food&search_terms=${encodeURIComponent(query)}&json=true&fields=product_name,brands,image_url,nutriscore_grade,ingredients_text,nova_group,ecoscore_grade,categories_tags&page_size=20`; // Cache key: off:baby:{query}, TTL: 1 hour // Transform to unified Product model } async function getBabyProductDetails(barcode) { const url = `${OFF_API_BASE}/product/${barcode}.json`; // Cache key: off:product:{barcode}, TTL: 24 hours // Transform to unified Product model } ``` ### 2. Modify: `apps/backend/cima-service.js` Add OTC-specific search function: ```javascript async function searchOTC(query) { // Same as searchMedicines but adds cpresc=Sin+Receta filter // Cache key: cima:otc:{query}, TTL: 1 hour // Transform to unified Product model (add source: 'cima', category: 'otc') } ``` ### 3. Modify: `apps/backend/server.js` New unified search route: ```javascript app.get('/api/products/search', async (req, res) => { const { q } = req.query; // Launch parallel searches: const [cimaResults, offResults] = await Promise.allSettled([ searchOTC(q), searchBabyProducts(q) ]); // Merge, deduplicate, sort by relevance // Return unified results }); app.get('/api/products/:source/:id', async (req, res) => { const { source, id } = req.params; if (source === 'cima') return getMedicineDetails(id); if (source === 'openfoodfacts') return getBabyProductDetails(id); }); ``` ### 4. Redis Cache Strategy | Key Pattern | TTL | Source | |-------------|-----|--------| | `products:search:{query}` | 1 hour | Merged results | | `cima:otc:{query}` | 1 hour | CIMA OTC only | | `off:baby:{query}` | 1 hour | OFF baby only | | `off:product:{barcode}` | 24 hours | OFF product detail | ## Frontend Changes ### 1. New component: `ProductResults.jsx` Displays unified search results with: - Product card with image, name, brand - Source badge: "CIMA" or "Open Food Facts" - Category badge: "OTC", "Baby Food", "Formula" - Click navigates to `/product/{source}/{id}` ### 2. Modify: `PublicView.jsx` - Update search to call `/api/products/search?q=` - Show unified results alongside existing medicine results - Add category filter tabs: "All" | "Medications" | "OTC" | "Baby" ### 3. Modify: `MedicineResults.jsx` - Accept mixed product types - Conditionally render fields based on `source` - Show CIMA-specific fields (active ingredient, dosage) for CIMA products - Show OFF-specific fields (nutriscore, ingredients) for OFF products ### 4. New route: `/product/:source/:id` - Detail page for any product type - CIMA products: show full medication info + pharmacies - OFF products: show nutritional info + "Available at pharmacies" section ## Mobile Changes ### 1. `apps/frontend-mobile/services/products.ts` ```typescript export async function searchProducts(query: string): Promise { const { data } = await api.get('/products/search', { params: { q: query } }); return data.results; } ``` ### 2. `apps/frontend-mobile/app/(tabs)/search.tsx` - Add product search alongside medicine search - Display unified results with source/category badges ### 3. `apps/frontend-mobile/app/product/[source]/[id].tsx` - New detail screen for non-medication products - Adapt layout based on product source ## Error Handling - CIMA API down: return OFF results only (graceful degradation) - OFF API down: return CIMA results only - Both down: return cached results if available, else empty - OFF rate limit: implement 100ms delay between requests ## Testing 1. **Unit tests:** off-service.js search/transform functions 2. **Integration tests:** `/api/products/search` endpoint with mocked APIs 3. **Manual test:** Search "leche" and verify both CIMA OTC + OFF baby results appear 4. **Edge cases:** Empty results, API timeout, malformed OFF responses ## Out of Scope - Homeopathy (not reliably available in either API) - Commercial parapharmacy APIs (paid, not needed for OTC + baby) - Product price/stock for OFF products (pharmacies don't stock these in the current model) - User reviews/ratings for products