feat(leads): doble via de captura — lead rapido a Leads, afluencia a Oportunidades

- POST /api/leads: captura ligera al tablero Leads (sin oportunidad),
  self-assign a Vendedor o round-robin; tambien cubre la captura manual
  desde la UI de Monday (webhook create_item re-registrado).
- POST /api/afluencias (nuevo): la forma completa sigue cayendo en
  Oportunidades con todo el perfil (etapa Afluencia, columna Asesor).
- Round-robin dual (ambos tableros), Mi dia mezcla leads+oportunidades
  con insignia de origen, acciones con parametro fuente, reportes:
  leads desde Leads, perfilamiento desde Oportunidades, funnel
  Leads->Afluencia->...->Ganado.
This commit is contained in:
2026-09-04 06:09:38 +00:00
parent 772fdef09f
commit 135e3e6de5
21 changed files with 1053 additions and 326 deletions

View File

@@ -1033,6 +1033,27 @@ export class MondayService {
}
}
/**
* Item en el tablero Leads (Estado NUEVO; captura rápida del vendedor desde
* /hoy o captura manual). La columna date 'Fecha' lleva la fecha de captura
* (hoy, hora local). Los leads NO crean oportunidad (eso es /api/afluencias).
*/
async crearLead(datos: DatosLead): Promise<string> {
const boardId = config.monday.boards.leads
const hoy = new Date()
const fechaHoy = `${hoy.getFullYear()}-${String(hoy.getMonth() + 1).padStart(2, '0')}-${String(hoy.getDate()).padStart(2, '0')}`
const columnValues = await this.construirColumnValues(boardId, {
Estado: 'NUEVO',
Fecha: fechaHoy,
Teléfono: datos.telefono,
Email: datos.email,
Origen: datos.origenLabel,
Desarrollo: datos.desarrolloLabel,
Notas: datos.notas,
})
return this.crearItem(boardId, datos.nombre, columnValues)
}
/**
* Lead con el mismo teléfono (dedup de captura rápida). Compara solo dígitos
* y los últimos 10 (tolerante a lada/prefijo 52). null si no hay duplicado.
@@ -1111,10 +1132,9 @@ export class MondayService {
return abiertas
}
/** Asigna un asesor (user ID de Monday) a la columna people "Asesor" de una oportunidad */
async asignarAsesorAOportunidad(itemId: string, userId: number): Promise<void> {
const boardId = config.monday.boards.oportunidades
const col = await this.columnaId(boardId, 'Asesor')
/** Asigna una persona (user ID de Monday) a una columna people de un item */
private async asignarPersona(boardId: string, itemId: string, tituloColumna: string, userId: number): Promise<void> {
const col = await this.columnaId(boardId, tituloColumna)
await this.graphql(
`mutation ($boardId: ID!, $itemId: ID!, $columnId: String!, $value: JSON!) {
change_column_value(board_id: $boardId, item_id: $itemId, column_id: $columnId, value: $value) { id }
@@ -1128,6 +1148,16 @@ export class MondayService {
)
}
/** Asigna un asesor (user ID de Monday) a la columna people "Asesor" de una oportunidad */
async asignarAsesorAOportunidad(itemId: string, userId: number): Promise<void> {
return this.asignarPersona(config.monday.boards.oportunidades, itemId, 'Asesor', userId)
}
/** Asigna un vendedor (user ID de Monday) a la columna people "Vendedor" de un lead */
async asignarVendedorALead(itemId: string, userId: number): Promise<void> {
return this.asignarPersona(config.monday.boards.leads, itemId, 'Vendedor', userId)
}
/** Crea un update (comentario) en un item — usado para los recordatorios de inactividad */
async crearUpdateEnItem(itemId: string, body: string): Promise<string> {
const data = await this.graphql<{ create_update: { id: string } }>(
@@ -1255,6 +1285,16 @@ export interface DatosActividad {
leadItemId?: string // item del tablero Leads relacionado (se omite si no aplica)
}
/** Captura ligera de lead (tablero Leads): la forma corta de /hoy y Monday UI */
export interface DatosLead {
nombre: string
telefono?: string | undefined
email?: string | undefined
origenLabel?: string | undefined // label real de la columna status 'Origen' del board Leads
desarrolloLabel?: string | undefined // label real de la columna status 'Desarrollo' del board Leads
notas?: string | undefined
}
/** Singleton usado por los controllers (los tests instancian el suyo) */
export const mondayService = new MondayService()