fix(auditoria general): 25 errores encontrados en la revision modulo por modulo
SEGURIDAD (agente IA): - Conversaciones: GET /ai/conversations/:id y el reanudar chat verifican propiedad (antes cualquier usuario leia/continuaba la conversacion de otro). - /ai/documents pasa a solo-admin (antes cualquien podia inyectar o borrar documentos de la base de conocimiento). DATOS / CORRUPCION: - Cotizador: elegir un cliente del maestro en el buscador ya no crea un lead fantasma "Registro previo (CRM anterior)" en el embudo. - Afluencia con oportunidadId inexistente: 404 en vez de crear sombra fantasma. - Cobranza: los 103 planes importados quedaron con vendedor asignado (7 con su asesor real segun el Excel, el resto al admin) y el motor de recordatorios cae al admin cuando un plan no tiene vendedor: antes los 411 recordatorios se registraban 'sin-destinatario' y no los veia nadie. - Backups: script reescrito para el host real (sqlite .backup + uploads + env en /root/backups) y cron instalado (antes: script que asumia docker y sin cron programado = cero respaldos). REPORTES / METRICAS: - Mi tablero: el embudo ya excluye cancelaciones reales (Cancelo/Cancelacion Apartado) y cuenta como vendidos Firma Cliente/Escriturado/Titulado (antes 41 "apartados" donde habia 13 activos y vendidos buscaba una etapa que no existe); tasa de descarte ya compara el mismo periodo. - Scoring: etapa real 'Agendo Cita' (antes 'AGENDO' nunca aplicaba) y los descartados/perdidos ya no reciben score. - ver_cobranza (IA): ignora planes cancelados y no cuenta como vencido el pago que vence hoy. UI / FLUJOS: - mi-dia: typo CANCO -> CANCELO (los cancelados reaparecen en Escrituracion); confirmar cita ya no retrocede la etapa de Cotizador/Aparto (boton y kanban). - alta-clientes: la firma ya no se borra al navegar fuera del paso de firmas; toast de exito solo cuando el API confirma; prefill de apartado trae direccion, estado civil y fecha de nacimiento desde el alta. - Auditoria: el filtro por usuario funciona (el front enviaba 'usuario' y el API esperaba 'usuarioId') y 'hasta' incluye todo el dia. - Contactos: paginacion aplicada despues de fusionar fuentes (antes saltaban o se repetian contactos entre paginas y 'Siguiente' se deshabilitaba antes de tiempo). - Buscador global: leads -> /hoy/, contactos -> /; el cotizador entiende ?cotizacion=<id> para retomar desde la liga. - Export mi-dia: columnas Desarrollo/Origen rellenas y sin duplicados. - Recibo de cobranza: "Recibio" es FRASA (el cliente paga, no recibe). - Alta de plan de cobranza: proyecto libre con sugerencias del catalogo real (antes limitado a 3 proyectos que no cubrian la cartera). Verificado en vivo: autorizacion IA (3x 403), filtro de auditoria, 404 de afluencia. Suites: 717 API + 124 alta-clientes + 115 cotizador + 92 cobranza + 89 landing + 32 mi-dia en verde. Backup de hoy generado en /root/backups.
This commit is contained in:
@@ -25,6 +25,7 @@ import { indiceMenorTotal, totalPagado, ENGANCHE_MAXIMO_PCT } from '../lib/calcu
|
||||
import { desarrolloEfectivo, idsCotizables, propuestaVigente } from '../lib/reglas'
|
||||
import {
|
||||
getMisCotizaciones,
|
||||
getCotizacion,
|
||||
guardarCotizacion,
|
||||
urlPublicaCotizacion,
|
||||
ErrorApiCotizacion,
|
||||
@@ -103,6 +104,9 @@ export default function CotizadorPage() {
|
||||
// ligado a la cotización al guardar; la unidad se preselecciona al cargar
|
||||
const [oportunidadId, setOportunidadId] = useState<string | null>(null)
|
||||
const [unidadPendiente, setUnidadPendiente] = useState<string | null>(null)
|
||||
// Retomar una cotización llegada por URL (/quote/?cotizacion=<id>, desde la
|
||||
// búsqueda global): se aplica cuando termina de cargar inventario+reglas
|
||||
const [cotizacionPendiente, setCotizacionPendiente] = useState<string | null>(null)
|
||||
|
||||
// Si el desarrollo activo deja de ofrecerse (lo desactivó el admin), se cae al
|
||||
// primero disponible para no dejar el cotizador en un desarrollo muerto.
|
||||
@@ -189,6 +193,8 @@ export default function CotizadorPage() {
|
||||
}
|
||||
if (unidadParam) setUnidadPendiente(unidadParam)
|
||||
if (opId) setOportunidadId(opId)
|
||||
const cotizacionParam = qs.get('cotizacion')
|
||||
if (cotizacionParam) setCotizacionPendiente(cotizacionParam)
|
||||
// Solo al montar: es el punto de entrada desde Mi día
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
@@ -221,6 +227,20 @@ export default function CotizadorPage() {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [unidades, cargando, unidadPendiente])
|
||||
|
||||
// Retoma la cotización pedida por URL cuando el inventario ya cargó
|
||||
useEffect(() => {
|
||||
if (cotizacionPendiente === null || cargando) return
|
||||
const id = cotizacionPendiente
|
||||
setCotizacionPendiente(null)
|
||||
void getCotizacion(id)
|
||||
.then((c) => {
|
||||
if (c) handleRetomar(c)
|
||||
else setAviso(`No se encontró la cotización entre las tuyas (${id}).`)
|
||||
})
|
||||
.catch(() => setAviso('No se pudo cargar la cotización de la liga.'))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [cotizacionPendiente, cargando, unidades])
|
||||
|
||||
// La propuesta elegida (para guardar/compartir); acotada por si cambia el desarrollo
|
||||
const propuestaActiva = propuestas[Math.min(propuestaIndex, Math.max(propuestas.length - 1, 0))]
|
||||
// Total pagado por el cliente con la propuesta elegida; null si no se puede conocer
|
||||
|
||||
Reference in New Issue
Block a user