# US-ADM-006: Configuración Básica de Aula
**Épica:** EAI-005 (Plataforma de Maestro Básica)
**Sprint:** Mes 1, Semana 3
**Story Points:** 6 SP
**Presupuesto:** $2,400 MXN
**Prioridad:** Alta (Alcance Inicial)
**Estado:** ✅ Completada (Mes 1)
---
## Descripción
Como profesor, quiero configurar aspectos básicos de mi aula como fechas de inicio/fin, visibilidad de módulos y gamificación para controlar la experiencia de mis estudiantes.
**Contexto del Alcance Inicial:**
Esta funcionalidad proporciona configuraciones básicas del aula con valores simples (on/off, fechas). NO incluye configuración avanzada por módulo, parametrización de gamificación, reglas personalizadas, ni configuración de notificaciones (eso va a EXT-001 Portal de Maestros Completo).
---
## Criterios de Aceptación
### CA-01: Configuración de Fechas
- [ ] Campo: Fecha de inicio del aula (opcional)
- [ ] Campo: Fecha de fin del aula (opcional)
- [ ] Validación: fecha fin > fecha inicio
- [ ] Efecto: si está fuera del rango de fechas, mostrar mensaje a estudiantes
### CA-02: Visibilidad de Módulos
- [ ] Toggle global: "Módulos visibles para estudiantes"
- [ ] Opciones:
- **Todos visibles:** estudiantes ven todos los módulos asignados
- **Ocultos:** estudiantes no ven módulos (profesor puede habilitar individualmente)
- [ ] Default: Todos visibles
### CA-03: Configuración de Gamificación
- [ ] Toggle: "Gamificación activa"
- [ ] Opciones:
- **Activa:** estudiantes ganan XP, suben niveles, desbloquean logros
- **Inactiva:** sin XP, niveles, ni logros (solo progreso %)
- [ ] Default: Activa
### CA-04: Vista de Configuración
- [ ] Formulario claro con secciones:
- Información General (fechas)
- Configuración de Contenido (visibilidad)
- Configuración de Gamificación
- [ ] Botón "Guardar Cambios"
- [ ] Confirmación al guardar
### CA-05: Valores Fijos (No Configurables en Alcance Inicial)
- [ ] XP por actividad: valores hardcodeados
- [ ] Niveles: sistema predefinido
- [ ] Logros: catálogo predefinido
- [ ] Nota: configuración avanzada va a EXT-001
---
## Especificaciones Técnicas
### Backend
**Endpoint:**
```typescript
// Obtener configuración del aula
GET /api/teacher/classrooms/{classroomId}/settings
// Actualizar configuración del aula
PATCH /api/teacher/classrooms/{classroomId}/settings
Body: {
startDate?: string;
endDate?: string;
modulesVisible?: boolean;
gamificationEnabled?: boolean;
}
```
**Response:**
```json
{
"classroomId": "uuid",
"settings": {
"startDate": "2025-09-01",
"endDate": "2026-06-30",
"modulesVisible": true,
"gamificationEnabled": true
},
"updatedAt": "2025-11-02T10:00:00Z"
}
```
**Controller:**
```typescript
@Controller('teacher/classrooms/:classroomId')
export class ClassroomSettingsController {
@Get('settings')
async getSettings(
@Param('classroomId') classroomId: string,
@CurrentUser() teacher: User
) {
return this.classroomService.getSettings(classroomId, teacher.id);
}
@Patch('settings')
async updateSettings(
@Param('classroomId') classroomId: string,
@Body() dto: UpdateSettingsDto,
@CurrentUser() teacher: User
) {
return this.classroomService.updateSettings(
classroomId,
dto,
teacher.id
);
}
}
```
**DTO:**
```typescript
export class UpdateSettingsDto {
@IsDateString()
@IsOptional()
startDate?: string;
@IsDateString()
@IsOptional()
endDate?: string;
@IsBoolean()
@IsOptional()
modulesVisible?: boolean;
@IsBoolean()
@IsOptional()
gamificationEnabled?: boolean;
}
```
**Service:**
```typescript
async updateSettings(
classroomId: string,
dto: UpdateSettingsDto,
teacherId: string
) {
await this.validateTeacherAccess(classroomId, teacherId);
const classroom = await this.classroomRepository.findOne({
where: { id: classroomId },
relations: ['settings']
});
// Validar fechas
if (dto.startDate && dto.endDate) {
const start = new Date(dto.startDate);
const end = new Date(dto.endDate);
if (end <= start) {
throw new BadRequestException(
'La fecha de fin debe ser posterior a la fecha de inicio'
);
}
}
// Actualizar o crear settings
if (!classroom.settings) {
classroom.settings = this.settingsRepository.create({
classroomId: classroom.id,
...dto
});
} else {
Object.assign(classroom.settings, dto);
}
await this.settingsRepository.save(classroom.settings);
return {
classroomId,
settings: {
startDate: classroom.settings.startDate,
endDate: classroom.settings.endDate,
modulesVisible: classroom.settings.modulesVisible,
gamificationEnabled: classroom.settings.gamificationEnabled
},
updatedAt: classroom.settings.updatedAt
};
}
```
**Modelo:**
```typescript
@Entity('classroom_settings')
export class ClassroomSettings {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'classroom_id', unique: true })
classroomId: string;
@OneToOne(() => Classroom)
@JoinColumn({ name: 'classroom_id' })
classroom: Classroom;
@Column({ type: 'date', nullable: true, name: 'start_date' })
startDate: string;
@Column({ type: 'date', nullable: true, name: 'end_date' })
endDate: string;
@Column({ default: true, name: 'modules_visible' })
modulesVisible: boolean;
@Column({ default: true, name: 'gamification_enabled' })
gamificationEnabled: boolean;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
// Actualizar Classroom entity
@Entity('classrooms')
export class Classroom {
// ... campos existentes ...
@OneToOne(() => ClassroomSettings, settings => settings.classroom, {
cascade: true,
eager: true
})
settings: ClassroomSettings;
}
```
### Frontend
**Ruta:**
```
/teacher/classroom/:classroomId/settings
```
**Vista de Configuración:**
```typescript
// ClassroomSettingsView.tsx
export const ClassroomSettingsView = () => {
const { classroomId } = useParams();
const { settings, isLoading } = useClassroomSettings(classroomId);
const {
register,
handleSubmit,
watch,
formState: { errors, isSubmitting, isDirty }
} = useForm({
defaultValues: settings
});
const onSubmit = async (data) => {
try {
await updateClassroomSettings(classroomId, data);
toast.success('Configuración guardada exitosamente');
} catch (error) {
toast.error(error.message);
}
};
if (isLoading) return