feat(round-robin): reparto solo con el equipo 'vendedores' de Monday
- obtenerVendedores ahora lee el team de Monday (MONDAY_RR_TEAM, default 'vendedores') con cache de 1 h; MONDAY_RR_USER_IDS queda como override manual. - Escrituras a columnas status resuelven el label real sin importar mayusculas: si renombran labels en la UI de Monday (paso con NUEVO→Nuevo) el API sigue escribiendo en vez de tronar con 502. - mapearLead: un status nunca escrito (value null) se lee como vacio; Monday regresaba el label default del tablero como texto y el round-robin creia que el lead estaba descartado.
This commit is contained in:
@@ -370,6 +370,23 @@ export class MondayService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resuelve el label real de una columna status tolerando mayúsculas y
|
||||
* minúsculas (el código escribe 'NUEVO' aunque el board diga 'Nuevo' tras
|
||||
* un rename en la UI). Sin match devuelve el valor tal cual (Monday dirá
|
||||
* que el label no existe, como antes).
|
||||
*/
|
||||
private async resolverLabelStatus(
|
||||
boardId: string,
|
||||
col: ColumnaMeta,
|
||||
valor: string | number | boolean,
|
||||
): Promise<string | number | boolean> {
|
||||
if (col.type !== 'status') return valor
|
||||
const buscado = String(valor).trim().toUpperCase()
|
||||
const labels = await this.labelsDeColumna(boardId, col.title)
|
||||
return labels.find((l) => l.trim().toUpperCase() === buscado) ?? valor
|
||||
}
|
||||
|
||||
private async construirColumnValues(
|
||||
boardId: string,
|
||||
valores: Record<string, string | number | boolean | undefined>,
|
||||
@@ -378,7 +395,7 @@ export class MondayService {
|
||||
for (const [titulo, crudo] of Object.entries(valores)) {
|
||||
if (crudo === undefined || crudo === '') continue
|
||||
const col = await this.columnaId(boardId, titulo)
|
||||
out[col.id] = this.valorPorTipo(col, crudo)
|
||||
out[col.id] = this.valorPorTipo(col, await this.resolverLabelStatus(boardId, col, crudo))
|
||||
}
|
||||
return JSON.stringify(out)
|
||||
}
|
||||
@@ -544,11 +561,12 @@ export class MondayService {
|
||||
valor: string | number | boolean,
|
||||
): Promise<void> {
|
||||
const col = await this.columnaId(boardId, tituloColumna)
|
||||
const resuelto = await this.resolverLabelStatus(boardId, col, valor)
|
||||
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 }
|
||||
}`,
|
||||
{ boardId, itemId, columnId: col.id, value: JSON.stringify(this.valorPorTipo(col, valor)) },
|
||||
{ boardId, itemId, columnId: col.id, value: JSON.stringify(this.valorPorTipo(col, resuelto)) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -706,6 +724,11 @@ export class MondayService {
|
||||
const colOrigen = cols.get('Origen')
|
||||
const colTelefono = cols.get('Teléfono')
|
||||
const colEmail = cols.get('Email')
|
||||
// Cuando el status nunca se ha escrito, Monday devuelve value=null pero
|
||||
// `text` muestra el label default del tablero (hoy "Descartado"): el estado
|
||||
// real es vacío, no el default. Se lee `text` SOLO si hay value.
|
||||
const estadoCv = colEstado ? porId.get(colEstado.id) : undefined
|
||||
const estado = estadoCv?.value ? (estadoCv.text ?? '') : ''
|
||||
// Perfilamiento: lectura tolerante por título ('' / null si falta la columna)
|
||||
const textoDe = (titulo: string): string => {
|
||||
const col = cols.get(titulo)
|
||||
@@ -718,7 +741,7 @@ export class MondayService {
|
||||
nombre: item.name,
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
estado: colEstado ? (porId.get(colEstado.id)?.text ?? '') : '',
|
||||
estado,
|
||||
origen: colOrigen ? (porId.get(colOrigen.id)?.text ?? '') : '',
|
||||
telefono: colTelefono ? (porId.get(colTelefono.id)?.text ?? '') : '',
|
||||
email: colEmail ? (porId.get(colEmail.id)?.text ?? '') : '',
|
||||
@@ -1083,6 +1106,23 @@ export class MondayService {
|
||||
.filter((u) => !u.is_guest && u.enabled && !u.email.endsWith('@agent.monday.com'))
|
||||
.map((u) => ({ id: Number(u.id), nombre: u.name, email: u.email }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Miembros de un equipo (team) de Monday por nombre, case-insensitive
|
||||
* (p. ej. "vendedores" para el round-robin). Mismo filtro que el workspace.
|
||||
*/
|
||||
async getUsuariosEquipo(nombre: string): Promise<UsuarioMonday[]> {
|
||||
const data = await this.graphql<{
|
||||
teams: { name: string; users: { id: string; name: string; email: string; is_guest: boolean; enabled: boolean }[] }[] | null
|
||||
}>(`query { teams { name users { id name email is_guest enabled } } }`)
|
||||
const equipo = (data.teams ?? []).find(
|
||||
(t) => t.name.trim().toUpperCase() === nombre.trim().toUpperCase(),
|
||||
)
|
||||
if (!equipo) throw new AppError(500, `El equipo "${nombre}" no existe en Monday`)
|
||||
return equipo.users
|
||||
.filter((u) => !u.is_guest && u.enabled && !u.email.endsWith('@agent.monday.com'))
|
||||
.map((u) => ({ id: Number(u.id), nombre: u.name, email: u.email }))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- DTOs de escritura ----------
|
||||
|
||||
Reference in New Issue
Block a user