feat: roles con modulos por rol + mejoras Mi dia y descarte
- Matriz de roles (vendedor, administracion_venta, director_ventas, escrituracion, direccion_general, admin) con modulos visibles por rol; auth/me y login incluyen 'modulos'; PATCH /usuarios/:id/rol (admin). - Landing: Cobranza -> Clientes, Chat IA -> Chat Hermes, nuevo modulo Contactos (vista interna leyendo el tablero Contactos), grid filtrado por rol, rol visible en Configuracion de cuenta. - Dashboards: tab 'Tablero comercial' y tabs filtrados por rol. - Boton '<- Menu' en el header de todas las apps. - Mi dia: cada lead muestra el ultimo cambio (que, quien, cuando) desde el activity log de Monday (normalizacion de created_at 100ns/us/ms). - Descarte: el motivo se crea en el grupo de su categoria en el tablero Motivos No Compra.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { MondayService } from './monday.service.js'
|
||||
import { config } from '../config/index.js'
|
||||
|
||||
/**
|
||||
* Agregador "Mi día": lo que un vendedor necesita al abrir la app /hoy.
|
||||
@@ -39,6 +40,8 @@ export interface LeadMiDia {
|
||||
updatedAt: string
|
||||
/** Tablero del item: 'lead' (tablero Leads) u 'oportunidad' (Oportunidades) */
|
||||
fuente: 'lead' | 'oportunidad'
|
||||
/** Último evento del activity log del board; null si el item no tiene eventos */
|
||||
ultimoCambio: { fechaISO: string; usuario: string | null; detalle: string } | null
|
||||
}
|
||||
|
||||
export interface ActividadMiDia {
|
||||
@@ -105,10 +108,30 @@ export interface ResumenMiDia {
|
||||
}
|
||||
}
|
||||
|
||||
type MondayMiDia = Pick<MondayService, 'getLeads' | 'getOportunidades' | 'getActividades' | 'getApartados'>
|
||||
type MondayMiDia = Pick<
|
||||
MondayService,
|
||||
'getLeads' | 'getOportunidades' | 'getActividades' | 'getApartados' | 'getUltimosCambios'
|
||||
>
|
||||
|
||||
const MS_DIA = 24 * 60 * 60 * 1000
|
||||
|
||||
/** Texto legible del evento del activity log (los nombres de columna no vienen legibles en el payload) */
|
||||
export function detalleEventoUltimoCambio(evento: string): string {
|
||||
switch (evento) {
|
||||
case 'create_pulse':
|
||||
return 'Alta'
|
||||
case 'create_update':
|
||||
return 'Nota agregada'
|
||||
case 'update_column_value':
|
||||
case 'update_status':
|
||||
return 'Actualización'
|
||||
case 'move_to_group':
|
||||
return 'Cambio de grupo'
|
||||
default:
|
||||
return evento
|
||||
}
|
||||
}
|
||||
|
||||
/** Fecha local 'YYYY-MM-DD' desplazada n días desde `base` */
|
||||
function fechaLocal(base: Date, dias: number): string {
|
||||
const d = new Date(base.getTime() + dias * MS_DIA)
|
||||
@@ -180,6 +203,7 @@ export async function resumenMiDia(
|
||||
origen: l.origen,
|
||||
updatedAt: l.updatedAt,
|
||||
fuente: 'lead',
|
||||
ultimoCambio: null,
|
||||
}))
|
||||
const mios = sinMapeo ? [] : oportunidades.filter((o) => o.asesorIds.includes(miId))
|
||||
const misLeadsOportunidades: LeadMiDia[] = mios
|
||||
@@ -193,11 +217,37 @@ export async function resumenMiDia(
|
||||
origen: o.origen,
|
||||
updatedAt: o.updatedAt,
|
||||
fuente: 'oportunidad',
|
||||
ultimoCambio: null,
|
||||
}))
|
||||
const misLeads: LeadMiDia[] = [...misLeadsLeads, ...misLeadsOportunidades].sort((a, b) =>
|
||||
b.updatedAt.localeCompare(a.updatedAt),
|
||||
)
|
||||
|
||||
// "Último cambio" por item: UNA llamada al activity log por tablero (máx 2),
|
||||
// agrupando los ids por fuente. El usuario se resuelve con el mapa
|
||||
// mondayUserId → nombre que pasa el controller (ecosistema + workspace).
|
||||
const [cambiosLeads, cambiosOportunidades] = await Promise.all([
|
||||
monday.getUltimosCambios(
|
||||
config.monday.boards.leads,
|
||||
misLeads.filter((l) => l.fuente === 'lead').map((l) => l.id),
|
||||
),
|
||||
monday.getUltimosCambios(
|
||||
config.monday.boards.oportunidades,
|
||||
misLeads.filter((l) => l.fuente === 'oportunidad').map((l) => l.id),
|
||||
),
|
||||
])
|
||||
const cambiosPorFuente = { lead: cambiosLeads, oportunidad: cambiosOportunidades }
|
||||
for (const lead of misLeads) {
|
||||
const cambio = cambiosPorFuente[lead.fuente].get(lead.id)
|
||||
lead.ultimoCambio = cambio
|
||||
? {
|
||||
fechaISO: cambio.fechaISO,
|
||||
usuario: cambio.usuarioId !== null ? (nombresPorMondayId.get(cambio.usuarioId) ?? null) : null,
|
||||
detalle: detalleEventoUltimoCambio(cambio.evento),
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
// Conteo de MIS oportunidades por etapa (incluye terminales: es el resumen
|
||||
// del pipeline personal, no la lista de trabajo activo)
|
||||
const porEtapa: Record<string, number> = {}
|
||||
|
||||
Reference in New Issue
Block a user