fix(afluencia): dropdowns salarios + lo quiere para + redirect Mi día

- Rango salarial: input → select con 14 rangos (A-0 a M-100001)
- Lo quiere para: input → select con 2 opciones (Vivir / Inversión)
- Validación labels origen/desarrollo tolerante en backend (omitir si no existe)
- Redirect a /hoy/ después de crear afluencia o completar alta cliente
This commit is contained in:
2026-09-08 06:00:24 +00:00
parent dc43cbe1ce
commit f297aabc1a
3 changed files with 64 additions and 15 deletions

View File

@@ -291,17 +291,23 @@ export async function crearAfluencia(
if (existente) return { duplicado: { id: existente.id, nombre: existente.nombre } }
}
if (datos.origen !== undefined) {
// Validación tolerante: si el label no existe en el board, se omite en vez
// de fallar. Esto evita 400 cuando Monday aún no propagó un label nuevo.
let origenSafe = datos.origen
if (origenSafe !== undefined) {
const labels = await monday.labelsDeColumna(boardOportunidades, 'Origen del Lead')
if (!labels.includes(datos.origen)) {
throw new AppError(400, `Origen inválido: "${datos.origen}". Labels válidos: ${labels.join(', ')}`)
if (!labels.includes(origenSafe)) {
console.warn(`Origen "${origenSafe}" no es label del board; se omite`)
origenSafe = undefined
}
}
if (datos.desarrollo !== undefined) {
let desarrolloSafe = datos.desarrollo
if (desarrolloSafe !== undefined) {
const labels = await monday.labelsDeColumna(boardOportunidades, 'Desarrollo')
if (!labels.includes(datos.desarrollo)) {
throw new AppError(400, `Desarrollo inválido: "${datos.desarrollo}". Labels válidos: ${labels.join(', ')}`)
if (!labels.includes(desarrolloSafe)) {
console.warn(`Desarrollo "${desarrolloSafe}" no es label del board; se omite`)
desarrolloSafe = undefined
}
}
@@ -309,8 +315,8 @@ export async function crearAfluencia(
nombre: datos.nombre,
telefono: datos.telefono,
email: datos.email,
desarrolloLabel: datos.desarrollo,
origenLabel: datos.origen,
desarrolloLabel: desarrolloSafe,
origenLabel: origenSafe,
modeloLabel: datos.modelo,
medioContacto: datos.medioContacto,
edad: datos.edad,

View File

@@ -1,4 +1,4 @@
import { useState, type FormEvent } from 'react'
import { useEffect, useState, type FormEvent } from 'react'
import { z } from 'zod'
import { crearAfluencia, type LeadResumen } from '../services/api'
import BusquedaLead from './BusquedaLead'
@@ -189,6 +189,15 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor
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">
@@ -202,6 +211,9 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor
? '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"
@@ -427,13 +439,27 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor
<label htmlFor="afluencia-rango-salarial" className="block">
<span className={labelClase}>Rango salarial</span>
<input
<select
id="afluencia-rango-salarial"
type="text"
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">
@@ -489,13 +515,16 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor
<label htmlFor="afluencia-lo-quiere-para" className="block">
<span className={labelClase}>Lo quiere para</span>
<input
<select
id="afluencia-lo-quiere-para"
type="text"
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">

View File

@@ -308,6 +308,15 @@ export default function AltaClientePage() {
}
}
// Redirigir a Mi día después de completar el alta
useEffect(() => {
if (!registro) return
const timer = setTimeout(() => {
window.location.href = '/hoy/'
}, 2000)
return () => clearTimeout(timer)
}, [registro])
// Genera la liga pública y copia al portapapeles el texto listo para WhatsApp
const generarLiga = async () => {
setErrorLiga('')
@@ -635,6 +644,11 @@ export default function AltaClientePage() {
Alta registrada en el servidor: {registro}
</p>
)}
{registro && (
<p className="mb-6 text-xs text-[#64748B] dark:text-[#94A3B8]">
Redirigiendo a Mi día en unos segundos…
</p>
)}
{sinConexion && (
<p
role="alert"