- GET /api/contactos/:id/detalle: todas las columnas del contacto, la propiedad comprada/en proceso (apartado relacionado por teléfono/email, enriquecido con el desarrollo de la unidad), quién atiende cada item relacionado (leads/oportunidades/apartados, columna people) y el historial completo de atención (activity log de los 4 tableros + capturas y alta local con nombre del capturista) - monday.service: getContactoDetalle (columnas por título), getHistorialItems (todos los eventos del activity log por items) y ApartadoMonday enriquecido (teléfono, email, precio final, fecha compra) - Landing: la lista de Contactos abre un modal por cliente con datos, propiedad, relaciones CRM (con quién atiende) y timeline de historial - Tests: 465 API + 12 landing, verde
95 lines
3.8 KiB
TypeScript
95 lines
3.8 KiB
TypeScript
import type { Request, Response } from 'express'
|
|
import { mondayService } from '../services/monday.service.js'
|
|
import { detalleContacto, claveTelefono } from '../services/contactos-detalle.service.js'
|
|
import { prisma } from '../services/prisma.js'
|
|
import { NotFoundError } from '../utils/errors.js'
|
|
|
|
/**
|
|
* GET /api/contactos (Bearer) → contactos del tablero Contactos de Monday
|
|
* (nombre, teléfono, email, estado), lo más reciente primero, máx 100.
|
|
*/
|
|
export async function listarContactosEndpoint(_req: Request, res: Response): Promise<void> {
|
|
res.json(await mondayService.getContactos())
|
|
}
|
|
|
|
/**
|
|
* GET /api/contactos/:id/detalle (Bearer) → ficha completa del contacto:
|
|
* todas sus columnas, la propiedad comprada/en proceso (apartado relacionado),
|
|
* quién atiende cada item relacionado (leads, oportunidades, apartados) y el
|
|
* historial completo de atención (activity log de Monday + capturas locales).
|
|
*/
|
|
export async function detalleContactoEndpoint(req: Request, res: Response): Promise<void> {
|
|
const contactoId = String(req.params['id'])
|
|
|
|
const contacto = await mondayService.getContactoDetalle(contactoId)
|
|
if (contacto === null) throw new NotFoundError('Contacto no encontrado')
|
|
|
|
// Nombres para "atendidoPor"/historial: usuarios del ecosistema + workspace
|
|
const mapeados = await prisma.usuario.findMany({
|
|
where: { mondayUserId: { not: null }, activo: true },
|
|
select: { nombre: true, mondayUserId: true },
|
|
})
|
|
const nombresPorMondayId = new Map<number, string>(
|
|
mapeados.filter((m) => m.mondayUserId !== null).map((m) => [m.mondayUserId!, m.nombre]),
|
|
)
|
|
try {
|
|
for (const u of await mondayService.getUsuariosWorkspace()) {
|
|
if (!nombresPorMondayId.has(u.id)) nombresPorMondayId.set(u.id, u.nombre)
|
|
}
|
|
} catch (err) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('No se pudieron listar los usuarios del workspace de Monday:', err)
|
|
}
|
|
|
|
// Registro local: capturas con el mismo teléfono (Monday no guarda quién
|
|
// capturó) y la ficha de cliente ligada a este contacto (o por celular).
|
|
// LeadCaptura/Cliente no tienen relación Prisma a Usuario: se resuelve a mano.
|
|
const clave = claveTelefono(contacto['Teléfono'] ?? '')
|
|
const capturasTodas = await prisma.leadCaptura.findMany({
|
|
where: { telefono: { not: null } },
|
|
orderBy: { createdAt: 'desc' },
|
|
})
|
|
const capturasCrudas = capturasTodas.filter((c) => c.telefono !== null && clave !== '' && claveTelefono(c.telefono) === clave)
|
|
|
|
const clienteCrudo = await prisma.cliente.findFirst({
|
|
where: {
|
|
OR: [
|
|
{ mondayContactoId: contactoId },
|
|
...(clave !== '' ? [{ celular: { contains: clave } }] : []),
|
|
],
|
|
},
|
|
include: { _count: { select: { documentos: true } } },
|
|
orderBy: { createdAt: 'desc' },
|
|
})
|
|
|
|
// Nombres de capturistas (ids locales → nombre para mostrar)
|
|
const idsUsuarios = [
|
|
...new Set([
|
|
...capturasCrudas.map((c) => c.usuarioId),
|
|
...(clienteCrudo?.capturadoPorId ? [clienteCrudo.capturadoPorId] : []),
|
|
]),
|
|
]
|
|
const usuariosLocal = idsUsuarios.length
|
|
? await prisma.usuario.findMany({ where: { id: { in: idsUsuarios } }, select: { id: true, nombre: true } })
|
|
: []
|
|
const nombreLocal = new Map(usuariosLocal.map((u) => [u.id, u.nombre]))
|
|
|
|
const capturas = capturasCrudas.map((c) => ({
|
|
leadId: c.leadId,
|
|
nombreLead: c.nombreLead,
|
|
createdAt: c.createdAt,
|
|
usuario: nombreLocal.has(c.usuarioId) ? { nombre: nombreLocal.get(c.usuarioId)! } : null,
|
|
}))
|
|
const clienteLocal = clienteCrudo
|
|
? {
|
|
...clienteCrudo,
|
|
capturadoPor:
|
|
clienteCrudo.capturadoPorId && nombreLocal.has(clienteCrudo.capturadoPorId)
|
|
? { nombre: nombreLocal.get(clienteCrudo.capturadoPorId)! }
|
|
: null,
|
|
}
|
|
: null
|
|
|
|
res.json(await detalleContacto(mondayService, contactoId, nombresPorMondayId, capturas, clienteLocal))
|
|
}
|