From 23f0d02fe36efd0a777bd3623111899edca072ae Mon Sep 17 00:00:00 2001 From: dmolinari Date: Thu, 17 Jul 2025 11:52:00 -0300 Subject: [PATCH] Feat: RawData Table Format --- .gitignore | 4 + .../src/components/raw-data/RawAgroTable.tsx | 73 ++++----- .../raw-data/RawBolsaLocalTable.tsx | 138 ++++++++++-------- .../components/raw-data/RawBolsaUsaTable.tsx | 121 +++++++++------ .../components/raw-data/RawGranosTable.tsx | 61 ++++---- frontend/src/config/priorityTickers.ts | 11 ++ 6 files changed, 227 insertions(+), 181 deletions(-) create mode 100644 frontend/src/config/priorityTickers.ts diff --git a/.gitignore b/.gitignore index 7e2e97c..8a47aad 100644 --- a/.gitignore +++ b/.gitignore @@ -178,6 +178,10 @@ DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html +# DocFx +[Dd]ocs/ +docfx.build.json +docfx.metadata.json # Click-Once directory publish/ diff --git a/frontend/src/components/raw-data/RawAgroTable.tsx b/frontend/src/components/raw-data/RawAgroTable.tsx index fd89764..74dcd68 100644 --- a/frontend/src/components/raw-data/RawAgroTable.tsx +++ b/frontend/src/components/raw-data/RawAgroTable.tsx @@ -1,33 +1,29 @@ -import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material'; +import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, Typography } from '@mui/material'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; // Importaciones de nuestro proyecto import { useApiData } from '../../hooks/useApiData'; import { useIsHoliday } from '../../hooks/useIsHoliday'; import type { CotizacionGanado } from '../../models/mercadoModels'; -import { formatInteger, formatCurrency, formatFullDateTime } from '../../utils/formatters'; +import { formatInteger, formatFullDateTime } from '../../utils/formatters'; import { copyToClipboard } from '../../utils/clipboardUtils'; import { HolidayAlert } from '../common/HolidayAlert'; /** - * Función para convertir los datos de la tabla a un formato CSV para el portapapeles. + * Función para convertir los datos a formato TSV (Tab-Separated Values) + * con el formato específico solicitado por redacción. */ -const toCSV = (headers: string[], data: CotizacionGanado[]) => { - const headerRow = headers.join(';'); - const dataRows = data.map(row => - [ - row.categoria, - row.especificaciones, - formatCurrency(row.maximo), - formatCurrency(row.minimo), - formatCurrency(row.mediano), - formatInteger(row.cabezas), - formatInteger(row.kilosTotales), - formatInteger(row.importeTotal), - formatFullDateTime(row.fechaRegistro) - ].join(';') - ); - return [headerRow, ...dataRows].join('\n'); +const toTSV = (data: CotizacionGanado[]) => { + const dataRows = data.map(row => { + // Unimos Categoría y Especificaciones en una sola columna para el copiado + const categoriaCompleta = `${row.categoria}/${row.especificaciones}`; + const cabezas = formatInteger(row.cabezas); + const importeTotal = formatInteger(row.importeTotal); + + return [categoriaCompleta, cabezas, importeTotal].join('\t'); + }); + + return dataRows.join('\n'); }; /** @@ -35,24 +31,21 @@ const toCSV = (headers: string[], data: CotizacionGanado[]) => { * diseñado para la página de redacción. */ export const RawAgroTable = () => { - // Hooks para obtener los datos y el estado de feriado. const { data, loading: dataLoading, error: dataError } = useApiData('/mercados/agroganadero'); const isHoliday = useIsHoliday('BA'); const handleCopy = () => { if (!data) return; - const headers = ["Categoría", "Especificaciones", "Máximo", "Mínimo", "Mediano", "Cabezas", "Kg Total", "Importe Total", "Fecha de Registro"]; - const csvData = toCSV(headers, data); + const tsvData = toTSV(data); - copyToClipboard(csvData) - .then(() => alert('¡Tabla copiada al portapapeles!')) + copyToClipboard(tsvData) + .then(() => alert('Datos del Mercado Agroganadero copiados al portapapeles!')) .catch(err => { console.error('Error al copiar:', err); alert('Error: No se pudo copiar la tabla.'); }); }; - // Estado de carga unificado. const isLoading = dataLoading || isHoliday === null; if (isLoading) return ; @@ -67,43 +60,37 @@ export const RawAgroTable = () => { return ( - {/* Si es feriado, mostramos una alerta informativa encima de la tabla. */} {isHoliday && ( )} - + + + + Última actualización: {formatFullDateTime(data[0].fechaRegistro)} + + + + {/* La tabla ahora muestra solo las columnas requeridas para facilitar la visualización */} - Categoría - Especificaciones - Máximo - Mínimo - Mediano + Categoría / Especificaciones Cabezas - Kg Total Importe Total - Última Act. {data.map(row => ( - {row.categoria} - {row.especificaciones} - ${formatCurrency(row.maximo)} - ${formatCurrency(row.minimo)} - ${formatCurrency(row.mediano)} + {row.categoria} / {row.especificaciones} {formatInteger(row.cabezas)} - {formatInteger(row.kilosTotales)} ${formatInteger(row.importeTotal)} - {formatFullDateTime(row.fechaRegistro)} ))} diff --git a/frontend/src/components/raw-data/RawBolsaLocalTable.tsx b/frontend/src/components/raw-data/RawBolsaLocalTable.tsx index cf1b65c..05c236f 100644 --- a/frontend/src/components/raw-data/RawBolsaLocalTable.tsx +++ b/frontend/src/components/raw-data/RawBolsaLocalTable.tsx @@ -1,105 +1,121 @@ -import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material'; +import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, Typography, Divider } from '@mui/material'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; - -// Importaciones de nuestro proyecto import { useApiData } from '../../hooks/useApiData'; -import { useIsHoliday } from '../../hooks/useIsHoliday'; import type { CotizacionBolsa } from '../../models/mercadoModels'; import { formatCurrency, formatFullDateTime } from '../../utils/formatters'; import { copyToClipboard } from '../../utils/clipboardUtils'; -import { HolidayAlert } from '../common/HolidayAlert'; +import { TICKERS_PRIORITARIOS_LOCAL } from '../../config/priorityTickers'; /** - * Función para convertir los datos de la tabla a formato CSV. + * Función para convertir los datos prioritarios a formato TSV (Tab-Separated Values). */ -const toCSV = (headers: string[], data: CotizacionBolsa[]) => { - const headerRow = headers.join(';'); - const dataRows = data.map(row => - [ - row.ticker, - row.nombreEmpresa, - formatCurrency(row.precioActual), - formatCurrency(row.cierreAnterior), - `${row.porcentajeCambio.toFixed(2)}%`, - formatFullDateTime(row.fechaRegistro) - ].join(';') - ); - return [headerRow, ...dataRows].join('\n'); +const toTSV = (data: CotizacionBolsa[]) => { + const dataRows = data.map(row => { + // Formateamos el nombre para que quede como "GGAL.BA (GRUPO FINANCIERO GALICIA)" + const nombreCompleto = `${row.ticker} (${row.nombreEmpresa || ''})`; + const precio = `$${formatCurrency(row.precioActual)}` + const cambio = `${row.porcentajeCambio.toFixed(2)}%` + + // Unimos los campos con un carácter de tabulación '\t' + return [nombreCompleto, precio, cambio].join('\t'); + }); + + // Unimos todas las filas con un salto de línea + return dataRows.join('\n'); }; /** - * Componente de tabla de datos crudos para la Bolsa Local (MERVAL y acciones), - * diseñado para la página de redacción. + * Componente de tabla de datos crudos para la Bolsa Local, adaptado para redacción. */ export const RawBolsaLocalTable = () => { - // Hooks para obtener los datos y el estado de feriado. - const { data, loading: dataLoading, error: dataError } = useApiData('/mercados/bolsa/local'); - const isHoliday = useIsHoliday('BA'); + const { data, loading, error } = useApiData('/mercados/bolsa/local'); + + // Separamos los datos en prioritarios y el resto + const priorityData = data?.filter(d => TICKERS_PRIORITARIOS_LOCAL.includes(d.ticker)) + .sort((a, b) => TICKERS_PRIORITARIOS_LOCAL.indexOf(a.ticker) - TICKERS_PRIORITARIOS_LOCAL.indexOf(b.ticker)); // Mantenemos el orden + + const otherData = data?.filter(d => !TICKERS_PRIORITARIOS_LOCAL.includes(d.ticker)); const handleCopy = () => { - if (!data) return; - const headers = ["Ticker", "Nombre", "Último Precio", "Cierre Anterior", "Variación %", "Fecha de Registro"]; - const csvData = toCSV(headers, data); + if (!priorityData) return; + const tsvData = toTSV(priorityData); - copyToClipboard(csvData) - .then(() => alert('¡Tabla copiada al portapapeles!')) + copyToClipboard(tsvData) + .then(() => alert('Datos prioritarios copiados al portapapeles.')) .catch(err => { console.error('Error al copiar:', err); alert('Error: No se pudo copiar la tabla.'); }); }; - // Estado de carga unificado. - const isLoading = dataLoading || isHoliday === null; - - if (isLoading) return ; - if (dataError) return {dataError}; - - if (!data || data.length === 0) { - if (isHoliday) { - return ; - } - return No hay datos disponibles para el mercado local.; - } + if (loading) return ; + if (error) return {error}; + if (!data || data.length === 0) return No hay datos disponibles para el mercado local.; return ( - {/* Si es feriado, mostramos una alerta informativa encima de la tabla. */} - {isHoliday && ( - - - - )} + + + + Última actualización: {formatFullDateTime(data[0].fechaRegistro)} + + - + {/* Tabla de Datos Prioritarios */}
- Ticker - Nombre - Último Precio - Cierre Anterior - Variación % - Última Act. + Símbolo (Nombre) + Precio Actual + % Cambio - {data.map(row => ( + {priorityData?.map(row => ( - {row.ticker} - {row.nombreEmpresa} + + {row.ticker} + ({row.nombreEmpresa}) + ${formatCurrency(row.precioActual)} - ${formatCurrency(row.cierreAnterior)} {row.porcentajeCambio.toFixed(2)}% - {formatFullDateTime(row.fechaRegistro)} ))}
+ + {/* Sección para Otros Tickers (solo para consulta) */} + {otherData && otherData.length > 0 && ( + + + Otros Tickers (Solo Consulta) + + + + + + Ticker + Nombre + Precio + + + + {otherData.map(row => ( + + {row.ticker} + {row.nombreEmpresa} + ${formatCurrency(row.precioActual)} + + ))} + +
+
+
+ )}
); }; \ No newline at end of file diff --git a/frontend/src/components/raw-data/RawBolsaUsaTable.tsx b/frontend/src/components/raw-data/RawBolsaUsaTable.tsx index 9be22a6..4553184 100644 --- a/frontend/src/components/raw-data/RawBolsaUsaTable.tsx +++ b/frontend/src/components/raw-data/RawBolsaUsaTable.tsx @@ -1,4 +1,4 @@ -import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material'; +import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, Typography, Divider } from '@mui/material'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; // Importaciones de nuestro proyecto @@ -8,98 +8,125 @@ import type { CotizacionBolsa } from '../../models/mercadoModels'; import { formatCurrency, formatFullDateTime } from '../../utils/formatters'; import { copyToClipboard } from '../../utils/clipboardUtils'; import { HolidayAlert } from '../common/HolidayAlert'; +import { TICKERS_PRIORITARIOS_USA } from '../../config/priorityTickers'; /** - * Función para convertir los datos de la tabla a formato CSV. + * Función para convertir los datos prioritarios a formato TSV (Tab-Separated Values). */ -const toCSV = (headers: string[], data: CotizacionBolsa[]) => { - const headerRow = headers.join(';'); - const dataRows = data.map(row => - [ - row.ticker, - row.nombreEmpresa, - formatCurrency(row.precioActual, 'USD'), - formatCurrency(row.cierreAnterior, 'USD'), - `${row.porcentajeCambio.toFixed(2)}%`, - formatFullDateTime(row.fechaRegistro) - ].join(';') - ); - return [headerRow, ...dataRows].join('\n'); +const toTSV = (data: CotizacionBolsa[]) => { + const dataRows = data.map(row => { + // Formateamos los datos según los requisitos de redacción + const nombreCompleto = `${row.ticker} (${row.nombreEmpresa || ''})`; + const precio = `$${formatCurrency(row.precioActual)}`; + const cambio = `${row.porcentajeCambio.toFixed(2)}%`; + + return [nombreCompleto, precio, cambio].join('\t'); + }); + return dataRows.join('\n'); }; /** * Componente de tabla de datos crudos para la Bolsa de EEUU y ADRs, - * diseñado para la página de redacción. + * adaptado para las necesidades de redacción. */ export const RawBolsaUsaTable = () => { - // Hooks para obtener los datos y el estado de feriado para el mercado de EEUU. const { data, loading: dataLoading, error: dataError } = useApiData('/mercados/bolsa/eeuu'); const isHoliday = useIsHoliday('US'); + // Separamos los datos en prioritarios y el resto, manteniendo el orden de la lista + const priorityData = data?.filter(d => TICKERS_PRIORITARIOS_USA.includes(d.ticker)) + .sort((a, b) => TICKERS_PRIORITARIOS_USA.indexOf(a.ticker) - TICKERS_PRIORITARIOS_USA.indexOf(b.ticker)); + + const otherData = data?.filter(d => !TICKERS_PRIORITARIOS_USA.includes(d.ticker)); + const handleCopy = () => { - if (!data) return; - const headers = ["Ticker", "Nombre", "Último Precio (USD)", "Cierre Anterior (USD)", "Variación %", "Fecha de Registro"]; - const csvData = toCSV(headers, data); + if (!priorityData) return; + const tsvData = toTSV(priorityData); - copyToClipboard(csvData) - .then(() => alert('¡Tabla copiada al portapapeles!')) + copyToClipboard(tsvData) + .then(() => alert('Datos prioritarios copiados al portapapeles!')) .catch(err => { console.error('Error al copiar:', err); alert('Error: No se pudo copiar la tabla.'); }); }; - // Estado de carga unificado. const isLoading = dataLoading || isHoliday === null; if (isLoading) return ; if (dataError) return {dataError}; if (!data || data.length === 0) { - if (isHoliday) { - return ; - } - return No hay datos disponibles para el mercado de EEUU (el fetcher puede estar desactivado).; + if (isHoliday) { return ; } + return No hay datos disponibles para el mercado de EEUU.; } return ( - {/* Si es feriado, mostramos una alerta informativa encima de la tabla. */} - {isHoliday && ( - - - - )} + {isHoliday && } - + + + + Última actualización: {formatFullDateTime(data[0].fechaRegistro)} + + + + {/* Tabla de Datos Prioritarios */} - Ticker - Nombre - Último Precio - Cierre Anterior - Variación % - Última Act. + Símbolo (Nombre) + Precio Actual + % Cambio - {data.map(row => ( + {priorityData?.map(row => ( - {row.ticker} - {row.nombreEmpresa} + + {row.ticker} + ({row.nombreEmpresa}) + {formatCurrency(row.precioActual, 'USD')} - {formatCurrency(row.cierreAnterior, 'USD')} {row.porcentajeCambio.toFixed(2)}% - {formatFullDateTime(row.fechaRegistro)} ))}
+ + {/* Sección para Otros Tickers (solo para consulta) */} + {otherData && otherData.length > 0 && ( + + + Otros Tickers (Solo Consulta) + + + + + + Ticker + Nombre + Precio + + + + {otherData.map(row => ( + + {row.ticker} + {row.nombreEmpresa} + {formatCurrency(row.precioActual, 'USD')} + + ))} + +
+
+
+ )}
); }; \ No newline at end of file diff --git a/frontend/src/components/raw-data/RawGranosTable.tsx b/frontend/src/components/raw-data/RawGranosTable.tsx index da17bce..27bc2cd 100644 --- a/frontend/src/components/raw-data/RawGranosTable.tsx +++ b/frontend/src/components/raw-data/RawGranosTable.tsx @@ -1,29 +1,32 @@ -import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button } from '@mui/material'; +import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Button, Typography } from '@mui/material'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; // Importaciones de nuestro proyecto import { useApiData } from '../../hooks/useApiData'; import { useIsHoliday } from '../../hooks/useIsHoliday'; import type { CotizacionGrano } from '../../models/mercadoModels'; -import { formatInteger, formatDateOnly, formatFullDateTime } from '../../utils/formatters'; +import { formatInteger, formatFullDateTime } from '../../utils/formatters'; import { copyToClipboard } from '../../utils/clipboardUtils'; import { HolidayAlert } from '../common/HolidayAlert'; /** - * Función para convertir los datos de la tabla a formato CSV. + * Función para convertir los datos a formato TSV (Tab-Separated Values) + * con el formato específico solicitado por redacción. */ -const toCSV = (headers: string[], data: CotizacionGrano[]) => { - const headerRow = headers.join(';'); - const dataRows = data.map(row => - [ - row.nombre, - formatInteger(row.precio), - formatInteger(row.variacionPrecio), - formatDateOnly(row.fechaOperacion), - formatFullDateTime(row.fechaRegistro) - ].join(';') - ); - return [headerRow, ...dataRows].join('\n'); +const toTSV = (data: CotizacionGrano[]) => { + const dataRows = data.map(row => { + // Formateamos la variación para que muestre "=" si es cero. + const variacion = row.variacionPrecio === 0 + ? '= 0' + : formatInteger(row.variacionPrecio); + + const precio = formatInteger(row.precio); + + // Unimos los campos con un carácter de tabulación '\t' + return [row.nombre, precio, variacion].join('\t'); + }); + + return dataRows.join('\n'); }; /** @@ -31,24 +34,21 @@ const toCSV = (headers: string[], data: CotizacionGrano[]) => { * diseñado para la página de redacción. */ export const RawGranosTable = () => { - // Hooks para obtener los datos y el estado de feriado para el mercado argentino. const { data, loading: dataLoading, error: dataError } = useApiData('/mercados/granos'); const isHoliday = useIsHoliday('BA'); const handleCopy = () => { if (!data) return; - const headers = ["Grano", "Precio ($/Tn)", "Variación", "Fecha Op.", "Fecha de Registro"]; - const csvData = toCSV(headers, data); + const tsvData = toTSV(data); - copyToClipboard(csvData) - .then(() => alert('¡Tabla copiada al portapapeles!')) + copyToClipboard(tsvData) + .then(() => alert('Datos de Granos copiados al portapapeles!')) .catch(err => { console.error('Error al copiar:', err); alert('Error: No se pudo copiar la tabla.'); }); }; - // Estado de carga unificado. const isLoading = dataLoading || isHoliday === null; if (isLoading) return ; @@ -63,16 +63,21 @@ export const RawGranosTable = () => { return ( - {/* Si es feriado, mostramos una alerta informativa encima de la tabla. */} {isHoliday && ( )} - + + + + Última actualización: {formatFullDateTime(data[0].fechaRegistro)} + + + @@ -80,8 +85,6 @@ export const RawGranosTable = () => { Grano Precio ($/Tn) Variación - Fecha Op. - Última Act. @@ -89,9 +92,7 @@ export const RawGranosTable = () => { {row.nombre} ${formatInteger(row.precio)} - {formatInteger(row.variacionPrecio)} - {formatDateOnly(row.fechaOperacion)} - {formatFullDateTime(row.fechaRegistro)} + {row.variacionPrecio === 0 ? '= 0' : formatInteger(row.variacionPrecio)} ))} diff --git a/frontend/src/config/priorityTickers.ts b/frontend/src/config/priorityTickers.ts new file mode 100644 index 0000000..3a1b24c --- /dev/null +++ b/frontend/src/config/priorityTickers.ts @@ -0,0 +1,11 @@ +export const TICKERS_PRIORITARIOS_LOCAL = [ + '^MERV', 'GGAL.BA', 'YPFD.BA', 'PAMP.BA', 'BMA.BA', + 'COME.BA', 'TECO2.BA', 'EDN.BA', 'CRES.BA', 'TXAR.BA', + 'MIRG.BA', 'CEPU.BA', 'LOMA.BA', 'VALO.BA' +]; + +// Dejaremos las otras listas aquí para los siguientes componentes +export const TICKERS_PRIORITARIOS_USA = [ + 'AAPL', 'AMD', 'AMZN', 'BRK-B', 'KO', 'MSFT', 'NVDA', + 'GLD', 'XLF', 'XLI', 'XLE', 'XLK', 'MELI' +]; \ No newline at end of file