feat(modo-local): CRM 100% local sin dependencia de Monday.com
Nuevas tablas: - ActividadLocal (historial de llamadas, reuniones, tareas) - AsignacionLocal (round-robin local) - fechaLimite en LeadCaptura Servicios nuevos: - actividad-local.service.ts (CRUD actividades) - local-canal.ts (canal de envío para recordatorios) - seed-modo-local.js (activar modo local) Mi día local mejorado: - Mis actividades hoy (filtros usuario/fecha/estado) - Apartados por vencer (con fechaLimite real) - Equipo (contadores por vendedor, solo coordinador/admin) - unidadCodigo en oportunidades desde datosJson Automatizaciones locales: - Round-robin: asigna leads sin vendedor + crea actividad - Inactividad: detecta oportunidades >10 días sin update - Escalado: alerta oportunidades >48h sin contacto Reportes: actividadesAbiertas + parcial:false en modo local Cron wiring: crons locales cuando Monday caído 661 tests verdes.
This commit is contained in:
@@ -12,6 +12,9 @@ const prismaMock = vi.hoisted(() => ({
|
||||
unidad: {
|
||||
findMany: vi.fn(),
|
||||
},
|
||||
actividadLocal: {
|
||||
count: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
||||
@@ -21,7 +24,8 @@ import { cargarDatosLocalesPrisma, ReportesService, type DatosLocalesReportes }
|
||||
/**
|
||||
* 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) con el flag aditivo `parcial: true`.
|
||||
* PlanPago como siempre). `parcial: false` porque en modo local los datos
|
||||
* son completos (no parciales).
|
||||
*/
|
||||
|
||||
function pago(id: string, fechaVencimiento: string, monto: number, montoPagado = 0): PlanPago['pagos'][number] {
|
||||
@@ -114,6 +118,7 @@ beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([])
|
||||
prismaMock.unidad.findMany.mockResolvedValue([])
|
||||
prismaMock.actividadLocal.count.mockResolvedValue(0)
|
||||
})
|
||||
|
||||
describe('ReportesService (fallback local)', () => {
|
||||
@@ -126,7 +131,7 @@ describe('ReportesService (fallback local)', () => {
|
||||
|
||||
const r = await svc.resumen()
|
||||
|
||||
expect(r.parcial).toBe(true)
|
||||
expect(r.parcial).toBe(false) // modo local completo: datos no son parciales
|
||||
expect(r.leads.total).toBe(2)
|
||||
expect(r.leads.porEstado).toEqual({ Nuevo: 1, Descartado: 1 })
|
||||
expect(r.oportunidades.total).toBe(1)
|
||||
@@ -134,7 +139,7 @@ describe('ReportesService (fallback local)', () => {
|
||||
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.actividadesAbiertas).toBe(0) // no hay tabla local de actividades
|
||||
expect(r.actividadesAbiertas).toBe(0) // no hay actividades en el fixture
|
||||
expect(monday.getLeads).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -168,7 +173,7 @@ describe('ReportesService (fallback local)', () => {
|
||||
})
|
||||
|
||||
const r = await svc.resumen()
|
||||
expect(r.parcial).toBe(true)
|
||||
expect(r.parcial).toBe(false) // modo local completo: datos no son parciales
|
||||
expect(r.leads.total).toBe(2)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user