fix(classrooms): blindar vistas presenciales y virtuales sin aulas + feat(admin): reinicializacion de sistema con clave de admin

This commit is contained in:
2026-09-23 21:03:40 -03:00
parent 95d2aac323
commit 1e6f493358
8 changed files with 536 additions and 63 deletions
+133
View File
@@ -2565,3 +2565,136 @@ def delete_building(id):
'status': 'success',
'message': f'Edificio "{building.name}" eliminado correctamente.'
}), 200
@api_admin_bp.route('/system/reset', methods=['POST'])
@api_admin_bp.route('/settings/reset_system', methods=['POST'])
@jwt_required
def reset_entire_system():
"""
Reinicializa el sistema depurando historiales, reservas, espacios, sedes y académica.
PRESERVA: Todas las cuentas de usuario, roles RBAC, permisos, configuraciones globales
y tipos de hitos (pestaña Administración intacta).
REQUISITO CRÍTICO DE SEGURIDAD: Debe solicitar y validar la contraseña del perfil administrador en sesión.
"""
from app.models.grade import MilestoneGrade
from app.models.enrollment import StudentEnrollment
from app.models.classroom import ClassroomResource
from app.models.building import Building
from app.models.sync_task import MoodleSyncTask
acting_user = getattr(g, 'jwt_user', None)
if not (acting_user and (acting_user.is_admin() or getattr(acting_user, 'role', '').upper() == 'ADMIN')):
return jsonify({
'status': 'error',
'error': 'Forbidden',
'message': 'Acceso denegado. Solo un usuario con perfil Administrador puede solicitar la reinicialización del sistema.'
}), 403
data = request.get_json(silent=True) or request.form.to_dict() or {}
password = str(data.get('password') or '').strip()
if not password:
return jsonify({
'status': 'error',
'error': 'BadRequest',
'message': 'Debe ingresar la contraseña de su usuario administrador para autorizar la reinicialización.'
}), 400
if not acting_user.check_password(password):
# Registrar intento fallido de reinicio en bitácora de auditoría
try:
failed_audit = AuditLog(
user_id=acting_user.id,
user_email=acting_user.email,
action='SYSTEM_RESET_FAILED',
module='admin',
details='Intento de reinicialización del sistema cancelado: contraseña incorrecta.',
ip_address=request.remote_addr
)
db.session.add(failed_audit)
db.session.commit()
except Exception:
pass
return jsonify({
'status': 'error',
'error': 'Unauthorized',
'message': 'Contraseña de administrador incorrecta. Operación cancelada por seguridad.'
}), 401
try:
# Purga referencial ordenada de entidades operativas
# 1. Reservas
reservations_deleted = Reservation.query.delete()
# 2. Calificaciones e Hitos de cursadas
grades_deleted = MilestoneGrade.query.delete()
milestones_deleted = AcademicMilestone.query.delete()
# 3. Matrículas de alumnos y asignaciones docentes a comisiones
enrollments_deleted = StudentEnrollment.query.delete()
teachers_deleted = CommissionTeacher.query.delete()
# 4. Comisiones, Materias y Carreras
commissions_deleted = Commission.query.delete()
subjects_deleted = Subject.query.delete()
careers_deleted = Career.query.delete()
# 5. Recursos de aula, Aulas y Edificios/Sedes
resources_deleted = ClassroomResource.query.delete()
classrooms_deleted = Classroom.query.delete()
buildings_deleted = Building.query.delete()
# 6. Ciclos Lectivos / Términos Académicos
terms_deleted = AcademicTerm.query.delete()
# 7. Cola de eventos de sincronización Moodle huérfanos
moodle_tasks_deleted = MoodleSyncTask.query.delete()
# 8. Historiales: Limpiar auditorías anteriores
logs_deleted = AuditLog.query.delete()
# 9. Asentar registro oficial inmutable del reinicio en AuditLog
reset_audit = AuditLog(
user_id=acting_user.id,
user_email=acting_user.email,
action='SYSTEM_RESET',
module='admin',
details=(
f"Reinicialización total ejecutada por administrador {acting_user.email}. "
f"Entidades purgadas: {reservations_deleted} reservas, {classrooms_deleted} aulas, "
f"{buildings_deleted} sedes, {commissions_deleted} comisiones, {subjects_deleted} materias, "
f"{careers_deleted} carreras, {terms_deleted} ciclos lectivos, {enrollments_deleted} inscripciones, "
f"{grades_deleted} notas, {logs_deleted} logs históricos. Cuentas y configuración administrativa preservadas."
),
ip_address=request.remote_addr
)
db.session.add(reset_audit)
db.session.commit()
return jsonify({
'status': 'success',
'message': 'Sistema reinicializado correctamente. Se han depurado los historiales, reservas, espacios, sedes y académica. Las cuentas de usuario y configuraciones se encuentran intactas.',
'purged_stats': {
'reservations': reservations_deleted,
'classrooms': classrooms_deleted,
'buildings': buildings_deleted,
'commissions': commissions_deleted,
'subjects': subjects_deleted,
'careers': careers_deleted,
'academic_terms': terms_deleted,
'enrollments': enrollments_deleted,
'grades': grades_deleted,
'prior_audit_logs': logs_deleted
}
}), 200
except Exception as e:
db.session.rollback()
return jsonify({
'status': 'error',
'error': 'InternalServerError',
'message': f'Error durante la reinicialización de la base de datos: {str(e)}'
}), 500