Commit Graph
32 Commits
Author SHA1 Message Date
AgendaPro DevandClaude Opus 5 521c206ba7 fix: el paso de especialista solo ofrece a quien da ese servicio
Elegir "Corte + arreglo de barba" y luego cualquier especialista que no fuera el
barbero dejaba el asistente atascado: /slots responde 400 "El especialista no
ofrece este servicio" y la UI lo pintaba como "No pudimos cargar los horarios.
Intenta otra fecha" — un mensaje que manda a probar días al azar cuando ninguna
fecha lo resuelve.

La raíz es que el payload público listaba todos los empleados activos sin decir
qué servicios ofrece cada uno, así que el cliente no tenía con qué filtrar.

- GET /api/public/:slug devuelve ahora `service_ids` por empleado. El JOIN contra
  employees filtra por negocio: employee_services no tiene business_id propio.
- El paso 2 lista solo a los elegibles, y si nadie ofrece el servicio en concreto
  lo dice en vez de mostrar una lista vacía.
- Cambiar de servicio suelta un especialista que ya no aplique, en lugar de
  arrastrar una combinación que el servidor va a rechazar.
- El estado de error muestra el motivo real del servidor.

booking-e2e.mjs cubre el contrato: cada empleado publica service_ids, quien
ofrece el servicio responde 200 y quien no responde 400. 15/15.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 15:24:10 -06:00
AgendaPro DevandClaude Opus 5 f48a9ac3bf fix: anclar la agenda a la zona horaria del negocio, no a la del proceso
La reserva pública no ofrecía horarios en producción. La ventana laboral se
construía con `new Date(y, m, d, hh, mm)`, que resuelve el reloj de pared en la
tz del proceso. El Dockerfile no fijaba TZ y node:22-slim arranca en UTC,
mientras que la máquina de desarrollo está en America/Mexico_City: por eso solo
fallaba desplegado. Un negocio de 09:00-20:00 se publicaba como 09:00-20:00 UTC
(03:00-14:00 de México), y como el generador descarta lo anterior a ahora+30min,
a partir de la 1 PM la lista quedaba vacía.

Toda la API de scheduling.ts lleva ahora `tz` explícita y resuelve el reloj de
pared con wallToUtcDate/bizDateISO de time.ts, que ya existían para esto.

Arrastraba cinco defectos más en la misma ruta:

- getExistingBusy acotaba el día concatenando `${fecha}T00:00:00`. Como start_at
  se guarda en UTC, una cita de las 19:00 de México vive en el día UTC siguiente
  y quedaba fuera del rango: el guard anti doble-reserva no veía la tarde entera.
  Ahora usa bizDayBoundsIsoFor.
- Un negocio recién sembrado nacía con working_hours y slug en NULL, o sea con
  cero franjas agendables y /b/:slug en 404: el backfill vivía solo dentro de las
  migraciones, que corren antes de que exista la fila. Los defaults se fijan en el
  INSERT (server/lib/businessDefaults.ts) en los tres sitios que crean negocios, y
  migrateV4ToV5 repara los ya rotos. El demo usa slug fijo `mi-negocio-demo`
  porque es la URL ya publicada y el volumen se recrea en cada despliegue.
- El chip mostraba la hora formateada por el servidor y el resumen la del
  navegador: dos horas distintas para el mismo slot. Ambas salen ahora del
  instante resuelto en la tz del negocio.
- La separación mañana/tarde usaba /PM/i sobre un texto ya localizado, y es-MX
  rinde "05:00 p.m." con puntos: nunca casaba, así que el grupo "Tarde"
  desaparecía y toda la tarde se agrupaba bajo "Mañana".
- MonthCalendar comparaba canPrev contra el día 1 del mes visible en vez de
  contra minDate, de modo que la flecha de mes anterior nunca se podía pulsar.

Las guardas de migración comparaban la versión como texto ("10" >= "2" es false),
lo que habría reejecutado migrateV1ToV2 y su DROP TABLE users al llegar a dos
dígitos; ahora comparan números.

