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:
67
api-gateway/src/controllers/wacrm.controller.ts
Normal file
67
api-gateway/src/controllers/wacrm.controller.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Endpoints del módulo WhatsApp (WA CRM): bandeja del vendedor, estado del
|
||||
* round-robin para administración, configuración y reasignación manual.
|
||||
*/
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
import { z } from 'zod'
|
||||
import { AppError } from '../utils/errors.js'
|
||||
import {
|
||||
configurarWacrm,
|
||||
estadoWacrm,
|
||||
ejecutarRoundRobinWacrm,
|
||||
miBandeja,
|
||||
reasignarConversacion,
|
||||
} from '../services/wacrm.service.js'
|
||||
import * as auditoria from '../services/auditoria.service.js'
|
||||
|
||||
/** Admin/manager: estado, configuración y reasignación. */
|
||||
export function requireRolWacrmAdmin(req: Request, _res: Response, next: NextFunction): void {
|
||||
const rol = req.usuarioToken?.rol
|
||||
if (rol !== 'admin' && rol !== 'manager') {
|
||||
throw new AppError(403, 'Requiere rol de administrador')
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
/** Bandeja de WhatsApp del vendedor logueado (vínculo vía wacrmUserId). */
|
||||
export async function miBandejaEndpoint(req: Request, res: Response): Promise<void> {
|
||||
const usuarioId = req.usuarioToken?.sub
|
||||
if (!usuarioId) throw new AppError(401, 'Sesión requerida')
|
||||
const resultado = await miBandeja(usuarioId)
|
||||
res.json(resultado)
|
||||
}
|
||||
|
||||
export async function estadoWacrmEndpoint(_req: Request, res: Response): Promise<void> {
|
||||
res.json(await estadoWacrm())
|
||||
}
|
||||
|
||||
const configSchema = z
|
||||
.object({
|
||||
cuentaId: z.string().max(64).optional(),
|
||||
activo: z.boolean().optional(),
|
||||
})
|
||||
.refine((o) => Object.keys(o).length > 0, { message: 'Nada que actualizar' })
|
||||
|
||||
export async function configurarWacrmEndpoint(req: Request, res: Response): Promise<void> {
|
||||
const body = configSchema.parse(req.body)
|
||||
const resultado = await configurarWacrm(body)
|
||||
auditoria.registrar(req.usuarioToken?.sub ?? 'sistema', 'CONFIG_WACRM', 'configuracion', 'wacrm', JSON.stringify(body))
|
||||
res.json(resultado)
|
||||
}
|
||||
|
||||
const reasignarSchema = z.object({
|
||||
conversacionId: z.string().min(10),
|
||||
vendedorId: z.string().min(10),
|
||||
})
|
||||
|
||||
export async function reasignarWacrmEndpoint(req: Request, res: Response): Promise<void> {
|
||||
const body = reasignarSchema.parse(req.body)
|
||||
await reasignarConversacion(body)
|
||||
auditoria.registrar(req.usuarioToken?.sub ?? 'sistema', 'REASIGNAR_WACRM', 'conversacion', body.conversacionId, body.vendedorId)
|
||||
res.json({ ok: true })
|
||||
}
|
||||
|
||||
/** Dispara el round-robin a demanda (admin) — útil para pruebas. */
|
||||
export async function roundRobinWacrmEndpoint(_req: Request, res: Response): Promise<void> {
|
||||
res.json(await ejecutarRoundRobinWacrm())
|
||||
}
|
||||
Reference in New Issue
Block a user