feat(apartado): ficha completa en Monday + estado visible en la app

- El alta (directa y progresiva) llena el Apartado con telefono, correo,
  curp, Precio Final, Enganche, Fecha Compra, Vendedor (el capturista
  con Monday vinculado) y Forma de Pago; el path webhook/cron hereda
  contacto, precio y asesor de la oportunidad.
- Bidireccional: Mis clientes en /hoy muestra el estado actual del
  Apartado leyendolo de Monday (si lo mueven en Monday, la app lo
  refleja en el siguiente refresh).
- getApartados con la correccion value-null al leer Estado.
- Test del mapa de Tipo propuesta del cotizador (los 502 del log eran
  de un proceso previo al fix 4f31d1c; queda cubierto).
This commit is contained in:
2026-09-04 07:34:57 +00:00
parent d81797c610
commit 42cfe937cf
13 changed files with 428 additions and 12 deletions

View File

@@ -14,6 +14,9 @@ const prismaMock = vi.hoisted(() => ({
create: vi.fn(),
update: vi.fn(),
},
usuario: {
findUnique: vi.fn(),
},
}))
const mondayMock = vi.hoisted(() => ({
@@ -134,6 +137,7 @@ describe('crearCliente (POST /api/clientes) — leadMondayId del funnel', () =>
vi.clearAllMocks()
prismaMock.cliente.create.mockImplementation(async ({ data }) => ({ id: 'cli-1', ...data }))
prismaMock.cliente.update.mockResolvedValue({})
prismaMock.usuario.findUnique.mockResolvedValue(null) // default: capturista sin Monday vinculado
mondayMock.crearContacto.mockResolvedValue('cont-1')
funnelMock.moverAApartoConDedup.mockResolvedValue('op-1')
})
@@ -176,3 +180,99 @@ describe('crearCliente (POST /api/clientes) — leadMondayId del funnel', () =>
)
})
})
describe('formaPagoLabel (tipos de crédito del alta → dropdown Forma de Pago)', () => {
it('mapea los 8 valores del enum del formato a los labels sembrados en Monday', async () => {
const { formaPagoLabel } = await import('./clientes.controller.js')
expect(formaPagoLabel(['INFONAVIT'])).toBe('Infonavit')
expect(formaPagoLabel(['APOYO_INFONAVIT'])).toBe('Apoyo Infonavit')
expect(formaPagoLabel(['COFINAVIT'])).toBe('Cofinavit')
expect(formaPagoLabel(['SEGUNDO_CREDITO'])).toBe('Segundo crédito')
expect(formaPagoLabel(['CONTADO'])).toBe('Contado')
expect(formaPagoLabel(['FOVISSSTE'])).toBe('Fovissste')
expect(formaPagoLabel(['COFI_FOVISSSTE'])).toBe('Cofi-Fovissste')
expect(formaPagoLabel(['BANCARIO'])).toBe('Bancario')
})
it('toma el PRIMERO reconocido del arreglo y tolera desconocidos', async () => {
const { formaPagoLabel } = await import('./clientes.controller.js')
expect(formaPagoLabel(['OTRA_COSA', 'CONTADO'])).toBe('Contado')
expect(formaPagoLabel(['OTRA_COSA'])).toBeUndefined()
expect(formaPagoLabel([])).toBeUndefined()
expect(formaPagoLabel(undefined)).toBeUndefined()
expect(formaPagoLabel('CONTADO')).toBeUndefined() // no es arreglo
})
})
describe('crearCliente — Apartado con ficha completa', () => {
const altaConVenta = {
datosGenerales: {
nombreCompleto: 'Cliente Demo',
curp: 'CURP123',
rfc: 'RFC123',
email: 'cli@correo.com',
celular: '6641112233',
},
viviendaCredito: {
proyecto: 'SENDA',
ubicacion: 'U-101',
valorVenta: 1500000,
enganche: 20000,
tiposCredito: ['INFONAVIT'],
},
}
const reqFake = (body: unknown, usuarioToken?: { sub: string }): Request =>
({ body, usuarioToken } as unknown 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({})
prismaMock.usuario.findUnique.mockResolvedValue(null)
mondayMock.crearContacto.mockResolvedValue('cont-1')
mondayMock.crearApartado.mockResolvedValue('ap-1')
funnelMock.moverAApartoConDedup.mockResolvedValue('op-1')
})
it('el Apartado lleva contacto, precio, enganche, Fecha Compra (hoy) y Forma de Pago', async () => {
const { crearCliente } = await import('./clientes.controller.js')
const { res, status, json } = resFake()
await crearCliente(reqFake(altaConVenta), res)
expect(status).toHaveBeenCalledWith(201)
expect(mondayMock.crearApartado).toHaveBeenCalledWith(
expect.objectContaining({
clienteNombre: 'Cliente Demo',
unidadCodigo: 'U-101',
montoApartado: 20000,
estadoLabel: 'Pago Apartado',
telefono: '6641112233',
email: 'cli@correo.com',
curp: 'CURP123',
precioFinal: 1500000,
enganche: 20000,
formaPagoLabel: 'Infonavit',
fechaCompra: expect.stringMatching(/^\d{4}-\d{2}-\d{2}$/),
}),
)
expect(json.mock.calls[0]?.[0]).toMatchObject({ mondayApartadoId: 'ap-1' })
})
it('con capturista vinculado a Monday, el Apartado lo lleva como Vendedor (people)', async () => {
const { crearCliente } = await import('./clientes.controller.js')
prismaMock.usuario.findUnique.mockResolvedValue({ mondayUserId: 12345 })
await crearCliente(reqFake(altaConVenta, { sub: 'usr-1' }), resFake().res)
expect(mondayMock.crearApartado).toHaveBeenCalledWith(expect.objectContaining({ vendedorId: 12345 }))
})
it('sin capturista vinculado, el Apartado se crea sin Vendedor', async () => {
const { crearCliente } = await import('./clientes.controller.js')
await crearCliente(reqFake(altaConVenta), resFake().res)
expect(mondayMock.crearApartado).toHaveBeenCalledWith(expect.objectContaining({ vendedorId: undefined }))
})
})