feat(pendientes): cotizador (mensualidades, bloqueo de unidad, totales, numMensualidades), cobranza (recibos, atrasos, mora configurable, recordatorios), soporte (adjuntos, filtros, estados), clientes maestro (catalogos reales), contactos (ficha y duplicados) y permisos de captura por rol
This commit is contained in:
@@ -118,6 +118,99 @@ describe('listarContactosLocal', () => {
|
||||
expect(lista[0]?.id).toBe('c1')
|
||||
})
|
||||
|
||||
it('deduplica por nombre a los registros sin teléfono ni email (gana la ficha con más datos)', async () => {
|
||||
// Alta fallida: Cliente hueco (payload vacío, sin teléfono ni email) con el
|
||||
// mismo nombre que su ficha real
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
clienteFake({
|
||||
id: 'hueco',
|
||||
nombreCompleto: 'LUIS ENRIQUE SALGADO ZAVALETA',
|
||||
payloadJson: '{}',
|
||||
createdAt: new Date('2026-09-11T10:00:00'),
|
||||
}),
|
||||
clienteFake({
|
||||
id: 'completo',
|
||||
nombreCompleto: 'Luis Enrique Salgado Zavaleta',
|
||||
celular: '+52 5586880444',
|
||||
email: 'luisenriquesalgadozavaleta@gmail.com',
|
||||
curp: 'SAZL990504HDFLVS06',
|
||||
rfc: 'SAZL990504KY0',
|
||||
proyecto: 'SOLANA',
|
||||
unidadCodigo: '9',
|
||||
payloadJson: '{"datosGenerales":{"estadoCivil":"SOLTERO"}}',
|
||||
}),
|
||||
])
|
||||
|
||||
const lista = (await listarContactosLocal()).contactos
|
||||
|
||||
expect(lista).toHaveLength(1)
|
||||
expect(lista[0]).toMatchObject({
|
||||
id: 'completo',
|
||||
nombre: 'Luis Enrique Salgado Zavaleta',
|
||||
telefono: '+52 5586880444',
|
||||
email: 'luisenriquesalgadozavaleta@gmail.com',
|
||||
estado: 'Cliente',
|
||||
})
|
||||
})
|
||||
|
||||
it('fusiona el registro hueco aunque llegue después de la ficha completa', async () => {
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
clienteFake({
|
||||
id: 'completo',
|
||||
nombreCompleto: 'GRACIELA BAUTISTA VELASCO',
|
||||
celular: '+52 6641159092',
|
||||
email: 'gbautista1304@gmail.com',
|
||||
}),
|
||||
clienteFake({ id: 'hueco', nombreCompleto: 'Graciela Bautista Velasco', payloadJson: '{}' }),
|
||||
])
|
||||
|
||||
const lista = (await listarContactosLocal()).contactos
|
||||
expect(lista).toHaveLength(1)
|
||||
expect(lista[0]?.id).toBe('completo')
|
||||
})
|
||||
|
||||
it('no fusiona a dos personas distintas con el mismo nombre si tienen teléfonos distintos', async () => {
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
clienteFake({ id: 'c1', nombreCompleto: 'JUAN PÉREZ', celular: '4771234567' }),
|
||||
clienteFake({ id: 'c2', nombreCompleto: 'JUAN PÉREZ', celular: '6649998877' }),
|
||||
])
|
||||
|
||||
const lista = (await listarContactosLocal()).contactos
|
||||
expect(lista.map((c) => c.id).sort()).toEqual(['c1', 'c2'])
|
||||
})
|
||||
|
||||
it('un Cliente hueco se fusiona con la captura del mismo nombre (gana la que trae más datos)', async () => {
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
clienteFake({
|
||||
id: 'hueco',
|
||||
nombreCompleto: 'Guadalupe',
|
||||
curp: '',
|
||||
rfc: '',
|
||||
payloadJson: '{}',
|
||||
createdAt: new Date('2026-09-15T10:00:00'),
|
||||
}),
|
||||
])
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||
capturaFake({
|
||||
leadId: 'loc_g',
|
||||
nombreLead: 'Guadalupe',
|
||||
telefono: '+52 664 222 6090',
|
||||
email: 'lupisintj1@hotmail.com',
|
||||
etapa: 'Contactado',
|
||||
}),
|
||||
])
|
||||
|
||||
const lista = (await listarContactosLocal()).contactos
|
||||
|
||||
expect(lista).toHaveLength(1)
|
||||
expect(lista[0]).toMatchObject({
|
||||
id: 'loc_g',
|
||||
telefono: '+52 664 222 6090',
|
||||
email: 'lupisintj1@hotmail.com',
|
||||
estado: 'Contactado',
|
||||
})
|
||||
})
|
||||
|
||||
it('pagina de 100 en 100 con hayMas (la página 2 trae el siguiente bloque)', async () => {
|
||||
// 101 clientes: la página 1 llena 100 y detecta que hay más
|
||||
prismaMock.cliente.findMany.mockResolvedValue(
|
||||
@@ -233,4 +326,110 @@ describe('detalleContactoLocal', () => {
|
||||
expect(detalle.relaciones.oportunidades).toHaveLength(1)
|
||||
expect(detalle.historial.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('completa el perfil con el payloadJson del alta (estado civil, nacimiento, actividad, dirección)', async () => {
|
||||
prismaMock.cliente.findFirst.mockResolvedValue(
|
||||
clienteFake({
|
||||
id: 'c1',
|
||||
nombreCompleto: 'LUIS ENRIQUE SALGADO ZAVALETA',
|
||||
celular: '5586880444',
|
||||
email: 'luis@correo.mx',
|
||||
curp: '',
|
||||
rfc: '',
|
||||
payloadJson: JSON.stringify({
|
||||
datosGenerales: {
|
||||
estadoCivil: 'SOLTERO',
|
||||
fechaNacimiento: '1999-05-04',
|
||||
actividadOcupacion: 'PILOTO AVIADOR',
|
||||
direccion: 'C.SAN FELIPE EJIDO FRANCISCO VILLA',
|
||||
},
|
||||
empleo: { compania: 'FRASA', direccion: 'AV COLINA DE SAN PABLO' },
|
||||
// El co-acreditado repite los campos vacíos: no debe pisar los reales
|
||||
coacreditado: { tieneCoacreditado: false, generales: { estadoCivil: '', fechaNacimiento: '', actividadOcupacion: '', direccion: '' } },
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
const detalle = await detalleContactoLocal('c1')
|
||||
expect(detalle?.contacto).toMatchObject({
|
||||
estadoCivil: 'SOLTERO',
|
||||
fechaNacimiento: '1999-05-04',
|
||||
actividadEconomica: 'PILOTO AVIADOR',
|
||||
direccion: 'C.SAN FELIPE EJIDO FRANCISCO VILLA',
|
||||
})
|
||||
})
|
||||
|
||||
it('toma el perfilamiento del datosJson de la captura cuando no hay Cliente', async () => {
|
||||
const captura = capturaFake({
|
||||
leadId: 'loc_1',
|
||||
nombreLead: 'RAMON MORALES',
|
||||
telefono: '6643535353',
|
||||
email: 'ramon.morales@gmail.com',
|
||||
datosJson: JSON.stringify({
|
||||
edad: 41,
|
||||
estadoCivil: 'Casado(a)',
|
||||
ocupacion: 'empleado',
|
||||
rangoSalarial: '50000',
|
||||
}),
|
||||
})
|
||||
prismaMock.leadCaptura.findUnique.mockResolvedValue(captura)
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([captura])
|
||||
|
||||
const detalle = await detalleContactoLocal('loc_1')
|
||||
expect(detalle?.contacto).toMatchObject({
|
||||
nombre: 'RAMON MORALES',
|
||||
estadoCivil: 'Casado(a)',
|
||||
actividadEconomica: 'empleado',
|
||||
})
|
||||
// No se inventa fecha de nacimiento a partir de la edad
|
||||
expect(detalle?.contacto['fechaNacimiento']).toBe('')
|
||||
expect(detalle?.contacto['direccion']).toBe('')
|
||||
})
|
||||
|
||||
it('incluye los documentos del registro hueco de la misma persona (no se pierden al fusionar)', async () => {
|
||||
prismaMock.cliente.findFirst.mockResolvedValue(
|
||||
clienteFake({
|
||||
id: 'c1',
|
||||
nombreCompleto: 'DEYSI LORENA ALEMAN FIGUEROA',
|
||||
celular: '6643998451',
|
||||
email: 'daleman@frasa.com.mx',
|
||||
}),
|
||||
)
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
{ id: 'hueco', nombreCompleto: 'DEYSI LORENA ALEMAN FIGUEROA', celular: null, email: null },
|
||||
{ id: 'otro', nombreCompleto: 'OTRA PERSONA', celular: null, email: null },
|
||||
])
|
||||
prismaMock.documento.findMany.mockResolvedValue([
|
||||
{ id: 'd1', tipo: 'INE', createdAt: new Date('2026-09-01T12:00:00') },
|
||||
])
|
||||
|
||||
const detalle = await detalleContactoLocal('c1')
|
||||
|
||||
expect(prismaMock.documento.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ where: { clienteId: { in: ['c1', 'hueco'] } } }),
|
||||
)
|
||||
expect(detalle?.historial.some((h) => h.detalle === 'Documento subido (INE)')).toBe(true)
|
||||
})
|
||||
|
||||
it('si la ficha visible es la captura, también se muestran los documentos de su Cliente hueco', async () => {
|
||||
prismaMock.leadCaptura.findUnique.mockResolvedValue(
|
||||
capturaFake({ leadId: 'loc_g', nombreLead: 'Guadalupe', telefono: '6642226090', etapa: 'Contactado' }),
|
||||
)
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||
capturaFake({ leadId: 'loc_g', nombreLead: 'Guadalupe', telefono: '6642226090', etapa: 'Contactado' }),
|
||||
])
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
{ id: 'hueco', nombreCompleto: 'Guadalupe', celular: null, email: null },
|
||||
])
|
||||
prismaMock.documento.findMany.mockResolvedValue([
|
||||
{ id: 'd1', tipo: 'INE', createdAt: new Date('2026-09-01T12:00:00') },
|
||||
])
|
||||
|
||||
const detalle = await detalleContactoLocal('loc_g')
|
||||
|
||||
expect(prismaMock.documento.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ where: { clienteId: { in: ['hueco'] } } }),
|
||||
)
|
||||
expect(detalle?.historial.some((h) => h.detalle === 'Documento subido (INE)')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user