- 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>
299 lines
10 KiB
TypeScript
299 lines
10 KiB
TypeScript
/**
|
||
* Utilidades de formato y catálogos de etiquetas para el CRM.
|
||
*/
|
||
|
||
export interface Option {
|
||
value: string;
|
||
label: string;
|
||
}
|
||
|
||
export function formatMoney(value: number | null | undefined, currency = 'MXN'): string {
|
||
if (value === null || value === undefined) return '—';
|
||
return new Intl.NumberFormat('es-MX', { style: 'currency', currency }).format(Number(value));
|
||
}
|
||
|
||
export function formatDate(value: string | null | undefined): string {
|
||
if (!value) return '—';
|
||
const d = new Date(value);
|
||
if (Number.isNaN(d.getTime())) return '—';
|
||
return d.toLocaleDateString('es-MX', { year: 'numeric', month: 'short', day: 'numeric' });
|
||
}
|
||
|
||
export function labelOf(list: Option[], value: string | null | undefined): string {
|
||
if (!value) return '—';
|
||
return list.find((x) => x.value === value)?.label ?? value;
|
||
}
|
||
|
||
// ----- Clientes / Prospectos -----
|
||
export const RECORD_TYPES: Option[] = [
|
||
{ value: 'cliente', label: 'Cliente' },
|
||
{ value: 'prospecto', label: 'Prospecto' }
|
||
];
|
||
|
||
export const PERSON_TYPES: Option[] = [
|
||
{ value: 'fisica', label: 'Física' },
|
||
{ value: 'moral', label: 'Moral' }
|
||
];
|
||
|
||
export const ACCOUNT_STATUS: Option[] = [
|
||
{ value: 'active', label: 'Activo' },
|
||
{ value: 'inactive', label: 'Inactivo' }
|
||
];
|
||
|
||
export const COMMERCIAL_CLASSIFICATION: Option[] = [
|
||
{ value: 'importador', label: 'Importador' },
|
||
{ value: 'exportador', label: 'Exportador' },
|
||
{ value: 'importador_exportador', label: 'Importador / Exportador' }
|
||
];
|
||
|
||
export const ACCOUNT_TYPES: Option[] = [
|
||
{ value: 'importador', label: 'Importador' },
|
||
{ value: 'exportador', label: 'Exportador' },
|
||
{ value: 'immex', label: 'IMMEX / Maquila' },
|
||
{ value: 'agencia_aduanal', label: 'Agencia aduanal' },
|
||
{ value: 'transportista', label: 'Transportista' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|
||
|
||
export const CONTACT_METHODS: Option[] = [
|
||
{ value: 'llamada', label: 'Llamada telefónica' },
|
||
{ value: 'correo', label: 'Correo electrónico' },
|
||
{ value: 'videoconferencia', label: 'Videoconferencia' },
|
||
{ value: 'whatsapp', label: 'WhatsApp' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|
||
|
||
// ----- Proveedores -----
|
||
export const SUPPLIER_CLASSIFICATIONS: Option[] = [
|
||
{ value: 'naviera', label: 'Naviera' },
|
||
{ value: 'aerolinea', label: 'Aerolínea' },
|
||
{ value: 'transportista_terrestre', label: 'Transportista terrestre' },
|
||
{ value: 'ferrocarril', label: 'Ferrocarril' },
|
||
{ value: 'agente_aduanal', label: 'Agente aduanal' },
|
||
{ value: 'agente_carga', label: 'Agente de carga' },
|
||
{ value: 'agente_corresponsal', label: 'Agente corresponsal' },
|
||
{ value: 'almacen', label: 'Almacén' },
|
||
{ value: 'aseguradora', label: 'Aseguradora' },
|
||
{ value: 'paqueteria', label: 'Paquetería' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|
||
|
||
export const COVERAGE: Option[] = [
|
||
{ value: 'nacional', label: 'Nacional' },
|
||
{ value: 'internacional', label: 'Internacional' },
|
||
{ value: 'ambos', label: 'Nacional e internacional' }
|
||
];
|
||
|
||
// ----- Direcciones -----
|
||
export const ADDRESS_TYPES: Option[] = [
|
||
{ value: 'fiscal', label: 'Fiscal' },
|
||
{ value: 'oficina', label: 'Oficina' },
|
||
{ value: 'sucursal', label: 'Sucursal' },
|
||
{ value: 'bodega', label: 'Bodega' },
|
||
{ value: 'patio', label: 'Patio' },
|
||
{ value: 'terminal', label: 'Terminal' },
|
||
{ value: 'almacen', label: 'Almacén' }
|
||
];
|
||
|
||
// ----- Documentos -----
|
||
export const DOC_TYPES: Option[] = [
|
||
{ value: 'constancia_fiscal', label: 'Constancia de Situación Fiscal' },
|
||
{ value: 'acta_constitutiva', label: 'Acta Constitutiva' },
|
||
{ value: 'identificacion', label: 'Identificación Oficial' },
|
||
{ value: 'comprobante_domicilio', label: 'Comprobante de Domicilio' },
|
||
{ value: 'contrato', label: 'Contrato' },
|
||
{ value: 'presentacion', label: 'Presentación Comercial' },
|
||
{ value: 'certificacion', label: 'Certificación' },
|
||
{ value: 'licencia', label: 'Licencia' },
|
||
{ value: 'convenio', label: 'Convenio' },
|
||
{ value: 'tarifario', label: 'Tarifario' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|
||
|
||
// ----- Contactos -----
|
||
export const CONTACT_AREAS: Option[] = [
|
||
{ value: 'ventas', label: 'Ventas' },
|
||
{ value: 'operaciones', label: 'Operaciones' },
|
||
{ value: 'facturacion', label: 'Facturación' },
|
||
{ value: 'cobranza', label: 'Cobranza' },
|
||
{ value: 'servicio_cliente', label: 'Servicio al Cliente' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|
||
|
||
export const CONTACT_STATUS: Option[] = [
|
||
{ value: 'active', label: 'Activo' },
|
||
{ value: 'inactive', label: 'Inactivo' }
|
||
];
|
||
|
||
// ----- Prospectos (leads / funnel) -----
|
||
export const LEAD_SOURCES: Option[] = [
|
||
{ value: 'web', label: 'Web' },
|
||
{ value: 'referido', label: 'Referido' },
|
||
{ value: 'evento', label: 'Evento' },
|
||
{ value: 'llamada', label: 'Llamada' },
|
||
{ value: 'email', label: 'Email' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|
||
|
||
export const LEAD_STATUS: Option[] = [
|
||
{ value: 'new', label: 'Nuevo' },
|
||
{ value: 'contacted', label: 'Contactado' },
|
||
{ value: 'qualified', label: 'Calificado' },
|
||
{ value: 'unqualified', label: 'No calificado' },
|
||
{ value: 'converted', label: 'Convertido' }
|
||
];
|
||
|
||
// ----- Actividades -----
|
||
export const ACTIVITY_TYPES: Option[] = [
|
||
{ value: 'call', label: 'Llamada' },
|
||
{ value: 'meeting', label: 'Reunión' },
|
||
{ value: 'task', label: 'Tarea' },
|
||
{ value: 'email', label: 'Correo' },
|
||
{ value: 'note', label: 'Nota' }
|
||
];
|
||
|
||
export const ACTIVITY_STATUS: Option[] = [
|
||
{ value: 'pending', label: 'Pendiente' },
|
||
{ value: 'completed', label: 'Completada' },
|
||
{ value: 'canceled', label: 'Cancelada' }
|
||
];
|
||
|
||
// ----- Comercial: Solicitudes / Cotizaciones -----
|
||
export const OPERATION_TYPES: Option[] = [
|
||
{ value: 'importacion', label: 'Importación' },
|
||
{ value: 'exportacion', label: 'Exportación' }
|
||
];
|
||
|
||
export const TRANSPORT_MODES: Option[] = [
|
||
{ value: 'maritimo', label: 'Marítimo' },
|
||
{ value: 'aereo', label: 'Aéreo' },
|
||
{ value: 'terrestre', label: 'Terrestre' },
|
||
{ value: 'ferroviario', label: 'Ferroviario' },
|
||
{ value: 'multimodal', label: 'Multimodal' }
|
||
];
|
||
|
||
export const SERVICE_TYPES: Option[] = [
|
||
{ value: 'puerto_puerto', label: 'Puerto – Puerto' },
|
||
{ value: 'puerto_puerta', label: 'Puerto – Puerta' },
|
||
{ value: 'puerta_puerto', label: 'Puerta – Puerto' },
|
||
{ value: 'puerta_puerta', label: 'Puerta – Puerta (Door to Door)' }
|
||
];
|
||
|
||
export const LOAD_TYPES: Option[] = [
|
||
{ value: 'FCL', label: 'FCL (contenedor completo)' },
|
||
{ value: 'LCL', label: 'LCL (carga consolidada)' },
|
||
{ value: 'AMBAS', label: 'Ambas (comparar FCL y LCL)' },
|
||
{ value: 'AEREO', label: 'Aéreo (carga aérea)' }
|
||
];
|
||
|
||
export const PRIORITIES: Option[] = [
|
||
{ value: 'baja', label: 'Baja' },
|
||
{ value: 'normal', label: 'Normal' },
|
||
{ value: 'alta', label: 'Alta' },
|
||
{ value: 'urgente', label: 'Urgente' }
|
||
];
|
||
|
||
// Pestañas del formulario de solicitud de servicio (documento maestro de cotización)
|
||
export const SR_FORM_TABS: Option[] = [
|
||
{ value: 'datos', label: 'Datos' },
|
||
{ value: 'ruta', label: 'Servicio y ruta' },
|
||
{ value: 'mercancia', label: 'Mercancía' },
|
||
{ value: 'dimensiones', label: 'Dimensiones' },
|
||
{ value: 'servicios', label: 'Servicios' },
|
||
{ value: 'notas', label: 'Notas' }
|
||
];
|
||
|
||
export const SR_STATUS: Option[] = [
|
||
{ value: 'nueva', label: 'Nueva' },
|
||
{ value: 'contacto', label: 'Contacto realizado' },
|
||
{ value: 'en_analisis', label: 'En análisis' },
|
||
{ value: 'cotizada', label: 'Cotizada' },
|
||
{ value: 'aceptada', label: 'Aceptada' },
|
||
{ value: 'rechazada', label: 'Rechazada' },
|
||
{ value: 'liberada', label: 'Liberada a operaciones' }
|
||
];
|
||
|
||
export const QUOTE_STATUS: Option[] = [
|
||
{ value: 'borrador', label: 'Borrador' },
|
||
{ value: 'enviada', label: 'Enviada' },
|
||
{ value: 'aceptada', label: 'Aceptada' },
|
||
{ value: 'rechazada', label: 'Rechazada' }
|
||
];
|
||
|
||
export const QUOTE_CONCEPTS: Option[] = [
|
||
{ value: 'flete_internacional', label: 'Flete internacional' },
|
||
{ value: 'transporte_terrestre', label: 'Transporte terrestre' },
|
||
{ value: 'despacho_aduanal', label: 'Despacho aduanal' },
|
||
{ value: 'gastos_destino', label: 'Gastos en destino' },
|
||
{ value: 'otros', label: 'Otros cargos' }
|
||
];
|
||
|
||
export const RATE_STATUS: Option[] = [
|
||
{ value: 'solicitada', label: 'Solicitada' },
|
||
{ value: 'recibida', label: 'Recibida' },
|
||
{ value: 'declinada', label: 'Declinada' }
|
||
];
|
||
|
||
// ----- Operaciones: Embarques -----
|
||
export const SHIPMENT_STATUS: Option[] = [
|
||
{ value: 'abierta', label: 'Abierta' },
|
||
{ value: 'booking', label: 'Booking' },
|
||
{ value: 'en_transito', label: 'En tránsito' },
|
||
{ value: 'arribado', label: 'Arribado' },
|
||
{ value: 'entregada', label: 'Entregada' },
|
||
{ value: 'cerrada', label: 'Cerrada' },
|
||
{ value: 'cancelada', label: 'Cancelada' }
|
||
];
|
||
|
||
export const DOC_KINDS: Option[] = [
|
||
{ value: 'master', label: 'Master' },
|
||
{ value: 'house', label: 'House' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|
||
|
||
// ----- Bitácora del embarque: hitos y decisiones -----
|
||
export const EVENT_STATUS: Option[] = [
|
||
{ value: 'pendiente', label: 'Pendiente' },
|
||
{ value: 'completado', label: 'Completado' },
|
||
{ value: 'omitido', label: 'Omitido' },
|
||
{ value: 'rechazado', label: 'Rechazado' },
|
||
{ value: 'en_correccion', label: 'En corrección' }
|
||
];
|
||
|
||
export const EVENT_OUTCOME: Option[] = [
|
||
{ value: 'autorizado', label: 'Autorizado' },
|
||
{ value: 'rechazado', label: 'Rechazado' }
|
||
];
|
||
|
||
// ----- Facturación -----
|
||
export const INVOICE_STATUS: Option[] = [
|
||
{ value: 'borrador', label: 'Borrador' },
|
||
{ value: 'emitida', label: 'Emitida' },
|
||
{ value: 'enviada', label: 'Enviada' },
|
||
{ value: 'en_revision_cliente', label: 'En revisión del cliente' },
|
||
{ value: 'pagada', label: 'Pagada' },
|
||
{ value: 'cancelada', label: 'Cancelada' }
|
||
];
|
||
|
||
export const PAYMENT_METHODS: Option[] = [
|
||
{ value: 'transferencia', label: 'Transferencia' },
|
||
{ value: 'efectivo', label: 'Efectivo' },
|
||
{ value: 'cheque', label: 'Cheque' },
|
||
{ value: 'tarjeta', label: 'Tarjeta' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|
||
|
||
export const SHIPMENT_DOC_TYPES: Option[] = [
|
||
{ value: 'MBL', label: 'MBL (Master Bill of Lading)' },
|
||
{ value: 'HBL', label: 'HBL (House Bill of Lading)' },
|
||
{ value: 'MAWB', label: 'MAWB (Master Air Waybill)' },
|
||
{ value: 'HAWB', label: 'HAWB (House Air Waybill)' },
|
||
{ value: 'CMR', label: 'CMR (Carta Porte Internacional)' },
|
||
{ value: 'factura_comercial', label: 'Factura Comercial' },
|
||
{ value: 'packing_list', label: 'Packing List' },
|
||
{ value: 'carta_encomienda', label: 'Carta Encomienda' },
|
||
{ value: 'carta_garantia', label: 'Carta Garantía' },
|
||
{ value: 'certificado_permiso', label: 'Certificado / Permiso' },
|
||
{ value: 'otro', label: 'Otro' }
|
||
];
|