36.1 Objetivo y alcance
Construiremos un monitor local que lista instrumentos, calcula punto medio, spread relativo, variación y antigüedad; permite buscar, filtrar y ordenar; compara dos alternativas; y resume una cartera en moneda base.
Los datos son ficticios y no se conectan a mercados. Esto permite concentrarse en arquitectura, validaciones y experiencia de uso sin confundir la demostración con precios o recomendaciones reales.
Resultado esperado: una pantalla explica qué dato observa, de dónde viene, cuándo fue actualizado y qué fórmula produjo cada indicador.
36.2 Requisitos funcionales
Monitorear
Mostrar puntas, variación, spread, edad y calidad.
Filtrar
Buscar por texto y limitar por tipo de instrumento.
Comparar
Usar porcentajes y convenciones comunes, sin ranking opaco.
Valuar
Integrar posiciones, efectivo y conversión de moneda.
También debe haber estados vacíos, advertencias por datos vencidos, validación de controles y diseño adaptable a pantallas pequeñas.
36.3 Arquitectura por capas
Separar cálculo y presentación permite probar fórmulas sin navegador. La vista recibe valores ya validados y se limita a formatearlos y responder a eventos.
36.4 Modelo de datos integrado
Cada fila combina un instrumento relativamente estable con una cotización temporal. La cartera agrega cantidad, costo y tipo de cambio de valuación sin modificar el objeto original.
| Objeto | Campos principales | Regla |
|---|---|---|
| Instrumento | ID, símbolo, nombre, tipo y moneda. | Identidad estable. |
| Cotización | Bid, ask, cierre previo, fuente y edad. | Bid positivo y no mayor que ask. |
| Selección | Búsqueda, tipo, orden y vigencia máxima. | No altera los datos base. |
| Posición | Cantidad, costo promedio y conversión. | Valuación en una fecha común. |
const instrumento = Object.freeze({
id: "INS-ALFA", symbol: "ALFA", name: "Acción Alfa",
type: "EQUITY", currency: "ARS"
});
const cotizacion = Object.freeze({
instrumentId: "INS-ALFA", bid: 99.80, ask: 100.20,
previousClose: 98.50, source: "FUENTE-DEMO", ageSeconds: 4
});
console.log({ instrumento, cotizacion });
36.5 Métricas comparables
Los precios absolutos de instrumentos distintos no indican cuál es “más barato”. El monitor usa variación porcentual y spread relativo para poner cambios y costos de inmediatez sobre bases comparables.
function metricas(quote) {
const values = [quote.bid, quote.ask, quote.previousClose];
if (!values.every(Number.isFinite) || quote.bid <= 0 || quote.ask < quote.bid || quote.previousClose <= 0) {
throw new RangeError("Cotización no válida");
}
const mid = (quote.bid + quote.ask) / 2;
return {
mid,
changePercent: (mid / quote.previousClose - 1) * 100,
spreadPercent: (quote.ask - quote.bid) / mid * 100
};
}
console.log(metricas({ bid: 99.80, ask: 100.20, previousClose: 98.50 }));
36.6 Monitor y comparador interactivo
Usá los controles para explorar la tabla. El botón “Comparar” asigna alternativamente el instrumento al panel A o B. También podés seleccionarlos desde las listas.
Monitor de instrumentos · datos ficticios
La edad simula segundos transcurridos desde cada cotización; no hay conexión externa.
| Instrumento | Tipo | Bid | Ask | Mid | Variación | Spread | Edad | Acción |
|---|
Cartera de demostración en ARS
3 de 5 cotizaciones cumplen la vigencia máxima de 30 segundos. Las comparaciones no constituyen una recomendación.
36.7 Búsqueda, filtro y orden
La búsqueda se aplica sobre símbolo y nombre normalizados. El filtro por tipo reduce el conjunto; luego el comparador de orden decide la secuencia. Este orden de operaciones hace que el resumen describa exactamente las filas visibles.
sort() modifica el arreglo sobre el que opera. Para conservar el origen puede ordenarse una copia con [...datos].sort(...) o utilizar una alternativa no mutante disponible en el entorno objetivo.
function seleccionarInstrumentos(datos, { search = "", type = "ALL", compare }) {
const term = search.trim().toLowerCase();
return datos
.filter(item => type === "ALL" || item.type === type)
.filter(item => !term || `${item.symbol} ${item.name}`.toLowerCase().includes(term))
.sort(compare);
}
const datos = [
{ symbol: "BETA", name: "Bono Beta", type: "BOND", spread: 0.60 },
{ symbol: "ALFA", name: "Acción Alfa", type: "EQUITY", spread: 0.40 }
];
console.log(seleccionarInstrumentos(datos, {
search: "a", type: "ALL", compare: (a, b) => a.spread - b.spread
}));
36.8 Vigencia y calidad
La edad no dice si el precio es correcto, pero evita presentar una observación antigua como actual. El umbral se define por caso de uso e instrumento.
Una implementación real también distinguiría mercado cerrado, fuente desconectada, dato indicativo, corrección y secuencia fuera de orden.
36.9 Comparar sin fabricar un ganador
El panel coloca los instrumentos lado a lado, pero no calcula un “mejor” global. Mayor variación, menor spread o dato más fresco responden preguntas diferentes.
Los nocionales en monedas distintas no son comparables hasta convertirlos con un tipo de cambio y una fecha. La interfaz conserva el código de moneda junto a cada importe.
function comparar(a, b, quantity) {
if (!Number.isInteger(quantity) || quantity <= 0) throw new RangeError("Cantidad no válida");
const resumen = item => {
const mid = (item.bid + item.ask) / 2;
return {
symbol: item.symbol,
currency: item.currency,
spreadPercent: (item.ask - item.bid) / mid * 100,
buyNotional: item.ask * quantity
};
};
return { a: resumen(a), b: resumen(b), comparableDirectamente: a.currency === b.currency };
}
console.log(comparar(
{ symbol: "ALFA", currency: "ARS", bid: 99.80, ask: 100.20 },
{ symbol: "FONDO-G", currency: "USD", bid: 25.10, ask: 25.30 },
100
));
36.10 Integrar posiciones y efectivo
La cartera del monitor usa cantidades y costos predeterminados. Valúa cada posición al punto medio, convierte su valor a ARS y suma efectivo.
Para una aplicación de producción deberían incorporarse precios autorizados, tipos de cambio con hora, resultados realizados, cargos, ingresos y estado de liquidación.
function valuarCartera(posiciones, cotizaciones, fxToBase, cash) {
const values = posiciones.map(position => {
const quote = cotizaciones.get(position.instrumentId);
if (!quote) throw new Error(`Falta cotización: ${position.instrumentId}`);
const fx = fxToBase[quote.currency];
if (!Number.isFinite(fx) || fx <= 0) throw new Error(`Falta FX: ${quote.currency}`);
const mid = (quote.bid + quote.ask) / 2;
return position.quantity * mid * fx;
});
const positionsValue = values.reduce((sum, value) => sum + value, 0);
return { positionsValue, equity: cash + positionsValue };
}
const quotes = new Map([
["ALFA", { bid: 99.80, ask: 100.20, currency: "ARS" }],
["FONDO-G", { bid: 25.10, ask: 25.30, currency: "USD" }]
]);
console.log(valuarCartera(
[{ instrumentId: "ALFA", quantity: 100 }, { instrumentId: "FONDO-G", quantity: 20 }],
quotes, { ARS: 1, USD: 1200 }, 250000
));
36.11 Estado derivado, no duplicado
El conjunto filtrado, las métricas y el resumen no se guardan como nuevas fuentes permanentes: se derivan de datos y controles actuales. Esto evita que una fila diga una cosa y la tarjeta resumen otra.
El estado mínimo contiene búsqueda, filtro, orden, umbral, selecciones y cantidad. Cada cambio dispara una única función que recalcula y renderiza.
36.12 Renderizado seguro
Los textos provenientes de datos se asignan con textContent. Construir HTML concatenando cadenas no confiables puede convertir un símbolo o nombre en contenido ejecutable.
En el proyecto, las filas se generan con elementos DOM y las tarjetas de comparación usan nodos explícitos. Si una plantilla necesita HTML, debe escaparse o sanearse con una política adecuada.
Regla práctica: no uses innerHTML para insertar cotizaciones, nombres o mensajes recibidos de una fuente externa.
36.13 Formato y unidades
Intl.NumberFormat presenta moneda y porcentajes según una configuración regional. El valor interno continúa siendo numérico; no se debe volver a parsear el texto formateado.
Cada celda conserva unidad: ARS, USD, porcentaje o segundos. Colorear una variación ayuda, pero el signo y el texto siguen presentes para no depender únicamente del color.
36.14 Validación y degradación
Un dato inválido no debería derribar todo el monitor. Puede excluirse la fila, marcarse un error de fuente y mantener disponibles las demás observaciones.
| Problema | Respuesta de interfaz | Respuesta técnica |
|---|---|---|
| Sin coincidencias | Estado vacío claro. | Conservar filtros para corregirlos. |
| Dato vencido | Insignia y resumen de vigencia. | No ocultar la edad. |
| Cotización inválida | Fila no disponible. | Registrar fuente y regla fallida. |
| Falta conversión | Valor no calculable. | No asumir FX igual a uno. |
36.15 Reconciliaciones
El número de filas visibles debe coincidir con el resumen; vigentes más vencidas debe igualar visibles; cada comparación debe referirse a un instrumento existente; y el patrimonio debe ser efectivo más posiciones.
function reconciliarMonitor({ visible, fresh, stale, cash, positionsValue, equity }) {
const diferencias = [];
if (fresh + stale !== visible) diferencias.push("CONTEO_DE_VIGENCIA");
if (Math.abs(cash + positionsValue - equity) > 0.005) diferencias.push("PATRIMONIO");
return { valido: diferencias.length === 0, diferencias };
}
console.log(reconciliarMonitor({
visible: 5, fresh: 3, stale: 2,
cash: 250000, positionsValue: 631330, equity: 881330
}));
36.16 Pruebas del proyecto
Las funciones puras se prueban con casos de borde; la integración verifica que los controles produzcan filas y resúmenes coherentes.
function assertAlmostEqual(actual, expected, tolerance = 1e-9) {
if (Math.abs(actual - expected) > tolerance) {
throw new Error(`Esperado ${expected}, recibido ${actual}`);
}
}
const mid = (99.80 + 100.20) / 2;
const spreadPercent = (100.20 - 99.80) / mid * 100;
assertAlmostEqual(mid, 100);
assertAlmostEqual(spreadPercent, 0.4);
console.log("Pruebas de métricas superadas");
También conviene probar búsqueda sin resultados, igualdad de selecciones, cantidad cero, umbral inválido, monedas distintas y redondeos.
36.17 Accesibilidad y diseño adaptable
Los controles tienen etiquetas visibles, el estado usa aria-live y la información no depende solo del color. En pantallas pequeñas, los controles y tarjetas pasan a una columna y la tabla conserva desplazamiento horizontal.
Los botones mantienen foco visible y nombres concretos. Una versión más avanzada debería gestionar encabezados fijos, navegación de tabla y preferencias de movimiento.
36.18 Persistencia y datos reales
Para persistencia local se podría serializar únicamente preferencias no sensibles. Posiciones, órdenes y credenciales requieren almacenamiento, autenticación, cifrado, control de acceso y auditoría acordes al riesgo.
Una fuente real necesita conexión, reconexión, autenticación, límites de frecuencia, secuencias, instantáneas, actualizaciones y licencias de uso. El indicador “en línea” debe probar flujo reciente, no solo socket abierto.
Separación recomendada: adaptar mensajes externos a un modelo interno validado antes de permitir que lleguen a cálculos y componentes.
36.19 Extensiones posibles
- Alertas por cambio, spread o vencimiento de datos.
- Gráficos históricos con intervalo y fuente visibles.
- Costos totales y precio efectivo por instrumento.
- Comparación de rendimiento total con moneda común.
- Profundidad del libro y nocional ejecutable.
- Límites de concentración y escenarios de riesgo.
- Importación de operaciones con vista previa y conciliación.
- Registro de errores y panel de calidad de datos.
Cada extensión debería incorporarse con su modelo, validaciones, estados de error y pruebas, en lugar de acumular lógica directamente en el renderizado.
36.20 Cierre del curso
El monitor integra los principios centrales: comprender el instrumento antes de comparar, mantener precio y cantidad con sus unidades, separar orden de ejecución, incorporar costos, reconocer riesgos y conservar trazabilidad.
- Un precio necesita instrumento, moneda, fuente e instante.
- Una métrica necesita fórmula, horizonte y denominador.
- Comparar exige normalizar sin ocultar diferencias económicas.
- Una cartera combina posiciones, efectivo, conversiones y resultados.
- La interfaz debe hacer visibles datos vencidos, faltantes o incomparables.
- Las funciones puras, validaciones y reconciliaciones reducen errores.
- El software organiza evidencia; no reemplaza criterio ni regulación.
Con este proyecto queda una base extensible para conectar datos, registrar operaciones y construir herramientas financieras más completas de manera responsable.