diff --git a/frontend/public/audios/clase1_completa.m4a b/frontend/public/audios/clase1_completa.m4a new file mode 100644 index 0000000..4375f4f Binary files /dev/null and b/frontend/public/audios/clase1_completa.m4a differ diff --git a/frontend/public/audios/clase2_completa.m4a b/frontend/public/audios/clase2_completa.m4a new file mode 100644 index 0000000..315280b Binary files /dev/null and b/frontend/public/audios/clase2_completa.m4a differ diff --git a/frontend/public/audios/clase3_completa.m4a b/frontend/public/audios/clase3_completa.m4a new file mode 100644 index 0000000..2b3a026 Binary files /dev/null and b/frontend/public/audios/clase3_completa.m4a differ diff --git a/frontend/public/audios/clase4_completa.m4a b/frontend/public/audios/clase4_completa.m4a new file mode 100644 index 0000000..ac7ff5c Binary files /dev/null and b/frontend/public/audios/clase4_completa.m4a differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 18d6ab7..9d160f3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import { Modulos } from './pages/Modulos'; import { Modulo } from './pages/Modulo'; import { AdminPanel } from './pages/admin/AdminPanel'; import { RecursosPage } from './pages/Recursos'; +import { ClasesGrabadasPage } from './pages/ClasesGrabadas'; function ProtectedRoute({ children }: { children: React.ReactNode }) { const { isAuthenticated, isLoading } = useAuthStore(); @@ -77,6 +78,14 @@ function App() { } /> + + + + } + /> } /> diff --git a/frontend/src/components/announcements/SistemaAnuncios.tsx b/frontend/src/components/announcements/SistemaAnuncios.tsx new file mode 100644 index 0000000..f8938ad --- /dev/null +++ b/frontend/src/components/announcements/SistemaAnuncios.tsx @@ -0,0 +1,142 @@ +import { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { X, Volume2, BookOpen } from 'lucide-react'; +import { Button } from '../ui/Button'; +import { Link } from 'react-router-dom'; + +interface Anuncio { + id: string; + titulo: string; + mensaje: string; + tipo: 'info' | 'success' | 'warning'; + link?: string; + linkText?: string; + fechaExpiracion?: Date; +} + +const ANUNCIOS: Anuncio[] = [ + { + id: 'nuevas-clases-audio', + titulo: '🎉 ¡Nueva función disponible!', + mensaje: 'Ahora puedes escuchar las clases grabadas en audio. Accede a la sección "Clases Grabadas" para escuchar o descargar las 4 clases completas.', + tipo: 'success', + link: '/clases', + linkText: 'Ver Clases Grabadas' + } +]; + +export function SistemaAnuncios() { + const [anunciosVisibles, setAnunciosVisibles] = useState([]); + + useEffect(() => { + // Cargar anuncios no descartados desde localStorage + const anunciosDescartados = JSON.parse(localStorage.getItem('anunciosDescartados') || '[]'); + + const anunciosActivos = ANUNCIOS.filter(anuncio => { + // Si ya fue descartado, no mostrar + if (anunciosDescartados.includes(anuncio.id)) return false; + + // Si tiene fecha de expiración y ya pasó, no mostrar + if (anuncio.fechaExpiracion && new Date() > anuncio.fechaExpiracion) return false; + + return true; + }); + + setAnunciosVisibles(anunciosActivos); + }, []); + + const cerrarAnuncio = (id: string) => { + // Guardar en localStorage para no mostrar de nuevo + const anunciosDescartados = JSON.parse(localStorage.getItem('anunciosDescartados') || '[]'); + anunciosDescartados.push(id); + localStorage.setItem('anunciosDescartados', JSON.stringify(anunciosDescartados)); + + // Remover de la vista actual + setAnunciosVisibles(prev => prev.filter(a => a.id !== id)); + }; + + if (anunciosVisibles.length === 0) return null; + + return ( +
+ + {anunciosVisibles.map((anuncio) => ( + + {/* Botón cerrar */} + + +
+ {/* Icono */} +
+ {anuncio.id === 'nuevas-clases-audio' ? ( + + ) : ( + + )} +
+ + {/* Contenido */} +
+

+ {anuncio.titulo} +

+ +

+ {anuncio.mensaje} +

+ + {anuncio.link && ( + + + + )} +
+
+
+ ))} +
+
+ ); +} + +export default SistemaAnuncios; diff --git a/frontend/src/pages/ClasesGrabadas.tsx b/frontend/src/pages/ClasesGrabadas.tsx new file mode 100644 index 0000000..6913632 --- /dev/null +++ b/frontend/src/pages/ClasesGrabadas.tsx @@ -0,0 +1,224 @@ +import { useState } from 'react'; +import { Card } from '../components/ui/Card'; +import { Button } from '../components/ui/Button'; +import { + Headphones, + Download, + Play, + Clock, + BookOpen, + Calendar, + ArrowLeft +} from 'lucide-react'; +import { Link } from 'react-router-dom'; + +interface Clase { + id: number; + titulo: string; + modulo: string; + duracion: string; + fecha: string; + descripcion: string; + archivo: string; +} + +const CLASES: Clase[] = [ + { + id: 1, + titulo: 'Clase 1: Fundamentos de Economía', + modulo: 'Módulo 1', + duracion: '63 minutos', + fecha: '27 de Enero, 2025', + descripcion: 'Introducción a la economía, el problema económico fundamental, sistemas económicos, frontera de posibilidades de producción, agentes económicos y factores de producción.', + archivo: '/audios/clase1_completa.m4a' + }, + { + id: 2, + titulo: 'Clase 2: Oferta, Demanda y Equilibrio', + modulo: 'Módulo 2', + duracion: '103 minutos', + fecha: '30 de Enero, 2025', + descripcion: 'Ley de la demanda, ley de la oferta, equilibrio de mercado, elasticidad de la demanda y controles de precio. Análisis completo del funcionamiento de los mercados.', + archivo: '/audios/clase2_completa.m4a' + }, + { + id: 3, + titulo: 'Clase 3: Elasticidad y Teoría del Consumidor', + modulo: 'Módulo 3', + duracion: '52 minutos', + fecha: '3 de Febrero, 2025', + descripcion: 'Elasticidad precio, ingreso y cruzada. Utilidad total y marginal, restricción presupuestaria y maximización de la satisfacción del consumidor.', + archivo: '/audios/clase3_completa.m4a' + }, + { + id: 4, + titulo: 'Clase 4: Teoría del Productor', + modulo: 'Módulo 4', + duracion: '46 minutos', + fecha: '6 de Febrero, 2025', + descripcion: 'Función de producción, ley de rendimientos decrecientes, costos a corto y largo plazo, ingresos y maximización de beneficios en competencia perfecta.', + archivo: '/audios/clase4_completa.m4a' + } +]; + +export function ClasesGrabadasPage() { + const [claseReproduciendo, setClaseReproduciendo] = useState(null); + + return ( +
+
+ {/* Header */} +
+ + + Volver al Dashboard + + +
+
+ +
+
+

Clases Grabadas

+

+ Escucha las clases completas en audio o descárgalas +

+
+
+
+ + {/* Info Banner */} + +
+
+ +
+
+

+ ¿Cómo usar las clases grabadas? +

+
    +
  • + + Cada clase corresponde a un módulo del curso +
  • +
  • + + Puedes escuchar directamente en la web o descargar para escuchar offline +
  • +
  • + + Te recomendamos escuchar la clase antes de hacer los ejercicios del módulo +
  • +
  • + + Total: {CLASES.length} clases · Duración total aproximada: 4.5 horas +
  • +
+
+
+
+ + {/* Lista de Clases */} +
+ {CLASES.map((clase) => ( + +
+ {/* Icono/Info */} +
+
+ {clase.id} +
+ +
+ + {clase.modulo} + + +

+ {clase.titulo} +

+ +
+ + + {clase.duracion} + + + + {clase.fecha} + +
+
+
+ + {/* Descripción */} +
+

+ {clase.descripcion} +

+
+ + {/* Acciones */} +
+ + + + + +
+
+ + {/* Reproductor de Audio */} + {claseReproduciendo === clase.id && ( +
+ +

+ 💡 Tip: Puedes descargar el audio para escucharlo sin conexión +

+
+ )} +
+ ))} +
+ + {/* Footer */} +
+

+ ¿Ya escuchaste las clases? Pasa a los ejercicios interactivos para practicar. +

+ + + +
+
+
+ ); +} + +export default ClasesGrabadasPage; diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index e5621c8..b0567f2 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -8,7 +8,8 @@ import { ProgressBar } from '../components/progress/ProgressBar'; import { ScoreDisplay } from '../components/progress/ScoreDisplay'; import { BadgesSection } from '../components/progress/Badges'; import { Loader } from '../components/ui/Loader'; -import { BookOpen, User, LogOut, LayoutGrid, Award, Star, Target, CheckCircle, FileText } from 'lucide-react'; +import { BookOpen, User, LogOut, LayoutGrid, Award, Star, Target, CheckCircle, FileText, Headphones } from 'lucide-react'; +import { SistemaAnuncios } from '../components/announcements/SistemaAnuncios'; const MODULOS_CONFIG = [ { id: 'modulo1', numero: 1, titulo: 'Fundamentos de Economía', descripcion: 'Introducción a los conceptos básicos', totalEjercicios: 3 }, @@ -121,6 +122,9 @@ export function Dashboard() {

Continúa donde lo dejaste y desbloquea nuevos logros

+ {/* Sistema de Anuncios */} + + {/* Stats Cards */}
@@ -252,6 +256,12 @@ export function Dashboard() { Material PDF + + +