diff --git a/Gemfile b/Gemfile
index 1ff1bafae8..5aaf96239b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -67,3 +67,5 @@ end
gem "tailwindcss-rails", "~> 4.4"
gem "rspec-rails", "~> 8.0", groups: [:development, :test]
+
+gem "csv", "~> 3.3"
diff --git a/Gemfile.lock b/Gemfile.lock
index e57f6437fc..6422dd0a68 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -98,6 +98,7 @@ GEM
concurrent-ruby (1.3.5)
connection_pool (2.5.4)
crass (1.0.6)
+ csv (3.3.5)
cucumber (10.1.1)
base64 (~> 0.2)
builder (~> 3.2)
@@ -439,6 +440,7 @@ DEPENDENCIES
bootsnap
brakeman
capybara
+ csv (~> 3.3)
cucumber-rails
database_cleaner
debug
diff --git a/app/controllers/avaliacoes_controller.rb b/app/controllers/avaliacoes_controller.rb
index 8b2fe38e4f..12340a6b2e 100644
--- a/app/controllers/avaliacoes_controller.rb
+++ b/app/controllers/avaliacoes_controller.rb
@@ -38,4 +38,25 @@ def create
redirect_to gestao_envios_avaliacoes_path, alert: "Erro ao criar avaliação: #{@avaliacao.errors.full_messages.join(', ')}"
end
end
+
+ def resultados
+ @avaliacao = Avaliacao.find(params[:id])
+ # Pré-carrega dependências para evitar N+1.
+ # Nota: a associação 'respostas' existe no Modelo, mesmo que a tabela esteja pendente.
+ # Usamos array vazio como fallback por segurança se o BD falhar.
+ begin
+ @respostas = @avaliacao.respostas.includes(:aluno)
+ rescue ActiveRecord::StatementInvalid
+ @respostas = []
+ flash.now[:alert] = "A tabela de respostas ainda não está disponível."
+ end
+
+ respond_to do |format|
+ format.html
+ format.csv do
+ send_data CsvFormatterService.new(@avaliacao).generate,
+ filename: "resultados-avaliacao-#{@avaliacao.id}-#{Date.today}.csv"
+ end
+ end
+ end
end
diff --git a/app/models/avaliacao.rb b/app/models/avaliacao.rb
index 6e8cd2f838..384c274692 100644
--- a/app/models/avaliacao.rb
+++ b/app/models/avaliacao.rb
@@ -2,4 +2,5 @@ class Avaliacao < ApplicationRecord
belongs_to :turma
belongs_to :modelo
belongs_to :professor_alvo, class_name: 'User', optional: true
+ has_many :respostas
end
diff --git a/app/models/resposta.rb b/app/models/resposta.rb
index 5bd07d1d29..92dd79310f 100644
--- a/app/models/resposta.rb
+++ b/app/models/resposta.rb
@@ -3,6 +3,7 @@ class Resposta < ApplicationRecord
belongs_to :aluno, class_name: "User"
belongs_to :formulario
belongs_to :questao
+ belongs_to :avaliacao, optional: true
validates :aluno_id, presence: true
validates :formulario_id, presence: true
diff --git a/app/services/csv_formatter_service.rb b/app/services/csv_formatter_service.rb
new file mode 100644
index 0000000000..ca56214c3b
--- /dev/null
+++ b/app/services/csv_formatter_service.rb
@@ -0,0 +1,45 @@
+require 'csv'
+
+class CsvFormatterService
+ def initialize(avaliacao)
+ @avaliacao = avaliacao
+ end
+
+ def generate
+ CSV.generate(headers: true) do |csv|
+ csv << headers
+
+ @avaliacao.respostas.includes(:aluno).group_by(&:aluno).each do |aluno, respostas|
+ row = [aluno.matricula, aluno.nome]
+
+ # Organiza as respostas pela ordem das questões se possível, ou mapeamento simples
+ # Assumindo que queremos mapear questões para colunas
+
+ # Para este MVP, vamos apenas despejar o conteúdo na ordem das questões encontradas
+ # Uma solução mais robusta ordenaria por ID da questão ou número
+
+ respostas.each do |resposta|
+ row << resposta.conteudo
+ end
+
+ csv << row
+ end
+ end
+ end
+
+ private
+
+ def headers
+ # Cabeçalhos estáticos para informações do Aluno
+ base_headers = ["Matrícula", "Nome"]
+
+ # Cabeçalhos dinâmicos para questões
+ # Identificando questões únicas respondidas ou todas as questões do modelo
+ # Para o MVP, vamos assumir que queremos todas as questões do modelo
+
+ questoes = @avaliacao.modelo.perguntas
+ question_headers = questoes.map.with_index { |q, i| "Questão #{i + 1}" }
+
+ base_headers + question_headers
+ end
+end
diff --git a/app/views/avaliacoes/gestao_envios.html.erb b/app/views/avaliacoes/gestao_envios.html.erb
index 89c9c80484..c26f95dd4f 100644
--- a/app/views/avaliacoes/gestao_envios.html.erb
+++ b/app/views/avaliacoes/gestao_envios.html.erb
@@ -57,6 +57,12 @@
%>
<%= f.submit "Gerar Avaliação", class: "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline transition duration-150 ease-in-out cursor-pointer" %>
<% end %>
+
+ <% if (ultima_avaliacao = turma.avaliacoes.last) %>
+
+ <%= link_to "Ver Resultados (Última)", resultados_avaliacao_path(ultima_avaliacao), class: "text-blue-500 hover:text-blue-800 text-xs font-semibold" %>
+
+ <% end %>
<% end %>
diff --git a/app/views/avaliacoes/resultados.html.erb b/app/views/avaliacoes/resultados.html.erb
new file mode 100644
index 0000000000..3f17134a77
--- /dev/null
+++ b/app/views/avaliacoes/resultados.html.erb
@@ -0,0 +1,48 @@
+
+
+
Resultados da Avaliação
+ <%= link_to "Voltar", gestao_envios_avaliacoes_path, class: "bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded" %>
+
+
+
+
+
Turma: <%= @avaliacao.turma.codigo %> - <%= @avaliacao.turma.nome %>
+
Template: <%= @avaliacao.modelo.titulo %>
+
+
+ <% if @respostas.any? %>
+
+ <%= link_to "Download CSV", resultados_avaliacao_path(@avaliacao, format: :csv), class: "bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" %>
+
+
+
+
+
+
+ | Matrícula |
+ Aluno |
+ Conteúdo |
+
+
+
+ <% @respostas.each do |resposta| %>
+
+ | <%= resposta.aluno&.matricula %> |
+ <%= resposta.aluno&.nome %> |
+ <%= resposta.conteudo %> |
+
+ <% end %>
+
+
+
+ <% else %>
+
+
Atenção
+
Nenhuma resposta encontrada para esta avaliação.
+ <% if flash[:alert] %>
+
<%= flash[:alert] %>
+ <% end %>
+
+ <% end %>
+
+
diff --git a/config/routes.rb b/config/routes.rb
index 5caf2531a4..71be84da79 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -4,6 +4,9 @@
collection do
get :gestao_envios
end
+ member do
+ get :resultados
+ end
end
resource :session
diff --git a/db/seeds.rb b/db/seeds.rb
index 47e62bdf56..a54d07ce7d 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,7 +1,33 @@
-# This file should ensure the existence of records required to run the application in every environment (production,
-# development, test). The code here should be idempotent so that it can be executed at any point in every environment.
-# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
+# Admin User
+admin = User.find_or_create_by!(login: 'admin') do |u|
+ u.email_address = 'admin@camaar.unb.br'
+ u.nome = 'Administrador'
+ u.matricula = '000000000'
+ u.password = 'password'
+ u.password_confirmation = 'password'
+ u.eh_admin = true
+end
+puts "Usuário Admin garantido (ID: #{admin.id})"
-Modelo.find_or_create_by!(titulo: "Template Padrão") do |m|
+# Template Padrão
+modelo = Modelo.find_or_create_by!(titulo: 'Template Padrão') do |m|
m.ativo = true
end
+puts "Modelo 'Template Padrão' garantido (ID: #{modelo.id})"
+
+# Perguntas do Template Padrão
+perguntas_data = [
+ { enunciado: 'O professor demonstrou domínio do conteúdo?', tipo: 'escala', opcoes: { min: 1, max: 5 } },
+ { enunciado: 'O plano de ensino foi seguido?', tipo: 'escala', opcoes: { min: 1, max: 5 } },
+ { enunciado: 'Como você avalia a didática do professor?', tipo: 'escala', opcoes: { min: 1, max: 5 } },
+ { enunciado: 'Pontos positivos:', tipo: 'texto', opcoes: {} },
+ { enunciado: 'Pontos a melhorar:', tipo: 'texto', opcoes: {} }
+]
+
+perguntas_data.each do |p_data|
+ Pergunta.find_or_create_by!(modelo: modelo, enunciado: p_data[:enunciado]) do |p|
+ p.tipo = p_data[:tipo]
+ p.opcoes = p_data[:opcoes]
+ end
+end
+puts "#{modelo.perguntas.count} perguntas garantidas para o Template Padrão."
diff --git a/spec/helpers/avaliacoes_helper_spec.rb b/spec/helpers/avaliacoes_helper_spec.rb
deleted file mode 100644
index 95cf479b6f..0000000000
--- a/spec/helpers/avaliacoes_helper_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'rails_helper'
-
-# Specs in this file have access to a helper object that includes
-# the AvaliacoesHelper. For example:
-#
-# describe AvaliacoesHelper do
-# describe "string concat" do
-# it "concats two strings with spaces" do
-# expect(helper.concat_strings("this","that")).to eq("this that")
-# end
-# end
-# end
-RSpec.describe AvaliacoesHelper, type: :helper do
- pending "add some examples to (or delete) #{__FILE__}"
-end
diff --git a/spec/services/csv_formatter_service_spec.rb b/spec/services/csv_formatter_service_spec.rb
new file mode 100644
index 0000000000..12b5cb6174
--- /dev/null
+++ b/spec/services/csv_formatter_service_spec.rb
@@ -0,0 +1,46 @@
+require 'rails_helper'
+
+RSpec.describe CsvFormatterService do
+ describe '#generate' do
+ let(:modelo) { double('Modelo', perguntas: [
+ double('Pergunta', enunciado: 'Q1'),
+ double('Pergunta', enunciado: 'Q2')
+ ])}
+
+ let(:avaliacao) { double('Avaliacao', id: 1, modelo: modelo) }
+
+ let(:aluno1) { double('User', matricula: '123', nome: 'Alice') }
+ let(:aluno2) { double('User', matricula: '456', nome: 'Bob') }
+
+ let(:resposta_a1_q1) { double('Resposta', aluno: aluno1, conteudo: 'Ans 1A') }
+ let(:resposta_a1_q2) { double('Resposta', aluno: aluno1, conteudo: 'Ans 1B') }
+ let(:resposta_a2_q1) { double('Resposta', aluno: aluno2, conteudo: 'Ans 2A') }
+
+ # Estrutura agrupada por aluno simulando resultado de query ActiveRecord
+ let(:grouped_responses) do
+ {
+ aluno1 => [resposta_a1_q1, resposta_a1_q2],
+ aluno2 => [resposta_a2_q1]
+ }
+ end
+
+ before do
+ # Mock da cadeia: avaliacao.respostas.includes.group_by
+ allow(avaliacao).to receive_message_chain(:respostas, :includes, :group_by).and_return(grouped_responses)
+ end
+
+ it 'gera uma string CSV válida com cabeçalhos e linhas' do
+ csv_string = described_class.new(avaliacao).generate
+ rows = csv_string.split("\n")
+
+ # Cabeçalhos: Matrícula, Nome, Questão 1, Questão 2
+ expect(rows[0]).to include("Matrícula,Nome,Questão 1,Questão 2")
+
+ # Linha 1: Respostas da Alice
+ expect(rows[1]).to include("123,Alice,Ans 1A,Ans 1B")
+
+ # Linha 2: Respostas do Bob
+ expect(rows[2]).to include("456,Bob,Ans 2A")
+ end
+ end
+end
diff --git a/spec/views/avaliacoes/create.html.tailwindcss_spec.rb b/spec/views/avaliacoes/create.html.tailwindcss_spec.rb
deleted file mode 100644
index 794cc9b1e6..0000000000
--- a/spec/views/avaliacoes/create.html.tailwindcss_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe "avaliacoes/create.html.tailwindcss", type: :view do
- pending "add some examples to (or delete) #{__FILE__}"
-end
diff --git a/spec/views/avaliacoes/gestao_envios.html.tailwindcss_spec.rb b/spec/views/avaliacoes/gestao_envios.html.tailwindcss_spec.rb
deleted file mode 100644
index c056da0962..0000000000
--- a/spec/views/avaliacoes/gestao_envios.html.tailwindcss_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe "avaliacoes/gestao_envios.html.tailwindcss", type: :view do
- pending "add some examples to (or delete) #{__FILE__}"
-end
diff --git a/spec/views/avaliacoes/index.html.tailwindcss_spec.rb b/spec/views/avaliacoes/index.html.tailwindcss_spec.rb
deleted file mode 100644
index b6bebe4cbb..0000000000
--- a/spec/views/avaliacoes/index.html.tailwindcss_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe "avaliacoes/index.html.tailwindcss", type: :view do
- pending "add some examples to (or delete) #{__FILE__}"
-end