feat(monday): alinear Frasa-CAS con CRM real del cliente (7 mejoras)

- Vocabulario funnel: labels Intento de contacto/Abandono en Leads,
  etapas completas en Oportunidades (Cotizador, Ganado, Perdido...)
- Apartados: pipeline Enkontrol (8 estados) + checkboxes CURP/RFC/Comprobante
  que la app de alta prende al subir documentos
- Motivos No Compra: board catálogo + dropdown + endpoint /oportunidades/:id/perder
- Perfil demográfico en Contactos mapeado desde el alta (estado civil,
  vivienda, propósito, fecha nac., rango de edad calculado)
- Desarrollos: catálogo maestro sincronizado (precios, m2, tipo, entrega)
- Actividades: board nuevo + creación automática en round-robin e inactividad
- Cobranza: plan liquidado mueve el apartado a 'X Escriturar' en Monday
- Scripts: setup:crm y sync:desarrollos (idempotentes)
- Tests 119/119 verdes
This commit is contained in:
2026-08-22 07:18:48 +00:00
parent afb454b54c
commit f0f923b2ef
22 changed files with 1178 additions and 13 deletions

View File

@@ -0,0 +1,75 @@
import { describe, expect, it } from 'vitest'
import {
actividadEconomicaLabel,
checkboxParaTipoDocumento,
perfilDemografico,
rangoEdadLabel,
} from './clientes.controller.js'
describe('checkboxParaTipoDocumento (Apartado → checkboxes de documentos)', () => {
it('mapea los tipos con checkbox al título de la columna', () => {
expect(checkboxParaTipoDocumento('CURP')).toBe('CURP')
expect(checkboxParaTipoDocumento('RFC')).toBe('RFC')
expect(checkboxParaTipoDocumento('CONSTANCIA_RFC')).toBe('RFC')
expect(checkboxParaTipoDocumento('COMPROBANTE')).toBe('Comprobante')
expect(checkboxParaTipoDocumento('COMPROBANTE_DOMICILIO')).toBe('Comprobante')
})
it('normaliza mayúsculas y espacios', () => {
expect(checkboxParaTipoDocumento(' curp ')).toBe('CURP')
expect(checkboxParaTipoDocumento('comprobante_domicilio')).toBe('Comprobante')
})
it('INE y otros tipos no tienen checkbox (null)', () => {
expect(checkboxParaTipoDocumento('INE')).toBeNull()
expect(checkboxParaTipoDocumento('ACTA_NACIMIENTO')).toBeNull()
expect(checkboxParaTipoDocumento('OTRO')).toBeNull()
})
})
describe('rangoEdadLabel', () => {
const hoy = new Date('2026-08-22T12:00:00')
it('deriva el rango del CRM a partir de la fecha de nacimiento', () => {
expect(rangoEdadLabel('2003-01-01', hoy)).toBe('Menor de 24') // 23 años
expect(rangoEdadLabel('2002-08-22', hoy)).toBe('Entre 24 y 40') // cumple 24 hoy
expect(rangoEdadLabel('1990-05-10', hoy)).toBe('Entre 24 y 40')
expect(rangoEdadLabel('1986-08-21', hoy)).toBe('Entre 24 y 40') // 40 años: límite inclusivo
expect(rangoEdadLabel('1985-01-01', hoy)).toBe('Mayor de 40')
})
it('devuelve undefined con fechas inválidas o ausentes', () => {
expect(rangoEdadLabel(undefined, hoy)).toBeUndefined()
expect(rangoEdadLabel('no-es-fecha', hoy)).toBeUndefined()
expect(rangoEdadLabel('2999-01-01', hoy)).toBeUndefined()
})
})
describe('perfilDemografico (alta → Contacto Monday)', () => {
it('mapea los valores del formato a los labels del CRM', () => {
const perfil = perfilDemografico({
estadoCivil: 'UNION_LIBRE',
tipoVivienda: 'FAMILIAR',
tipoCompra: 'VIVIR_AHI',
fechaNacimiento: '1990-05-10',
})
expect(perfil.estadoCivilLabel).toBe('Union Libre')
expect(perfil.viviendaActualLabel).toBe('Comparte con familia')
expect(perfil.propositoCompraLabel).toBe('Habitarla')
expect(perfil.rangoEdadLabel).toBe('Entre 24 y 40')
expect(perfil.fechaNacimiento).toBe('1990-05-10')
})
it('omite campos vacíos o desconocidos (no inventa datos)', () => {
expect(perfilDemografico({})).toEqual({})
expect(perfilDemografico({ estadoCivil: '', tipoVivienda: 'OTRA_COSA' })).toEqual({})
})
})
describe('actividadEconomicaLabel', () => {
it('solo ASALARIADO tiene equivalencia inequívoca (Empleado)', () => {
expect(actividadEconomicaLabel('ASALARIADO')).toBe('Empleado')
expect(actividadEconomicaLabel('INDEPENDIENTE')).toBeUndefined() // ambiguo: no se adivina
expect(actividadEconomicaLabel(undefined)).toBeUndefined()
})
})