Webhook change_column_value en los 5 tableros (Leads, Oportunidades, Unidades, Apartado, Contactos) + mapeo central estado->grupo: el API re-consulta el item y lo mueve con move_item_to_group (idempotente). El funnel mueve la oportunidad de Afluencia al crearla (Monday la crea en el grupo top del board). Incluye script de backfill one-off (249 items reubicados) y tests del mapeo y del webhook.
115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
vi.mock('../config/index.js', () => ({
|
|
config: {
|
|
monday: {
|
|
boards: {
|
|
leads: '111',
|
|
oportunidades: '222',
|
|
contactos: '333',
|
|
unidades: '444',
|
|
apartado: '555',
|
|
},
|
|
},
|
|
},
|
|
}))
|
|
|
|
import { GRUPOS_POR_ESTADO, grupoDestino, moverAGrupoSegunEstado } from './grupos-estado.service.js'
|
|
|
|
interface ItemFake {
|
|
id: string
|
|
name: string
|
|
groupId: string
|
|
column_values: { id: string; text: string | null; value: string | null }[]
|
|
}
|
|
|
|
/** Monday mockeado: registra los movimientos de grupo solicitados */
|
|
function mondayMock(opts: { item?: ItemFake | null; columnaId?: string | null } = {}) {
|
|
return {
|
|
movimientos: [] as { itemId: string; groupId: string }[],
|
|
async columnaIdPorTitulo(_boardId: string, _titulo: string) {
|
|
return opts.columnaId !== undefined ? opts.columnaId : 'status_col'
|
|
},
|
|
async getItemConGrupo(_itemId: string) {
|
|
return opts.item ?? null
|
|
},
|
|
async moverItemAGrupo(itemId: string, groupId: string) {
|
|
this.movimientos.push({ itemId, groupId })
|
|
},
|
|
}
|
|
}
|
|
|
|
const itemCon = (groupId: string, estado: string): ItemFake => ({
|
|
id: '999',
|
|
name: 'Item Prueba',
|
|
groupId,
|
|
column_values: [{ id: 'status_col', text: estado, value: null }],
|
|
})
|
|
|
|
describe('grupoDestino', () => {
|
|
it('mapea labels conocidos de cada tablero', () => {
|
|
expect(grupoDestino('111', 'NUEVO')).toBe('topics')
|
|
expect(grupoDestino('111', 'CONTACTADO')).toBe('group_mm6s1jct')
|
|
expect(grupoDestino('111', 'DESCARTADO')).toBe('group_mm6s96mc')
|
|
expect(grupoDestino('222', 'Ganado')).toBe('group_mm6scqz3')
|
|
expect(grupoDestino('222', 'Afluencia')).toBe('topics')
|
|
expect(grupoDestino('333', 'INACTIVO')).toBe('group_mm6sqqq2')
|
|
expect(grupoDestino('444', 'Notaría')).toBe('group_mm6syf9c')
|
|
expect(grupoDestino('555', 'LIBERADO')).toBe('group_mm6s2yyx')
|
|
})
|
|
|
|
it('label desconocido → null', () => {
|
|
expect(grupoDestino('111', 'INVENTADO')).toBeNull()
|
|
})
|
|
|
|
it('board sin mapeo → null', () => {
|
|
expect(grupoDestino('999', 'NUEVO')).toBeNull()
|
|
})
|
|
|
|
it('etapa vacía en Unidades → null (no se mueve)', () => {
|
|
expect(grupoDestino('444', '')).toBeNull()
|
|
expect(grupoDestino('444', ' ')).toBeNull()
|
|
})
|
|
|
|
it('los 5 tableros del CRM tienen mapeo', () => {
|
|
expect(Object.keys(GRUPOS_POR_ESTADO).sort()).toEqual(['111', '222', '333', '444', '555'])
|
|
})
|
|
})
|
|
|
|
describe('moverAGrupoSegunEstado', () => {
|
|
it('mueve el item cuando el grupo actual difiere del destino', async () => {
|
|
const monday = mondayMock({ item: itemCon('topics', 'DESCARTADO') })
|
|
const r = await moverAGrupoSegunEstado(monday, '111', '999')
|
|
expect(r).toEqual({ movido: true, grupo: 'group_mm6s96mc' })
|
|
expect(monday.movimientos).toEqual([{ itemId: '999', groupId: 'group_mm6s96mc' }])
|
|
})
|
|
|
|
it('no mueve cuando el item ya está en el grupo destino', async () => {
|
|
const monday = mondayMock({ item: itemCon('group_mm6s96mc', 'DESCARTADO') })
|
|
const r = await moverAGrupoSegunEstado(monday, '111', '999')
|
|
expect(r).toEqual({ movido: false })
|
|
expect(monday.movimientos).toEqual([])
|
|
})
|
|
|
|
it('item inexistente (borrado tras el webhook) no truena ni mueve', async () => {
|
|
const monday = mondayMock({ item: null })
|
|
const r = await moverAGrupoSegunEstado(monday, '111', '999')
|
|
expect(r).toEqual({ movido: false })
|
|
expect(monday.movimientos).toEqual([])
|
|
})
|
|
|
|
it('board sin mapeo no consulta ni mueve nada', async () => {
|
|
const monday = mondayMock({ item: itemCon('topics', 'NUEVO') })
|
|
const r = await moverAGrupoSegunEstado(monday, '999', '999')
|
|
expect(r).toEqual({ movido: false })
|
|
expect(monday.movimientos).toEqual([])
|
|
})
|
|
|
|
it('label no mapeado no mueve', async () => {
|
|
const monday = mondayMock({ item: itemCon('topics', 'INVENTADO') })
|
|
const r = await moverAGrupoSegunEstado(monday, '111', '999')
|
|
expect(r).toEqual({ movido: false })
|
|
expect(monday.movimientos).toEqual([])
|
|
})
|
|
})
|