feat: vínculo con usuario Monday en landing + alta prellenada desde leads

API:
- GET /api/monday/usuarios: usuarios activos no-guest del workspace
- PATCH /api/usuarios/me/monday: vincula/desvincula el mondayUserId del
  usuario autenticado (valida contra el workspace)
- GET /api/leads/buscar?q=: búsqueda de leads por nombre/teléfono/email
  (sin acentos, orden updatedAt desc, límite 15)
- aPublico/me ahora incluyen mondayUserId

Landing (/):
- Nueva sección 'Configuración de cuenta': login inline, selector del
  usuario de Monday, guardar/desvincular, cerrar sesión

Alta de clientes (/client):
- Búsqueda de leads de Monday con debounce en la pantalla de entrada;
  al elegir uno prellena nombre/email/celular y guarda leadMondayId
- El picker (borradores + alta nueva + búsqueda de lead) es ahora
  siempre la pantalla de entrada

Tests: api 294, landing 8, alta-clientes 96 — todos verdes
This commit is contained in:
2026-08-28 18:36:08 +00:00
parent d99d90d344
commit 1692dde887
28 changed files with 2501 additions and 41 deletions

View File

@@ -0,0 +1,44 @@
import type { Request, Response } from 'express'
import { z } from 'zod'
import { mondayService } from '../services/monday.service.js'
import { prisma } from '../services/prisma.js'
import { aPublico } from '../services/auth.service.js'
import { BadRequestError } from '../utils/errors.js'
/**
* Vínculo usuario del ecosistema ↔ usuario de Monday (people). Es el mapeo
* que alimenta round-robin, mis-leads, actividades y alertas; el usuario lo
* elige él mismo desde la landing (Configuración de cuenta).
*/
/** GET /api/monday/usuarios (Bearer) → usuarios activos del workspace */
export async function listarUsuariosMondayEndpoint(_req: Request, res: Response): Promise<void> {
const usuarios = await mondayService.getUsuariosWorkspace()
res.json(usuarios.map((u) => ({ id: u.id, nombre: u.nombre, email: u.email })))
}
const vinculoMondaySchema = z.object({
mondayUserId: z.number().int().positive().nullable(),
})
/**
* PATCH /api/usuarios/me/monday (Bearer) { mondayUserId: number | null }
* null desvincula. Un id que no existe en el workspace → 400.
*/
export async function actualizarVinculoMondayEndpoint(req: Request, res: Response): Promise<void> {
if (!req.usuarioToken) throw new BadRequestError('Falta el token verificado')
const { mondayUserId } = vinculoMondaySchema.parse(req.body)
if (mondayUserId !== null) {
const usuarios = await mondayService.getUsuariosWorkspace()
if (!usuarios.some((u) => u.id === mondayUserId)) {
throw new BadRequestError(`El usuario de Monday ${mondayUserId} no existe en el workspace`)
}
}
const usuario = await prisma.usuario.update({
where: { id: req.usuarioToken.sub },
data: { mondayUserId },
})
res.json(aPublico(usuario))
}