feat(platform): multi-tenancy con credenciales por negocio y sincronización por id

El backend Postgres de `platform/` asumía un solo negocio con un solo token del
CRM. Este cambio lo convierte en una plataforma multi-cuenta y añade la
sincronización selectiva de las cinco entidades del encargo.

## Multi-tenancy

El `locationId` ya era por negocio, pero el token vivía en la variable de entorno
`CRM_TOKEN`, una sola para todo el proceso. Con dos negocios eso usaba el token
del primero contra la subcuenta del segundo: 401 en el mejor caso, escritura en
la subcuenta equivocada en el peor.

- `lib/crypto.ts` — AES-256-GCM para los tokens. Autenticado a propósito: una
  fila manipulada hace que el descifrado FALLE, en vez de devolver basura que
  acabaríamos mandando como credencial al CRM. La clave maestra vive en
  `CRM_MASTER_KEY`, fuera de la base.
- `crm/ctx.ts` — `CrmCtx { businessId, locationId, token }` sustituye al
  `locationId: string` suelto que viajaba por once firmas. Es un objeto y no dos
  parámetros porque dos `string` seguidos se cruzan sin que el compilador diga
  nada, y cruzarlos aquí manda el token de un cliente a la subcuenta de otro. Es
  el único sitio donde el token existe descifrado, y solo en memoria.
- `crm/client.ts` — `CrmOptions.token` pasa a ser OBLIGATORIO, sin valor por
  defecto: olvidarlo es ahora un error de compilación. El estrangulador pasa a
  ser por token y aprende la cuota de las cabeceras `x-ratelimit-*`, que declaran
  100 peticiones por 10 s — el cliente iba 6,5x por debajo con una estimación.
- Migración 003: credencial cifrada, calendario y la red de seguridad de mensajes
  POR NEGOCIO. Como variable global decidía por todas las cuentas a la vez.

Lo único de la credencial que sale del servidor es la huella de 6 caracteres.

## Consola de superadministración

`/api/admin`, solo para el rol `admin`: alta de cuentas con su dueña en una
transacción, vínculo, desvínculo y suspensión. Las credenciales se COMPRUEBAN
contra el CRM antes de guardarse — un token sin validar traslada el fallo al
primer intento de sincronizar, lejos de donde se cometió. El error distingue
«token inválido» de «subcuenta inexistente» de «token de otra subcuenta».

Pantalla en `/admin/cuentas`, verificada en navegador: el campo del token es de
contraseña y viene vacío, porque no hay valor que traer.

## Sincronización por identificador

`POST /api/crm/sync/:entidad/:id` para contacto, conversación, mensaje, cita y
servicio. La dirección la decide la entidad: las tres primeras se TRAEN porque el
CRM es su dueño; las dos últimas se EMPUJAN, porque el calendario del CRM tiene
una sola cita en dos años y su catálogo de servicios está vacío.

- `crm/conversations.ts` — lectura por id de conversaciones y mensajes sueltos.
- `crm/syncConversations.ts` — el espejo persistido. Las tablas existían desde
  002_crm.sql y nadie escribía en ellas: la bandeja consultaba el CRM en vivo.
- `crm/calendars.ts` — escritura de citas al calendario. `isoConDesplazamiento`
  escribe la hora de pared del negocio con su desplazamiento; `toISOString()`
  habría movido la hora que el CRM enseña en su interfaz.
- `crm/services.ts` — publicación de servicios al catálogo.

## Verificado contra la subcuenta real, no deducido

Las cinco entidades se ejercieron contra el CRM del cliente. Las escrituras van
en un ciclo crear → releer → borrar → confirmar borrado, con la limpieza en un
`finally`, y antes se comprobó que el borrado existe: preguntar si se puede
deshacer ANTES de escribir en el CRM de un cliente, no después. La subcuenta
quedó como estaba.

47 hallazgos medidos en `crm/HALLAZGOS.md`, y la referencia de endpoints en
`crm/API.md`, con la lista explícita de dónde la documentación oficial falla.

