AvanzaCast/docs/broadcast_panel.md

44 KiB

INFORME TÉCNICO COMPLETO

I. Estructura y Componentes Lógicos:

La página presenta una estructura de panel de control (dashboard) dividida en un diseño de dos columnas principales: una barra lateral de navegación fija a la izquierda y un área de contenido principal dinámica a la derecha.

  • Componentes Lógicos Modulares:
    1. PageContainer.tsx: Actúa como el contenedor principal, organizando la disposición global.

      • Jerarquía DOM: Un div principal que encapsula una aside para la barra lateral y una main para el contenido principal.
      • Medidas Clave:
        • Layout: display: flex; para posicionar la barra lateral y el contenido.
        • Ancho de aside (barra lateral): Aproximadamente 260px (fijo).
        • main (contenido principal): flex-grow: 1; para ocupar el espacio restante.
        • El contenido dentro de main tiene un padding horizontal de aproximadamente 32px.
        • No se observa un max-width global para toda la aplicación; el diseño se adapta al ancho de la ventana, con el contenido interno centrado o con padding.
    2. Sidebar.tsx: Contiene la navegación principal de la aplicación.

      • Jerarquía DOM: aside (contenedor) > div (logo y título "StreamYard") > nav (para enlaces de navegación) > ul > li (elementos de menú) > div (sección de almacenamiento) > div (sección de configuración y ayuda).
      • Medidas Clave:
        • Ancho: 260px (fijo).
        • Altura: 100vh (altura completa de la ventana).
        • Fondo: Blanco.
        • Items de menú: padding vertical de 10px y horizontal de 20px.
        • Elemento activo: background-color (azul claro) y border-left (azul primario) de 4px.
        • Separación entre grupos de enlaces y secciones: margin-top de 20px a 30px.
    3. Header.tsx: La barra superior del área de contenido principal.

      • Jerarquía DOM: header (contenedor) > div (botones "Mejora tu plan") > div (grupo de iconos y "Mi cuenta").
      • Medidas Clave:
        • Altura: Aproximadamente 64px (fija).
        • Fondo: Blanco.
        • Layout: display: flex; justify-content: flex-end; align-items: center; (elementos alineados a la derecha, con espacio entre ellos). padding horizontal de 32px.
        • Borde inferior sutil: border-bottom: 1px solid con color gris claro.
    4. HeroSection.tsx: La sección "Crear", que presenta las opciones principales al usuario.

      • Jerarquía DOM: section (contenedor) > h2 (título "Crear") > div (contenedor de tarjetas) > div (tarjeta individual, por ejemplo, "Transmisión en vivo").
      • Medidas Clave:
        • Sistema de Rejilla: display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; (para escritorio).
        • Tarjetas: min-height de aproximadamente 120px - 150px. padding: 20px;.
        • Radio de borde: border-radius: 8px.
        • Separación: margin-bottom de 40px después de la sección.
    5. TransmissionsTable.tsx: La sección de "Transmisiones y grabaciones".

      • Jerarquía DOM: section (contenedor) > h2 (título "Transmisiones y grabaciones") > div (contenedor de pestañas) > button (pestañas "Próximamente" y "Anteriores") > div (contenedor de la tabla) > table > thead, tbody, tr, th, td.
      • Medidas Clave:
        • Pestañas: display: flex; con padding: 10px 0; margin-right: 20px; para cada botón. La pestaña activa tiene border-bottom: 3px solid (azul primario).
        • Tabla: width: 100%; border-collapse: collapse;.
        • Celdas de tabla: padding: 12px 15px;.
        • Filas de tabla: border-bottom: 1px solid (gris claro).
        • Separación: margin-bottom de 30px después del título de la sección, margin-bottom de 20px después de las pestañas.

