feat(afluencia): el campo de interes muestra los modelos del desarrollo
'Al departamento o casa de interes' ahora es un select dependiente: al elegir desarrollo se cargan sus modelos (MODELOS_POR_PROYECTO) y al cambiar de desarrollo se resetea. En Monday la columna 'Tipo de Propiedad' se renombro a 'Modelo de Interes' con los 16 modelos como labels del status.
This commit is contained in:
@@ -105,7 +105,7 @@ describe('crearLead (POST /api/leads)', () => {
|
|||||||
const monday = mondayMock()
|
const monday = mondayMock()
|
||||||
const r = await crearLead(monday, {
|
const r = await crearLead(monday, {
|
||||||
...datosBase,
|
...datosBase,
|
||||||
tipoPropiedad: 'Casa',
|
modelo: 'HIMALAYA',
|
||||||
medioContacto: 'WhatsApp',
|
medioContacto: 'WhatsApp',
|
||||||
edad: 35,
|
edad: 35,
|
||||||
estadoCivil: 'Casado(a)',
|
estadoCivil: 'Casado(a)',
|
||||||
@@ -119,7 +119,7 @@ describe('crearLead (POST /api/leads)', () => {
|
|||||||
})
|
})
|
||||||
expect(r).toEqual({ id: 'lead-nuevo-1', oportunidadId: null })
|
expect(r).toEqual({ id: 'lead-nuevo-1', oportunidadId: null })
|
||||||
expect(monday.leadsCreados[0]).toMatchObject({
|
expect(monday.leadsCreados[0]).toMatchObject({
|
||||||
tipoPropiedadLabel: 'Casa',
|
modeloLabel: 'HIMALAYA',
|
||||||
medioContacto: 'WhatsApp',
|
medioContacto: 'WhatsApp',
|
||||||
edad: 35,
|
edad: 35,
|
||||||
estadoCivilLabel: 'Casado(a)',
|
estadoCivilLabel: 'Casado(a)',
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const crearLeadSchema = z.object({
|
|||||||
notas: z.string().trim().min(1).optional(),
|
notas: z.string().trim().min(1).optional(),
|
||||||
// Campos de calificación de la afluencia (opcionales; los labels fijos del
|
// Campos de calificación de la afluencia (opcionales; los labels fijos del
|
||||||
// front coinciden con los sembrados en Monday, no se validan contra el board)
|
// front coinciden con los sembrados en Monday, no se validan contra el board)
|
||||||
tipoPropiedad: z.string().trim().min(1).optional(),
|
modelo: z.string().trim().min(1).optional(),
|
||||||
medioContacto: z.string().trim().min(1).optional(),
|
medioContacto: z.string().trim().min(1).optional(),
|
||||||
edad: z.coerce.number().int().min(15).max(120).optional(),
|
edad: z.coerce.number().int().min(15).max(120).optional(),
|
||||||
estadoCivil: z.string().trim().min(1).optional(),
|
estadoCivil: z.string().trim().min(1).optional(),
|
||||||
@@ -125,7 +125,7 @@ export async function crearLead(
|
|||||||
origenLabel: datos.origen,
|
origenLabel: datos.origen,
|
||||||
desarrolloLabel: datos.desarrollo,
|
desarrolloLabel: datos.desarrollo,
|
||||||
notas: datos.notas,
|
notas: datos.notas,
|
||||||
tipoPropiedadLabel: datos.tipoPropiedad,
|
modeloLabel: datos.modelo,
|
||||||
medioContacto: datos.medioContacto,
|
medioContacto: datos.medioContacto,
|
||||||
edad: datos.edad,
|
edad: datos.edad,
|
||||||
estadoCivilLabel: datos.estadoCivil,
|
estadoCivilLabel: datos.estadoCivil,
|
||||||
|
|||||||
@@ -882,7 +882,7 @@ export class MondayService {
|
|||||||
Origen: datos.origenLabel,
|
Origen: datos.origenLabel,
|
||||||
Desarrollo: datos.desarrolloLabel,
|
Desarrollo: datos.desarrolloLabel,
|
||||||
Notas: datos.notas,
|
Notas: datos.notas,
|
||||||
'Tipo de Propiedad': datos.tipoPropiedadLabel,
|
'Modelo de Interés': datos.modeloLabel,
|
||||||
'Medio de Contacto': datos.medioContacto,
|
'Medio de Contacto': datos.medioContacto,
|
||||||
Edad: datos.edad,
|
Edad: datos.edad,
|
||||||
'Estado Civil': datos.estadoCivilLabel,
|
'Estado Civil': datos.estadoCivilLabel,
|
||||||
@@ -1095,7 +1095,7 @@ export interface DatosLead {
|
|||||||
desarrolloLabel?: string | undefined // label real de la columna status 'Desarrollo' del board Leads
|
desarrolloLabel?: string | undefined // label real de la columna status 'Desarrollo' del board Leads
|
||||||
notas?: string | undefined
|
notas?: string | undefined
|
||||||
// Campos de calificación del interesado (labels reales de las columnas status del board Leads)
|
// Campos de calificación del interesado (labels reales de las columnas status del board Leads)
|
||||||
tipoPropiedadLabel?: string | undefined
|
modeloLabel?: string | undefined // label de la columna status 'Modelo de Interés' (modelo según desarrollo)
|
||||||
medioContacto?: string | undefined
|
medioContacto?: string | undefined
|
||||||
edad?: number | undefined
|
edad?: number | undefined
|
||||||
estadoCivilLabel?: string | undefined
|
estadoCivilLabel?: string | undefined
|
||||||
|
|||||||
@@ -71,7 +71,19 @@ describe('AltaAfluenciaForm', () => {
|
|||||||
const opcionesDe = (label: RegExp) =>
|
const opcionesDe = (label: RegExp) =>
|
||||||
Array.from((screen.getByLabelText(label) as HTMLSelectElement).options).map((o) => o.value)
|
Array.from((screen.getByLabelText(label) as HTMLSelectElement).options).map((o) => o.value)
|
||||||
|
|
||||||
expect(opcionesDe(/Departamento o casa de interés/)).toEqual(['', 'Casa', 'Departamento'])
|
// El select de modelo arranca deshabilitado hasta elegir desarrollo
|
||||||
|
const selectModelo = screen.getByLabelText(/Departamento o casa de interés/) as HTMLSelectElement
|
||||||
|
expect(selectModelo.disabled).toBe(true)
|
||||||
|
fireEvent.change(screen.getByLabelText(/Desarrollo de interés/), { target: { value: 'SENDA' } })
|
||||||
|
expect(selectModelo.disabled).toBe(false)
|
||||||
|
expect(opcionesDe(/Departamento o casa de interés/)).toEqual([
|
||||||
|
'',
|
||||||
|
'HIMALAYA',
|
||||||
|
'SANTIAGO',
|
||||||
|
'ESMERALDA PA',
|
||||||
|
'ESMERALDA PB',
|
||||||
|
'SANTA CRUZ',
|
||||||
|
])
|
||||||
expect(opcionesDe(/^Estado civil/)).toEqual(['', 'Soltero(a)', 'Casado(a)', 'Unión libre', 'Divorciado(a)', 'Viudo(a)'])
|
expect(opcionesDe(/^Estado civil/)).toEqual(['', 'Soltero(a)', 'Casado(a)', 'Unión libre', 'Divorciado(a)', 'Viudo(a)'])
|
||||||
expect(opcionesDe(/Tipo de empleo/)).toEqual(['', 'Asalariado', 'Independiente'])
|
expect(opcionesDe(/Tipo de empleo/)).toEqual(['', 'Asalariado', 'Independiente'])
|
||||||
expect(opcionesDe(/^Crédito/)).toEqual([
|
expect(opcionesDe(/^Crédito/)).toEqual([
|
||||||
@@ -93,7 +105,8 @@ describe('AltaAfluenciaForm', () => {
|
|||||||
fetchMock.mockResolvedValueOnce(respuesta({ id: 'l-1', estado: 'NUEVO', oportunidadId: 'op-1' }))
|
fetchMock.mockResolvedValueOnce(respuesta({ id: 'l-1', estado: 'NUEVO', oportunidadId: 'op-1' }))
|
||||||
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
||||||
llenarRequeridos()
|
llenarRequeridos()
|
||||||
fireEvent.change(screen.getByLabelText(/Departamento o casa de interés/), { target: { value: 'Casa' } })
|
fireEvent.change(screen.getByLabelText(/Desarrollo de interés/), { target: { value: 'SENDA' } })
|
||||||
|
fireEvent.change(screen.getByLabelText(/Departamento o casa de interés/), { target: { value: 'HIMALAYA' } })
|
||||||
fireEvent.change(screen.getByLabelText(/Medio de contacto/), { target: { value: 'Llamada' } })
|
fireEvent.change(screen.getByLabelText(/Medio de contacto/), { target: { value: 'Llamada' } })
|
||||||
fireEvent.change(screen.getByLabelText(/^Edad/), { target: { value: '35' } })
|
fireEvent.change(screen.getByLabelText(/^Edad/), { target: { value: '35' } })
|
||||||
fireEvent.change(screen.getByLabelText(/^Estado civil/), { target: { value: 'Casado(a)' } })
|
fireEvent.change(screen.getByLabelText(/^Estado civil/), { target: { value: 'Casado(a)' } })
|
||||||
@@ -109,7 +122,7 @@ describe('AltaAfluenciaForm', () => {
|
|||||||
await screen.findByRole('status')
|
await screen.findByRole('status')
|
||||||
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]
|
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]
|
||||||
expect(JSON.parse(String(init.body))).toMatchObject({
|
expect(JSON.parse(String(init.body))).toMatchObject({
|
||||||
tipoPropiedad: 'Casa',
|
modelo: 'HIMALAYA',
|
||||||
medioContacto: 'Llamada',
|
medioContacto: 'Llamada',
|
||||||
edad: '35',
|
edad: '35',
|
||||||
estadoCivil: 'Casado(a)',
|
estadoCivil: 'Casado(a)',
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { crearLead, type LeadResumen } from '../services/api'
|
|||||||
import {
|
import {
|
||||||
DESARROLLOS_LEAD,
|
DESARROLLOS_LEAD,
|
||||||
ESTADOS_CIVILES,
|
ESTADOS_CIVILES,
|
||||||
|
MODELOS_POR_PROYECTO,
|
||||||
OPCIONES_CALIFICA,
|
OPCIONES_CALIFICA,
|
||||||
ORIGENES_LEAD,
|
ORIGENES_LEAD,
|
||||||
TIPOS_CREDITO,
|
TIPOS_CREDITO,
|
||||||
TIPOS_ECONOMIA,
|
TIPOS_ECONOMIA,
|
||||||
TIPOS_EMPLEO,
|
TIPOS_EMPLEO,
|
||||||
TIPOS_PROPIEDAD_LEAD,
|
|
||||||
} from '../data/constantes'
|
} from '../data/constantes'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -38,7 +38,7 @@ const FORM_INICIAL = {
|
|||||||
email: '',
|
email: '',
|
||||||
origen: '',
|
origen: '',
|
||||||
desarrollo: '',
|
desarrollo: '',
|
||||||
tipoPropiedad: '',
|
modelo: '',
|
||||||
medioContacto: '',
|
medioContacto: '',
|
||||||
edad: '',
|
edad: '',
|
||||||
estadoCivil: '',
|
estadoCivil: '',
|
||||||
@@ -69,6 +69,14 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
|
|||||||
setErrores((prev) => ({ ...prev, [campo]: undefined }))
|
setErrores((prev) => ({ ...prev, [campo]: undefined }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Al cambiar de desarrollo se resetea el modelo (las opciones dependen de él)
|
||||||
|
const cambiarDesarrollo = (e: { target: { value: string } }) => {
|
||||||
|
setForm((prev) => ({ ...prev, desarrollo: e.target.value, modelo: '' }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelosDelDesarrollo =
|
||||||
|
MODELOS_POR_PROYECTO[form.desarrollo as keyof typeof MODELOS_POR_PROYECTO] ?? []
|
||||||
|
|
||||||
const validar = (): boolean => {
|
const validar = (): boolean => {
|
||||||
const nuevos: Errores = {}
|
const nuevos: Errores = {}
|
||||||
if (!form.nombre.trim()) nuevos.nombre = 'El nombre es obligatorio'
|
if (!form.nombre.trim()) nuevos.nombre = 'El nombre es obligatorio'
|
||||||
@@ -91,7 +99,7 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
|
|||||||
...(form.email.trim() ? { email: form.email.trim() } : {}),
|
...(form.email.trim() ? { email: form.email.trim() } : {}),
|
||||||
...(form.origen ? { origen: form.origen } : {}),
|
...(form.origen ? { origen: form.origen } : {}),
|
||||||
...(form.desarrollo ? { desarrollo: form.desarrollo } : {}),
|
...(form.desarrollo ? { desarrollo: form.desarrollo } : {}),
|
||||||
...(form.tipoPropiedad ? { tipoPropiedad: form.tipoPropiedad } : {}),
|
...(form.modelo ? { modelo: form.modelo } : {}),
|
||||||
...(form.medioContacto.trim() ? { medioContacto: form.medioContacto.trim() } : {}),
|
...(form.medioContacto.trim() ? { medioContacto: form.medioContacto.trim() } : {}),
|
||||||
...(form.edad.trim() ? { edad: form.edad.trim() } : {}),
|
...(form.edad.trim() ? { edad: form.edad.trim() } : {}),
|
||||||
...(form.estadoCivil ? { estadoCivil: form.estadoCivil } : {}),
|
...(form.estadoCivil ? { estadoCivil: form.estadoCivil } : {}),
|
||||||
@@ -212,7 +220,7 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
|
|||||||
<select
|
<select
|
||||||
id="afluencia-desarrollo"
|
id="afluencia-desarrollo"
|
||||||
value={form.desarrollo}
|
value={form.desarrollo}
|
||||||
onChange={cambiar('desarrollo')}
|
onChange={cambiarDesarrollo}
|
||||||
className={inputClase}
|
className={inputClase}
|
||||||
>
|
>
|
||||||
<option value="">Sin especificar</option>
|
<option value="">Sin especificar</option>
|
||||||
@@ -224,18 +232,21 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label htmlFor="afluencia-tipo-propiedad" className="block">
|
<label htmlFor="afluencia-modelo" className="block">
|
||||||
<span className={labelClase}>Departamento o casa de interés</span>
|
<span className={labelClase}>Departamento o casa de interés</span>
|
||||||
<select
|
<select
|
||||||
id="afluencia-tipo-propiedad"
|
id="afluencia-modelo"
|
||||||
value={form.tipoPropiedad}
|
value={form.modelo}
|
||||||
onChange={cambiar('tipoPropiedad')}
|
onChange={cambiar('modelo')}
|
||||||
className={inputClase}
|
disabled={form.desarrollo === ''}
|
||||||
|
className={`${inputClase} disabled:bg-neutral-100 disabled:text-neutral-400`}
|
||||||
>
|
>
|
||||||
<option value="">— Selecciona —</option>
|
<option value="">
|
||||||
{TIPOS_PROPIEDAD_LEAD.map((t) => (
|
{form.desarrollo === '' ? 'Selecciona primero el desarrollo' : '— Selecciona —'}
|
||||||
<option key={t} value={t}>
|
</option>
|
||||||
{t}
|
{modelosDelDesarrollo.map((m) => (
|
||||||
|
<option key={m} value={m}>
|
||||||
|
{m}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ export const TAMANO_MAX_DOCUMENTO = 10 * 1024 * 1024 // 10 MB
|
|||||||
export const ORIGENES_LEAD = ['WhatsApp', 'Web', 'Recomendado', 'Facebook', 'Otro'] as const
|
export const ORIGENES_LEAD = ['WhatsApp', 'Web', 'Recomendado', 'Facebook', 'Otro'] as const
|
||||||
export const DESARROLLOS_LEAD = ['ATELIER', 'DIAGONAL', 'ESTANZA', 'SENDA', 'SOLANA'] as const
|
export const DESARROLLOS_LEAD = ['ATELIER', 'DIAGONAL', 'ESTANZA', 'SENDA', 'SOLANA'] as const
|
||||||
|
|
||||||
// Labels de las columnas status nuevas del tablero Leads (captura de afluencia)
|
// Label de la columna status 'Califica' del tablero Leads (captura de afluencia).
|
||||||
export const TIPOS_PROPIEDAD_LEAD = ['Casa', 'Departamento'] as const
|
// El modelo de interés sale de MODELOS_POR_PROYECTO según el desarrollo elegido.
|
||||||
export const OPCIONES_CALIFICA = ['Sí', 'No', 'Por confirmar'] as const
|
export const OPCIONES_CALIFICA = ['Sí', 'No', 'Por confirmar'] as const
|
||||||
|
|
||||||
export const ASESOR_DEFAULT = 'ESMERALDA BECERRA DOMINGUEZ'
|
export const ASESOR_DEFAULT = 'ESMERALDA BECERRA DOMINGUEZ'
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ export async function crearLead(payload: {
|
|||||||
origen?: string
|
origen?: string
|
||||||
desarrollo?: string
|
desarrollo?: string
|
||||||
notas?: string
|
notas?: string
|
||||||
tipoPropiedad?: string
|
modelo?: string
|
||||||
medioContacto?: string
|
medioContacto?: string
|
||||||
edad?: string
|
edad?: string
|
||||||
estadoCivil?: string
|
estadoCivil?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user