Files
Antoni Nuñez Romeu 2c1b3cfca6
Build & Push Docker Images / test-backend (push) Successful in 24s
Build & Push Docker Images / test-frontend (push) Successful in 23s
Build & Push Docker Images / build-backend (push) Successful in 2m22s
Build & Push Docker Images / build-frontend (push) Successful in 57s
Documentos + Homepage rediseñada
2026-06-26 16:59:20 +02:00

19 KiB

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.
  • 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
  1. 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.