Files
FarmaFinder/docs/superpowers/specs/2026-07-13-parapharmacy-baby-products-design.md
Antoni Nuñez Romeu 4df1594b3b
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
docs: add design spec and implementation plan for parapharmacy + baby products
2026-07-13 15:56:00 +02:00

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:

  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

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

  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