# Memoria tecnica FarmaFinder

## 1. Resumen ejecutivo

FarmaFinder es una aplicacion web/PWA para buscar medicamentos en la base oficial de CIMA (AEMPS), localizar farmacias que los comercializan y administrar el catalogo de farmacias y sus relaciones con medicamentos. La aplicacion tambien incluye perfil de usuario con ubicacion guardada, geocodificacion, ordenacion por distancia, estados de apertura de farmacias, notificaciones push para medicamentos y un panel de administracion para altas/bajas/importacion de farmacias.

La base funcional actual esta repartida en cuatro capas principales:

- `frontend/`: interfaz React + Vite, tambien preparada para PWA y empaquetado Capacitor.
- `backend/`: API Express con sesion, autenticacion, persistencia SQLite/PG y notificaciones push.
- `API/`: helpers de importacion y normalizacion de farmacias desde fuentes externas.
- `android/` e `ios/`: shells nativos generados por Capacitor.

## 2. Stack tecnologico

### Frontend

- React 18
- Vite 5
- Leaflet + React Leaflet para el mapa
- Vitest + Testing Library + JSDOM para tests de UI
- PWA con `vite-plugin-pwa`
- Capacitor 6 para envoltorio nativo

### Backend

- Node.js en modo ESM
- Express 4
- SQLite como almacenamiento base local
- PostgreSQL opcional para usuarios y sesiones cuando `PG_URL` esta definido
- Redis para cache de CIMA
- `express-session` para sesion de usuario
- `bcrypt` para hash de contrasenas
- `web-push` para notificaciones push web
- `express-rate-limit` para proteccion de endpoints sensibles

### Integraciones externas

- CIMA API de la AEMPS para busqueda y detalle de medicamentos
- Nominatim / OpenStreetMap para geocodificacion
- OpenStreetMap Overpass u otras fuentes JSON para importacion de farmacias
- Webhooks externos tipo n8n para importacion de farmacias

## 3. Estructura del repositorio

- `frontend/`: SPA principal
- `backend/`: API principal y scripts de mantenimiento
- `API/`: libreria de importacion de farmacias desde fuentes externas
- `scraper/`: utilidades de scraping con Puppeteer
- `android/` y `ios/`: proyectos nativos Capacitor
- `docker-compose.yml`: entorno completo con backend, frontend, Redis y PostgreSQL

## 4. Arquitectura funcional

### Flujo publico

1. El usuario escribe un medicamento en el buscador.
2. El frontend llama a `GET /api/medicines/search`.
3. El backend consulta CIMA, filtra resultados y los cachea en Redis.
4. Al seleccionar un medicamento, el frontend llama a `GET /api/medicines/:id/pharmacies`.
5. El backend devuelve farmacias enlazadas en SQLite/PG con precio, stock, coordenadas y horario.
6. La UI permite ordenar por distancia si el usuario tiene ubicacion guardada o concede geolocalizacion.

### Flujo de administracion

1. Un admin inicia sesion.
2. El panel permite gestionar farmacias, buscar medicamentos en CIMA e importar farmacias externas.
3. El admin enlaza medicamentos a farmacias con precio y stock.
4. Al crear un nuevo enlace de farmacia-medicamento, el backend dispara notificaciones push a usuarios suscritos.

### Flujo de perfil y ubicacion

1. Un usuario autenticado puede editar su perfil.
2. Puede guardar direccion y coordenadas manualmente.
3. Tambien puede geocodificar su direccion via Nominatim o usar la geolocalizacion del navegador.
4. Esa ubicacion se usa en el ordenamiento por distancia en la vista publica.

## 5. Frontend

### Punto de entrada

- `frontend/src/main.jsx` monta `<App />` en `#root`.
- Se inicializa el shell nativo con `initNativeShell()`.

### Componente raiz

`frontend/src/App.jsx` controla tres vistas:

- `public`
- `admin`
- `profile`

Tambien gestiona:

- comprobacion de sesion con `GET /api/auth/check`
- apertura/cierre del modal de login
- cierre de sesion con `POST /api/auth/logout`
- panel de notificaciones guardadas

### Vistas principales

#### PublicView

`frontend/src/views/PublicView.jsx`

Responsabilidades:

- busqueda de medicamentos con debounce
- consulta de farmacias asociadas al medicamento
- mapa de farmacias
- lista de farmacias con precio, stock, horario y notificaciones
- ordenacion por distancia usando ubicacion guardada o geolocalizacion del navegador

