diff --git a/frontend/src/components/BolsaLocalWidget.tsx b/frontend/src/components/BolsaLocalWidget.tsx index 3d6455e..2a8a249 100644 --- a/frontend/src/components/BolsaLocalWidget.tsx +++ b/frontend/src/components/BolsaLocalWidget.tsx @@ -1,4 +1,3 @@ -// Importaciones de React y Material-UI import React, { useState, useRef } from 'react'; import { Box, CircularProgress, Alert, Table, TableBody, TableCell, TableContainer, @@ -11,7 +10,7 @@ import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import RemoveIcon from '@mui/icons-material/Remove'; import { PiChartLineUpBold } from 'react-icons/pi'; -// Importaciones de nuestros modelos, hooks y utilidades +// Importaciones de nuestro proyecto import type { CotizacionBolsa } from '../models/mercadoModels'; import { useApiData } from '../hooks/useApiData'; import { useIsHoliday } from '../hooks/useIsHoliday'; @@ -34,79 +33,13 @@ const Variacion = ({ value }: { value: number }) => { }; /** - * Sub-componente que renderiza la tabla de acciones detalladas. - * Se extrae para mantener el componente principal más limpio. - */ -const RenderContent = ({ data, handleOpenModal }: { - data: CotizacionBolsa[], - handleOpenModal: (ticker: string, event: React.MouseEvent) => void, -}) => { - // Filtramos para obtener solo las acciones, excluyendo el índice. - const panelPrincipal = data.filter(d => d.ticker !== '^MERV'); - - if (panelPrincipal.length === 0) { - return No hay acciones líderes para mostrar en este momento.; - } - - return ( - - - - Última actualización de acciones: {formatFullDateTime(panelPrincipal[0].fechaRegistro)} - - - - - - Símbolo - Precio Actual - Apertura - Cierre Anterior - % Cambio - Historial - - - - {panelPrincipal.map((row) => ( - - {row.ticker} - ${formatCurrency(row.precioActual)} - ${formatCurrency(row.apertura)} - ${formatCurrency(row.cierreAnterior)} - - - handleOpenModal(row.ticker, event)} - sx={{ - boxShadow: '0 1px 3px rgba(0,0,0,0.1)', - transition: 'all 0.2s ease-in-out', - '&:hover': { transform: 'scale(1.1)', boxShadow: '0 2px 6px rgba(0,0,0,0.2)' } - }} - > - - - - - ))} - -
-
- ); -}; - - -/** - * Widget principal para la sección "Bolsa Local". - * Muestra una tarjeta de héroe para el MERVAL y una tabla detallada para las acciones líderes. + * Widget autónomo para la tabla de acciones líderes locales (Panel Merval). */ export const BolsaLocalWidget = () => { - // Hooks para obtener los datos y el estado de feriado. Las llamadas se disparan en paralelo. + // Este widget obtiene todos los datos del mercado local y luego los filtra. const { data, loading: dataLoading, error: dataError } = useApiData('/mercados/bolsa/local'); const isHoliday = useIsHoliday('BA'); - // Estado y referencia para manejar el modal del gráfico. const [selectedTicker, setSelectedTicker] = useState(null); const triggerButtonRef = useRef(null); @@ -117,14 +50,14 @@ export const BolsaLocalWidget = () => { const handleCloseDialog = () => { setSelectedTicker(null); - // Devuelve el foco al botón que abrió el modal para mejorar la accesibilidad. setTimeout(() => { triggerButtonRef.current?.focus(); }, 0); }; - // Estado de carga unificado: el componente está "cargando" si los datos principales - // o la información del feriado todavía no han llegado. + // Filtramos para obtener solo las acciones, excluyendo el índice. + const panelPrincipal = data?.filter(d => d.ticker !== '^MERV') || []; + const isLoading = dataLoading || isHoliday === null; if (isLoading) { @@ -135,31 +68,69 @@ export const BolsaLocalWidget = () => { return {dataError}; } - // Si no hay ningún dato en absoluto, mostramos un mensaje final. - if (!data || data.length === 0) { - // Si sabemos que es feriado, la alerta de feriado tiene prioridad. + // Si después de filtrar no queda ninguna acción, mostramos el mensaje apropiado. + if (panelPrincipal.length === 0) { + // Si sabemos que es feriado, la alerta de feriado es el mensaje más relevante. if (isHoliday) { return ; } - return No hay datos disponibles para el mercado local.; + return No hay acciones líderes disponibles para mostrar.; } return ( - <> - {/* Si es feriado, mostramos la alerta informativa en la parte superior. */} + + {/* La alerta de feriado también se aplica a esta tabla. */} {isHoliday && ( )} - {/* La tabla de acciones detalladas se muestra siempre que haya datos para ella. */} - + + + + Última actualización de acciones: {formatFullDateTime(panelPrincipal[0].fechaRegistro)} + + + + + + Símbolo + Precio Actual + Apertura + Cierre Anterior + % Cambio + Historial + + + + {panelPrincipal.map((row) => ( + + {row.ticker} + ${formatCurrency(row.precioActual)} + ${formatCurrency(row.apertura)} + ${formatCurrency(row.cierreAnterior)} + + + handleOpenModal(row.ticker, event)} + sx={{ + boxShadow: '0 1px 3px rgba(0,0,0,0.1)', + transition: 'all 0.2s ease-in-out', + '&:hover': { transform: 'scale(1.1)', boxShadow: '0 2px 6px rgba(0,0,0,0.2)' } + }} + > + + + + + ))} + +
+
- {/* El Dialog para mostrar el gráfico histórico. */} { {selectedTicker && } - +
); }; \ No newline at end of file diff --git a/frontend/src/components/MervalHeroCard.tsx b/frontend/src/components/MervalHeroCard.tsx index 1be9f45..be3fb0d 100644 --- a/frontend/src/components/MervalHeroCard.tsx +++ b/frontend/src/components/MervalHeroCard.tsx @@ -5,12 +5,17 @@ import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import RemoveIcon from '@mui/icons-material/Remove'; import type { CotizacionBolsa } from '../models/mercadoModels'; -import { formatCurrency, formatCurrency2Decimal } from '../utils/formatters'; +import { formatCurrency2Decimal, formatCurrency } from '../utils/formatters'; import { HistoricalChartWidget } from './HistoricalChartWidget'; import { useApiData } from '../hooks/useApiData'; +import { useIsHoliday } from '../hooks/useIsHoliday'; // <-- Importamos el hook +import { HolidayAlert } from './common/HolidayAlert'; // <-- Importamos la alerta +/** + * Sub-componente para la variación del índice. + */ const VariacionMerval = ({ actual, anterior }: { actual: number, anterior: number }) => { - if (anterior === 0) return null; // Evitar división por cero + if (anterior === 0) return null; const variacionPuntos = actual - anterior; const variacionPorcentaje = (variacionPuntos / anterior) * 100; @@ -34,41 +39,78 @@ const VariacionMerval = ({ actual, anterior }: { actual: number, anterior: numbe ); }; +/** + * Widget autónomo para la tarjeta de héroe del S&P Merval. + */ export const MervalHeroCard = () => { - const { data: allLocalData, loading, error } = useApiData('/mercados/bolsa/local'); + // Cada widget gestiona sus propias llamadas a la API + const { data: allLocalData, loading: dataLoading, error: dataError } = useApiData('/mercados/bolsa/local'); + const isHoliday = useIsHoliday('BA'); + + // Estado interno para el gráfico const [dias, setDias] = useState(30); const handleRangoChange = ( _event: React.MouseEvent, nuevoRango: number | null ) => { if (nuevoRango !== null) { setDias(nuevoRango); } }; + // Filtramos el dato específico que este widget necesita const mervalData = allLocalData?.find(d => d.ticker === '^MERV'); + + // --- LÓGICA DE RENDERIZADO CORREGIDA --- + + // El estado de carga depende de AMBAS llamadas a la API. + const isLoading = dataLoading || isHoliday === null; - if (loading) { return ; } - if (error) { return {error}; } - if (!mervalData) { return No se encontraron datos para el índice MERVAL.; } + if (isLoading) { + return ; + } + if (dataError) { + return {dataError}; + } + + // Si no hay datos del Merval, es un estado final. + if (!mervalData) { + // Si no hay datos PERO sabemos que es feriado, la alerta de feriado es más informativa. + if (isHoliday) { + return ; + } + return No se encontraron datos para el índice MERVAL.; + } + + // Si llegamos aquí, SÍ tenemos datos para mostrar. return ( - - - - Índice S&P MERVAL - {formatCurrency2Decimal(mervalData.precioActual)} + + {/* Si es feriado, mostramos la alerta como un AVISO encima del contenido. */} + {isHoliday && ( + + - - + )} + + {/* El contenido principal del widget siempre se muestra si hay datos. */} + + + + Índice S&P MERVAL + {formatCurrency2Decimal(mervalData.precioActual)} + + + + - - - - - Semanal - Mensual - Anual - + + + + Semanal + Mensual + Anual + + + - - - + + ); }; \ No newline at end of file diff --git a/src/Mercados.Infrastructure/DataFetchers/HolidayDataFetcher.cs b/src/Mercados.Infrastructure/DataFetchers/HolidayDataFetcher.cs index 9638307..af1e71d 100644 --- a/src/Mercados.Infrastructure/DataFetchers/HolidayDataFetcher.cs +++ b/src/Mercados.Infrastructure/DataFetchers/HolidayDataFetcher.cs @@ -46,7 +46,7 @@ namespace Mercados.Infrastructure.DataFetchers public async Task<(bool Success, string Message)> FetchDataAsync() { - _logger.LogInformation("Iniciando actualización de feriados desde Finnhub."); + _logger.LogInformation("Iniciando actualización de feriados."); var apiKey = _configuration["ApiKeys:Finnhub"]; if (string.IsNullOrEmpty(apiKey)) return (false, "API Key de Finnhub no configurada.");