feat: modulo WhatsApp (WA CRM) con round-robin de conversaciones
- Servicio wacrm en api-gateway (pg pool lazy): round-robin de conversaciones sin agente entre vendedores vinculados y miembro-agentes de la cuenta wacrm, cron cada 30 s; bandeja del vendedor; estado/config/reasignar para admin. - Endpoints /api/wacrm/* con guarda de rol admin/manager. - Usuario.wacrmUserId en schema Prisma (vinculo CRM<->wacrm). - App apps/wa-crm (/wa-crm/): bandeja + administracion del round-robin. - Landing: tarjeta WhatsApp; clave 'whatsapp' en MATRIZ de roles. - Plan de nuevas reglas de negocio del cotizador (docs/PLAN-REGLAS-COTIZADOR.md). - WACRM_PG_URL apunta a Postgres de Supabase por 127.0.0.1:5433. - Tests de integracion wacrm (3) y suite completa 764/764 en verde.
This commit is contained in:
54
docs/MODULO-WHATSAPP.md
Normal file
54
docs/MODULO-WHATSAPP.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Módulo WhatsApp (WA CRM) — CRM360 FRASA
|
||||
|
||||
Integración entre el CRM y [wacrm](https://wacrm.tech) (WhatsApp CRM
|
||||
self-hosted en `/root/wacrm`, datos en Supabase self-hosted `/root/supabase`).
|
||||
|
||||
## Cómo funciona
|
||||
|
||||
- **Vínculo de usuarios**: cada `Usuario` del CRM tiene `wacrmUserId` (id de
|
||||
`auth.users` en Supabase). Los 30 usuarios activos del CRM están dados de
|
||||
alta en wacrm y su perfil pertenece a la **cuenta compartida**
|
||||
(`7b17a9ab-6723-43aa-a9da-28e97f0071ae`, del usuario administrador).
|
||||
Vendedores = rol `agent`, managers/admins = rol `admin`.
|
||||
- **Round-robin**: el API Gateway (`frasa-api`) revisa cada 30 s las
|
||||
conversaciones de la cuenta compartida que llegan sin asignar
|
||||
(`assigned_agent_id IS NULL`, estatus `open`) y las turna entre los
|
||||
vendedores del pool (rol `vendedor`, activos, vinculados y miembros agente
|
||||
de la cuenta). El índice del turno vive en `ConfiguracionSistema`
|
||||
(`wacrm_rr_idx`), así que sobrevive reinicios.
|
||||
- **Bandeja del vendedor**: `GET /api/wacrm/mi-bandeja` devuelve las
|
||||
conversaciones asignadas al usuario logueado (contacto, último mensaje,
|
||||
no leídos). La app `apps/wa-crm` (`/wa-crm/`) las muestra con liga a
|
||||
`/wa/inbox` para responder desde wacrm.
|
||||
- **Administración** (rol admin/manager, pestaña en `/wa-crm/`):
|
||||
- Estado: sin asignar, total, pool con "es miembro" y cuántas tiene cada uno.
|
||||
- Activar/desactivar round-robin y cambiar la cuenta (`POST /api/wacrm/config`).
|
||||
- "Asignar ahora" dispara el reparto a demanda (`POST /api/wacrm/round-robin`).
|
||||
- Reasignar una conversación a cualquier vendedor (`POST /api/wacrm/reasignar`).
|
||||
|
||||
## Conexión técnica
|
||||
|
||||
- `WACRM_PG_URL` en `/root/frasa-ecosystem/.env` apunta al Postgres de
|
||||
Supabase por `127.0.0.1:5433` (puerto publicado solo en localhost del
|
||||
contenedor `supabase-db`; el 5432 es Supavisor y exige tenant).
|
||||
- El servicio `api-gateway/src/services/wacrm.service.ts` usa `pg` (Pool lazy;
|
||||
sin URL lanza 503 y el resto del CRM sigue igual).
|
||||
- Al escribir (`UPDATE conversations`) se hace como superusuario postgres,
|
||||
bypass RLS — solo el API lo hace, nunca el cliente.
|
||||
|
||||
## Puesta en marcha del número
|
||||
|
||||
El round-robin está activo sobre la cuenta compartida. Falta conectar el
|
||||
número de WhatsApp (API oficial de Meta) en wacrm: credenciales y pasos en
|
||||
`/root/wacrm/ENV-PENDIENTES.md`. Cuando Meta entregue el número, conectarlo
|
||||
desde **wacrm → Settings → WhatsApp** con la cuenta del administrador.
|
||||
|
||||
## Tests
|
||||
|
||||
`api-gateway/src/services/wacrm.service.test.ts` — integración real contra
|
||||
Supabase (se auto-salta si no hay `WACRM_PG_URL`); crea usuarios/cuenta/
|
||||
conversaciones de prueba y los limpia. Correr:
|
||||
|
||||
```bash
|
||||
cd api-gateway && npx vitest run src/services/wacrm.service.test.ts
|
||||
```
|
||||
125
docs/PLAN-REGLAS-COTIZADOR.md
Normal file
125
docs/PLAN-REGLAS-COTIZADOR.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Plan — Nuevas reglas de negocio del Cotizador FRASA
|
||||
|
||||
Fuente: `Reglas de negocio Cotizador FRASA .xlsx` (entregado por el cliente).
|
||||
Alcance: aplicar estas reglas al cotizador web (`apps/cotizador`) y a su espejo
|
||||
del lado del API (`api-gateway/src/services/cotizacion-esquema.service.ts`).
|
||||
|
||||
## 1. Lectura de las reglas (interpretación)
|
||||
|
||||
### Globales (todos los tipos)
|
||||
|
||||
| Concepto | Regla |
|
||||
|---|---|
|
||||
| Apartado | $15,000 (cantidad fija, ya confirmada por el cliente) |
|
||||
| Gastos de escrituración | 5.5 % del precio de venta |
|
||||
| Gastos — opción del cliente | Puede **incluirlos** en el pago de firma (toggle). Si los incluye, se liquidan junto con el enganche para firma de contrato |
|
||||
| Mensualidades a diferir | Número de **meses calendario completos** entre la fecha de cotización y la fecha de entrega de la vivienda (ya implementado vía captura) |
|
||||
| Mensualidad a pagar | Enganche diferido ÷ mensualidades |
|
||||
| Enganche total | El **% total que el cliente elige pagar** como enganche (incluye el apartado). Tope por tipo de crédito |
|
||||
|
||||
### Tipos de crédito
|
||||
|
||||
| Tipo | Rango enganche **para firma** | Tope enganche **total** | Aforo | Notaría |
|
||||
|---|---|---|---|---|
|
||||
| Bancario | 2.5 % – 15 % | 15 % | 84.5 % | No aplica |
|
||||
| Infonavit-Cofinavit | 2.5 % – 15 % | 15 % | 89.5 % | No aplica |
|
||||
| Semi Contado | 50 % – 85 % | 85 % | No aplica | Debe quedar **15 %** para notaría |
|
||||
| Contado | 86 % – 95 % | 95 % | No aplica | Debe quedar **mínimo 5 %** para notaría |
|
||||
|
||||
### Fórmulas
|
||||
|
||||
```
|
||||
gastos = 0.055 × precio
|
||||
firma = % enganche firma elegido (dentro del rango del tipo)
|
||||
totalEng = % enganche total elegido (≤ tope del tipo; firma ≤ totalEng)
|
||||
diferido = totalEng − firma (lo que se difiere en mensualidades)
|
||||
mensualidad = diferido × precio ÷ N_meses
|
||||
saldoPorPagar = precio + gastos − (firma% + diferido%) × precio
|
||||
(= precio + gastos − totalEng% × precio)
|
||||
```
|
||||
|
||||
- **Bancario / Infonavit-Cofinavit**: `monto a financiar = saldoPorPagar`.
|
||||
Validación de **aforo**: `saldoPorPagar ≤ aforo × precio`; si se rebasa, la
|
||||
combinación elegida no es viable y hay que pedir más enganche.
|
||||
- **Semi Contado**: no hay financiamiento. Restricción: el saldo por pagar debe
|
||||
dejar **≥ 15 %** para el pago en notaría (`saldoPorPagar ≥ 0.15 × precio`,
|
||||
equivalente a `totalEng ≤ 0.85 + gastos%`). El "monto pago en notaría" = 15 %.
|
||||
- **Contado**: sin mensualidades (diferido = 0; todo el enganche se paga a la
|
||||
firma). Restricción: saldo ≥ 5 % para notaría (`totalEng ≤ 0.95 + gastos%`).
|
||||
|
||||
### Diferencias contra el esquema actual
|
||||
|
||||
| Aspecto | Esquema actual (FRASA) | Nuevo |
|
||||
|---|---|---|
|
||||
| Enganche firma | fijo 2.5 % | rango por tipo (2.5–15 / 50–85 / 86–95 %) |
|
||||
| Enganche diferible | fijo 7.5 % editable | **total elegido − firma** |
|
||||
| Tope enganche | 90 % global | por tipo: 15 / 15 / 85 / 95 % |
|
||||
| Aforo | no se valida | validación 84.5 / 89.5 % (bancario/infonavit) |
|
||||
| Gastos 5.5 % | siempre aparte | **toggle**: incluidos en firma u aparte |
|
||||
| Notaría | no existe | piso 15 % (semi) / 5 % (contado) |
|
||||
| Anticipo adicional | campo extra (decisión previa: sin espacio separado) | se absorbe en "enganche total" |
|
||||
|
||||
## 2. Puntos a confirmar con el cliente (antes o durante la implementación)
|
||||
|
||||
1. **Apartado dentro del %**: se interpreta que el % de enganche total es
|
||||
dinero total (el apartado de $15,000 forma parte de él), según la columna
|
||||
K ("% de enganche elegido incluido el apartado"). Confirmar.
|
||||
2. **Semi Contado / Contado**: ¿el "pago en notaría" (15 % / 5 %) es parte del
|
||||
saldo por pagar que el cliente liquida al final? Se asume que sí.
|
||||
3. **Aforo**: si el financiamiento calculado supera el aforo, ¿bloquear la
|
||||
opción o mostrarla en rojo como "no viable"? Se propone mostrar no viable.
|
||||
4. **Rango de enganche para firma en créditos**: ¿el 2.5 % mínimo sigue
|
||||
incluyendo el apartado como parte del pago a firma (como hoy)? Se propone
|
||||
que sí.
|
||||
|
||||
## 3. Plan de implementación
|
||||
|
||||
### 3.1 Motor de cálculo — `esquema-frasa.ts` (app) y `cotizacion-esquema.service.ts` (API, espejo)
|
||||
|
||||
1. Nuevo tipo `TipoCredito = 'bancario' | 'infonavit' | 'contado' | 'semicontado'`
|
||||
(reemplaza/usa el catálogo actual de opciones de crédito).
|
||||
2. Tabla de reglas por tipo (única fuente de verdad en ambos lados, misma
|
||||
constante): `REGLAS_TIPO` con `{rangoFirmaMin, rangoFirmaMax, topeTotal,
|
||||
aforo, pisoNotaria, permiteDiferir}`.
|
||||
3. Entrada del motor: `{precio, apartado, tipo, pctFirma, pctTotal, incluirGastosEnFirma, meses}`.
|
||||
4. Salida: `{gastos, montoFirma, montoDiferido, mensualidad, saldoPorPagar,
|
||||
montoFinanciar, pagoNotaria, viable, motivos[]}` con validaciones:
|
||||
- firma dentro de rango; total ≤ tope; total ≥ firma;
|
||||
- si diferido > 0 → meses ≥ 1 (tope por fecha de entrega, ya existente);
|
||||
- aforo (bancario/infonavit): `saldo ≤ aforo × precio`;
|
||||
- piso notaría (semi/contado): `saldo ≥ piso × precio`.
|
||||
5. Quitar del esquema actual lo que queda reemplazado: enganche fijo 2.5/7.5,
|
||||
tope global 90 %, `engancheAdicional` (el "total elegido" lo absorbe).
|
||||
|
||||
### 3.2 UI del cotizador (`apps/cotizador`)
|
||||
|
||||
1. Selector de **tipo de crédito** con las 4 opciones y sus topes visibles.
|
||||
2. **Slider/input de enganche total** (0 → tope del tipo) y **slider/input de
|
||||
enganche a firma** (rango del tipo, ≤ total).
|
||||
3. **Toggle "Incluir gastos de escrituración en el pago de firma"**.
|
||||
4. Tabla de resultados: apartado, firma, diferido, mensualidad (×N meses),
|
||||
gastos, saldo, monto a financiar (o pago en notaría), y badge **viable /
|
||||
no viable** con motivo (aforo excedido, piso notaría, etc.).
|
||||
5. Las 4 opciones de crédito del guardado existente pasan a ser las 4
|
||||
combinaciones tipo+porcentajes que el vendedor guarde, no 4 presets fijos.
|
||||
|
||||
### 3.3 Persistencia y reportes
|
||||
|
||||
1. `Cotizacion`: columnas nuevas `tipoCredito`, `pctFirma`, `pctTotal`,
|
||||
`incluirGastosFirma` (migración SQLite manual, estilo existente). Mantener
|
||||
`opcionesCredito` JSON para compatibilidad de lectura histórica.
|
||||
2. PDF: sección de desglose según nuevo esquema (firma / diferido / notaría /
|
||||
financiamiento, viable o no).
|
||||
|
||||
### 3.4 Pruebas
|
||||
|
||||
- Unitarias del motor: casos dorados por tipo (límites de rango, tope, aforo
|
||||
justo arriba/abajo, pisos de notaría, toggle de gastos).
|
||||
- Smoke API: `POST` cotización con cada tipo → verificar montos y `viable`.
|
||||
- Regresión: cotizaciones históricas existentes siguen leyéndose.
|
||||
|
||||
### 3.5 Entrega
|
||||
|
||||
- Migración + build de `api-gateway` y `apps/cotizador`.
|
||||
- Actualización de guía de usuario del cotizador (sección reglas).
|
||||
- Resumen de cambios para el cliente con los puntos 2 resueltos.
|
||||
Reference in New Issue
Block a user