feat(crm): cotización aérea con peso/volumen (P/Vol) operacional

- Modalidad AÉREO (4ª opción en "¿Cómo desea cotizar?"): en la solicitud
  muestra la sección aérea con el cálculo en vivo P/Vol = (L×A×H cm × bultos)/6000
  y el peso a cobrar = max(peso bruto, P/Vol). Fija el transporte en aéreo.
- Utilidad compartida crm/common/pricing.py (air_volumetric_kg / air_chargeable_kg,
  factor internacional 6000).
- Motor de costeo (rates): la rama aérea usa el P/Vol por dimensiones si vienen
  (CostRequest ahora acepta length/width/height_cm); respaldo m³×167 cuando no.
- Cotizador: captura por dimensiones (L×A×H + bultos) en modo aéreo y muestra el P/Vol.
- Solicitud→Cotización: si es AÉREO, siembra el concepto de flete con cantidad =
  peso a cobrar (P/Vol) para capturar la tarifa por kg.
- Pruebas: test_pricing (ejemplo del doc → 720; max bruto/volumétrico) + cotización
  aérea desde solicitud. Suite en verde (108).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ernesto Herrera
2026-08-04 07:30:25 -06:00
parent 36e98ee976
commit f1e6fba75d
10 changed files with 168 additions and 6 deletions

View File

@@ -35,8 +35,9 @@ export interface ImportPreview { mode: RateMode; total: number; valid: number; r
export interface CostRequest {
mode: RateMode; origin?: string | null; destination?: string | null; on_date?: string | null;
gross_weight_kg?: number | null; volume_m3?: number | null; equipment_type?: string | null;
quantity?: number; dangerous?: boolean;
gross_weight_kg?: number | null; volume_m3?: number | null;
length_cm?: number | null; width_cm?: number | null; height_cm?: number | null;
equipment_type?: string | null; quantity?: number; dangerous?: boolean;
}
export interface CostChargeLine { concept: string; amount: number; }
export interface CostOption {