feat(leads): doble via de captura — lead rapido a Leads, afluencia a Oportunidades

- POST /api/leads: captura ligera al tablero Leads (sin oportunidad),
  self-assign a Vendedor o round-robin; tambien cubre la captura manual
  desde la UI de Monday (webhook create_item re-registrado).
- POST /api/afluencias (nuevo): la forma completa sigue cayendo en
  Oportunidades con todo el perfil (etapa Afluencia, columna Asesor).
- Round-robin dual (ambos tableros), Mi dia mezcla leads+oportunidades
  con insignia de origen, acciones con parametro fuente, reportes:
  leads desde Leads, perfilamiento desde Oportunidades, funnel
  Leads->Afluencia->...->Ganado.
This commit is contained in:
2026-09-04 06:09:38 +00:00
parent 772fdef09f
commit 135e3e6de5
21 changed files with 1053 additions and 326 deletions

View File

@@ -557,6 +557,97 @@ describe('MondayService', () => {
})
})
describe('asignarVendedorALead (columna people Vendedor del tablero Leads)', () => {
it('escribe personsAndTeams con el user id en el board Leads (444 en tests)', async () => {
const llamadas = mockearFetch((body) => {
if (body.query.includes('columns')) {
return respuestaOk({ boards: [{ columns: [{ id: 'people_v', title: 'Vendedor', type: 'people' }] }] })
}
return respuestaOk({ change_column_value: { id: 'lead-1' } })
})
await crearServicio().asignarVendedorALead('lead-1', 12345)
const mutacion = llamadas.find((l) => l.body.query.includes('change_column_value'))
expect(mutacion?.body.variables['boardId']).toBe('444')
expect(mutacion?.body.variables['columnId']).toBe('people_v')
expect(JSON.parse(String(mutacion?.body.variables['value']))).toEqual({
personsAndTeams: [{ id: 12345, kind: 'person' }],
})
})
})
describe('crearLead (captura ligera en el tablero Leads)', () => {
const COLUMNAS_LEADS_LIGERO = [
{ id: 'color_estado', title: 'Estado', type: 'status' },
{ id: 'date_fecha', title: 'Fecha', type: 'date' },
{ id: 'phone_tel', title: 'Teléfono', type: 'phone' },
{ id: 'email_em', title: 'Email', type: 'email' },
{ id: 'color_or', title: 'Origen', type: 'status' },
{ id: 'color_des', title: 'Desarrollo', type: 'status' },
{ id: 'text_notas', title: 'Notas', type: 'text' },
]
it('crea el item ligero con Estado NUEVO y Fecha de hoy (sin perfil ni oportunidad)', async () => {
const llamadas = mockearFetch((body) => {
if (body.query.includes('settings_str')) {
return respuestaOk({
boards: [
{
columns: COLUMNAS_LEADS_LIGERO.filter((c) => c.type === 'status').map((c) => ({
id: c.id,
title: c.title,
settings_str: '{"labels":{"1":"Nuevo"}}',
})),
},
],
})
}
if (body.query.includes('columns')) return respuestaOk({ boards: [{ columns: COLUMNAS_LEADS_LIGERO }] })
return respuestaOk({ create_item: { id: 'lead-nuevo-1' } })
})
const id = await crearServicio().crearLead({
nombre: 'Lead Prueba',
telefono: '4771234567',
email: 'lead@correo.com',
origenLabel: 'Facebook',
desarrolloLabel: 'SENDA',
notas: 'Vio el volante',
})
expect(id).toBe('lead-nuevo-1')
const mutacion = llamadas.find((l) => l.body.query.includes('create_item'))
expect(mutacion?.body.variables['boardId']).toBe('444') // tablero Leads
const parsed = JSON.parse(String(mutacion?.body.variables['columnValues'])) as Record<string, unknown>
expect(parsed['color_estado']).toEqual({ label: 'Nuevo' }) // 'NUEVO' resuelto al label real
expect(parsed['date_fecha']).toMatchObject({ date: expect.stringMatching(/^\d{4}-\d{2}-\d{2}$/) })
expect(parsed['phone_tel']).toEqual({ phone: '4771234567', countryShortName: 'MX' })
expect(parsed['email_em']).toEqual({ email: 'lead@correo.com', text: 'lead@correo.com' })
expect(parsed['color_or']).toEqual({ label: 'Facebook' })
expect(parsed['color_des']).toEqual({ label: 'SENDA' })
expect(parsed['text_notas']).toBe('Vio el volante')
})
it('solo envía las columnas capturadas (las vacías se omiten)', async () => {
const llamadas = mockearFetch((body) => {
if (body.query.includes('settings_str')) {
return respuestaOk({
boards: [{ columns: [{ id: 'color_estado', title: 'Estado', settings_str: '{"labels":{"1":"Nuevo"}}' }] }],
})
}
if (body.query.includes('columns')) return respuestaOk({ boards: [{ columns: COLUMNAS_LEADS_LIGERO }] })
return respuestaOk({ create_item: { id: 'lead-nuevo-2' } })
})
await crearServicio().crearLead({ nombre: 'Solo Nombre' })
const mutacion = llamadas.find((l) => l.body.query.includes('create_item'))
const parsed = JSON.parse(String(mutacion?.body.variables['columnValues'])) as Record<string, unknown>
expect(Object.keys(parsed).sort()).toEqual(['color_estado', 'date_fecha'])
})
})
describe('asignarAsesorAOportunidad (columna people Asesor)', () => {
it('escribe personsAndTeams con el user id en el board Oportunidades (555 en tests)', async () => {
const llamadas = mockearFetch((body) => {