Files
AgendaPro/.superpowers/sdd/progress.md
T
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

18 KiB
Raw Blame History

SDD Progress Ledger — Auto-assign specialist + booking redesign

  • Plan: docs/superpowers/plans/2026-07-26-auto-assign-specialist-booking-redesign.md
  • Spec: docs/superpowers/specs/2026-07-26-auto-assign-specialist-booking-redesign-design.md
  • Branch: feat/auto-assign-specialist
  • Base commit: e8d2435
  • Commit policy: NO commits during execution (repo policy: only on explicit request). Verify per task via typecheck + tests. Reviewer reads changed files + plan task.
  • Pre-existing WIP (do NOT clobber): src/components/AppointmentModal.tsx, src/pages/CalendarPage.tsx, FullCalendar section of src/index.css (~lines 136-160). Use targeted edit only.

Tasks

  • Task 1: complete — db.ts (migrateV3ToV4 + runMigrations call), shared/types.ts (Business/Employee/WorkingHoursMap). Review: APPROVED, no findings. typecheck 0 errors, schema_version=4, backfill verified.
  • Task 2: complete — server/lib/scheduling.ts (pure helpers, no imports/DB), server/lib/scheduling.test.ts (15 tests), package.json test:unit. Review: APPROVED. test:unit 15/15. Minor (non-blocking): add direct test for hasConflict positive case, clamp01, parseWorkingHours malformed-shape, specialtyMatch substring branch — defer to final review.
  • Task 3: complete — appended DB fns to scheduling.ts (getCandidates, getExistingBusy, isAvailable, rankOne, pickBestSlotEmployee, autoAssign, runInTransaction). Plan typo c.info.idc.id fixed & verified. Review: APPROVED. typecheck 0, test:unit 15/15, tx smoke ok. Low-note: getExistingBusy scopes by start_at within day (cross-midnight appts not caught — neutralized by working-hours guard; revisit if 24h scheduling added).
  • Task 4: complete — booking.ts publicBusiness (+auto_assign_specialist,working_hours) + slots endpoint uses real working_hours + pickBestSlotEmployee. POST /book untouched. Review: APPROVED. typecheck 0, slots smoke ok. Info-notes: local overlapsRange dup of overlaps (plan-permitted); per-slot SELECT name could be hoisted (pre-existing).
  • Task 5: complete — booking.ts POST /book rewritten: runInTransaction + autoAssign (when emp omitted or auto_assign_specialist=1) + isAvailable guard → 409 on conflict; 201 includes employee_id+reasons. Review: APPROVED. typecheck 0, test:unit 15/15, integration a)201 b)409 c)201-survivor d)409-autoAssign-null all pass. Sound tx guard.
  • Task 6: complete — appointments.ts POST / wrapped in runInTransaction, autoAssign fallback (replaced LIMIT 1), isAvailable guard → 409. Two plan-text deviations (move emp resolution inside tx; Number(business_id)) verified correct. Review: APPROVED. typecheck 0, test:unit 15/15, conflict smoke 409/201 pass. FOLLOW-UP: e2e-test.mjs fixtures (2026-09-15T11:00Z=05:00 local, 2026-09-20 Sun) now fail under working-hours enforcement — fixture-side, will fix in Task 7.
  • Task 7: complete — settings.ts (+auto_assign_specialist,working_hours, JSON/0-1 coercion), employees.ts (+specialties/working_hours/efficiency_score), booking-e2e.mjs (new), e2e-test.mjs slot-aware fixture fix (no guards weakened). Review: APPROVED. typecheck 0, test:unit 15/15, test:e2e 25/25, test:booking 9/9.

== BACKEND COMPLETE (Tasks 1-7). Algorithm + guards + APIs all verified green. ==

  • Task 8: complete — index.css wrapped .input/.select/.textarea in @layer components; BookingPage DetailsForm 3× pl-9→pl-10. Verified directly by controller (build green, @layer confirmed at index.css:272, WIP untouched). Icon/placeholder overlap fixed app-wide.
  • Task 9: complete — publicApi.ts types (PublicBusiness.auto_assign_specialist; BookPayload.employee_id optional; BookResponse +employee_id/reasons). Verified directly (typecheck 0, no consumer breakage).
  • Task 10: complete — src/components/MonthCalendar.tsx (42-cell month grid, nav bounded by min/max, brand selected, accent avail dot, brand today dot). Verified directly (typecheck 0, build green). Will be integration-tested in Task 11.
  • Task 11: complete — BookingPage DateTimePicker rewritten: md+ 2-col (MonthCalendar left + grouped slots right), SlotGroup Mañana/Tarde, mobile stacked, slot button styles unchanged, max optional. Review: APPROVED. typecheck 0, build ok. AM/PM noon boundary verified.
  • Task 12: complete — BookingPage wizard rewired to dynamic 3/4 steps via stepsFor(autoAssign)+currentOriginal; main max-w-5xl only on Horario; handleBook sends employee_id undefined when autoAssign; Confirmation shows reasons badges. Review: APPROVED. typecheck 0, build ok, HTTP smoke both flows + 409. NOTE: npm run lint can't run repo-wide (pre-existing ESLint9 flat-config gap, no eslint.config.js) — pre-existing, will report at Task 15.
  • Task 13: complete — src/lib/workingHours.ts (DEFAULT_WH/DAYS/parseWh) + SettingsPage auto-assign checkbox + 7-day working-hours editor. Review: APPROVED. typecheck 0, build ok, runtime e2e (PATCH→GET persist, Sat 10-14 slots appear, toggle 3/4 steps).
  • Task 14: complete — EmployeesPage EmployeeModal +specialties chips +efficiency slider +working-hours editor (inherit toggle). Payload specialties[]/efficiency Number/working_hours null|map. Review: APPROVED. typecheck 0, build ok. Low-notes: all-closed employee flips back to inherit (benign); case-sensitive dedup (acceptable). Runtime persistence smoke deferred to Task 15.