Verificación: scheduling.test.ts fija TZ=UTC y usa negocios en America/Mexico_City
para que la tz del proceso y la del negocio nunca coincidan; el Dockerfile fija
ENV TZ=UTC por lo mismo. 43 unitarias + 33 e2e + 12 booking + 17 admin en verde
con el servidor en UTC y base recién sembrada; typecheck limpio. booking-e2e.mjs
busca el próximo día abierto en vez de asumir "mañana", que lo hacía fallar cada
viernes y sábado por calendario.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-28 10:47:59 -06:00
AgendaPro DevandClaude Opus 5 0069d23744 docs: record the production verification of both fixes
Adds the production evidence for commit 4281567 (`test:pwa` passing against the HTTPS
domain, the install button present with the chunk delayed, the chart running as a
CSSAnimation, smoke 16/16) and what this deploy taught about the setup:

- Auto-deploy webhooks are active on BOTH remotes, so pushing to gitea and github in
  sequence queues two concurrent deploys of the same commit.
- `force=true` is never needed after a push; it stops the container before building and
  adds avoidable downtime.
- Measured 241s of unavailability for this deploy — explicitly not attributed to cold
  start, since a second deploy was building concurrently.
- This Coolify instance only answers on collection endpoints; everything per-resource
  404s, so container env vars and logs are not readable over the API.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 15:41:17 -06:00
AgendaPro DevandClaude Opus 5 4281567207 fix: two defects that only reproduce over a real network
Neither is visible against localhost, which is why both shipped.

**Install button never appeared on `/login`** — a regression from making the login a
lazy route. `beforeinstallprompt` fires once per page load and is never replayed;
`useInstallPrompt` attached its listener from a `useEffect`, i.e. at mount, and
`InstallAppPrompt` now mounts only after an extra round-trip for its chunk. Locally
that round-trip is a millisecond so the listener still won a race it should never
have been in. Over a real connection the event was long gone, so a user on a slow
link lost the install button entirely.

The listener now lives in `src/lib/installPrompt.ts` and registers when the module
evaluates — `main.tsx` imports it for its side effect before mounting React. The hook
only reads from that store. Reproduced deterministically by delaying
`/assets/LoginPage-*.js` by 1.5s via `route()`: absent before, present after (also at
a 4s delay). `test:pwa` against the HTTPS domain now passes.

**Revenue chart did not animate on a real iPhone.** Measured rather than guessed:
the path animation does run in WebKit, and it triggers with the chart 100% visible at
y=601..715 of an 844px viewport — so neither "broken" nor "fires too early". What
fits is that iOS Safari suspends `requestAnimationFrame` during momentum scrolling
while framer-motion interpolates against wall-clock time: the animation spends its
1.4s without painting a frame and snaps to the end on resume, which looks exactly
like it never ran.

The chart's three animations move to CSS keyframes, which keep their own timeline in
the engine. The component only decides *when* (a `useInView` setting
`data-ld-rev-visible`). `prefers-reduced-motion` resolves in CSS too, and still
resolves to the *drawn* state — a line left at `dashoffset: 1px` with no animation
would be invisible forever. Verified: `getAnimations()` returns a `CSSAnimation`, and
under reduced motion the line renders complete.

Not verified: no physical iPhone here, and Playwright WebKit on Windows does not
reproduce iOS's rAF suspension. This is the standard mitigation and changes nothing
on desktop, but on-device confirmation is still outstanding.

Verified: typecheck clean; landing 13/13, PWA passed, responsive 0 findings, visual
62 screens / 0 errors, unit 39/39, e2e 33/33, admin 17/17, booking 12/12.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 15:09:29 -06:00
AgendaPro DevandClaude Opus 5 842a9ea07e docs: record the landing deploy and document the demo build flag
README: route table for `/`, `/login` and `/b/:slug`; what `VITE_DEMO_UI` does to a
production build and how to turn the demo accounts off; the two test commands that
were missing (`test:landing`, `audit:responsive`).

progress.md: the deploy entry, including the defect it caught — the deployed bundle
had every magic-login string eliminated, so the new landing would have promised "no
registration" and led to an empty form — and the two measurement traps that cost a
false pass (a Playwright context without `hasTouch` reports `pointer: fine`, and
`GET /deployments/{uuid}` nests the application object before the deployment status).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 14:36:46 -06:00
AgendaPro DevandClaude Opus 5 4c19244df9 feat: public landing page with magic login, brand palette and demo build flag
Adds a public funnel landing at `/` and moves the login to `/login`, rebuilt
around the panel's own colors instead of an invented palette.