110 pruebas de plataforma en verde, typecheck limpio, build correcto. El backend
de demo de `server/` no se ha tocado y sigue con sus 43 pruebas.

## Deuda conocida, dicha sin rodeos

- La bandeja de mensajes todavía lee en vivo del CRM, no del espejo.
- La autenticación sigue siendo el id del usuario en texto plano, también para el
  rol admin. Esta consola crea cuentas y guarda credenciales de clientes encima
  de esa base: no debe quedar expuesta a internet hasta endurecerla.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
AgendaPro Dev
2026-08-30 15:07:20 -06:00
co-authored by Claude Opus 5
parent dcbf750c09
commit 6d67b23e55
95 changed files with 16132 additions and 41 deletions
+187 -36
View File
@@ -1,42 +1,193 @@
# AgendaPro — Auditoría de funcionalidades (basada en investigación real)
# AgendaPro — Auditoría de producto e investigación de mercado
Fuentes: agendapro.com/mx (site + blog), comparativa Wellbe vs AgendaPro, artículo de política de cancelaciones (CEO Julio Guzmán). G2/Capterra/Trustpilot bloquearon scraping (403), pero el blog oficial expone casos de uso y pain points reales.
> Revisión: **2026-07-28**. Sustituye la versión anterior de este archivo, que daba por faltantes
> cinco módulos que ya están construidos (caja, comisiones, recordatorios, booking público y política
> de cancelación). Cada fila de las tablas de abajo se verificó contra el esquema de
> [server/db.ts](server/db.ts), los endpoints montados en [server/index.ts](server/index.ts) y las
> páginas de [src/pages/](src/pages/) — no contra memoria ni contra el documento anterior.
## Lo que YA tenemos (vs AgendaPro real)
- ✅ Calendario drag & drop (mes/semana/día/lista) con auto-asignación a empleados
- ✅ Servicios, empleados, clientes + historial/ficha
- ✅ Tickets, pagos, propinas
- ✅ Dashboard dueño (mejor empleado/servicio, top tickets, top/frecuentes clientes, ingresos)
- ✅ Multi-tenant SaaS + consola admin + plantillas
- ✅ Reseñas/ratings
---
## Brechas CRÍTICAS vs AgendaPro real (oportunidades de mejora)
1. **Política de cancelación + no-shows** — AgendaPro destaca esto; en MX la inasistencia es 15–35%. Nosotros solo marcamos estado, sin penalización ni depósito. **#1 pain real.**
2. **Comisiones** — cálculo automático por venta/servicio. Nosotros no lo tenemos. Pain operativo grande.
3. **Cierre de caja / flujo de caja** — apertura/cierre diario, ingresos/egresos. Nosotros solo listamos tickets.
4. **Página de reservas online pública (24/7)** — el cliente se auto-agenda. Nuestro mayor gap de adquisición. Reduce ruido de WhatsApp.
5. **Recordatorios automáticos** (WhatsApp/SMS/email) — reducen inasistencias. Nosotros no tenemos centro de notificaciones.
6. **Inventario** — control de productos con alertas de stock bajo.
7. **Lista de espera / waitlist** — para rellenar cancelaciones.
8. **Fidelización / giftcards / membresías** — paquetes y crédito.
9. **Encuestas de satisfacción (NPS)** — aparte de reseñas puntuales.
10. **Multi-sucursal** dentro de un negocio (ya tenemos multi-tenant, no multi-branch).
11. **Sincronización Google Calendar.**
## 1. El producto que emulamos
## Pain points reales de los usuarios (del blog/casos)
- No-shows = pérdida directa de ingresos (un salón perdía 15 citas/mes).
- Cierre de caja con descuadre ("menos dinero del que debería haber").
- Cálculo manual de comisiones = error y tiempo.
- Inventario desordenado en salones.
- Cobranza incómoda ("recordatorios de pago").
- Pérdida de clientes por falta de fidelización.
- Saturación de recepción y WhatsApps repetitivos preguntando horario/precio.
AgendaPro es el software de agendamiento líder en Latinoamérica: **+20.000 negocios**, presencia en
**+100 países**, foco en México, Colombia, Argentina y Chile. Su promesa comercial es *«el único
software que ordena tu negocio y acelera su crecimiento un 82%»*.
## Priorización (impacto × factibilidad) — lo que implementaremos
- **P0 Política de cancelación + no-show** (company) — resuelve el dolor #1.
- **P0 Comisiones** (company/empleado) — dolor operativo top.
- **P0 Página pública de reservas /b/:slug** (cliente) — mayor diferenciador, reduce WhatsApp.
- **P1 Cierre de caja** (company) — control financiero del día.
- **P1 Centro de notificaciones/recordatorios** (company) — reduce no-shows (simulado, sin WhatsApp real).
**Verticales:** salones de belleza, spas, barberías, peluquerías, centros de estética, clínicas,
psicólogos, nutricionistas, fisioterapeutas, podólogos y bienestar en general.
Perspectivas cubiertas: **cliente** (booking público), **empresa** (caja, comisiones, política, dashboard), **empleado** (sus comisiones, su agenda).
### 1.1 La escalera de planes (esto *es* el producto)
El modelo de negocio de AgendaPro es la escalera de planes, y cada módulo está deliberadamente
asignado a un escalón. Precios de lista en USD (LatAm) y EUR (España):
| Plan | USD/mes | EUR/mes | Profesionales | Correos mkt | Qué añade sobre el plan anterior |
|---|---|---|---|---|---|
| **Individual** | $9 | €10 | 1 | 500 | Agenda ilimitada + presencia en Marketplace, CRM, recordatorios automáticos, sitio de reservas, reportes de gestión, sistema de caja, niveles de acceso, control de ocupación, gestión de presupuesto |
| **Básico** | $29 | €19 | hasta 20 | 1.000 | **Inventario** (control + alertas de stock bajo), **Comisiones** (cálculo automático) |
| **Premium** ★ | $59 | €59 | hasta 20 | 2.000 | **Encuestas de satisfacción**, **Fichas personalizables**, **Ficha clínica + consentimiento informado**, **Giftcards**, **Presupuestos**, email automático de cumpleaños, sitio con URL y colores propios |
| **Pro** | $199 | €199 | hasta 20 | 5.000 | **Acceso a API**, soporte personalizado, integración Google Analytics / Meta Pixel |
★ = el que ellos marcan como «más popular».
**Complementos que se cobran aparte** (dato relevante: lo que su marketing presenta como bandera
central no viene incluido en ningún plan):
| Add-on | Precio | Detalle |
|---|---|---|
| WhatsApp | desde $7 USD / €5 al mes | **50 mensajes mensuales** |
| Videoconferencia | desde $11 USD al mes | pack de 2.500 min |
| Charly (asistente de marketing con IA) | desde $55 USD al mes | pago por resultados |
| Facturación electrónica | «próximamente» | — |
Prueba gratuita: **7 días**.
### 1.2 Módulos que anuncian, agrupados como ellos los agrupan
- **Citas:** agenda online, sitio de reservas 24/7, recordatorios automáticos por WhatsApp y email,
«IA de recordatorios por WhatsApp», administración de horarios.
- **CRM:** base de datos de clientes, historial de visitas, control de sesiones y tratamientos, ficha
del cliente, promociones personalizadas.
- **Inventario:** control de inventario, alertas de inventario bajo, comisiones por venta de productos.
- **Marketing y fidelización:** integración con Google Reserve en Google My Business, LinkPro (tarjeta
de presentación para redes), campañas de email marketing, programas de lealtad y giftcards, acceso
al **marketplace** de servicios.
- **Pagos:** registro y reportes de pagos, pagos con terminal, pagos online y link de pago,
facturación / CFDI, pago de sesiones.
- **Control del negocio:** control de caja, reportes de ingresos y egresos, reportes de ventas con IA,
control de múltiples sucursales, cálculo automático de comisiones.
- **Gimnasios / fitness:** agenda de clases grupales, control de membresías, gestión de entrenadores.
- **Multi-sucursal:** varias sedes desde una sola cuenta, con reportes consolidados.
### 1.3 Dónde AgendaPro es débil (según reseñas de usuarios)
Esto importa: son los huecos donde un competidor puede ganar en lugar de empatar.
1. **Fichas clínicas genéricas.** No tienen CIE-10 electrónico ni plantillas por especialidad. Es la
queja más concreta y sistemática.
2. **Permisos poco granulares por sede.** Problema real para clínicas medianas y grandes: no se puede
acotar bien qué ve cada persona en cada sucursal.
3. **Precio y alzas periódicas.** Usuarios reportan subidas recurrentes y retiro de funcionalidades de
planes que ya pagaban.
4. **Prueba de 7 días**, contra 14+ de la competencia.
5. **WhatsApp de pago y racionado** (50 mensajes/mes desde $7), siendo el canal que su propio marketing
pone al frente.
6. **Curva de costo para negocios chicos:** los planes intermedios superan los $40–80 USD/mes, lo que
los saca de rango para un negocio de 1–3 personas.
### 1.4 El marco competitivo, y la parte que no se resuelve con features
| | Modelo | Implicación |
|---|---|---|
| **AgendaPro** | Suscripción por escalones + add-ons | Ingreso predecible; el cliente paga antes de ver valor |
| **Fresha** | **$0 de mensualidad**; comisión sobre clientes nuevos del marketplace + ~2,19% + $0,20 USD por transacción | Sin barrera de entrada; monetiza adquisición y pagos |
La conclusión honesta de la investigación: **el foso de los dos líderes no es el software, es el
marketplace.** Fresha y AgendaPro traen clientes nuevos al negocio; eso no se replica implementando
módulos. Cualquier plan de equivalencia funcional debe asumir que empata en producto y no en
distribución.
### 1.5 Datos de industria (con su fuente, y una corrección)
- **No-shows: 10%–30%** según sector; **15%–25%** en belleza y estética. En clínicas de bienestar
(España) 12%–19%, hasta **23%** en odontología y masajes.
- **Recordatorios por WhatsApp a 3 días y 24 h antes reducen las ausencias entre 30% y 50%.**
- **Depósitos recomendados: 20%–30%** del valor del servicio, y el depósito debe **abonarse al costo
final**, no ser un cargo extra.
- **Ventana de cancelación estándar: 24–48 h**; 72–96 h en sectores de alta demanda.
- **Escala de penalización de ejemplo:** gratis con más de 48 h; 25% entre 48 y 24 h; 50% entre 24 y
12 h; 100% el mismo día.
- Coste ilustrativo: 5 citas perdidas por semana a 60 € ≈ **18.000 €/año** de ingreso perdido.
> **Corrección al documento anterior:** la versión previa de este archivo afirmaba «en MX la
> inasistencia es 15–35%» sin fuente. No encontré respaldo para el techo del 35%. Los rangos de arriba
> sí están sostenidos. Además, el artículo de política de cancelaciones de AgendaPro —citado antes como
> fuente de cifras— **no contiene estadísticas de no-show**: solo recomendaciones. Las cifras de arriba
> vienen de fuentes de industria independientes.
---
## 2. Auditoría: qué tiene hoy AgendaMax
Verificado contra esquema, endpoints y páginas.
### 2.1 Construido y funcionando
| Capacidad de AgendaPro | Estado | Evidencia en el repo |
|---|---|---|
| Agenda / calendario | ✅ | [CalendarPage.tsx](src/pages/CalendarPage.tsx) con FullCalendar, 3 breakpoints, drag & drop; `/api/appointments` |
| Sitio de reservas público 24/7 | ✅ | `/b/:slug` → [BookingPage.tsx](src/pages/public/BookingPage.tsx); `/api/public/:slug`, `/slots`, `/book` |
| Guard anti doble-reserva | ✅ **superior** | `runInTransaction` + `BEGIN IMMEDIATE` + `isAvailable` + INSERT en la misma tx ([scheduling.ts](server/lib/scheduling.ts)) |
| Auto-asignación de especialista | ✅ **no lo tiene AgendaPro** | `autoAssign`, `pickBestSlotEmployee`, `scoreCandidate` con especialidades y `efficiency_score` |
| Horarios por negocio y por empleado | ✅ | `businesses.working_hours`, `employees.working_hours` (JSON 1..7, `null` = hereda) |
| CRM / ficha e historial de cliente | ✅ | `clients` + [ClientDetailPage.tsx](src/pages/ClientDetailPage.tsx) con `no_show_count` |
| Comisiones | ✅ | `services.commission_pct`, `employees.commission_pct`, snapshot en `tickets.commission`; `/api/dashboard/commissions`, `/api/me/commissions`, [MyPerformancePage.tsx](src/pages/MyPerformancePage.tsx) |
| Control de caja / cierre diario | ✅ | `cash_sessions` (apertura, cierre, `expected_amount` → descuadre) + `cash_entries` (income/expense); `/api/cash/*`; [CashPage.tsx](src/pages/CashPage.tsx) |
| Reportes de gestión | ✅ | 8 endpoints en `/api/dashboard/*` + [DashboardPage.tsx](src/pages/DashboardPage.tsx) con recharts |
| Reseñas / ratings | ✅ | tabla `reviews` (AgendaPro no lo vende como módulo) |
| Multi-tenant + consola de plataforma | ✅ **superior** | `/api/admin/*`, plantillas por vertical, alta de negocio, `reset-demo`, cascade |
| Sitio de reservas con marca propia | ✅ | landing + booking con paleta heredada del panel (AgendaPro lo cobra en Premium) |
| App instalable | ✅ | PWA con service worker y prompt de instalación (equivalente funcional a su app nativa) |
| Aislamiento por tenant verificado | ✅ | `test:e2e` y `test:admin` comprueban 403 y no-visibilidad cruzada |
### 2.2 Construido a medias — el hueco entre «configurable» y «operante»
Estas tres son las más engañosas de la auditoría: existen en el esquema y en la UI, pero **no cierran
el ciclo**. Un demo puede mostrarlas y no hacen nada.
| Capacidad | Qué hay | Qué falta |
|---|---|---|
| **Política de cancelación y depósito** | `businesses.cancel_window_hours`, `cancel_penalty_pct`, `require_deposit`, `deposit_pct`; `GET /api/appointments/:id/cancellation-policy`; editor en [SettingsPage.tsx](src/pages/SettingsPage.tsx) | **Nunca se cobra nada.** No hay captura de depósito en el booking, ni aplicación de la penalización a un ticket, ni registro del cargo. La política se configura y se consulta; no tiene efecto económico. |
| **Recordatorios automáticos** | tabla `notifications` (canales `whatsapp`/`sms`/`email`, tipos `confirmation`/`reminder`/`cancellation`/`follow_up`/`review`), `/api/notifications` con `regenerate`/`send`/`cancel`/`stats`, [NotificationsPage.tsx](src/pages/NotificationsPage.tsx) | Es una **bandeja simulada**: `send` marca `sent` sin salir a ningún proveedor. Aceptable para demo, pero no equivale al módulo real. |
| **Niveles de acceso y permisos** | 3 roles fijos (`admin`/`owner`/`employee`) con autorización real en middlewares y aislamiento probado | Sin permisos granulares. Nota: **AgendaPro también es débil aquí** (§1.3), así que es una oportunidad, no solo una deuda. |
### 2.3 Brechas reales — lo que no existe
Ordenado por el escalón de plan al que pertenece en AgendaPro, porque eso define qué se puede
reclamar como «equivalente al plan X».
| # | Brecha | Plan AgendaPro | Notas de implementación |
|---|---|---|---|
| 1 | **Inventario de productos + alertas de stock bajo** | Básico | No hay tabla de productos ni movimientos de stock |
| 2 | **Venta multi-ítem (POS real)** | Básico/Individual | **Bloqueo estructural:** hoy `tickets` es 1 ticket = 1 cita = 1 servicio (`service_id` único). No se puede vender «corte + shampoo + giftcard» en una venta |
| 3 | **Comisión por venta de productos** | Básico | Depende de 1 y 2 |
| 4 | **Encuestas de satisfacción / NPS** | Premium | `reviews` cubre la reseña puntual, no la campaña de medición |
| 5 | **Fichas personalizables + ficha clínica + consentimiento informado** | Premium | Hoy solo `clients.notes`, texto libre. **Aquí AgendaPro es débil** (§1.3) |
| 6 | **Giftcards** | Premium | Depende de 2 |
| 7 | **Presupuestos** | Premium | Depende de 2 |
| 8 | **Paquetes, sesiones y membresías** | transversal | «Control de sesiones y tratamientos», «pago de sesiones», membresías de gimnasio. Nada de saldo prepago |
| 9 | **Clases grupales (capacidad > 1)** | fitness | `appointments` es estrictamente 1:1. Toca la invariante de agendado |
| 10 | **Multi-sucursal con reportes consolidados** | transversal | Tenemos multi-*tenant*, no multi-*branch*. Cambio de esquema profundo |
| 11 | **Email marketing / campañas / cumpleaños** | Individual+ (con cuotas por plan) | `notifications` es transaccional, no de campañas |
| 12 | **Pagos en línea / link de pago / cobro anticipado** | transversal | Cero captura de pago. Es lo que bloquea §2.2 |
| 13 | **Facturación electrónica / CFDI** | add-on | En AgendaPro está «próximamente» |
| 14 | **Google Reserve / Google Calendar** | Individual+ | Integración externa |
| 15 | **Control de ocupación (% de agenda ocupada)** | Individual | [metrics.ts](server/lib/metrics.ts) solo tiene `cancelRate`, `noShowRate`, `completionRate`. **Barato y visible** |
| 16 | **Gating por plan** | — | `businesses.plan` existe pero **no restringe nada**. Sin esto, la escalera de planes —que es el producto de AgendaPro— no se está emulando |
| 17 | **Videoconferencia** | add-on | |
| 18 | **API pública documentada** | Pro | |
| 19 | **Marketplace** | Individual+ | **El foso real (§1.4).** No es un módulo, es un canal de distribución |
| 20 | **Reportes con IA** | add-on (Charly) | |
---
## 3. Lectura de la auditoría
Tres conclusiones que condicionan cualquier plan:
1. **La brecha no es de agenda, es de dinero.** Todo lo que falta cuelga de dos cosas que no existen:
la **venta multi-ítem** (brecha 2) y la **captura de pago** (brecha 12). Inventario, giftcards,
presupuestos, paquetes, comisión de producto y el cierre de la política de cancelación dependen de
una o de las dos. Atacar módulos sueltos antes de eso produce features que no se pueden conectar.
2. **Ya somos mejores en el núcleo de agendado.** Auto-asignación por especialidad y eficiencia, guard
transaccional anti doble-reserva y consola de plataforma multi-tenant no están en la oferta de
AgendaPro. La equivalencia que falta es comercial y administrativa, no de calendario.
3. **La escalera de planes no se está emulando.** Es lo más desalineado del proyecto respecto al
producto real: `businesses.plan` es decorativo. Es además de las cosas más baratas de arreglar y la
que más hace que el demo se lea como un SaaS y no como una app.
El backlog priorizado y la descomposición en sub-proyectos viven en el spec de diseño de esta tanda,
no en este archivo. Este documento es la línea base de hechos.