import React, { useState, useEffect, useRef } from 'react'; import { createRoot } from 'react-dom/client'; import { GoogleGenAI, Modality, LiveServerMessage, } from '@google/genai'; import { MessageSquare, Image as ImageIcon, Mic, MicOff, Send, Sparkles, Search, History, Layers, Zap, Loader2, ExternalLink, ChevronRight, Home as HomeIcon, ArrowRight, Heart, Brain, Coffee, Stars, Quote, CheckCircle2, ShieldCheck, MousePointer2, Clock, Sparkle, FileText, User, MapPin, Stethoscope, Info, BookOpen, Phone, LayoutDashboard, Mail, Globe, Grid, BarChart3, Calendar, CreditCard, Settings, Shield, Activity, Users, AlertCircle, TrendingUp, FileSearch, Plus, MoreVertical, Bell, Truck, FileUp, Link as LinkIcon, Filter, Check, X, Clock3, BarChart, Trophy, Calculator, Target, LineChart, Eye, PieChart, Map as MapIcon, Briefcase, ZapOff, UserCheck, DollarSign, Download, Smartphone, SendHorizontal, Inbox, ShieldAlert, FileBadge, Stamp, ClipboardList, Headphones, Scale, Smile, Frown, Utensils, Ambulance, Menu, Moon, Sun, LogOut, ChevronDown, Library, GraduationCap, PlayCircle, Hash, Share2, Command, Instagram, Twitter, Linkedin, Navigation, Compass, ArrowLeft, StickyNote, Server, Cloud, FileCode } from 'lucide-react'; // --- Utility Functions for Audio (Live API) --- function encode(bytes: Uint8Array) { let binary = ''; const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } function decode(base64: string) { const binaryString = atob(base64); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes; } async function decodeAudioData( data: Uint8Array, ctx: AudioContext, sampleRate: number, numChannels: number, ): Promise { const dataInt16 = new Int16Array(data.buffer); const frameCount = dataInt16.length / numChannels; const buffer = ctx.createBuffer(numChannels, frameCount, sampleRate); for (let channel = 0; channel < numChannels; channel++) { const channelData = buffer.getChannelData(channel); for (let i = 0; i < frameCount; i++) { channelData[i] = dataInt16[i * numChannels + channel] / 32768.0; } } return buffer; } const App: React.FC = () => { type Tabs = 'home' | 'chat' | 'image' | 'live' | 'admission' | 'methodology' | 'contact' | 'status' | 'solutions' | 'settings' | 'dashboard' | 'resources' | 'faculty' | 'fees-billing' | 'academic-records' | 'attendance' | 'schedule' | 'incident-management' | 'progress-tracking' | 'communications' | 'map' | 'lesson'; const [activeTab, setActiveTab] = useState('home'); const [messages, setMessages] = useState<{ role: 'user' | 'model'; text: string; sources?: any[] }[]>([]); const [chatInput, setChatInput] = useState(''); const [isSearching, setIsSearching] = useState(false); const [isTyping, setIsTyping] = useState(false); const [imagePrompt, setImagePrompt] = useState(''); const [generatedImage, setGeneratedImage] = useState(null); const [isGeneratingImage, setIsGeneratingImage] = useState(false); const [isLiveActive, setIsLiveActive] = useState(false); const [isNotifOpen, setIsNotifOpen] = useState(false); // Dashboard states const [currentTime, setCurrentTime] = useState(new Date()); // Resources states const [resourceSearch, setResourceSearch] = useState(''); // Faculty states const [selectedFaculty, setSelectedFaculty] = useState(null); // Lesson states const [studentNotes, setStudentNotes] = useState(''); // Admission Form State const [admissionStep, setAdmissionStep] = useState(1); const [statusId, setStatusId] = useState(''); const [isCheckingStatus, setIsCheckingStatus] = useState(false); const [statusResult, setStatusResult] = useState(null); // Incident Form State const [incidentType, setIncidentType] = useState('Medico'); const [incidentDesc, setIncidentDesc] = useState(''); const [isReporting, setIsReporting] = useState(false); const audioContextInputRef = useRef(null); const audioContextOutputRef = useRef(null); const liveSessionRef = useRef(null); const nextStartTimeRef = useRef(0); useEffect(() => { const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => clearInterval(timer); }, []); const handleSendMessage = async (e?: React.FormEvent) => { if (e) e.preventDefault(); if (!chatInput.trim() || isTyping) return; const userMsg = chatInput; setMessages(prev => [...prev, { role: 'user', text: userMsg }]); setChatInput(''); setIsTyping(true); try { const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); const config: any = {}; if (isSearching) config.tools = [{ googleSearch: {} }]; const response = await ai.models.generateContent({ model: 'gemini-3-flash-preview', contents: userMsg, config: config }); const text = response.text || "No response generado."; const sources = response.candidates?.[0]?.groundingMetadata?.groundingChunks || []; setMessages(prev => [...prev, { role: 'model', text, sources }]); } catch (err) { setMessages(prev => [...prev, { role: 'model', text: "Error de conexión con la IA." }]); } finally { setIsTyping(false); } }; const handleGenerateImage = async () => { if (!imagePrompt.trim() || isGeneratingImage) return; setIsGeneratingImage(true); setGeneratedImage(null); try { const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); const response = await ai.models.generateContent({ model: 'gemini-2.5-flash-image', contents: { parts: [{ text: imagePrompt }] }, }); for (const part of response.candidates[0].content.parts) { if (part.inlineData) { setGeneratedImage(`data:image/png;base64,${part.inlineData.data}`); break; } } } catch (err) { alert("Error al generar imagen."); } finally { setIsGeneratingImage(false); } }; const startLiveSession = async () => { try { const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); audioContextInputRef.current = new AudioContext({ sampleRate: 16000 }); audioContextOutputRef.current = new AudioContext({ sampleRate: 24000 }); const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const sessionPromise = ai.live.connect({ model: 'gemini-2.5-flash-native-audio-preview-12-2025', callbacks: { onopen: () => { setIsLiveActive(true); const source = audioContextInputRef.current!.createMediaStreamSource(stream); const scriptProcessor = audioContextInputRef.current!.createScriptProcessor(4096, 1, 1); scriptProcessor.onaudioprocess = (e) => { const inputData = e.inputBuffer.getChannelData(0); const int16 = new Int16Array(inputData.length); for (let i = 0; i < inputData.length; i++) int16[i] = inputData[i] * 32768; const pcmBlob = { data: encode(new Uint8Array(int16.buffer)), mimeType: 'audio/pcm;rate=16000' }; sessionPromise.then(s => s.sendRealtimeInput({ media: pcmBlob })); }; source.connect(scriptProcessor); scriptProcessor.connect(audioContextInputRef.current!.destination); }, onmessage: async (m: LiveServerMessage) => { const base64Audio = m.serverContent?.modelTurn?.parts[0]?.inlineData?.data; if (base64Audio && audioContextOutputRef.current) { const ctx = audioContextOutputRef.current; nextStartTimeRef.current = Math.max(nextStartTimeRef.current, ctx.currentTime); const audioBuffer = await decodeAudioData(decode(base64Audio), ctx, 24000, 1); const source = ctx.createBufferSource(); source.buffer = audioBuffer; source.connect(ctx.destination); source.start(nextStartTimeRef.current); nextStartTimeRef.current += audioBuffer.duration; } }, onclose: () => setIsLiveActive(false) }, config: { responseModalities: [Modality.AUDIO], inputAudioTranscription: {}, outputAudioTranscription: {} } }); liveSessionRef.current = await sessionPromise; } catch (err) { alert("Acceso al micrófono denegado."); } }; const stopLiveSession = () => { if (liveSessionRef.current) liveSessionRef.current.close(); setIsLiveActive(false); }; const checkStatus = (e?: React.FormEvent) => { if (e) e.preventDefault(); if(!statusId.trim()) return; setIsCheckingStatus(true); setStatusResult(null); setTimeout(() => { setIsCheckingStatus(false); if(statusId === 'ADM-2025') setStatusResult('accepted'); else if(statusId === 'ADM-PEND') setStatusResult('pending'); else setStatusResult('not_found'); }, 1500); }; const isDarkPage = ['chat', 'image', 'live'].includes(activeTab); const BackToHub = () => ( ); const resourcesData = [ { title: "Neurociencia I", cat: "Manual", icon: Brain }, { title: "Lab de Robótica", cat: "Guía", icon: Zap }, { title: "Sistemas Éticos", cat: "Ensayo", icon: ShieldCheck }, { title: "Matemática IA", cat: "Práctica", icon: Calculator }, { title: "Historia de la IA", cat: "Documento", icon: Library }, { title: "Algoritmos Genéticos", cat: "Manual", icon: Zap }, ]; const facultyData = [ { name: "Dr. Elara Vega", role: "Directora de IA", img: "https://i.pravatar.cc/150?u=elara", bio: "Experta en redes neuronales profundas con más de 15 años de investigación en Google Brain." }, { name: "Mtro. Julian Paz", role: "Investigador Neuro", img: "https://i.pravatar.cc/150?u=julian", bio: "Especialista en interfaces cerebro-computador y neuroplasticidad aplicada al aprendizaje." }, { name: "Dra. Sofia Sol", role: "Ética Aplicada", img: "https://i.pravatar.cc/150?u=sofia", bio: "Consultora internacional en ética algorítmica y gobernanza de sistemas autónomos." } ]; const filteredResources = resourcesData.filter(r => r.title.toLowerCase().includes(resourceSearch.toLowerCase()) || r.cat.toLowerCase().includes(resourceSearch.toLowerCase()) ); return (
{/* Desktop Sidebar Nav */} {/* Mobile Bottom Nav */} {/* Main Content Area */}
{/* Header */} {!isDarkPage && (
setActiveTab('home')} className="text-xl md:text-2xl font-black tracking-tighter text-[#001429] flex items-center gap-3 cursor-pointer">
FORMA
setActiveTab('settings')} className="w-10 h-10 rounded-full bg-slate-200 overflow-hidden cursor-pointer border border-slate-300 hover:ring-2 hover:ring-[#566262] transition-all"> Profile
)} {/* --- Home --- */} {activeTab === 'home' && (
Redefiniendo la Educación del Futuro

Aprender con
intención.

FORMA Academy no es solo un colegio, es un ecosistema de aprendizaje potenciado por IA donde el potencial humano no tiene límites.

)} {/* --- Dashboard --- */} {activeTab === 'dashboard' && (
Dashboard Estudiantil

Resumen Académico de Hoy.

Hora Local Campus

{currentTime.toLocaleTimeString()}

{currentTime.toLocaleDateString('es-ES', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}

{[ { label: "Promedio", val: "9.05", icon: Trophy, color: "text-[#566262] bg-slate-50" }, { label: "Asistencia", val: "98.2%", icon: Activity, color: "text-blue-600 bg-blue-50" }, { label: "Créditos", val: "142/240", icon: Library, color: "text-[#001429] bg-slate-100" }, { label: "Pendientes", val: "2", icon: AlertCircle, color: "text-orange-600 bg-orange-50" } ].map((kpi, i) => (

{kpi.label}

{kpi.val}

))}

Cronograma del Día

{[ { time: "08:00 AM", subject: "Cálculo Multivariado", room: "A-201", active: false }, { time: "10:30 AM", subject: "Neurociencia Cognitiva", room: "Lab B", active: true }, { time: "01:00 PM", subject: "Ética en IA", room: "Auditorio", active: false } ].map((item, i) => (

{item.time}

{item.subject}

{item.room}

{item.active &&
En Curso
}
))}

Siguiente Meta: Dominio en Cálculo.

Has completado el 85% del módulo. Termina la práctica de hoy para acreditar créditos extra.

)} {/* --- Settings & Deployment --- */} {activeTab === 'settings' && (

Ajustes & Configuración.

{/* Hostinger Deployment Card */}

Hostinger Cloud Link

Configura el despliegue automático de FORMA ACADEMY en tu alojamiento compartido de Hostinger.

1

Sube el archivo .htaccess incluido en el paquete a tu carpeta public_html.

2

Copia el contenido del index.html y crea el archivo en el directorio raíz de Hostinger.

3

Configura tu API_KEY en el Panel de Control de Hostinger si usas un bundler, o inyéctala directamente en el script.

Datos Personales

)} {/* --- Academic Records --- */} {activeTab === 'academic-records' && (
Control Escolar

Registro Académico.

{[ { name: "Cálculo Multivariado", credits: 12, grade: "9.2", status: "Acreditado" }, { name: "IA Ethics", credits: 8, grade: "9.5", status: "Acreditado" }, { name: "Neurociencia II", credits: 10, grade: "8.8", status: "En Curso" }, { name: "Robótica Avanzada", credits: 15, grade: "-", status: "Pendiente" } ].map((item, i) => ( ))}
Asignatura Créditos Nota Estatus
{item.name} {item.credits} {item.grade} {item.status}
)} {/* --- Faculty --- */} {activeTab === 'faculty' && (
Mentores FORMA

Liderazgo Cognitivo.

{facultyData.map((mentor, i) => (
setSelectedFaculty(mentor)} className="bg-white border border-slate-100 p-8 md:p-12 rounded-[50px] shadow-lg hover:shadow-2xl transition-all text-center group cursor-pointer border-b-4 border-transparent hover:border-[#566262]">
{mentor.name}

{mentor.name}

{mentor.role}

{mentor.bio}

))}
{/* Faculty Detail Modal */} {selectedFaculty && (
setSelectedFaculty(null)} />

{selectedFaculty.name}

{selectedFaculty.role}

{selectedFaculty.bio}

)}
)}
{/* --- Notification Panel --- */} {isNotifOpen && (
setIsNotifOpen(false)} />

Alertas FORMA

{[ { title: "Calificación Publicada", desc: "Neurociencia II: 9.5", time: "10m ago", icon: Trophy, color: "text-[#566262] bg-slate-50" }, { title: "Aviso de Pago", desc: "Colegiatura Mayo liquidada", time: "2h ago", icon: CreditCard, color: "text-blue-600 bg-blue-50" }, { title: "Evento Mañana", desc: "Conferencia Ética IA", time: "5h ago", icon: Calendar, color: "text-[#001429] bg-slate-100" } ].map((notif, i) => (

{notif.title}

{notif.time}

{notif.desc}

))}
)} {/* IA Overlays (Chat, etc.) */} {activeTab === 'chat' && (

Diálogo Estratégico

IA Núcleo • Gemini 3.1 Pro

{messages.length === 0 && (

¿Qué desafío cognitivo resolveremos hoy?

)} {messages.map((m, i) => (

{m.text}

{m.sources && m.sources.length > 0 && (

Referencias de búsqueda

{m.sources.map((src, si) => ( {src.web?.title || 'Fuente Externa'} ))}
)}
))} {isTyping && (
Analizando bases de datos...
)}
setChatInput(e.target.value)} className="flex-1 bg-white/5 border border-white/10 rounded-full py-5 md:py-6 px-8 md:px-12 text-white outline-none focus:border-[#566262] transition-all text-sm shadow-inner" placeholder="Escribe tu consulta académica..." />
)} {/* Live Voice Overlay */} {activeTab === 'live' && (
{isLiveActive ? : }
{isLiveActive && (
{[1,2,3].map(i => (
))}
)}

Canal Directo.

Inicia una conversación fluida con el tutor inteligente de FORMA para resolver dudas complejas al instante.

{!isLiveActive ? ( ) : ( )}
)}
); }; const rootElement = document.getElementById('root'); if (rootElement) { createRoot(rootElement).render(); }