docs: add design spec and implementation plan for parapharmacy + baby products
Run Tests on Branches / Detect Changes (push) Successful in 8s
Run Tests on Branches / Frontend Tests (push) Has been cancelled
Run Tests on Branches / Frontend Mobile Tests (push) Has been cancelled
Run Tests on Branches / Backend Tests (push) Has been cancelled

This commit is contained in:
Antoni Nuñez Romeu
2026-07-13 15:56:00 +02:00
parent 981f3bd3db
commit 4df1594b3b
3 changed files with 1477 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,226 @@
# 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<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
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