feat(mi-dia): gate de afluencia para apartar, pestana Afluencia en bitacora, cita cliente con fecha en calendario, filtros por desarrollo/origen corregidos, rangos salariales nuevos y soporte como acceso directo
This commit is contained in:
@@ -234,9 +234,26 @@ describe('CrmLocalService.apartar', () => {
|
||||
email: 'luis@correo.com',
|
||||
desarrollo: 'ATELIER',
|
||||
modelo: 'SOHO',
|
||||
datosJson: '{"edad":35}',
|
||||
datosJson: JSON.stringify({
|
||||
edad: 35,
|
||||
estadoCivil: 'Casado',
|
||||
tipoEmpleo: 'Formal',
|
||||
rangoSalarial: 'B. 20,000-40,000',
|
||||
tipoEconomia: 'Formal',
|
||||
califica: 'Si',
|
||||
loQuierePara: 'Para vivir',
|
||||
}),
|
||||
}
|
||||
|
||||
it('sin afluencia completa rechaza el apartado (regla de negocio)', async () => {
|
||||
prismaMock.leadCaptura.findUnique.mockResolvedValue({
|
||||
...filaOportunidad,
|
||||
datosJson: '{"edad":35}',
|
||||
})
|
||||
await expect(svc.apartar('loc_op', 'u-1')).rejects.toThrow(/afluencia/i)
|
||||
expect(prismaMock.leadCaptura.create).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('pone la oportunidad en Aparto y crea el apartado local (Pago Apartado)', async () => {
|
||||
prismaMock.leadCaptura.findUnique.mockResolvedValue(filaOportunidad)
|
||||
const apartado = await svc.apartar('loc_op', 'u-1')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { AppError, NotFoundError } from '../utils/errors.js'
|
||||
import { afluenciaCompleta } from '../config/vocabulario.js'
|
||||
import { claveApartadoCreado } from './automatizaciones.service.js'
|
||||
import { calcularClienteClave } from './recordatorio-vendedor.service.js'
|
||||
import { prisma } from './prisma.js'
|
||||
@@ -229,9 +230,10 @@ export class CrmLocalService {
|
||||
opciones: { fecha?: string | undefined; usuarioId?: string | undefined; sombra?: boolean | undefined } = {},
|
||||
): Promise<void> {
|
||||
await this.filaLocal(itemId, board, opciones.usuarioId, opciones.sombra ?? false)
|
||||
// Guardar fechaCita en datosJson para que la oportunidad la muestre en el calendario
|
||||
// Guardar fechaCita en datosJson (oportunidad y apartado) para que la
|
||||
// muestre Mi día y el calendario
|
||||
const updateData: Record<string, unknown> = { etapa: label, pendienteSync: true }
|
||||
if (opciones.fecha !== undefined && board === 'OPORTUNIDADES') {
|
||||
if (opciones.fecha !== undefined && (board === 'OPORTUNIDADES' || board === 'APARTADO')) {
|
||||
const existente = await prisma.leadCaptura.findUnique({ where: { leadId: itemId }, select: { datosJson: true } })
|
||||
let datos: Record<string, unknown> = {}
|
||||
if (existente?.datosJson) {
|
||||
@@ -347,6 +349,11 @@ export class CrmLocalService {
|
||||
!esIdLocal(oportunidadIdLocal),
|
||||
)
|
||||
|
||||
// Regla de negocio: no se aparta sin la afluencia completa del cliente
|
||||
if (!afluenciaCompleta(oportunidad.datosJson)) {
|
||||
throw new AppError(400, 'Primero debes capturar la afluencia del cliente (botón Afluencia) antes de apartar')
|
||||
}
|
||||
|
||||
const clave = claveApartadoCreado(oportunidadIdLocal)
|
||||
const previo = await prisma.automatizacionState.findUnique({ where: { clave } })
|
||||
if (previo !== null) {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* resuelve por el usuarioId de cada fila.
|
||||
*/
|
||||
|
||||
import { camposAfluenciaFaltantes } from '../config/vocabulario.js'
|
||||
|
||||
// Estados del board Leads en los que el lead ya no es trabajo activo del vendedor
|
||||
export const ESTADOS_TERMINALES_LEAD = ['DESCARTADO', 'CALIFICADO', 'ABANDONO']
|
||||
|
||||
@@ -133,6 +135,8 @@ export interface OportunidadMiDia {
|
||||
ultimoCambio: { fechaISO: string; usuario: string | null; detalle: string } | null
|
||||
/** Fecha y hora de la cita agendada (ej. '2026-09-10T14:30' o '2026-09-10') */
|
||||
fechaCita?: string | undefined
|
||||
/** ¿Afluencia con perfil completo? Sin esto no puede avanzar a 'Aparto' */
|
||||
afluenciaCompleta: boolean
|
||||
}
|
||||
|
||||
/** Apartado asignado al vendedor (tab "Mis apartados" de Mi día) */
|
||||
@@ -339,6 +343,7 @@ export function resumenMiDiaLocal(
|
||||
updatedAt: c.updatedAt.toISOString(),
|
||||
ultimoCambio: ultimoCambioLocal(c, nombrePorUsuarioId.get(c.usuarioId) ?? ''),
|
||||
fechaCita,
|
||||
afluenciaCompleta: camposAfluenciaFaltantes(c.datosJson).length === 0,
|
||||
pendienteSync: c.pendienteSync,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user