680 lines
26 KiB
TypeScript
680 lines
26 KiB
TypeScript
import { useEffect, useState, type FormEvent } from 'react'
|
|
import { z } from 'zod'
|
|
import { crearAfluencia, type LeadResumen } from '../services/api'
|
|
import BusquedaLead from './BusquedaLead'
|
|
import {
|
|
DESARROLLOS_LEAD,
|
|
ESTADOS_CIVILES,
|
|
MODELOS_POR_PROYECTO,
|
|
OPCIONES_CALIFICA,
|
|
ORIGENES_LEAD,
|
|
TIPOS_CREDITO,
|
|
TIPOS_ECONOMIA,
|
|
TIPOS_EMPLEO,
|
|
} from '../data/constantes'
|
|
|
|
interface Props {
|
|
onVolver: () => void
|
|
/** Conversión directa: tras registrar la afluencia, abrir el alta completa
|
|
* prellenada con los datos recién capturados (sin rebuscar el lead) */
|
|
onPasarAAltaCliente: (lead: LeadResumen) => void
|
|
/** Prefill desde Mis oportunidades (Mi día): valores iniciales del formulario */
|
|
valoresIniciales?: Partial<typeof FORM_INICIAL>
|
|
/** Oportunidad a ACTUALIZAR al enviar (sin duplicar el registro) */
|
|
oportunidadId?: string
|
|
}
|
|
|
|
const emailSchema = z.string().trim().email()
|
|
|
|
const inputClase =
|
|
'w-full rounded-lg border border-[#D1D5DB] bg-white px-3 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2.5 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20'
|
|
|
|
const labelClase = 'mb-1 block text-xs font-medium text-[#4B5563] dark:text-[#CBD5E1]'
|
|
|
|
interface Errores {
|
|
nombre?: string
|
|
telefono?: string
|
|
email?: string
|
|
}
|
|
|
|
const FORM_INICIAL = {
|
|
nombre: '',
|
|
nombrePila: '',
|
|
apellidoPaterno: '',
|
|
apellidoMaterno: '',
|
|
telefono: '',
|
|
email: '',
|
|
origen: '',
|
|
desarrollo: '',
|
|
modelo: '',
|
|
numDependientes: '',
|
|
edad: '',
|
|
estadoCivil: '',
|
|
tipoEmpleo: '',
|
|
ocupacion: '',
|
|
rangoSalarial: '',
|
|
credito: '',
|
|
tipoEconomia: '',
|
|
califica: '',
|
|
loQuierePara: '',
|
|
notas: '',
|
|
}
|
|
|
|
/**
|
|
* Captura ligera del interesado al entrar al embudo (POST /leads): crea el
|
|
* lead y su oportunidad en etapa Afluencia. El alta completa de
|
|
* cliente sigue siendo el wizard del picker.
|
|
*/
|
|
export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valoresIniciales, oportunidadId }: Props) {
|
|
const [form, setForm] = useState({ ...FORM_INICIAL, ...valoresIniciales })
|
|
const [lada, setLada] = useState<'MX' | 'US'>(
|
|
(valoresIniciales?.telefono ?? '').trim().startsWith('+1') ? 'US' : 'MX',
|
|
)
|
|
const [errores, setErrores] = useState<Errores>({})
|
|
const [enviando, setEnviando] = useState(false)
|
|
const [leadCreado, setLeadCreado] = useState<LeadResumen | null>(null)
|
|
const [asignacion, setAsignacion] = useState<'capturista' | 'round-robin' | null>(null)
|
|
const [aviso, setAviso] = useState('')
|
|
const [avisoTipo, setAvisoTipo] = useState<'error' | 'advertencia'>('error')
|
|
// Registro existente con ese teléfono: permite actualizarlo en vez de fallar
|
|
const [duplicado, setDuplicado] = useState<{ id: string; nombre: string } | null>(null)
|
|
|
|
const cambiar = (campo: keyof typeof FORM_INICIAL) => (e: { target: { value: string } }) => {
|
|
setForm((prev) => ({ ...prev, [campo]: e.target.value }))
|
|
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] ?? []
|
|
|
|
/**
|
|
* Jala los datos de un lead u oportunidad ya capturados:
|
|
* nombre/contacto siempre; origen, desarrollo y modelo solo si el valor del
|
|
* lead existe entre las opciones del formulario.
|
|
*/
|
|
const prellenarDesdeLead = (lead: LeadResumen) => {
|
|
// Teléfono con prefijo internacional: separa lada y número para el selector
|
|
const tel = lead.telefono.trim()
|
|
const esUsa = tel.startsWith('+1')
|
|
// Strip lada: +52 or +1 prefix
|
|
let numero = tel
|
|
if (tel.startsWith('+52 ')) numero = tel.slice(4).trim()
|
|
else if (tel.startsWith('+52')) numero = tel.slice(3).trim()
|
|
else if (tel.startsWith('+1 ')) numero = tel.slice(3).trim()
|
|
else if (tel.startsWith('+1')) numero = tel.slice(2).trim()
|
|
setLada(esUsa ? 'US' : 'MX')
|
|
setForm((prev) => {
|
|
const desarrollo =
|
|
lead.desarrollo !== undefined && (DESARROLLOS_LEAD as readonly string[]).includes(lead.desarrollo)
|
|
? lead.desarrollo
|
|
: prev.desarrollo
|
|
const modelos = MODELOS_POR_PROYECTO[desarrollo as keyof typeof MODELOS_POR_PROYECTO] ?? []
|
|
const modelo =
|
|
lead.modeloInteres !== undefined && (modelos as readonly string[]).includes(lead.modeloInteres)
|
|
? lead.modeloInteres
|
|
: ''
|
|
// Split name: lead.nombre may contain full name or just first name
|
|
const partes = lead.nombre.trim().split(/\s+/)
|
|
const nombrePila = partes[0] || ''
|
|
const apellidoPaterno = partes.length > 1 ? partes[1] : ''
|
|
const apellidoMaterno = partes.length > 2 ? partes.slice(2).join(' ') : ''
|
|
return {
|
|
...prev,
|
|
nombre: lead.nombre,
|
|
nombrePila,
|
|
apellidoPaterno,
|
|
apellidoMaterno,
|
|
telefono: numero,
|
|
email: lead.email,
|
|
origen: (ORIGENES_LEAD as readonly string[]).includes(lead.origen) ? lead.origen : prev.origen,
|
|
desarrollo,
|
|
modelo,
|
|
}
|
|
})
|
|
setErrores({})
|
|
setAviso('')
|
|
}
|
|
|
|
const validar = (): boolean => {
|
|
const nuevos: Errores = {}
|
|
if (!form.nombre.trim()) nuevos.nombre = 'El nombre es obligatorio'
|
|
if (!form.telefono.trim()) nuevos.telefono = 'El teléfono es obligatorio'
|
|
if (form.email.trim() && !emailSchema.safeParse(form.email).success) {
|
|
nuevos.email = 'Correo electrónico inválido'
|
|
}
|
|
setErrores(nuevos)
|
|
return Object.keys(nuevos).length === 0
|
|
}
|
|
|
|
const enviar = async (e: FormEvent) => {
|
|
e.preventDefault()
|
|
setAviso('')
|
|
setDuplicado(null)
|
|
if (!validar()) return
|
|
setEnviando(true)
|
|
const res = await crearAfluencia(construirPayload(oportunidadId))
|
|
setEnviando(false)
|
|
procesarResultado(res, oportunidadId)
|
|
}
|
|
|
|
/** Reenvía la captura como ACTUALIZACIÓN del registro existente (duplicado) */
|
|
const actualizarExistente = async () => {
|
|
if (!duplicado) return
|
|
setAviso('')
|
|
setEnviando(true)
|
|
const res = await crearAfluencia(construirPayload(duplicado.id))
|
|
setEnviando(false)
|
|
if (res.ok) {
|
|
setDuplicado(null)
|
|
procesarResultado(res, duplicado.id)
|
|
return
|
|
}
|
|
if (res.motivo === 'duplicado') {
|
|
setDuplicado({ id: res.leadId, nombre: res.nombre })
|
|
setAvisoTipo('error')
|
|
setAviso(`No se pudo actualizar: el teléfono pertenece a ${res.nombre}.`)
|
|
return
|
|
}
|
|
setAvisoTipo('advertencia')
|
|
setAviso('La afluencia se actualizó, pero hubo un problema de conexión. Verifica en Mi día.')
|
|
}
|
|
|
|
/** Teléfono con lada: "<prefijo> <número>" (si ya trae +, se respeta tal cual) */
|
|
const telefonoConLada = () => {
|
|
const numeroTel = form.telefono.trim()
|
|
return numeroTel === '' ? '' : numeroTel.startsWith('+') ? numeroTel : `${lada === 'US' ? '+1' : '+52'} ${numeroTel}`
|
|
}
|
|
|
|
/** Payload del alta/actualización de afluencia (teléfono normalizado con lada) */
|
|
const construirPayload = (oportunidadIdElegido?: string) => {
|
|
const telefono = telefonoConLada()
|
|
return {
|
|
nombre: [form.nombrePila, form.apellidoPaterno, form.apellidoMaterno].filter(Boolean).join(' ').trim() || form.nombre.trim(),
|
|
telefono,
|
|
...(oportunidadIdElegido !== undefined ? { oportunidadId: oportunidadIdElegido } : {}),
|
|
...(form.email.trim() ? { email: form.email.trim() } : {}),
|
|
...(form.origen ? { origen: form.origen } : {}),
|
|
...(form.desarrollo ? { desarrollo: form.desarrollo } : {}),
|
|
...(form.modelo ? { modelo: form.modelo } : {}),
|
|
...(form.numDependientes ? { numDependientes: form.numDependientes } : {}),
|
|
...(form.edad.trim() ? { edad: form.edad.trim() } : {}),
|
|
...(form.estadoCivil ? { estadoCivil: form.estadoCivil } : {}),
|
|
...(form.tipoEmpleo ? { tipoEmpleo: form.tipoEmpleo } : {}),
|
|
...(form.ocupacion.trim() ? { ocupacion: form.ocupacion.trim() } : {}),
|
|
...(form.rangoSalarial.trim() ? { rangoSalarial: form.rangoSalarial.trim() } : {}),
|
|
...(form.credito ? { credito: form.credito } : {}),
|
|
...(form.tipoEconomia ? { tipoEconomia: form.tipoEconomia } : {}),
|
|
...(form.califica ? { califica: form.califica } : {}),
|
|
...(form.loQuierePara.trim() ? { loQuierePara: form.loQuierePara.trim() } : {}),
|
|
...(form.notas.trim() ? { notas: form.notas.trim() } : {}),
|
|
}
|
|
}
|
|
|
|
/** Resultado del API: éxito, duplicado (ofrece actualizar) o problema de red */
|
|
const procesarResultado = (
|
|
res: Awaited<ReturnType<typeof crearAfluencia>>,
|
|
oportunidadIdElegido?: string,
|
|
) => {
|
|
if (res.ok) {
|
|
setLeadCreado({
|
|
id: res.lead.id,
|
|
nombre: [form.nombrePila, form.apellidoPaterno, form.apellidoMaterno].filter(Boolean).join(' ').trim() || form.nombre.trim(),
|
|
telefono: telefonoConLada(),
|
|
email: form.email.trim(),
|
|
estado: res.lead.estado,
|
|
origen: form.origen,
|
|
fuente: 'oportunidad', // la captura nueva vive en el tablero Oportunidades
|
|
})
|
|
setAsignacion(res.lead.asignacion ?? 'round-robin')
|
|
return
|
|
}
|
|
if (res.motivo === 'duplicado') {
|
|
setDuplicado({ id: res.leadId, nombre: res.nombre })
|
|
setAvisoTipo('error')
|
|
setAviso(`Ya existe un registro con ese teléfono: ${res.nombre}.`)
|
|
return
|
|
}
|
|
if (oportunidadIdElegido !== undefined) {
|
|
// El backend es local: la actualización YA se guardó aunque el fetch se cortara
|
|
setAvisoTipo('advertencia')
|
|
setAviso('La afluencia se actualizó, pero hubo un problema de conexión. Verifica en Mi día.')
|
|
return
|
|
}
|
|
setAvisoTipo('advertencia')
|
|
setAviso('La afluencia se registró, pero hubo un problema de conexión. Verifica en Mi día.')
|
|
}
|
|
|
|
const capturarOtro = () => {
|
|
setForm(FORM_INICIAL)
|
|
setLada('MX')
|
|
setErrores({})
|
|
setAviso('')
|
|
setLeadCreado(null)
|
|
setAsignacion(null)
|
|
}
|
|
|
|
// Redirigir a Mi día 2 s después de registrar la afluencia
|
|
useEffect(() => {
|
|
if (leadCreado === null) return
|
|
const timer = setTimeout(() => {
|
|
window.location.href = '/hoy/'
|
|
}, 2000)
|
|
return () => clearTimeout(timer)
|
|
}, [leadCreado])
|
|
|
|
if (leadCreado !== null) {
|
|
return (
|
|
<div className="max-w-md">
|
|
<p
|
|
role="status"
|
|
className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700"
|
|
>
|
|
{oportunidadId !== undefined
|
|
? 'Afluencia actualizada — el perfil completo quedó guardado en la oportunidad'
|
|
: asignacion === 'capturista'
|
|
? 'Registrado en afluencia — asignado a ti'
|
|
: 'Registrado en afluencia — se asignará por round-robin'}
|
|
</p>
|
|
<p className="mt-2 text-xs text-[#64748B] dark:text-[#94A3B8]">
|
|
Redirigiendo a Mi día en unos segundos…
|
|
</p>
|
|
<div className="mt-6 flex flex-wrap gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={() => onPasarAAltaCliente(leadCreado)}
|
|
className="rounded-lg bg-accent px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover"
|
|
>
|
|
Pasar a Alta de Cliente
|
|
</button>
|
|
{oportunidadId === undefined && (
|
|
<button
|
|
type="button"
|
|
onClick={capturarOtro}
|
|
className="rounded-lg border border-accent bg-white px-5 py-2.5 text-sm font-medium text-accent shadow-sm transition-all duration-100 ease-out hover:bg-accent hover:text-white"
|
|
>
|
|
Capturar otro
|
|
</button>
|
|
)}
|
|
<button
|
|
type="button"
|
|
onClick={onVolver}
|
|
className="rounded-lg border border-[#D1D5DB] bg-white px-5 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2.5 text-sm font-medium text-[#374151] dark:text-[#CBD5E1] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
|
|
>
|
|
Volver
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={(e) => void enviar(e)} noValidate>
|
|
{oportunidadId !== undefined ? (
|
|
<p
|
|
role="note"
|
|
className="mb-6 rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-xs font-medium text-blue-800"
|
|
>
|
|
Estás completando la afluencia de {form.nombre || 'esta oportunidad'} — al enviar se actualiza su
|
|
oportunidad ya capturada (no se duplica).
|
|
</p>
|
|
) : (
|
|
<div className="mb-6 border-b border-[#E5E7EB] pb-6">
|
|
<BusquedaLead onSeleccion={prellenarDesdeLead} />
|
|
<p className="mt-2 text-xs text-[#64748B] dark:text-[#94A3B8]">
|
|
Si el interesado ya está capturado (lead u oportunidad), sus datos se prellenan solos.
|
|
</p>
|
|
</div>
|
|
)}
|
|
<div className="grid gap-6 sm:grid-cols-2">
|
|
<label htmlFor="afluencia-nombre-pila" className="block">
|
|
<span className={labelClase}>
|
|
Nombre(s)<span className="text-red-700"> *</span>
|
|
</span>
|
|
<input
|
|
id="afluencia-nombre-pila"
|
|
type="text"
|
|
value={form.nombrePila}
|
|
onChange={cambiar('nombrePila')}
|
|
className={`${inputClase} ${errores.nombre ? 'border-red-400' : ''}`}
|
|
/>
|
|
{errores.nombre && <p className="mt-1 text-xs text-red-700">{errores.nombre}</p>}
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-apellido-paterno" className="block">
|
|
<span className={labelClase}>Apellido paterno</span>
|
|
<input
|
|
id="afluencia-apellido-paterno"
|
|
type="text"
|
|
value={form.apellidoPaterno}
|
|
onChange={cambiar('apellidoPaterno')}
|
|
className={inputClase}
|
|
/>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-apellido-materno" className="block">
|
|
<span className={labelClase}>Apellido materno</span>
|
|
<input
|
|
id="afluencia-apellido-materno"
|
|
type="text"
|
|
value={form.apellidoMaterno}
|
|
onChange={cambiar('apellidoMaterno')}
|
|
className={inputClase}
|
|
/>
|
|
</label>
|
|
|
|
<div>
|
|
<label htmlFor="afluencia-telefono" className={labelClase}>
|
|
Teléfono<span className="text-red-700"> *</span>
|
|
</label>
|
|
<div className="flex gap-2">
|
|
<select
|
|
aria-label="Lada del teléfono"
|
|
value={lada}
|
|
onChange={(e) => setLada(e.target.value as 'MX' | 'US')}
|
|
className="w-28 shrink-0 rounded-lg border border-[#D1D5DB] bg-white px-2 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2.5 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
|
|
>
|
|
<option value="MX">🇲🇽 +52</option>
|
|
<option value="US">🇺🇸 +1</option>
|
|
</select>
|
|
<input
|
|
id="afluencia-telefono"
|
|
type="tel"
|
|
placeholder="664 123 4567"
|
|
value={form.telefono}
|
|
onChange={cambiar('telefono')}
|
|
className={`${inputClase} ${errores.telefono ? 'border-red-400' : ''}`}
|
|
/>
|
|
</div>
|
|
{errores.telefono && <p className="mt-1 text-xs text-red-700">{errores.telefono}</p>}
|
|
</div>
|
|
|
|
<label htmlFor="afluencia-email" className="block">
|
|
<span className={labelClase}>Correo</span>
|
|
<input
|
|
id="afluencia-email"
|
|
type="email"
|
|
value={form.email}
|
|
onChange={cambiar('email')}
|
|
className={`${inputClase} ${errores.email ? 'border-red-400' : ''}`}
|
|
/>
|
|
{errores.email && <p className="mt-1 text-xs text-red-700">{errores.email}</p>}
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-desarrollo" className="block">
|
|
<span className={labelClase}>Desarrollo de interés</span>
|
|
<select
|
|
id="afluencia-desarrollo"
|
|
value={form.desarrollo}
|
|
onChange={cambiarDesarrollo}
|
|
className={inputClase}
|
|
>
|
|
<option value="">Sin especificar</option>
|
|
{DESARROLLOS_LEAD.map((d) => (
|
|
<option key={d} value={d}>
|
|
{d}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-modelo" className="block">
|
|
<span className={labelClase}>Departamento o casa de interés</span>
|
|
<select
|
|
id="afluencia-modelo"
|
|
value={form.modelo}
|
|
onChange={cambiar('modelo')}
|
|
disabled={form.desarrollo === ''}
|
|
className={`${inputClase} disabled:bg-[#F1F5F9] disabled:text-[#94A3B8]`}
|
|
>
|
|
<option value="">
|
|
{form.desarrollo === '' ? 'Selecciona primero el desarrollo' : '— Selecciona —'}
|
|
</option>
|
|
{modelosDelDesarrollo.map((m) => (
|
|
<option key={m} value={m}>
|
|
{m}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-num-dependientes" className="block">
|
|
<span className={labelClase}>Número de dependientes</span>
|
|
<select
|
|
id="afluencia-num-dependientes"
|
|
value={form.numDependientes}
|
|
onChange={cambiar('numDependientes')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">Seleccionar...</option>
|
|
<option value="1">1</option>
|
|
<option value="2">2</option>
|
|
<option value="3">3</option>
|
|
<option value="4">4</option>
|
|
<option value="5">5 o más</option>
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-origen" className="block">
|
|
<span className={labelClase}>¿Cómo se enteró de nosotros?</span>
|
|
<select
|
|
id="afluencia-origen"
|
|
value={form.origen}
|
|
onChange={cambiar('origen')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">— Selecciona —</option>
|
|
{ORIGENES_LEAD.map((o) => (
|
|
<option key={o} value={o}>
|
|
{o}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-edad" className="block">
|
|
<span className={labelClase}>Edad</span>
|
|
<input
|
|
id="afluencia-edad"
|
|
type="number"
|
|
min={15}
|
|
max={120}
|
|
value={form.edad}
|
|
onChange={cambiar('edad')}
|
|
className={inputClase}
|
|
/>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-estado-civil" className="block">
|
|
<span className={labelClase}>Estado civil</span>
|
|
<select
|
|
id="afluencia-estado-civil"
|
|
value={form.estadoCivil}
|
|
onChange={cambiar('estadoCivil')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">— Selecciona —</option>
|
|
{ESTADOS_CIVILES.map((e) => (
|
|
<option key={e.valor} value={e.etiqueta}>
|
|
{e.etiqueta}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-tipo-empleo" className="block">
|
|
<span className={labelClase}>Tipo de empleo</span>
|
|
<select
|
|
id="afluencia-tipo-empleo"
|
|
value={form.tipoEmpleo}
|
|
onChange={cambiar('tipoEmpleo')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">— Selecciona —</option>
|
|
{TIPOS_EMPLEO.map((t) => (
|
|
<option key={t.valor} value={t.etiqueta}>
|
|
{t.etiqueta}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-ocupacion" className="block">
|
|
<span className={labelClase}>Ocupación</span>
|
|
<input
|
|
id="afluencia-ocupacion"
|
|
type="text"
|
|
value={form.ocupacion}
|
|
onChange={cambiar('ocupacion')}
|
|
className={inputClase}
|
|
/>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-rango-salarial" className="block">
|
|
<span className={labelClase}>Rango salarial</span>
|
|
<select
|
|
id="afluencia-rango-salarial"
|
|
value={form.rangoSalarial}
|
|
onChange={cambiar('rangoSalarial')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">— Selecciona —</option>
|
|
<option value="A-0-10000.00">A-0-10000.00</option>
|
|
<option value="B-10000.01-15000.00">B-10000.01-15000.00</option>
|
|
<option value="C-15000.01-20000.00">C-15000.01-20000.00</option>
|
|
<option value="D-17000.01-22000.00">D-17000.01-22000.00</option>
|
|
<option value="E-20001.00-25000.00">E-20001.00-25000.00</option>
|
|
<option value="F-25001.00-30000.00">F-25001.00-30000.00</option>
|
|
<option value="G-30001.00-35000.00">G-30001.00-35000.00</option>
|
|
<option value="H-35001.00-40000.00">H-35001.00-40000.00</option>
|
|
<option value="I-40001.00-45000.00">I-40001.00-45000.00</option>
|
|
<option value="J-45001.00-50000.00">J-45001.00-50000.00</option>
|
|
<option value="K-50001.00-75000.00">K-50001.00-75000.00</option>
|
|
<option value="L-75001.00-100000.00">L-75001.00-100000.00</option>
|
|
<option value="M-100001.00-500000.00">M-100001.00-500000.00</option>
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-credito" className="block">
|
|
<span className={labelClase}>Crédito</span>
|
|
<select
|
|
id="afluencia-credito"
|
|
value={form.credito}
|
|
onChange={cambiar('credito')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">— Selecciona —</option>
|
|
{TIPOS_CREDITO.map((c) => (
|
|
<option key={c.valor} value={c.etiqueta}>
|
|
{c.etiqueta}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-tipo-economia" className="block">
|
|
<span className={labelClase}>Tipo de economía</span>
|
|
<select
|
|
id="afluencia-tipo-economia"
|
|
value={form.tipoEconomia}
|
|
onChange={cambiar('tipoEconomia')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">— Selecciona —</option>
|
|
{TIPOS_ECONOMIA.map((t) => (
|
|
<option key={t.valor} value={t.etiqueta}>
|
|
{t.etiqueta}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-califica" className="block">
|
|
<span className={labelClase}>Califica</span>
|
|
<select
|
|
id="afluencia-califica"
|
|
value={form.califica}
|
|
onChange={cambiar('califica')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">— Selecciona —</option>
|
|
{OPCIONES_CALIFICA.map((c) => (
|
|
<option key={c} value={c}>
|
|
{c}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-lo-quiere-para" className="block">
|
|
<span className={labelClase}>Lo quiere para</span>
|
|
<select
|
|
id="afluencia-lo-quiere-para"
|
|
value={form.loQuierePara}
|
|
onChange={cambiar('loQuierePara')}
|
|
className={inputClase}
|
|
>
|
|
<option value="">— Selecciona —</option>
|
|
<option value="Para vivir">Para vivir</option>
|
|
<option value="Para inversión">Para inversión</option>
|
|
</select>
|
|
</label>
|
|
|
|
<label htmlFor="afluencia-notas" className="block sm:col-span-2">
|
|
<span className={labelClase}>Notas</span>
|
|
<input
|
|
id="afluencia-notas"
|
|
type="text"
|
|
value={form.notas}
|
|
onChange={cambiar('notas')}
|
|
className={inputClase}
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
{aviso && avisoTipo === 'advertencia' && (
|
|
<p role="status" className="mt-6 rounded-lg border border-yellow-200 bg-yellow-50 px-4 py-3 text-xs font-medium text-yellow-800">
|
|
{aviso}
|
|
</p>
|
|
)}
|
|
|
|
{aviso && avisoTipo === 'error' && (
|
|
<div role="alert" className="mt-6 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-xs text-red-700">
|
|
<p>{aviso}</p>
|
|
{duplicado && (
|
|
<button
|
|
type="button"
|
|
onClick={() => void actualizarExistente()}
|
|
disabled={enviando}
|
|
className="mt-3 rounded-lg bg-accent px-4 py-2 text-xs font-medium text-white shadow-sm transition-colors hover:bg-accent-hover disabled:opacity-50"
|
|
>
|
|
{enviando ? 'Actualizando…' : 'Actualizar este registro con los datos capturados'}
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-8 flex gap-3">
|
|
<button
|
|
type="submit"
|
|
disabled={enviando}
|
|
className="rounded-lg bg-accent px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover disabled:opacity-50"
|
|
>
|
|
{enviando ? 'Registrando…' : oportunidadId !== undefined ? 'Actualizar afluencia' : 'Registrar en afluencia'}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onVolver}
|
|
className="rounded-lg border border-[#D1D5DB] bg-white px-5 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2.5 text-sm font-medium text-[#374151] dark:text-[#CBD5E1] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
|
|
>
|
|
Volver
|
|
</button>
|
|
</div>
|
|
</form>
|
|
)
|
|
}
|