diff --git a/manager/components/AttendanceCapture.tsx b/manager/components/AttendanceCapture.tsx index 2e22004..9d6eb29 100644 --- a/manager/components/AttendanceCapture.tsx +++ b/manager/components/AttendanceCapture.tsx @@ -231,7 +231,7 @@ const AttendanceCapture: React.FC = ({ data, updateData return; } - // Encontrar a aula ativa para esta turma/aluno no momento da captura + // Encontrar a aula ativa para esta turma/aluno no momento exato da captura (Sem tolerância de 30 min) const nowLocal = new Date(); const activeLesson = (data.lessons || []).find(l => { if (l.classId !== detectedClassId || l.status === 'cancelled') return false; @@ -240,19 +240,19 @@ const AttendanceCapture: React.FC = ({ data, updateData const endStr = `${lessonDate}T${l.endTime || '23:59'}:00`; const lessonStart = new Date(startStr); const lessonEnd = new Date(endStr); - // Janela: 30 min antes do início até o fim da aula - const windowStart = new Date(lessonStart.getTime() - 30 * 60 * 1000); - return nowLocal >= windowStart && nowLocal <= lessonEnd; + + // REGRA ESTREITA: Apenas do início ao fim da aula + return nowLocal >= lessonStart && nowLocal <= lessonEnd; }); - // REGRA ESTRITA: A presença só pode ser marcada se houver uma aula ativa + // BLOQUEIO: Se não houver aula em andamento, impede o registro if (!activeLesson) { - showAlert('Atenção', "Nenhuma aula ativa detectada para esta turma no momento. A presença só pode ser registrada a partir de 30 minutos antes do início até o término da aula.", 'warning'); + showAlert('Atenção', "Nenhuma aula em andamento para esta turma no momento. O registro biométrico só é permitido durante o horário oficial da aula.", 'warning'); cancelCapture(); return; } - // Gerar string de data local (YYYY-MM-DDTHH:MM:SS) sem fuso UTC para evitar o bug do dia seguinte + // Gerar string de data local para o banco de dados const localDateStr = nowLocal.getFullYear() + '-' + String(nowLocal.getMonth() + 1).padStart(2, '0') + '-' + String(nowLocal.getDate()).padStart(2, '0') + 'T' + @@ -264,7 +264,7 @@ const AttendanceCapture: React.FC = ({ data, updateData id: crypto.randomUUID(), studentId: detectedStudentId, classId: detectedClassId, - lessonId: activeLesson.id, // Vínculo obrigatório agora + lessonId: activeLesson.id, date: localDateStr, photo: capturedImage, type: 'presence', @@ -273,16 +273,20 @@ const AttendanceCapture: React.FC = ({ data, updateData const updatedAttendance = [...(data.attendance || []), newAttendance]; updateData({ attendance: updatedAttendance }); - dbService.saveData({ ...data, attendance: updatedAttendance }); + + // Sincronização em duas etapas: Local e Servidor (SQL) + const updatedData = { ...data, attendance: updatedAttendance }; + dbService.saveData(updatedData); + dbService.saveToCloud(updatedData); - // Reset for next student + // Reset de interface setCapturedImage(null); setShowConfirmModal(false); setDetectedStudentId(null); setDetectedClassId(null); setIsProcessing(false); closeModal(); - showAlert('Sucesso', "Presença confirmada com sucesso!", 'success'); + showAlert('Sucesso', "Presença registrada e sincronizada com o servidor!", 'success'); }; const cancelCapture = () => { diff --git a/manager/components/AttendanceQuery.tsx b/manager/components/AttendanceQuery.tsx index 6274c16..c8d26da 100644 --- a/manager/components/AttendanceQuery.tsx +++ b/manager/components/AttendanceQuery.tsx @@ -118,7 +118,9 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee } updateData({ attendance: updatedAttendance }); - dbService.saveData({ ...data, attendance: updatedAttendance }); + const updatedData = { ...data, attendance: updatedAttendance }; + dbService.saveData(updatedData); + dbService.saveToCloud(updatedData); // Sincronia imediata com SQL showAlert('Sucesso', 'Status de frequência atualizado com sucesso.', 'success'); }; @@ -216,8 +218,10 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee updatedAttendance.push(newAbsence); } + const updatedData = { ...data, attendance: updatedAttendance }; updateData({ attendance: updatedAttendance }); - dbService.saveData({ ...data, attendance: updatedAttendance }); + dbService.saveData(updatedData); + dbService.saveToCloud(updatedData); // Sincronia imediata com SQL setAbsenceStudentId(''); setAbsenceJustification(''); @@ -402,17 +406,16 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee let justified = 0; const now = new Date(); - deduplicatedLessons.forEach(lesson => { - const lessonStart = new Date(lesson.date + 'T' + (lesson.startTime || '00:00') + ':00'); - const lessonEnd = new Date(lesson.date + 'T' + (lesson.endTime || '23:59') + ':00'); - const presenceStartWindow = new Date(lessonStart.getTime() - 30 * 60 * 1000); + deduplicatedLessons.forEach(lesson => { + const lessonStart = new Date(lesson.date + 'T' + (lesson.startTime || '00:00') + ':00'); + const lessonEnd = new Date(lesson.date + 'T' + (lesson.endTime || '23:59') + ':00'); - const matchedRecord = studentActualRecords.find(a => { - if ((a as any).lessonId === lesson.id) return true; - if (a.date === `${lesson.date}T${lesson.startTime || '00:00'}:00`) return true; - const recordTime = new Date(a.date); - return recordTime >= presenceStartWindow && recordTime <= lessonEnd; - }); + const matchedRecord = studentActualRecords.find(a => { + if ((a as any).lessonId === lesson.id) return true; + if (a.date === `${lesson.date}T${lesson.startTime || '00:00'}:00`) return true; + const recordTime = new Date(a.date); + return recordTime >= lessonStart && recordTime <= lessonEnd; + }); if (matchedRecord) { if (matchedRecord.type === 'absence') { @@ -520,16 +523,15 @@ const AttendanceQuery: React.FC = ({ data, updateData, dee deduplicatedLessons.forEach(lesson => { const lessonStart = new Date(lesson.date + 'T' + (lesson.startTime || '00:00') + ':00'); const lessonEnd = new Date(lesson.date + 'T' + (lesson.endTime || '23:59') + ':00'); - const presenceStartWindow = new Date(lessonStart.getTime() - 30 * 60 * 1000); // 30 mins before - + // Regra Estrita: Comparação exata com o horário da aula (sem 30 min de tolerância) let record = actualRecords.find(a => { if ((a as any).lessonId === lesson.id) return true; if (a.date === `${lesson.date}T${lesson.startTime || '00:00'}:00`) return true; const recordTime = new Date(a.date); - return recordTime >= presenceStartWindow && recordTime <= lessonEnd; + return recordTime >= lessonStart && recordTime <= lessonEnd; }); - if (!record && now >= presenceStartWindow) { + if (!record && now >= lessonStart) { const isFinished = now > lessonEnd; record = { id: `v-${lesson.id}`, diff --git a/portal/src/pages/Frequencia.tsx b/portal/src/pages/Frequencia.tsx index 911364c..5b263a7 100644 --- a/portal/src/pages/Frequencia.tsx +++ b/portal/src/pages/Frequencia.tsx @@ -143,12 +143,11 @@ export default function Frequencia() { )) ); - // Merge and Categorize — Clone EXATO do Manager (AttendanceQuery.tsx) + // Merge and Categorize — Clone EXATO do Manager (AttendanceQuery.tsx) const processedItems = deduplicatedLessons.map(lesson => { // Construir janela de tempo de forma resiliente const lessonStartMs = parseLessonDateTime(lesson.date, lesson.startTime || '00:00', 0); const lessonEndMs = parseLessonDateTime(lesson.date, lesson.endTime || '23:59', 23); - const presenceStartWindowMs = lessonStartMs - (30 * 60 * 1000); // 30 mins before // Buscar registro com a MESMA lógica do Manager (find, não filter) let record = attendance.find(a => { @@ -157,19 +156,18 @@ export default function Frequencia() { if ((a as any).lessonId === lesson.id) return true; // 2. Match exato de string (formato do JSON/sync) if (a.date === `${lesson.date}T${lesson.startTime || '00:00'}:00`) return true; - // 3. Match por janela de tempo (30 min antes até fim da aula) + // 3. Match por janela de tempo (Início ao Fim da aula - REGRA ESTREITA) const recordTimeMs = new Date(a.date).getTime(); - return recordTimeMs >= presenceStartWindowMs && recordTimeMs <= lessonEndMs; + return recordTimeMs >= lessonStartMs && recordTimeMs <= lessonEndMs; }); // Se não encontrou registro real, verificar se precisa de justificativa associada - // (pode existir um registro de justificativa com data ligeiramente diferente) if (!record) { record = attendance.find(a => { if (!a.date || typeof a.date !== 'string' || !a.justification) return false; if (a.date === `${lesson.date}T${lesson.startTime || '00:00'}:00`) return true; const recordTimeMs = new Date(a.date).getTime(); - return recordTimeMs >= presenceStartWindowMs && recordTimeMs <= lessonEndMs; + return recordTimeMs >= lessonStartMs && recordTimeMs <= lessonEndMs; }) || undefined; }