II. Métricas Pixel-Perfect:

  • Tipografía:

    • Familia de Fuentes: 'Inter', 'Roboto', sans-serif (estimado visualmente). Se utilizará 'Inter' como principal.
    • Títulos (H1/H2, por ejemplo, "Crear", "Transmisiones y grabaciones"):
      • font-size: 22px
      • font-weight: 600 (Semi-bold)
      • line-height: 1.4
    • Subtítulos (ej. "Próximamente", "Anteriores"):
      • font-size: 16px
      • font-weight: 500 (Medium)
      • line-height: 1.5
    • Texto Principal (P, elementos de menú, contenido de tabla):
      • font-size: 14px
      • font-weight: 400 (Regular)
      • line-height: 1.6
    • Texto Secundario/Pequeño (ej. "0 de 5 horas", "Agregar más"):
      • font-size: 12px
      • font-weight: 400 (Regular)
      • line-height: 1.5
  • Paleta de Colores (Códigos HEX/RGB estimados):

    • Fondo de Página (Main Background): #F7F8FA (rgb(247, 248, 250)) - Gris muy claro.
    • Fondo de Componentes/Tarjetas/Sidebar/Header: #FFFFFF (rgb(255, 255, 255)) - Blanco.
    • Texto Principal (Dark Grey): #212121 (rgb(33, 33, 33)) - Casi negro.
    • Texto Secundario (Medium Grey): #6B7280 (rgb(107, 114, 128)) - Gris medio para subtítulos y texto menos prominente.
    • Color de Acento (Primary Blue): #1876F2 (rgb(24, 118, 242)) - Azul vibrante para elementos interactivos.
    • Fondo Activo de Sidebar/Hover: #EBF3FF (rgb(235, 243, 255)) - Azul muy claro.
    • Bordes/Divisores (Light Grey): #E5E7EB (rgb(229, 231, 235)) - Gris claro.
    • Sombras (Box Shadow): 0px 1px 3px rgba(0, 0, 0, 0.08) (sutil para tarjetas y botón flotante).
    • Color de Iconos (Grey): #6B7280
  • Espaciado y Bordeado:

    • Separación vertical entre secciones: margin-top: 32px; o padding-top: 32px;.
    • Padding horizontal global en contenido principal: 32px.
    • Padding vertical en Header: 16px.
    • Radius de Borde:
      • Botones ("Mejora tu plan", "Entrar al estudio", "Ayuda"): border-radius: 6px.
      • Tarjetas ("Crear" section): border-radius: 8px.
      • Alternadores (Theme Toggle): border-radius: 9999px (píldora).

III. Funcionalidad y Scripts Internos:

  • Funcionalidad del Aplicativo Web:

    1. Navegación Interactiva del Sidebar: Los elementos del menú en la barra lateral son clicables. El elemento actualmente seleccionado ("Inicio") se destaca con un fondo azul claro y un borde azul a la izquierda para indicar su estado activo.
    2. Toggle de Tema (Modo Claro/Oscuro): En la esquina superior derecha del área de contenido principal, hay un control que permite al usuario alternar entre modos de visualización (claro y oscuro), representado por iconos de sol y luna. El modo "claro" (sol) está activo por defecto.
    3. Pestañas de Contenido (Transmisiones y Grabaciones): La sección "Transmisiones y grabaciones" contiene dos pestañas ("Próximamente" y "Anteriores"). Al hacer clic en cada pestaña, el contenido de la tabla subyacente debe actualizarse para mostrar las transmisiones correspondientes a la selección. La pestaña "Próximamente" está activa.
    4. Botón de Acción Flotante ("Ayuda"): Un botón persistente en la parte inferior derecha de la pantalla que ofrece acceso rápido a la ayuda o soporte. Este botón tiene una sombra para darle un efecto elevado.
    5. Estados de Interacción (Hover/Active): Botones, enlaces de navegación y tarjetas presentan cambios visuales (ej. cambio de color de fondo, elevación de sombra) al pasar el cursor (hover) y al hacer clic (active) para proporcionar retroalimentación al usuario.
  • Scripts Internos (Comportamiento):

    1. Manejo de Estado para Navegación Activa: Se utilizará useState en el componente Sidebar para controlar el id o la ruta del elemento de navegación activo, aplicando dinámicamente las clases CSS (styles.activeLink) al elemento seleccionado.
    2. Lógica de Toggle de Tema: Un useState en PageContainer o un contexto global para gestionar el estado del tema (light/dark). Se aplicará una clase CSS (.theme-dark o similar) al body o al contenedor principal para modificar los colores a través de variables CSS (ej. --background-color, --text-color).
    3. Manejo de Estado para Pestañas: Dentro del componente TransmissionsTable, se utilizará useState para mantener el control de la pestaña activa. Esto determinará qué conjunto de datos de transmisiones se renderiza en la tabla.
    4. Renderizado Condicional y Mapeo de Datos: El contenido de la tabla en TransmissionsTable se renderizará condicionalmente o mediante .map() basándose en el estado de la pestaña activa y un array de objetos de transmisiones.
    5. Event Handlers: Funciones de onClick serán implementadas en los elementos interactivos (enlaces de navegación, botones de tema, pestañas) para actualizar los estados correspondientes y desencadenar los cambios en la UI.
    6. Desplazamiento Suave (Scroll Behavior): Aunque no es explícitamente interactivo, la barra lateral debe permanecer fija mientras el contenido principal es desplazable. El botón "Ayuda" también se fijará en la esquina inferior derecha (position: fixed).

GENERACIÓN DE CÓDIGO

A continuación, se presenta el código para los componentes React/TypeScript y sus módulos CSS, siguiendo las métricas y la funcionalidad descritas en el informe técnico. Se incluirá un componente Sidebar adicional por su relevancia estructural en el diseño.

1. src/components/PageContainer.tsx

