fix(mi-dia): admin reasigna/mueve leads, capturista real en cards, datos en apartados, INE obligatoria y bitácora persistente

This commit is contained in:
2026-09-17 06:54:57 +00:00
parent b99707689f
commit 31f83b0504
11 changed files with 162 additions and 14 deletions

View File

@@ -80,15 +80,21 @@ describe('resumenMiDiaLocal', () => {
expect(r.equipo).toBeUndefined()
})
it('ultimoCambio viene del updatedAt local y el quién es el capturista ("app" si no hay nombre)', () => {
it('ultimoCambio viene del updatedAt local y el quién es el capturista REAL de la fila (no el que ve)', () => {
const capturas = [fila({ leadId: 'loc_l1', board: 'LEADS', updatedAt: new Date('2026-09-05T08:00:00') })]
const r = resumenMiDiaLocal(vendedor, capturas, [], AHORA)
// El capturista de la fila (u1) es otro usuario distinto del que consulta
const usuarios = [{ id: 'u1', nombre: 'Martín Zavala', rol: 'vendedor', activo: true }]
const r = resumenMiDiaLocal(vendedor, capturas, [], AHORA, [], usuarios)
expect(r.misLeads[0]?.ultimoCambio).toEqual({
fechaISO: '2026-09-05T08:00:00.000Z',
usuario: 'Martin Zavala',
usuario: 'Martín Zavala',
detalle: 'Actualización',
})
// Sin la lista de usuarios (o el capturista fuera de ella) → 'app'
const rSinUsuarios = resumenMiDiaLocal(vendedor, capturas, [], AHORA)
expect(rSinUsuarios.misLeads[0]?.ultimoCambio?.usuario).toBe('app')
const rSinNombre = resumenMiDiaLocal(sinNombre, capturas, [], AHORA)
expect(rSinNombre.misLeads[0]?.ultimoCambio?.usuario).toBe('app')
})

View File

@@ -135,6 +135,10 @@ export interface OportunidadMiDia {
export interface ApartadoMiDia {
id: string
nombre: string
telefono: string
email: string
origen: string
desarrollo: string
unidadCodigo: string
estado: string
fechaLimite: string // 'YYYY-MM-DD' ('' si no tiene)
@@ -268,6 +272,9 @@ export function resumenMiDiaLocal(
): ResumenMiDia {
const hace7DiasMs = ahora.getTime() - 7 * MS_DIA
// Resolución del capturista real de cada fila (no el usuario que ve la pantalla)
const nombrePorUsuarioId = new Map(usuarios.map((u) => [u.id, u.nombre]))
const leadsLocales = capturas.filter((c) => c.board === 'LEADS')
const oportunidadesLocales = capturas.filter((c) => c.board === 'OPORTUNIDADES')
const apartadosLocales = capturas.filter((c) => c.board === 'APARTADO')
@@ -285,7 +292,7 @@ export function resumenMiDiaLocal(
desarrollo: c.desarrollo ?? '',
updatedAt: c.updatedAt.toISOString(),
fuente: 'lead' as const,
ultimoCambio: ultimoCambioLocal(c, usuario.nombre),
ultimoCambio: ultimoCambioLocal(c, nombrePorUsuarioId.get(c.usuarioId) ?? ''),
pendienteSync: c.pendienteSync,
}))
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
@@ -321,7 +328,7 @@ export function resumenMiDiaLocal(
modeloInteres: c.modelo ?? '',
unidadCodigo,
updatedAt: c.updatedAt.toISOString(),
ultimoCambio: ultimoCambioLocal(c, usuario.nombre),
ultimoCambio: ultimoCambioLocal(c, nombrePorUsuarioId.get(c.usuarioId) ?? ''),
fechaCita,
pendienteSync: c.pendienteSync,
}
@@ -339,10 +346,14 @@ export function resumenMiDiaLocal(
.map((c) => ({
id: c.leadId,
nombre: c.nombreLead,
telefono: c.telefono ?? '',
email: c.email ?? '',
origen: c.origen ?? '',
desarrollo: c.desarrollo ?? '',
unidadCodigo: unidadPorApartadoId.get(c.leadId) ?? '',
estado: c.etapa,
fechaLimite: c.fechaLimite ? c.fechaLimite.toISOString().slice(0, 10) : '',
ultimoCambio: ultimoCambioLocal(c, usuario.nombre),
ultimoCambio: ultimoCambioLocal(c, nombrePorUsuarioId.get(c.usuarioId) ?? ''),
pendienteSync: c.pendienteSync,
}))
@@ -492,6 +503,10 @@ export function resumenMiDiaLocal(
.map((c) => ({
id: c.leadId,
nombre: c.nombreLead,
telefono: c.telefono ?? '',
email: c.email ?? '',
origen: c.origen ?? '',
desarrollo: c.desarrollo ?? '',
unidadCodigo: unidadPorApartadoId.get(c.leadId) ?? '',
estado: c.etapa,
fechaLimite: c.fechaLimite ? c.fechaLimite.toISOString().slice(0, 10) : '',