workspace/projects/gamilit/orchestration/analisis/SPEC-FASE-5C-FUSION-ANALYTICS.md
rckrdmrd 608e1e2a2e
Some checks are pending
CI Pipeline / changes (push) Waiting to run
CI Pipeline / core (push) Blocked by required conditions
CI Pipeline / trading-backend (push) Blocked by required conditions
CI Pipeline / trading-data-service (push) Blocked by required conditions
CI Pipeline / trading-frontend (push) Blocked by required conditions
CI Pipeline / erp-core (push) Blocked by required conditions
CI Pipeline / erp-mecanicas (push) Blocked by required conditions
CI Pipeline / gamilit-backend (push) Blocked by required conditions
CI Pipeline / gamilit-frontend (push) Blocked by required conditions
Multi-project update: gamilit, orchestration, trading-platform
Gamilit:
- Backend: Teacher services, assignments, gamification, exercise submissions
- Frontend: Admin/Teacher/Student portals, module 4-5 mechanics, monitoring
- Database: DDL functions, seeds for dev/prod, auth/gamification schemas
- Docs: Architecture, features, guides cleanup and reorganization

Core/Orchestration:
- New workspace directives index
- Documentation directive

Trading-platform:
- Database seeds and inventory updates
- Tech leader validation report

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 07:17:46 -06:00

830 lines
26 KiB
Markdown

