7.4 KiB
7.4 KiB
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:
- OTC medications from CIMA (filtered by
cpresc = "Sin Receta") - 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+Recetato get only OTC products - Endpoints used:
GET /medicamentos?nombre={query}&cpresc=Sin+Receta— search OTC productsGET /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 productsGET /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 foodbaby-milks— formula milkcereals-for-babies— baby cerealssnacks-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
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:
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:
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:
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
export async function searchProducts(query: string): Promise<Product[]> {
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
- Unit tests: off-service.js search/transform functions
- Integration tests:
/api/products/searchendpoint with mocked APIs - Manual test: Search "leche" and verify both CIMA OTC + OFF baby results appear
- 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