feat(purga): Monday eliminado completamente del sistema
14 servicios eliminados, 7 controllers local-only, crons locales, config sin MONDAY_*, frontend sin banners/avisos Monday. 552 tests API + 7 apps construidas.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { PlanPago } from './cobranza.service.js'
|
||||
import type { LeadMonday, OportunidadMonday, Unidad } from './monday.service.js'
|
||||
import { AppError } from '../utils/errors.js'
|
||||
import type { LeadFila, OportunidadFila, Unidad } from './reportes.service.js'
|
||||
|
||||
// ---------- Prisma mockeado (los tests no tocan la BD) ----------
|
||||
|
||||
@@ -22,10 +21,9 @@ vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
||||
import { cargarDatosLocalesPrisma, ReportesService, type DatosLocalesReportes } from './reportes.service.js'
|
||||
|
||||
/**
|
||||
* Fallback de reportes (modo local): Monday caído o 502 → el resumen se
|
||||
* calcula desde tablas locales (LeadCaptura por board, Unidad por status,
|
||||
* PlanPago como siempre). `parcial: false` porque en modo local los datos
|
||||
* son completos (no parciales).
|
||||
* Reportes 100% locales: el resumen se calcula desde tablas locales
|
||||
* (LeadCaptura por board, Unidad por status, PlanPago como siempre).
|
||||
* `parcial: false` porque los datos locales son completos.
|
||||
*/
|
||||
|
||||
function pago(id: string, fechaVencimiento: string, monto: number, montoPagado = 0): PlanPago['pagos'][number] {
|
||||
@@ -50,7 +48,7 @@ function plan(id: string, overrides: Partial<PlanPago> = {}): PlanPago {
|
||||
}
|
||||
}
|
||||
|
||||
function leadMonday(id: string, estado = 'Nuevo'): LeadMonday {
|
||||
function leadFila(id: string, estado = 'Nuevo'): LeadFila {
|
||||
return {
|
||||
id,
|
||||
nombre: `Lead ${id}`,
|
||||
@@ -64,7 +62,7 @@ function leadMonday(id: string, estado = 'Nuevo'): LeadMonday {
|
||||
}
|
||||
}
|
||||
|
||||
function unidadMonday(codigo: string, status = 'DISPONIBLE'): Unidad {
|
||||
function unidadFila(codigo: string, status = 'DISPONIBLE'): Unidad {
|
||||
return {
|
||||
codigo,
|
||||
modelo: '1R',
|
||||
@@ -81,18 +79,8 @@ function unidadMonday(codigo: string, status = 'DISPONIBLE'): Unidad {
|
||||
}
|
||||
}
|
||||
|
||||
function mondayMock(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
getLeads: vi.fn(async () => [leadMonday('m1')]),
|
||||
getOportunidades: vi.fn(async () => [] as OportunidadMonday[]),
|
||||
getUnidades: vi.fn(async () => [unidadMonday('M-1')]),
|
||||
getActividadesAbiertas: vi.fn(async () => 3),
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
const datosLocales: DatosLocalesReportes = {
|
||||
leads: [leadMonday('loc_l1'), leadMonday('loc_l2', 'Descartado')],
|
||||
leads: [leadFila('loc_l1'), leadFila('loc_l2', 'Descartado')],
|
||||
oportunidades: [
|
||||
{
|
||||
id: 'loc_o1',
|
||||
@@ -111,7 +99,7 @@ const datosLocales: DatosLocalesReportes = {
|
||||
credito: 'Infonavit',
|
||||
},
|
||||
],
|
||||
unidades: [unidadMonday('U-1'), unidadMonday('U-2', 'APARTADO')],
|
||||
unidades: [unidadFila('U-1'), unidadFila('U-2', 'APARTADO')],
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -121,74 +109,62 @@ beforeEach(() => {
|
||||
prismaMock.actividadLocal.count.mockResolvedValue(0)
|
||||
})
|
||||
|
||||
describe('ReportesService (fallback local)', () => {
|
||||
it('Monday caído (salud): resumen desde datos locales con parcial: true, sin llamar a Monday', async () => {
|
||||
const monday = mondayMock()
|
||||
const svc = new ReportesService(monday, async () => [plan('a')], {
|
||||
salud: { estaDisponible: () => false },
|
||||
describe('ReportesService (resumen desde tablas locales)', () => {
|
||||
it('resumen desde datos locales: leads, oportunidades, unidades y planes en una respuesta', async () => {
|
||||
const svc = new ReportesService(async () => [plan('a')], {
|
||||
cargarDatosLocales: async () => datosLocales,
|
||||
})
|
||||
|
||||
const r = await svc.resumen()
|
||||
|
||||
expect(r.parcial).toBe(false) // modo local completo: datos no son parciales
|
||||
expect(r.parcial).toBe(false)
|
||||
expect(r.leads.total).toBe(2)
|
||||
expect(r.leads.porEstado).toEqual({ Nuevo: 1, Descartado: 1 })
|
||||
expect(r.oportunidades.total).toBe(1)
|
||||
expect(r.oportunidades.porEtapa).toEqual({ Afluencia: 1 })
|
||||
expect(r.unidades.total).toBe(2)
|
||||
expect(r.unidades.disponibles).toBe(1)
|
||||
expect(r.cobranza.planes.activos).toBe(1) // planes locales como siempre
|
||||
expect(r.cobranza.planes.activos).toBe(1)
|
||||
expect(r.actividadesAbiertas).toBe(0) // no hay actividades en el fixture
|
||||
expect(monday.getLeads).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('Monday vivo: camino normal intacto (sin parcial) y cache de 60 s', async () => {
|
||||
const monday = mondayMock()
|
||||
const cargarPlanes = vi.fn(async () => [plan('a')])
|
||||
let ahora = 1_000_000
|
||||
const svc = new ReportesService(monday, cargarPlanes, {
|
||||
now: () => ahora,
|
||||
salud: { estaDisponible: () => true },
|
||||
cargarDatosLocales: async () => datosLocales,
|
||||
})
|
||||
|
||||
const r1 = await svc.resumen()
|
||||
expect(r1.parcial).toBeUndefined()
|
||||
expect(r1.leads.total).toBe(1)
|
||||
expect(r1.actividadesAbiertas).toBe(3)
|
||||
|
||||
ahora += 30_000 // dentro del TTL: segunda llamada sale del cache
|
||||
const r2 = await svc.resumen()
|
||||
expect(r2).toBe(r1)
|
||||
expect(monday.getLeads).toHaveBeenCalledTimes(1)
|
||||
expect(cargarPlanes).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('Monday lanza AppError 502 a media lectura: cae al resumen local con parcial', async () => {
|
||||
const monday = mondayMock({ getLeads: vi.fn(async () => { throw new AppError(502, 'Monday no respondió') }) })
|
||||
const svc = new ReportesService(monday, async () => [], {
|
||||
salud: { estaDisponible: () => true },
|
||||
cargarDatosLocales: async () => datosLocales,
|
||||
it('cuenta actividades abiertas locales cuando el fixture las trae', async () => {
|
||||
const svc = new ReportesService(async () => [], {
|
||||
cargarDatosLocales: async () => ({ ...datosLocales, actividadesAbiertas: 3 }),
|
||||
})
|
||||
|
||||
const r = await svc.resumen()
|
||||
expect(r.parcial).toBe(false) // modo local completo: datos no son parciales
|
||||
expect(r.leads.total).toBe(2)
|
||||
|
||||
expect(r.actividadesAbiertas).toBe(3)
|
||||
})
|
||||
|
||||
it('otro error de Monday se propaga (no se traga nada)', async () => {
|
||||
const monday = mondayMock({ getLeads: vi.fn(async () => { throw new AppError(500, 'bug') }) })
|
||||
const svc = new ReportesService(monday, async () => [], {
|
||||
salud: { estaDisponible: () => true },
|
||||
cargarDatosLocales: async () => datosLocales,
|
||||
it('usa el cargador local por defecto (cargarDatosLocalesPrisma) cuando no se inyecta', async () => {
|
||||
const svc = new ReportesService(async () => [])
|
||||
|
||||
const r = await svc.resumen()
|
||||
|
||||
// Con las tablas vacías el resumen sale en ceros pero completo
|
||||
expect(r.parcial).toBe(false)
|
||||
expect(r.leads.total).toBe(0)
|
||||
expect(r.oportunidades.total).toBe(0)
|
||||
expect(r.unidades.total).toBe(0)
|
||||
expect(prismaMock.leadCaptura.findMany).toHaveBeenCalled()
|
||||
expect(prismaMock.unidad.findMany).toHaveBeenCalled()
|
||||
expect(prismaMock.actividadLocal.count).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('un error del cargador local se propaga (no se traga nada)', async () => {
|
||||
const svc = new ReportesService(async () => [], {
|
||||
cargarDatosLocales: async () => {
|
||||
throw new Error('bug')
|
||||
},
|
||||
})
|
||||
await expect(svc.resumen()).rejects.toThrow('bug')
|
||||
})
|
||||
})
|
||||
|
||||
describe('cargarDatosLocalesPrisma (mapeo LeadCaptura/Unidad → DTOs Monday)', () => {
|
||||
it('mapea boards a LeadMonday/OportunidadMonday con el perfil de datosJson y unidades al DTO', async () => {
|
||||
describe('cargarDatosLocalesPrisma (mapeo LeadCaptura/Unidad → DTOs del embudo)', () => {
|
||||
it('mapea boards a leads/oportunidades con el perfil de datosJson y unidades al DTO', async () => {
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||
{
|
||||
leadId: 'loc_l1',
|
||||
|
||||
Reference in New Issue
Block a user