Caracteristicas clave:

- Si el usuario tiene coordenadas en el perfil, se priorizan sobre la geolocalizacion del navegador.
- La lista y el mapa usan la misma ordenacion.
- Si no hay coordenadas o el navegador falla, se muestra un mensaje de error contextual.

#### AdminView

`frontend/src/views/AdminView.jsx`

Responsabilidades:

- autenticar acceso al panel
- mostrar tabs de:
  - farmacias
  - medicamentos
  - enlace farmacia-medicamento
- logout de admin

#### ProfileView

`frontend/src/views/ProfileView.jsx`

Responsabilidades:

- editar direccion y coordenadas del usuario
- geocodificar la direccion con `/api/geocode`
- obtener ubicacion actual del dispositivo
- abrir la vista de notificaciones guardadas
- cerrar sesion

## 6. Componentes de UI

### Busqueda y resultados

- `frontend/src/components/SearchBar.jsx`
- `frontend/src/components/MedicineResults.jsx`
- `frontend/src/components/PharmacyList.jsx`
- `frontend/src/components/PharmacyMap.jsx`

#### MedicineResults

- Lista resultados de CIMA.
- Cada card incluye boton de notificaciones para ese medicamento.
- Al pulsar la card se abre el detalle de farmacias asociadas.

#### PharmacyList

- Muestra farmacias enlazadas al medicamento.
- Incluye:
  - distancia al usuario si existe ubicacion
  - estado de apertura calculado a partir de `opening_hours`
  - direccion
  - telefono
  - precio
  - stock
  - boton de notificacion por farmacia cuando el stock esta agotado

#### PharmacyMap

- Muestra farmacias sobre Leaflet.
- Usa coordenadas de cada farmacia para ubicarlas en el mapa.

### Admin components

- `frontend/src/components/admin/PharmacyManagement.jsx`
- `frontend/src/components/admin/MedicineManagement.jsx`
- `frontend/src/components/admin/PharmacyMedicineLink.jsx`
- `frontend/src/components/admin/LoginForm.jsx`

#### PharmacyManagement

- Lista farmacias existentes.
- Permite crear, editar y borrar farmacias.
- Integra importacion por:
  - webhook externo
  - OpenStreetMap / Overpass
  - URL de open data JSON/GeoJSON
- Permite filtrar farmacias por radio geografico.
- Permite geocodificar una ciudad para obtener lat/lon/radio de trabajo.
- Gestiona horario semanal estructurado.

#### MedicineManagement

- Busca medicamentos en CIMA desde el panel admin.
- Solo sirve para exploracion y seleccion previa al enlace.

#### PharmacyMedicineLink

- Permite seleccionar farmacia y medicamento.
- Crea o actualiza el enlace con precio y stock.
- Permite editar y eliminar relaciones ya existentes.

## 7. Utilidades de frontend

### Geolocalizacion

`frontend/src/utils/geo.js`

- `haversineKm()`: calcula distancia entre dos puntos GPS.
- `getUserPosition()`: obtiene ubicacion del navegador con validaciones de contexto seguro.
- `formatDistance()`: da formato a metros/kilometros.

### Horarios

`frontend/src/utils/hours.js`

- `getOpenStatus()`: interpreta el horario semanal y devuelve un estado legible.
- Soporta:
  - abierto
  - cerrado
  - abre a una hora concreta
  - abre el siguiente dia habil
- El formato almacenado es JSON con claves `sun`, `mon`, `tue`, `wed`, `thu`, `fri`, `sat`.

### Notificaciones

`frontend/src/utils/notifications.js`

- comprueba soporte de Push API y Service Worker
- obtiene la VAPID public key del backend
- registra suscripciones push del navegador
- guarda una huella local en `localStorage`
- permite suscripcion global por medicamento o por medicamento + farmacia

### Service worker

`frontend/src/sw.js`

- precache de assets PWA
- muestra notificaciones al recibir eventos `push`
- navega a la URL asociada al click en la notificacion

## 8. Backend

### Arranque

`backend/server.js` es el nucleo de la API. Expone:

- la aplicacion Express
- `initDatabase()`
- la conexion `db`

Si `NODE_ENV !== test`, inicializa DB y levanta el servidor en `PORT` o `3001`.

### Configuracion basica

- CORS con credenciales
- `express.json()`
- session middleware
- rate limiting en:
  - busqueda
  - login
  - registro
  - geocoding

