From f648430da05695010d829b1b25f51d0e154a17ee Mon Sep 17 00:00:00 2001 From: Consult AS Technology Date: Tue, 8 Sep 2026 09:36:46 +0000 Subject: [PATCH] =?UTF-8?q?fix(afluencia):=20validaci=C3=B3n=20duplicados?= =?UTF-8?q?=20+=20campos=20nombre=20+=20fix=20lada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lead creation: validación por email y teléfono normalizados (últimos 10 dígitos) - Si ya existe lead con mismo email/teléfono → 409 con aviso - Alta Afluencia: nombre dividido en Nombre(s), Apellido Paterno, Apellido Materno - Fix lada: strip robusto de +52 y +1 con/sin espacio - Backend + frontend actualizados --- .../src/controllers/leads.controller.ts | 12 +++++ api-gateway/src/services/crm-local.service.ts | 19 +++++-- .../src/components/AltaAfluenciaForm.tsx | 54 ++++++++++++++++--- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/api-gateway/src/controllers/leads.controller.ts b/api-gateway/src/controllers/leads.controller.ts index f1df132..1caab23 100644 --- a/api-gateway/src/controllers/leads.controller.ts +++ b/api-gateway/src/controllers/leads.controller.ts @@ -212,6 +212,18 @@ export async function crearLead( const existente = await monday.buscarLeadPorTelefono(datos.telefono) if (existente) return { duplicado: { id: existente.id, nombre: existente.nombre } } } + // Dedup por email en ambos tableros + if (datos.email !== undefined && datos.email.trim() !== '') { + const [leads, oportunidades] = await Promise.all([ + monday.getLeads(), + monday.getOportunidades(), + ]) + const emailLower = datos.email.trim().toLowerCase() + const existenteLead = leads.find((l) => l.email?.toLowerCase() === emailLower) + if (existenteLead) return { duplicado: { id: existenteLead.id, nombre: existenteLead.nombre } } + const existenteOp = oportunidades.find((o) => o.email?.toLowerCase() === emailLower) + if (existenteOp) return { duplicado: { id: existenteOp.id, nombre: existenteOp.nombre } } + } if (datos.origen !== undefined) { const labels = await monday.labelsDeColumna(boardLeads, 'Origen') diff --git a/api-gateway/src/services/crm-local.service.ts b/api-gateway/src/services/crm-local.service.ts index 9950555..7ffae48 100644 --- a/api-gateway/src/services/crm-local.service.ts +++ b/api-gateway/src/services/crm-local.service.ts @@ -139,11 +139,22 @@ export class CrmLocalService { * sincronizarse. */ async crearLead(datos: DatosLeadLocal, usuarioId?: string): Promise { - if (datos.telefono !== undefined) { - const existente = await prisma.leadCaptura.findFirst({ - where: { telefono: datos.telefono, board: 'LEADS' }, + // Dedup por teléfono (normalizando: últimos 10 dígitos) + if (datos.telefono !== undefined && datos.telefono.trim() !== '') { + const telLimpio = datos.telefono.replace(/\s+/g, '').slice(-10) + if (telLimpio.length >= 7) { + const existente = await prisma.leadCaptura.findFirst({ + where: { board: 'LEADS', telefono: { contains: telLimpio } }, + }) + if (existente) return { duplicado: { id: existente.leadId, nombre: existente.nombreLead } } + } + } + // Dedup por email + if (datos.email !== undefined && datos.email.trim() !== '') { + const existenteEmail = await prisma.leadCaptura.findFirst({ + where: { email: datos.email.trim(), board: 'LEADS' }, }) - if (existente) return { duplicado: { id: existente.leadId, nombre: existente.nombreLead } } + if (existenteEmail) return { duplicado: { id: existenteEmail.leadId, nombre: existenteEmail.nombreLead } } } const id = idLocal() const datosMonday = { diff --git a/apps/alta-clientes/src/components/AltaAfluenciaForm.tsx b/apps/alta-clientes/src/components/AltaAfluenciaForm.tsx index b2b3f2b..7c46f9a 100644 --- a/apps/alta-clientes/src/components/AltaAfluenciaForm.tsx +++ b/apps/alta-clientes/src/components/AltaAfluenciaForm.tsx @@ -39,6 +39,9 @@ interface Errores { const FORM_INICIAL = { nombre: '', + nombrePila: '', + apellidoPaterno: '', + apellidoMaterno: '', telefono: '', email: '', origen: '', @@ -95,7 +98,12 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor // Teléfono con prefijo internacional: separa lada y número para el selector const tel = lead.telefono.trim() const esUsa = tel.startsWith('+1') - const numero = tel.startsWith('+1') ? tel.slice(2).trim() : tel.startsWith('+52') ? tel.slice(3).trim() : tel + // Strip lada: +52 or +1 prefix + let numero = tel + if (tel.startsWith('+52 ')) numero = tel.slice(4).trim() + else if (tel.startsWith('+52')) numero = tel.slice(3).trim() + else if (tel.startsWith('+1 ')) numero = tel.slice(3).trim() + else if (tel.startsWith('+1')) numero = tel.slice(2).trim() setLada(esUsa ? 'US' : 'MX') setForm((prev) => { const desarrollo = @@ -107,9 +115,17 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor lead.modeloInteres !== undefined && (modelos as readonly string[]).includes(lead.modeloInteres) ? lead.modeloInteres : '' + // Split name: lead.nombre may contain full name or just first name + const partes = lead.nombre.trim().split(/\s+/) + const nombrePila = partes[0] || '' + const apellidoPaterno = partes.length > 1 ? partes[1] : '' + const apellidoMaterno = partes.length > 2 ? partes.slice(2).join(' ') : '' return { ...prev, nombre: lead.nombre, + nombrePila, + apellidoPaterno, + apellidoMaterno, telefono: numero, email: lead.email, origen: (ORIGENES_LEAD as readonly string[]).includes(lead.origen) ? lead.origen : prev.origen, @@ -142,7 +158,7 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor const telefono = numeroTel === '' ? '' : numeroTel.startsWith('+') ? numeroTel : `${lada === 'US' ? '+1' : '+52'} ${numeroTel}` const res = await crearAfluencia({ - nombre: form.nombre.trim(), + nombre: [form.nombrePila, form.apellidoPaterno, form.apellidoMaterno].filter(Boolean).join(' ').trim() || form.nombre.trim(), telefono, ...(oportunidadId !== undefined ? { oportunidadId } : {}), ...(form.email.trim() ? { email: form.email.trim() } : {}), @@ -165,7 +181,7 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor if (res.ok) { setLeadCreado({ id: res.lead.id, - nombre: form.nombre.trim(), + nombre: [form.nombrePila, form.apellidoPaterno, form.apellidoMaterno].filter(Boolean).join(' ').trim() || form.nombre.trim(), telefono, email: form.email.trim(), estado: res.lead.estado, @@ -262,20 +278,42 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor )}
-