feat(mi-dia): botón Afluencia abre el formulario prellenado y actualiza la oportunidad
- Mis oportunidades: 'Afluencia →' navega a /client/?afluencia=<id> con nombre, contacto, desarrollo y modelo prellenados - Alta Afluencia: con oportunidadId el envío ACTUALIZA la oportunidad en Monday (perfil completo + etapa Afluencia) en vez de crear duplicado; sin oportunidadId el flujo de captura nueva no cambia - monday.service: actualizarOportunidadAfluencia (mismo builder de la creación + name); moverAGrupoSegunEstado tras actualizar - Tests: 457 API + 16 mi-dia + 116 alta-clientes, verde
This commit is contained in:
@@ -65,6 +65,10 @@ function mondayMock(
|
||||
this.oportunidadesCreadas.push(datos)
|
||||
return 'op-nueva-1'
|
||||
},
|
||||
oportunidadesActualizadas: [] as { id: string; datos: Record<string, unknown> }[],
|
||||
async actualizarOportunidadAfluencia(id: string, datos: Record<string, unknown>) {
|
||||
this.oportunidadesActualizadas.push({ id, datos })
|
||||
},
|
||||
async buscarLeadPorTelefono(_telefono: string) {
|
||||
return opts.duplicadoLead ?? null
|
||||
},
|
||||
@@ -260,6 +264,33 @@ describe('crearAfluencia (POST /api/afluencias) — la afluencia ES la oportunid
|
||||
espia.mockRestore()
|
||||
})
|
||||
|
||||
it('con oportunidadId (botón "Afluencia" de Mi día): ACTUALIZA la oportunidad, sin duplicado ni validación de teléfono', async () => {
|
||||
const monday = mondayMock()
|
||||
const r = await crearAfluencia(monday, {
|
||||
...datosBase,
|
||||
oportunidadId: 'op-existente-3',
|
||||
desarrollo: 'SENDA',
|
||||
modelo: 'SOHO',
|
||||
edad: 34,
|
||||
notas: 'Perfil completado en punto de venta',
|
||||
})
|
||||
expect(r).toEqual({ id: 'op-existente-3', oportunidadId: 'op-existente-3', actualizada: true })
|
||||
expect(monday.oportunidadesCreadas).toEqual([]) // no se duplica
|
||||
expect(monday.oportunidadesActualizadas).toEqual([
|
||||
{
|
||||
id: 'op-existente-3',
|
||||
datos: expect.objectContaining({
|
||||
nombre: 'Lead Prueba',
|
||||
telefono: '4771234567',
|
||||
desarrolloLabel: 'SENDA',
|
||||
modeloLabel: 'SOHO',
|
||||
edad: 34,
|
||||
notas: 'Perfil completado en punto de venta',
|
||||
}),
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('rechaza un desarrollo que no sea label real de la columna Desarrollo (board Oportunidades)', async () => {
|
||||
const monday = mondayMock()
|
||||
await expect(crearAfluencia(monday, { ...datosBase, desarrollo: 'MONTEBELLO' })).rejects.toThrow(
|
||||
|
||||
@@ -62,6 +62,9 @@ const crearAfluenciaSchema = z.object({
|
||||
origen: z.string().trim().min(1).optional(),
|
||||
desarrollo: z.string().trim().min(1).optional(),
|
||||
notas: z.string().trim().min(1).optional(),
|
||||
// Con oportunidadId (botón "Afluencia" de Mis oportunidades en Mi día): se
|
||||
// ACTUALIZA esa oportunidad con el perfil capturado en vez de crear una nueva
|
||||
oportunidadId: z.string().trim().min(1).optional(),
|
||||
// Campos de calificación de la afluencia (opcionales; los labels fijos del
|
||||
// front coinciden con los sembrados en Monday, no se validan contra el board)
|
||||
modelo: z.string().trim().min(1).optional(),
|
||||
@@ -165,6 +168,7 @@ type MondayLeads = Pick<
|
||||
| 'cambiarValorColumna'
|
||||
| 'crearLead'
|
||||
| 'crearOportunidadAfluencia'
|
||||
| 'actualizarOportunidadAfluencia'
|
||||
| 'crearMotivoNoCompra'
|
||||
| 'buscarLeadPorTelefono'
|
||||
| 'buscarOportunidadPorTelefono'
|
||||
@@ -176,7 +180,7 @@ type MondayLeads = Pick<
|
||||
>
|
||||
|
||||
export type ResultadoCrearLead =
|
||||
| { id: string; oportunidadId: string | null }
|
||||
| { id: string; oportunidadId: string | null; actualizada?: boolean }
|
||||
| { duplicado: { id: string; nombre: string } }
|
||||
|
||||
/**
|
||||
@@ -237,6 +241,38 @@ export async function crearAfluencia(
|
||||
): Promise<ResultadoCrearLead> {
|
||||
const boardOportunidades = config.monday.boards.oportunidades
|
||||
|
||||
// Con oportunidadId: el formulario venía prellenado desde Mis oportunidades
|
||||
// (Mi día) → se ACTUALIZA esa oportunidad con el perfil completo y queda en
|
||||
// etapa 'Afluencia'; no se crea duplicado ni se valida teléfono.
|
||||
if (datos.oportunidadId !== undefined) {
|
||||
await monday.actualizarOportunidadAfluencia(datos.oportunidadId, {
|
||||
nombre: datos.nombre,
|
||||
telefono: datos.telefono,
|
||||
email: datos.email,
|
||||
desarrolloLabel: datos.desarrollo,
|
||||
origenLabel: datos.origen,
|
||||
modeloLabel: datos.modelo,
|
||||
medioContacto: datos.medioContacto,
|
||||
edad: datos.edad,
|
||||
estadoCivilLabel: datos.estadoCivil,
|
||||
tipoEmpleoLabel: datos.tipoEmpleo,
|
||||
ocupacion: datos.ocupacion,
|
||||
rangoSalarial: datos.rangoSalarial,
|
||||
creditoLabel: datos.credito,
|
||||
tipoEconomiaLabel: datos.tipoEconomia,
|
||||
calificaLabel: datos.califica,
|
||||
loQuierePara: datos.loQuierePara,
|
||||
notas: datos.notas,
|
||||
})
|
||||
try {
|
||||
await moverAGrupoSegunEstado(monday, boardOportunidades, datos.oportunidadId)
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`No se pudo mover al grupo correcto la oportunidad ${datos.oportunidadId}:`, err)
|
||||
}
|
||||
return { id: datos.oportunidadId, oportunidadId: datos.oportunidadId, actualizada: true }
|
||||
}
|
||||
|
||||
if (datos.telefono !== undefined) {
|
||||
const existente = await monday.buscarOportunidadPorTelefono(datos.telefono)
|
||||
if (existente) return { duplicado: { id: existente.id, nombre: existente.nombre } }
|
||||
@@ -429,6 +465,12 @@ export async function crearAfluenciaEndpoint(req: Request, res: Response): Promi
|
||||
})
|
||||
return
|
||||
}
|
||||
// Actualización de oportunidad existente (botón "Afluencia" de Mi día): no
|
||||
// hay captura nueva que registrar ni round-robin que correr
|
||||
if (resultado.actualizada === true) {
|
||||
res.json({ id: resultado.id, estado: 'Afluencia', oportunidadId: resultado.oportunidadId, actualizada: true })
|
||||
return
|
||||
}
|
||||
// Registro local del capturista para "Mis capturas" del panel Mi día.
|
||||
// leadId = el id de la oportunidad (la afluencia ES la oportunidad). Un
|
||||
// fallo aquí no rompe la respuesta: la afluencia ya quedó creada en Monday.
|
||||
|
||||
Reference in New Issue
Block a user