- Add 5 frontend specification documents (ET-*-frontend.md): - ET-AUTH-006: Authentication module frontend spec - ET-ML-008: ML Signals module frontend spec - ET-LLM-007: LLM Agent module frontend spec - ET-PFM-008: Portfolio Manager frontend spec (design) - ET-MKT-003: Marketplace frontend spec (design) - Add 8 new user stories: - US-AUTH-013: Global logout - US-AUTH-014: Device management - US-ML-008: Ensemble signal view - US-ML-009: ICT analysis view - US-ML-010: Multi-symbol scan - US-LLM-011: Execute trade from chat - US-PFM-013: Rebalance alerts - US-PFM-014: PDF report generation - Update task index with completed analysis Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
657 lines
16 KiB
Markdown
657 lines
16 KiB
Markdown
# ET-PFM-008: Especificación Frontend - Módulo Portfolio Manager
|
|
|
|
**Módulo:** OQI-008 Portfolio Manager
|
|
**Documento:** ET-PFM-008
|
|
**Estado:** A IMPLEMENTAR
|
|
**Versión:** 1.0.0
|
|
**Fecha de Creación:** 2026-01-25
|
|
**Última Actualización:** 2026-01-25
|
|
|
|
---
|
|
|
|
## 📋 Resumen Ejecutivo
|
|
|
|
Este documento especifica la arquitectura, estructura de componentes y flujos de interfaz de usuario para el módulo Portfolio Manager en la plataforma de trading. **Este es un diseño a implementar que no cuenta actualmente con código base.**
|
|
|
|
El módulo proporciona funcionalidades de visualización, análisis y gestión de portafolios de inversión, incluyendo posiciones, métricas de riesgo, pruebas de estrés y generación de reportes.
|
|
|
|
---
|
|
|
|
## 🎯 Objetivos del Frontend
|
|
|
|
1. Proporcionar visualización clara y en tiempo real del estado del portafolio
|
|
2. Facilitar análisis de riesgo mediante métricas consolidadas
|
|
3. Permitir simulaciones de rebalanceo de posiciones
|
|
4. Generar reportes analíticos exportables
|
|
5. Integrar datos de múltiples fuentes de mercado
|
|
|
|
---
|
|
|
|
## 📄 Páginas Propuestas
|
|
|
|
### 1. PortfolioPage
|
|
|
|
**Ruta:** `/portfolio`
|
|
**Descripción:** Página principal de gestión del portafolio
|
|
|
|
**Responsabilidades:**
|
|
- Mostrar resumen ejecutivo del portafolio
|
|
- Listar posiciones activas
|
|
- Mostrar indicadores clave de desempeño (KPIs)
|
|
- Permitir filtrado y búsqueda de posiciones
|
|
- Facilitar acceso a rebalanceo
|
|
|
|
**Estructura:**
|
|
```
|
|
PortfolioPage
|
|
├── PortfolioSummaryCard
|
|
├── PerformanceChart
|
|
├── PositionsTable
|
|
│ ├── Filtros
|
|
│ └── Paginación
|
|
└── Acciones (Rebalancear, Exportar)
|
|
```
|
|
|
|
**Datos Requeridos:**
|
|
- Valor total del portafolio
|
|
- Rentabilidad YTD
|
|
- Rentabilidad acumulada
|
|
- Composición por activo
|
|
- Posiciones individuales con métricas
|
|
|
|
---
|
|
|
|
### 2. StressTestPage
|
|
|
|
**Ruta:** `/portfolio/stress-tests`
|
|
**Descripción:** Página para ejecución y análisis de pruebas de estrés
|
|
|
|
**Responsabilidades:**
|
|
- Crear nuevas pruebas de estrés
|
|
- Ejecutar escenarios predefinidos
|
|
- Visualizar resultados de stress tests
|
|
- Comparar múltiples escenarios
|
|
- Exportar análisis de sensibilidad
|
|
|
|
**Estructura:**
|
|
```
|
|
StressTestPage
|
|
├── ScenarioSelector
|
|
├── SimulationControls
|
|
├── RiskMetricsPanel
|
|
├── ResultsVisualization
|
|
│ ├── ImpactChart
|
|
│ └── SensitivityTable
|
|
└── ExportOptions
|
|
```
|
|
|
|
**Datos Requeridos:**
|
|
- Escenarios disponibles
|
|
- Parámetros de simulación
|
|
- Resultados de ejecución
|
|
- Metricas de sensibilidad
|
|
|
|
---
|
|
|
|
### 3. ReportsPage
|
|
|
|
**Ruta:** `/portfolio/reports`
|
|
**Descripción:** Página de generación y consulta de reportes analíticos
|
|
|
|
**Responsabilidades:**
|
|
- Listar reportes generados
|
|
- Crear nuevos reportes personalizados
|
|
- Visualizar reportes en tiempo real
|
|
- Descargar reportes en múltiples formatos
|
|
- Programar generación automática de reportes
|
|
|
|
**Estructura:**
|
|
```
|
|
ReportsPage
|
|
├── ReportsList
|
|
│ └── ReportFilters
|
|
├── ReportGenerator
|
|
│ ├── ParameterSelector
|
|
│ └── FormatSelector
|
|
├── ReportViewer
|
|
│ ├── PaginationControls
|
|
│ └── ExportOptions
|
|
└── ScheduleManager
|
|
```
|
|
|
|
**Datos Requeridos:**
|
|
- Lista de reportes disponibles
|
|
- Historico de reportes generados
|
|
- Parámetros configurables
|
|
- Formato de salida (PDF, Excel, CSV)
|
|
|
|
---
|
|
|
|
## 🧩 Componentes Propuestos
|
|
|
|
### 1. PortfolioSummaryCard
|
|
|
|
**Tipo:** Componente Presentacional
|
|
**Ubicación:** `src/components/portfolio/PortfolioSummaryCard.vue`
|
|
|
|
**Props:**
|
|
```typescript
|
|
interface PortfolioSummaryProps {
|
|
totalValue: number;
|
|
ytdReturn: number;
|
|
cumulativeReturn: number;
|
|
currency: string;
|
|
lastUpdated: Date;
|
|
isLoading?: boolean;
|
|
}
|
|
```
|
|
|
|
**Funcionalidades:**
|
|
- Mostrar valor total del portafolio
|
|
- Indicadores de rentabilidad (YTD, acumulada)
|
|
- Tendencia visual (arriba/abajo)
|
|
- Moneda de referencia
|
|
- Timestamp de actualización
|
|
- Estado de carga
|
|
|
|
**Emits:**
|
|
- `refresh`: Solicitar actualización de datos
|
|
|
|
---
|
|
|
|
### 2. PositionsTable
|
|
|
|
**Tipo:** Componente Presentacional
|
|
**Ubicación:** `src/components/portfolio/PositionsTable.vue`
|
|
|
|
**Props:**
|
|
```typescript
|
|
interface PositionsTableProps {
|
|
positions: Position[];
|
|
loading?: boolean;
|
|
sortBy?: string;
|
|
sortOrder?: 'asc' | 'desc';
|
|
pageSize?: number;
|
|
currentPage?: number;
|
|
}
|
|
|
|
interface Position {
|
|
id: string;
|
|
symbol: string;
|
|
name: string;
|
|
quantity: number;
|
|
entryPrice: number;
|
|
currentPrice: number;
|
|
currentValue: number;
|
|
percentageChange: number;
|
|
percentageOfPortfolio: number;
|
|
sector?: string;
|
|
lastUpdated: Date;
|
|
}
|
|
```
|
|
|
|
**Funcionalidades:**
|
|
- Visualización de tabla de posiciones
|
|
- Ordenamiento por columnas
|
|
- Paginación configurable
|
|
- Filtrado por sector/tipo
|
|
- Búsqueda por símbolo o nombre
|
|
- Indicadores visuales de desempeño
|
|
- Acciones por fila (detalles, vender, rebalancear)
|
|
|
|
**Emits:**
|
|
- `sort`: Cambio de ordenamiento
|
|
- `page-change`: Cambio de página
|
|
- `position-selected`: Selección de posición
|
|
- `position-action`: Acción sobre posición
|
|
|
|
---
|
|
|
|
### 3. PerformanceChart
|
|
|
|
**Tipo:** Componente Presentacional
|
|
**Ubicación:** `src/components/portfolio/PerformanceChart.vue`
|
|
|
|
**Props:**
|
|
```typescript
|
|
interface PerformanceChartProps {
|
|
data: PerformanceData[];
|
|
timeframe?: 'D' | 'W' | 'M' | 'Y' | 'ALL';
|
|
currency?: string;
|
|
showComparison?: boolean;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
interface PerformanceData {
|
|
date: Date;
|
|
value: number;
|
|
return: number;
|
|
benchmark?: number;
|
|
}
|
|
```
|
|
|
|
**Funcionalidades:**
|
|
- Gráfico de línea del desempeño del portafolio
|
|
- Múltiples plazos (Día, Semana, Mes, Año, Todo)
|
|
- Comparación con benchmark
|
|
- Visualización de retorno absoluto y porcentual
|
|
- Interactividad con tooltips
|
|
- Exportación como imagen
|
|
|
|
**Emits:**
|
|
- `timeframe-change`: Cambio de período
|
|
- `range-select`: Selección de rango personalizado
|
|
|
|
---
|
|
|
|
### 4. RiskMetricsPanel
|
|
|
|
**Tipo:** Componente Presentacional
|
|
**Ubicación:** `src/components/portfolio/RiskMetricsPanel.vue`
|
|
|
|
**Props:**
|
|
```typescript
|
|
interface RiskMetricsPanelProps {
|
|
metrics: RiskMetrics;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
interface RiskMetrics {
|
|
volatility: number; // Volatilidad anualizada
|
|
sharpeRatio: number; // Ratio de Sharpe
|
|
sortinoRatio: number; // Ratio de Sortino
|
|
valueAtRisk: number; // VaR 95%
|
|
expectedShortfall: number; // CVaR (Expected Shortfall)
|
|
beta: number; // Beta respecto a benchmark
|
|
correlation: number; // Correlación con benchmark
|
|
maxDrawdown: number; // Máxima caída histórica
|
|
diversificationRatio: number; // Ratio de diversificación
|
|
}
|
|
```
|
|
|
|
**Funcionalidades:**
|
|
- Visualización de métricas de riesgo clave
|
|
- Indicadores de riesgo (bajo/medio/alto)
|
|
- Comparación con benchmarks
|
|
- Explicación de métricas (tooltips)
|
|
- Gráficos de sensibilidad
|
|
- Histórico de evolución de métricas
|
|
|
|
**Emits:**
|
|
- `metric-selected`: Selección de métrica para análisis profundo
|
|
- `learn-more`: Solicitud de explicación de métrica
|
|
|
|
---
|
|
|
|
### 5. RebalanceModal
|
|
|
|
**Tipo:** Componente Modal
|
|
**Ubicación:** `src/components/portfolio/RebalanceModal.vue`
|
|
|
|
**Props:**
|
|
```typescript
|
|
interface RebalanceModalProps {
|
|
visible: boolean;
|
|
portfolio: Portfolio;
|
|
targetAllocation: AllocationTarget[];
|
|
currentAllocation: AllocationCurrent[];
|
|
constraints?: RebalanceConstraints;
|
|
}
|
|
|
|
interface AllocationTarget {
|
|
assetType: string;
|
|
targetPercentage: number;
|
|
minPercentage?: number;
|
|
maxPercentage?: number;
|
|
}
|
|
|
|
interface RebalanceConstraints {
|
|
maxTransactionCost?: number;
|
|
minTradeSize?: number;
|
|
excludeAssets?: string[];
|
|
allowNewPositions?: boolean;
|
|
}
|
|
```
|
|
|
|
**Funcionalidades:**
|
|
- Mostrar asignación actual vs. objetivo
|
|
- Calcular transacciones necesarias
|
|
- Permitir selección de restricciones
|
|
- Vista previa de cambios
|
|
- Estimación de costos de transacción
|
|
- Confirmación antes de ejecutar
|
|
- Historial de rebalanceos
|
|
|
|
**Emits:**
|
|
- `close`: Cierre del modal
|
|
- `confirm`: Confirmación de rebalanceo
|
|
- `cancel`: Cancelación de operación
|
|
- `preview`: Solicitud de vista previa
|
|
|
|
---
|
|
|
|
## 🗂️ Estructura de Store
|
|
|
|
### portfolioStore
|
|
|
|
**Ubicación:** `src/stores/portfolioStore.ts`
|
|
|
|
**Estado:**
|
|
```typescript
|
|
interface PortfolioState {
|
|
portfolio: Portfolio | null;
|
|
positions: Position[];
|
|
selectedPosition: Position | null;
|
|
performanceData: PerformanceData[];
|
|
riskMetrics: RiskMetrics | null;
|
|
stressTestResults: StressTestResult[] | null;
|
|
reports: Report[];
|
|
loading: {
|
|
portfolio: boolean;
|
|
positions: boolean;
|
|
riskMetrics: boolean;
|
|
stressTest: boolean;
|
|
reports: boolean;
|
|
};
|
|
filters: {
|
|
sector?: string;
|
|
minValue?: number;
|
|
maxValue?: number;
|
|
searchTerm?: string;
|
|
};
|
|
error: string | null;
|
|
}
|
|
|
|
interface Portfolio {
|
|
id: string;
|
|
name: string;
|
|
totalValue: number;
|
|
ytdReturn: number;
|
|
cumulativeReturn: number;
|
|
currency: string;
|
|
positions: Position[];
|
|
lastUpdated: Date;
|
|
}
|
|
```
|
|
|
|
**Acciones Principales:**
|
|
|
|
```typescript
|
|
// Portafolio
|
|
fetchPortfolio(portfolioId: string): Promise<Portfolio>
|
|
updatePortfolio(portfolioId: string, data: Partial<Portfolio>): Promise<Portfolio>
|
|
selectPosition(position: Position): void
|
|
clearSelection(): void
|
|
|
|
// Posiciones
|
|
fetchPositions(portfolioId: string): Promise<Position[]>
|
|
updatePosition(positionId: string, data: Partial<Position>): Promise<Position>
|
|
deletePosition(positionId: string): Promise<void>
|
|
addPosition(position: Position): Promise<Position>
|
|
|
|
// Desempeño
|
|
fetchPerformanceData(portfolioId: string, timeframe: string): Promise<PerformanceData[]>
|
|
|
|
// Métricas de Riesgo
|
|
fetchRiskMetrics(portfolioId: string): Promise<RiskMetrics>
|
|
|
|
// Pruebas de Estrés
|
|
executeStressTest(portfolioId: string, scenario: StressScenario): Promise<StressTestResult>
|
|
fetchStressTestResults(portfolioId: string): Promise<StressTestResult[]>
|
|
|
|
// Rebalanceo
|
|
calculateRebalance(portfolioId: string, targets: AllocationTarget[]): Promise<RebalancePlan>
|
|
executeRebalance(portfolioId: string, plan: RebalancePlan): Promise<RebalanceResult>
|
|
|
|
// Reportes
|
|
fetchReports(portfolioId: string): Promise<Report[]>
|
|
generateReport(portfolioId: string, config: ReportConfig): Promise<Report>
|
|
exportReport(reportId: string, format: 'pdf' | 'excel' | 'csv'): Promise<Blob>
|
|
|
|
// Filtros
|
|
setFilter(filterName: string, value: any): void
|
|
clearFilters(): void
|
|
```
|
|
|
|
**Getters:**
|
|
```typescript
|
|
getPortfolioValue(): number
|
|
getTotalReturn(): number
|
|
getPositionCount(): number
|
|
getFilteredPositions(): Position[]
|
|
getTopPerformer(): Position | null
|
|
getTopLoser(): Position | null
|
|
getRiskLevel(): 'low' | 'medium' | 'high'
|
|
```
|
|
|
|
---
|
|
|
|
## 🔄 Flujos de Datos
|
|
|
|
### Flujo 1: Carga Inicial de Portafolio
|
|
|
|
```
|
|
PortfolioPage Montada
|
|
↓
|
|
[Dispatch] fetchPortfolio()
|
|
↓
|
|
API Backend (GET /api/portfolio/{id})
|
|
↓
|
|
[Commit] setPortfolio()
|
|
↓
|
|
PortfolioSummaryCard actualizado
|
|
PositionsTable actualizado
|
|
```
|
|
|
|
### Flujo 2: Búsqueda y Filtrado
|
|
|
|
```
|
|
Usuario escribe en SearchInput
|
|
↓
|
|
[Dispatch] setFilter('searchTerm', value)
|
|
↓
|
|
Computed getFilteredPositions()
|
|
↓
|
|
PositionsTable re-renderiza
|
|
```
|
|
|
|
### Flujo 3: Ejecución de Stress Test
|
|
|
|
```
|
|
Usuario selecciona escenario
|
|
↓
|
|
[Dispatch] executeStressTest(scenario)
|
|
↓
|
|
API Backend (POST /api/portfolio/stress-test)
|
|
↓
|
|
[Commit] setStressTestResults()
|
|
↓
|
|
RiskMetricsPanel actualizado
|
|
ResultsVisualization mostrada
|
|
```
|
|
|
|
### Flujo 4: Rebalanceo
|
|
|
|
```
|
|
Usuario abre RebalanceModal
|
|
↓
|
|
[Dispatch] calculateRebalance(targets)
|
|
↓
|
|
API Backend (POST /api/portfolio/calculate-rebalance)
|
|
↓
|
|
Modal muestra vista previa
|
|
↓
|
|
Usuario confirma
|
|
↓
|
|
[Dispatch] executeRebalance(plan)
|
|
↓
|
|
API Backend (POST /api/portfolio/execute-rebalance)
|
|
↓
|
|
[Dispatch] fetchPortfolio() para refrescar
|
|
```
|
|
|
|
---
|
|
|
|
## 🎨 Estilos y Temas
|
|
|
|
### Principios de Diseño
|
|
|
|
- **Diseño Responsivo:** Compatible con desktop, tablet y mobile
|
|
- **Accesibilidad:** WCAG 2.1 AA como mínimo
|
|
- **Consistencia:** Uso de design system corporativo
|
|
- **Rendimiento:** Carga y animaciones fluidas
|
|
|
|
### Componentes de UI Requeridos
|
|
|
|
- Cards para resumen de métricas
|
|
- Tablas con ordenamiento y paginación
|
|
- Gráficos interactivos (Chart.js o Echarts)
|
|
- Modals para acciones confirmables
|
|
- Alerts y notificaciones Toast
|
|
- Loaders y spinners
|
|
- Iconografía consistente
|
|
|
|
---
|
|
|
|
## 📊 Integraciones Externas
|
|
|
|
### APIs Backend Requeridas
|
|
|
|
| Endpoint | Método | Descripción |
|
|
|----------|--------|-------------|
|
|
| `/api/portfolio/{id}` | GET | Obtener datos del portafolio |
|
|
| `/api/portfolio/{id}` | PUT | Actualizar portafolio |
|
|
| `/api/portfolio/{id}/positions` | GET | Listar posiciones |
|
|
| `/api/portfolio/{id}/positions` | POST | Crear posición |
|
|
| `/api/portfolio/position/{id}` | PUT | Actualizar posición |
|
|
| `/api/portfolio/position/{id}` | DELETE | Eliminar posición |
|
|
| `/api/portfolio/{id}/performance` | GET | Obtener datos de desempeño |
|
|
| `/api/portfolio/{id}/risk-metrics` | GET | Calcular métricas de riesgo |
|
|
| `/api/portfolio/stress-test` | POST | Ejecutar prueba de estrés |
|
|
| `/api/portfolio/{id}/rebalance/calculate` | POST | Calcular plan de rebalanceo |
|
|
| `/api/portfolio/{id}/rebalance/execute` | POST | Ejecutar rebalanceo |
|
|
| `/api/portfolio/{id}/reports` | GET | Listar reportes |
|
|
| `/api/portfolio/report/generate` | POST | Generar reporte |
|
|
| `/api/portfolio/report/{id}/export` | GET | Exportar reporte |
|
|
|
|
### Servicios Externos
|
|
|
|
- **Market Data Provider:** Para precios en tiempo real
|
|
- **Benchmark Data:** Índices de referencia (S&P 500, etc.)
|
|
- **Reporting Engine:** Generación de reportes complejos
|
|
|
|
---
|
|
|
|
## ⚙️ Configuración y Constantes
|
|
|
|
### Variables de Entorno
|
|
|
|
```env
|
|
VITE_PORTFOLIO_API_BASE_URL=http://localhost:3000/api
|
|
VITE_CHART_LIBRARY=echarts|chart.js
|
|
VITE_REPORT_FORMAT_DEFAULT=pdf
|
|
VITE_AUTO_REFRESH_INTERVAL=30000
|
|
VITE_MAX_POSITIONS_DISPLAY=50
|
|
```
|
|
|
|
### Constantes de Aplicación
|
|
|
|
```typescript
|
|
const CURRENCY_SYMBOLS: Record<string, string> = {
|
|
USD: '$',
|
|
EUR: '€',
|
|
COP: '$',
|
|
};
|
|
|
|
const TIMEFRAMES = ['D', 'W', 'M', 'Y', 'ALL'] as const;
|
|
|
|
const RISK_LEVELS = {
|
|
low: { min: 0, max: 0.08 },
|
|
medium: { min: 0.08, max: 0.15 },
|
|
high: { min: 0.15, max: Infinity },
|
|
};
|
|
|
|
const STRESS_SCENARIOS = [
|
|
{ id: 'market-crash', label: 'Caída de Mercado (-10%)', intensity: -0.1 },
|
|
{ id: 'rate-shock', label: 'Choque de Tasas (+2%)', intensity: 0.02 },
|
|
{ id: 'geopolitical', label: 'Evento Geopolítico', intensity: -0.05 },
|
|
];
|
|
```
|
|
|
|
---
|
|
|
|
## 📱 Responsive Design
|
|
|
|
### Breakpoints
|
|
|
|
- **Mobile:** < 640px
|
|
- **Tablet:** 640px - 1024px
|
|
- **Desktop:** > 1024px
|
|
|
|
### Ajustes por Pantalla
|
|
|
|
| Elemento | Mobile | Tablet | Desktop |
|
|
|----------|--------|--------|---------|
|
|
| PortfolioSummaryCard | Apilado | 2 columnas | 4 columnas |
|
|
| PositionsTable | Horizontal scroll | Scroll limitado | Completo |
|
|
| PerformanceChart | Altura reducida | Altura media | Altura completa |
|
|
| RiskMetricsPanel | 1 columna | 2 columnas | 3 columnas |
|
|
|
|
---
|
|
|
|
## 🔒 Seguridad
|
|
|
|
- **Autenticación:** Bearer token en headers
|
|
- **Validación Frontend:** Validación de input antes de envío
|
|
- **HTTPS Obligatorio:** En producción
|
|
- **CORS:** Configuración restrictiva
|
|
- **Rate Limiting:** Control de llamadas a API
|
|
|
|
---
|
|
|
|
## 📝 Notas de Implementación
|
|
|
|
### Estado Actual
|
|
|
|
**Este documento describe un diseño que aún no ha sido implementado.** No existe código base en el repositorio para estos componentes.
|
|
|
|
### Próximos Pasos
|
|
|
|
1. Crear estructura de directorios de componentes
|
|
2. Implementar store de Pinia con acciones básicas
|
|
3. Desarrollar componentes en orden de dependencia
|
|
4. Integrar con APIs backend
|
|
5. Implementar pruebas unitarias
|
|
6. Realizar pruebas de integración
|
|
7. Optimizar rendimiento
|
|
|
|
### Consideraciones Técnicas
|
|
|
|
- **Framework:** Vue 3 (Composition API recomendada)
|
|
- **State Management:** Pinia
|
|
- **Gráficos:** Echarts o Chart.js según disponibilidad
|
|
- **Validación:** Vee-validate
|
|
- **Estilos:** TailwindCSS o SCSS
|
|
- **Pruebas:** Vitest + Vue Test Utils
|
|
|
|
---
|
|
|
|
## 📚 Referencias Relacionadas
|
|
|
|
- OQI-008 Portfolio Manager - Especificación General
|
|
- ET-PFM-001-backend.md - Especificación Backend
|
|
- ET-PFM-002-database.md - Especificación Base de Datos
|
|
- Arquitectura Trading Platform
|
|
|
|
---
|
|
|
|
## ✅ Control de Cambios
|
|
|
|
| Versión | Fecha | Autor | Cambios |
|
|
|---------|-------|-------|---------|
|
|
| 1.0.0 | 2026-01-25 | Sistema | Especificación inicial |
|
|
|
|
---
|
|
|
|
**Estado:** A IMPLEMENTAR | **Prioridad:** Alta | **Dependencias:** Backend Portfolio Manager (OQI-008)
|