fix(6-mejoras): subcategoría+apartado libre+PDF+coacreditado+docs+dedup

1. Subcategoría en modal descarte (catálogo completo + select)
2. Apartado sin monto mínimo (eliminada validación de 0,000)
3. PDF solo con propuesta elegida (no las 4)
4. Co-acreditado: checkbox 'no tiene' valida sin campos obligatorios
5. Documentos apartado: previsualizar + descargar (endpoint + frontend)
6. Oportunidades: sin duplicados, orden Confirmar→Afluencia→Cotizar→Aparto(verde)→Descartar
This commit is contained in:
2026-09-12 07:09:15 +00:00
parent c8357fe582
commit 852182fabc
12 changed files with 261 additions and 112 deletions

View File

@@ -2,11 +2,9 @@ import { useMemo, useState } from 'react'
import type { Unidad, DescuentosPropuestas, ParametrosDesarrollo } from '../types'
import {
APARTADO_DEFAULT,
APARTADO_MINIMO,
calcularPropuestas,
obtenerPrecioLista,
resolverNumMensualidades,
validarApartado,
validarEnganchePct,
type PropuestaResultado,
} from '../lib/calculations'
@@ -23,10 +21,8 @@ interface UseCotizacionResult {
setAcabados: (v: boolean) => void
apartado: number
setApartado: (v: number) => void
/** Apartado aplicado a los cálculos (acotado al mínimo cuando la captura es inválida) */
/** Apartado aplicado a los cálculos (acotado a >= 0; el vendedor puede apartar con cualquier monto, incluso $0) */
apartadoEfectivo: number
/** Mensaje de error de la captura del apartado; null si es válida */
errorApartado: string | null
/** Enganche capturado en PORCENTAJE (10 = 10%); null = % del Excel de cada propuesta */
enganchePct: number | null
setEnganchePct: (v: number | null) => void
@@ -125,9 +121,9 @@ export function useCotizacion(): UseCotizacionResult {
[unidad, acabados],
)
// Capturas por debajo del mínimo no se usan: los cálculos se acotan
const errorApartado = validarApartado(apartado)
const apartadoEfectivo = Math.max(apartado, APARTADO_MINIMO)
// Sin mínimo de apartado: el vendedor puede apartar con cualquier monto,
// incluso $0; solo se acota a >= 0 como guarda contra capturas raras
const apartadoEfectivo = Math.max(0, apartado)
// Enganche personalizado: null = default del Excel; inválido no entra al cálculo
const errorEnganche = validarEnganchePct(enganchePct)
@@ -149,7 +145,6 @@ export function useCotizacion(): UseCotizacionResult {
apartado,
setApartado,
apartadoEfectivo,
errorApartado,
enganchePct,
setEnganchePct,
errorEnganche,