feat(mejoras): 25 mejoras UX en todos los módulos + purga Monday
Backend: mi-dia siempre local, vocabulario canónico, AplicacionPago con usuario Mi día: modal español real, cita desde modal, score falso eliminado Cotizador: barra sticky, persistencia local con folio Alta Clientes: redirect eliminado, foto conectada, asesor desde sesión, refs dinámicas Cobranza: contrato Mora arreglado, link a Plan, abono próximo pago, WhatsApp Landing: contactos locales, ModalContacto eliminado, ModoTrabajo eliminado Dashboards: MiTablero unificado, funnel con descartados, drill-down, rango fechas Chat FRASA: corpus 5 desarrollos, 6 herramientas, historial conversaciones 69 archivos, 7 apps construidas
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
import type { Request, Response } from 'express'
|
||||
import { prisma } from '../services/prisma.js'
|
||||
import { mondayService } from '../services/monday.service.js'
|
||||
import { resumenMiDia, resumenMiDiaLocal, unirPendientesLocales, type ActividadLocalMiDia, type UsuarioLocal } from '../services/mi-dia.service.js'
|
||||
import { saludMonday } from '../services/salud-monday.service.js'
|
||||
import { esFalloMonday } from '../services/sync-monday.service.js'
|
||||
import { config } from '../config/index.js'
|
||||
import { resumenMiDiaLocal, type ActividadLocalMiDia, type UsuarioLocal } from '../services/mi-dia.service.js'
|
||||
import { AppError } from '../utils/errors.js'
|
||||
|
||||
const ROLES_CON_EQUIPO = ['coordinador', 'corporativo', 'admin']
|
||||
|
||||
/**
|
||||
* GET /api/mi-dia/resumen — agenda del vendedor logueado.
|
||||
* El "mío" se resuelve por Usuario.mondayUserId (mapeo usuario↔Monday);
|
||||
* sin mapeo, las listas personales van vacías con sinMapeoMonday: true.
|
||||
* SIEMPRE responde desde datos locales (LeadCaptura / Cliente / ActividadLocal):
|
||||
* Monday ya no participa en este endpoint. El "mío" se resuelve por el
|
||||
* usuarioId de cada captura; admin/dirección ven todas.
|
||||
*/
|
||||
export async function resumenMiDiaEndpoint(req: Request, res: Response): Promise<void> {
|
||||
// Con AUTH_ENABLED=false (arranque temprano/tests) no hay token: se usa el admin
|
||||
@@ -22,27 +19,6 @@ export async function resumenMiDiaEndpoint(req: Request, res: Response): Promise
|
||||
: await prisma.usuario.findUnique({ where: { username: 'admin' } })
|
||||
if (!usuario || !usuario.activo) throw new AppError(401, 'Credenciales inválidas')
|
||||
|
||||
// Nombres de los usuarios ya mapeados a Monday (para la sección equipo)
|
||||
const mapeados = await prisma.usuario.findMany({
|
||||
where: { mondayUserId: { not: null }, activo: true },
|
||||
select: { nombre: true, mondayUserId: true },
|
||||
})
|
||||
const nombresPorMondayId = new Map<number, string>(
|
||||
mapeados.filter((m) => m.mondayUserId !== null).map((m) => [m.mondayUserId!, m.nombre]),
|
||||
)
|
||||
|
||||
// TODOS los usuarios del workspace de Monday (el "último cambio" de un lead
|
||||
// puede ser de alguien sin cuenta en el ecosistema); si ya estaba mapeado
|
||||
// gana el nombre del ecosistema. Si Monday falla, el resumen sale igual.
|
||||
try {
|
||||
for (const u of await mondayService.getUsuariosWorkspace()) {
|
||||
if (!nombresPorMondayId.has(u.id)) nombresPorMondayId.set(u.id, u.nombre)
|
||||
}
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('No se pudieron listar los usuarios del workspace de Monday:', err)
|
||||
}
|
||||
|
||||
// Capturas de afluencia: admin ve todas, otros solo las suyas
|
||||
const esAdmin = usuario.rol === 'admin' || usuario.rol === 'direccion_general' || usuario.rol === 'director_ventas'
|
||||
const capturas = await prisma.leadCaptura.findMany({
|
||||
@@ -50,92 +26,40 @@ export async function resumenMiDiaEndpoint(req: Request, res: Response): Promise
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
// "Lo mío" incluye las oportunidades ligadas a mis capturas (mapping
|
||||
// LeadOportunidad: conversión con "Agendo" y afluencias históricas)
|
||||
const mappings = await prisma.leadOportunidad.findMany({
|
||||
where: { leadId: { in: capturas.map((c) => c.leadId) } },
|
||||
})
|
||||
const capturadosIds = new Set([
|
||||
...capturas.map((c) => c.leadId),
|
||||
...capturas.map((c) => c.oportunidadId).filter((id): id is string => id !== null),
|
||||
...mappings.map((m) => m.oportunidadId),
|
||||
])
|
||||
|
||||
// Altas de clientes: admin ve todas, otros solo las suyas
|
||||
const clientes = await prisma.cliente.findMany({
|
||||
where: esAdmin ? {} : { capturadoPorId: usuario.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
// Actividades locales del usuario
|
||||
const actividadesRaw = await prisma.actividadLocal.findMany({
|
||||
where: { usuarioId: usuario.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
const actividades: ActividadLocalMiDia[] = actividadesRaw.map((a) => ({
|
||||
id: a.id,
|
||||
titulo: a.titulo,
|
||||
descripcion: a.descripcion,
|
||||
usuarioId: a.usuarioId,
|
||||
leadCapturaId: a.leadCapturaId,
|
||||
fechaVencimiento: a.fechaVencimiento,
|
||||
fechaCierre: a.fechaCierre,
|
||||
estado: a.estado,
|
||||
tipo: a.tipo,
|
||||
}))
|
||||
|
||||
// Usuarios activos (para la sección equipo, solo coordinador/admin)
|
||||
let usuarios: UsuarioLocal[] = []
|
||||
if (ROLES_CON_EQUIPO.includes(usuario.rol)) {
|
||||
const usuariosRaw = await prisma.usuario.findMany({
|
||||
where: { activo: true },
|
||||
select: { id: true, nombre: true, rol: true, activo: true },
|
||||
})
|
||||
usuarios = usuariosRaw.map((u) => ({ id: u.id, nombre: u.nombre, rol: u.rol, activo: u.activo }))
|
||||
}
|
||||
|
||||
const usuarioMiDia = { nombre: usuario.nombre, rol: usuario.rol, mondayUserId: usuario.mondayUserId }
|
||||
|
||||
// Monday caído (circuit breaker / MODO_LOCAL_FORZADO): todo desde local
|
||||
if (!saludMonday.estaDisponible()) {
|
||||
// Actividades locales del usuario
|
||||
const actividadesRaw = await prisma.actividadLocal.findMany({
|
||||
where: { usuarioId: usuario.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
const actividades: ActividadLocalMiDia[] = actividadesRaw.map((a) => ({
|
||||
id: a.id,
|
||||
titulo: a.titulo,
|
||||
descripcion: a.descripcion,
|
||||
usuarioId: a.usuarioId,
|
||||
leadCapturaId: a.leadCapturaId,
|
||||
fechaVencimiento: a.fechaVencimiento,
|
||||
fechaCierre: a.fechaCierre,
|
||||
estado: a.estado,
|
||||
tipo: a.tipo,
|
||||
}))
|
||||
|
||||
// Usuarios activos (para la sección equipo, solo coordinador/admin)
|
||||
let usuarios: UsuarioLocal[] = []
|
||||
if (ROLES_CON_EQUIPO.includes(usuario.rol)) {
|
||||
const usuariosRaw = await prisma.usuario.findMany({
|
||||
where: { activo: true },
|
||||
select: { id: true, nombre: true, rol: true, activo: true },
|
||||
})
|
||||
usuarios = usuariosRaw.map((u) => ({ id: u.id, nombre: u.nombre, rol: u.rol, activo: u.activo }))
|
||||
}
|
||||
|
||||
res.json(resumenMiDiaLocal(usuarioMiDia, capturas, clientes, new Date(), actividades, usuarios))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const resumen = await resumenMiDia(
|
||||
mondayService,
|
||||
usuarioMiDia,
|
||||
nombresPorMondayId,
|
||||
capturas,
|
||||
clientes,
|
||||
new Date(),
|
||||
config.automatizaciones.vendedorCompartidoId > 0 ? config.automatizaciones.vendedorCompartidoId : null,
|
||||
capturadosIds,
|
||||
)
|
||||
// Unión: capturas locales aún no sincronizadas se agregan con badge ⏳
|
||||
res.json(unirPendientesLocales(resumen, capturas.filter((c) => c.pendienteSync), usuario.nombre))
|
||||
} catch (err) {
|
||||
// Monday se cayó a media lectura: mismo resumen, pero todo local
|
||||
if (!esFalloMonday(err)) throw err
|
||||
const actividadesRaw = await prisma.actividadLocal.findMany({
|
||||
where: { usuarioId: usuario.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
const actividades: ActividadLocalMiDia[] = actividadesRaw.map((a) => ({
|
||||
id: a.id, titulo: a.titulo, descripcion: a.descripcion,
|
||||
usuarioId: a.usuarioId, leadCapturaId: a.leadCapturaId,
|
||||
fechaVencimiento: a.fechaVencimiento, fechaCierre: a.fechaCierre,
|
||||
estado: a.estado, tipo: a.tipo,
|
||||
}))
|
||||
let usuarios: UsuarioLocal[] = []
|
||||
if (ROLES_CON_EQUIPO.includes(usuario.rol)) {
|
||||
const usuariosRaw = await prisma.usuario.findMany({
|
||||
where: { activo: true },
|
||||
select: { id: true, nombre: true, rol: true, activo: true },
|
||||
})
|
||||
usuarios = usuariosRaw.map((u) => ({ id: u.id, nombre: u.nombre, rol: u.rol, activo: u.activo }))
|
||||
}
|
||||
res.json(resumenMiDiaLocal(usuarioMiDia, capturas, clientes, new Date(), actividades, usuarios))
|
||||
}
|
||||
res.json(resumenMiDiaLocal(usuarioMiDia, capturas, clientes, new Date(), actividades, usuarios))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user