diff --git a/GEMINI.md b/GEMINI.md index 5b454ea..f2a2ed2 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,37 +1,37 @@ # EduManager - Regras Globais e Escopo -## 🚀 Escopo do Projeto -**EduManager** é um Sistema de Gestão Escolar completo, focado em alta performance, usabilidade premium e automação de processos administrativos e acadêmicos. +## 🚀 Escopo do Projeto +**EduManager** é um Sistema de Gestão Escolar completo, focado em alta performance, usabilidade premium e automação de processos administrativos e acadêmicos. -## 🛠️ Stack Tecnológica +## 🛠ï¸� Stack Tecnológica - **Frontend/Backend:** Remix (React) - **Banco de Dados:** PostgreSQL (100% Local/Self-Hosted) - **Storage Architecture**: 100% Self-Hosted (MinIO) - Decoupled from Supabase Cloud. - **Image Serving**: All images are served via a backend proxy route (`/storage/:bucket/:key`) to ensure cross-origin compatibility and security. - **Synchronization**: High-performance local API for bank reconciliation (Asaas). -- **Orquestração e CI/CD:** Portainer (Docker) com GitHub Actions via Self-Hosted Runner (Oracle ARM64 nativo) e Watchtower. +- **Orquestração e CI/CD:** Portainer (Docker) com GitHub Actions via Self-Hosted Runner (Oracle ARM64 nativo) e Watchtower. - **Production Entry Point**: All production deployments MUST use `server.selfhosted.js` renamed/copied as `server.js` in the Docker containers to ensure full local feature availability. -## ⚠️ Regras de Negócio Críticas (MANDATÓRIO) +## âš ï¸� Regras de Negócio Críticas (MANDATÓRIO) > [!IMPORTANT] -> **Migração de Dados (Legado 'schoodat'):** -> Ao realizar a migração completa dos dados do sistema legado 'schoodat' para o nosso banco de dados local Postgres, **é terminantemente proibido alterar, resetar ou re-hashear as senhas existentes.** -> As credenciais devem ser mantidas exatamente como estão para garantir que o acesso dos usuários não seja interrompido. +> **Migração de Dados (Legado 'schoodat'):** +> Ao realizar a migração completa dos dados do sistema legado 'schoodat' para o nosso banco de dados local Postgres, **é terminantemente proibido alterar, resetar ou re-hashear as senhas existentes.** +> As credenciais devem ser mantidas exatamente como estão para garantir que o acesso dos usuários não seja interrompido. -## 🚨 Regras de Fluxo de Trabalho (CRÍTICO) +## 🚨 Regras de Fluxo de Trabalho (CRÃ�TICO) > [!CAUTION] -> **Git Push Proibido Sem Demanda Explícita:** -> NUNCA execute `git add`, `git commit` ou `git push` sem que o USUÁRIO solicite explicitamente. Alterações devem ser feitas nos arquivos, mas o envio ao repositório remoto é uma ação exclusiva do usuário. Aguarde sempre o comando direto do usuário para realizar qualquer operação de versionamento. -> **ESTA REGRA É INVIOLÁVEL E O ASSISTENTE JÁ FALHOU NELA EM 01/05/2026. NÃO REPITA O ERRO.** +> **Git Push Proibido Sem Demanda Explícita:** +> NUNCA execute `git add`, `git commit` ou `git push` sem que o USUÃ�RIO solicite explicitamente. Alterações devem ser feitas nos arquivos, mas o envio ao repositório remoto é uma ação exclusiva do usuário. Aguarde sempre o comando direto do usuário para realizar qualquer operação de versionamento. +> **ESTA REGRA É INVIOLÃ�VEL E O ASSISTENTE JÃ� FALHOU NELA EM 01/05/2026. NÃO REPITA O ERRO.** -## 📜 Padrões de Desenvolvimento -1. **Design System:** Estética Premium, Dark Mode por padrão (ou glassmorphism), micro-animações e ausência de placeholders. -2. **Segurança:** Todas as rotas sensíveis devem validar o token JWT local (via secrets do ambiente). Proibido usar Supabase SDK para lógica de autenticação ou sincronização no frontend. -3. **Resiliência:** Tratamento rigoroso de erros em chamadas de API de terceiros (Asaas, Evolution API). +## 📜 Padrões de Desenvolvimento +1. **Design System:** Estética Premium, Dark Mode por padrão (ou glassmorphism), micro-animações e ausência de placeholders. +2. **Segurança:** Todas as rotas sensíveis devem validar o token JWT local (via secrets do ambiente). Proibido usar Supabase SDK para lógica de autenticação ou sincronização no frontend. +3. **Resiliência:** Tratamento rigoroso de erros em chamadas de API de terceiros (Asaas, Evolution API). 4. **Upload de Arquivos:** Proibido o uso de Base64 para envio de novos arquivos ao servidor. Use obrigatoriamente `FormData` e envie o objeto `File/Blob` para as rotas de API que integram com o MinIO. -5. **Build & Deploy Stability:** O pipeline de deploy deve obrigatoriamente utilizar `runs-on: self-hosted` e compilar apenas a plataforma `linux/arm64` (sem emulação QEMU). A atualização da stack em produção deve ser automatizada via container transiente do Watchtower. +5. **Build & Deploy Stability:** O pipeline de deploy deve obrigatoriamente utilizar `runs-on: self-hosted` e compilar apenas a plataforma `linux/arm64` (sem emulação QEMU). A atualização da stack em produção deve ser automatizada via container transiente do Watchtower. 6. **Express Compatibility**: Avoid using raw `/*` wildcards in Express 5 routes; use Regex paths (`/^\/route\/(.+)$/`) for compatibility with `path-to-regexp` v8. 7. **Frontend Independence**: NEVER import files from `services/` or `server.js` directly into React components to prevent Node.js/SDK leakage (causes White Screen). Physical isolation is enforced: backend-only services (like MinIO/S3 storage) MUST stay outside the `src/` directory in Vite/React projects. Use `helpers.ts` for UI logic and standard `fetch` for API calls. 8. **Login Persistence**: Administrative sessions are persisted via `localStorage` ('edumanager_session'). The main entry point MUST validate the session on mount to ensure UX continuity. @@ -42,12 +42,13 @@ 13. **Grading & Evaluation Standards**: Assessments are categorized as `exam` (violet/violet labels) or `activity` (sky/blue labels). The report card (Boletim) MUST support multiple evaluations per period, showing the individual breakdown (name and value). The module MUST be named **"Atividades e Provas"** for clarity. 14. **Asaas Safety**: All financial generation forms MUST implement a loading state (`isCreating`) to disable submit buttons and prevent duplicate charges. 15. **Retake Policy**: Students ARE allowed to retake activities and exams. The system MUST implement a "Lock" (Cadeado) logic: if locked, the retake button is hidden; if unlocked, the button is visible and the new submission overwrites the previous grade. -16. **Financial Categories**: The system supports categories: `monthly` (Mensalidade), `registration` (Matrícula), `handout` (Apostila), and `others` (Outros). Automated forms must map references (Cursos -> monthly, Registration -> registration, Handout -> handout). +16. **Financial Categories**: The system supports categories: `monthly` (Mensalidade), `registration` (Matrícula), `handout` (Apostila), and `others` (Outros). Automated forms must map references (Cursos -> monthly, Registration -> registration, Handout -> handout). 17. **Modal Floating Principle**: All system modals must avoid backdrop-blur and background overlays. Use `bg-transparent` for the fixed container and `bg-white` (solid) for the modal box, ensuring contrast via large soft shadows (`shadow-2xl` or equivalent). 18. **Automated Messaging (Cron Jobs)**: The system uses `node-cron` for independent message scheduling (Preventive vs. Overdue). Overdue logic MUST implement safety checks using `overdue_warnings_count` and `last_overdue_warning_at` to avoid spamming the student. Immediate webhook triggers for `PAYMENT_OVERDUE` are disabled in favor of scheduled routines. 19. **Numerical Data Integrity**: When retrieving data from PostgreSQL `NUMERIC` or `DECIMAL` columns, values MUST be explicitly cast to `Number()` in the backend before being sent to the frontend to prevent crashes when using `.toFixed()` in React. -20. **Exam Duplication**: The system supports cloning evaluations. Duplicated exams MUST default to `draft` status and include `(Cópia)` in the title to allow verification before deployment to new classes. +20. **Exam Duplication**: The system supports cloning evaluations. Duplicated exams MUST default to `draft` status and include `(Cópia)` in the title to allow verification before deployment to new classes. 21. **Automatic Attendance Closure**: The system implements an automatic closure routine (`processAutoAbsences`) that generates physical "Absence" records in the PostgreSQL database for any past lesson where a student has no presence or justification. This routine is triggered during data save operations to ensure retroactive consistency between lessons and records. +22. **Biometric Data Preservation**: In PostgreSQL sync operations (`syncJsonToRelationalTables`), the `face_descriptor` column MUST ALWAYS use `COALESCE(EXCLUDED.face_descriptor, alunos.face_descriptor)` during updates. This prevents facial biometrics and photos from being overwritten or nullified by legacy JSON sync operations. 22. **Unified Notification System (SQL)**: The system uses the `notificacoes` PostgreSQL table as the single source of truth for both Portal and Manager. JSON-based notifications in `school_data` are deprecated. New features (exams, justifications) MUST use SQL INSERT/SELECT and implement **Intelligent Polling (30s)** in the UI to ensure synchronization. 23. **Soft Delete Policy (Lixeira)**: Evaluations (exams/activities) MUST NOT be hard-deleted to preserve grade history. Use the `isDeleted` flag to hide them from students and active management views, keeping them accessible in the "Lixeira" for restoration and ensuring reference integrity in the Report Card. 24. **Grading Arithmetic Logic**: All grade averages (Period, Subject, and General) MUST be calculated as arithmetic means (Average of Means). Summing grades for a final period result is prohibited to ensure consistency between Portal and Manager. @@ -59,18 +60,18 @@ 30. **Financial Data Integrity**: Automation routines MUST prioritize `school_data.payments` (JSON) as the trigger source while using `alunos_cobrancas` (SQL) for tracking notification counters (spam control), ensuring 100% parity with the management UI. 31. **Real-Time Bidirectional Sync**: Asaas webhooks MUST update both the PostgreSQL `alunos_cobrancas` table and the legacy `school_data.json` file in real-time to ensure immediate UI feedback in the administrative panel. 32. **Numerical Type Enforcement**: When serving data from PostgreSQL `NUMERIC` columns, the backend MUST map the result to ensure amounts are sent as `Number` (not String) to prevent string concatenation bugs in the frontend. -33. **Reverse Sync Requirement (SQL -> JSON) e (JSON -> SQL)**: Para módulos em fase de transição híbrida (como Aulas e Contratos), o sistema implementa a sincronização de Mão Dupla no `syncJsonToRelationalTables`. O frontend Manager invoca a API SQL mas também atualiza o contexto. O backend detecta a gravação e roda INSERTs ON CONFLICT para popular o PostgreSQL. +33. **Reverse Sync Requirement (SQL -> JSON) e (JSON -> SQL)**: Para módulos em fase de transição híbrida (como Aulas e Contratos), o sistema implementa a sincronização de Mão Dupla no `syncJsonToRelationalTables`. O frontend Manager invoca a API SQL mas também atualiza o contexto. O backend detecta a gravação e roda INSERTs ON CONFLICT para popular o PostgreSQL. 34. **Deletion & Notification Order (Async)**: When processing deletions that trigger notifications (e.g., WhatsApp via Asaas Webhook), local database deletion MUST happen only AFTER the notification dispatch. Manual deletion routes should only trigger the external API delete and wait for the webhook to finalize local cleanup, ensuring data availability for message variables. 35. **Mass Send Standard (V3)**: The mass send feature MUST use the student's or guardian's first name for the {nome} variable (via `.split(' ')[0]`). It MUST support dual dispatch (sending to both student and guardian if phones are distinct) and allow attachments (Image/PDF) handled via `multipart/form-data` and the Evolution API `sendMedia` endpoint. -36. **Frequency Visibility & Justification Tracking**: All attendance records of type `absence` MUST be excluded from the "Presence Time" calculation in the Portal, showing a dash (`—`). Justifications MUST store the `submittedAt` timestamp in both SQL and JSON formats to allow auditing and clear display of submission time in the UI. +36. **Frequency Visibility & Justification Tracking**: All attendance records of type `absence` MUST be excluded from the "Presence Time" calculation in the Portal, showing a dash (`—`). Justifications MUST store the `submittedAt` timestamp in both SQL and JSON formats to allow auditing and clear display of submission time in the UI. 37. **Server Entry Point Safety**: The original `server.js` files in both Manager and Portal are OBSOLETE and kept only for historical context. You MUST NEVER modify or edit `server.js`. All backend changes must be applied exclusively to `server.selfhosted.js`. 38. **Database Connection & MCP Integration**: Direct PostgreSQL access has been successfully configured via the `@modelcontextprotocol/server-postgres` MCP server to connect directly to the production database on the VPS (`150.230.87.131`). Database telemetry and operations can be executed via MCP SQL tools. 39. **Portal SQL-First Migration**: O Portal do Aluno consome dados majoritariamente de tabelas PostgreSQL (`alunos`, `aulas`, `frequencias`, `contratos`) usando `SELECT` e implementa Fallbacks legados lendo do JSON apenas se o BD retornar vazio. -40. **CamelCase Backend Mapping**: Funções do backend que buscam dados do PostgreSQL (`getAlunos`, `getProvas`, `getFuncionarios`) DEVEM mapear as chaves de `snake_case` (ex: `turma_id`, `is_deleted`) para `camelCase` (ex: `classId`, `isDeleted`) antes de enviar para o Frontend. O Frontend deve usar fallbacks (`p.classId || p.turma_id`) durante a fase de transição para evitar quebras em views legadas (White Screen) e perdas de filtro de contagem (ex: "0 alunos"). -41. **Database Schema Sync**: Sempre garanta que as tabelas de destino possuam colunas como `is_deleted` e demais modificadores antes de habilitar rotas PUT/DELETE, pois a ausência do schema falhará silenciosamente no Express 5. -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. -44. **Biometria SQL-First Parcial**: A biometria (faceDescriptor) j est preparada para persistncia nativa na coluna jsonb 'face_descriptor' do PostgreSQL (com mapeamento camelCase na API). Contudo, o cadastro de alunos via Manager ainda no utiliza a API SQL em sua plenitude no handleSave. At a refatorao do Students.tsx, os alunos novos continuaro dependendo do fallback do school_data.json. -45. **Forar Fuso Horrio Brasileiro na Origem**: Ao gerar datas ou horrios locais no frontend para envio ao banco PostgreSQL (como registros de biometria ou ponto), OBRIGATRIO forar o clculo no fuso 'America/Sao_Paulo' (ex: \ -ew Date().toLocaleString('en-US', { timeZone: 'America/Sao_Paulo' })\). Isso evita que instncias rodando em UTC no Windows Server ou celulares com fuso errado enviem horrios defasados, o que causaria quebra em lgicas de restrio de janela de tempo (ex: horrio de aula). +40. **CamelCase Backend Mapping**: Funções do backend que buscam dados do PostgreSQL (`getAlunos`, `getProvas`, `getFuncionarios`) DEVEM mapear as chaves de `snake_case` (ex: `turma_id`, `is_deleted`) para `camelCase` (ex: `classId`, `isDeleted`) antes de enviar para o Frontend. O Frontend deve usar fallbacks (`p.classId || p.turma_id`) durante a fase de transição para evitar quebras em views legadas (White Screen) e perdas de filtro de contagem (ex: "0 alunos"). +41. **Database Schema Sync**: Sempre garanta que as tabelas de destino possuam colunas como `is_deleted` e demais modificadores antes de habilitar rotas PUT/DELETE, pois a ausência do schema falhará silenciosamente no Express 5. +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. +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). \ No newline at end of file diff --git a/MEMORY.md b/MEMORY.md index 57c47ae..654d7f6 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -6,10 +6,11 @@ Nesta sessão de trabalho, realizamos o "core" da transição do sistema EduMana ## Módulos Migrados ### Fase 4: Gestão de Alunos e Autenticação -- **Backend Manager**: CRUD no `database.js` para tabela `alunos`, rotas `/api/alunos` no `server.selfhosted.js`. -- **Frontend Manager (`Students.tsx`)**: Refatorado para buscar via `fetch('/api/alunos')`. +- **Backend Manager**: CRUD no `database.js` para tabela `alunos`, com rotas `/api/alunos` e `/api/alunos/:id/rematricular` no `server.selfhosted.js` que executam operações no PostgreSQL e realizam sincronização reversa em tempo real no legado `school_data.json` para total compatibilidade legacy. +- **Frontend Manager (`Students.tsx`)**: Completamente refatorado para ler e gravar diretamente nas rotas relacionais `/api/alunos`, eliminando 100% das chamadas `dbService.saveData` nesta tela, mantendo apenas o estado em memória sincronizado para continuidade UX. - **Portal do Aluno**: Login (`/api/portal/login`) e perfil (`/api/portal/me`) reescritos para consultar a tabela `alunos` no PostgreSQL. + ### Fase 5: Avaliações e Provas - Backend e Frontend conectados às tabelas `provas` e `questoes_provas`. diff --git a/manager/components/Students.tsx b/manager/components/Students.tsx index 51c5ad8..d518378 100644 --- a/manager/components/Students.tsx +++ b/manager/components/Students.tsx @@ -849,7 +849,7 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId s.id === editingStudent.id ? studentToSave : s ); } else { - updatedStudents = [...data.students, studentToSave]; + updatedStudents = [...dbStudents, studentToSave]; } // Process Generate Fee and Contract @@ -969,7 +969,23 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId }; updateData(newData); - dbService.saveData({ ...data, ...newData }); + const isNew = !editingStudent; + const endpoint = isNew ? '/api/alunos' : `/api/alunos/${studentToSave.id}`; + const method = isNew ? 'POST' : 'PUT'; + + const saveResponse = await fetch(endpoint, { + method, + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(studentToSave) + }); + + if (!saveResponse.ok) { + throw new Error('Falha ao salvar o aluno no banco de dados'); + } + + await loadStudents(); showAlert('Sucesso', (formData as any).generateFee ? 'Aluno salvo e nova cobrança gerada com sucesso.' : 'Aluno salvo com sucesso.', 'success'); closeModal(); } catch (error) { @@ -997,7 +1013,27 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId ); updateData({ students: updatedStudents }); - dbService.saveData({ ...data, students: updatedStudents }); + try { + const studentToUpdate = { + ...showDeleteModal, + status: 'cancelled' as const, + cancellationReason, + classId: '' + }; + const response = await fetch(`/api/alunos/${showDeleteModal.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(studentToUpdate) + }); + if (!response.ok) { + throw new Error('Falha ao cancelar matrícula no servidor'); + } + await loadStudents(); + } catch (err) { + console.error(err); + showAlert('Erro', 'Falha ao cancelar a matrícula no servidor.', 'error'); + return; + } if (generatePDF) { await pdfService.generateCancellationTermPDF(showDeleteModal, data, cancellationReason); @@ -1030,7 +1066,7 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId ); updateData({ students: updatedStudents }); - dbService.saveData({ ...data, students: updatedStudents }); + await loadStudents(); showAlert('Sucesso', 'Aluno rematriculado com sucesso.', 'success'); } catch (error) { @@ -1061,7 +1097,21 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId }; updateData(newData); - dbService.saveData({ ...data, ...newData }); + try { + const response = await fetch(`/api/alunos/${student.id}`, { + method: 'DELETE' + }); + + if (!response.ok) { + throw new Error('Falha ao deletar aluno no servidor'); + } + + await loadStudents(); + } catch (err) { + console.error(err); + showAlert('Erro', 'Ocorreu um erro ao excluir o aluno do servidor.', 'error'); + return; + } showAlert('Sucesso', 'O aluno e todo o seu histórico foram removidos permanentemente.', 'success'); } ); @@ -1074,7 +1124,25 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId s.id === transferringStudent.id ? { ...s, classId: newClassId } : s ); updateData({ students: updatedStudents }); - dbService.saveData({ ...data, students: updatedStudents }); + try { + const studentToUpdate = { + ...transferringStudent, + classId: newClassId + }; + const response = await fetch(`/api/alunos/${transferringStudent.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(studentToUpdate) + }); + if (!response.ok) { + throw new Error('Falha ao transferir aluno no servidor'); + } + await loadStudents(); + } catch (err) { + console.error(err); + showAlert('Erro', 'Ocorreu um erro ao transferir o aluno no servidor.', 'error'); + return; + } showAlert('Sucesso', 'Aluno transferido com sucesso.', 'success'); closeTransferModal(); }; @@ -1117,7 +1185,7 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId setShowModal(true); }; - const filteredStudents = data.students + const filteredStudents = dbStudents .filter(s => { const matchesSearch = (s.name || '').toLowerCase().includes((searchTerm || '').toLowerCase()) || (s.cpf || '').includes(searchTerm) || @@ -1243,7 +1311,7 @@ const Students: React.FC = ({ data, updateData, deepLinkStudentId })} {/* Card for students without class */} - {data.students.some(s => !s.classId && (activeTab === 'active' ? s.status !== 'cancelled' : s.status === 'cancelled')) && ( + {dbStudents.some(s => !s.classId && (activeTab === 'active' ? s.status !== 'cancelled' : s.status === 'cancelled')) && (