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:
co-authored by
Claude Opus 5
parent
dcbf750c09
commit
6d67b23e55
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,384 @@
|
||||
# Propuesta técnica breve: plataforma de gestión para Yola Franco Spa
|
||||
|
||||
**Versión:** 0.1 — propuesta conceptual
|
||||
**Objetivo:** construir una web app de operación diaria para el spa, con una experiencia extremadamente rápida para empleadas y un dashboard de control para la dueña, conectada con GoHighLevel (GHL) mediante API y webhooks.
|
||||
|
||||
---
|
||||
|
||||
## 1. Resumen de la solución
|
||||
|
||||
La plataforma funcionará como un sistema operativo interno del spa:
|
||||
|
||||
- **Dueña/administradora:** visualiza ventas, citas, rendimiento, clientes, servicios, campañas y operación.
|
||||
- **Empleada:** gestiona su agenda, crea y modifica citas, consulta clientes y registra la atención con el mínimo número de clics.
|
||||
- **GoHighLevel:** conserva la relación omnicanal y automatizaciones de marketing. La app sincroniza contactos, conversaciones y eventos de cita mediante API/webhooks.
|
||||
- **PostgreSQL:** fuente de datos operativos de la plataforma: citas, clientes, servicios, empleadas, pagos, comisiones, auditoría y sincronizaciones.
|
||||
|
||||
La recomendación es no intentar clonar toda la superficie de AgendaPro en la primera versión. El MVP debe resolver primero la operación diaria, la visibilidad del negocio y la integración confiable con GHL.
|
||||
|
||||
## 2. Alcance funcional
|
||||
|
||||
### 2.1 Panel de la administradora
|
||||
|
||||
1. **Dashboard ejecutivo**
|
||||
- Ventas del día, semana y mes.
|
||||
- Citas agendadas, atendidas, canceladas y no-show.
|
||||
- Ingresos por servicio, empleada y canal.
|
||||
- Ticket promedio.
|
||||
- Tasa de recompra y clientes nuevos.
|
||||
- Ocupación de agenda por empleada.
|
||||
- Top clientes por frecuencia, gasto y última visita.
|
||||
- Top servicios y servicios con baja demanda.
|
||||
- Fuente de adquisición: orgánico, Instagram, Facebook, WhatsApp, campañas GHL, referido u otro.
|
||||
|
||||
2. **Calendario global**
|
||||
- Vista diaria, semanal y mensual.
|
||||
- Filtros por empleada, servicio, estado y ubicación.
|
||||
- Crear, mover, confirmar, reprogramar y cancelar citas.
|
||||
- Bloqueos de horario, descansos, vacaciones y días no laborables.
|
||||
|
||||
3. **Clientes/CRM operativo**
|
||||
- Búsqueda por nombre, teléfono, correo o identificador GHL.
|
||||
- Historial de citas, servicios, pagos, notas y conversaciones enlazadas.
|
||||
- Etiquetas: nuevo, frecuente, VIP, inactivo, campaña, referido, etc.
|
||||
- Consentimiento de comunicaciones y preferencias.
|
||||
- Próxima recomendación de servicio y fecha sugerida de regreso.
|
||||
- Detección de posibles duplicados antes de crear un cliente.
|
||||
|
||||
4. **Catálogo y configuración**
|
||||
- Servicios, categorías, duración, precio, buffer y empleadas habilitadas.
|
||||
- Horarios de atención y reglas de disponibilidad.
|
||||
- Comisiones por servicio o por empleada.
|
||||
- Paquetes, promociones y tarjetas/membresías en una fase posterior.
|
||||
|
||||
5. **Reportes**
|
||||
- Exportación CSV/XLSX de clientes, citas y ventas.
|
||||
- Reporte por periodo, empleada, servicio y canal.
|
||||
- Registro de cambios y actividad administrativa.
|
||||
|
||||
### 2.2 Panel de la empleada
|
||||
|
||||
Diseñado primero para móvil y tablet, con navegación reducida:
|
||||
|
||||
- **Hoy:** próximas citas, hora, cliente, servicio y estado.
|
||||
- **Mi agenda:** día/semana con bloques visuales.
|
||||
- **Nueva cita rápida:** seleccionar fecha/hora, servicio, cliente y confirmar.
|
||||
- **Búsqueda de cliente:** resultados mientras se escribe; evitar duplicados.
|
||||
- **Alta rápida:** nombre y teléfono obligatorios; correo y notas opcionales.
|
||||
- **Acciones de una cita:** confirmar, iniciar, completar, reprogramar, cancelar y marcar no-show.
|
||||
- **Ficha resumida:** historial reciente, notas relevantes, preferencias y próxima visita.
|
||||
- **Rendimiento personal:** citas atendidas, ventas generadas, ticket promedio, cancelaciones y comisión estimada.
|
||||
|
||||
La empleada no debe ver información financiera global ni clientes ajenos a sus permisos, salvo que la administradora lo configure.
|
||||
|
||||
## 3. Roles y permisos
|
||||
|
||||
Usar autorización basada en roles (RBAC), no solamente ocultamiento visual:
|
||||
|
||||
| Recurso | Administradora | Empleada |
|
||||
|---|---:|---:|
|
||||
| Dashboard global | Sí | No |
|
||||
| Dashboard personal | Sí | Sí |
|
||||
| Calendario global | Sí | Según permiso |
|
||||
| Mi agenda | Sí | Sí |
|
||||
| Crear cita | Sí | Sí |
|
||||
| Editar/cancelar cualquier cita | Sí | Solo propias, según regla |
|
||||
| Ver clientes | Todos | Necesarios para operar |
|
||||
| Exportar clientes/ventas | Sí | No |
|
||||
| Editar precios/comisiones | Sí | No |
|
||||
| Ver ventas globales | Sí | No |
|
||||
| Configurar GHL | Sí | No |
|
||||
| Auditoría | Sí | No |
|
||||
|
||||
La aplicación debe estar preparada para `tenant_id`, aunque inicialmente exista un solo spa. Esto evita rediseñar la base si después se ofrecen cuentas a otros negocios.
|
||||
|
||||
## 4. Arquitectura propuesta
|
||||
|
||||
```text
|
||||
[Web app responsive]
|
||||
|
|
||||
v
|
||||
[API Python: FastAPI]
|
||||
| | |
|
||||
| | +--> [Worker: Celery/RQ + Redis]
|
||||
| +-----------> [GoHighLevel API]
|
||||
+-------------------> [PostgreSQL]
|
||||
|
|
||||
+--> métricas/reportes
|
||||
|
||||
[GHL Webhooks] ---> [Endpoint seguro] ---> [Event inbox] ---> [Worker]
|
||||
```
|
||||
|
||||
### Componentes
|
||||
|
||||
- **Frontend:** Next.js/React + TypeScript, PWA instalable, Tailwind CSS o sistema de componentes equivalente.
|
||||
- **Backend:** Python 3.12+ con FastAPI, Pydantic y SQLAlchemy 2.x/SQLModel.
|
||||
- **Base de datos:** PostgreSQL 16+, migraciones con Alembic.
|
||||
- **Cola y caché:** Redis; workers para webhooks, sincronizaciones y mensajes sin bloquear la interfaz.
|
||||
- **Autenticación:** sesiones seguras con cookies HttpOnly o JWT de corta duración con refresh rotativo. MFA para la administradora en una fase posterior.
|
||||
- **Despliegue inicial:** Docker Compose en staging; producción con PostgreSQL administrado, Redis administrado y servicio web/worker separado.
|
||||
- **Observabilidad:** logs estructurados, Sentry/OpenTelemetry opcional, métricas de errores de integración y tiempos de respuesta.
|
||||
|
||||
## 5. Modelo de datos inicial
|
||||
|
||||
Tablas principales:
|
||||
|
||||
- `tenants`: spa/cuenta, zona horaria, configuración y estado.
|
||||
- `users`: usuarios internos, correo, estado y último acceso.
|
||||
- `roles`, `user_roles`: administradora y empleada.
|
||||
- `employees`: perfil operativo, especialidades, horarios y comisión.
|
||||
- `services`: nombre, categoría, duración, precio, buffer, activo.
|
||||
- `employee_services`: servicios que puede realizar cada empleada.
|
||||
- `customers`: nombre, teléfono normalizado, correo, consentimiento, `ghl_contact_id`.
|
||||
- `customer_tags`: etiquetas operativas y de adquisición.
|
||||
- `appointments`: cliente, empleada, servicio, inicio, fin, estado, origen, notas y `ghl_appointment_id`.
|
||||
- `appointment_events`: historial de cambios de una cita.
|
||||
- `payments`: monto, método, estado, referencia y fecha.
|
||||
- `campaign_attributions`: UTM, campaña, fuente, medio y primer/último contacto.
|
||||
- `conversations`: referencia a conversación/canal en GHL; no necesariamente almacenar todo el contenido si GHL es la fuente principal.
|
||||
- `integration_connections`: ubicación GHL, tokens cifrados, scopes y estado.
|
||||
- `integration_events`: webhook/evento recibido, payload hash, estado, reintentos e idempotency key.
|
||||
- `outbox_events`: eventos internos pendientes de enviar a GHL.
|
||||
- `audit_logs`: quién cambió qué, cuándo y desde dónde.
|
||||
|
||||
### Reglas importantes
|
||||
|
||||
- Normalizar teléfonos a formato E.164 (`+52...`) antes de buscar o crear contactos.
|
||||
- `UNIQUE (tenant_id, normalized_phone)` para evitar duplicados básicos.
|
||||
- Guardar fechas en UTC y mostrar en `America/Mexico_City`.
|
||||
- Usar `timestamptz` y rangos para impedir doble reserva.
|
||||
- Crear una restricción de exclusión PostgreSQL por empleada para evitar solapamientos de citas confirmadas.
|
||||
- No borrar clientes físicamente; usar estado, anonimización y política de retención.
|
||||
|
||||
## 6. Integración con GoHighLevel
|
||||
|
||||
### 6.1 Autenticación y configuración
|
||||
|
||||
Preferir OAuth 2.0 para una integración comercial reutilizable. Para una sola subcuenta controlada por el equipo, puede iniciarse con credenciales de ubicación/API adecuadas, almacenadas cifradas en el servidor.
|
||||
|
||||
No guardar tokens en frontend ni en PostgreSQL en texto plano. Usar un gestor de secretos o variables de entorno del servidor; las variables deben contener secretos, no configuración funcional.
|
||||
|
||||
Configurar en GHL:
|
||||
|
||||
- Location/Sub-account ID del spa.
|
||||
- Client ID y Client Secret de la aplicación, si se usa OAuth.
|
||||
- Scopes mínimos necesarios.
|
||||
- URLs de redirección OAuth.
|
||||
- URLs de webhooks.
|
||||
- Firma/secreto de validación de webhooks, si está disponible en el evento utilizado.
|
||||
|
||||
### 6.2 Sincronización de contactos
|
||||
|
||||
**Crear desde la app:**
|
||||
|
||||
1. La empleada captura teléfono y nombre.
|
||||
2. La API busca primero en PostgreSQL por teléfono normalizado.
|
||||
3. Si existe `ghl_contact_id`, actualiza o reutiliza el contacto.
|
||||
4. Si no existe, consulta GHL por teléfono/correo.
|
||||
5. Si tampoco existe, crea el contacto mediante API.
|
||||
6. Guarda `ghl_contact_id`, respuesta resumida y evento de sincronización.
|
||||
7. La cita se crea solamente después de resolver el cliente local.
|
||||
|
||||
**Actualizar desde la app:** usar una cola `outbox_events`, con reintentos y clave de idempotencia. La pantalla no debe quedar bloqueada si GHL está temporalmente fuera de servicio; debe mostrar “guardado local / sincronización pendiente”.
|
||||
|
||||
**Recibir desde GHL:** registrar webhooks de creación/actualización de contacto, cambios relevantes y eventos de conversación disponibles para la cuenta. El webhook debe responder rápido con HTTP 2xx y procesarse en segundo plano.
|
||||
|
||||
### 6.3 Conversaciones y mensajes
|
||||
|
||||
La app puede mostrar una vista resumida de conversaciones, pero conviene mantener GHL como sistema principal de mensajería omnicanal:
|
||||
|
||||
- GHL recibe mensajes de WhatsApp, Facebook e Instagram.
|
||||
- GHL dispara webhooks hacia la plataforma cuando exista un evento compatible.
|
||||
- La plataforma almacena metadatos y referencias, no necesariamente todo el historial.
|
||||
- Cuando la dueña o empleada envía un mensaje desde la app, el backend ejecuta una petición autenticada a la API de GHL.
|
||||
- El frontend nunca llama directamente a GHL.
|
||||
|
||||
**Flujo de envío:**
|
||||
|
||||
```text
|
||||
Frontend -> POST /api/conversations/{id}/messages
|
||||
-> valida permiso y contenido
|
||||
-> crea outbox_event
|
||||
-> worker llama API GHL
|
||||
-> guarda resultado/id externo
|
||||
-> frontend recibe estado enviado/fallido
|
||||
```
|
||||
|
||||
Debe contemplar límites de frecuencia, reintentos con backoff, mensajes duplicados, archivos multimedia y errores de permisos. Las capacidades exactas de envío deben validarse contra la versión actual de la API y los canales habilitados en la subcuenta.
|
||||
|
||||
### 6.4 Webhooks seguros e idempotentes
|
||||
|
||||
Endpoint sugerido:
|
||||
|
||||
```text
|
||||
POST /api/integrations/gohighlevel/webhooks/{tenant_id}
|
||||
```
|
||||
|
||||
Proceso:
|
||||
|
||||
1. Validar firma, secreto o mecanismo oficial disponible.
|
||||
2. Validar tamaño y estructura del payload.
|
||||
3. Calcular hash del evento y revisar `integration_events`.
|
||||
4. Si ya fue procesado, responder 200 sin duplicar efectos.
|
||||
5. Insertar el evento en la bandeja de entrada.
|
||||
6. Responder 200 rápidamente.
|
||||
7. Worker transforma el evento y actualiza contacto/conversación/cita.
|
||||
8. Registrar resultado, duración y número de reintentos.
|
||||
|
||||
## 7. API interna sugerida
|
||||
|
||||
```text
|
||||
POST /api/auth/login
|
||||
GET /api/dashboard/summary?from=&to=
|
||||
GET /api/calendar?from=&to=&employee_id=
|
||||
POST /api/appointments
|
||||
PATCH /api/appointments/{id}
|
||||
POST /api/appointments/{id}/confirm
|
||||
POST /api/appointments/{id}/complete
|
||||
POST /api/appointments/{id}/cancel
|
||||
GET /api/customers?query=
|
||||
POST /api/customers
|
||||
GET /api/customers/{id}
|
||||
PATCH /api/customers/{id}
|
||||
GET /api/services
|
||||
POST /api/services
|
||||
GET /api/employees
|
||||
GET /api/reports/sales
|
||||
GET /api/reports/performance
|
||||
GET /api/conversations
|
||||
POST /api/conversations/{id}/messages
|
||||
POST /api/integrations/gohighlevel/connect
|
||||
POST /api/integrations/gohighlevel/sync
|
||||
POST /api/integrations/gohighlevel/webhooks/{tenant_id}
|
||||
GET /api/integrations/jobs/{id}
|
||||
```
|
||||
|
||||
Todos los endpoints deben validar `tenant_id`, rol, permisos de recurso y esquema de entrada. La API debe devolver errores consistentes (`code`, `message`, `details`, `request_id`).
|
||||
|
||||
## 8. Métricas que importan al dueño
|
||||
|
||||
### Ventas
|
||||
|
||||
- Ingresos brutos/netos por periodo.
|
||||
- Ticket promedio.
|
||||
- Ventas por servicio, empleada y canal.
|
||||
- Métodos de pago.
|
||||
- Comisiones.
|
||||
|
||||
### Operación
|
||||
|
||||
- Utilización de horas disponibles.
|
||||
- Citas atendidas, canceladas, reprogramadas y no-show.
|
||||
- Tiempo promedio entre citas.
|
||||
- Huecos disponibles próximos 7/14 días.
|
||||
|
||||
### Clientes
|
||||
|
||||
- Clientes nuevos vs recurrentes.
|
||||
- Recompra a 30/60/90 días.
|
||||
- Frecuencia y valor acumulado.
|
||||
- Clientes inactivos.
|
||||
- Fuente de adquisición y campaña.
|
||||
|
||||
### Marketing/GHL
|
||||
|
||||
- Contactos creados.
|
||||
- Conversaciones iniciadas.
|
||||
- Leads que terminaron en cita.
|
||||
- Citas por campaña/UTM.
|
||||
- Tiempo de respuesta, si GHL expone el dato necesario.
|
||||
|
||||
Los dashboards deben mostrar periodo, filtros, definición de cada métrica y fuente del dato. No presentar “ROI” si no se cuenta con costo de campaña confiable.
|
||||
|
||||
## 9. Experiencia de usuario y responsive
|
||||
|
||||
Prioridad de diseño: **la empleada opera con una mano y pocos segundos disponibles**.
|
||||
|
||||
- Mobile-first; soportar 360 px de ancho como mínimo.
|
||||
- Botón persistente “Nueva cita”.
|
||||
- Búsqueda global rápida de cliente.
|
||||
- Calendario con colores por estado, no solamente por empleada.
|
||||
- Formularios cortos y autoguardado de notas.
|
||||
- Confirmación clara antes de cancelar o modificar una cita.
|
||||
- Estados offline/pending para sincronizaciones.
|
||||
- Accesibilidad WCAG 2.2 AA como objetivo.
|
||||
- PWA para acceso desde la pantalla de inicio; no asumir aplicación nativa en el MVP.
|
||||
|
||||
## 10. Seguridad, privacidad y operación
|
||||
|
||||
- HTTPS obligatorio y cookies `Secure`, `HttpOnly`, `SameSite`.
|
||||
- Hash de contraseñas con Argon2id o bcrypt configurado correctamente.
|
||||
- Rate limiting en login, búsquedas y envío de mensajes.
|
||||
- Validación de permisos en backend.
|
||||
- Auditoría de cambios sensibles.
|
||||
- Cifrado de secretos y datos sensibles en reposo cuando el proveedor lo permita.
|
||||
- Backups automáticos de PostgreSQL y prueba periódica de restauración.
|
||||
- Protección contra CSRF si se usan cookies de sesión.
|
||||
- Sanitización de notas y contenido de mensajes.
|
||||
- Política de privacidad, consentimiento para marketing y procedimiento de eliminación/anonimización.
|
||||
- No guardar datos completos de tarjeta; integrar un proveedor de pagos si después se requiere cobro en línea.
|
||||
|
||||
## 11. Fases recomendadas
|
||||
|
||||
### Fase 0 — Descubrimiento técnico
|
||||
|
||||
- Confirmar documentación y scopes vigentes de GHL.
|
||||
- Confirmar canales disponibles y eventos de webhook.
|
||||
- Levantar catálogo, horarios, empleadas, reglas de reserva y métodos de pago.
|
||||
- Definir si la fuente principal de agenda será la nueva app o AgendaPro durante la transición.
|
||||
|
||||
### Fase 1 — MVP operativo
|
||||
|
||||
- Login y RBAC.
|
||||
- Clientes y búsqueda anti-duplicados.
|
||||
- Servicios y empleadas.
|
||||
- Calendario y nueva cita rápida.
|
||||
- Estados de cita.
|
||||
- Dashboard básico.
|
||||
- PostgreSQL, migraciones, backups y auditoría.
|
||||
|
||||
### Fase 2 — Integración GHL
|
||||
|
||||
- Conexión segura con subcuenta.
|
||||
- Crear/actualizar contactos.
|
||||
- Sincronización inicial controlada.
|
||||
- Webhooks idempotentes.
|
||||
- Outbox y workers.
|
||||
- Vista de conversaciones y envío de mensajes compatible con canales habilitados.
|
||||
|
||||
### Fase 3 — Analítica y crecimiento
|
||||
|
||||
- Ventas y pagos.
|
||||
- Comisiones.
|
||||
- Campañas/UTM.
|
||||
- Recompra y reactivación.
|
||||
- Reportes exportables.
|
||||
- Paquetes, promociones, recordatorios y membresías.
|
||||
|
||||
## 12. Criterios de aceptación del MVP
|
||||
|
||||
- Una empleada puede crear una cita en menos de un minuto desde móvil.
|
||||
- La búsqueda por teléfono encuentra un cliente existente sin crear duplicado.
|
||||
- Dos usuarios no pueden reservar el mismo horario para la misma empleada.
|
||||
- La administradora puede filtrar citas, ventas y rendimiento por periodo y empleada.
|
||||
- Un cliente creado localmente se sincroniza con GHL o queda claramente marcado como pendiente.
|
||||
- Un webhook repetido no duplica clientes, citas ni mensajes.
|
||||
- Una caída temporal de GHL no borra ni impide guardar la operación local.
|
||||
- Cada cambio relevante deja registro de usuario, fecha y acción.
|
||||
- Los permisos impiden a una empleada consultar reportes globales o modificar precios.
|
||||
- Los datos mostrados en dashboard incluyen su periodo y fuente.
|
||||
|
||||
## 13. Riesgos y decisiones pendientes
|
||||
|
||||
1. **API de GHL:** endpoints, scopes, límites y eventos disponibles pueden variar por versión y plan; deben validarse en un spike antes de comprometer el alcance.
|
||||
2. **Doble agenda:** operar simultáneamente AgendaPro y la nueva app puede producir conflictos. Se debe elegir una fuente de verdad o construir sincronización explícita.
|
||||
3. **Mensajería omnicanal:** Instagram, Facebook y WhatsApp pueden tener restricciones distintas; el sistema debe degradar con gracia y mostrar el estado real.
|
||||
4. **Migración de datos:** antes de importar contactos hay que normalizar teléfonos y definir política de duplicados.
|
||||
5. **Privacidad:** nombre, teléfono, historial y conversaciones requieren consentimiento, controles de acceso y política de retención.
|
||||
6. **Pagos:** si inicialmente solo se registra pago manual, etiquetarlo como registro operativo y no como conciliación bancaria.
|
||||
|
||||
## Recomendación final
|
||||
|
||||
Construir primero una **agenda operacional mobile-first con clientes y dashboard**, y después agregar la capa omnicanal de GHL mediante una arquitectura de eventos (`outbox`, `event inbox`, workers e idempotencia). PostgreSQL es una elección adecuada para escalar la operación y los mensajes referenciados, pero GHL debe permanecer como sistema de conversaciones mientras la app se consolida como sistema de agenda, clientes y rendimiento.
|
||||
|
||||
El siguiente paso técnico recomendable es un **spike de integración de 3–5 días** que pruebe: autenticación GHL, creación/búsqueda de contacto, recepción de un webhook, envío de un mensaje permitido y sincronización de una cita. El resultado debe incluir scopes reales, payloads, límites y decisiones de fuente de verdad antes de iniciar el desarrollo completo.
|
||||
Reference in New Issue
Block a user