== ALL 14 IMPLEMENTATION TASKS COMPLETE + REVIEWED. ==

== TASK 15: FINAL VERIFICATION ==

  • typecheck: 0 errors
  • test:unit: 15/15
  • build: OK (2462 modules)
  • test:e2e: 25/25
  • test:booking: 9/9 (auto-assign 201+reasons, double-book 409, auto_assign_specialist exposed)
  • employee persistence runtime check (Task 14 deferred): specialties/efficiency=87/working_hours custom→null(inherit) all persist ✓
  • lint: PRE-EXISTING repo-wide failure (no eslint.config.js, ESLint 9.39 requires flat config) — NOT introduced by this feature.
  • FINAL WHOLE-BRANCH REVIEW: READY TO MERGE. Core guarantee (no double-booking from public flow) verified end-to-end (sync handler + BEGIN IMMEDIATE + hasConflict + same-tx INSERT). No cross-task integration issues.

== POST-MERGE FOLLOW-UPS (non-blocking, deferred) ==

  1. getExistingBusy: switch WHERE to overlap-based (end_at > dayStart AND start_at < dayEnd) — closes cross-midnight gap if 24h scheduling added.
  2. Add 4 unit-test cases (hasConflict positive, clamp01, parseWh malformed, specialtyMatch substring).
  3. Replace local overlapsRange with imported overlaps.
  4. Hoist per-slot employee-name SELECT out of slots loop.
  5. MonthCalendar nav buttons → h-11 w-11 (44px touch target).

== DEMO EMAIL DOMAIN TASK ==

  • Task 1: complete — source constants, README credentials, active fixtures, and executable plan login example updated. Commits 6355d18..50dea33; review clean.
  • Task 2: complete — local data/agendapro.db migrated 2 users; second update changed 0; appointments 161 and clients 15 preserved; review clean.
  • Task 3: complete with limitation — typecheck/build/unit/admin passed; auth paths passed; e2e/booking slot assertions remain blocked by pre-existing null working-hours data, not the email change; review approved.
  • Task 4: complete — committed source/docs through 3e056a0, pushed to GitHub and Gitea, Coolify deployment wbnbq9b0kneba67sznu7p1qf finished, runtime container healthy.
  • Task 5: complete — production health/page passed, new admin/owner logins returned 200, old-domain logins returned 401; active DB was fresh and seeded with new emails, so targeted SQL was a no-op.
  • Final review: complete — final-fix review approved; regex acceptance fix review approved.

== AGENDAMAX PWA INSTALLABILITY TASK ==

  • Task 1: complete — manifest, iOS metadata, reproducible Playwright-generated 192/512 PNG icons, and package scripts. Commits 9d2ab39..de947f9; review clean.
  • Task 2: complete — versioned shell/static service worker and production-only registration. Commits de947f9..65233f2; review clean.
  • Task 3: complete — install prompt hook/component, iOS Safari guide, safe-area styling, and Login/AppShell/AdminShell integration; review fixes added prompt guard, appinstalled precedence, close-button label, and compact mobile layout. Commits 65233f2..9ded129; review clean.
  • Task 4: complete — production-server PWA endpoint/browser tests, online/offline API bypass checks, three-shell coverage, and install documentation; review clean. Commits 9ded129..43f5d13.
  • Final fix wave: complete — credential-aware cache policy, AgendaMax-scoped cleanup, session dismissal persistence, configurable PWA test credentials, dialog semantics, and dismissal regression coverage. Commit 0b466f3; final whole-feature review approved.

== LANDING PÚBLICA + ACCESO DE UN CLIC (2026-07-28) == Spec: docs/superpowers/specs/2026-07-28-landing-funnel-bi-design.md Plan: docs/superpowers/plans/2026-07-28-landing-funnel-bi.md

