# 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