Landing (`src/components/landing/`, one section per file, no props):
- Seven funnel sections composed by `LandingPage`. The hero's eight swatches are
  literally `PIE_COLORS` from `DashboardPage`, the same hex values the seed hands
  to avatars and services, so "your colors become your numbers" is literal.
- `BrandMark` becomes the single source for the logo, replicating
  `public/favicon.svg`. Blue is the action surface, orange only ever marks.
- `NotebookVisual` is the one deliberate exception to the palette: it is what the
  product replaces.
- Copy drops all system vocabulary; motion comes from `src/lib/motion.ts` with a
  single easing, and reduced-motion resolves `initial` to the final state so a
  never-firing `whileInView` cannot leave a section invisible forever.

Routing and bundle:
- `homePathFor` is the single definition of each role's destination.
- Landing, login, dashboard and calendar load lazily. Eager, the login dragged
  framer-motion (~40 KB gz) into every panel load and the landing downloaded
  recharts + FullCalendar (~187 KB gz) without charting anything. `clsx` is
  pinned to the `react` chunk because Rollup otherwise assigns it to `charts`,
  making the entry import 111 KB gz for a 200-byte utility.

Responsiveness (iPhone/iPad), verified with `npm run audit:responsive`:
- No touch form field below 16px, `dvh` height utilities, safe-area insets, and
  40px touch targets keyed off `pointer: coarse` rather than `sm:`.
- New `.ld-gutter`: `.safe-x` lives outside `@layer` and beats Tailwind's `px-*`,
  so it left the login's side padding at 0 on anything but an iPhone in
  landscape. No overflow check could see it — there was no overflow, just zero
  margin. The audit now guards it with a `gutter` check.
- `shell-height` no longer fires on pages that legitimately scroll; the static
  `raw-viewport-unit` scan covers those instead.

Production build:
- The Dockerfile now sets `VITE_DEMO_UI=1` as a build arg. `DEMO` is a build-time
  constant, so without it Vite eliminated the magic login and the "Ver como…"
  switcher: the deployed landing promised "no registration" and led to an empty
  form. Verified by building both ways and diffing the bundle.
- Service worker cache bumped to v2 so the orphaned pre-landing chunks get purged
  from returning visitors' caches.

