fix(auditoria completa): bloqueos de Mi Dia, cobranza (saldos y fechas), captura (departamento y rol), contactos (privacidad), cotizador (QR) y validaciones de API

This commit is contained in:
2026-09-21 07:18:39 +00:00
parent 75956a709f
commit 5e9b2ac456
16 changed files with 259 additions and 71 deletions

View File

@@ -364,7 +364,29 @@ export function resumenMiDiaLocal(
pendienteSync: c.pendienteSync,
}))
// "Mis capturas" son sólo las altas del tablero Leads (antes listaba TODAS las
// filas del usuario, así que el mismo cliente aparecía 2-3 veces con etapas
// de oportunidad/apartado).
// Todos los apartados, incluidos los terminales (Escrituración y cancelados)
const todosApartadosLocal: ApartadoMiDia[] = apartadosLocales
.filter((c) => c.board === 'APARTADO')
.map((c) => ({
id: c.leadId,
nombre: c.nombreLead,
telefono: c.telefono ?? '',
email: c.email ?? '',
origen: c.origen ?? '',
desarrollo: c.desarrollo ?? '',
vendedor: nombrePorUsuarioId.get(c.usuarioId) ?? '',
unidadCodigo: unidadPorApartadoId.get(c.leadId) ?? '',
estado: c.etapa,
fechaLimite: c.fechaLimite ? c.fechaLimite.toISOString().slice(0, 10) : '',
ultimoCambio: null,
pendienteSync: c.pendienteSync,
}))
const misCapturas: CapturaMiDia[] = capturas
.filter((c) => c.board === 'LEADS')
.map((c) => ({
leadId: c.leadId,
nombre: c.nombreLead,
@@ -375,13 +397,26 @@ export function resumenMiDiaLocal(
pendienteSync: c.pendienteSync,
}))
.sort((a, b) => b.capturadoEn.localeCompare(a.capturadoEn))
// Excluir de "Mis capturas" los que ya aparecen en "Mis leads" u "Mis
// oportunidades" para no duplicar visualmente.
// Excluir de "Mis capturas" los que ya aparecen en cualquiera de los otros
// tabs (por id y por cliente, para no repetir a la misma persona cuando su
// lead se convirtió en oportunidad o apartado).
const claveCliente = (nombre: string, telefono: string): string =>
`${nombre.trim().toLowerCase()}|${(telefono ?? '').replace(/\D/g, '').slice(-10)}`
const idsEnOtrosTabsLocal = new Set([
...misLeads.map((l) => l.id),
...itemsOportunidades.map((o) => o.id),
...misApartados.map((a) => a.id),
...todosApartadosLocal.map((a) => a.id),
])
const misCapturasSinDuplicadosLocal = misCapturas.filter((c) => !idsEnOtrosTabsLocal.has(c.leadId))
const clavesEnOtrosTabs = new Set([
...misLeads.map((l) => claveCliente(l.nombre, l.telefono)),
...itemsOportunidades.map((o) => claveCliente(o.nombre, o.telefono)),
...misApartados.map((a) => claveCliente(a.nombre, a.telefono)),
...todosApartadosLocal.map((a) => claveCliente(a.nombre, a.telefono)),
])
const misCapturasSinDuplicadosLocal = misCapturas.filter(
(c) => !idsEnOtrosTabsLocal.has(c.leadId) && !clavesEnOtrosTabs.has(claveCliente(c.nombre, c.telefono)),
)
const etapaApartadoPorId = new Map(apartadosLocales.map((a) => [a.leadId, a.etapa]))
const misClientes: ClienteMiDia[] = clientes
@@ -505,22 +540,7 @@ export function resumenMiDiaLocal(
misOportunidades: { porEtapa, items: itemsOportunidades },
misApartados,
// Todos los apartados incluyendo terminales (para Escrituración)
todosApartados: apartadosLocales
.filter((c) => c.board === 'APARTADO')
.map((c) => ({
id: c.leadId,
nombre: c.nombreLead,
telefono: c.telefono ?? '',
email: c.email ?? '',
origen: c.origen ?? '',
desarrollo: c.desarrollo ?? '',
vendedor: nombrePorUsuarioId.get(c.usuarioId) ?? '',
unidadCodigo: unidadPorApartadoId.get(c.leadId) ?? '',
estado: c.etapa,
fechaLimite: c.fechaLimite ? c.fechaLimite.toISOString().slice(0, 10) : '',
ultimoCambio: null,
pendienteSync: c.pendienteSync,
})),
todosApartados: todosApartadosLocal,
conteos: {
leadsNuevosSemana: [...leadsLocales, ...oportunidadesLocales].filter(
(c) => c.createdAt.getTime() >= hace7DiasMs,