import { prisma } from './prisma.js' import { calcularScore } from './lead-scoring.service.js' /** * Perfil Completo del Contacto: datos locales del cliente/lead, historial de * pagos, cotizaciones, documentos, lead score y última actividad. */ export interface RelacionCrm { id: string nombre: string estado: string // estado/etapa actual /** Quién lo atiende ahora; null si sin asignar */ atendidoPor: string | null detalle: string // origen/desarrollo/unidad según el tablero ('' si no aplica) } export interface EventoHistorialContacto { fechaISO: string usuario: string | null detalle: string fuente: string } export interface DetalleContacto { contacto: Record propiedad: { id: string unidadCodigo: string desarrollo: string estado: string precioFinal: number fechaCompra: string fechaLimite: string atendidoPor: string | null } | null relaciones: { leads: RelacionCrm[] oportunidades: RelacionCrm[] apartados: RelacionCrm[] } historial: EventoHistorialContacto[] } /** Últimos 10 dígitos del teléfono (dedup/relación tolerante a lada y formato) */ export function claveTelefono(telefono: string): string { const digitos = telefono.replace(/\D/g, '') return digitos.length >= 10 ? digitos.slice(-10) : '' } // ── Perfil Completo del Contacto (modo local) ───────────────────────── export interface PerfilCompletoContacto { contacto: Record historialPagos: Array<{ planId: string clienteNombre: string unidadCodigo: string proyecto: string numPago: number fechaVencimiento: string monto: number montoPagado: number estado: string }> cotizaciones: Array<{ id: string unidadCodigo: string tipoPropuesta: string precioFinal: number createdAt: string }> documentos: Array<{ id: string tipo: string nombreArchivo: string createdAt: string }> leadScore: number | null ultimaActividad: { titulo: string fecha: string tipo: string } | null } /** * Perfil completo de un contacto: combina datos del cliente/lead, historial * de pagos, cotizaciones, documentos, lead score y última actividad local. */ export async function perfilCompleto(contactoId: string): Promise { // Buscar el cliente const cliente = await prisma.cliente.findFirst({ where: { OR: [{ id: contactoId }, { mondayContactoId: contactoId }] }, // mondayContactoId: compatibilidad de datos }) // Buscar la captura del lead const captura = await prisma.leadCaptura.findUnique({ where: { leadId: contactoId } }) if (!cliente && !captura) return null const telefono = cliente?.celular ?? captura?.telefono ?? '' const email = (cliente?.email ?? captura?.email ?? '').trim().toLowerCase() const nombre = cliente?.nombreCompleto ?? captura?.nombreLead ?? 'Sin nombre' // Contacto fields const contacto: Record = { nombre, Teléfono: telefono, Email: email, } if (cliente) { contacto['CURP'] = cliente.curp contacto['RFC'] = cliente.rfc contacto['Proyecto'] = cliente.proyecto } if (captura) { contacto['Etapa'] = captura.etapa contacto['Origen'] = captura.origen ?? '' } // Historial de pagos (por teléfono o nombre del cliente) const historialPagos: PerfilCompletoContacto['historialPagos'] = [] if (cliente) { const planes = await prisma.planPago.findMany({ where: { clienteNombre: cliente.nombreCompleto }, include: { pagos: true }, }) for (const plan of planes) { for (const pago of plan.pagos) { const restante = pago.monto - pago.montoPagado const estado = restante <= 0.01 ? 'pagado' : 'pendiente' historialPagos.push({ planId: plan.id, clienteNombre: plan.clienteNombre, unidadCodigo: plan.unidadCodigo, proyecto: plan.proyecto, numPago: pago.numPago, fechaVencimiento: pago.fechaVencimiento.toISOString().slice(0, 10), monto: pago.monto, montoPagado: pago.montoPagado, estado, }) } } } // Cotizaciones (por teléfono) const clave = cliente?.celular?.replace(/\D/g, '').slice(-10) ?? '' const cotizacionesRaw = clave !== '' ? await prisma.cotizacion.findMany({ where: { clienteTelefono: { contains: clave } }, orderBy: { createdAt: 'desc' }, }) : [] // Documentos const documentosRaw = cliente ? await prisma.documento.findMany({ where: { clienteId: cliente.id }, orderBy: { createdAt: 'desc' }, }) : [] // Lead score let leadScore: number | null = null if (captura) { leadScore = calcularScore({ etapa: captura.etapa, telefono: captura.telefono, email: captura.email, origen: captura.origen, createdAt: captura.createdAt, updatedAt: captura.updatedAt, }) } // Última actividad let ultimaActividad: PerfilCompletoContacto['ultimaActividad'] = null if (captura) { const ultima = await prisma.actividadLocal.findFirst({ where: { leadCapturaId: captura.leadId }, orderBy: { createdAt: 'desc' }, }) if (ultima) { ultimaActividad = { titulo: ultima.titulo, fecha: ultima.createdAt.toISOString(), tipo: ultima.tipo, } } } return { contacto, historialPagos, cotizaciones: cotizacionesRaw.map((c) => ({ id: c.id, unidadCodigo: c.unidadCodigo, tipoPropuesta: c.tipoPropuesta, precioFinal: c.precioFinal, createdAt: c.createdAt.toISOString(), })), documentos: documentosRaw.map((d) => ({ id: d.id, tipo: d.tipo, nombreArchivo: d.nombreArchivo, createdAt: d.createdAt.toISOString(), })), leadScore, ultimaActividad, } }