forked from val-lop20/Pagina-de-Asistencia-MAC
471 lines
17 KiB
Python
471 lines
17 KiB
Python
#!/usr/bin/env python
|
||
"""
|
||
Script para cargar datos iniciales desde archivos Excel a la base de datos.
|
||
Este script importa:
|
||
- Eventos desde Conferencias MAC Agenda Completa.xlsx
|
||
- Estudiantes desde Student.xlsx
|
||
- Perfiles de Asistentes desde AssistantProfile.xlsx
|
||
- Estadísticas de Asistencia desde AttendanceStats.xlsx
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import django
|
||
from pathlib import Path
|
||
|
||
# Configurar Django
|
||
BASE_DIR = Path(__file__).resolve().parent
|
||
sys.path.append(str(BASE_DIR))
|
||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mac_attendance.settings')
|
||
django.setup()
|
||
|
||
import openpyxl
|
||
from datetime import datetime, time
|
||
from django.contrib.auth.models import User
|
||
from authentication.models import Student, AssistantProfile, Asistente, SystemConfiguration
|
||
from events.models import Event
|
||
from attendance.models import Attendance, AttendanceStats
|
||
from django.utils import timezone
|
||
|
||
def import_events(file_path):
|
||
"""Importar eventos desde Conferencias MAC Agenda Completa.xlsx"""
|
||
print(f"\n📅 Importando eventos desde {file_path}...")
|
||
|
||
wb = openpyxl.load_workbook(file_path)
|
||
ws = wb.active
|
||
|
||
events_created = 0
|
||
events_updated = 0
|
||
|
||
# Saltar el encabezado (primera fila)
|
||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||
if not row[0]: # Si no hay título, saltar
|
||
continue
|
||
|
||
title = row[0]
|
||
speaker = row[1] if len(row) > 1 and row[1] else ""
|
||
date_str = row[2] if len(row) > 2 and row[2] else None
|
||
start_time_str = row[3] if len(row) > 3 and row[3] else None
|
||
end_time_str = row[4] if len(row) > 4 and row[4] else None
|
||
event_type = row[5] if len(row) > 5 and row[5] else "Conferencia"
|
||
modality = row[6] if len(row) > 6 and row[6] else "Presencial"
|
||
location = row[7] if len(row) > 7 and row[7] else ""
|
||
description = row[8] if len(row) > 8 and row[8] else ""
|
||
is_active = bool(row[9]) if len(row) > 9 and row[9] else True
|
||
|
||
if not date_str or not start_time_str:
|
||
print(f" ⚠️ Saltando evento sin fecha u hora: {title}")
|
||
continue
|
||
|
||
# Convertir fecha
|
||
if isinstance(date_str, datetime):
|
||
event_date = date_str.date()
|
||
elif isinstance(date_str, str):
|
||
try:
|
||
event_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||
except:
|
||
try:
|
||
event_date = datetime.strptime(date_str, '%d/%m/%Y').date()
|
||
except:
|
||
print(f" ⚠️ Formato de fecha inválido para {title}: {date_str}")
|
||
continue
|
||
else:
|
||
event_date = date_str
|
||
|
||
# Convertir hora de inicio
|
||
if isinstance(start_time_str, time):
|
||
start_time = start_time_str
|
||
elif isinstance(start_time_str, str):
|
||
try:
|
||
start_time = datetime.strptime(start_time_str, '%H:%M:%S').time()
|
||
except:
|
||
try:
|
||
start_time = datetime.strptime(start_time_str, '%H:%M').time()
|
||
except:
|
||
print(f" ⚠️ Formato de hora inválido para {title}: {start_time_str}")
|
||
continue
|
||
else:
|
||
start_time = start_time_str
|
||
|
||
# Convertir hora de fin
|
||
end_time = None
|
||
if end_time_str:
|
||
if isinstance(end_time_str, time):
|
||
end_time = end_time_str
|
||
elif isinstance(end_time_str, str):
|
||
try:
|
||
end_time = datetime.strptime(end_time_str, '%H:%M:%S').time()
|
||
except:
|
||
try:
|
||
end_time = datetime.strptime(end_time_str, '%H:%M').time()
|
||
except:
|
||
pass
|
||
|
||
# Crear o actualizar evento (sin validaciones para permitir fechas pasadas)
|
||
try:
|
||
event = Event.objects.get(title=title, date=event_date)
|
||
# Actualizar usando update() para evitar validaciones
|
||
Event.objects.filter(id=event.id).update(
|
||
description=description,
|
||
location=location,
|
||
start_time=start_time,
|
||
end_time=end_time,
|
||
is_active=is_active
|
||
)
|
||
created = False
|
||
except Event.DoesNotExist:
|
||
# Crear sin validaciones usando el método directo de la base
|
||
event = Event(
|
||
title=title,
|
||
date=event_date,
|
||
description=description,
|
||
location=location,
|
||
start_time=start_time,
|
||
end_time=end_time,
|
||
is_active=is_active
|
||
)
|
||
# Guardar sin llamar a clean() para evitar validación de fechas pasadas
|
||
super(Event, event).save(force_insert=True)
|
||
created = True
|
||
|
||
if created:
|
||
events_created += 1
|
||
print(f" ✅ Evento creado: {title} - {event_date} {start_time}")
|
||
else:
|
||
events_updated += 1
|
||
|
||
print(f"\n✅ Eventos importados: {events_created} creados, {events_updated} actualizados")
|
||
return events_created + events_updated
|
||
|
||
|
||
def import_students(file_path):
|
||
"""Importar estudiantes desde Student.xlsx"""
|
||
print(f"\n👨🎓 Importando estudiantes desde {file_path}...")
|
||
|
||
wb = openpyxl.load_workbook(file_path)
|
||
ws = wb.active
|
||
|
||
students_created = 0
|
||
students_updated = 0
|
||
|
||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||
if not row[0]: # Si no hay número de cuenta, saltar
|
||
continue
|
||
|
||
account_number = str(row[0]).strip()
|
||
full_name = row[1] if len(row) > 1 and row[1] else "Sin nombre"
|
||
|
||
student, created = Student.objects.update_or_create(
|
||
account_number=account_number,
|
||
defaults={
|
||
'full_name': full_name,
|
||
'user_type': 'student'
|
||
}
|
||
)
|
||
|
||
if created:
|
||
students_created += 1
|
||
print(f" ✅ Estudiante creado: {account_number} - {full_name}")
|
||
else:
|
||
students_updated += 1
|
||
|
||
print(f"\n✅ Estudiantes importados: {students_created} creados, {students_updated} actualizados")
|
||
return students_created + students_updated
|
||
|
||
|
||
def import_assistant_profiles(file_path):
|
||
"""Importar perfiles de asistentes desde AssistantProfile.xlsx"""
|
||
print(f"\n👔 Importando perfiles de asistentes desde {file_path}...")
|
||
|
||
from authentication.models import UserProfile
|
||
wb = openpyxl.load_workbook(file_path)
|
||
ws = wb.active
|
||
|
||
profiles_created = 0
|
||
profiles_updated = 0
|
||
profiles_skipped = 0
|
||
|
||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||
if not row[0]: # Si no hay número de cuenta, saltar
|
||
continue
|
||
|
||
account_number = str(row[0]).strip()
|
||
full_name = row[1] if len(row) > 1 and row[1] else "Sin nombre"
|
||
|
||
# Verificar si ya existe un UserProfile con este account_number
|
||
existing_profile = UserProfile.objects.filter(account_number=account_number).first()
|
||
|
||
if existing_profile:
|
||
# Si existe y es estudiante, actualizar a asistente
|
||
if existing_profile.user_type == 'student':
|
||
print(f" ⚠️ Saltando {account_number} - ya existe como estudiante")
|
||
profiles_skipped += 1
|
||
continue
|
||
else:
|
||
# Si ya es asistente, actualizar
|
||
existing_profile.full_name = full_name
|
||
existing_profile.save()
|
||
profiles_updated += 1
|
||
else:
|
||
# No existe, crear nuevo
|
||
profile = AssistantProfile.objects.create(
|
||
account_number=account_number,
|
||
full_name=full_name,
|
||
user_type='assistant'
|
||
)
|
||
profiles_created += 1
|
||
print(f" ✅ Perfil de asistente creado: {account_number} - {full_name}")
|
||
|
||
print(f"\n✅ Perfiles de asistentes importados: {profiles_created} creados, {profiles_updated} actualizados, {profiles_skipped} saltados")
|
||
return profiles_created + profiles_updated
|
||
|
||
|
||
def assign_permissions_to_assistant():
|
||
"""Asignar permisos al asistente para registrar asistencias"""
|
||
print(f"\n🔑 Asignando permisos a asistentes...")
|
||
|
||
from authentication.models import UserProfile
|
||
# Obtener solo los UserProfile que son asistentes
|
||
assistants = UserProfile.objects.filter(user_type='assistant')
|
||
permissions_created = 0
|
||
|
||
for assistant_profile in assistants:
|
||
# Crear permiso de asistente (Asistente model)
|
||
asistente, created = Asistente.objects.get_or_create(
|
||
user_profile=assistant_profile,
|
||
defaults={
|
||
'can_manage_events': True
|
||
}
|
||
)
|
||
|
||
if created:
|
||
permissions_created += 1
|
||
print(f" ✅ Permisos asignados a: {assistant_profile.full_name}")
|
||
|
||
print(f"\n✅ Permisos asignados: {permissions_created} asistentes")
|
||
return permissions_created
|
||
|
||
|
||
def import_attendance_stats(file_path):
|
||
"""Importar estadísticas de asistencia desde AttendanceStats.xlsx"""
|
||
print(f"\n📊 Importando estadísticas de asistencia desde {file_path}...")
|
||
|
||
from authentication.models import UserProfile
|
||
wb = openpyxl.load_workbook(file_path)
|
||
ws = wb.active
|
||
|
||
stats_created = 0
|
||
attendances_created = 0
|
||
|
||
# Obtener eventos principales
|
||
event1 = Event.objects.filter(title__icontains="Transformación Digital").first()
|
||
event2 = Event.objects.filter(title__icontains="Finanzas").first()
|
||
|
||
if not event1:
|
||
print(" ⚠️ Evento 'Transformación Digital' no encontrado")
|
||
if not event2:
|
||
print(" ⚠️ Evento 'Finanzas' no encontrado")
|
||
|
||
# Obtener el asistente para registered_by
|
||
assistant = UserProfile.objects.filter(user_type='assistant').first()
|
||
if not assistant:
|
||
print(" ⚠️ No hay asistentes en el sistema. Creando asistente por defecto...")
|
||
assistant = UserProfile.objects.create(
|
||
account_number='99999999',
|
||
full_name='Sistema - Importación Automática',
|
||
user_type='assistant'
|
||
)
|
||
print(f" ✅ Asistente creado: {assistant.full_name}")
|
||
|
||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||
if not row[0]: # Si no hay número de cuenta, saltar
|
||
continue
|
||
|
||
account_number = str(row[0]).strip()
|
||
full_name = row[1] if len(row) > 1 else ""
|
||
attended_events = int(row[2]) if len(row) > 2 and row[2] else 0
|
||
total_events = int(row[3]) if len(row) > 3 and row[3] else 18
|
||
attendance_percentage = float(row[4]) if len(row) > 4 and row[4] else 0.0
|
||
|
||
try:
|
||
student = Student.objects.get(account_number=account_number)
|
||
except Student.DoesNotExist:
|
||
# Si el estudiante no existe, crearlo
|
||
student = Student.objects.create(
|
||
account_number=account_number,
|
||
full_name=full_name,
|
||
user_type='student'
|
||
)
|
||
print(f" ℹ️ Estudiante creado desde stats: {account_number}")
|
||
|
||
# Crear estadísticas
|
||
stats, created = AttendanceStats.objects.update_or_create(
|
||
student=student,
|
||
defaults={
|
||
'total_events': total_events,
|
||
'attended_events': attended_events,
|
||
'attendance_percentage': attendance_percentage
|
||
}
|
||
)
|
||
|
||
if created:
|
||
stats_created += 1
|
||
|
||
# Crear asistencias según el número total
|
||
if attended_events == 2:
|
||
# Asistió a ambas conferencias
|
||
if event1:
|
||
try:
|
||
attendance = Attendance.objects.get(student=student, event=event1)
|
||
att_created = False
|
||
except Attendance.DoesNotExist:
|
||
attendance = Attendance(
|
||
student=student,
|
||
event=event1,
|
||
timestamp=timezone.now(),
|
||
is_valid=True,
|
||
registered_by=assistant
|
||
)
|
||
# Guardar sin validaciones
|
||
super(Attendance, attendance).save(force_insert=True)
|
||
att_created = True
|
||
attendances_created += 1
|
||
|
||
if event2:
|
||
try:
|
||
attendance = Attendance.objects.get(student=student, event=event2)
|
||
att_created = False
|
||
except Attendance.DoesNotExist:
|
||
attendance = Attendance(
|
||
student=student,
|
||
event=event2,
|
||
timestamp=timezone.now(),
|
||
is_valid=True,
|
||
registered_by=assistant
|
||
)
|
||
# Guardar sin validaciones
|
||
super(Attendance, attendance).save(force_insert=True)
|
||
att_created = True
|
||
attendances_created += 1
|
||
|
||
elif attended_events == 1:
|
||
# Asistió solo a "Transformación Digital"
|
||
if event1:
|
||
try:
|
||
attendance = Attendance.objects.get(student=student, event=event1)
|
||
att_created = False
|
||
except Attendance.DoesNotExist:
|
||
attendance = Attendance(
|
||
student=student,
|
||
event=event1,
|
||
timestamp=timezone.now(),
|
||
is_valid=True,
|
||
registered_by=assistant
|
||
)
|
||
# Guardar sin validaciones
|
||
super(Attendance, attendance).save(force_insert=True)
|
||
att_created = True
|
||
attendances_created += 1
|
||
|
||
print(f"\n✅ Estadísticas importadas: {stats_created} creadas")
|
||
print(f"✅ Asistencias creadas: {attendances_created}")
|
||
return stats_created
|
||
|
||
|
||
def configure_system_settings():
|
||
"""Configurar settings globales del sistema"""
|
||
print(f"\n⚙️ Configurando sistema global...")
|
||
|
||
config, created = SystemConfiguration.objects.get_or_create(
|
||
id=1,
|
||
defaults={
|
||
'minimum_attendance_percentage': 80.0,
|
||
'minutes_before_event': 10, # 10 minutos antes
|
||
'minutes_after_start': 25, # 25 minutos después
|
||
}
|
||
)
|
||
|
||
if created:
|
||
print(f" ✅ Configuración creada: 80% asistencia, 10min antes, 25min después")
|
||
else:
|
||
config.minimum_attendance_percentage = 80.0
|
||
config.minutes_before_event = 10
|
||
config.minutes_after_start = 25
|
||
config.save()
|
||
print(f" 🔄 Configuración actualizada")
|
||
|
||
return config
|
||
|
||
|
||
def main():
|
||
"""Función principal"""
|
||
print("="*60)
|
||
print("🚀 CARGA DE DATOS INICIALES - SISTEMA DE ASISTENCIAS MAC")
|
||
print("="*60)
|
||
|
||
# Rutas a los archivos Excel
|
||
base_path = Path(__file__).resolve().parent
|
||
|
||
events_file = base_path / "Conferencias MAC Agenda Completa (1) (1).xlsx"
|
||
students_file = base_path / "Student-2025-10-21.xlsx"
|
||
assistants_file = base_path / "AssistantProfile-2025-10-21.xlsx"
|
||
stats_file = base_path / "AttendanceStats-2025-10-21.xlsx"
|
||
|
||
# Verificar que existan los archivos
|
||
files_ok = True
|
||
for file_path, name in [
|
||
(events_file, "Eventos"),
|
||
(students_file, "Estudiantes"),
|
||
(assistants_file, "Asistentes"),
|
||
(stats_file, "Estadísticas")
|
||
]:
|
||
if not file_path.exists():
|
||
print(f"❌ Archivo no encontrado: {name} - {file_path}")
|
||
files_ok = False
|
||
|
||
if not files_ok:
|
||
print("\n⚠️ Algunos archivos no se encontraron. Abortando.")
|
||
return
|
||
|
||
# Importar datos
|
||
try:
|
||
# 1. Configurar sistema
|
||
configure_system_settings()
|
||
|
||
# 2. Importar eventos
|
||
import_events(events_file)
|
||
|
||
# 3. Importar estudiantes
|
||
import_students(students_file)
|
||
|
||
# 4. Importar asistentes
|
||
import_assistant_profiles(assistants_file)
|
||
|
||
# 5. Asignar permisos a asistentes
|
||
assign_permissions_to_assistant()
|
||
|
||
# 6. Importar estadísticas y asistencias
|
||
import_attendance_stats(stats_file)
|
||
|
||
print("\n" + "="*60)
|
||
print("✅ IMPORTACIÓN COMPLETADA EXITOSAMENTE")
|
||
print("="*60)
|
||
|
||
# Mostrar resumen
|
||
print(f"\n📊 Resumen de datos en la base de datos:")
|
||
print(f" - Eventos: {Event.objects.count()}")
|
||
print(f" - Estudiantes: {Student.objects.count()}")
|
||
print(f" - Asistentes: {AssistantProfile.objects.count()}")
|
||
print(f" - Permisos de asistentes: {Asistente.objects.count()}")
|
||
print(f" - Asistencias: {Attendance.objects.count()}")
|
||
print(f" - Estadísticas: {AttendanceStats.objects.count()}")
|
||
|
||
except Exception as e:
|
||
print(f"\n❌ Error durante la importación: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|