- Circuit breaker saludMonday (3 fallos → modo local; ping cada 60s)
- Endpoint GET /api/sistema/estado {mondayOk, pendientesSync}
- LeadCaptura extendida (board, etapa, origen, motivo, pendienteSync)
- Tablas Unidad (espejo inventario, 288 seed) + SyncPendiente
- crmLocal: crearLead, afluencia, cambio etapa, descarte, conversión, apartar
- Ruteo en controllers: fallback local ante 502 o Monday caído
- Mi día local: resumen completo desde SQLite + unión con Monday
- Fallbacks: unidades, contactos, reportes, prefill, buscador
- Sync worker cada 2min + handlers 15 tipos (con DependenciaPendienteError)
- Gate automatizaciones: skip si !mondayOk
- Banner 'Modo local' en 5 apps + badge pendiente en mi-dia
- 597 API tests + 263 front tests verdes
Env MODO_LOCAL_FORZADO=true para testing sin Monday.
86 lines
3.4 KiB
TypeScript
86 lines
3.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { notificarEscrituracionSiLiquidado, resolverFiltroMios } from './planes.controller.js'
|
|
|
|
// ---------- Prisma mockeado: el encolado de la réplica no toca la BD ----------
|
|
|
|
const prismaMock = vi.hoisted(() => ({
|
|
syncPendiente: {
|
|
create: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
|
|
|
/** Monday mockeado: registra cambios de columna */
|
|
function mondayMock() {
|
|
return {
|
|
cambios: [] as { boardId: string; itemId: string; columna: string; valor: unknown }[],
|
|
async cambiarValorColumna(boardId: string, itemId: string, columna: string, valor: string | number | boolean) {
|
|
this.cambios.push({ boardId, itemId, columna, valor })
|
|
},
|
|
}
|
|
}
|
|
|
|
describe('notificarEscrituracionSiLiquidado (puente cobranza → postventa)', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
prismaMock.syncPendiente.create.mockResolvedValue({})
|
|
})
|
|
|
|
it('plan LIQUIDADO con mondayItemId: mueve el apartado a "X Escriturar"', async () => {
|
|
const monday = mondayMock()
|
|
const ok = await notificarEscrituracionSiLiquidado({ mondayItemId: 'apt-55' }, true, monday)
|
|
expect(ok).toBe(true)
|
|
// board 666 = MONDAY_BOARD_APARTADO de test
|
|
expect(monday.cambios).toEqual([
|
|
{ boardId: '666', itemId: 'apt-55', columna: 'Estado', valor: 'X Escriturar' },
|
|
])
|
|
})
|
|
|
|
it('no hace nada si el plan no está liquidado o no tiene item ligado', async () => {
|
|
const monday = mondayMock()
|
|
expect(await notificarEscrituracionSiLiquidado({ mondayItemId: 'apt-55' }, false, monday)).toBe(false)
|
|
expect(await notificarEscrituracionSiLiquidado({ mondayItemId: null }, true, monday)).toBe(false)
|
|
expect(await notificarEscrituracionSiLiquidado({}, true, monday)).toBe(false)
|
|
expect(monday.cambios).toEqual([])
|
|
})
|
|
|
|
it('un fallo de Monday no rompe el abono: loguea, encola LIQUIDADO_ESCRITURAR y devuelve false', async () => {
|
|
const monday = mondayMock()
|
|
vi.spyOn(monday, 'cambiarValorColumna').mockRejectedValueOnce(new Error('Monday caído'))
|
|
const spyError = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const ok = await notificarEscrituracionSiLiquidado({ mondayItemId: 'apt-55' }, true, monday)
|
|
expect(ok).toBe(false)
|
|
expect(spyError).toHaveBeenCalled()
|
|
// La escrituración queda encolada para que el worker la aplique al volver Monday
|
|
expect(prismaMock.syncPendiente.create).toHaveBeenCalledWith({
|
|
data: expect.objectContaining({
|
|
tipo: 'LIQUIDADO_ESCRITURAR',
|
|
payloadJson: JSON.stringify({ mondayItemId: 'apt-55' }),
|
|
referenciaLocal: 'apt-55',
|
|
}),
|
|
})
|
|
spyError.mockRestore()
|
|
})
|
|
})
|
|
|
|
describe('resolverFiltroMios (?mios=1)', () => {
|
|
it('sin ?mios=1 no filtra nada', () => {
|
|
expect(resolverFiltroMios(undefined, { sub: 'u1', rol: 'vendedor' })).toBeUndefined()
|
|
expect(resolverFiltroMios('0', { sub: 'u1', rol: 'vendedor' })).toBeUndefined()
|
|
})
|
|
|
|
it('vendedor con ?mios=1 → filtra por su sub', () => {
|
|
expect(resolverFiltroMios('1', { sub: 'u1', rol: 'vendedor' })).toEqual({ vendedorId: 'u1' })
|
|
})
|
|
|
|
it('admin y coordinador ven todo aunque pidan ?mios=1', () => {
|
|
expect(resolverFiltroMios('1', { sub: 'u1', rol: 'admin' })).toBeUndefined()
|
|
expect(resolverFiltroMios('1', { sub: 'u1', rol: 'coordinador' })).toBeUndefined()
|
|
})
|
|
|
|
it('sin token (AUTH_ENABLED=false) el filtro no aplica', () => {
|
|
expect(resolverFiltroMios('1', undefined)).toBeUndefined()
|
|
})
|
|
})
|