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]>
AgendaMax
Aplicación web multi-tenant (SaaS) para la gestión de negocios de servicios (estética, spa, barbería, clínicas, etc.): calendario de citas con arrastrar y soltar, gestión de empleados, servicios, clientes y tickets, y un tablero con ingresos, mejores empleados, servicios más rentables y clientes top.
Tres roles: Administrador de plataforma (crea y gestiona todos los negocios desde la consola admin), Dueño (ve su negocio y el tablero) y Empleado (agenda citas que se auto-asignan a él).
Stack
- Frontend: React 18 + TypeScript + Vite + Tailwind CSS
- Calendario: FullCalendar (drag & drop, resize, day/time grid)
- Gráficas: Recharts
- Backend: Node.js + Express
- Base de datos: SQLite (vía
node:sqlite, nativo de Node — sin compilación nativa)
Requisitos
- Node.js 22.5+ (recomendado 24+). Este proyecto usa el módulo nativo
node:sqlite.
Instalación
npm install
La base de datos se siembra automáticamente con datos demo la primera vez que arranca el servidor. Para regenerarla desde cero:
# Borra la DB y vuelve a sembrar
npm run seed
# o, forzando reset desde el servidor:
$env:RESET_DB="1"; npm run dev:server # PowerShell
RESET_DB=1 npm run dev:server # bash
Desarrollo
npm run dev
Abre http://localhost:5173 (el frontend proxya /api al backend en el puerto 3000).
Producción
npm run build # compila TS y empaqueta el frontend en dist/
npm start # sirve la API y el frontend estático en http://localhost:3000
Instalar AgendaMax como app
- Android/Chrome: abre la URL HTTPS y selecciona la acción de instalación mostrada por AgendaMax o por la barra de direcciones del navegador.
- iPhone/iPad Safari: usa Compartir -> Añadir a pantalla de inicio; iOS no expone el aviso de instalación dentro de la página de Android.
- La app instalada puede abrirse sin la interfaz del navegador, pero los datos del negocio siguen requiriendo conexión a internet.
- Validación local: ejecuta
npm.cmd run build, inicia producción connpm.cmd starty después ejecutanpm.cmd run test:pwa. - La validación de producción requiere el dominio HTTPS de Coolify, no una dirección IP HTTP.
npm.cmd run test:pwa usa estas variables opcionales para los flujos autenticados;
si no se definen, usa las cuentas demo locales:
[email protected]
[email protected]
PWA_PASSWORD=demo1234
Tests y auditoría visual
npm run test:e2e # 22 pruebas end-to-end de la API (login, CRUD, dashboard, auto-asignación, roles, multi-tenant)
npm run test:admin # 17 pruebas del admin (crear negocio + plantilla, aislamiento, reset-demo, delete cascade)
npm run audit:visual # Playwright: navega 10 páginas × 4 viewports (móvil/tablet/desktop/wide),
# mide overflow horizontal, captura errores de consola y guarda capturas en screenshots/
La auditoría visual verifica responsividad en 375 / 768 / 1280 / 1536 px, detecta overflow, errores de consola y confirma que el calendario, el modal de cita y el cambio de sesión funcionan en cada tamaño.
Cuentas demo
Todas usan la contraseña demo1234.
| Rol | Contraseña | Nombre | |
|---|---|---|---|
| Admin | [email protected] |
demo1234 |
Administrador |
| Dueño | [email protected] |
demo1234 |
Daniela Reyes |
| Empleado | [email protected] |
demo1234 |
Valentina Cruz |
| Empleado | [email protected] |
demo1234 |
Mateo Herrera |
| Empleado | sofía.ramí[email protected] |
demo1234 |
Sofía Ramírez |
| Empleado | [email protected] |
demo1234 |
Diego Castillo |
| Empleado | [email protected] |
demo1234 |
Isabela Torres |
| Empleado | carolina.mé[email protected] |
demo1234 |
Carolina Méndez |
En la pantalla de login aparecen botones de acceso rápido a las cuentas demo, y desde la barra lateral puedes cambiar de cuenta con “Ver como…”.
Multi-tenant y consola de administrador
AgendaMax es multi-tenant: cada negocio es un tenant aislado (todos los datos llevan business_id
y las consultas se filtran por el negocio del usuario). El administrador de plataforma gestiona
todos los negocios desde /admin:
- Resumen: KPIs globales (negocios activos, usuarios, ingresos y citas de toda la plataforma).
- Negocios: lista buscable con stats por negocio (usuarios, citas, ingresos).
- Crear negocio (2 pasos): eliges una plantilla → se cargan empleados, servicios y clientes demo con historial de citas/tickets listos para usar; defines los datos del negocio y la cuenta del dueño.
- Detalle de negocio: editar nombre/industria, cambiar plan (Prueba/Pro/Free) y estado (Activo/Suspendido), recargar datos demo con otra plantilla (preserva las cuentas de usuario) o eliminar el negocio por completo (con cascade de todos sus datos).
Plantillas disponibles: estetica-spa (Lumière), barberia (Urban Cut), clinica (Dental Sonrisa)
y blank (vacío, para configurar desde cero). Defínelas en server/lib/templates.ts.
El aislamiento entre tenants se valida: un dueño solo ve los datos de su negocio, y los empleados no pueden acceder al tablero ni a la gestión (403).
Funcionalidades
Calendario (Dueño y Empleado)
- Vistas de mes / semana / día, con indicador de hora actual.
- En móvil se abre por defecto en vista Día y se ofrece una vista Lista (agenda) — la vista Semana/Mes queda reservada para tablet/desktop para evitar columnas aplastadas.
- Arrastra para reagendar y redimensiona para cambiar duración.
- Clic en un hueco o en Nueva cita para abrir el formulario.
- Clic en una cita para editarla: cambiar servicio, especialista, cliente, hora, precio, notas.
- Marcar como completada (genera un ticket con propina y método de pago).
- Cancelar o eliminar citas.
- Filtra por empleado y por estado (programada / completada / cancelada).
Formulario de cita
- Servicio desplegable (menú del negocio), con precio y duración.
- Especialista: el empleado lo tiene auto-asignado por defecto (filtrado a quienes ofrecen el servicio).
- Cliente: buscar existente o crear uno nuevo al vuelo.
- Duración, precio y notas editables.
Tablero (Dueño)
- KPIs: ingresos del período, citas programadas, ticket promedio, ocupación y % de cancelación.
- Gráfica de ingresos diarios y distribución por categoría.
- Mejores empleados por ingreso, con rating y utilización.
- Servicios más rentables.
- Tickets más altos recientes.
- Top clientes (por gasto) y clientes frecuentes (por visitas).
- Rango de tiempo ajustable (7 / 30 / 60 días).
Gestión (Dueño)
- Empleados: crear, editar, asignar servicios, ver ingresos/citas/rating.
- Servicios: catálogo agrupado por categoría, con especialistas asignados.
- Tickets: historial de ventas con filtros por método de pago.
Clientes (Dueño y Empleado)
- Buscador por nombre / teléfono / correo.
- Ficha del cliente con historial de citas, gasto total y visitas.
Estructura
.
├── server/ # API Express + SQLite
│ ├── db.ts # esquema multi-tenant + sistema de migraciones versionado
│ ├── index.ts # app Express (auto-siembla admin + negocio demo en primer arranque)
│ ├── lib/auth.ts # middlewares: authRequired, ownerOnly, adminOnly
│ ├── lib/templates.ts # plantillas: estetica-spa, barberia, clinica, blank
│ ├── routes/ # auth, business, employees, services, clients, appointments, dashboard, admin
│ └── scripts/seed.ts# seedBusiness(businessId, template) reutilizable + script standalone
├── shared/types.ts # tipos TS compartidos cliente/servidor
├── src/ # frontend React
│ ├── components/ # AppShell, AdminShell, Modal, AppointmentModal, ui, DemoSwitcher, admin/CreateBusinessModal
│ ├── lib/ # api, auth, format
│ └── pages/ # Login, Dashboard, Calendar, Employees, Services, Clients, Tickets + admin/*
└── data/ # SQLite (generado, .gitignored)
Notas de seguridad
Es una demo. La autenticación usa un token trivial (el id del usuario) sin hashing de contraseña ni sesiones reales — no usar en producción tal cual. Antes de un despliegue real habría que añadir hashing (bcrypt), JWT firmados, validación estricta con zod, rate-limiting y HTTPS.