- Firma Cliente (Venta) renombrada a Alta Enkontrol (mismo redirect a alta cliente) - Campo 'Usuario Enkontrol' añadido en Datos Generales del alta cliente - Unidad se bloquea (APARTADA) al pasar cliente a etapa Aparto - Backend y frontend actualizados
186 lines
6.9 KiB
TypeScript
186 lines
6.9 KiB
TypeScript
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'
|
|
import type { Request, Response } from 'express'
|
|
import {
|
|
cambiarEstadoApartado,
|
|
cambiarEstadoApartadoEndpoint,
|
|
FASES_APARTADO_PERMITIDAS,
|
|
subirDocumentosApartado,
|
|
TIPOS_DOCUMENTO_APARTADO,
|
|
} from './apartados.controller.js'
|
|
import { saludMonday } from '../services/salud-monday.service.js'
|
|
|
|
// ---------- Prisma mockeado (los tests no tocan la BD) ----------
|
|
|
|
const prismaMock = vi.hoisted(() => ({
|
|
leadCaptura: {
|
|
create: vi.fn(),
|
|
findFirst: vi.fn(),
|
|
findUnique: vi.fn(),
|
|
update: vi.fn(),
|
|
},
|
|
cliente: {
|
|
create: vi.fn(),
|
|
findFirst: vi.fn(),
|
|
},
|
|
documento: {
|
|
create: vi.fn(),
|
|
},
|
|
syncPendiente: {
|
|
create: vi.fn(),
|
|
},
|
|
auditoriaLog: {
|
|
create: vi.fn().mockResolvedValue({}),
|
|
},
|
|
$transaction: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
|
|
|
/** Monday mockeado: registra cambios de columna y movimientos de grupo */
|
|
function mondayMock(grupoActual = 'topics') {
|
|
return {
|
|
cambios: [] as { boardId: string; itemId: string; columna: string; valor: unknown }[],
|
|
movimientos: [] as { itemId: string; grupo: string }[],
|
|
async cambiarValorColumna(boardId: string, itemId: string, columna: string, valor: string | number | boolean) {
|
|
this.cambios.push({ boardId, itemId, columna, valor })
|
|
},
|
|
async columnaIdPorTitulo(_boardId: string, titulo: string) {
|
|
return titulo === 'Estado' ? 'status' : null
|
|
},
|
|
async getItemConGrupo(itemId: string) {
|
|
return {
|
|
id: itemId,
|
|
name: 'Cliente Demo',
|
|
groupId: grupoActual,
|
|
column_values: [
|
|
{ id: 'status', text: (this.cambios.find((c) => c.columna === 'Estado')?.valor as string) ?? '', value: '{}' },
|
|
],
|
|
}
|
|
},
|
|
async moverItemAGrupo(itemId: string, grupo: string) {
|
|
this.movimientos.push({ itemId, grupo })
|
|
},
|
|
}
|
|
}
|
|
|
|
describe('cambiarEstadoApartado (POST /api/apartados/:id/estado)', () => {
|
|
it('cambia la fase (board 666 = MONDAY_BOARD_APARTADO de test)', async () => {
|
|
const monday = mondayMock()
|
|
await cambiarEstadoApartado(monday, 'apt-1', 'Alta Enkontrol')
|
|
expect(monday.cambios).toEqual([
|
|
{ boardId: '666', itemId: 'apt-1', columna: 'Estado', valor: 'Alta Enkontrol' },
|
|
])
|
|
// 'Alta Enkontrol' mapea al grupo topics: ya estaba ahí → no se mueve
|
|
expect(monday.movimientos).toEqual([])
|
|
})
|
|
|
|
it('Alta Enkontrol se queda en "Nuevos Clientes" (grupo topics)', async () => {
|
|
const monday = mondayMock()
|
|
await cambiarEstadoApartado(monday, 'apt-2', 'Alta Enkontrol')
|
|
expect(monday.movimientos).toEqual([])
|
|
})
|
|
|
|
it('Cancelacion Apartado mueve el item a "Historico"', async () => {
|
|
const monday = mondayMock()
|
|
await cambiarEstadoApartado(monday, 'apt-3', 'Cancelacion Apartado')
|
|
expect(monday.movimientos).toEqual([{ itemId: 'apt-3', grupo: 'group_mm6s2yyx' }])
|
|
})
|
|
|
|
it('las fases permitidas son los labels reales del board Apartado', () => {
|
|
expect(FASES_APARTADO_PERMITIDAS).toEqual([
|
|
'Pago Apartado',
|
|
'Alta Enkontrol',
|
|
'Entrego Documentacion',
|
|
'Cita Clientes',
|
|
'Contrato Enkontrol',
|
|
'Cancelacion Apartado',
|
|
])
|
|
})
|
|
|
|
it('los tipos de documento de la precarga son capturas obligatorias + generales', () => {
|
|
expect(TIPOS_DOCUMENTO_APARTADO).toEqual(['CAPTURA_COTIZADOR', 'CAPTURA_PAGO_APARTADO', 'OTRO'])
|
|
})
|
|
})
|
|
|
|
// ---------- Modo local (saludMonday caído / ids loc_*) ----------
|
|
|
|
describe('ruteo modo local (apartados)', () => {
|
|
function 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.leadCaptura.findUnique.mockResolvedValue(null)
|
|
prismaMock.leadCaptura.findFirst.mockResolvedValue(null)
|
|
prismaMock.leadCaptura.create.mockImplementation(async ({ data }) => ({ ...data }))
|
|
prismaMock.leadCaptura.update.mockImplementation(async ({ data }) => ({ ...data }))
|
|
prismaMock.cliente.findFirst.mockResolvedValue(null)
|
|
prismaMock.cliente.create.mockImplementation(async ({ data }) => ({ id: 'cli-local', ...data }))
|
|
prismaMock.documento.create.mockImplementation(async ({ data }) => ({ id: 'doc-1', ...data }))
|
|
prismaMock.syncPendiente.create.mockResolvedValue({})
|
|
prismaMock.$transaction.mockImplementation(async (ops: Promise<unknown>[]) => Promise.all(ops))
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('cambiarEstadoApartadoEndpoint con id loc_*: fase local + CAMBIO_ETAPA encolado', async () => {
|
|
vi.spyOn(saludMonday, 'estaDisponible').mockReturnValue(true) // el id loc_* manda aunque Monday viva
|
|
prismaMock.leadCaptura.findUnique.mockResolvedValue({ leadId: 'loc_apt', board: 'APARTADO' })
|
|
const { res, json } = resFake()
|
|
|
|
await cambiarEstadoApartadoEndpoint(
|
|
{ params: { itemId: 'loc_apt' }, body: { estado: 'Alta Enkontrol' } } as unknown as Request,
|
|
res,
|
|
)
|
|
|
|
expect(prismaMock.leadCaptura.update).toHaveBeenCalledWith({
|
|
where: { leadId: 'loc_apt' },
|
|
data: { etapa: 'Alta Enkontrol', pendienteSync: true },
|
|
})
|
|
expect(prismaMock.syncPendiente.create).toHaveBeenCalledWith({
|
|
data: expect.objectContaining({ tipo: 'CAMBIO_ETAPA', referenciaLocal: 'loc_apt' }),
|
|
})
|
|
expect(json).toHaveBeenCalledWith({ itemId: 'loc_apt', estado: 'Alta Enkontrol' })
|
|
})
|
|
|
|
it('subirDocumentosApartado con Monday caído: nombre desde LeadCaptura local y checkbox encolado (CHECKBOX_DOC)', async () => {
|
|
vi.spyOn(saludMonday, 'estaDisponible').mockReturnValue(false)
|
|
// El apartado local nació del "Aparto" de la oportunidad: su fila tiene el nombre
|
|
prismaMock.leadCaptura.findFirst.mockResolvedValue({ leadId: 'loc_apt', nombreLead: 'Luis Demo' })
|
|
const archivo = {
|
|
originalname: 'curp.pdf',
|
|
path: '/tmp/uploads/apartados/loc_apt/CURP_1.pdf',
|
|
mimetype: 'application/pdf',
|
|
size: 1234,
|
|
} as Express.Multer.File
|
|
const { res, status } = resFake()
|
|
|
|
await subirDocumentosApartado(
|
|
{ params: { itemId: 'loc_apt' }, body: { tipo: 'CURP' }, files: [archivo], usuarioToken: { sub: 'u-1' } } as unknown as Request,
|
|
res,
|
|
)
|
|
|
|
// Cliente mínimo creado con el nombre de la fila local (no truena por obtenerNombreItem)
|
|
expect(prismaMock.cliente.create).toHaveBeenCalledWith({
|
|
data: expect.objectContaining({ nombreCompleto: 'Luis Demo', mondayApartadoId: 'loc_apt' }),
|
|
})
|
|
expect(prismaMock.documento.create).toHaveBeenCalledWith({
|
|
data: expect.objectContaining({ clienteId: 'cli-local', tipo: 'CURP' }),
|
|
})
|
|
// El checkbox no se intenta en Monday: queda encolado para el worker
|
|
expect(prismaMock.syncPendiente.create).toHaveBeenCalledWith({
|
|
data: expect.objectContaining({
|
|
tipo: 'CHECKBOX_DOC',
|
|
payloadJson: JSON.stringify({ checkbox: 'CURP', itemId: 'loc_apt' }),
|
|
referenciaLocal: 'loc_apt',
|
|
}),
|
|
})
|
|
expect(status).toHaveBeenCalledWith(201)
|
|
})
|
|
})
|