import React, { useState } from 'react';
import styles from './PageContainer.module.css';
import Sidebar from './Sidebar/Sidebar'; // Assuming Sidebar component
import Header from './Header/Header'; // Assuming Header component
import HeroSection from './HeroSection/HeroSection'; // Assuming HeroSection component
import TransmissionsTable from './TransmissionsTable/TransmissionsTable'; // Assuming TransmissionsTable component

const PageContainer: React.FC = () => {
  // Estado para manejar el tema (claro/oscuro)
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  // Aplica la clase del tema al body o al contenedor principal
  // En un proyecto real, esto podría hacerse con un contexto o CSS variables
  // document.body.className = theme === 'dark' ? 'theme-dark' : 'theme-light';

  return (
    <div className={`${styles.pageContainer} ${theme === 'dark' ? styles.themeDark : styles.themeLight}`}>
      {/* Sidebar - componente lateral de navegación */}
      <Sidebar activeLink="Inicio" />

      {/* Main Content Area */}
      <div className={styles.mainContent}>
        {/* Header - barra superior del contenido principal */}
        <Header currentTheme={theme} onToggleTheme={toggleTheme} />

        <div className={styles.contentWrapper}>
          {/* Hero Section - sección "Crear" */}
          <HeroSection />

          {/* Transmissions Table - sección de tablas */}
          <TransmissionsTable />
        </div>

        {/* Botón flotante de Ayuda */}
        <button className={styles.helpButton}>
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-help-circle">
            <circle cx="12" cy="12" r="10"></circle>
            <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
            <line x1="12" y1="17" x2="12.01" y2="17"></line>
          </svg>
          Ayuda
        </button>
      </div>
    </div>
  );
};

export default PageContainer;

2. src/components/PageContainer.module.css

/* Variables CSS para el tema */
:root {
  --background-color: #f7f8fa; /* Gris muy claro para el fondo de la página */
  --surface-color: #ffffff; /* Blanco para componentes/tarjetas */
  --text-primary: #212121; /* Gris oscuro casi negro */
  --text-secondary: #6b7280; /* Gris medio para texto secundario */
  --primary-blue: #1876f2; /* Azul vibrante */
  --active-bg-light: #ebf3ff; /* Azul muy claro para activo/hover */
  --border-light: #e5e7eb; /* Gris claro para bordes */
  --shadow-light: rgba(0, 0, 0, 0.08); /* Sombra sutil */
}

/* Tema oscuro (ejemplo, no completamente implementado en la imagen) */
.themeDark {
  --background-color: #1a1a1a;
  --surface-color: #2c2c2c;
  --text-primary: #f0f0f0;
  --text-secondary: #a0a0a0;
  --border-light: #444;
  /* Otros colores oscuros */
}

.pageContainer {
  display: flex;
  min-height: 100vh; /* Asegura que ocupe al menos la altura de la ventana */
  background-color: var(--background-color);
  font-family: 'Inter', 'Roboto', sans-serif; /* Familia de fuentes */
  color: var(--text-primary);
}

.mainContent {
  flex-grow: 1; /* Ocupa el espacio restante */
  display: flex;
  flex-direction: column;
  position: relative; /* Para el botón flotante */
  background-color: var(--background-color); /* Fondo del área de contenido */
  overflow-y: auto; /* Permite el scroll vertical en el contenido principal */
}

.contentWrapper {
  padding: 32px; /* Padding global para el contenido principal */
  flex-grow: 1;
}

.helpButton {
  position: fixed;
  bottom: 24px;
  right: 24px;
  background-color: var(--primary-blue);
  color: #ffffff;
  padding: 12px 20px;
  border: none;
  border-radius: 6px;
  box-shadow: 0px 4px 12px var(--shadow-light); /* Sombra para elevarlo */
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 500;
  z-index: 1000; /* Asegura que esté por encima de otros elementos */
}

.helpButton:hover {
  background-color: #146bdc; /* Azul un poco más oscuro al hacer hover */
}

/* Media Queries para responsividad */
@media (max-width: 768px) {
  .pageContainer {
    flex-direction: column; /* La barra lateral se apilará en móviles */
  }

  .mainContent {
    width: 100%; /* El contenido principal ocupa todo el ancho */
  }

  .contentWrapper {
    padding: 20px; /* Reducir padding en móviles */
  }

  .helpButton {
    bottom: 16px;
    right: 16px;
    padding: 10px 16px;
  }
}

3. src/components/Sidebar/Sidebar.tsx

import React from 'react';
import styles from './Sidebar.module.css';

interface SidebarProps {
  activeLink: string;
}

