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]>
287 lines
24 KiB
Markdown
287 lines
24 KiB
Markdown
# 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.id`→`c.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 <Outlet> 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.
|
||
|
||
---
|
||
|
||
## 2026-07-28 — Dos defectos que solo aparecen en producción
|
||
|
||
Encontrados después del despliegue anterior: ninguno de los dos se ve contra `localhost`.
|
||
|
||
### 1. El botón «Instalar AgendaMax» no aparecía en `/login` (regresión propia)
|
||
|
||
`npm run test:pwa` con `PWA_BASE_URL=https://agendamax.urieljareth.org` falló en
|
||
`assertInstallAction(..., "login")`. Contra `localhost` pasaba.
|
||
|
||
Causa: `beforeinstallprompt` se dispara **una sola vez** por carga y no se repite.
|
||
`useInstallPrompt` registraba su listener dentro de un `useEffect`, o sea al montar el componente. Al
|
||
volver `LoginPage` una ruta `lazy` —cambio de esta misma tanda— `InstallAppPrompt` monta después de
|
||
una ida y vuelta de red extra para traer su chunk. En local eso es un milisegundo y el listener llega
|
||
a tiempo; sobre la red real el evento ya pasó. No era solo el test: un usuario en conexión lenta
|
||
perdía el botón de instalar.
|
||
|
||
Arreglo: `src/lib/installPrompt.ts`, un almacén que registra el listener al evaluarse el módulo, que
|
||
`main.tsx` importa por su efecto antes de montar React. `useInstallPrompt` pasa a leer de ahí
|
||
(`eventoDisponible`/`tomarEvento`/`devolverEvento`) y ya no añade listeners propios.
|
||
|
||
Reproducción determinista, porque el test local no lo veía: interceptar `/assets/LoginPage-*.js` con
|
||
`route()` y retrasarlo 1.5s, manteniendo el disparo sintético en `load`+100ms.
|
||
- Contra producción con el código viejo: evento a los 490ms, botón AUSENTE.
|
||
- Contra el build con el arreglo, retraso de 1.5s y de 4s: botón PRESENTE en los dos.
|
||
|
||
### 2. La gráfica de ingresos no animaba en un iPhone real (reportado por el usuario)
|
||
|
||
`aria-label="Ingresos mensuales en tendencia ascendente"`, en la landing.
|
||
|
||
Lo primero fue descartar hipótesis midiendo, no suponiendo:
|
||
- ¿Está roto el `pathLength`? No: en WebKit a 390px con scroll lento la animación **sí** corría
|
||
(dasharray 0.14 → 1 en ~1.5s).
|
||
- ¿Se dispara demasiado pronto, con el elemento asomando por el borde? **No.** Se dispara con la
|
||
gráfica en y=601..715 de un viewport de 844px, **100% visible**. Hipótesis descartada.
|
||
- ¿`prefers-reduced-motion`? Reproduce el síntoma **exactamente**: sale ya dibujada y estática. Es
|
||
comportamiento por diseño, no un defecto. Queda como causa posible del reporte.
|
||
|
||
Causa que sí se puede blindar: iOS Safari suspende `requestAnimationFrame` durante el scroll por
|
||
inercia. framer-motion interpola contra el reloj de pared, así que la animación consume su 1.4s sin
|
||
pintar un fotograma y al reanudarse salta al estado final — se ve idéntico a "nunca animó".
|
||
|
||
Arreglo: sacar las tres animaciones de la gráfica de framer-motion y pasarlas a keyframes CSS
|
||
(`ld-rev-trazo`, `ld-rev-aparecer`, `ld-rev-aterrizar` en index.css). El componente solo decide
|
||
cuándo empezar, con un `useInView` que pone `data-ld-rev-visible`. Las animaciones CSS llevan su
|
||
propia línea de tiempo en el motor y siguen dibujando cuando rAF no corre. `prefers-reduced-motion`
|
||
también se resuelve en CSS, así que el componente ya no necesita el hook.
|
||
|
||
Verificado: dashoffset 0.93 → 0 en ~1.5s con el área entrando en su retardo; con movimiento reducido
|
||
la línea queda dibujada (dashoffset 0), **no invisible**; y `getAnimations()` devuelve una
|
||
`CSSAnimation` llamada `ld-rev-trazo` de 1400ms en estado `running`, o sea que la lleva el motor.
|
||
|
||
**Lo que NO pude verificar:** no tengo un iPhone físico aquí, y Playwright WebKit en Windows no
|
||
reproduce la suspensión de rAF durante el scroll de iOS. El arreglo es la mitigación estándar para
|
||
ese comportamiento y no cambia nada visualmente en escritorio, pero la confirmación en el dispositivo
|
||
queda pendiente del usuario. Si con Reduce Motion **desactivado** sigue sin animar, la causa es otra
|
||
y hay que volver a medir en el dispositivo.
|
||
|
||
Gates tras los dos arreglos: typecheck 0 · test:landing 13/13 · test:pwa passed · audit:responsive 0
|
||
hallazgos · audit:visual 62 pantallas 0 fallos 0 errores · unit 39/39 · e2e 33/33 · admin 17/17 ·
|
||
booking 12/12.
|
||
|
||
VERIFICADO EN PRODUCCIÓN (commit `4281567`, bundle `index-CGg6by-0.js`):
|
||
- `test:pwa` con `PWA_BASE_URL=https://agendamax.urieljareth.org` **passed** — es exactamente el test
|
||
que fallaba antes del arreglo.
|
||
- La reproducción con el chunk retrasado 1.5s contra producción: evento sintético a los 471ms, botón
|
||
«Instalar AgendaMax» PRESENTE (antes AUSENTE).
|
||
- La gráfica: `getAnimations()` devuelve `CSSAnimation` en producción, y el muestreo confirma que la
|
||
línea se dibuja.
|
||
- Smoke completo 16/16.
|
||
|
||
### Sobre desplegar aquí, para la próxima
|
||
|
||
- **El auto-deploy por webhook está activo en los DOS remotos.** Pushear a gitea y a github seguido
|
||
encoló dos deploys simultáneos del mismo commit (150 y 151). Es inofensivo —misma imagen— pero
|
||
duplica el trabajo del servidor y añade un swap de contenedor extra. Si solo se quiere un deploy,
|
||
pushear a uno y dejar que el otro se sincronice después.
|
||
- **Nunca hace falta `force=true` después de un push**; el webhook ya construye. El force detiene el
|
||
contenedor antes de compilar y añade una caída evitable.
|
||
- Ventana de indisponibilidad medida en este despliegue: **241s** desde el primer 502 hasta el primer
|
||
200 en `/api/health`. Ojo con atribuirla: había un segundo deploy construyendo a la vez, así que es
|
||
la ventana del despliegue completo, **no** una medida limpia del arranque en frío.
|
||
- Hecho de código relacionado, sin medir y sin tocar: `tsx` está en `devDependencies` y la etapa de
|
||
runtime del Dockerfile hace `npm ci --omit=dev`, así que la imagen no lo contiene y
|
||
`CMD ["npx","tsx",...]` lo resuelve del registro **en cada arranque de contenedor**. Eso alarga el
|
||
arranque y hace que levantar dependa de que npm sea alcanzable. Moverlo a `dependencies` es una
|
||
línea; no se hizo porque nadie lo pidió y cambia la imagen de runtime.
|
||
- La API de esta instancia (Coolify 4.1.2) solo responde en endpoints de colección: `/resources`,
|
||
`/projects`, `/servers`, `/deployments` y `/deployments/{uuid}`. Todo lo per-resource
|
||
(`/applications/{uuid}`, `/…/envs`, `/…/logs`) devuelve 404, así que no se pueden leer las
|
||
variables de entorno ni los logs del contenedor por API.
|
||
|
||
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.
|