### Persistencia

#### SQLite

Se usa como base local principal:

- farmacias
- relaciones farmacia-medicamento
- subscripciones push
- fallback de usuarios cuando no hay PostgreSQL

#### PostgreSQL opcional

Si existe `PG_URL`, la tabla `users` y el store de sesiones se montan sobre PostgreSQL.

Esto genera una arquitectura mixta:

- SQLite para catalogo/relaciones
- PostgreSQL para usuarios y sesiones, si se configura

### Capa de sesion

- `express-session`
- store SQLite o PostgreSQL segun entorno
- cookie httpOnly con maxAge de 24h
- `trust proxy` configurable con `TRUST_PROXY`

## 9. Modelo de datos

### `pharmacies`

Campos:

- `id`
- `name`
- `address`
- `phone`
- `latitude`
- `longitude`
- `opening_hours`

### `pharmacy_medicines`

Relacion farmacia-medicamento.

Campos:

- `id`
- `pharmacy_id`
- `medicine_nregistro`
- `medicine_name`
- `price`
- `stock`

Restriccion:

- `UNIQUE(pharmacy_id, medicine_nregistro)`

### `users`

Campos actuales:

- `id`
- `username`
- `password_hash`
- `is_admin`
- `address`
- `latitude`
- `longitude`
- `created_at`

### `push_subscriptions`

Subscripciones push globales por medicamento.

Campos:

- `id`
- `medicine_nregistro`
- `medicine_name`
- `endpoint`
- `p256dh`
- `auth`
- `user_id`
- `created_at`

### `push_subscriptions_pharmacy`

Subscripciones push por medicamento y farmacia.

Campos:

- `id`
- `medicine_nregistro`
- `medicine_name`
- `pharmacy_id`
- `endpoint`
- `p256dh`
- `auth`
- `user_id`
- `created_at`

## 10. API backend

### Medicamentos

- `GET /api/medicines/search?q=...`
  - busca en CIMA con cache Redis
  - devuelve una lista normalizada
- `GET /api/medicines/:medicineId`
  - devuelve detalle de un medicamento por `nregistro`
- `GET /api/medicines/:medicineId/pharmacies`
  - devuelve farmacias que tienen ese medicamento

### Farmacias

- `GET /api/pharmacies`
  - lista completa de farmacias

### Autenticacion

- `POST /api/auth/login`
- `POST /api/auth/register`
- `POST /api/auth/logout`
- `GET /api/auth/check`

### Perfil

- `GET /api/users/me`
- `PUT /api/users/me`

### Geocoding

- `GET /api/geocode?q=...`
  - autenticado
  - usa Nominatim
  - devuelve lat/lon y texto de resultado
- `GET /api/admin/geocode?q=...`
  - solo admin
  - ademas calcula radio sugerido

### Admin: farmacias

- `POST /api/admin/pharmacies`
- `PUT /api/admin/pharmacies/:id`
- `DELETE /api/admin/pharmacies/:id`
- `POST /api/admin/pharmacies/import-webhook`
- `POST /api/admin/pharmacies/import-external`

### Admin: medicamentos y relaciones

- `GET /api/admin/medicines`
- `GET /api/admin/pharmacies/:pharmacyId/medicines`
- `POST /api/admin/pharmacy-medicines`
- `PUT /api/admin/pharmacy-medicines/:id`
- `DELETE /api/admin/pharmacy-medicines/:id`

### Notificaciones

- `GET /api/notifications/vapid-public-key`
- `POST /api/notifications/subscribe`
- `DELETE /api/notifications/unsubscribe`
- `GET /api/notifications/mine`
- `DELETE /api/notifications/mine`

## 11. Capa de importacion externa

La carpeta `API/` no expone un servidor HTTP propio, sino funciones reutilizables para importar farmacias desde distintas fuentes.

### `API/index.js`

- `fetchPharmaciesExternal(opts)`
  - `source = 'osm'`
  - `source = 'openData'`
- decide el extractor adecuado segun la fuente

### `API/normalize.js`

- `buildAddressFromOsmTags()`
- `osmElementToPharmacy()`
- normaliza nodos Overpass a un formato comun de farmacia
- parsea horarios OSM si existen

### `API/opening-hours-osm.js`

- parser de un subconjunto de `opening_hours`
- soporta:
  - `24/7`
  - rangos semanales
  - dias multiples
  - `off` / `closed`
  - franjas partidas resumidas

