|
|
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
|
|
import { getServerSession } from 'next-auth'
|
|
|
import { authOptions } from '@/lib/auth'
|
|
|
import { db } from '@/lib/db'
|
|
|
-import { attendance, users, sections, classes, studentEnrollments } from '@/lib/db/schema'
|
|
|
+import { attendance, users, sections, classes, teacherAssignments, partials } from '@/lib/db/schema'
|
|
|
import { eq, and, gte, lte, desc } from 'drizzle-orm'
|
|
|
import { format } from 'date-fns'
|
|
|
import { es } from 'date-fns/locale'
|
|
|
@@ -35,36 +35,120 @@ export async function GET(request: NextRequest) {
|
|
|
// Convertir fecha a strings para comparación (el campo date es string en la DB)
|
|
|
const dateStr = date // La fecha ya viene en formato YYYY-MM-DD
|
|
|
|
|
|
- // Construir query base
|
|
|
- let whereConditions = [
|
|
|
- eq(attendance.date, dateStr)
|
|
|
- ]
|
|
|
-
|
|
|
- // Agregar filtro de sección si se proporciona
|
|
|
+ // Verificar acceso del profesor a las secciones
|
|
|
+ let teacherSections: string[] = []
|
|
|
+
|
|
|
if (sectionId && sectionId !== 'all') {
|
|
|
- whereConditions.push(eq(attendance.sectionId, sectionId))
|
|
|
+ // Verificar acceso a la sección específica
|
|
|
+ const teacherAssignment = await db
|
|
|
+ .select()
|
|
|
+ .from(teacherAssignments)
|
|
|
+ .where(
|
|
|
+ and(
|
|
|
+ eq(teacherAssignments.teacherId, session.user.id),
|
|
|
+ eq(teacherAssignments.sectionId, sectionId)
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ if (teacherAssignment.length === 0) {
|
|
|
+ return NextResponse.json(
|
|
|
+ { error: 'No tienes acceso a esta sección' },
|
|
|
+ { status: 403 }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ teacherSections = [sectionId]
|
|
|
+ } else {
|
|
|
+ // Obtener todas las secciones del profesor
|
|
|
+ const assignments = await db
|
|
|
+ .select({ sectionId: teacherAssignments.sectionId })
|
|
|
+ .from(teacherAssignments)
|
|
|
+ .where(eq(teacherAssignments.teacherId, session.user.id))
|
|
|
+
|
|
|
+ teacherSections = assignments
|
|
|
+ .map(a => a.sectionId)
|
|
|
+ .filter((id): id is string => id !== null)
|
|
|
+
|
|
|
+ if (teacherSections.length === 0) {
|
|
|
+ return NextResponse.json(
|
|
|
+ { error: 'No tienes secciones asignadas' },
|
|
|
+ { status: 403 }
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // Construir query base con verificación de acceso
|
|
|
+ let whereConditions = [
|
|
|
+ eq(attendance.date, dateStr),
|
|
|
+ // Solo incluir secciones a las que el profesor tiene acceso
|
|
|
+ teacherSections.length === 1
|
|
|
+ ? eq(attendance.sectionId, teacherSections[0])
|
|
|
+ : attendance.sectionId // Si hay múltiples secciones, se filtrará en el query
|
|
|
+ ]
|
|
|
+
|
|
|
// Obtener registros de asistencia con información relacionada
|
|
|
- const attendanceData = await db
|
|
|
- .select({
|
|
|
- id: attendance.id,
|
|
|
- date: attendance.date,
|
|
|
- status: attendance.status,
|
|
|
- reason: attendance.reason,
|
|
|
- studentName: users.firstName,
|
|
|
- studentLastName: users.lastName,
|
|
|
- studentEmail: users.email,
|
|
|
- sectionName: sections.name,
|
|
|
- className: classes.name,
|
|
|
- classCode: classes.code
|
|
|
- })
|
|
|
- .from(attendance)
|
|
|
- .innerJoin(users, eq(attendance.studentId, users.id))
|
|
|
- .innerJoin(sections, eq(attendance.sectionId, sections.id))
|
|
|
- .innerJoin(classes, eq(sections.classId, classes.id))
|
|
|
- .where(and(...whereConditions))
|
|
|
- .orderBy(desc(attendance.date), users.firstName, users.lastName)
|
|
|
+ let attendanceData
|
|
|
+
|
|
|
+ if (teacherSections.length === 1) {
|
|
|
+ // Caso simple: una sola sección
|
|
|
+ attendanceData = await db
|
|
|
+ .select({
|
|
|
+ id: attendance.id,
|
|
|
+ date: attendance.date,
|
|
|
+ status: attendance.status,
|
|
|
+ reason: attendance.reason,
|
|
|
+ studentName: users.firstName,
|
|
|
+ studentLastName: users.lastName,
|
|
|
+ studentEmail: users.email,
|
|
|
+ sectionName: sections.name,
|
|
|
+ className: classes.name,
|
|
|
+ classCode: classes.code,
|
|
|
+ partialName: partials.name,
|
|
|
+ sectionId: attendance.sectionId
|
|
|
+ })
|
|
|
+ .from(attendance)
|
|
|
+ .innerJoin(users, eq(attendance.studentId, users.id))
|
|
|
+ .innerJoin(sections, eq(attendance.sectionId, sections.id))
|
|
|
+ .innerJoin(classes, eq(sections.classId, classes.id))
|
|
|
+ .leftJoin(partials, eq(attendance.partialId, partials.id))
|
|
|
+ .where(
|
|
|
+ and(
|
|
|
+ eq(attendance.date, dateStr),
|
|
|
+ eq(attendance.sectionId, teacherSections[0])
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .orderBy(desc(attendance.date), users.firstName, users.lastName)
|
|
|
+ } else {
|
|
|
+ // Caso múltiples secciones: obtener todos los registros y filtrar
|
|
|
+ const allData = await db
|
|
|
+ .select({
|
|
|
+ id: attendance.id,
|
|
|
+ date: attendance.date,
|
|
|
+ status: attendance.status,
|
|
|
+ reason: attendance.reason,
|
|
|
+ studentName: users.firstName,
|
|
|
+ studentLastName: users.lastName,
|
|
|
+ studentEmail: users.email,
|
|
|
+ sectionName: sections.name,
|
|
|
+ className: classes.name,
|
|
|
+ classCode: classes.code,
|
|
|
+ partialName: partials.name,
|
|
|
+ sectionId: attendance.sectionId
|
|
|
+ })
|
|
|
+ .from(attendance)
|
|
|
+ .innerJoin(users, eq(attendance.studentId, users.id))
|
|
|
+ .innerJoin(sections, eq(attendance.sectionId, sections.id))
|
|
|
+ .innerJoin(classes, eq(sections.classId, classes.id))
|
|
|
+ .leftJoin(partials, eq(attendance.partialId, partials.id))
|
|
|
+ .where(eq(attendance.date, dateStr))
|
|
|
+ .orderBy(desc(attendance.date), users.firstName, users.lastName)
|
|
|
+
|
|
|
+ // Filtrar solo las secciones del profesor
|
|
|
+ attendanceData = allData.filter(record =>
|
|
|
+ record.sectionId !== null && teacherSections.includes(record.sectionId)
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ const filteredData = attendanceData
|
|
|
|
|
|
// Generar contenido CSV
|
|
|
const csvHeaders = [
|
|
|
@@ -75,17 +159,20 @@ export async function GET(request: NextRequest) {
|
|
|
'Código de Clase',
|
|
|
'Sección',
|
|
|
'Estado',
|
|
|
+ 'Parcial',
|
|
|
'Razón'
|
|
|
]
|
|
|
|
|
|
- const csvRows = attendanceData.map(record => [
|
|
|
- format(new Date(record.date), 'dd/MM/yyyy', { locale: es }),
|
|
|
+ const csvRows = filteredData.map(record => [
|
|
|
+ // Usar la fecha directamente para evitar offset de timezone
|
|
|
+ record.date.split('-').reverse().join('/'), // Convertir YYYY-MM-DD a DD/MM/YYYY
|
|
|
`${record.studentName || ''} ${record.studentLastName || ''}`.trim(),
|
|
|
record.studentEmail || '',
|
|
|
record.className || '',
|
|
|
record.classCode || '',
|
|
|
record.sectionName || '',
|
|
|
getStatusText(record.status),
|
|
|
+ record.partialName || '',
|
|
|
record.reason || ''
|
|
|
])
|
|
|
|
|
|
@@ -95,10 +182,10 @@ export async function GET(request: NextRequest) {
|
|
|
...csvRows.map(row => row.map(field => `"${field.toString().replace(/"/g, '""')}"`).join(','))
|
|
|
].join('\n')
|
|
|
|
|
|
- // Generar nombre de archivo
|
|
|
+ // Generar nombre de archivo usando la fecha directamente para evitar offset de timezone
|
|
|
const fileName = sectionId && sectionId !== 'all'
|
|
|
- ? `asistencia_${format(new Date(date), 'yyyy-MM-dd', { locale: es })}_seccion_${sectionId}.csv`
|
|
|
- : `asistencia_${format(new Date(date), 'yyyy-MM-dd', { locale: es })}.csv`
|
|
|
+ ? `asistencia_${date}_seccion_${sectionId}.csv`
|
|
|
+ : `asistencia_${date}.csv`
|
|
|
|
|
|
// Retornar archivo CSV
|
|
|
return new NextResponse(csvContent, {
|