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:
2026-09-26 22:00:29 +00:00
parent ea03e6eeba
commit 23d288d0aa
2 changed files with 40 additions and 3 deletions

View File

@@ -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 () => {

View File

@@ -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')