### `backend/farmacias-webhook-import.js`

- importa datos desde un webhook HTTP
- normaliza campos en espanol o ingles
- acepta ubicacion en distintas variantes
- extrae horarios si vienen en formato OSM
- inserta o salta duplicados con estadisticas

## 12. CIMA y cache

`backend/cima-service.js` implementa la integracion con CIMA.

### Busqueda

- consulta `https://cima.aemps.es/cima/rest/medicamentos`
- usa el parametro `nombre`
- normaliza el resultado a campos internos:
  - `id`
  - `nregistro`
  - `name`
  - `active_ingredient`
  - `dosage`
  - `form`
  - `formSimplified`
  - `laboratory`
  - `prescription`
  - `commercialized`
  - `generic`
  - `photos`
  - `docs`

### Detalle

- consulta `GET /medicamento/:nregistro`
- cachea por mas tiempo que la busqueda

### Cache

- Redis guarda resultados de busqueda y detalle
- si CIMA falla, se intenta devolver cache obsoleto si existe

## 13. Notificaciones push

### Estado actual

La app ya soporta notificaciones push web completas.

### Backend

- usa VAPID si `VAPID_PUBLIC_KEY` y `VAPID_PRIVATE_KEY` existen
- si faltan, push queda desactivado con respuesta `503`
- al crear una relacion nueva farmacia-medicamento, se ejecuta `sendPushForMedicine()`
- se deduplican endpoints para evitar notificacion doble global + farmacia
- si el endpoint devuelve `404` o `410`, la suscripcion se limpia

### Frontend

- permite suscripcion global por medicamento
- permite suscripcion por medicamento + farmacia
- el icono de campana refleja el estado local
- la vista `Saved notifications` permite ver y borrar suscripciones guardadas del usuario

## 14. PWA y capa nativa

### PWA

- existe `frontend/src/sw.js`
- hay assets publicos de icono
- la app esta pensada para funcionar como PWA instalable

### Capacitor

- `capacitor.config.json`
- `android/`
- `ios/`

El proyecto esta preparado para empaquetarse como app nativa. La documentacion de `NATIVE.md` indica que no hay una base de codigo separada para Android e iOS; la UI real vive en `frontend/`.

## 15. Scripts y comandos

### Raiz

- `npm run dev`: backend + frontend en paralelo
- `npm run start`: backend + preview frontend
- `npm run install:all`: instala dependencias raiz, backend y frontend
- `npm run build:web`: build de frontend
- `npm run cap:sync`: build web + sync Capacitor

### Backend

- `npm start`
- `npm run dev`
- `npm run seed`
- `npm run create-admin`
- `npm run migrate`
- `npm run import-farmacias`
- `npm test`

### Frontend

- `npm run dev`
- `npm run build`
- `npm run preview`
- `npm run test`

## 16. Docker

`docker-compose.yml` levanta:

- `redis`
- `postgres`
- `backend`
- `frontend`

Puertos:

- frontend: `3000`
- backend: `3001`

Persistencia:

- `backend_data` para la DB SQLite del backend
- `postgres_data` para PostgreSQL

## 17. Variables de entorno

### Backend

- `PORT`
- `SESSION_SECRET`
- `CORS_ORIGIN`
- `FARMACIAS_WEBHOOK_URL`
- `REDIS_HOST`
- `REDIS_PORT`
- `REDIS_PASSWORD`
- `DATABASE_PATH`
- `PG_URL`
- `TRUST_PROXY`
- `VAPID_PUBLIC_KEY`
- `VAPID_PRIVATE_KEY`
- `VAPID_SUBJECT`
- `NODE_ENV`

### App

- en produccion, el frontend debe poder resolver la URL del backend segun despliegue
- en native shell, el manual `NATIVE.md` indica que conviene usar URL absoluta o servir el PWA desplegado

## 18. Seeds, migraciones y administracion

### `backend/seed.js`

- crea tablas basicas
- inserta farmacias y medicamentos de ejemplo
- genera relaciones ficticias usando `EXAMPLE_...`
- actualmente es util para demos, no como dato real

### `backend/create-admin.js`

- crea usuario admin en SQLite o PostgreSQL segun `PG_URL`
- por defecto:
  - usuario: `admin`
  - password: `admin123`

### `backend/migrate.js`

- migracion para el cambio historico de `medicine_id` a `medicine_nregistro`
- la tabla `medicines` antigua se conserva como referencia si existe

