fix(auditoria general): 25 errores encontrados en la revision modulo por modulo
SEGURIDAD (agente IA): - Conversaciones: GET /ai/conversations/:id y el reanudar chat verifican propiedad (antes cualquier usuario leia/continuaba la conversacion de otro). - /ai/documents pasa a solo-admin (antes cualquien podia inyectar o borrar documentos de la base de conocimiento). DATOS / CORRUPCION: - Cotizador: elegir un cliente del maestro en el buscador ya no crea un lead fantasma "Registro previo (CRM anterior)" en el embudo. - Afluencia con oportunidadId inexistente: 404 en vez de crear sombra fantasma. - Cobranza: los 103 planes importados quedaron con vendedor asignado (7 con su asesor real segun el Excel, el resto al admin) y el motor de recordatorios cae al admin cuando un plan no tiene vendedor: antes los 411 recordatorios se registraban 'sin-destinatario' y no los veia nadie. - Backups: script reescrito para el host real (sqlite .backup + uploads + env en /root/backups) y cron instalado (antes: script que asumia docker y sin cron programado = cero respaldos). REPORTES / METRICAS: - Mi tablero: el embudo ya excluye cancelaciones reales (Cancelo/Cancelacion Apartado) y cuenta como vendidos Firma Cliente/Escriturado/Titulado (antes 41 "apartados" donde habia 13 activos y vendidos buscaba una etapa que no existe); tasa de descarte ya compara el mismo periodo. - Scoring: etapa real 'Agendo Cita' (antes 'AGENDO' nunca aplicaba) y los descartados/perdidos ya no reciben score. - ver_cobranza (IA): ignora planes cancelados y no cuenta como vencido el pago que vence hoy. UI / FLUJOS: - mi-dia: typo CANCO -> CANCELO (los cancelados reaparecen en Escrituracion); confirmar cita ya no retrocede la etapa de Cotizador/Aparto (boton y kanban). - alta-clientes: la firma ya no se borra al navegar fuera del paso de firmas; toast de exito solo cuando el API confirma; prefill de apartado trae direccion, estado civil y fecha de nacimiento desde el alta. - Auditoria: el filtro por usuario funciona (el front enviaba 'usuario' y el API esperaba 'usuarioId') y 'hasta' incluye todo el dia. - Contactos: paginacion aplicada despues de fusionar fuentes (antes saltaban o se repetian contactos entre paginas y 'Siguiente' se deshabilitaba antes de tiempo). - Buscador global: leads -> /hoy/, contactos -> /; el cotizador entiende ?cotizacion=<id> para retomar desde la liga. - Export mi-dia: columnas Desarrollo/Origen rellenas y sin duplicados. - Recibo de cobranza: "Recibio" es FRASA (el cliente paga, no recibe). - Alta de plan de cobranza: proyecto libre con sugerencias del catalogo real (antes limitado a 3 proyectos que no cubrian la cartera). Verificado en vivo: autorizacion IA (3x 403), filtro de auditoria, 404 de afluencia. Suites: 717 API + 124 alta-clientes + 115 cotizador + 92 cobranza + 89 landing + 32 mi-dia en verde. Backup de hoy generado en /root/backups.
This commit is contained in:
@@ -2,6 +2,7 @@ import crypto from 'node:crypto'
|
||||
import { prisma } from './prisma.js'
|
||||
import { config } from '../config/index.js'
|
||||
import { AppError, NotFoundError } from '../utils/errors.js'
|
||||
import { esAdminOManager } from '../utils/roles.js'
|
||||
import { getUnidadesLocal } from './unidades-local.service.js'
|
||||
import {
|
||||
APARTADO_DEFAULT,
|
||||
@@ -1020,10 +1021,12 @@ export function crearEjecutoresHerramientas(
|
||||
})
|
||||
if (planes.length === 0) return { error: `No hay planes de cobranza para "${cliente}"` }
|
||||
const hoy = new Date()
|
||||
// Medianoche de hoy: lo que vence HOY aún no está vencido
|
||||
const inicioHoy = new Date(hoy.getFullYear(), hoy.getMonth(), hoy.getDate())
|
||||
return planes.map((p) => {
|
||||
const pagado = p.pagos.reduce((a, x) => a + x.montoPagado, 0)
|
||||
const saldo = p.pagos.reduce((a, x) => a + (x.monto - x.montoPagado), 0)
|
||||
const vencidos = p.pagos.filter((x) => x.fechaVencimiento < hoy && x.montoPagado < x.monto)
|
||||
const vencidos = p.pagos.filter((x) => x.fechaVencimiento < inicioHoy && x.montoPagado < x.monto)
|
||||
const proximo = p.pagos.filter((x) => x.montoPagado < x.monto && x.fechaVencimiento >= hoy)
|
||||
.sort((a, b) => a.fechaVencimiento.getTime() - b.fechaVencimiento.getTime())[0]
|
||||
const abonos = p.pagos
|
||||
@@ -1054,7 +1057,8 @@ export function crearEjecutoresHerramientas(
|
||||
prisma.planPago.count({ where: { status: 'ACTIVO', ...alcance } }),
|
||||
prisma.planPago.count({ where: { status: 'CANCELADO', ...alcance } }),
|
||||
prisma.pago.findMany({
|
||||
where: { fechaVencimiento: { lt: new Date() }, plan: alcance },
|
||||
// Sólo planes activos: el adeudo de planes cancelados no cuenta
|
||||
where: { fechaVencimiento: { lt: new Date() }, plan: { ...alcance, status: 'ACTIVO' } },
|
||||
select: {
|
||||
monto: true,
|
||||
montoPagado: true,
|
||||
@@ -1505,6 +1509,18 @@ export async function chat(
|
||||
? await prisma.aIConversacion.findUnique({ where: { id: sessionId } })
|
||||
: null
|
||||
if (sessionId && !conversacion) throw new NotFoundError('Conversación no encontrada')
|
||||
// Propiedad: sólo el dueño (o un rol de supervisión) puede reanudar una
|
||||
// conversación existente. Sin esto, cualquiera con el uuid continuaba el
|
||||
// chat ajeno con todo su historial.
|
||||
if (
|
||||
conversacion &&
|
||||
opciones.usuarioId &&
|
||||
conversacion.usuarioId &&
|
||||
conversacion.usuarioId !== opciones.usuarioId &&
|
||||
!esAdminOManager(opciones.contexto?.rol)
|
||||
) {
|
||||
throw new AppError(403, 'No puedes continuar la conversación de otro usuario')
|
||||
}
|
||||
conversacion ??= await prisma.aIConversacion.create({
|
||||
data: { usuarioId: opciones.usuarioId ?? null },
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user