# ESPECIFICACION TECNICA - FASE 5C
## Fusion de TeacherAnalytics en TeacherProgressPage (Alto Riesgo)
**Fecha:** 2025-12-15
**Prioridad:** MEDIA (ejecutar despues de FASE 5A y 5B)
**Riesgo:** ALTO
**Tiempo estimado de implementacion:** 3-4 horas
**Prerequisitos:** FASE 5A completada
---
## 1. OBJETIVO
Fusionar la funcionalidad de `TeacherAnalyticsPage` dentro de `TeacherProgressPage` como una nueva pestaña "Engagement", eliminando la redundancia entre ambas paginas y consolidando todas las metricas de rendimiento en un solo lugar.
### 1.1 Justificacion
```yaml
Solapamiento_detectado:
TeacherProgress:
- Progreso academico por estudiante
- Stats por modulo
- Identificacion de estudiantes rezagados
- Vista por clase
TeacherAnalytics:
- Puntuacion promedio (DUPLICADO)
- Tasa de completitud (DUPLICADO)
- Tasa de engagement (UNICO)
- DAU/WAU metrics (UNICO)
- Session duration (UNICO)
- Feature usage stats (UNICO)
- Comparacion con periodo anterior (UNICO)
Funcionalidad_unica_Analytics:
- Metricas de engagement (DAU, WAU)
- Duracion promedio de sesion
- Sesiones por usuario
- Uso de funcionalidades
- Comparacion con periodo anterior
```
---
## 2. ARCHIVOS INVOLUCRADOS
### 2.1 Archivos a MODIFICAR
| Archivo | Tipo de cambio | Complejidad |
|---------|---------------|-------------|
| `TeacherProgressPage.tsx` | Agregar sistema de tabs y tab Engagement | ALTA |
| `GamilitSidebar.tsx` | Remover item analytics | BAJA |
### 2.2 Archivos a REUTILIZAR (sin modificar)
| Archivo | Uso |
|---------|-----|
| `useAnalytics.ts` | Hook para obtener datos de engagement |
| `analyticsApi.ts` | API calls existentes |
### 2.3 Archivos a DEPRECAR (no eliminar)
| Archivo | Razon |
|---------|-------|
| `TeacherAnalyticsPage.tsx` | Wrapper - mantener para rutas legacy |
| `TeacherAnalytics.tsx` | Componente principal - referencia para migracion |
---
## 3. CAMBIOS DETALLADOS
### 3.1 TeacherProgressPage.tsx - REESTRUCTURACION COMPLETA
**Ruta:** `/home/isem/workspace/projects/gamilit/apps/frontend/src/apps/teacher/pages/TeacherProgressPage.tsx`
#### NUEVAS IMPORTACIONES A AGREGAR:
```typescript
// Agregar al inicio del archivo despues de las importaciones existentes
import { useState, useMemo, useEffect } from 'react';
import { useAnalytics } from '../hooks/useAnalytics';
import { Bar } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from 'chart.js';
import {
Activity,
Clock,
ArrowUp,
ArrowDown,
Users as UsersIcon,
Download,
} from 'lucide-react';
import { FormField } from '@shared/components/common/FormField';
// Registrar Chart.js
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
```
#### NUEVO ESTADO A AGREGAR:
```typescript
// Dentro del componente, despues de los estados existentes
const [activeTab, setActiveTab] = useState<'progress' | 'engagement'>('progress');
const [dateRange, setDateRange] = useState({
start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
end: new Date().toISOString().split('T')[0],
});
// Query para analytics (solo cuando tab engagement esta activo)
const analyticsQuery = useMemo(
() =>
activeTab === 'engagement' && selectedClassroomId !== 'all'
? {
classroom_id: selectedClassroomId,
start_date: dateRange.start,
end_date: dateRange.end,
}
: undefined,
[activeTab, selectedClassroomId, dateRange.start, dateRange.end],
);
const engagementQuery = useMemo(
() =>
activeTab === 'engagement' && selectedClassroomId !== 'all'
? {
classroom_id: selectedClassroomId,
start_date: dateRange.start,
end_date: dateRange.end,
period: 'daily' as const,
}
: undefined,
[activeTab, selectedClassroomId, dateRange.start, dateRange.end],
);
// Hook de analytics
const {
analytics: analyticsData,
engagement: engagementData,
loading: analyticsLoading,
error: analyticsError,
generateReport,
refresh: refreshAnalytics,
} = useAnalytics(analyticsQuery, engagementQuery);
```
#### FUNCIONES AUXILIARES A AGREGAR:
```typescript
/**
* Safely format a number to fixed decimals
*/
const safeFormat = (
value: number | undefined | null,
decimals: number = 1,
suffix: string = '',
fallback: string = 'N/A',
): string => {
if (typeof value !== 'number' || isNaN(value)) {
return fallback;
}
return `${value.toFixed(decimals)}${suffix}`;
};
/**
* Export analytics to CSV
*/
const exportToCSV = async () => {
if (selectedClassroomId === 'all') {
alert('Por favor selecciona una clase especifica para exportar');
return;
}
try {
const report = await generateReport({
type: 'custom',
title: `Engagement Report - ${selectedClassroomName}`,
classroom_id: selectedClassroomId,
start_date: dateRange.start,
end_date: dateRange.end,
format: 'csv',
include_charts: true,
include_recommendations: true,
});
if (report.status === 'completed' && report.file_url) {
window.open(report.file_url, '_blank');
} else {
alert('El reporte esta siendo generado. Por favor intenta nuevamente en unos momentos.');
}
} catch (err) {
console.error('[TeacherProgressPage] Error exporting CSV:', err);
alert('Error al generar el reporte. Por favor intenta nuevamente.');
}
};
// Chart options para engagement
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: { color: '#e5e7eb' },
},
},
scales: {
x: {
ticks: { color: '#9ca3af' },
grid: { color: 'rgba(156, 163, 175, 0.1)' },
},
y: {
ticks: { color: '#9ca3af' },
grid: { color: 'rgba(156, 163, 175, 0.1)' },
},
},
};
```
#### ESTRUCTURA JSX - TAB SWITCHER (agregar despues del selector de clase):
```tsx
{/* Tab Switcher - Agregar despues del selector de clase */}
{!loading && !error && (
<div className="flex gap-2">
<button
onClick={() => setActiveTab('progress')}
className={`rounded-lg px-4 py-2 font-semibold transition-colors ${
activeTab === 'progress'
? 'bg-detective-orange text-white'
: 'bg-detective-bg-secondary text-detective-text hover:bg-opacity-80'
}`}
>
<BarChart3 className="mr-2 inline h-5 w-5" />
Progreso
</button>
<button
onClick={() => setActiveTab('engagement')}
className={`rounded-lg px-4 py-2 font-semibold transition-colors ${
activeTab === 'engagement'
? 'bg-detective-orange text-white'
: 'bg-detective-bg-secondary text-detective-text hover:bg-opacity-80'
}`}
>
<Activity className="mr-2 inline h-5 w-5" />
Engagement
</button>
</div>
)}
```
#### CONTENIDO TAB ENGAGEMENT (agregar como seccion condicional):
```tsx
{/* Tab Engagement Content */}
{!loading && !error && activeTab === 'engagement' && (
<div className="space-y-6">
{/* Date Range Filter */}
<DetectiveCard hoverable={false}>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<FormField
label="Fecha Inicio"
name="startDate"
type="date"
value={dateRange.start}
onChange={(e) => setDateRange({ ...dateRange, start: e.target.value })}
/>
<FormField
label="Fecha Fin"
name="endDate"
type="date"
value={dateRange.end}
onChange={(e) => setDateRange({ ...dateRange, end: e.target.value })}
/>
<div className="flex items-end gap-2">
<DetectiveButton
variant="primary"
onClick={exportToCSV}
disabled={selectedClassroomId === 'all'}
>
<Download className="h-4 w-4" />
Exportar CSV
</DetectiveButton>
<DetectiveButton variant="secondary" onClick={refreshAnalytics}>
<RefreshCw className="h-4 w-4" />
</DetectiveButton>
</div>
</div>
</DetectiveCard>
{/* Loading state for analytics */}
{analyticsLoading && (
<div className="flex flex-col items-center justify-center py-12">
<Loader2 className="mb-4 h-12 w-12 animate-spin text-detective-orange" />
<p className="text-detective-text-secondary">Cargando metricas de engagement...</p>
</div>
)}
{/* Error state for analytics */}
{analyticsError && !analyticsLoading && (
<DetectiveCard variant="danger">
<div className="flex items-start gap-4">
<AlertCircle className="h-8 w-8 flex-shrink-0 text-red-500" />
<div className="flex-1">
<h3 className="mb-2 text-lg font-bold text-detective-text">
Error al cargar engagement
</h3>
<p className="mb-4 text-detective-text-secondary">
{analyticsError.message}
</p>
<DetectiveButton onClick={refreshAnalytics} variant="primary">
<RefreshCw className="h-4 w-4" />
Reintentar
</DetectiveButton>
</div>
</div>
</DetectiveCard>
)}
{/* Select class prompt for engagement */}
{selectedClassroomId === 'all' && !analyticsLoading && (
<DetectiveCard hoverable={false}>
<div className="py-12 text-center">
<Activity className="mx-auto mb-4 h-16 w-16 text-detective-text-secondary" />
<h3 className="mb-2 text-lg font-semibold text-detective-text">
Selecciona una clase
</h3>
<p className="text-detective-text-secondary">
Las metricas de engagement requieren seleccionar una clase especifica
</p>
</div>
</DetectiveCard>
)}
{/* Engagement Metrics - Main Cards */}
{engagementData && selectedClassroomId !== 'all' && !analyticsLoading && (
<>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
<DetectiveCard hoverable={false}>
<div className="flex items-center gap-3">
<div className="rounded-lg bg-blue-500/20 p-3">
<UsersIcon className="h-8 w-8 text-blue-500" />
</div>
<div>
<p className="text-sm text-detective-text-secondary">Usuarios Activos Diarios</p>
<p className="text-3xl font-bold text-detective-text">{engagementData.dau}</p>
</div>
</div>
</DetectiveCard>
<DetectiveCard hoverable={false}>
<div className="flex items-center gap-3">
<div className="rounded-lg bg-green-500/20 p-3">
<Activity className="h-8 w-8 text-green-500" />
</div>
<div>
<p className="text-sm text-detective-text-secondary">Usuarios Activos Semanales</p>
<p className="text-3xl font-bold text-detective-text">{engagementData.wau}</p>
</div>
</div>
</DetectiveCard>
<DetectiveCard hoverable={false}>
<div className="flex items-center gap-3">
<div className="rounded-lg bg-orange-500/20 p-3">
<Clock className="h-8 w-8 text-orange-500" />
</div>
<div>
<p className="text-sm text-detective-text-secondary">Duracion Promedio (min)</p>
<p className="text-3xl font-bold text-detective-text">
{safeFormat(engagementData?.session_duration_avg, 0, '', '0')}
</p>
</div>
</div>
</DetectiveCard>
<DetectiveCard hoverable={false}>
<div className="flex items-center gap-3">
<div className="rounded-lg bg-purple-500/20 p-3">
<TrendingUp className="h-8 w-8 text-purple-500" />
</div>
<div>
<p className="text-sm text-detective-text-secondary">Sesiones por Usuario</p>
<p className="text-3xl font-bold text-detective-text">
{safeFormat(engagementData?.sessions_per_user, 1, '', '0.0')}
</p>
</div>
</div>
</DetectiveCard>
</div>
{/* Comparison with Previous Period */}
<DetectiveCard hoverable={false}>
<h3 className="mb-4 text-lg font-bold text-detective-text">
Comparacion con Periodo Anterior
</h3>
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
<div className="flex items-center gap-3">
{(engagementData?.comparison_previous_period?.dau_change ?? 0) >= 0 ? (
<ArrowUp className="h-6 w-6 text-green-500" />
) : (
<ArrowDown className="h-6 w-6 text-red-500" />
)}
<div>
<p className="text-sm text-detective-text-secondary">Cambio en DAU</p>
<p
className={`text-2xl font-bold ${
(engagementData?.comparison_previous_period?.dau_change ?? 0) >= 0
? 'text-green-500'
: 'text-red-500'
}`}
>
{(engagementData?.comparison_previous_period?.dau_change ?? 0) >= 0 ? '+' : ''}
{safeFormat(
engagementData?.comparison_previous_period?.dau_change,
1,
'%',
'0.0%',
)}
</p>
</div>
</div>
<div className="flex items-center gap-3">
{(engagementData?.comparison_previous_period?.wau_change ?? 0) >= 0 ? (
<ArrowUp className="h-6 w-6 text-green-500" />
) : (
<ArrowDown className="h-6 w-6 text-red-500" />
)}
<div>
<p className="text-sm text-detective-text-secondary">Cambio en WAU</p>
<p
className={`text-2xl font-bold ${
(engagementData?.comparison_previous_period?.wau_change ?? 0) >= 0
? 'text-green-500'
: 'text-red-500'
}`}
>
{(engagementData?.comparison_previous_period?.wau_change ?? 0) >= 0 ? '+' : ''}
{safeFormat(
engagementData?.comparison_previous_period?.wau_change,
1,
'%',
'0.0%',
)}
</p>
</div>
</div>
<div className="flex items-center gap-3">
{(engagementData?.comparison_previous_period?.engagement_change ?? 0) >= 0 ? (
<ArrowUp className="h-6 w-6 text-green-500" />
) : (
<ArrowDown className="h-6 w-6 text-red-500" />
)}
<div>
<p className="text-sm text-detective-text-secondary">Cambio en Engagement</p>
<p
className={`text-2xl font-bold ${
(engagementData?.comparison_previous_period?.engagement_change ?? 0) >= 0
? 'text-green-500'
: 'text-red-500'
}`}
>
{(engagementData?.comparison_previous_period?.engagement_change ?? 0) >= 0
? '+'
: ''}
{safeFormat(
engagementData?.comparison_previous_period?.engagement_change,
1,
'%',
'0.0%',
)}
</p>
</div>
</div>
</div>
</DetectiveCard>
{/* Feature Usage Table */}
{engagementData?.feature_usage && engagementData.feature_usage.length > 0 && (
<DetectiveCard hoverable={false}>
<h3 className="mb-4 text-lg font-bold text-detective-text">
Uso de Funcionalidades
</h3>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-detective-border">
<th className="px-4 py-3 text-left text-sm font-semibold text-detective-text-secondary">
Funcionalidad
</th>
<th className="px-4 py-3 text-left text-sm font-semibold text-detective-text-secondary">
Usos Totales
</th>
<th className="px-4 py-3 text-left text-sm font-semibold text-detective-text-secondary">
Usuarios Unicos
</th>
</tr>
</thead>
<tbody>
{engagementData.feature_usage
.filter(
(feature) =>
feature &&
typeof feature.feature_name === 'string' &&
typeof feature.usage_count === 'number',
)
.map((feature, index) => (
<tr
key={feature.feature_name || index}
className="border-b border-detective-border transition-colors hover:bg-detective-bg-secondary"
>
<td className="px-4 py-3 font-medium text-detective-text">
{feature.feature_name}
</td>
<td className="px-4 py-3 text-detective-text">
{feature.usage_count.toLocaleString()}
</td>
<td className="px-4 py-3 text-detective-text">
{feature.unique_users ?? 0}
</td>
</tr>
))}
</tbody>
</table>
</div>
</DetectiveCard>
)}
</>
)}
</div>
)}
{/* Tab Progress Content - Envolver el contenido existente */}
{!loading && !error && activeTab === 'progress' && (
<>
{/* Todo el contenido existente de progreso va aqui */}
{/* ClassProgressDashboard, Tips card, etc. */}
</>
)}
```
---
### 3.2 GamilitSidebar.tsx - REMOVER ITEM ANALYTICS
**Ruta:** `/home/isem/workspace/projects/gamilit/apps/frontend/src/shared/components/layout/GamilitSidebar.tsx`
**Accion:** Este cambio YA esta incluido en FASE 5A. El item `analytics` se remueve junto con los otros items en esa fase.
**Verificacion:** Confirmar que en SPEC-FASE-5A-SIDEBAR.md el item con id `analytics` esta en la lista de items removidos.
---
## 4. ORDEN DE IMPLEMENTACION
```yaml
Paso_1:
descripcion: "Agregar imports necesarios a TeacherProgressPage"
archivo: TeacherProgressPage.tsx
lineas: "1-30 (imports)"
riesgo: BAJO
Paso_2:
descripcion: "Agregar nuevos estados y queries"
archivo: TeacherProgressPage.tsx
ubicacion: "Dentro del componente, despues de estados existentes"
riesgo: MEDIO
Paso_3:
descripcion: "Agregar funciones auxiliares"
archivo: TeacherProgressPage.tsx
ubicacion: "Despues de los estados, antes del return"
riesgo: BAJO
Paso_4:
descripcion: "Agregar Tab Switcher en JSX"
archivo: TeacherProgressPage.tsx
ubicacion: "Despues del selector de clase"
riesgo: MEDIO
Paso_5:
descripcion: "Envolver contenido existente en condicional activeTab === 'progress'"
archivo: TeacherProgressPage.tsx
ubicacion: "Todo el contenido actual de progreso"
riesgo: ALTO
Paso_6:
descripcion: "Agregar seccion de contenido engagement"
archivo: TeacherProgressPage.tsx
ubicacion: "Despues del contenido de progress"
riesgo: MEDIO
Paso_7:
descripcion: "Verificar que GamilitSidebar no tiene item analytics (FASE 5A)"
archivo: GamilitSidebar.tsx
riesgo: BAJO
```
---
## 5. DEPENDENCIAS Y HOOKS
### 5.1 Hooks Reutilizados
| Hook | Origen | Uso en TeacherProgressPage |
|------|--------|---------------------------|
| `useAnalytics` | `../hooks/useAnalytics` | Obtener datos de engagement |
| `useClassrooms` | YA EXISTE | Selector de clase (existente) |
| `useClassroomsStats` | YA EXISTE | Stats agregadas (existente) |
### 5.2 APIs Utilizadas
| API | Endpoint | Metodo |
|-----|----------|--------|
| `analyticsApi.getClassroomAnalytics` | `/teacher/analytics` | GET |
| `analyticsApi.getEngagementMetrics` | `/teacher/analytics/engagement` | GET |
| `analyticsApi.generateReport` | `/teacher/reports` | POST |
### 5.3 Tipos Requeridos
```typescript
// Importar desde @apps/teacher/types
import type { ClassroomAnalytics, EngagementMetrics } from '@apps/teacher/types';
// Importar desde @services/api/teacher/analyticsApi
import type {
GetAnalyticsQueryDto,
GetEngagementMetricsDto,
GenerateReportsDto,
Report,
} from '@services/api/teacher/analyticsApi';
```
---
## 6. PLAN DE PRUEBAS
### 6.1 Pruebas Manuales
```yaml
Prueba_1_Tab_Switching:
precondicion: "Login como teacher, navegar a /teacher/progress"
pasos:
- Verificar que aparecen dos tabs: Progreso y Engagement
- Click en tab Engagement
- Verificar que se muestra mensaje de seleccionar clase
- Click en tab Progreso
- Verificar que se muestra el contenido original
resultado_esperado: "Tabs funcionan correctamente"
Prueba_2_Engagement_Data:
precondicion: "Login como teacher, navegar a /teacher/progress"
pasos:
- Seleccionar una clase especifica
- Click en tab Engagement
- Verificar que se cargan datos (DAU, WAU, etc.)
- Verificar filtro de fechas
- Click en Exportar CSV
resultado_esperado: "Datos de engagement se muestran correctamente"
Prueba_3_Progress_Unchanged:
precondicion: "Login como teacher, navegar a /teacher/progress"
pasos:
- Seleccionar clase
- Verificar ClassProgressDashboard funciona
- Verificar tips card visible
- Verificar estadisticas generales
resultado_esperado: "Funcionalidad de progreso no afectada"
Prueba_4_Analytics_Route:
precondicion: "Login como teacher"
pasos:
- Navegar directamente a /teacher/analytics
- Verificar que la pagina carga (ruta mantenida)
resultado_esperado: "Ruta legacy funcional"
Prueba_5_Sidebar_Analytics_Removed:
precondicion: "Login como teacher"
pasos:
- Verificar que el sidebar NO muestra item "Analiticas"
- Verificar que SI muestra item "Progreso"
resultado_esperado: "Sidebar correcto despues de FASE 5A"
```
### 6.2 Criterios de Aceptacion
- [ ] Tab switcher visible y funcional
- [ ] Tab Progreso muestra contenido original sin cambios
- [ ] Tab Engagement muestra metricas cuando hay clase seleccionada
- [ ] Tab Engagement muestra mensaje apropiado cuando no hay clase
- [ ] Filtro de fechas funciona
- [ ] Exportar CSV funciona
- [ ] Comparacion con periodo anterior visible
- [ ] Tabla de uso de funcionalidades visible (si hay datos)
- [ ] No hay errores en consola
- [ ] Loading states correctos
- [ ] Error handling correcto
---
## 7. ROLLBACK
En caso de necesitar revertir:
1. **Restaurar TeacherProgressPage.tsx:**
- Revertir a version anterior sin tabs
- Remover imports de analytics
- Remover estados de engagement
2. **Restaurar GamilitSidebar.tsx:**
- Agregar item analytics de vuelta al array teacherItems
- Este paso depende de si FASE 5A se revierte tambien
3. **No hay archivos eliminados:**
- TeacherAnalyticsPage.tsx sigue existiendo
- Ruta /teacher/analytics sigue funcional
---
## 8. CONSIDERACIONES TECNICAS
### 8.1 Performance
```yaml
Optimizaciones_implementadas:
- useMemo para queries (evita re-fetches innecesarios)
- Carga condicional de engagement (solo cuando tab activo)
- Queries undefined cuando no hay classroom seleccionado
Carga_lazy:
- Datos de engagement NO se cargan hasta que:
1. Tab engagement esta activo
2. Una clase especifica esta seleccionada
```
### 8.2 UX Considerations
```yaml
Flujo_usuario:
1. Usuario entra a /teacher/progress
2. Ve contenido de progreso por defecto
3. Si quiere engagement, click en tab
4. Debe seleccionar clase primero
5. Se muestran metricas de engagement
Mensajes_claros:
- "Selecciona una clase" cuando no hay clase seleccionada
- Loading spinner mientras carga
- Error con boton de reintentar
```
### 8.3 Compatibilidad
```yaml
Rutas_legacy:
- /teacher/analytics: FUNCIONAL (no se elimina)
- Bookmarks existentes: FUNCIONAN
- Links externos: FUNCIONAN
Recomendacion_futura:
- Agregar redirect de /teacher/analytics a /teacher/progress#engagement
- Esto puede hacerse en una fase posterior
```
---
## 9. ESTRUCTURA FINAL DEL ARCHIVO
```
TeacherProgressPage.tsx (despues de cambios)
├── Imports (originales + nuevos de engagement)
├── ChartJS.register()
├── safeFormat() helper
├── TeacherProgressPage component
│ ├── Estados originales (selectedClassroomId, showClassroomDropdown)
│ ├── Estados nuevos (activeTab, dateRange)
│ ├── Hooks originales (useClassrooms, useClassroomsStats, useUserGamification)
│ ├── Hooks nuevos (useAnalytics - condicional)
│ ├── useMemo (selectedClassroomName, overallStats, analyticsQuery, engagementQuery)
│ ├── Funciones (handleLogout, exportToCSV)
│ └── JSX
│ ├── Header
│ ├── Loading/Error states
│ ├── Stats cards (cuando all selected)
│ ├── Classroom selector
│ ├── Tab switcher (NUEVO)
│ ├── Tab Progress content (contenido original envuelto)
│ └── Tab Engagement content (NUEVO)
```
---
## 10. NOTAS ADICIONALES
### Sobre la ruta /teacher/analytics
La pagina TeacherAnalyticsPage.tsx NO se elimina. Permanece funcional para:
- Usuarios con bookmarks
- Links externos
- Compatibilidad hacia atras
En una fase futura se puede agregar un redirect automatico.
### Sobre el orden de ejecucion
Esta fase (5C) DEBE ejecutarse DESPUES de FASE 5A porque:
1. FASE 5A remueve el item "Analiticas" del sidebar
2. Sin este cambio, habria dos formas de acceder a la misma funcionalidad
### Sobre testing
Se recomienda hacer testing manual exhaustivo porque:
1. La funcionalidad de progreso NO debe cambiar
2. La funcionalidad de engagement debe ser identica a TeacherAnalytics
3. Los tabs deben funcionar correctamente
---
**Estado:** LISTO PARA IMPLEMENTAR
**Prerequisitos:** FASE 5A completada
**Siguiente fase:** Validacion final y ejecucion