fix(afluencia): validación duplicados + campos nombre + fix lada
- 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
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -139,12 +139,23 @@ export class CrmLocalService {
|
||||
* sincronizarse.
|
||||
*/
|
||||
async crearLead(datos: DatosLeadLocal, usuarioId?: string): Promise<ResultadoCapturaLocal> {
|
||||
if (datos.telefono !== undefined) {
|
||||
// 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: { telefono: datos.telefono, board: 'LEADS' },
|
||||
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 (existenteEmail) return { duplicado: { id: existenteEmail.leadId, nombre: existenteEmail.nombreLead } }
|
||||
}
|
||||
const id = idLocal()
|
||||
const datosMonday = {
|
||||
nombre: datos.nombre,
|
||||
|
||||
@@ -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
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-6 sm:grid-cols-2">
|
||||
<label htmlFor="afluencia-nombre" className="block sm:col-span-2">
|
||||
<label htmlFor="afluencia-nombre-pila" className="block">
|
||||
<span className={labelClase}>
|
||||
Nombre<span className="text-red-700"> *</span>
|
||||
Nombre(s)<span className="text-red-700"> *</span>
|
||||
</span>
|
||||
<input
|
||||
id="afluencia-nombre"
|
||||
id="afluencia-nombre-pila"
|
||||
type="text"
|
||||
value={form.nombre}
|
||||
onChange={cambiar('nombre')}
|
||||
value={form.nombrePila}
|
||||
onChange={cambiar('nombrePila')}
|
||||
className={`${inputClase} ${errores.nombre ? 'border-red-400' : ''}`}
|
||||
/>
|
||||
{errores.nombre && <p className="mt-1 text-xs text-red-700">{errores.nombre}</p>}
|
||||
</label>
|
||||
|
||||
<label htmlFor="afluencia-apellido-paterno" className="block">
|
||||
<span className={labelClase}>Apellido paterno</span>
|
||||
<input
|
||||
id="afluencia-apellido-paterno"
|
||||
type="text"
|
||||
value={form.apellidoPaterno}
|
||||
onChange={cambiar('apellidoPaterno')}
|
||||
className={inputClase}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label htmlFor="afluencia-apellido-materno" className="block">
|
||||
<span className={labelClase}>Apellido materno</span>
|
||||
<input
|
||||
id="afluencia-apellido-materno"
|
||||
type="text"
|
||||
value={form.apellidoMaterno}
|
||||
onChange={cambiar('apellidoMaterno')}
|
||||
className={inputClase}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div>
|
||||
<label htmlFor="afluencia-telefono" className={labelClase}>
|
||||
Teléfono<span className="text-red-700"> *</span>
|
||||
|
||||
Reference in New Issue
Block a user