const Sidebar: React.FC<SidebarProps> = ({ activeLink }) => {
  const navItems = [
    { id: 'Inicio', label: 'Inicio', icon: <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg> },
    { id: 'Biblioteca', label: 'Biblioteca', icon: <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-video"><path d="M23 7l-7 5V2l7 5z"></path><path d="M22 17H7a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h15v16z"></path></svg> },
    { id: 'Destinos', label: 'Destinos', icon: <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-share-2"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg> },
    { id: 'Miembros', label: 'Miembros', icon: <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg> },
  ];

  const secondaryItems = [
    { id: 'Referidos', label: 'Referidos', icon: <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-gift"><polyline points="20 12 20 22 4 22 4 12"></polyline><rect x="2" y="7" width="20" height="5"></rect><line x1="12" y1="22" x2="12" y2="7"></line><path d="M12 7H7.5a2.5 2 0 0 1 0-5C11 2 12 7 12 7z"></path><path d="M12 7h4.5a2.5 2 0 0 0 0-5C13 2 12 7 12 7z"></path></svg> },
    { id: 'Configuracion', label: 'Configuración del equipo', icon: <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0-.33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2v-2a2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg> },
    { id: 'Estado', label: 'Estado del sistema', icon: <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-bar-chart-2"><line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line></svg> },
  ];

  return (
    <aside className={styles.sidebar}>
      <div className={styles.logoSection}>
        {/* Usar una imagen real o un SVG para el logo de StreamYard */}
        <img src="https://static.streamyard.com/images/logo-icon.svg" alt="StreamYard Logo" className={styles.logoIcon} />
        <span className={styles.logoText}>StreamYard</span>
      </div>

      <nav className={styles.navMenu}>
        <ul className={styles.navList}>
          {navItems.map((item) => (
            <li key={item.id} className={`${styles.navItem} ${activeLink === item.id ? styles.activeLink : ''}`}>
              <a href="#" onClick={(e) => e.preventDefault()} className={styles.navLink}>
                {item.icon}
                <span>{item.label}</span>
              </a>
            </li>
          ))}
        </ul>

        <div className={styles.secondaryNavGroup}>
          <ul className={styles.navList}>
            {secondaryItems.map((item) => (
              <li key={item.id} className={`${styles.navItem} ${activeLink === item.id ? styles.activeLink : ''}`}>
                <a href="#" onClick={(e) => e.preventDefault()} className={styles.navLink}>
                  {item.icon}
                  <span>{item.label}</span>
                </a>
              </li>
            ))}
          </ul>
        </div>
      </nav>

      <div className={styles.storageInfo}>
        <h4 className={styles.storageTitle}>Almacenamiento <span className={styles.infoIcon}>?</span></h4>
        <div className={styles.progressBarContainer}>
          <div className={styles.progressBarFill} style={{ width: '0%' }}></div> {/* Simula el progreso */}
        </div>
        <p className={styles.storageUsage}>0 de 5 horas</p>
        <a href="#" className={styles.addMoreLink}>Agregar más</a>
      </div>
    </aside>
  );
};

export default Sidebar;

4. src/components/Sidebar/Sidebar.module.css

.sidebar {
  width: 260px; /* Ancho fijo */
  min-width: 260px;
  height: 100vh; /* Ocupa toda la altura */
  background-color: var(--surface-color);
  padding: 20px 0; /* Padding vertical */
  box-shadow: 1px 0 5px rgba(0, 0, 0, 0.05); /* Sombra sutil a la derecha */
  display: flex;
  flex-direction: column;
  overflow-y: auto; /* Para permitir scroll si el contenido es largo */
}

.logoSection {
  display: flex;
  align-items: center;
  padding: 0 20px 20px; /* Padding bottom para separación */
  border-bottom: 1px solid var(--border-light); /* Separador sutil */
  margin-bottom: 20px;
}

.logoIcon {
  width: 32px;
  height: 32px;
  margin-right: 10px;
}

.logoText {
  font-size: 20px;
  font-weight: 600;
  color: var(--text-primary);
}

.navMenu {
  flex-grow: 1; /* Permite que el menú crezca y ocupe el espacio */
}

.navList {
  list-style: none;
  padding: 0;
  margin: 0;
}

.navItem {
  margin-bottom: 4px; /* Pequeña separación entre items */
}

.navLink {
  display: flex;
  align-items: center;
  padding: 10px 20px;
  color: var(--text-secondary); /* Color por defecto */
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  border-left: 4px solid transparent; /* Borde transparente por defecto */
  transition: all 0.2s ease-in-out;
  gap: 10px;
}

.navLink svg {
  color: var(--text-secondary); /* Color de icono por defecto */
}

.navLink:hover {
  background-color: var(--active-bg-light); /* Fondo azul claro al hover */
  color: var(--primary-blue); /* Texto azul al hover */
}

.navLink:hover svg {
  color: var(--primary-blue); /* Icono azul al hover */
}

.activeLink .navLink {
  background-color: var(--active-bg-light); /* Fondo azul claro para activo */
  color: var(--primary-blue); /* Texto azul para activo */
  border-left-color: var(--primary-blue); /* Borde azul para activo */
}

.activeLink .navLink svg {
  color: var(--primary-blue); /* Icono azul para activo */
}

.secondaryNavGroup {
  margin-top: 30px; /* Separación para el segundo grupo */
  padding-top: 20px;
  border-top: 1px solid var(--border-light);
}

.storageInfo {
  padding: 20px;
  margin-top: auto; /* Empuja el almacenamiento al final */
  border-top: 1px solid var(--border-light); /* Separador */
}

.storageTitle {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 8px;
  display: flex;
  align-items: center;
  gap: 4px;
}

.infoIcon {
  background-color: var(--border-light);
  border-radius: 50%;
  width: 16px;
  height: 16px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 10px;
  color: var(--text-secondary);
  font-weight: 500;
}

.progressBarContainer {
  width: 100%;
  height: 6px;
  background-color: var(--border-light);
  border-radius: 3px;
  margin-bottom: 8px;
}

.progressBarFill {
  height: 100%;
  background-color: var(--primary-blue);
  border-radius: 3px;
  width: 0%; /* Se controlará con JS para el progreso real */
}

.storageUsage {
  font-size: 12px;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

.addMoreLink {
  font-size: 12px;
  color: var(--primary-blue);
  text-decoration: none;
  font-weight: 500;
}

.addMoreLink:hover {
  text-decoration: underline;
}

/* Media Queries para responsividad */
@media (max-width: 768px) {
  .sidebar {
    width: 100%; /* Ocupa todo el ancho en móviles */
    height: auto; /* Altura automática */
    padding: 10px 0;
    box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05); /* Sombra inferior */
    position: relative;
    z-index: 100; /* Asegura que esté encima de otros elementos */
  }

  .navMenu {
    display: none; /* Ocultar el menú de navegación principal en móvil por defecto */
    /* En un caso real, aquí iría la lógica para un menú hamburguesa */
  }

  .logoSection {
    padding: 10px 20px;
    border-bottom: none;
    margin-bottom: 0;
    justify-content: center; /* Centrar logo en móvil */
  }
  
  .storageInfo {
    display: none; /* Ocultar info de almacenamiento en móvil */
  }
}

5. src/components/Header/Header.tsx

import React from 'react';
import styles from './Header.module.css';

interface HeaderProps {
  currentTheme: 'light' | 'dark';
  onToggleTheme: () => void;
}

const Header: React.FC<HeaderProps> = ({ currentTheme, onToggleTheme }) => {
  return (
    <header className={styles.header}>
      <div className={styles.headerActions}>
        <button className={styles.planButton}>Mejora tu plan</button>

        <div className={styles.themeToggleGroup}>
          <button
            className={`${styles.themeToggleButton} ${currentTheme === 'light' ? styles.active : ''}`}
            onClick={onToggleTheme}
            aria-label="Toggle light theme"
          >
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-sun">
              <circle cx="12" cy="12" r="5"></circle>
              <line x1="12" y1="1" x2="12" y2="3"></line>
              <line x1="12" y1="21" x2="12" y2="23"></line>
              <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
              <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
              <line x1="1" y1="12" x2="3" y2="12"></line>
              <line x1="21" y1="12" x2="23" y2="12"></line>
              <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
              <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
            </svg>
          </button>
          <button
            className={`${styles.themeToggleButton} ${currentTheme === 'dark' ? styles.active : ''}`}
            onClick={onToggleTheme}
            aria-label="Toggle dark theme"
          >
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-moon">
              <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
            </svg>
          </button>
        </div>

        <button className={styles.notificationButton}>
          <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-bell">
            <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path>
            <path d="M13.73 21a2 2 0 0 1-3.46 0"></path>
          </svg>
          <span className={styles.notificationDot}></span> {/* Punto de notificación */}
        </button>

        <div className={styles.userMenu}>
          <span>Mi cuenta</span>
          <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-chevron-down">
            <polyline points="6 9 12 15 18 9"></polyline>
          </svg>
        </div>
      </div>
    </header>
  );
};

export default Header;

6. src/components/Header/Header.module.css

.header {
  height: 64px; /* Altura fija del header */
  background-color: var(--surface-color);
  border-bottom: 1px solid var(--border-light); /* Borde inferior sutil */
  display: flex;
  justify-content: flex-end; /* Alinea el contenido a la derecha */
  align-items: center;
  padding: 0 32px; /* Padding horizontal */
  flex-shrink: 0; /* Previene que el header se encoja */
}

.headerActions {
  display: flex;
  align-items: center;
  gap: 20px; /* Espacio entre los grupos de elementos */
}

.planButton {
  background-color: transparent;
  color: var(--primary-blue);
  border: 1px solid var(--primary-blue);
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.2s ease-in-out;
}

.planButton:hover {
  background-color: var(--primary-blue);
  color: #ffffff;
}

.themeToggleGroup {
  display: flex;
  background-color: var(--background-color); /* Fondo gris claro */
  border-radius: 9999px; /* Forma de píldora */
  padding: 4px;
  gap: 4px;
}

.themeToggleButton {
  background-color: transparent;
  border: none;
  border-radius: 9999px; /* Forma redonda */
  padding: 8px;
  cursor: pointer;
  color: var(--text-secondary); /* Icono gris por defecto */
  transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
  display: flex;
  align-items: center;
  justify-content: center;
}

.themeToggleButton.active {
  background-color: var(--surface-color); /* Fondo blanco para el activo */
  color: var(--text-primary); /* Icono oscuro para el activo */
  box-shadow: 0px 1px 3px var(--shadow-light); /* Sombra sutil para el activo */
}

.themeToggleButton:hover:not(.active) {
  background-color: var(--border-light); /* Fondo ligeramente más oscuro al hover */
}

.notificationButton {
  background-color: transparent;
  border: none;
  cursor: pointer;
  color: var(--text-secondary);
  position: relative;
  padding: 0; /* Eliminar padding para que el icono se centre mejor */
  display: flex; /* Asegura que el SVG se centre */
  align-items: center;
  justify-content: center;
  height: 36px; /* Ajusta la altura para que coincida con otros elementos interactivos */
  width: 36px;
  border-radius: 50%;
  transition: background-color 0.2s ease-in-out;
}

.notificationButton:hover {
  background-color: var(--border-light);
}

.notificationDot {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 8px;
  height: 8px;
  background-color: var(--primary-blue); /* Color del punto de notificación */
  border-radius: 50%;
  border: 1px solid var(--surface-color); /* Pequeño borde blanco */
}

.userMenu {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 14px;
  color: var(--text-primary);
  font-weight: 500;
}

.userMenu svg {
  color: var(--text-secondary);
}

/* Media Queries para responsividad */
@media (max-width: 768px) {
  .header {
    padding: 0 16px;
    height: 56px;
    justify-content: space-between; /* Alinear elementos al principio y al final */
  }

  .headerActions {
    gap: 10px;
  }

  .planButton {
    padding: 6px 12px;
    font-size: 13px;
  }

  .themeToggleGroup {
    display: none; /* Ocultar el selector de tema en móviles para simplificar */
  }

  .notificationButton, .userMenu {
    /* Ajustes menores si es necesario */
  }
}

7. src/components/HeroSection/HeroSection.tsx

import React from 'react';
import styles from './HeroSection.module.css';

interface CardProps {
  icon: React.ReactNode;
  title: string;
}

const CreateCard: React.FC<CardProps> = ({ icon, title }) => {
  return (
    <div className={styles.createCard}>
      <div className={styles.cardIcon}>{icon}</div>
      <p className={styles.cardTitle}>{title}</p>
    </div>
  );
};

const HeroSection: React.FC = () => {
  return (
    <section className={styles.heroSection}>
      <h2 className={styles.sectionTitle}>Crear</h2>
      <div className={styles.cardGrid}>
        <CreateCard
          icon={<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-video"><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg>}
          title="Transmisión en vivo"
        />
        <CreateCard
          icon={<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-radio"><circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path></svg>}
          title="Grabación"
        />
        <CreateCard
          icon={<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-monitor"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>}
          title="Seminario web On-Air"
        />
      </div>
    </section>
  );
};

export default HeroSection;

8. src/components/HeroSection/HeroSection.module.css

.heroSection {
  margin-bottom: 40px; /* Separación con la siguiente sección */
}

.sectionTitle {
  font-size: 22px;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 24px; /* Espacio debajo del título */
}

.cardGrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); /* Adaptativo */
  gap: 20px; /* Espacio entre las tarjetas */
}

.createCard {
  background-color: var(--surface-color);
  border: 1px solid var(--border-light);
  border-radius: 8px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  align-items: flex-start; /* Alinea los elementos a la izquierda */
  cursor: pointer;
  transition: all 0.2s ease-in-out;
  min-height: 120px; /* Altura mínima para la tarjeta */
  box-shadow: 0px 1px 3px var(--shadow-light); /* Sombra sutil */
}

.createCard:hover {
  border-color: var(--primary-blue); /* Borde azul al hover */
  box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.12); /* Sombra más pronunciada */
}

.cardIcon {
  color: var(--primary-blue); /* Color del icono */
  margin-bottom: 12px;
  width: 32px;
  height: 32px;
}

.cardTitle {
  font-size: 16px;
  font-weight: 500;
  color: var(--text-primary);
  margin: 0;
  line-height: 1.5;
}

/* Media Queries para responsividad */
@media (max-width: 768px) {
  .heroSection {
    margin-bottom: 30px;
  }

  .sectionTitle {
    font-size: 20px;
    margin-bottom: 16px;
  }

  .cardGrid {
    grid-template-columns: 1fr; /* Una columna en móviles */
    gap: 16px;
  }

  .createCard {
    padding: 16px;
    min-height: 100px;
  }

  .cardIcon {
    width: 28px;
    height: 28px;
    margin-bottom: 8px;
  }

  .cardTitle {
    font-size: 15px;
  }
}

9. src/components/TransmissionsTable/TransmissionsTable.tsx

import React, { useState } from 'react';
import styles from './TransmissionsTable.module.css';

interface Transmission {
  id: string;
  title: string;
  platformIcon: React.ReactNode;
  created: string;
  scheduled: string;
}

const dummyTransmissions: Transmission[] = [
  {
    id: '1',
    title: 'Transmision',
    platformIcon: <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="#FF0000" stroke="none" className="youtube-icon"><path d="M2.5 17.5V6.5L12 12.5L2.5 17.5Z"></path><path d="M22.5 6.5C22.5 6.5 22.5 4.5 20 4.5C17.5 4.5 12.5 4.5 12.5 4.5L12.5 4.5C12.5 4.5 7.5 4.5 5 4.5C2.5 4.5 2.5 6.5 2.5 6.5V17.5C2.5 17.5 2.5 19.5 5 19.5C7.5 19.5 12.5 19.5 12.5 19.5L12.5 19.5C12.5 19.5 17.5 19.5 20 19.5C22.5 19.5 22.5 17.5 22.5 17.5V6.5Z" stroke="#FF0000" strokeWidth="1.5" strokeLinejoin="round"></path></svg>,
    created: '01:56',
    scheduled: '-',
  },
  // Más datos de transmisiones pueden ir aquí
];

const TransmissionsTable: React.FC = () => {
  const [activeTab, setActiveTab] = useState<'upcoming' | 'past'>('upcoming');

  // Datos simulados para las pestañas
  const upcomingTransmissions: Transmission[] = dummyTransmissions; // De momento, los mismos datos
  const pastTransmissions: Transmission[] = []; // Sin datos pasados por ahora

  const displayedTransmissions = activeTab === 'upcoming' ? upcomingTransmissions : pastTransmissions;

  return (
    <section className={styles.transmissionsSection}>
      <h2 className={styles.sectionTitle}>Transmisiones y grabaciones</h2>

      <div className={styles.tabContainer}>
        <button
          className={`${styles.tabButton} ${activeTab === 'upcoming' ? styles.activeTab : ''}`}
          onClick={() => setActiveTab('upcoming')}
        >
          Próximamente
        </button>
        <button
          className={`${styles.tabButton} ${activeTab === 'past' ? styles.activeTab : ''}`}
          onClick={() => setActiveTab('past')}
        >
          Anteriores
        </button>
      </div>

      <div className={styles.tableWrapper}>
        <table className={styles.transmissionsTable}>
          <thead>
            <tr>
              <th className={styles.tableHeader}>Título</th>
              <th className={styles.tableHeader}>Creada</th>
              <th className={styles.tableHeader}>Programado</th>
              <th className={styles.tableHeader}>Acciones</th>
            </tr>
          </thead>
          <tbody>
            {displayedTransmissions.length > 0 ? (
              displayedTransmissions.map((transmission) => (
                <tr key={transmission.id} className={styles.tableRow}>
                  <td className={styles.tableCell}>
                    <div className={styles.titleCellContent}>
                      <span className={styles.platformAvatar}>
                        {/* Avatar simulado con "a" o imagen de usuario */}
                        <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="#E5E7EB" stroke="none" className="avatar"><circle cx="12" cy="12" r="10"></circle><text x="50%" y="50%" dominantBaseline="middle" textAnchor="middle" fill="#6B7280" fontSize="12px">a</text></svg>
                      </span>
                      <div className={styles.titleTextContent}>
                        <span className={styles.transmissionTitle}>{transmission.title}</span>
                        <span className={styles.transmissionPlatformIcon}>
                          {transmission.platformIcon}
                        </span>
                      </div>
                    </div>
                  </td>
                  <td className={styles.tableCell}>{transmission.created}</td>
                  <td className={styles.tableCell}>{transmission.scheduled}</td>
                  <td className={`${styles.tableCell} ${styles.actionsCell}`}>
                    <button className={styles.enterStudioButton}>Entrar al estudio</button>
                    <button className={styles.moreOptionsButton}>
                      <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="feather feather-more-horizontal">
                        <circle cx="12" cy="12" r="1"></circle>
                        <circle cx="19" cy="12" r="1"></circle>
                        <circle cx="5" cy="12" r="1"></circle>
                      </svg>
                    </button>
                  </td>
                </tr>
              ))
            ) : (
              <tr className={styles.tableRow}>
                <td colSpan={4} className={styles.noDataCell}>No hay transmisiones en esta categoría.</td>
              </tr>
            )}
          </tbody>
        </table>
      </div>
    </section>
  );
};

export default TransmissionsTable;

10. src/components/TransmissionsTable/TransmissionsTable.module.css

.transmissionsSection {
  margin-bottom: 40px; /* Separación con el pie de página o final */
}

.sectionTitle {
  font-size: 22px;
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: 24px;
}

.tabContainer {
  display: flex;
  border-bottom: 1px solid var(--border-light); /* Borde debajo de las pestañas */
  margin-bottom: 20px; /* Espacio debajo del contenedor de pestañas */
}

.tabButton {
  background-color: transparent;
  border: none;
  padding: 10px 0;
  margin-right: 20px; /* Espacio entre pestañas */
  font-size: 16px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  position: relative;
  transition: color 0.2s ease-in-out;
}

.tabButton:hover {
  color: var(--primary-blue);
}

.tabButton.activeTab {
  color: var(--primary-blue);
  border-bottom: 3px solid var(--primary-blue); /* Borde azul para la pestaña activa */
  padding-bottom: 7px; /* Ajustar padding para compensar el borde */
}

.tableWrapper {
  overflow-x: auto; /* Permite el scroll horizontal en tablas grandes */
  background-color: var(--surface-color);
  border-radius: 8px;
  box-shadow: 0px 1px 3px var(--shadow-light);
  border: 1px solid var(--border-light);
}

.transmissionsTable {
  width: 100%;
  border-collapse: collapse; /* Elimina los espacios entre bordes de celda */
}

.tableHeader {
  text-align: left;
  padding: 12px 15px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  text-transform: uppercase;
  border-bottom: 1px solid var(--border-light);
}

.tableRow {
  transition: background-color 0.2s ease-in-out;
}

.tableRow:hover {
  background-color: var(--active-bg-light); /* Fondo suave al hover en la fila */
}

.tableCell {
  padding: 12px 15px;
  font-size: 14px;
  color: var(--text-primary);
  border-bottom: 1px solid var(--border-light);
}

.tableRow:last-child .tableCell {
  border-bottom: none; /* Elimina el borde inferior de la última fila */
}

.titleCellContent {
  display: flex;
  align-items: center;
  gap: 12px;
}

.platformAvatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--border-light); /* Fondo para el avatar genérico */
  flex-shrink: 0; /* Evita que el avatar se encoja */
}

.transmissionTitle {
  font-weight: 500;
  color: var(--text-primary);
}

.transmissionPlatformIcon {
  margin-left: 8px; /* Espacio entre el título y el icono de la plataforma */
  display: inline-flex; /* Para alinear el SVG correctamente */
  vertical-align: middle;
}

.transmissionPlatformIcon svg {
  width: 16px;
  height: 16px;
}

.actionsCell {
  white-space: nowrap; /* Evita que los botones se rompan en varias líneas */
  text-align: right;
}

.enterStudioButton {
  background-color: transparent;
  color: var(--primary-blue);
  border: 1px solid var(--primary-blue);
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.2s ease-in-out;
  margin-right: 8px;
}

.enterStudioButton:hover {
  background-color: var(--primary-blue);
  color: #ffffff;
}

.moreOptionsButton {
  background-color: transparent;
  border: none;
  padding: 8px;
  border-radius: 6px;
  cursor: pointer;
  color: var(--text-secondary);
  transition: background-color 0.2s ease-in-out;
}

.moreOptionsButton:hover {
  background-color: var(--border-light);
}

.noDataCell {
  text-align: center;
  padding: 40px;
  color: var(--text-secondary);
  font-style: italic;
}

/* Media Queries para responsividad */
@media (max-width: 768px) {
  .sectionTitle {
    font-size: 20px;
    margin-bottom: 16px;
  }

  .tabButton {
    font-size: 15px;
    margin-right: 15px;
    padding-bottom: 5px;
  }

  .tabButton.activeTab {
    padding-bottom: 2px; /* Ajustar por el borde */
  }

  .tableWrapper {
    /* El overflow-x: auto ya maneja el responsive */
  }

  .tableHeader, .tableCell {
    padding: 10px 12px;
    font-size: 13px;
  }

  .titleCellContent {
    gap: 8px;
  }

  .platformAvatar {
    width: 28px;
    height: 28px;
  }
  
  .platformAvatar text {
    font-size: 10px;
  }

  .transmissionTitle {
    font-size: 13px;
  }

  .enterStudioButton {
    padding: 6px 12px;
    font-size: 13px;
    margin-right: 4px;
  }

  .moreOptionsButton {
    padding: 6px;
  }
}