/** * VOCABULARIO canónico FRASA del backend: fases, estados, etapas, motivos, * subcategorías, orígenes y desarrollos en un solo catálogo. * * Espejo de apps/mi-dia/src/lib/mi-dia.ts (mantener sincronizados). Los * controllers siguen exportando sus constantes de validación zod derivadas de * este mismo vocabulario: * - leads.controller.ts → ESTADOS_LEAD_PERMITIDOS (FASES_LEAD sin NUEVO/DESCARTADO) * - oportunidades.controller.ts → ETAPAS_OPORTUNIDAD_PERMITIDAS (= FASES_OPORTUNIDAD), * MOTIVOS_NO_COMPRA, SUBCATEGORIAS_NO_COMPRA * - apartados.controller.ts → FASES_APARTADO_PERMITIDAS (= FASES_APARTADO) */ export const VOCABULARIO = { // Estados de la columna status del board Leads: ESTADOS_LEAD_PERMITIDOS del // API + NUEVO (captura) y DESCARTADO (terminal) FASES_LEAD: ['NUEVO', 'Intento de contacto', 'CONTACTADO', 'INTERESADO', 'AGENDA CITA', 'CALIFICADO', 'DESCARTADO'], // Etapas del funnel de Oportunidades (labels REALES de la columna status 'Estado') FASES_OPORTUNIDAD: ['Agendo Cita', 'Afluencia', 'Cotizador', 'Aparto', 'No Asistio', 'Abandono'], // Fases del pipeline Enkontrol del tablero Apartado (labels REALES de la // columna status 'Estado'); incluye las fases de escrituración FASES_APARTADO: [ 'Pago Apartado', 'Alta Enkontrol', 'Entrego Documentacion', 'Cita Clientes', 'Contrato Enkontrol', 'Firma Cliente', 'Cancelacion Apartado', 'Venta', 'Seguimiento', 'Integracion', 'Llamada Escrituras', 'X Escriturar', 'Banco/Infonavit', 'X Autorizar', 'Autorizado', 'Escriturado', 'Titulado', 'Cancelo', ], // Fases de escrituración (pipeline posterior a 'Firma Cliente') FASES_ESCRITURACION: ['Venta', 'Seguimiento', 'Integracion', 'X Autorizar', 'Autorizado', 'X Escriturar', 'Escriturado', 'Titulado', 'Cancelo'], // Categorías del dropdown Motivo No Compra (vocabulario CRM FRASA) MOTIVOS_NO_COMPRA: ['Precio', 'Producto', 'Ubicacion', 'Financiamiento', 'Competencia', 'Cliente', 'Timing'], // Subcategorías por categoría (catálogo del tablero Motivos No Compra, // sembrado en Frasa-CAS el 2026-09-04; 'Mesualidad' y 'Cambo de Planes' // conservan las faltas de ortografía originales del tablero del cliente) SUBCATEGORIAS: { Precio: ['Precio Total', 'Enganche', 'Mesualidad'], Producto: ['Distribucion', 'Tamaño', 'Recamaras', 'Baños', 'Amenidades', 'Acabados', 'Estacionamiento', 'Seguridad', 'Inventario'], Ubicacion: ['Zona', 'Accesos', 'Distancia', 'Servicios', 'Seguridad', 'Escuela', 'Trabajo'], Financiamiento: ['No Califica', 'Credito Insuficiente', 'Tasa de interes', 'Mensualidad Alta', 'Enganche Alto', 'Forma de Pago', 'Infonavit Insuficiente', 'No tiene liquidez'], Competencia: ['Precio', 'Producto', 'Ubicacion', 'Promocion', 'Financiamiento'], Cliente: ['Cambo de Planes', 'Decision Familiar', 'No estaba Listo', 'Emergencia Personal'], Timing: ['Compra Posterior', 'Esperando Credito', 'Esperando Venta de Propiedad', 'Esperando Proyecto'], }, // Labels de la columna 'Origen' (board Leads) / 'Origen del Lead' // (afluencia), verificados y sembrados el 2026-09-04 ORIGENES: ['PASE', 'PROSPECCION ASESOR', 'ACTIVACION', 'FACEBOOK FRASA', 'REFERIDO', 'BROCKER', 'OTRO'], // Labels oficiales de desarrollo (verificados el 2026-09-04; SOLE y MUTUO // agregados 2026-09 — existen en captura e inventario) DESARROLLOS: ['ATELIER', 'DIAGONAL', 'ESTANZA', 'SENDA', 'SOLANA', 'SOLE', 'MUTUO'], // Campos de calificación que hacen "completa" una afluencia: sin ellos la // oportunidad NO puede avanzar a 'Aparto' (regla de negocio del cliente) CAMPOS_AFLUENCIA_REQUERIDOS: ['estadoCivil', 'tipoEmpleo', 'rangoSalarial', 'tipoEconomia', 'califica', 'loQuierePara'], } as const /** Campos de afluencia faltantes en un datosJson ('[]' = afluencia completa) */ export function camposAfluenciaFaltantes(datosJson: string | null): string[] { let datos: Record = {} if (datosJson) { try { datos = JSON.parse(datosJson) as Record } catch { datos = {} } } return VOCABULARIO.CAMPOS_AFLUENCIA_REQUERIDOS.filter( (c) => typeof datos[c] !== 'string' || (datos[c] as string).trim() === '', ) } /** ¿La fila tiene la afluencia completa? (gate de 'Aparto' y flag de Mi día) */ export function afluenciaCompleta(datosJson: string | null): boolean { return camposAfluenciaFaltantes(datosJson).length === 0 } export type FaseLead = (typeof VOCABULARIO.FASES_LEAD)[number] export type FaseOportunidad = (typeof VOCABULARIO.FASES_OPORTUNIDAD)[number] export type FaseApartado = (typeof VOCABULARIO.FASES_APARTADO)[number] export type FaseEscrituracion = (typeof VOCABULARIO.FASES_ESCRITURACION)[number] export type MotivoNoCompra = (typeof VOCABULARIO.MOTIVOS_NO_COMPRA)[number]