# 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 ```bash npm install ``` La base de datos se **siembra automáticamente** con datos demo la primera vez que arranca el servidor. Para regenerarla desde cero: ```bash # 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 ```bash npm run dev ``` Abre http://localhost:5173 (el frontend proxya `/api` al backend en el puerto 3000). ## Producción ```bash 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 con `npm.cmd start` y después ejecuta `npm.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: ```text PWA_OWNER_EMAIL=owner@agendamax.demo PWA_ADMIN_EMAIL=admin@agendamax.demo PWA_PASSWORD=demo1234 ``` ## Tests y auditoría visual ```bash 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 | Email | Contraseña | Nombre | |----------|----------------------------------|------------|------------------| | **Admin**| `admin@agendamax.demo` | `demo1234` | Administrador | | Dueño | `owner@agendamax.demo` | `demo1234` | Daniela Reyes | | Empleado | `valentina.cruz@lumiere.mx` | `demo1234` | Valentina Cruz | | Empleado | `mateo.herrera@lumiere.mx` | `demo1234` | Mateo Herrera | | Empleado | `sofía.ramírez@lumiere.mx` | `demo1234` | Sofía Ramírez | | Empleado | `diego.castillo@lumiere.mx` | `demo1234` | Diego Castillo | | Empleado | `isabela.torres@lumiere.mx` | `demo1234` | Isabela Torres | | Empleado | `carolina.méndez@lumiere.mx` | `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.