Verified: typecheck clean; unit 39/39, e2e 33/33, admin 17/17, booking 12/12,
landing 13/13 (WebKit), PWA passed against the real production build.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 14:25:58 -06:00
AgendaPro Dev 0b466f33f3 fix: address final PWA review findings 2026-07-27 17:19:45 -06:00
AgendaPro Dev 43f5d1374c test: verify service worker API bypass 2026-07-27 17:01:25 -06:00
AgendaPro Dev a8becf07d6 test: harden AgendaMax PWA checks 2026-07-27 16:57:40 -06:00
AgendaPro Dev b6dcad68fb test: verify AgendaMax PWA installation 2026-07-27 16:45:14 -06:00
AgendaPro Dev 9ded129478 fix: harden PWA install prompt 2026-07-27 16:32:58 -06:00
AgendaPro Dev 0238dff5b1 feat: add cross-platform PWA install prompt 2026-07-27 16:28:54 -06:00
AgendaPro Dev 65233f2e4d feat: add production PWA service worker 2026-07-27 16:24:26 -06:00
AgendaPro Dev de947f9d35 feat: add AgendaMax PWA metadata and icons 2026-07-27 16:20:36 -06:00
AgendaPro Dev 9d2ab39d84 docs: plan AgendaMax PWA implementation 2026-07-27 16:16:58 -06:00
AgendaPro Dev fe8b701e27 docs: specify AgendaMax PWA installability 2026-07-27 15:12:58 -06:00
AgendaPro Dev 3e056a0dbf docs: fix demo domain acceptance check 2026-07-27 13:57:44 -06:00
AgendaPro Dev 3f84371842 docs: harden demo email migration checks 2026-07-27 13:48:12 -06:00
AgendaPro Dev 811b664405 docs: plan demo email migration 2026-07-27 12:13:35 -06:00
AgendaPro Dev 50dea33728 docs: record demo access task report 2026-07-27 11:38:49 -06:00
AgendaPro Dev 6355d18a07 docs: standardize AgendaMax demo access 2026-07-27 11:36:42 -06:00
AgendaPro Dev 0e6195ce23 Merge branch 'main' of https://gitea-hjwh0svsoo9p5w5kj2j6b1bd.urieljareth.org/urieljareth/AgendaPro 2026-07-27 10:12:48 -06:00
AgendaPro Dev bec3e45573 deploy: add Dockerfile + .dockerignore for Coolify 2026-07-27 10:11:47 -06:00
AgendaPro Dev d796538fd9 feat: rebrand AgendaPro -> AgendaMax + calendar fixes + current-week demo seed
- Rebrand all user-facing text to AgendaMax
- Fix sidebar scrolling: card container no longer overflows html (flex h-full min-h-0)
- Fix calendar toolbar hover: active buttons keep readable contrast
- Sticky calendar toolbar (month/week/day always visible on scroll)
- Seed guarantees 2-4 appointments per day for current week (Mon-Sat)
2026-07-27 10:11:16 -06:00
AgendaPro Dev 4227db0c8b docs: spec + implementation plan for specialist auto-assignment
Design doc (docs/superpowers/specs) and task-by-task implementation plan (docs/superpowers/plans) for the auto-assign + booking redesign.
2026-07-26 16:00:04 -06:00
AgendaPro Dev 5410e27c89 feat(ui): booking redesign + auto-assign settings + specialist fields
Dynamic 3/4-step wizard (omits specialist step when auto-assign on), always-visible MonthCalendar, 2-column desktop layout with morning/afternoon slot grouping, confirmation reasons badges. Settings: auto-assign checkbox + business working-hours editor. Employee modal: specialties, efficiency, per-employee working hours. Fix icon/placeholder overlap app-wide via @layer components + pl-10.
2026-07-26 15:59:41 -06:00
AgendaPro Dev ed608656e0 feat(backend): auto-assign + transactional double-booking guard + working-hours slots
POST /book and POST /appointments now resolve the specialist via autoAssign (or isAvailable-guard a chosen one) inside BEGIN IMMEDIATE...COMMIT, returning 409 on conflict. Slots endpoint derives the day window from real working_hours and ranks the best specialist per slot. settings/employees expose + persist the new fields. Adds booking-e2e.mjs and adapts e2e-test.mjs fixtures to in-hours slots.
2026-07-26 15:59:28 -06:00
AgendaPro Dev 811d2a24b2 feat(scheduling): specialist scoring + availability module with unit tests
Pure helpers (parseWorkingHours, getWorkingHoursForDate, specialtyMatch, overlaps, scoreCandidate) + DB-backed functions (getCandidates, isAvailable, pickBestSlotEmployee, autoAssign, runInTransaction). Hard constraint: no overlap with existing (scheduled,completed) appts + within working-hours window. Score = 50*specialty + 30*efficiency + 20*(1-load). 15 node:test unit tests.
2026-07-26 15:59:16 -06:00
AgendaPro Dev 9d78663cfe feat(db): v4 migration + shared types for specialist auto-assignment
Adds auto_assign_specialist + working_hours to businesses, and specialties + working_hours + efficiency_score to employees, with idempotent migrateV3ToV4 (backfills default Lun-Vie 09:00-20:00 working hours). Updates Business/Employee types and adds WorkingHoursMap.
2026-07-26 15:59:02 -06:00
urieljareth 6b28b9f6c0 Improve background launch and port overrides 2026-07-25 17:17:22 -06:00
urieljareth feaa882fc3 Harden local setup and development startup 2026-07-25 17:14:19 -06:00
AgendaPro Dev e8d2435cc2 AgendaPro v1.0 - multi-tenant SaaS, mobile calendar, public booking
Multi-tenant scheduling SaaS (AgendaPro-equivalent):
- Backend: Node + Express + node:sqlite, multi-tenant (admin/owner/employee),
  versioned migrations, 4 templates (estetica-spa, barberia, clinica, blank).
- Frontend: React 18 + TS + Vite + Tailwind, FullCalendar drag-and-drop,
  Recharts dashboard, mobile-first (day/list views on mobile).
- Features: calendar+services+employees+clients+tickets, dashboard with
  best employee/service, top tickets, top/frequent clients, commissions,
  cancellation policy + no-show tracking, public online booking (/b/:slug),
  cash register (cierre de caja), reminders/notifications center.
- Verification: 65/65 e2e API, 58/58 visual (0 overflow, 0 console errors),
  typecheck clean, vite build OK.
2026-07-25 13:45:53 -06:00