feat: columna Asesor en export CSV de contactos
Nombre del usuario que dio de alta cada contacto (cliente: capturadoPor; lead/oportunidad/apartado: usuarioId). Resuelto con un solo findMany de Usuario a mapa, sin N+1.
This commit is contained in:
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
const prismaMock = vi.hoisted(() => ({
|
const prismaMock = vi.hoisted(() => ({
|
||||||
cliente: { findMany: vi.fn() },
|
cliente: { findMany: vi.fn() },
|
||||||
leadCaptura: { findMany: vi.fn() },
|
leadCaptura: { findMany: vi.fn() },
|
||||||
|
usuario: { findMany: vi.fn() },
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
||||||
@@ -13,12 +14,42 @@ beforeEach(() => {
|
|||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
prismaMock.cliente.findMany.mockResolvedValue([])
|
prismaMock.cliente.findMany.mockResolvedValue([])
|
||||||
prismaMock.leadCaptura.findMany.mockResolvedValue([])
|
prismaMock.leadCaptura.findMany.mockResolvedValue([])
|
||||||
|
prismaMock.usuario.findMany.mockResolvedValue([{ id: 'u-1', username: 'alfonso castillo' }])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('exportarCSV', () => {
|
describe('exportarCSV', () => {
|
||||||
it('genera CSV con encabezados cuando no hay contactos', async () => {
|
it('genera CSV con encabezados cuando no hay contactos', async () => {
|
||||||
const csv = await exportarCSV()
|
const csv = await exportarCSV()
|
||||||
expect(csv).toContain('Nombre,Teléfono,Email,Desarrollo,Estatus,Última actividad')
|
expect(csv).toContain('Nombre,Teléfono,Email,Desarrollo,Estatus,Última actividad,Asesor')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('incluye el nombre del asesor que dio de alta el contacto', async () => {
|
||||||
|
prismaMock.cliente.findMany.mockResolvedValue([
|
||||||
|
{
|
||||||
|
nombreCompleto: 'Juan Pérez',
|
||||||
|
celular: '4771234567',
|
||||||
|
email: null,
|
||||||
|
proyecto: 'SENDA',
|
||||||
|
createdAt: new Date('2026-09-01'),
|
||||||
|
capturadoPorId: 'u-1',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||||
|
{
|
||||||
|
nombreLead: 'Ana López',
|
||||||
|
telefono: '6649998877',
|
||||||
|
email: null,
|
||||||
|
desarrollo: 'DIAGONAL',
|
||||||
|
etapa: 'NUEVO',
|
||||||
|
updatedAt: new Date('2026-09-05'),
|
||||||
|
usuarioId: 'u-1',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const csv = await exportarCSV()
|
||||||
|
const lineas = csv.split('\n')
|
||||||
|
expect(lineas[1]).toContain('alfonso castillo')
|
||||||
|
expect(lineas[2]).toContain('alfonso castillo')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('incluye clientes y capturas en el CSV', async () => {
|
it('incluye clientes y capturas en el CSV', async () => {
|
||||||
|
|||||||
@@ -447,6 +447,9 @@ export async function exportarCSV(
|
|||||||
},
|
},
|
||||||
orderBy: { updatedAt: 'desc' },
|
orderBy: { updatedAt: 'desc' },
|
||||||
})
|
})
|
||||||
|
// Nombre del asesor que dio de alta cada contacto (para la columna Asesor)
|
||||||
|
const usuarios = await prisma.usuario.findMany({ select: { id: true, username: true } })
|
||||||
|
const nombreAsesor = new Map(usuarios.map((u) => [u.id, u.username]))
|
||||||
|
|
||||||
interface FilaCSV {
|
interface FilaCSV {
|
||||||
Nombre: string
|
Nombre: string
|
||||||
@@ -455,6 +458,7 @@ export async function exportarCSV(
|
|||||||
Desarrollo: string
|
Desarrollo: string
|
||||||
Estatus: string
|
Estatus: string
|
||||||
'Última actividad': string
|
'Última actividad': string
|
||||||
|
Asesor: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const filas: FilaCSV[] = []
|
const filas: FilaCSV[] = []
|
||||||
@@ -476,6 +480,7 @@ export async function exportarCSV(
|
|||||||
Desarrollo: c.proyecto,
|
Desarrollo: c.proyecto,
|
||||||
Estatus: 'Cliente',
|
Estatus: 'Cliente',
|
||||||
'Última actividad': c.createdAt.toISOString().slice(0, 10),
|
'Última actividad': c.createdAt.toISOString().slice(0, 10),
|
||||||
|
Asesor: c.capturadoPorId ? (nombreAsesor.get(c.capturadoPorId) ?? '') : '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +496,7 @@ export async function exportarCSV(
|
|||||||
Desarrollo: cap.desarrollo ?? '',
|
Desarrollo: cap.desarrollo ?? '',
|
||||||
Estatus: cap.etapa,
|
Estatus: cap.etapa,
|
||||||
'Última actividad': cap.updatedAt.toISOString().slice(0, 10),
|
'Última actividad': cap.updatedAt.toISOString().slice(0, 10),
|
||||||
|
Asesor: cap.usuarioId ? (nombreAsesor.get(cap.usuarioId) ?? '') : '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,10 +512,10 @@ export async function exportarCSV(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generar CSV
|
// Generar CSV
|
||||||
const encabezado = 'Nombre,Teléfono,Email,Desarrollo,Estatus,Última actividad'
|
const encabezado = 'Nombre,Teléfono,Email,Desarrollo,Estatus,Última actividad,Asesor'
|
||||||
const escape = (s: string) => (s.includes(',') || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s)
|
const escape = (s: string) => (s.includes(',') || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s)
|
||||||
const lineas = filtradas.map((f) =>
|
const lineas = filtradas.map((f) =>
|
||||||
[f.Nombre, f.Teléfono, f.Email, f.Desarrollo, f.Estatus, f['Última actividad']].map(escape).join(',')
|
[f.Nombre, f.Teléfono, f.Email, f.Desarrollo, f.Estatus, f['Última actividad'], f.Asesor].map(escape).join(',')
|
||||||
)
|
)
|
||||||
|
|
||||||
return [encabezado, ...lineas].join('\n')
|
return [encabezado, ...lineas].join('\n')
|
||||||
|
|||||||
Reference in New Issue
Block a user