229 lines
10 KiB
TypeScript
229 lines
10 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
import AltaAfluenciaForm from './AltaAfluenciaForm'
|
|
|
|
type FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
|
|
|
const respuesta = (data: unknown, status = 201) =>
|
|
new Response(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json' } })
|
|
|
|
let fetchMock: ReturnType<typeof vi.fn<FetchFn>>
|
|
|
|
beforeEach(() => {
|
|
fetchMock = vi.fn<FetchFn>()
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
})
|
|
|
|
afterEach(() => {
|
|
cleanup()
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
const llenarRequeridos = () => {
|
|
fireEvent.change(screen.getByLabelText(/^Nombre/), { target: { value: 'Ana Demo' } })
|
|
fireEvent.change(screen.getByLabelText(/^Teléfono/), { target: { value: '6641234567' } })
|
|
}
|
|
|
|
describe('AltaAfluenciaForm', () => {
|
|
it('sin nombre ni teléfono no llama al API y muestra los errores', () => {
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
expect(screen.getByText('El nombre es obligatorio')).toBeTruthy()
|
|
expect(screen.getByText('El teléfono es obligatorio')).toBeTruthy()
|
|
})
|
|
|
|
it('con email inválido no llama al API', () => {
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
llenarRequeridos()
|
|
fireEvent.change(screen.getByLabelText(/^Correo/), { target: { value: 'correo-malo' } })
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
expect(screen.getByText('Correo electrónico inválido')).toBeTruthy()
|
|
})
|
|
|
|
it('al registrar (201) muestra el mensaje de éxito', async () => {
|
|
fetchMock.mockResolvedValueOnce(respuesta({ id: 'l-1', estado: 'NUEVO', oportunidadId: 'op-1' }))
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
llenarRequeridos()
|
|
fireEvent.change(screen.getByLabelText(/¿Cómo se enteró de nosotros\?/), { target: { value: 'PASE' } })
|
|
fireEvent.change(screen.getByLabelText(/Desarrollo de interés/), { target: { value: 'SENDA' } })
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
expect((await screen.findByRole('status')).textContent).toContain(
|
|
'Registrado en afluencia — se asignará por round-robin',
|
|
)
|
|
const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]
|
|
expect(url).toContain('/afluencias')
|
|
expect(JSON.parse(String(init.body))).toMatchObject({
|
|
nombre: 'Ana Demo',
|
|
telefono: '+52 6641234567',
|
|
origen: 'PASE',
|
|
desarrollo: 'SENDA',
|
|
})
|
|
})
|
|
|
|
it('con asignacion "capturista" el mensaje dice que quedó asignado a ti', async () => {
|
|
fetchMock.mockResolvedValueOnce(
|
|
respuesta({ id: 'l-1', estado: 'NUEVO', oportunidadId: 'op-1', asignacion: 'capturista' }),
|
|
)
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
llenarRequeridos()
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
expect((await screen.findByRole('status')).textContent).toContain(
|
|
'Registrado en afluencia — asignado a ti',
|
|
)
|
|
})
|
|
|
|
it('renderiza los selects de calificación con las opciones de los catálogos', () => {
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
|
|
const opcionesDe = (label: RegExp) =>
|
|
Array.from((screen.getByLabelText(label) as HTMLSelectElement).options).map((o) => o.value)
|
|
|
|
// 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(/Tipo de empleo/)).toEqual(['', 'Asalariado', 'Independiente'])
|
|
expect(opcionesDe(/^Crédito/)).toEqual([
|
|
'',
|
|
'Infonavit',
|
|
'Apoyo Infonavit',
|
|
'Cofinavit',
|
|
'Segundo crédito',
|
|
'Contado',
|
|
'Fovissste',
|
|
'Cofi-Fovissste',
|
|
'Bancario',
|
|
])
|
|
expect(opcionesDe(/Tipo de economía/)).toEqual(['', 'Economía mexicana', 'Economía americana', 'Mixta'])
|
|
expect(opcionesDe(/^Califica/)).toEqual(['', 'Sí', 'No', 'Por confirmar'])
|
|
})
|
|
|
|
it('el payload incluye los campos de calificación capturados', async () => {
|
|
fetchMock.mockResolvedValueOnce(respuesta({ id: 'l-1', estado: 'NUEVO', oportunidadId: 'op-1' }))
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
llenarRequeridos()
|
|
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(/Número de dependientes/), { target: { value: '3' } })
|
|
fireEvent.change(screen.getByLabelText(/^Edad/), { target: { value: '35' } })
|
|
fireEvent.change(screen.getByLabelText(/^Estado civil/), { target: { value: 'Casado(a)' } })
|
|
fireEvent.change(screen.getByLabelText(/Tipo de empleo/), { target: { value: 'Asalariado' } })
|
|
fireEvent.change(screen.getByLabelText(/^Ocupación/), { target: { value: 'Ingeniero' } })
|
|
fireEvent.change(screen.getByLabelText(/Rango salarial/), { target: { value: '20,000-40,000' } })
|
|
fireEvent.change(screen.getByLabelText(/^Crédito/), { target: { value: 'Infonavit' } })
|
|
fireEvent.change(screen.getByLabelText(/Tipo de economía/), { target: { value: 'Economía mexicana' } })
|
|
fireEvent.change(screen.getByLabelText(/^Califica/), { target: { value: 'Sí' } })
|
|
fireEvent.change(screen.getByLabelText(/Lo quiere para/), { target: { value: 'Para vivir' } })
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
await screen.findByRole('status')
|
|
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]
|
|
expect(JSON.parse(String(init.body))).toMatchObject({
|
|
modelo: 'HIMALAYA',
|
|
numDependientes: '3',
|
|
edad: '35',
|
|
estadoCivil: 'Casado(a)',
|
|
tipoEmpleo: 'Asalariado',
|
|
ocupacion: 'Ingeniero',
|
|
rangoSalarial: '20,000-40,000',
|
|
credito: 'Infonavit',
|
|
tipoEconomia: 'Economía mexicana',
|
|
califica: 'Sí',
|
|
loQuierePara: 'Para vivir',
|
|
})
|
|
})
|
|
|
|
it('sin campos de calificación el payload no los incluye', async () => {
|
|
fetchMock.mockResolvedValueOnce(respuesta({ id: 'l-1', estado: 'NUEVO', oportunidadId: 'op-1' }))
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
llenarRequeridos()
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
await screen.findByRole('status')
|
|
const [, init] = fetchMock.mock.calls[0] as [string, RequestInit]
|
|
const cuerpo = JSON.parse(String(init.body)) as Record<string, unknown>
|
|
expect(cuerpo).toEqual({ nombre: 'Ana Demo', telefono: '+52 6641234567' })
|
|
})
|
|
|
|
it('con 409 muestra el duplicado con el nombre del lead existente', async () => {
|
|
fetchMock.mockResolvedValueOnce(
|
|
respuesta({ error: 'Ya existe un lead con ese teléfono', leadId: 'l-9', nombre: 'ANA DEMO' }, 409),
|
|
)
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
llenarRequeridos()
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
expect((await screen.findByRole('alert')).textContent).toContain(
|
|
'Ya existe un registro con ese teléfono: ANA DEMO.',
|
|
)
|
|
})
|
|
|
|
it('con error de red muestra aviso amarillo: el registro se guardó igualmente', async () => {
|
|
fetchMock.mockRejectedValueOnce(new Error('red caída'))
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
llenarRequeridos()
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
expect((await screen.findByRole('status')).textContent).toContain(
|
|
'La afluencia se registró, pero hubo un problema de conexión. Verifica en Mi día.',
|
|
)
|
|
})
|
|
|
|
it('"Capturar otro" limpia el formulario tras el éxito', async () => {
|
|
fetchMock.mockResolvedValueOnce(respuesta({ id: 'l-1', estado: 'NUEVO', oportunidadId: 'op-1' }))
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={() => undefined} />)
|
|
llenarRequeridos()
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
fireEvent.click(await screen.findByRole('button', { name: /Capturar otro/ }))
|
|
|
|
expect((screen.getByLabelText(/^Nombre/) as HTMLInputElement).value).toBe('')
|
|
expect((screen.getByLabelText(/^Teléfono/) as HTMLInputElement).value).toBe('')
|
|
expect(screen.queryByRole('status')).toBeNull()
|
|
})
|
|
|
|
it('"Volver" avisa a la pantalla de entrada', () => {
|
|
const onVolver = vi.fn()
|
|
render(<AltaAfluenciaForm onVolver={onVolver} onPasarAAltaCliente={() => undefined} />)
|
|
fireEvent.click(screen.getByRole('button', { name: 'Volver' }))
|
|
expect(onVolver).toHaveBeenCalled()
|
|
})
|
|
|
|
it('tras el éxito, "Pasar a Alta de Cliente" entrega el lead recién creado', async () => {
|
|
fetchMock.mockResolvedValueOnce(respuesta({ id: 'op-1', estado: 'Afluencia', oportunidadId: 'op-1' }))
|
|
const onPasar = vi.fn()
|
|
render(<AltaAfluenciaForm onVolver={() => undefined} onPasarAAltaCliente={onPasar} />)
|
|
llenarRequeridos()
|
|
fireEvent.change(screen.getByLabelText(/^Correo/), { target: { value: 'ana@correo.com' } })
|
|
fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ }))
|
|
|
|
fireEvent.click(await screen.findByRole('button', { name: /Pasar a Alta de Cliente/ }))
|
|
|
|
expect(onPasar).toHaveBeenCalledWith({
|
|
id: 'op-1',
|
|
nombre: 'Ana Demo',
|
|
telefono: '+52 6641234567',
|
|
email: 'ana@correo.com',
|
|
estado: 'Afluencia',
|
|
origen: '',
|
|
fuente: 'oportunidad', // la captura nueva vive en Oportunidades
|
|
})
|
|
})
|
|
})
|