feat(contactos): detalle completo del cliente con propiedad e historial

- 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
This commit is contained in:
2026-09-04 23:55:36 +00:00
parent 27ba9ea8fa
commit a4575bb948
8 changed files with 744 additions and 11 deletions

View File

@@ -53,3 +53,50 @@ export async function guardarVinculoMonday(mondayUserId: number | null): Promise
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
return (await resp.json()) as UsuarioSesion
}
// ---------- Detalle de contacto (modal del módulo Contactos) ----------
export interface RelacionCrm {
id: string
nombre: string
estado: string
/** Quién lo atiende ahora (columna people del tablero); null si sin asignar */
atendidoPor: string | null
detalle: string
}
export interface EventoHistorialContacto {
fechaISO: string
usuario: string | null
detalle: string
fuente: string // 'Contactos' | 'Leads' | 'Oportunidades' | 'Apartado' | 'App'
}
export interface DetalleContacto {
/** Columnas del board por título ('nombre' incluido); '' si vacía */
contacto: Record<string, string>
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[]
}
/** GET /contactos/:id/detalle — ficha completa: info, propiedad, quién atiende e historial */
export async function getContactoDetalle(id: string): Promise<DetalleContacto> {
const resp = await fetch(`${API_URL}/contactos/${id}/detalle`, conAuth())
manejar401(resp)
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
return (await resp.json()) as DetalleContacto
}