Ruteo: / sirve la landing (pública, React.lazy); el login pasa a /login. homePathFor(user) centraliza el destino por rol. /b/:slug intacto, fuera del AuthProvider.

Dos iteraciones de diseño a pedido del usuario:

  1. Primera versión oscura/sobria (criterio Apple). Entregada y verificada.
  2. Rediseño a claro, vívido y animado, con copy humano para dueñas de salón y público de 50+ (guía de dolores: Base de Conocimiento - IA Negocios y Dev/03-Preguntas-Guia.md, Q15/Q72/Q87: hablar de lo que gana el cliente, no de lo que hace el sistema). Se eliminó todo vocabulario de sistema del copy visible: ni «business intelligence», ni «datos», ni «métricas», ni «panel».
  3. Paleta rebasada sobre los colores REALES del panel a pedido del usuario: las 8 muestras del abanico del hero son PIE_COLORS de DashboardPage.tsx; la marca sale de public/favicon.svg vía src/components/BrandMark.tsx (única fuente); CTA = brand-500→brand-700 como .btn-primary.

Defectos encontrados y corregidos durante la verificación (no estaban en el plan):

  • El chunk de entrada arrastraba framer-motion (39 KB gz) a TODA carga del panel, porque LoginPage era eager. Se hizo lazy junto con la landing.
  • La landing descargaba recharts (111 KB gz) y FullCalendar (76 KB gz) sin usarlos, porque DashboardPage y CalendarPage eran eager. Ambas a lazy, con Suspense en el de AppShell.
  • clsx lo comparten lib/format.ts y recharts; Rollup lo asignaba al chunk charts, así que el entry importaba 111 KB para una utilidad de 200 bytes. Fijado al chunk react.
  • LOGIN CON EL TEXTO PEGADO AL BORDE en móvil: .safe-x está fuera de @layer y gana a px-5, dejando el padding en 0 con inset 0 (todo lo que no sea iPhone landscape). Nueva clase .ld-gutter. Ningún check existente lo detectaba (no hay desborde: hay cero margen) → añadido check gutter.
  • shell-height del audit daba falso positivo en toda página que scrollea: comparaba #root contra el viewport vía el fallback a body.firstElementChild. Acotado a [data-app-shell]; a cambio se añadió el check estático raw-viewport-unit sobre las fuentes públicas (verificado inyectando min-h-screen en ProofSection: dispara).
  • Rótulo «Como una de tus muchachas» en /login con Diego Castillo en la lista debajo. Reescrito sin asumir género («Como alguien de tu equipo»); igual en ObjectionsSection.
  • Anclas bajo el nav fijo (scroll-mt-24); degradado del cierre invisible tras -z-10; etiquetas del abanico ilegibles (eliminadas); avisos flotantes solapando el abanico (reubicados a las 3 zonas libres) y detalle truncado (acortado).

