From b1e408f435228a3b7a0a8aad8f77a5eaed6298a1 Mon Sep 17 00:00:00 2001 From: Consult AS Technology Date: Thu, 10 Sep 2026 22:55:11 +0000 Subject: [PATCH] fix(notas): notas persisten entre etapas via clienteClave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Campo clienteClave en RecordatorioVendedor (hash nombre+telefono) - Al crear nota: se calcula clienteClave del lead/cliente - Al buscar notas: busca por leadId O clienteClave (no por nombre suelto) - Mantiene compatibilidad: notas viejas sin clienteClave siguen por leadId - Migración SQL aplicada --- .../20260910120000_cliente_clave/migration.sql | 2 ++ api-gateway/prisma/schema.prisma | 1 + .../controllers/recordatorio-vendedor.controller.ts | 7 ++++++- .../src/services/recordatorio-vendedor.service.ts | 13 +++++++++++++ apps/mi-dia/src/components/DetalleLeadModal.tsx | 12 ++++++++++-- apps/mi-dia/src/services/recordatorios.ts | 4 +++- 6 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 api-gateway/prisma/migrations/20260910120000_cliente_clave/migration.sql diff --git a/api-gateway/prisma/migrations/20260910120000_cliente_clave/migration.sql b/api-gateway/prisma/migrations/20260910120000_cliente_clave/migration.sql new file mode 100644 index 0000000..685f0f8 --- /dev/null +++ b/api-gateway/prisma/migrations/20260910120000_cliente_clave/migration.sql @@ -0,0 +1,2 @@ +ALTER TABLE "RecordatorioVendedor" ADD COLUMN "clienteClave" TEXT; +CREATE INDEX IF NOT EXISTS "RecordatorioVendedor_clienteClave_idx" ON "RecordatorioVendedor"("clienteClave"); diff --git a/api-gateway/prisma/schema.prisma b/api-gateway/prisma/schema.prisma index c81da9f..d017e9c 100644 --- a/api-gateway/prisma/schema.prisma +++ b/api-gateway/prisma/schema.prisma @@ -420,6 +420,7 @@ model RecordatorioVendedor { id String @id @default(uuid()) usuarioId String leadId String? + clienteClave String? titulo String descripcion String? fechaRecordar DateTime diff --git a/api-gateway/src/controllers/recordatorio-vendedor.controller.ts b/api-gateway/src/controllers/recordatorio-vendedor.controller.ts index b06b5ca..b449748 100644 --- a/api-gateway/src/controllers/recordatorio-vendedor.controller.ts +++ b/api-gateway/src/controllers/recordatorio-vendedor.controller.ts @@ -9,6 +9,7 @@ const crearSchema = z.object({ titulo: z.string().min(1), descripcion: z.string().optional(), leadId: z.string().optional(), + clienteClave: z.string().optional(), fechaRecordar: z.string().min(1), }) @@ -24,6 +25,7 @@ export async function crearRecordatorioEndpoint(req: Request, res: Response): Pr titulo: body.titulo, descripcion: body.descripcion ?? null, leadId: body.leadId ?? null, + clienteClave: body.clienteClave ?? null, fechaRecordar: body.fechaRecordar, }) res.status(201).json(recordatorio) @@ -38,9 +40,10 @@ export async function listarRecordatoriosEndpoint(req: Request, res: Response): if (!usuarioId) throw new Error('Auth requerida') const completadosStr = req.query['completados'] as string | undefined - const opts: { completados?: boolean; desde?: string | null; hasta?: string | null } = { + const opts: { completados?: boolean; desde?: string | null; hasta?: string | null; clienteClave?: string | null } = { desde: (req.query['desde'] as string) ?? null, hasta: (req.query['hasta'] as string) ?? null, + clienteClave: (req.query['clienteClave'] as string) ?? null, } if (completadosStr === 'true') opts.completados = true else if (completadosStr === 'false') opts.completados = false @@ -78,6 +81,7 @@ export function crearController(svc: RecordatorioVendedorService) { titulo: body.titulo, descripcion: body.descripcion ?? null, leadId: body.leadId ?? null, + clienteClave: body.clienteClave ?? null, fechaRecordar: body.fechaRecordar, }) res.status(201).json(r) @@ -92,6 +96,7 @@ export function crearController(svc: RecordatorioVendedorService) { } if (completadosStr === 'true') opts.completados = true else if (completadosStr === 'false') opts.completados = false + opts.clienteClave = (req.query['clienteClave'] as string) ?? null const items = await svc.listar(usuarioId, opts) res.json(items) }, diff --git a/api-gateway/src/services/recordatorio-vendedor.service.ts b/api-gateway/src/services/recordatorio-vendedor.service.ts index db36b6f..c1a14d6 100644 --- a/api-gateway/src/services/recordatorio-vendedor.service.ts +++ b/api-gateway/src/services/recordatorio-vendedor.service.ts @@ -7,10 +7,18 @@ import { NotFoundError } from '../utils/errors.js' * muestra en "Mis recordatorios" de Mi día. */ +/** Calcula una clave estable del cliente: nombre normalizado + últimos 10 dígitos del teléfono */ +export function calcularClienteClave(nombre?: string | null, telefono?: string | null): string { + const n = (nombre ?? '').trim().toLowerCase() + const t = (telefono ?? '').replace(/\s+/g, '').slice(-10) + return `${n}|${t}` +} + export interface DatosCrearRecordatorio { titulo: string descripcion?: string | null leadId?: string | null + clienteClave?: string | null fechaRecordar: string // ISO o YYYY-MM-DDTHH:mm } @@ -18,6 +26,7 @@ export interface OpcionesListarRecordatorio { completados?: boolean desde?: string | null // ISO hasta?: string | null // ISO + clienteClave?: string | null } export class RecordatorioVendedorService { @@ -28,6 +37,7 @@ export class RecordatorioVendedorService { titulo: data.titulo.trim(), descripcion: data.descripcion?.trim() ?? null, leadId: data.leadId ?? null, + clienteClave: data.clienteClave ?? null, fechaRecordar: new Date(data.fechaRecordar), }, }) @@ -36,6 +46,9 @@ export class RecordatorioVendedorService { async listar(usuarioId: string, opciones: OpcionesListarRecordatorio = {}) { const where: Record = { usuarioId } + if (opciones.clienteClave) { + where['clienteClave'] = opciones.clienteClave + } if (opciones.completados !== undefined) { where['completado'] = opciones.completados } diff --git a/apps/mi-dia/src/components/DetalleLeadModal.tsx b/apps/mi-dia/src/components/DetalleLeadModal.tsx index fcb5f85..f4380a7 100644 --- a/apps/mi-dia/src/components/DetalleLeadModal.tsx +++ b/apps/mi-dia/src/components/DetalleLeadModal.tsx @@ -6,6 +6,13 @@ import { listarRecordatorios, crearRecordatorio, type Recordatorio } from '../se type LeadItem = LeadMiDia | OportunidadMiDia | ApartadoMiDia +/** Calcula una clave estable del cliente: nombre normalizado + últimos 10 dígitos del teléfono */ +function calcularClienteClave(nombre?: string | null, telefono?: string | null): string { + const n = (nombre ?? '').trim().toLowerCase() + const t = (telefono ?? '').replace(/\s+/g, '').slice(-10) + return `${n}|${t}` +} + function esLead(item: LeadItem): item is LeadMiDia { return 'email' in item && 'origen' in item && !('modeloInteres' in item) } @@ -40,7 +47,8 @@ export default function DetalleLeadModal({ item, onCerrar }: { item: LeadItem; o setCargandoNotas(true) try { const todas = await listarRecordatorios() - setNotas(todas.filter((r) => r.leadId === item.id || r.titulo?.toLowerCase().includes(item.nombre.toLowerCase()) || r.descripcion?.toLowerCase().includes(item.nombre.toLowerCase()))) + const clave = calcularClienteClave(item.nombre, (item as any).telefono) + setNotas(todas.filter((r) => r.leadId === item.id || (r.clienteClave && r.clienteClave === clave))) } catch { setNotaError('Error al cargar notas.') } finally { setCargandoNotas(false) } }, [item.id]) @@ -52,7 +60,7 @@ export default function DetalleLeadModal({ item, onCerrar }: { item: LeadItem; o if (!nuevaNota.trim()) return setEnviandoNota(true) try { - await crearRecordatorio({ titulo: nuevaNota.trim(), leadId: item.id, fechaRecordar: new Date().toISOString() }) + await crearRecordatorio({ titulo: nuevaNota.trim(), leadId: item.id, clienteClave: calcularClienteClave(item.nombre, (item as any).telefono), fechaRecordar: new Date().toISOString() }) setNuevaNota('') await cargarNotas() } catch { setNotaError('No se pudo guardar.') } diff --git a/apps/mi-dia/src/services/recordatorios.ts b/apps/mi-dia/src/services/recordatorios.ts index dc03c25..8661ead 100644 --- a/apps/mi-dia/src/services/recordatorios.ts +++ b/apps/mi-dia/src/services/recordatorios.ts @@ -6,7 +6,8 @@ export interface Recordatorio { id: string titulo: string descripcion: string | null - leadId: string | null + leadId: string + clienteClave: string | null fechaRecordar: string // ISO completado: boolean createdAt: string @@ -28,6 +29,7 @@ export async function crearRecordatorio(data: { titulo: string descripcion?: string leadId: string + clienteClave?: string fechaRecordar: string }): Promise { const res = await fetch(`${API_URL}/recordatorios`, conAuth({