e3e7d2f60b
Run Tests on Branches / Detect Changes (push) Successful in 17s
Run Tests on Branches / Backend Tests (push) Successful in 2m44s
Run Tests on Branches / Frontend Tests (push) Successful in 1m59s
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
Run Tests on Branches / Parapharmacy API Tests (push) Has been skipped
Run Tests on Branches / PIP Platform Tests (push) Has been skipped
- Add is_open_now and is_always_open helpers in backend - Add backend endpoint enrichment with is_open and is_24h fields - Add client-side getOpenStatus with 24h, opens-at, opens-tomorrow support - Add open-now filter toggle in PublicView and SearchView - Add pharmacy no-hours fallback display - Add sticky pharmacy controls on scroll - Add admin hours editor with 24h toggle - Add translations (es/ca) for all hour-related strings - Add backend tests for hours logic and pharmacy endpoint - Remove unused backup test files
6.9 KiB
6.9 KiB
Horarios de farmacias — Implementación
Resumen
Sistema completo para gestionar, calcular y filtrar horarios de apertura de farmacias. Implementado siguiendo el plan en docs/PLAN-HORARIOS.md.
Lo implementado
Backend (apps/backend/)
| Archivo | Qué hace |
|---|---|
src/hours.js |
Helper puro: isOpenNow(rawHours, now?) y isAlwaysOpen(rawHours). Soporta 24/7, rangos normales, cruce de medianoche, días cerrados, 24:00 como 1440 min. |
server.js |
Los 3 endpoints públicos (/api/medicines/:id/pharmacies, /api/products/:source/:id/pharmacies, /api/pharmacies) devuelven is_open (boolean o null) e is_24h (boolean) precalculados con la hora del servidor. Helper enrichPharmacy(row). |
Frontend (apps/frontend/)
| Archivo | Qué hace |
|---|---|
src/utils/hours.js |
getOpenStatus() refactorizado: devuelve labelKey/labelParams para i18n + label legacy. Nuevos: emptyHoursDraft(), hoursToDraft(), draftToHours() (movidos desde admin), isAlwaysOpen(), makeAlwaysOpenDraft(). |
src/views/PublicView.jsx |
Nuevo estado openNow. Botón "Mostrar solo abiertas ahora" en .pharmacy-controls. Filtro en displayedPharmacies (incluye farmacias sin horarios). Mensaje si 0 resultados. |
src/components/PharmacyList.jsx |
Badge 24h junto al nombre. Texto de estado usa t() con claves i18n. |
src/components/PharmacyMap.jsx |
Badge 24h en popup del marcador. |
src/components/admin/PharmacyManagement.jsx |
Toggle "24 horas" en el editor de horarios. Al activarlo, rellena todos los días con 00:00-24:00. Al desactivarlo, confirm() antes de descartar. |
src/i18n/locales/es.js |
Nuevas claves: pharmacy.openNow, pharmacy.closedAllDay, pharmacy.opensAt, pharmacy.opensTomorrow, pharmacy.opensDay, pharmacy.alwaysOpen, pharmacy.filterOpenNow, pharmacy.filterOpenNowActive, pharmacy.badge24h, pharmacy.filterNoResults, admin.pharmacy.alwaysOpen, admin.pharmacy.confirmDisable24h. |
src/i18n/locales/ca.js |
Traducciones catalanas de todas las claves nuevas. |
Tests
| Archivo | Tests |
|---|---|
apps/backend/__tests__/hours.test.js |
22 tests — isAlwaysOpen (6), isOpenNow (16): 24/7, normal, cerrado, cruce medianoche, null, malformed JSON. |
apps/backend/__tests__/pharmacy-hours-endpoint.test.js |
4 tests — verifica is_open/is_24h en respuesta JSON para 24h, normal, null, múltiples. |
apps/frontend/src/App.test.jsx |
6 tests existentes — sin regresión. |
apps/frontend/src/utils/notifications.test.js |
1 test existente — sin regresión. |
Cómo funciona
- Almacenamiento:
pharmacies.opening_hourscomo TEXT JSON. Shape:{ mon: ["09:00","21:00"], tue: null, ... }. 24/7 → todos los días["00:00","24:00"]. - Cálculo en servidor: Cada request a endpoints públicos ejecuta
isOpenNow()con la hora del servidor. El frontend recibeis_openeis_24hya calculados. - Cálculo en cliente:
getOpenStatus()existe como fallback siis_openno está presente (datos legacy). - Filtro: Cliente-side. Farmacias sin horarios (
opening_hours = null) no se filtran. - Admin: El toggle 24h rellena los 7 días. El editor manual permite día por día.
Puntos de mejora futuros
Pendientes del plan original
- Refresco automático cada 60s: Si la página permanece abierta mucho tiempo, el estado "abierto/cerrado" puede quedar desactualizado. Añadir
setIntervalde 60s enPublicView.jsxpara recalcular o re-fetch. - Cache server-side: Si el dataset de farmacias crece (>1000), cachear
isOpenNowcon TTL de 1 minuto por minuto actual. ComentarioTODO(cache)ya está enserver.js.
UI/UX
- Tooltip explicativo: Al pasar el ratón sobre el badge "24h", mostrar "Abierta las 24 horas del día".
- Color en el filtro: El botón "Abiertas ahora" ganaría con un icono verde intermitente o un cambio de color más evidente.
- Separar horas de apertura/cierre: Actualmente el editor de admin usa
<input type="time">que no acepta24:00como valor. Para crear una farmacia 24h hay que usar el toggle. El input manual no permite escribir24:00. - Ordenación combinada: "Abiertas ahora" + "Ordenar por distancia" deberían priorizar las abiertas pero ordenadas por distancia. Actualmente primero filtra, luego ordena.
Técnicos
- Zona horaria explícita: El servidor usa su hora local. Devolver
server_nowyserver_tzen la respuesta para que la UI pueda mostrar "según hora del servidor". Ver pregunta abierta #1 en el plan. - Tests de integración real: Los tests de backend usan SQLite en memoria. Con PostgreSQL real los endpoints deben comportarse igual.
- Parser OSM 24/7 →
24:00: El parser de OSM ya produce["00:00","24:00"]para24/7. Si en el futuro OSM cambia el formato, actualizar soloopening-hours-osm.js. No tocar nada más (los helpers son agnósticos al formato de entrada). - Cobertura frontend: Los helpers puros de
hours.jsno tienen tests unitarios. Añadir tests paraisAlwaysOpen()frontend,emptyHoursDraft(),hoursToDraft(),draftToHours(),makeAlwaysOpenDraft().
Admin
- Vista previa de horarios: En la lista de farmacias del admin, mostrar un resumen "L-V 9:00-21:00, S 9:00-14:00" o "24h" en vez del JSON crudo.
- Importación masiva con horarios: El importador OSM ya trae
opening_hours. El importador de datos abiertos también si el JSON incluye el campo. Verificar que los tres caminos de ingesta sigan parseando correctamente tras los cambios.
Mobile (apps/frontend-mobile/)
- Los mismos cambios en la app móvil: El frontend móvil tiene su propia copia de i18n (
apps/frontend-mobile/src/i18n/locales/es.jsyca.js) con menos claves. No se ha tocado. Habría que añadir las mismas clavespharmacy.*y replicar la lógica de filtrado/badge.
Archivos creados/modificados
Creados:
apps/backend/__tests__/pharmacy-hours-endpoint.test.jsdocs/horarios.md(este)
Modificados:
apps/backend/server.js— importsrc/hours.js, helperenrichPharmacy, 3 endpoints enriquecidosapps/frontend/src/utils/hours.js—getOpenStatus()con i18n, +5 nuevas exportacionesapps/frontend/src/views/PublicView.jsx— filtro openNowapps/frontend/src/components/PharmacyList.jsx— badge 24h, i18n en estadoapps/frontend/src/components/PharmacyMap.jsx— badge 24h en popupapps/frontend/src/components/admin/PharmacyManagement.jsx— toggle 24h, import desde hours.jsapps/frontend/src/i18n/locales/es.js— 12 nuevas clavesapps/frontend/src/i18n/locales/ca.js— 12 nuevas claves
No tocados (intencionalmente):
apps/API/opening-hours-osm.js— parser OSM en producciónapps/backend/farmacias-webhook-import.js— ya parsea correctamenteapps/frontend-mobile/— requiere移植 manual