feat(funnel): leads entran como Afluencia al funnel de Oportunidades
- Captura rápida crea lead + oportunidad en etapa Afluencia (vinculadas por nueva tabla LeadOportunidad); también aplica a leads creados directo en Monday vía webhook - Transiciones automáticas: AGENDA CITA → Agendo Cita, descarte → Perdido (+ dropdown Motivo No Compra), cotización con mismo teléfono ACTUALIZA la oportunidad (no duplica) → Cotizador, alta de cliente con leadMondayId → Aparto (dedup previo para no duplicar Apartado) - Columna Desarrollo agregada al board Oportunidades (color_mm6sa20t) - Migración lead_oportunidad aplicada con migrate diff + resolve (la DB tiene drift histórico por db push; migrate dev pedía reset) - Tests: 322 verdes; smoke en vivo verificado y limpiado
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Request, Response } from 'express'
|
||||
import {
|
||||
actividadEconomicaLabel,
|
||||
checkboxParaTipoDocumento,
|
||||
@@ -6,6 +7,28 @@ import {
|
||||
rangoEdadLabel,
|
||||
} from './clientes.controller.js'
|
||||
|
||||
// ---------- Mocks de módulo (los tests no tocan BD ni red) ----------
|
||||
|
||||
const prismaMock = vi.hoisted(() => ({
|
||||
cliente: {
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
const mondayMock = vi.hoisted(() => ({
|
||||
crearContacto: vi.fn(),
|
||||
crearApartado: vi.fn(),
|
||||
}))
|
||||
|
||||
const funnelMock = vi.hoisted(() => ({
|
||||
moverAApartoConDedup: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
||||
vi.mock('../services/monday.service.js', () => ({ mondayService: mondayMock }))
|
||||
vi.mock('../services/funnel.service.js', () => ({ funnelService: funnelMock }))
|
||||
|
||||
describe('checkboxParaTipoDocumento (Apartado → checkboxes de documentos)', () => {
|
||||
it('mapea los tipos con checkbox al título de la columna', () => {
|
||||
expect(checkboxParaTipoDocumento('CURP')).toBe('CURP')
|
||||
@@ -92,3 +115,52 @@ describe('crearClienteSchema', () => {
|
||||
).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('crearCliente (POST /api/clientes) — leadMondayId del funnel', () => {
|
||||
// Import diferido para que los vi.mock de arriba apliquen al controller
|
||||
const altaBase = {
|
||||
datosGenerales: { nombreCompleto: 'Cliente Demo', curp: 'CURP123', rfc: 'RFC123' },
|
||||
viviendaCredito: { proyecto: 'SENDA' },
|
||||
}
|
||||
|
||||
const reqFake = (body: unknown): Request => ({ body } as Request)
|
||||
const resFake = () => {
|
||||
const json = vi.fn()
|
||||
const status = vi.fn().mockReturnValue({ json })
|
||||
return { res: { json, status } as unknown as Response, status, json }
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
prismaMock.cliente.create.mockImplementation(async ({ data }) => ({ id: 'cli-1', ...data }))
|
||||
prismaMock.cliente.update.mockResolvedValue({})
|
||||
mondayMock.crearContacto.mockResolvedValue('cont-1')
|
||||
funnelMock.moverAApartoConDedup.mockResolvedValue('op-1')
|
||||
})
|
||||
|
||||
it('con leadMondayId mueve la oportunidad a Aparto (con dedup) antes de replicar en Monday', async () => {
|
||||
const { crearCliente } = await import('./clientes.controller.js')
|
||||
const { prismaStateStore } = await import('../services/automatizaciones.service.js')
|
||||
const { res, status } = resFake()
|
||||
await crearCliente(reqFake({ ...altaBase, leadMondayId: 'lead-9' }), res)
|
||||
expect(status).toHaveBeenCalledWith(201)
|
||||
expect(funnelMock.moverAApartoConDedup).toHaveBeenCalledWith('lead-9', prismaStateStore)
|
||||
})
|
||||
|
||||
it('sin leadMondayId no toca el funnel', async () => {
|
||||
const { crearCliente } = await import('./clientes.controller.js')
|
||||
const { res, status } = resFake()
|
||||
await crearCliente(reqFake(altaBase), res)
|
||||
expect(status).toHaveBeenCalledWith(201)
|
||||
expect(funnelMock.moverAApartoConDedup).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('un fallo del funnel no rompe el alta (201 igual)', async () => {
|
||||
const { crearCliente } = await import('./clientes.controller.js')
|
||||
funnelMock.moverAApartoConDedup.mockRejectedValue(new Error('Monday caído'))
|
||||
const { res, status, json } = resFake()
|
||||
await crearCliente(reqFake({ ...altaBase, leadMondayId: 'lead-9' }), res)
|
||||
expect(status).toHaveBeenCalledWith(201)
|
||||
expect(json.mock.calls[0]?.[0]).toMatchObject({ id: 'cli-1', mondayContactoId: 'cont-1' })
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user