Feat: RawData Table Format
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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/ | ||||
|   | ||||
| @@ -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<CotizacionGanado[]>('/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 <CircularProgress />; | ||||
| @@ -67,43 +60,37 @@ export const RawAgroTable = () => { | ||||
|  | ||||
|     return ( | ||||
|         <Box> | ||||
|             {/* Si es feriado, mostramos una alerta informativa encima de la tabla. */} | ||||
|             {isHoliday && ( | ||||
|                 <Box sx={{ mb: 2 }}> | ||||
|                     <HolidayAlert /> | ||||
|                 </Box> | ||||
|             )} | ||||
|  | ||||
|             <Button startIcon={<ContentCopyIcon />} onClick={handleCopy} sx={{ mb: 1 }}> | ||||
|                 Copiar como CSV | ||||
|             </Button> | ||||
|             <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}> | ||||
|                 <Button startIcon={<ContentCopyIcon />} onClick={handleCopy}> | ||||
|                     Copiar Datos para Redacción | ||||
|                 </Button> | ||||
|                 <Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}> | ||||
|                     Última actualización: {formatFullDateTime(data[0].fechaRegistro)} | ||||
|                 </Typography> | ||||
|             </Box> | ||||
|  | ||||
|             {/* La tabla ahora muestra solo las columnas requeridas para facilitar la visualización */} | ||||
|             <TableContainer component={Paper}> | ||||
|                 <Table size="small"> | ||||
|                     <TableHead> | ||||
|                         <TableRow> | ||||
|                             <TableCell>Categoría</TableCell> | ||||
|                             <TableCell>Especificaciones</TableCell> | ||||
|                             <TableCell align="right">Máximo</TableCell> | ||||
|                             <TableCell align="right">Mínimo</TableCell> | ||||
|                             <TableCell align="right">Mediano</TableCell> | ||||
|                             <TableCell>Categoría / Especificaciones</TableCell> | ||||
|                             <TableCell align="right">Cabezas</TableCell> | ||||
|                             <TableCell align="right">Kg Total</TableCell> | ||||
|                             <TableCell align="right">Importe Total</TableCell> | ||||
|                             <TableCell>Última Act.</TableCell> | ||||
|                         </TableRow> | ||||
|                     </TableHead> | ||||
|                     <TableBody> | ||||
|                         {data.map(row => ( | ||||
|                             <TableRow key={row.id}> | ||||
|                                 <TableCell>{row.categoria}</TableCell> | ||||
|                                 <TableCell>{row.especificaciones}</TableCell> | ||||
|                                 <TableCell align="right">${formatCurrency(row.maximo)}</TableCell> | ||||
|                                 <TableCell align="right">${formatCurrency(row.minimo)}</TableCell> | ||||
|                                 <TableCell align="right">${formatCurrency(row.mediano)}</TableCell> | ||||
|                                 <TableCell>{row.categoria} / {row.especificaciones}</TableCell> | ||||
|                                 <TableCell align="right">{formatInteger(row.cabezas)}</TableCell> | ||||
|                                 <TableCell align="right">{formatInteger(row.kilosTotales)}</TableCell> | ||||
|                                 <TableCell align="right">${formatInteger(row.importeTotal)}</TableCell> | ||||
|                                 <TableCell>{formatFullDateTime(row.fechaRegistro)}</TableCell> | ||||
|                             </TableRow> | ||||
|                         ))} | ||||
|                     </TableBody> | ||||
|   | ||||
| @@ -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<CotizacionBolsa[]>('/mercados/bolsa/local'); | ||||
|     const isHoliday = useIsHoliday('BA'); | ||||
|     const { data, loading, error } = useApiData<CotizacionBolsa[]>('/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 <CircularProgress />; | ||||
|     if (dataError) return <Alert severity="error">{dataError}</Alert>; | ||||
|  | ||||
|     if (!data || data.length === 0) { | ||||
|         if (isHoliday) { | ||||
|             return <HolidayAlert />; | ||||
|         } | ||||
|         return <Alert severity="info">No hay datos disponibles para el mercado local.</Alert>; | ||||
|     } | ||||
|     if (loading) return <CircularProgress />; | ||||
|     if (error) return <Alert severity="error">{error}</Alert>; | ||||
|     if (!data || data.length === 0) return <Alert severity="info">No hay datos disponibles para el mercado local.</Alert>; | ||||
|  | ||||
|     return ( | ||||
|         <Box> | ||||
|             {/* Si es feriado, mostramos una alerta informativa encima de la tabla. */} | ||||
|             {isHoliday && ( | ||||
|                 <Box sx={{ mb: 2 }}> | ||||
|                     <HolidayAlert /> | ||||
|                 </Box> | ||||
|             )} | ||||
|             <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}> | ||||
|                 <Button startIcon={<ContentCopyIcon />} onClick={handleCopy}> | ||||
|                     Copiar Datos Principales | ||||
|                 </Button> | ||||
|                 <Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}> | ||||
|                     Última actualización: {formatFullDateTime(data[0].fechaRegistro)} | ||||
|                 </Typography> | ||||
|             </Box> | ||||
|  | ||||
|             <Button startIcon={<ContentCopyIcon />} onClick={handleCopy} sx={{ mb: 1 }}> | ||||
|                 Copiar como CSV | ||||
|             </Button> | ||||
|             {/* Tabla de Datos Prioritarios */} | ||||
|             <TableContainer component={Paper}> | ||||
|                 <Table size="small"> | ||||
|                     <TableHead> | ||||
|                         <TableRow> | ||||
|                             <TableCell>Ticker</TableCell> | ||||
|                             <TableCell>Nombre</TableCell> | ||||
|                             <TableCell align="right">Último Precio</TableCell> | ||||
|                             <TableCell align="right">Cierre Anterior</TableCell> | ||||
|                             <TableCell align="right">Variación %</TableCell> | ||||
|                             <TableCell>Última Act.</TableCell> | ||||
|                             <TableCell>Símbolo (Nombre)</TableCell> | ||||
|                             <TableCell align="right">Precio Actual</TableCell> | ||||
|                             <TableCell align="right">% Cambio</TableCell> | ||||
|                         </TableRow> | ||||
|                     </TableHead> | ||||
|                     <TableBody> | ||||
|                         {data.map(row => ( | ||||
|                         {priorityData?.map(row => ( | ||||
|                             <TableRow key={row.id}> | ||||
|                                 <TableCell>{row.ticker}</TableCell> | ||||
|                                 <TableCell>{row.nombreEmpresa}</TableCell> | ||||
|                                 <TableCell> | ||||
|                                     <Typography component="span" sx={{ fontWeight: 'bold' }}>{row.ticker}</Typography> | ||||
|                                     <Typography component="span" sx={{ ml: 1, color: 'text.secondary' }}>({row.nombreEmpresa})</Typography> | ||||
|                                 </TableCell> | ||||
|                                 <TableCell align="right">${formatCurrency(row.precioActual)}</TableCell> | ||||
|                                 <TableCell align="right">${formatCurrency(row.cierreAnterior)}</TableCell> | ||||
|                                 <TableCell align="right">{row.porcentajeCambio.toFixed(2)}%</TableCell> | ||||
|                                 <TableCell>{formatFullDateTime(row.fechaRegistro)}</TableCell> | ||||
|                             </TableRow> | ||||
|                         ))} | ||||
|                     </TableBody> | ||||
|                 </Table> | ||||
|             </TableContainer> | ||||
|  | ||||
|             {/* Sección para Otros Tickers (solo para consulta) */} | ||||
|             {otherData && otherData.length > 0 && ( | ||||
|                 <Box mt={4}> | ||||
|                     <Divider sx={{ mb: 2 }}> | ||||
|                         <Typography variant="overline">Otros Tickers (Solo Consulta)</Typography> | ||||
|                     </Divider> | ||||
|                     <TableContainer component={Paper}> | ||||
|                         <Table size="small"> | ||||
|                             <TableHead> | ||||
|                                 <TableRow> | ||||
|                                     <TableCell>Ticker</TableCell> | ||||
|                                     <TableCell>Nombre</TableCell> | ||||
|                                     <TableCell align="right">Precio</TableCell> | ||||
|                                 </TableRow> | ||||
|                             </TableHead> | ||||
|                             <TableBody> | ||||
|                                 {otherData.map(row => ( | ||||
|                                     <TableRow key={row.id}> | ||||
|                                         <TableCell>{row.ticker}</TableCell> | ||||
|                                         <TableCell>{row.nombreEmpresa}</TableCell> | ||||
|                                         <TableCell align="right">${formatCurrency(row.precioActual)}</TableCell> | ||||
|                                     </TableRow> | ||||
|                                 ))} | ||||
|                             </TableBody> | ||||
|                         </Table> | ||||
|                     </TableContainer> | ||||
|                 </Box> | ||||
|             )} | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
| @@ -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<CotizacionBolsa[]>('/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 <CircularProgress />; | ||||
|     if (dataError) return <Alert severity="error">{dataError}</Alert>; | ||||
|  | ||||
|     if (!data || data.length === 0) { | ||||
|         if (isHoliday) { | ||||
|             return <HolidayAlert />; | ||||
|         } | ||||
|         return <Alert severity="info">No hay datos disponibles para el mercado de EEUU (el fetcher puede estar desactivado).</Alert>; | ||||
|         if (isHoliday) { return <HolidayAlert />; } | ||||
|         return <Alert severity="info">No hay datos disponibles para el mercado de EEUU.</Alert>; | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <Box> | ||||
|             {/* Si es feriado, mostramos una alerta informativa encima de la tabla. */} | ||||
|             {isHoliday && ( | ||||
|                 <Box sx={{ mb: 2 }}> | ||||
|                     <HolidayAlert /> | ||||
|                 </Box> | ||||
|             )} | ||||
|             {isHoliday && <Box sx={{ mb: 2 }}><HolidayAlert /></Box>} | ||||
|  | ||||
|             <Button startIcon={<ContentCopyIcon />} onClick={handleCopy} sx={{ mb: 1 }}> | ||||
|                 Copiar como CSV | ||||
|             </Button> | ||||
|             <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}> | ||||
|                 <Button startIcon={<ContentCopyIcon />} onClick={handleCopy}> | ||||
|                     Copiar Datos Principales | ||||
|                 </Button> | ||||
|                 <Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}> | ||||
|                     Última actualización: {formatFullDateTime(data[0].fechaRegistro)} | ||||
|                 </Typography> | ||||
|             </Box> | ||||
|  | ||||
|             {/* Tabla de Datos Prioritarios */} | ||||
|             <TableContainer component={Paper}> | ||||
|                 <Table size="small"> | ||||
|                     <TableHead> | ||||
|                         <TableRow> | ||||
|                             <TableCell>Ticker</TableCell> | ||||
|                             <TableCell>Nombre</TableCell> | ||||
|                             <TableCell align="right">Último Precio</TableCell> | ||||
|                             <TableCell align="right">Cierre Anterior</TableCell> | ||||
|                             <TableCell align="right">Variación %</TableCell> | ||||
|                             <TableCell>Última Act.</TableCell> | ||||
|                             <TableCell>Símbolo (Nombre)</TableCell> | ||||
|                             <TableCell align="right">Precio Actual</TableCell> | ||||
|                             <TableCell align="right">% Cambio</TableCell> | ||||
|                         </TableRow> | ||||
|                     </TableHead> | ||||
|                     <TableBody> | ||||
|                         {data.map(row => ( | ||||
|                         {priorityData?.map(row => ( | ||||
|                             <TableRow key={row.id}> | ||||
|                                 <TableCell>{row.ticker}</TableCell> | ||||
|                                 <TableCell>{row.nombreEmpresa}</TableCell> | ||||
|                                 <TableCell> | ||||
|                                     <Typography component="span" sx={{ fontWeight: 'bold' }}>{row.ticker}</Typography> | ||||
|                                     <Typography component="span" sx={{ ml: 1, color: 'text.secondary' }}>({row.nombreEmpresa})</Typography> | ||||
|                                 </TableCell> | ||||
|                                 <TableCell align="right">{formatCurrency(row.precioActual, 'USD')}</TableCell> | ||||
|                                 <TableCell align="right">{formatCurrency(row.cierreAnterior, 'USD')}</TableCell> | ||||
|                                 <TableCell align="right">{row.porcentajeCambio.toFixed(2)}%</TableCell> | ||||
|                                 <TableCell>{formatFullDateTime(row.fechaRegistro)}</TableCell> | ||||
|                             </TableRow> | ||||
|                         ))} | ||||
|                     </TableBody> | ||||
|                 </Table> | ||||
|             </TableContainer> | ||||
|  | ||||
|             {/* Sección para Otros Tickers (solo para consulta) */} | ||||
|             {otherData && otherData.length > 0 && ( | ||||
|                 <Box mt={4}> | ||||
|                     <Divider sx={{ mb: 2 }}> | ||||
|                         <Typography variant="overline">Otros Tickers (Solo Consulta)</Typography> | ||||
|                     </Divider> | ||||
|                     <TableContainer component={Paper}> | ||||
|                         <Table size="small"> | ||||
|                             <TableHead> | ||||
|                                 <TableRow> | ||||
|                                     <TableCell>Ticker</TableCell> | ||||
|                                     <TableCell>Nombre</TableCell> | ||||
|                                     <TableCell align="right">Precio</TableCell> | ||||
|                                 </TableRow> | ||||
|                             </TableHead> | ||||
|                             <TableBody> | ||||
|                                 {otherData.map(row => ( | ||||
|                                     <TableRow key={row.id}> | ||||
|                                         <TableCell>{row.ticker}</TableCell> | ||||
|                                         <TableCell>{row.nombreEmpresa}</TableCell> | ||||
|                                         <TableCell align="right">{formatCurrency(row.precioActual, 'USD')}</TableCell> | ||||
|                                     </TableRow> | ||||
|                                 ))} | ||||
|                             </TableBody> | ||||
|                         </Table> | ||||
|                     </TableContainer> | ||||
|                 </Box> | ||||
|             )} | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
| @@ -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<CotizacionGrano[]>('/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 <CircularProgress />; | ||||
| @@ -63,16 +63,21 @@ export const RawGranosTable = () => { | ||||
|  | ||||
|     return ( | ||||
|         <Box> | ||||
|             {/* Si es feriado, mostramos una alerta informativa encima de la tabla. */} | ||||
|             {isHoliday && ( | ||||
|                 <Box sx={{ mb: 2 }}> | ||||
|                     <HolidayAlert /> | ||||
|                 </Box> | ||||
|             )} | ||||
|  | ||||
|             <Button startIcon={<ContentCopyIcon />} onClick={handleCopy} sx={{ mb: 1 }}> | ||||
|                 Copiar como CSV | ||||
|             </Button> | ||||
|             <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}> | ||||
|                 <Button startIcon={<ContentCopyIcon />} onClick={handleCopy}> | ||||
|                     Copiar Datos para Redacción | ||||
|                 </Button> | ||||
|                 <Typography variant="caption" sx={{ fontStyle: 'italic', color: 'text.secondary' }}> | ||||
|                     Última actualización: {formatFullDateTime(data[0].fechaRegistro)} | ||||
|                 </Typography> | ||||
|             </Box> | ||||
|              | ||||
|             <TableContainer component={Paper}> | ||||
|                 <Table size="small"> | ||||
|                     <TableHead> | ||||
| @@ -80,8 +85,6 @@ export const RawGranosTable = () => { | ||||
|                             <TableCell>Grano</TableCell> | ||||
|                             <TableCell align="right">Precio ($/Tn)</TableCell> | ||||
|                             <TableCell align="right">Variación</TableCell> | ||||
|                             <TableCell>Fecha Op.</TableCell> | ||||
|                             <TableCell>Última Act.</TableCell> | ||||
|                         </TableRow> | ||||
|                     </TableHead> | ||||
|                     <TableBody> | ||||
| @@ -89,9 +92,7 @@ export const RawGranosTable = () => { | ||||
|                             <TableRow key={row.id}> | ||||
|                                 <TableCell>{row.nombre}</TableCell> | ||||
|                                 <TableCell align="right">${formatInteger(row.precio)}</TableCell> | ||||
|                                 <TableCell align="right">{formatInteger(row.variacionPrecio)}</TableCell> | ||||
|                                 <TableCell>{formatDateOnly(row.fechaOperacion)}</TableCell> | ||||
|                                 <TableCell>{formatFullDateTime(row.fechaRegistro)}</TableCell> | ||||
|                                 <TableCell align="right">{row.variacionPrecio === 0 ? '= 0' : formatInteger(row.variacionPrecio)}</TableCell> | ||||
|                             </TableRow> | ||||
|                         ))} | ||||
|                     </TableBody> | ||||
|   | ||||
							
								
								
									
										11
									
								
								frontend/src/config/priorityTickers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								frontend/src/config/priorityTickers.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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' | ||||
| ]; | ||||
		Reference in New Issue
	
	Block a user