diff --git a/public/app.js b/public/app.js index 8de8dd4..ffa036f 100644 --- a/public/app.js +++ b/public/app.js @@ -4713,9 +4713,566 @@ const initApp = () => { `); + }); + } + + // ============================================================ + // GARATUJAS (ANALISADOR DE TRAÇADO INFANTIL) + // ============================================================ + const garatujasModal = document.getElementById('garatujasModal'); + const btnCloseGaratujasModal = document.getElementById('btnCloseGaratujasModal'); + const barBtnGaratujas = document.getElementById('barBtnGaratujas'); + + const tabGaratujasNova = document.getElementById('tabGaratujasNova'); + const tabGaratujasHistorico = document.getElementById('tabGaratujasHistorico'); + const contentGaratujasNova = document.getElementById('contentGaratujasNova'); + const contentGaratujasHistorico = document.getElementById('contentGaratujasHistorico'); + + const garatujasAlunoInput = document.getElementById('garatujasAlunoInput'); + const garatujaFileInput = document.getElementById('garatujaFileInput'); + const btnUploadGaratuja = document.getElementById('btnUploadGaratuja'); + const garatujaFileStatus = document.getElementById('garatujaFileStatus'); + const garatujaPreviewContainer = document.getElementById('garatujaPreviewContainer'); + const garatujaImgPreview = document.getElementById('garatujaImgPreview'); + + const btnGenerateGaratuja = document.getElementById('btnGenerateGaratuja'); + const garatujaLoader = document.getElementById('garatujaLoader'); + const garatujaLoaderText = document.getElementById('garatujaLoaderText'); + const garatujaResultArea = document.getElementById('garatujaResultArea'); + const garatujaReportArea = document.getElementById('garatujaReportArea'); + + const btnCopyGaratujaReport = document.getElementById('btnCopyGaratujaReport'); + const btnPdfGaratujaReport = document.getElementById('btnPdfGaratujaReport'); + const garatujasListContainer = document.getElementById('garatujasListContainer'); + const garatujasNoData = document.getElementById('garatujasNoData'); + + let activeGaratujaReport = null; + + // Abas do Garatujas + if (tabGaratujasNova) { + tabGaratujasNova.addEventListener('click', () => { + tabGaratujasNova.classList.add('active'); + tabGaratujasHistorico.classList.remove('active'); + contentGaratujasNova.style.display = 'block'; + contentGaratujasHistorico.style.display = 'none'; + }); + } + + if (tabGaratujasHistorico) { + tabGaratujasHistorico.addEventListener('click', () => { + tabGaratujasHistorico.classList.add('active'); + tabGaratujasNova.classList.remove('active'); + contentGaratujasNova.style.display = 'none'; + contentGaratujasHistorico.style.display = 'block'; + loadGaratujasHistory(); + }); + } + + // Abrir Modal + if (barBtnGaratujas) { + barBtnGaratujas.addEventListener('click', () => { + garatujasModal.style.display = 'flex'; + garatujasAlunoInput.value = ''; + garatujaFileInput.value = ''; + garatujaFileStatus.textContent = 'Nenhum arquivo selecionado'; + garatujaPreviewContainer.style.display = 'none'; + garatujaResultArea.style.display = 'none'; + garatujaLoader.style.display = 'none'; + btnGenerateGaratuja.disabled = false; + activeGaratujaReport = null; + if (tabGaratujasNova) tabGaratujasNova.click(); + }); + } + + // Fechar Modal + if (btnCloseGaratujasModal) { + btnCloseGaratujasModal.addEventListener('click', () => { + garatujasModal.style.display = 'none'; + }); + } + + window.addEventListener('click', (e) => { + if (e.target === garatujasModal) { + garatujasModal.style.display = 'none'; + } + }); + + // Upload trigger + if (btnUploadGaratuja) { + btnUploadGaratuja.addEventListener('click', () => { + garatujaFileInput.click(); + }); + } + + if (garatujaFileInput) { + garatujaFileInput.addEventListener('change', () => { + const file = garatujaFileInput.files[0]; + if (file) { + garatujaFileStatus.textContent = file.name; + const reader = new FileReader(); + reader.onload = (e) => { + garatujaImgPreview.src = e.target.result; + garatujaPreviewContainer.style.display = 'block'; + }; + reader.readAsDataURL(file); + } else { + garatujaFileStatus.textContent = 'Nenhum arquivo selecionado'; + garatujaPreviewContainer.style.display = 'none'; + } + }); + } + + // Gerar Análise + if (btnGenerateGaratuja) { + btnGenerateGaratuja.addEventListener('click', async () => { + const alunoName = garatujasAlunoInput.value.trim(); + const file = garatujaFileInput.files[0]; + + if (!alunoName) { + alert('Por favor, informe o nome do aluno.'); + return; + } + if (!file) { + alert('Por favor, selecione a foto do desenho.'); + return; + } + + btnGenerateGaratuja.disabled = true; + garatujaLoader.style.display = 'flex'; + garatujaResultArea.style.display = 'none'; + garatujaLoaderText.textContent = 'Enviando imagem pedagógica...'; + + const formData = new FormData(); + formData.append('alunoName', alunoName); + formData.append('image', file); + + let statusMsgIdx = 0; + const statusMessages = [ + 'Carregando desenho infantil...', + 'IA escaneando os traços e formas...', + 'Analisando simetria e coordenação motora...', + 'Definindo estágio evolutivo do desenho...', + 'Criando sugestões de atividades formativas...', + 'Finalizando laudo psicopedagógico...' + ]; + + const statusInterval = setInterval(() => { + if (statusMsgIdx < statusMessages.length) { + garatujaLoaderText.textContent = statusMessages[statusMsgIdx]; + statusMsgIdx++; + } else { + garatujaLoaderText.textContent = 'Formatando parecer...'; + } + }, 4500); + + try { + const response = await fetch('/api/garatujas/analyze', { + method: 'POST', + body: formData + }); + + clearInterval(statusInterval); + + if (!response.ok) { + const errData = await response.json(); + throw new Error(errData.error || 'Erro desconhecido'); + } + + const data = await response.json(); + activeGaratujaReport = data; + + if (window.marked) { + garatujaReportArea.innerHTML = marked.parse(data.analise); + } else { + garatujaReportArea.textContent = data.analise; + } + + garatujaLoader.style.display = 'none'; + garatujaResultArea.style.display = 'flex'; + btnGenerateGaratuja.disabled = false; + + } catch (err) { + clearInterval(statusInterval); + console.error(err); + alert('Erro na análise: ' + err.message); + garatujaLoader.style.display = 'none'; + btnGenerateGaratuja.disabled = false; + } + }); + } + + // Copiar Parecer Garatujas + if (btnCopyGaratujaReport) { + btnCopyGaratujaReport.addEventListener('click', () => { + if (activeGaratujaReport && activeGaratujaReport.analise) { + navigator.clipboard.writeText(activeGaratujaReport.analise) + .then(() => { + btnCopyGaratujaReport.textContent = '✅ Copiado!'; + setTimeout(() => { btnCopyGaratujaReport.textContent = '📋 Copiar'; }, 2000); + }); + } + }); + } + + // Baixar PDF Garatujas + if (btnPdfGaratujaReport) { + btnPdfGaratujaReport.addEventListener('click', () => { + if (!activeGaratujaReport) return; + const printWindow = window.open('', '_blank'); + printWindow.document.write(` + + + Garatujas - Laudo do Desenho + + + +

🎨 Garatujas - Parecer de Traçado Infantil

+
+ +
+
Aluno(a): ${activeGaratujaReport.alunoName}
+
Data de Análise: ${new Date(activeGaratujaReport.createdAt).toLocaleDateString('pt-BR')}
+
+
+
${window.marked ? marked.parse(activeGaratujaReport.analise) : activeGaratujaReport.analise}
+ diff --git a/server.js b/server.js index ed9068f..1852e80 100644 --- a/server.js +++ b/server.js @@ -789,6 +789,254 @@ Retorne a resposta estruturada estritamente em Markdown. Separe as seções usan } }); +// ============================================================ +// ROTAS DO GARATUJAS (ANALISADOR DE TRAÇADO INFANTIL) +// ============================================================ +app.post('/api/garatujas/analyze', requireAuth, multerUpload.single('image'), async (req, res) => { + const { alunoName } = req.body; + const file = req.file; + + if (!alunoName) { + return res.status(400).json({ error: 'O nome do aluno é obrigatório.' }); + } + if (!file) { + return res.status(400).json({ error: 'A foto do desenho é obrigatória.' }); + } + + try { + const fs = require('fs'); + const mediaDir = path.join(__dirname, 'public', 'generated-media'); + if (!fs.existsSync(mediaDir)) { + fs.mkdirSync(mediaDir, { recursive: true }); + } + + const ext = path.extname(file.originalname) || '.png'; + const fileName = `garatuja_${Date.now()}${ext}`; + const filePath = path.join(mediaDir, fileName); + fs.writeFileSync(filePath, file.buffer); + const imageUrl = `/generated-media/${fileName}`; + + const base64Image = file.buffer.toString('base64'); + const mimeType = file.mimetype || 'image/png'; + + const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'google/gemini-2.5-flash', + messages: [ + { + role: 'user', + content: [ + { + type: 'text', + text: `Você é um psicopedagogo e especialista em desenvolvimento infantil no Brasil. +Analise a imagem deste desenho/rabisco feito pelo aluno(a) "${alunoName}" (até 6 anos de idade). + +Identifique e descreva com base na literatura científica (como Piaget, Luquet, Lowenfeld): +1. O estágio do desenho infantil em que a criança se encontra (ex: Garatuja Desordenada, Garatuja Ordenada, Pré-Esquematismo, etc.) justificando com elementos visuais identificados no desenho. +2. A análise do desenvolvimento cognitivo e da coordenação motora fina expressa nos traços. +3. Propostas práticas de estimulação pedagógica personalizadas para o professor aplicar com o aluno "${alunoName}" em sala de aula para avançar para o próximo estágio. + +Retorne a resposta estruturada em Markdown, usando títulos claros: +# Estágio do Desenho e Análise Psicomotora +[Sua análise aqui] + +# Diagnóstico de Desenvolvimento Cognitivo +[Seu diagnóstico aqui] + +# Sugestões de Atividades para Estimular ${alunoName} +[Suas propostas aqui]` + }, + { + type: 'image_url', + image_url: { + url: `data:${mimeType};base64,${base64Image}` + } + } + ] + } + ] + }) + }); + + if (!response.ok) { + const errText = await response.text(); + throw new Error(`Erro na API de visão: ${errText}`); + } + + const resData = await response.json(); + const analise = resData.choices?.[0]?.message?.content || ''; + + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + const dbRes = await dbPool.query( + `INSERT INTO escola.garatujas (usuario_id, aluno_name, image_url, analise) + VALUES ($1, $2, $3, $4) RETURNING id, created_at`, + [usuarioId, alunoName, imageUrl, analise] + ); + + res.json({ + success: true, + id: dbRes.rows[0].id, + alunoName, + imageUrl, + analise, + createdAt: dbRes.rows[0].created_at + }); + + } catch (err) { + console.error('Erro na análise de garatujas:', err); + res.status(500).json({ error: 'Erro ao analisar imagem: ' + err.message }); + } +}); + +app.get('/api/garatujas/list', requireAuth, async (req, res) => { + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + try { + const dbRes = await dbPool.query( + `SELECT id, aluno_name as "alunoName", image_url as "imageUrl", created_at as "createdAt" + FROM escola.garatujas + WHERE usuario_id = $1 + ORDER BY created_at DESC`, + [usuarioId] + ); + res.json(dbRes.rows); + } catch (err) { + console.error('Erro ao listar garatujas:', err); + res.status(500).json({ error: 'Erro ao obter histórico de desenhos.' }); + } +}); + +app.get('/api/garatujas/:id', requireAuth, async (req, res) => { + const { id } = req.params; + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + try { + const dbRes = await dbPool.query( + `SELECT id, aluno_name as "alunoName", image_url as "imageUrl", analise, created_at as "createdAt" + FROM escola.garatujas + WHERE id = $1 AND usuario_id = $2`, + [id, usuarioId] + ); + if (dbRes.rows.length === 0) { + return res.status(404).json({ error: 'Análise não encontrada.' }); + } + res.json(dbRes.rows[0]); + } catch (err) { + console.error('Erro ao buscar garatuja:', err); + res.status(500).json({ error: 'Erro ao buscar análise.' }); + } +}); + +// ============================================================ +// ROTAS DO BRINCARATIVO (CIRCUITOS PSICOMOTORES E BRINCADEIRAS) +// ============================================================ +app.post('/api/brincar-ativo/generate', requireAuth, async (req, res) => { + const { faixaEtaria, recursos } = req.body; + + if (!faixaEtaria) { + return res.status(400).json({ error: 'A faixa etária é obrigatória.' }); + } + if (!recursos) { + return res.status(400).json({ error: 'A descrição dos recursos é obrigatória.' }); + } + + try { + const prompt = `Você é um educador físico e pedagogo especializado na Educação Infantil (crianças de 0 a 6 anos) de acordo com a BNCC no Brasil. +Gere uma proposta de circuito psicomotor ou brincadeira ativa adequada para a seguinte faixa etária e com os recursos disponíveis: + +Faixa Etária: ${faixaEtaria} +Recursos e Materiais Disponíveis: ${recursos} + +Instruções para o conteúdo: +1. Dê um título lúdico e atraente para a atividade. +2. Especifique os Objetivos de Aprendizagem e Desenvolvimento da BNCC (incluindo códigos, como EI01CG02, EI02CG05, etc.). +3. Descreva o Passo a Passo detalhado de como montar o circuito/brincadeira no espaço e como guiar as crianças. +4. Diga o que o professor deve observar durante a atividade para avaliar o desenvolvimento psicomotor dos alunos. + +Retorne a resposta estritamente em Markdown estruturado: +# Atividade: [Nome Lúdico] +**Faixa Etária:** ${faixaEtaria} +**Campos de Experiência (BNCC):** [Campos e Códigos] + +## 📋 Objetivos +[Objetivos] + +## 🛠️ Como Montar e Conduzir +[Passo a passo] + +## 🔍 O que Observar (Avaliação Formativa) +[Dicas de observação]`; + + const aiResponse = await callMinimax({ + system: "Você é um especialista em educação física e psicomotricidade na educação infantil brasileira.", + messages: [{ role: 'user', content: prompt }], + temperature: 0.5, + max_tokens: 3000 + }); + + const atividade = aiResponse.text || ''; + + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + const dbRes = await dbPool.query( + `INSERT INTO escola.brincar_ativo (usuario_id, faixa_etaria, recursos, atividade) + VALUES ($1, $2, $3, $4) RETURNING id, created_at`, + [usuarioId, faixaEtaria, recursos, atividade] + ); + + res.json({ + success: true, + id: dbRes.rows[0].id, + faixaEtaria, + recursos, + atividade, + createdAt: dbRes.rows[0].created_at + }); + } catch (err) { + console.error('Erro no BrincarAtivo:', err); + res.status(500).json({ error: 'Erro ao gerar atividade com a IA: ' + err.message }); + } +}); + +app.get('/api/brincar-ativo/list', requireAuth, async (req, res) => { + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + try { + const dbRes = await dbPool.query( + `SELECT id, faixa_etaria as "faixaEtaria", recursos, created_at as "createdAt" + FROM escola.brincar_ativo + WHERE usuario_id = $1 + ORDER BY created_at DESC`, + [usuarioId] + ); + res.json(dbRes.rows); + } catch (err) { + console.error('Erro ao listar atividades:', err); + res.status(500).json({ error: 'Erro ao obter lista de atividades.' }); + } +}); + +app.get('/api/brincar-ativo/:id', requireAuth, async (req, res) => { + const { id } = req.params; + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + try { + const dbRes = await dbPool.query( + `SELECT id, faixa_etaria as "faixaEtaria", recursos, atividade, created_at as "createdAt" + FROM escola.brincar_ativo + WHERE id = $1 AND usuario_id = $2`, + [id, usuarioId] + ); + if (dbRes.rows.length === 0) { + return res.status(404).json({ error: 'Atividade não encontrada.' }); + } + res.json(dbRes.rows[0]); + } catch (err) { + console.error('Erro ao buscar atividade:', err); + res.status(500).json({ error: 'Erro ao buscar atividade.' }); + } +}); + // Rota GET para obter configurações do agente app.get('/api/agent-config', requireAuth, (req, res) => { const config = readAgentConfig(); @@ -968,6 +1216,26 @@ async function initDatabase() { descricao_fisica_en TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); + + -- Garatujas (Analisador de Traçado Infantil) + CREATE TABLE IF NOT EXISTS escola.garatujas ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + aluno_name VARCHAR(255) NOT NULL, + image_url TEXT NOT NULL, + analise TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + ); + + -- BrincarAtivo (Circuitos Psicomotores e Brincadeiras) + CREATE TABLE IF NOT EXISTS escola.brincar_ativo ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + faixa_etaria VARCHAR(100) NOT NULL, + recursos TEXT NOT NULL, + atividade TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + ); `); // Garantir que a coluna character_description_english exista na tabela projetos_comics