feat(apartado): ficha completa en Monday + estado visible en la app

- El alta (directa y progresiva) llena el Apartado con telefono, correo,
  curp, Precio Final, Enganche, Fecha Compra, Vendedor (el capturista
  con Monday vinculado) y Forma de Pago; el path webhook/cron hereda
  contacto, precio y asesor de la oportunidad.
- Bidireccional: Mis clientes en /hoy muestra el estado actual del
  Apartado leyendolo de Monday (si lo mueven en Monday, la app lo
  refleja en el siguiente refresh).
- getApartados con la correccion value-null al leer Estado.
- Test del mapa de Tipo propuesta del cotizador (los 502 del log eran
  de un proceso previo al fix 4f31d1c; queda cubierto).
This commit is contained in:
2026-09-04 07:34:57 +00:00
parent d81797c610
commit 42cfe937cf
13 changed files with 428 additions and 12 deletions

View File

@@ -557,12 +557,25 @@ export class MondayService {
async crearApartado(datos: DatosApartado): Promise<string> {
const boardId = config.monday.boards.apartado
// Mapeo a las columnas reales del tablero Apartado (verificadas en Monday):
// contacto (Teléfono/Correo/curp), operación (Precio Final/Enganche/
// Fecha Compra/Forma de Pago), asignación (Vendedor) y Observaciones.
// Todo lo opcional se omite si viene undefined (mismo patrón de siempre).
const columnValues = await this.construirColumnValues(boardId, {
Unidad: datos.unidadCodigo,
'Monto apartado': datos.montoApartado,
'Fecha apartado': datos.fechaApartado,
'Fecha límite': datos.fechaLimite,
Estado: datos.estadoLabel,
Teléfono: datos.telefono,
'Correo electrónico': datos.email,
curp: datos.curp, // el título de la columna es minúscula en el board
'Precio Final': datos.precioFinal,
Enganche: datos.enganche,
'Fecha Compra': datos.fechaCompra,
Vendedor: datos.vendedorId,
'Forma de Pago': datos.formaPagoLabel, // dropdown; el resolver ajusta case
Observaciones: datos.observaciones,
})
return this.crearItem(boardId, datos.clienteNombre, columnValues)
}
@@ -1001,12 +1014,16 @@ export class MondayService {
return items.map((item) => {
const porId = new Map(item.column_values.map((cv) => [cv.id, cv]))
// Misma corrección que mapearLead: status nunca escrito → value null y
// `text` muestra el default del tablero; el estado real es vacío
const estadoCv = colEstado ? porId.get(colEstado.id) : undefined
const estado = estadoCv?.value ? (estadoCv.text ?? '') : ''
return {
id: item.id,
nombre: item.name,
unidadCodigo: colUnidad ? (porId.get(colUnidad.id)?.text ?? '') : '',
fechaLimite: colFechaLimite ? (porId.get(colFechaLimite.id)?.text ?? '') : '',
estado: colEstado ? (porId.get(colEstado.id)?.text ?? '') : '',
estado,
}
})
}
@@ -1262,6 +1279,16 @@ export interface DatosApartado {
fechaApartado: string // 'YYYY-MM-DD'
fechaLimite: string // 'YYYY-MM-DD'
estadoLabel: string // p. ej. 'Pago Apartado' (pipeline Enkontrol)
// Contacto y operación (opcionales; columnas reales del tablero Apartado)
telefono?: string | undefined // phone 'Teléfono'
email?: string | undefined // email 'Correo electrónico'
curp?: string | undefined // text 'curp' (título minúscula en el board)
precioFinal?: number | undefined // numbers 'Precio Final'
enganche?: number | undefined // numbers 'Enganche'
fechaCompra?: string | undefined // date 'Fecha Compra' ('YYYY-MM-DD')
vendedorId?: number | undefined // people 'Vendedor' (user ID de Monday)
formaPagoLabel?: string | undefined // dropdown 'Forma de Pago' (labels ya sembrados)
observaciones?: string | undefined // long_text 'Observaciones'
}
export interface DatosVenta {