## 19. Tests

### Backend

- `backend/__tests__/server.test.js`
  - valida busqueda vacia
  - valida login incorrecto
  - valida proteccion de rutas admin
  - valida creacion de farmacia autenticado
- `backend/__tests__/opening-hours-osm.test.js`
  - cubre parser de horarios OSM

### Frontend

- existe configuracion de Vitest
- hay `frontend/src/App.test.jsx`
- la cobertura visible en el repo parece enfocada a smoke tests y utilidades

## 20. Observaciones tecnicas y limites actuales

1. El backend mezcla SQLite y PostgreSQL segun entorno. Esto funciona, pero introduce dos rutas de persistencia para usuarios y sesiones.
2. Los medicamentos no se guardan localmente como catalogo propio; se consumen desde CIMA en tiempo real y se cachean en Redis.
3. Los enlaces farmacia-medicamento dependen de `nregistro` de CIMA, no de un ID interno de medicamentos.
4. Las notificaciones push son web push; en shells nativos siguen la logica del navegador/PWA, no una integracion FCM/APNs independiente.
5. La importacion de farmacias depende de la calidad de la fuente externa y de la normalizacion de campos.
6. El seed actual contiene datos de ejemplo y relaciones ficticias; no representa datos de produccion.

## 21. Conclusiones

El estado actual de FarmaFinder es el de una plataforma funcional orientada a consulta y administracion de farmacias y medicamentos, con un backend ya bastante consolidado: autentica usuarios, controla roles, persiste relaciones, geocodifica, importa fuentes externas, cachea CIMA y dispara push notifications. La parte de frontend acompana ese backend con un flujo publico de busqueda, un perfil con ubicacion y un panel admin operativo.

Si quieres, el siguiente paso puede ser convertir esta memoria en una version formal de entrega, con estilo academico o empresarial, o bien en un documento mas tecnico con diagramas de arquitectura y secuencia.

## 22. Next steps propuestos

El siguiente paso funcional natural para FarmaFinder seria incorporar el escaneo de la TSI, de forma que el usuario pueda identificar rapidamente su perfil asistencial y, a partir de ahi, cruzar su necesidad con el stock real disponible en farmacias.

### Objetivo

- Capturar la informacion de la tarjeta sanitaria individual mediante escaneo.
- Asociar esa informacion al perfil del usuario de manera segura.
- Recuperar o inferir los medicamentos relevantes para el usuario.
- Contrastar esos medicamentos con el stock de farmacias cercanas.
- Permitir actuar si no hay stock disponible, por ejemplo:
  - reservar una farmacia concreta
  - generar una solicitud de pedido
  - iniciar un flujo de aviso o sustitucion

### Flujo funcional propuesto

1. El usuario abre el escaner de TSI desde la app.
2. La app lee la tarjeta y extrae los identificadores necesarios.
3. El backend valida la informacion y la vincula al usuario autenticado.
4. Se obtiene la lista de medicamentos asociados o derivados del contexto del usuario.
5. El sistema cruza esa lista con:
  - farmacias cercanas
  - stock disponible
  - precio
  - horario
6. Si no existe stock suficiente, la app muestra alternativas y acciones posibles.

### Consideraciones tecnicas

- Esta funcionalidad requiere definir el formato exacto de lectura de la TSI y las fuentes de datos sanitarias a consultar.
- Debe tratarse como una funcionalidad sensible, con control de acceso estricto y minimizacion de datos.
- El escaneo deberia integrarse con el mismo modelo de ubicacion, autenticacion y notificaciones que ya existe.
- La logica de contraste stock/medicamento encaja bien con el modelo actual de `pharmacy_medicines`, pero probablemente exigiria ampliar la capa de negocio para soportar reservas, pedidos o estados intermedios.

### Riesgos y dependencias

- Dependencia de la integracion real con sistemas sanitarios externos.
- Posible necesidad de cumplimiento legal adicional por el tipo de dato tratado.
- Definicion de si el sistema solo informa de disponibilidad o si tambien permite operar pedidos/reservas.
- Necesidad de auditar trazabilidad, consentimiento y control de uso de datos.

### Resultado esperado

Esta ampliacion convertiria FarmaFinder de un buscador de disponibilidad a un asistente operativo de acceso al medicamento, capaz de ayudar al usuario a pasar de la consulta del stock a la accion cuando no exista disponibilidad inmediata.
