fix(timezone): enforce BRT explicitly on capture to prevent server UTC offset and restore TO_CHAR constraint

This commit is contained in:
Sidney 2026-05-25 11:10:49 -03:00
parent 6cdf609f62
commit 3e5c9afda2
3 changed files with 14 additions and 8 deletions

View File

@ -71,4 +71,6 @@
42. **Cronological Display Standard**: Consultas a cadastros secundários (como Disciplinas e Categorias) DEVEM utilizar `ORDER BY created_at ASC` no SQL para manter a mesma ordem de listagem original do sistema (evitando reordenação alfabética não-intencional). 42. **Cronological Display Standard**: Consultas a cadastros secundários (como Disciplinas e Categorias) DEVEM utilizar `ORDER BY created_at ASC` no SQL para manter a mesma ordem de listagem original do sistema (evitando reordenação alfabética não-intencional).
43. **Mass Contracts Update**: A edição de um "Modelo de Contrato" dispara atualizações em massa (PUT `/api/contratos/:id`) recalcularizando as variáveis de todos os contratos já emitidos para os alunos vinculados àquele modelo, garantindo atualização em tempo real no Portal do Aluno via SQL. 43. **Mass Contracts Update**: A edição de um "Modelo de Contrato" dispara atualizações em massa (PUT `/api/contratos/:id`) recalcularizando as variáveis de todos os contratos já emitidos para os alunos vinculados àquele modelo, garantindo atualização em tempo real no Portal do Aluno via SQL.
44. **Biometria SQL-First Parcial**: A biometria (faceDescriptor) já está preparada para persistência nativa na coluna jsonb 'face_descriptor' do PostgreSQL (com mapeamento camelCase na API). Contudo, o cadastro de alunos via Manager ainda não utiliza a API SQL em sua plenitude no handleSave. Até a refatoração do Students.tsx, os alunos novos continuarão dependendo do fallback do school_data.json. 44. **Biometria SQL-First Parcial**: A biometria (faceDescriptor) já está preparada para persistência nativa na coluna jsonb 'face_descriptor' do PostgreSQL (com mapeamento camelCase na API). Contudo, o cadastro de alunos via Manager ainda não utiliza a API SQL em sua plenitude no handleSave. Até a refatoração do Students.tsx, os alunos novos continuarão dependendo do fallback do school_data.json.
45. **Forçar Fuso Horário Brasileiro na Origem**: Ao gerar datas ou horários locais no frontend para envio ao banco PostgreSQL (como registros de biometria ou ponto), é OBRIGATÓRIO forçar o cálculo no fuso 'America/Sao_Paulo' (ex: \
ew Date().toLocaleString('en-US', { timeZone: 'America/Sao_Paulo' })\). Isso evita que instâncias rodando em UTC no Windows Server ou celulares com fuso errado enviem horários defasados, o que causaria quebra em lógicas de restrição de janela de tempo (ex: horário de aula).

View File

@ -252,13 +252,17 @@ const AttendanceCapture: React.FC<AttendanceCaptureProps> = ({ data, updateData
return; return;
} }
// Gerar string de data local para o banco de dados // Gerar string de data local forçando o fuso horário de Brasília (America/Sao_Paulo)
const localDateStr = nowLocal.getFullYear() + '-' + // Isso evita que dispositivos em UTC (como o servidor Windows) salvem horários +3 horas
String(nowLocal.getMonth() + 1).padStart(2, '0') + '-' + const brtString = new Date().toLocaleString('en-US', { timeZone: 'America/Sao_Paulo' });
String(nowLocal.getDate()).padStart(2, '0') + 'T' + const brtDate = new Date(brtString);
String(nowLocal.getHours()).padStart(2, '0') + ':' +
String(nowLocal.getMinutes()).padStart(2, '0') + ':' + const localDateStr = brtDate.getFullYear() + '-' +
String(nowLocal.getSeconds()).padStart(2, '0'); String(brtDate.getMonth() + 1).padStart(2, '0') + '-' +
String(brtDate.getDate()).padStart(2, '0') + 'T' +
String(brtDate.getHours()).padStart(2, '0') + ':' +
String(brtDate.getMinutes()).padStart(2, '0') + ':' +
String(brtDate.getSeconds()).padStart(2, '0');
// Limpar qualquer falta auto-gerada para o mesmo aluno nesta mesma aula/dia // Limpar qualquer falta auto-gerada para o mesmo aluno nesta mesma aula/dia
const filteredAttendance = (data.attendance || []).filter(a => { const filteredAttendance = (data.attendance || []).filter(a => {

View File

@ -835,7 +835,7 @@ export async function getFrequencias() {
classId: r.turma_id, classId: r.turma_id,
lessonId: r.aula_id, lessonId: r.aula_id,
date: r.formatted_data || r.data, date: r.formatted_data || r.data,
photo: r.foto, photo: r.foto_url || r.foto,
verified: r.verificado, verified: r.verificado,
type: r.tipo, type: r.tipo,
justification: r.justificativa, justification: r.justificativa,