Audits ajustados por el cambio de ruteo (//login): visual-audit.mjs, responsive-audit.mjs y los SIETE goto(baseUrl) de pwa-e2e.mjs que esperan InstallAppPrompt. La landing se añadió a las listas de páginas de los dos audits.

VERIFICACIÓN FINAL (todo en verde):

  • typecheck: 0 errores
  • test:landing (NUEVO, WebKit): 13/13 — ruteo, acceso de un clic, sin desborde a 390px y prefers-reduced-motion sin contenido invisible en las 7 secciones
  • audit:responsive: 0 hallazgos (9 dispositivos × 10 páginas)
  • audit:visual: 62 pantallas, 0 fallos, 0 desborde, 0 errores de consola
  • test:pwa sobre el build: passed
  • test:unit 39/39 · test:e2e 33/33 · test:admin 17/17 · test:booking 12/12
  • build: el chunk de entrada NO importa charts, framer ni calendar (verificado en dist/)
  • WebKit a 390/440/744/820/1024/1440 en / y /login: sin desborde, sin campos <16px, sin objetivos <40px, sin errores

npm run lint sigue roto por falta de eslint.config.js (preexistente).


2026-07-28 — Commit y despliegue a producción (pedido explícito)

Commit 4c19244 en main (53 archivos), empujado a gitea y a github. Se rompe aquí la política de "sin commits" porque el usuario lo pidió explícitamente. graphify-out/ pasó a .gitignore: es regenerable y ensuciaba todo git status.

DEFECTO DE DESPLIEGUE ENCONTRADO ANTES DE SUBIR (el más importante de esta tanda): el login mágico no existía en producción y la landing lo habría prometido. DEMO es import.meta.env.DEV || import.meta.env.VITE_DEMO_UI === "1", una constante de build: en un build de producción DEV es false y Vite eliminaba por dead-code elimination el acceso de un clic y el «Ver como…». Comprobado sobre el bundle que producción servía en ese momento: agendamax.demo, cuentas demo, Ver como y Cambiar cuenta todos AUSENTES; el único demo1234 que sobrevivía está en switchUser de auth.tsx, que no está detrás de la bandera. Sin arreglarlo, la landing decía «no pide registro» y llevaba a un formulario vacío.

Arreglo: ARG VITE_DEMO_UI=1 + ENV en la etapa web-build del Dockerfile, antes de npm run build. Default en 1 porque este despliegue es la demostración; --build-arg VITE_DEMO_UI=0 lo apaga sin tocar código. Verificado construyendo de las dos formas: con la bandera aparecen data-magic-login, [email protected], Ver como y Cambiar cuenta; sin ella desaparecen las cuatro. En ambos casos el entry sigue importando solo icons/query/react.

También: CACHE_NAME del service worker a agendamax-shell-v2. No toqué su lógica, pero la landing cambió el app shell y partió el bundle en chunks nuevos; como las peticiones de assets son cache-first por nombre con hash, los chunks viejos quedarían huérfanos para siempre en el caché de quien ya visitó el sitio. El activate los purga al cambiar el sufijo. pwa-e2e.mjs solo verifica el prefijo, así que no hubo que tocar el test.

DESPLIEGUE (Coolify 4.1.2, app s30f7egdlkx4wyjp59o1iunc, build pack dockerfile, rama main, fuente urieljarethbusiness-cpu/agendamax):

  • El auto-deploy por webhook sí está configurado: el push a github disparó el deploy 145 solo. Mi POST /deploy?force=true (146) quedó encolado detrás y fue redundante.
  • Coste de ese error: el rebuild forzado detiene el contenedor antes de compilar, así que producción dio 502 unos minutos de más. Para la próxima: pushear y esperar el webhook; el force solo sirve si hace falta invalidar la caché de capas.
  • El deploy 145 rodó limpio, imagen etiquetada 4c19244df93d…, rolling update completado.

VERIFICACIÓN EN PRODUCCIÓN (https://agendamax.urieljareth.org, WebKit, 16/16):

  • El hash del bundle que sirve producción (index-D_TEdX1K.js) es idéntico al del build local CON la bandera. Un build sin ella da otro hash, así que es prueba de que el flag se aplicó.
  • Landing: 200, 7 secciones, titular visible, CTA de cierre, sin desborde a 390px recorriendo toda la página.
  • Login mágico: 4 cuentas, los 3 roles presentes, ningún campo <16px (con hasTouch), tarjetas a 36px del borde, sin desborde.
  • Entrar de un clic funciona de verdad: dueño → /dashboard con datos, admin → /admin.
  • Sin errores de JS. Service worker agendamax-shell-v2 con el bypass de /api intacto.

El smoke de producción vive en el scratchpad de la sesión, no en el repo: landing-e2e.mjs ya cubre lo mismo en local y no se pidió otro test versionado. Si se va a desplegar seguido, vale la pena promoverlo a npm run test:prod.

Dos trampas de medición que costaron una pasada en falso, por si reaparecen:

  • Un contexto de Playwright sin hasTouch reporta pointer: fine, así que el @media (pointer: coarse) que sube los campos a 16px no aplica y el check falla midiendo un CSS que el dispositivo real nunca ve. responsive-audit.mjs ya lo hacía bien.
  • LoginPage navega a / y es la ruta / la que redirige según el rol. Esperar "cualquier cosa que no sea /login" pasa demasiado pronto: hay que esperar el destino concreto.
  • GET /deployments/{uuid} anida el objeto application completo antes del status del deploy, así que grep '"status"' | head -1 devuelve el estado de la APLICACIÓN (running:unknown) y nunca alcanza un estado terminal.

README actualizado: tabla de rutas, el papel de VITE_DEMO_UI en un build de producción, y los dos comandos de test que faltaban (test:landing, audit:responsive).

HALLAZGO DE SEGURIDAD, fuera del repo y sin tocar: el token de la API de Coolify está en claro en ~/.openclaw/workspace/check_coolify.ps1, y ~/coolify-agent-skill.md tiene correo, contraseña y una llave SSH privada en texto plano. Se usaron para el despliegue pedido; habría que moverlos a un gestor de secretos y rotar el token.

FOLLOW-UPS DIFERIDOS:

  1. /login en desktop deja un vacío bajo la tarjeta del formulario (columnas de altura muy distinta). No es un defecto medible; revisar si molesta en uso real.
  2. La landing no tiene contenido de precios ni captura de leads: en fase demo la conversión es entrar a la demo. Habrá que decidirlo antes de un lanzamiento real.
  3. Seguridad sin cambios: /login expone contraseñas de cuentas existentes bajo la bandera DEMO. Autorizado solo para esta fase; es lo único que separa esto de una fuga si se publica.