From 0e11c4264bfe4f1023dad71ee7b693cbce69f159 Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:32:19 -0300 Subject: [PATCH 01/16] atualizei sigaa_import_service.rb para usar as dependencias de mailer --- app/services/sigaa_import_service.rb | 94 +++++++--------------------- 1 file changed, 23 insertions(+), 71 deletions(-) diff --git a/app/services/sigaa_import_service.rb b/app/services/sigaa_import_service.rb index 2a3995bc15..b1dd82c57e 100644 --- a/app/services/sigaa_import_service.rb +++ b/app/services/sigaa_import_service.rb @@ -2,14 +2,14 @@ require 'csv' class SigaaImportService - def initialize(file_path) + def initialize(file_path, mailer: UserMailer) @file_path = file_path + @mailer = mailer @results = { turmas_created: 0, turmas_updated: 0, - users_created: 0, - users_updated: 0, - new_users: [], # Array de hashes com credenciais dos novos usuários + usuarios_created: 0, + usuarios_updated: 0, errors: [] } end @@ -31,12 +31,10 @@ def process @results[:errors] << "Formato de arquivo não suportado: #{File.extname(@file_path)}" end - if @results[:errors].any? - raise ActiveRecord::Rollback - end + raise ActiveRecord::Rollback if @results[:errors].any? end rescue JSON::ParserError - @results[:errors] << "Arquivo JSON inválido" + @results[:errors] << 'Arquivo JSON inválido' rescue ActiveRecord::StatementInvalid => e @results[:errors] << "Erro de conexão com o banco de dados: #{e.message}" rescue StandardError => e @@ -49,42 +47,10 @@ def process private def process_json - data = JSON.parse(File.read(@file_path)) - - # class_members.json é um array de turmas + file_content = File.read(@file_path) + data = JSON.parse(file_content) data.each do |turma_data| - # Mapeia campos do formato real para o esperado - normalized_data = { - 'codigo' => turma_data['code'], - 'nome' => turma_data['code'], # Usa o código como nome se não tiver - 'semestre' => turma_data['semester'], - 'participantes' => [] - } - - # Processa dicentes (alunos) - if turma_data['dicente'] - turma_data['dicente'].each do |dicente| - normalized_data['participantes'] << { - 'nome' => dicente['nome'], - 'email' => dicente['email'], - 'matricula' => dicente['matricula'] || dicente['usuario'], - 'papel' => 'Discente' - } - end - end - - # Processa docente (professor) - if turma_data['docente'] - docente = turma_data['docente'] - normalized_data['participantes'] << { - 'nome' => docente['nome'], - 'email' => docente['email'], - 'matricula' => docente['usuario'], - 'papel' => 'Docente' - } - end - - process_turma(normalized_data) + process_turma(turma_data) end end @@ -96,9 +62,9 @@ def process_csv 'nome' => row['nome_turma'], 'semestre' => row['semestre'] } - + turma = process_turma_record(turma_data) - + if turma&.persisted? user_data = { 'nome' => row['nome_usuario'], @@ -113,17 +79,17 @@ def process_csv def process_turma(data) turma = process_turma_record(data) - if turma&.persisted? - process_participantes(turma, data['participantes']) if data['participantes'] - end + return unless turma&.persisted? + + process_participantes(turma, data['participantes']) if data['participantes'] end def process_turma_record(data) turma = Turma.find_or_initialize_by(codigo: data['codigo'], semestre: data['semestre']) - + is_new_record = turma.new_record? turma.nome = data['nome'] - + if turma.save if is_new_record @results[:turmas_created] += 1 @@ -144,15 +110,12 @@ def process_participantes(turma, participantes_data) end def process_participante_single(turma, p_data) - # User identificado pela matrícula + # Usuario identificado pela matrícula user = User.find_or_initialize_by(matricula: p_data['matricula']) - + is_new_user = user.new_record? user.nome = p_data['nome'] - user.email_address = p_data['email'] - - # Generate login from matricula if not present (assuming matricula is unique and good for login) - user.login = p_data['matricula'] if user.login.blank? + user.email = p_data['email'] generated_password = nil if is_new_user @@ -162,23 +125,12 @@ def process_participante_single(turma, p_data) if user.save if is_new_user - @results[:users_created] += 1 - - # Armazena credenciais do novo usuário para exibir depois - @results[:new_users] << { - matricula: user.matricula, - nome: user.nome, - login: user.login, - password: generated_password, - email: user.email_address - } - - # Envia email com senha para novo usuário (COMENTADO - muito lento) - # UserMailer.cadastro_email(user, generated_password).deliver_now + @results[:usuarios_created] += 1 + @mailer.cadastro_email(user, generated_password).deliver_now else - @results[:users_updated] += 1 + @results[:usuarios_updated] += 1 end - + matricula = MatriculaTurma.find_or_initialize_by(turma: turma, user: user) matricula.papel = p_data['papel'] matricula.save! From 62a999953a8a3326fd3ca81a1b5836a237ae5631 Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:38:54 -0300 Subject: [PATCH 02/16] teste rspec para matricula_turma --- spec/models/matricula_turma_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 spec/models/matricula_turma_spec.rb diff --git a/spec/models/matricula_turma_spec.rb b/spec/models/matricula_turma_spec.rb new file mode 100644 index 0000000000..7465846da9 --- /dev/null +++ b/spec/models/matricula_turma_spec.rb @@ -0,0 +1,8 @@ +require 'rails_helper' + +RSpec.describe MatriculaTurma, type: :model do + describe 'associations' do + it { should belong_to(:user).with_foreign_key('usuario_id') } + it { should belong_to(:turma) } + end +end From b75e812b954932e52f0f5be32bdd1320aa1bb98a Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:39:36 -0300 Subject: [PATCH 03/16] teste rspec para turma.rb --- spec/models/turma_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 spec/models/turma_spec.rb diff --git a/spec/models/turma_spec.rb b/spec/models/turma_spec.rb new file mode 100644 index 0000000000..dcbc05136a --- /dev/null +++ b/spec/models/turma_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe Turma, type: :model do + describe 'validations' do + it { should validate_presence_of(:codigo) } + it { should validate_presence_of(:nome) } + it { should validate_presence_of(:semestre) } + end + + describe 'associations' do + it { should have_many(:matricula_turmas) } + it { should have_many(:usuarios).through(:matricula_turmas) } + end +end From a624518fb6889b14248013ab29f179d4c7d614b3 Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:40:34 -0300 Subject: [PATCH 04/16] teste rspec para user.rb --- spec/models/user_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 spec/models/user_spec.rb diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000000..6670d1d046 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +RSpec.describe User, type: :model do + describe 'associations' do + it { should have_many(:matricula_turmas) } + it { should have_many(:turmas).through(:matricula_turmas) } + end + + describe 'validations' do + # Adicione validações aqui se houver + end +end From 2f825d1e71129e25c8e17cc748a5b464dab06c8f Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:43:43 -0300 Subject: [PATCH 05/16] teste rspec para sigaa_import_service.rb --- spec/services/sigaa_import_service_spec.rb | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 spec/services/sigaa_import_service_spec.rb diff --git a/spec/services/sigaa_import_service_spec.rb b/spec/services/sigaa_import_service_spec.rb new file mode 100644 index 0000000000..369954a344 --- /dev/null +++ b/spec/services/sigaa_import_service_spec.rb @@ -0,0 +1,87 @@ +require 'rails_helper' + +RSpec.describe SigaaImportService, type: :service do + let(:json_path) { Rails.root.join('spec/fixtures/turmas.json') } + let(:csv_path) { Rails.root.join('spec/fixtures/turmas.csv') } + let(:invalid_path) { Rails.root.join('spec/fixtures/invalid.txt') } + + before do + allow(File).to receive(:exist?).and_return(true) + allow(File).to receive(:read).with(json_path).and_return([ + { + 'codigo' => 'T01', + 'nome' => 'Engenharia de Software', + 'semestre' => '2024.1', + 'participantes' => [ + { + 'matricula' => '123456', + 'nome' => 'João Silva', + 'email' => 'joao@example.com', + 'papel' => 'aluno' + } + ] + } + ].to_json) + + allow(CSV).to receive(:foreach).with(csv_path, headers: true, col_sep: ',').and_yield( + CSV::Row.new(%w[codigo_turma nome_turma semestre nome_usuario email matricula papel], + ['T02', 'Banco de Dados', '2024.1', 'Maria Souza', 'maria@example.com', '654321', 'professor']) + ) + + allow(File).to receive(:extname).with(json_path).and_return('.json') + allow(File).to receive(:extname).with(csv_path).and_return('.csv') + allow(File).to receive(:extname).with(invalid_path).and_return('.txt') + end + + class DummyMessage + def deliver_now + true + end + end + + class DummyMailer + def self.cadastro_email(user, password) + DummyMessage.new + end + end + + describe '#process' do + context 'with JSON file' do + it 'creates turmas and users' do + Turma.delete_all + User.delete_all + + service = SigaaImportService.new(json_path, mailer: DummyMailer) + result = service.process + puts "JSON Import Errors: #{result[:errors]}" if result[:errors].any? + + expect(Turma.count).to eq(1) + expect(User.count).to eq(1) + end + end + + context 'with CSV file' do + it 'creates turmas and users' do + Turma.delete_all + User.delete_all + + service = SigaaImportService.new(csv_path, mailer: DummyMailer) + result = service.process + puts "CSV Import Errors: #{result[:errors]}" if result[:errors].any? + + expect(Turma.count).to eq(1) + expect(User.count).to eq(1) + end + end + + context 'with unsupported file format' do + it 'returns error' do + service = SigaaImportService.new(invalid_path, mailer: DummyMailer) + result = service.process + unless result[:errors].join(', ').include?('Formato de arquivo não suportado') + raise "Expected error 'Formato de arquivo não suportado' not found in: #{result[:errors]}" + end + end + end + end +end From de9db1dcffaaca130c90e7a98b7107e361c13564 Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:51:28 -0300 Subject: [PATCH 06/16] teste rspec para user_mailer.rb --- spec/mailers/user_mailer_spec.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 spec/mailers/user_mailer_spec.rb diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb new file mode 100644 index 0000000000..7220357061 --- /dev/null +++ b/spec/mailers/user_mailer_spec.rb @@ -0,0 +1,32 @@ +require 'rails_helper' + +RSpec.describe UserMailer, type: :mailer do + describe 'definicao_senha' do + let(:user) { User.create(nome: 'Teste', email: 'teste@example.com', matricula: '111', password: 'password') } + let(:mail) { UserMailer.definicao_senha(user) } + + it 'renders the headers' do + expect(mail.subject).to eq('Definição de Senha - Sistema de Gestão') + expect(mail.to).to eq([user.email]) + end + + it 'renders the body' do + expect(mail.body.encoded).to match('Definição de Senha') + end + end + + describe 'cadastro_email' do + let(:user) { User.create(nome: 'Teste', email: 'teste@example.com', matricula: '222', password: 'password') } + let(:password) { 'secret123' } + let(:mail) { UserMailer.cadastro_email(user, password) } + + it 'renders the headers' do + expect(mail.subject).to eq('Bem-vindo ao Sistema de Gestão - Cadastro Realizado') + expect(mail.to).to eq([user.email]) + end + + it 'renders the body with password' do + expect(mail.body.encoded).to match(password) + end + end +end From 848a3b06211407d971a3c1667172109925babd22 Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:55:20 -0300 Subject: [PATCH 07/16] teste rspec para model de "User" #84 --- spec/models/user_spec.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6670d1d046..fa7e3478e9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,8 +2,16 @@ RSpec.describe User, type: :model do describe 'associations' do - it { should have_many(:matricula_turmas) } - it { should have_many(:turmas).through(:matricula_turmas) } + it 'has many matricula_turmas' do + association = described_class.reflect_on_association(:matricula_turmas) + expect(association.macro).to eq :has_many + end + + it 'has many turmas through matricula_turmas' do + association = described_class.reflect_on_association(:turmas) + expect(association.macro).to eq :has_many + expect(association.options[:through]).to eq :matricula_turmas + end end describe 'validations' do From 82a563f2e1db2eab459bf537c480e6917748e90c Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:55:52 -0300 Subject: [PATCH 08/16] teste rspec para model de "Turma" #85 Refactor association tests for Turma model to use explicit expectations. --- spec/models/turma_spec.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/spec/models/turma_spec.rb b/spec/models/turma_spec.rb index dcbc05136a..795409feb0 100644 --- a/spec/models/turma_spec.rb +++ b/spec/models/turma_spec.rb @@ -1,14 +1,16 @@ require 'rails_helper' RSpec.describe Turma, type: :model do - describe 'validations' do - it { should validate_presence_of(:codigo) } - it { should validate_presence_of(:nome) } - it { should validate_presence_of(:semestre) } - end - describe 'associations' do - it { should have_many(:matricula_turmas) } - it { should have_many(:usuarios).through(:matricula_turmas) } + it 'has many matricula_turmas' do + association = described_class.reflect_on_association(:matricula_turmas) + expect(association.macro).to eq :has_many + end + + it 'has many users through matricula_turmas' do + association = described_class.reflect_on_association(:users) + expect(association.macro).to eq :has_many + expect(association.options[:through]).to eq :matricula_turmas + end end end From f7f1b91ade8dbbedf3785b5589d82d4cbbcb2cc6 Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:56:28 -0300 Subject: [PATCH 09/16] teste rspec para model "MatriculaTurma" #86 --- spec/models/matricula_turma_spec.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/models/matricula_turma_spec.rb b/spec/models/matricula_turma_spec.rb index 7465846da9..a50a3f4b36 100644 --- a/spec/models/matricula_turma_spec.rb +++ b/spec/models/matricula_turma_spec.rb @@ -2,7 +2,14 @@ RSpec.describe MatriculaTurma, type: :model do describe 'associations' do - it { should belong_to(:user).with_foreign_key('usuario_id') } - it { should belong_to(:turma) } + it 'belongs to user' do + association = described_class.reflect_on_association(:user) + expect(association.macro).to eq :belongs_to + end + + it 'belongs to turma' do + association = described_class.reflect_on_association(:turma) + expect(association.macro).to eq :belongs_to + end end end From 001bbedab70b6511d69a6b177eecdfce311f697c Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:13:40 -0300 Subject: [PATCH 10/16] Fix email address attribute in definicao_senha method --- app/mailers/user_mailer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 80bf09188e..e03dd9602c 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -3,7 +3,7 @@ class UserMailer < ApplicationMailer def definicao_senha(user) @user = user @url = "http://localhost:3000/definicao_senha" - mail(to: @user.email, subject: 'Definição de Senha - Sistema de Gestão') + mail(to: @user.email_address, subject: 'Definição de Senha - Sistema de Gestão') end # Email de cadastro com senha temporária (novo método) From 757e32388abbec9eed5c833b78fed4f4fbfeab28 Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:14:44 -0300 Subject: [PATCH 11/16] Refactor SigaaImportService to remove mailer dependency --- app/services/sigaa_import_service.rb | 94 +++++++++++++++++++++------- 1 file changed, 71 insertions(+), 23 deletions(-) diff --git a/app/services/sigaa_import_service.rb b/app/services/sigaa_import_service.rb index b1dd82c57e..2a3995bc15 100644 --- a/app/services/sigaa_import_service.rb +++ b/app/services/sigaa_import_service.rb @@ -2,14 +2,14 @@ require 'csv' class SigaaImportService - def initialize(file_path, mailer: UserMailer) + def initialize(file_path) @file_path = file_path - @mailer = mailer @results = { turmas_created: 0, turmas_updated: 0, - usuarios_created: 0, - usuarios_updated: 0, + users_created: 0, + users_updated: 0, + new_users: [], # Array de hashes com credenciais dos novos usuários errors: [] } end @@ -31,10 +31,12 @@ def process @results[:errors] << "Formato de arquivo não suportado: #{File.extname(@file_path)}" end - raise ActiveRecord::Rollback if @results[:errors].any? + if @results[:errors].any? + raise ActiveRecord::Rollback + end end rescue JSON::ParserError - @results[:errors] << 'Arquivo JSON inválido' + @results[:errors] << "Arquivo JSON inválido" rescue ActiveRecord::StatementInvalid => e @results[:errors] << "Erro de conexão com o banco de dados: #{e.message}" rescue StandardError => e @@ -47,10 +49,42 @@ def process private def process_json - file_content = File.read(@file_path) - data = JSON.parse(file_content) + data = JSON.parse(File.read(@file_path)) + + # class_members.json é um array de turmas data.each do |turma_data| - process_turma(turma_data) + # Mapeia campos do formato real para o esperado + normalized_data = { + 'codigo' => turma_data['code'], + 'nome' => turma_data['code'], # Usa o código como nome se não tiver + 'semestre' => turma_data['semester'], + 'participantes' => [] + } + + # Processa dicentes (alunos) + if turma_data['dicente'] + turma_data['dicente'].each do |dicente| + normalized_data['participantes'] << { + 'nome' => dicente['nome'], + 'email' => dicente['email'], + 'matricula' => dicente['matricula'] || dicente['usuario'], + 'papel' => 'Discente' + } + end + end + + # Processa docente (professor) + if turma_data['docente'] + docente = turma_data['docente'] + normalized_data['participantes'] << { + 'nome' => docente['nome'], + 'email' => docente['email'], + 'matricula' => docente['usuario'], + 'papel' => 'Docente' + } + end + + process_turma(normalized_data) end end @@ -62,9 +96,9 @@ def process_csv 'nome' => row['nome_turma'], 'semestre' => row['semestre'] } - + turma = process_turma_record(turma_data) - + if turma&.persisted? user_data = { 'nome' => row['nome_usuario'], @@ -79,17 +113,17 @@ def process_csv def process_turma(data) turma = process_turma_record(data) - return unless turma&.persisted? - - process_participantes(turma, data['participantes']) if data['participantes'] + if turma&.persisted? + process_participantes(turma, data['participantes']) if data['participantes'] + end end def process_turma_record(data) turma = Turma.find_or_initialize_by(codigo: data['codigo'], semestre: data['semestre']) - + is_new_record = turma.new_record? turma.nome = data['nome'] - + if turma.save if is_new_record @results[:turmas_created] += 1 @@ -110,12 +144,15 @@ def process_participantes(turma, participantes_data) end def process_participante_single(turma, p_data) - # Usuario identificado pela matrícula + # User identificado pela matrícula user = User.find_or_initialize_by(matricula: p_data['matricula']) - + is_new_user = user.new_record? user.nome = p_data['nome'] - user.email = p_data['email'] + user.email_address = p_data['email'] + + # Generate login from matricula if not present (assuming matricula is unique and good for login) + user.login = p_data['matricula'] if user.login.blank? generated_password = nil if is_new_user @@ -125,12 +162,23 @@ def process_participante_single(turma, p_data) if user.save if is_new_user - @results[:usuarios_created] += 1 - @mailer.cadastro_email(user, generated_password).deliver_now + @results[:users_created] += 1 + + # Armazena credenciais do novo usuário para exibir depois + @results[:new_users] << { + matricula: user.matricula, + nome: user.nome, + login: user.login, + password: generated_password, + email: user.email_address + } + + # Envia email com senha para novo usuário (COMENTADO - muito lento) + # UserMailer.cadastro_email(user, generated_password).deliver_now else - @results[:usuarios_updated] += 1 + @results[:users_updated] += 1 end - + matricula = MatriculaTurma.find_or_initialize_by(turma: turma, user: user) matricula.papel = p_data['papel'] matricula.save! From 244210712054972502cbf8691ff6478fa2cc4a45 Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:15:51 -0300 Subject: [PATCH 12/16] teste rspec para user_mailer.rb --- spec/mailers/user_mailer_spec.rb | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 7220357061..172cfe63ac 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -2,12 +2,17 @@ RSpec.describe UserMailer, type: :mailer do describe 'definicao_senha' do - let(:user) { User.create(nome: 'Teste', email: 'teste@example.com', matricula: '111', password: 'password') } + + let(:user) do + User.create(nome: 'Teste', email_address: 'teste@example.com', matricula: '111', password: 'password') + end let(:mail) { UserMailer.definicao_senha(user) } + + it 'renders the headers' do expect(mail.subject).to eq('Definição de Senha - Sistema de Gestão') - expect(mail.to).to eq([user.email]) + expect(mail.to).to eq([user.email_address]) end it 'renders the body' do @@ -16,13 +21,15 @@ end describe 'cadastro_email' do - let(:user) { User.create(nome: 'Teste', email: 'teste@example.com', matricula: '222', password: 'password') } + let(:user) do + User.create(nome: 'Teste', email_address: 'teste@example.com', matricula: '222', password: 'password') + end let(:password) { 'secret123' } let(:mail) { UserMailer.cadastro_email(user, password) } it 'renders the headers' do - expect(mail.subject).to eq('Bem-vindo ao Sistema de Gestão - Cadastro Realizado') - expect(mail.to).to eq([user.email]) + expect(mail.subject).to eq('Bem-vindo(a) ao CAMAAR - Sua senha de acesso') + expect(mail.to).to eq([user.email_address]) end it 'renders the body with password' do From 0a9a9a02735d36d0a2c12667dfe16eb6f6c3cbcb Mon Sep 17 00:00:00 2001 From: PPeregrino <125605570+PPeregrino@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:16:45 -0300 Subject: [PATCH 13/16] teste rspec para sigaa_import_service.rb Updated SigaaImportService tests to remove mailer dependency and adjusted user count expectations. --- spec/services/sigaa_import_service_spec.rb | 31 ++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/spec/services/sigaa_import_service_spec.rb b/spec/services/sigaa_import_service_spec.rb index 369954a344..67288385c0 100644 --- a/spec/services/sigaa_import_service_spec.rb +++ b/spec/services/sigaa_import_service_spec.rb @@ -1,4 +1,7 @@ require 'rails_helper' +require 'rspec/support/differ' +require 'rspec/support/hunk_generator' +require 'diff/lcs' RSpec.describe SigaaImportService, type: :service do let(:json_path) { Rails.root.join('spec/fixtures/turmas.json') } @@ -6,20 +9,24 @@ let(:invalid_path) { Rails.root.join('spec/fixtures/invalid.txt') } before do + # Garante que fixtures existem ou são mockados allow(File).to receive(:exist?).and_return(true) allow(File).to receive(:read).with(json_path).and_return([ { - 'codigo' => 'T01', - 'nome' => 'Engenharia de Software', - 'semestre' => '2024.1', - 'participantes' => [ + 'code' => 'T01', + 'semester' => '2024.1', + 'dicente' => [ { 'matricula' => '123456', 'nome' => 'João Silva', - 'email' => 'joao@example.com', - 'papel' => 'aluno' + 'email' => 'joao@example.com' } - ] + ], + 'docente' => { + 'usuario' => '654321', + 'nome' => 'Maria Professora', + 'email' => 'maria@prof.com' + } } ].to_json) @@ -28,6 +35,7 @@ ['T02', 'Banco de Dados', '2024.1', 'Maria Souza', 'maria@example.com', '654321', 'professor']) ) + allow(File).to receive(:extname).and_call_original allow(File).to receive(:extname).with(json_path).and_return('.json') allow(File).to receive(:extname).with(csv_path).and_return('.csv') allow(File).to receive(:extname).with(invalid_path).and_return('.txt') @@ -51,12 +59,12 @@ def self.cadastro_email(user, password) Turma.delete_all User.delete_all - service = SigaaImportService.new(json_path, mailer: DummyMailer) + service = SigaaImportService.new(json_path) result = service.process puts "JSON Import Errors: #{result[:errors]}" if result[:errors].any? expect(Turma.count).to eq(1) - expect(User.count).to eq(1) + expect(User.count).to eq(2) end end @@ -65,7 +73,7 @@ def self.cadastro_email(user, password) Turma.delete_all User.delete_all - service = SigaaImportService.new(csv_path, mailer: DummyMailer) + service = SigaaImportService.new(csv_path) result = service.process puts "CSV Import Errors: #{result[:errors]}" if result[:errors].any? @@ -76,8 +84,9 @@ def self.cadastro_email(user, password) context 'with unsupported file format' do it 'returns error' do - service = SigaaImportService.new(invalid_path, mailer: DummyMailer) + service = SigaaImportService.new(invalid_path) result = service.process + # Manual check to avoid RSpec HunkGenerator error unless result[:errors].join(', ').include?('Formato de arquivo não suportado') raise "Expected error 'Formato de arquivo não suportado' not found in: #{result[:errors]}" end From 2d29b40313827464048de9f7ebc26986f2005f42 Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 12 Dec 2025 11:29:27 -0300 Subject: [PATCH 14/16] Resolve problemas de lint,test e scan_ruby --- Gemfile | 7 +- app/controllers/avaliacoes_controller.rb | 4 +- app/controllers/concerns/authenticatable.rb | 8 +- .../controller_template.rb | 98 - .../models_questao.rb | 73 - .../models_template.rb | 55 - .../view_flash_messeges.html.erb | 19 - .../view_form_template_new_edit.html.erb | 261 - .../view_layout.html.erb | 34 - .../view_show_template.html.erb | 70 - .../view_template_fields.html.erb | 86 - .../view_template_index.html.erb | 111 - app/controllers/modelos_controller.rb | 56 +- app/controllers/pages_controller.rb | 6 +- app/controllers/respostas_controller.rb | 14 +- app/controllers/sessions_controller.rb | 2 +- app/controllers/sigaa_imports_controller.rb | 16 +- app/mailers/user_mailer.rb | 8 +- app/models/avaliacao.rb | 6 +- app/models/modelo.rb | 24 +- app/models/pergunta.rb | 54 +- app/models/resposta.rb | 11 +- app/models/submissao.rb | 6 +- app/models/user.rb | 2 +- app/services/csv_formatter_service.rb | 20 +- app/services/sigaa_import_service.rb | 102 +- app/views/modelos/show.html.erb | 51 +- config/initializers/inflections_custom.rb | 2 +- config/initializers/mail.rb | 39 +- config/routes.rb | 6 +- coverage/.last_run.json | 5 + coverage/.resultset.json | 534 +- coverage/index.html | 8872 +++++++++++++---- db/migrate/20251208012954_drop_usuarios.rb | 2 +- db/seeds.rb | 4 +- .../step_definitions/cria_avaliacao_steps.rb | 2 +- features/step_definitions/email_steps.rb | 12 +- .../importa_dados_sigaa_steps.rb | 6 +- .../step_definitions/resultados_adm_steps.rb | 6 +- features/step_definitions/shared_steps.rb | 38 +- .../step_definitions/student_view_steps.rb | 2 +- features/support/email.rb | 6 +- spec/mailers/user_mailer_spec.rb | 5 +- spec/requests/avaliacoes_spec.rb | 2 +- spec/services/csv_formatter_service_spec.rb | 14 +- spec/services/sigaa_import_service_spec.rb | 2 +- test/controllers/home_controller_test.rb | 1 - test/services/sigaa_import_service_test.rb | 112 +- 48 files changed, 7809 insertions(+), 3067 deletions(-) delete mode 100644 app/controllers/migration_tabela_submissao/controller_template.rb delete mode 100644 app/controllers/migration_tabela_submissao/models_questao.rb delete mode 100644 app/controllers/migration_tabela_submissao/models_template.rb delete mode 100644 app/controllers/migration_tabela_submissao/view_flash_messeges.html.erb delete mode 100644 app/controllers/migration_tabela_submissao/view_form_template_new_edit.html.erb delete mode 100644 app/controllers/migration_tabela_submissao/view_layout.html.erb delete mode 100644 app/controllers/migration_tabela_submissao/view_show_template.html.erb delete mode 100644 app/controllers/migration_tabela_submissao/view_template_fields.html.erb delete mode 100644 app/controllers/migration_tabela_submissao/view_template_index.html.erb create mode 100644 coverage/.last_run.json diff --git a/Gemfile b/Gemfile index aa909e94c3..983667a23c 100644 --- a/Gemfile +++ b/Gemfile @@ -66,13 +66,12 @@ end gem "tailwindcss-rails", "~> 4.4" -gem "rspec-rails", "~> 8.0", groups: [:development, :test] +gem "rspec-rails", "~> 8.0", groups: [ :development, :test ] -gem 'simplecov', require: false, group: :test +gem "simplecov", require: false, group: :test gem "rubycritic", require: false gem "csv", "~> 3.3" # Email preview in browser for development -gem 'letter_opener', group: :development - +gem "letter_opener", group: :development diff --git a/app/controllers/avaliacoes_controller.rb b/app/controllers/avaliacoes_controller.rb index 43a931f5d1..e855f9a70e 100644 --- a/app/controllers/avaliacoes_controller.rb +++ b/app/controllers/avaliacoes_controller.rb @@ -1,11 +1,11 @@ class AvaliacoesController < ApplicationController # Requer autenticação para todas as actions - + def index # Se for admin, mostrar todas as avaliações # Se for aluno, mostrar todas as turmas matriculadas @turmas = [] # Inicializa como array vazio por padrão - + if current_user&.eh_admin? @avaliacoes = Avaliacao.all elsif current_user diff --git a/app/controllers/concerns/authenticatable.rb b/app/controllers/concerns/authenticatable.rb index 4d7f7a6e56..3669bd8cfc 100644 --- a/app/controllers/concerns/authenticatable.rb +++ b/app/controllers/concerns/authenticatable.rb @@ -1,19 +1,19 @@ # app/controllers/concerns/authenticatable.rb module Authenticatable extend ActiveSupport::Concern - + included do helper_method :current_user, :user_signed_in? end - + def authenticate_user! redirect_to new_session_path, alert: "É necessário fazer login." unless user_signed_in? end - + def current_user Current.session&.user end - + def user_signed_in? current_user.present? end diff --git a/app/controllers/migration_tabela_submissao/controller_template.rb b/app/controllers/migration_tabela_submissao/controller_template.rb deleted file mode 100644 index 97f977ace1..0000000000 --- a/app/controllers/migration_tabela_submissao/controller_template.rb +++ /dev/null @@ -1,98 +0,0 @@ -# app/controllers/templates_controller.rb -class TemplatesController < ApplicationController - before_action :authenticate_user! - before_action :authorize_admin - before_action :set_template, only: [:show, :edit, :update, :destroy, :clone] - - # GET /templates - def index - @templates = Template.includes(:questaos).order(created_at: :desc) - end - - # GET /templates/1 - def show - end - - # GET /templates/new - def new - @template = Template.new - 3.times { @template.questaos.build } # Cria 3 questões em branco por padrão - end - - # GET /templates/1/edit - def edit - @template.questaos.build if @template.questaos.empty? - end - - # POST /templates - def create - @template = Template.new(template_params) - - if @template.save - redirect_to @template, notice: 'Template criado com sucesso.' - else - # Garante que tenha pelo menos uma questão para mostrar no formulário - @template.questaos.build if @template.questaos.empty? - render :new, status: :unprocessable_entity - end - end - - # PATCH/PUT /templates/1 - def update - if @template.update(template_params) - redirect_to @template, notice: 'Template atualizado com sucesso.' - else - render :edit, status: :unprocessable_entity - end - end - - # DELETE /templates/1 - def destroy - if @template.em_uso? - redirect_to templates_url, alert: 'Não é possível excluir um template que está em uso.' - else - @template.destroy - redirect_to templates_url, notice: 'Template excluído com sucesso.' - end - end - - # POST /templates/1/clone - def clone - novo_nome = "#{@template.nome} (Cópia)" - novo_template = @template.clonar_com_questoes(novo_nome) - - if novo_template.persisted? - redirect_to edit_template_path(novo_template), - notice: 'Template clonado com sucesso. Edite o nome se necessário.' - else - redirect_to @template, alert: 'Erro ao clonar template.' - end - end - - private - - def set_template - @template = Template.find(params[:id]) - end - - def template_params - params.require(:template).permit( - :nome, - :descricao, - questaos_attributes: [ - :id, - :enunciado, - :tipo, - :opcoes, - :ordem, - :_destroy - ] - ) - end - - def authorize_admin - unless current_user.admin? - redirect_to root_path, alert: 'Acesso restrito a administradores.' - end - end -end \ No newline at end of file diff --git a/app/controllers/migration_tabela_submissao/models_questao.rb b/app/controllers/migration_tabela_submissao/models_questao.rb deleted file mode 100644 index 533e85f762..0000000000 --- a/app/controllers/migration_tabela_submissao/models_questao.rb +++ /dev/null @@ -1,73 +0,0 @@ -# app/models/questao.rb -class Questao < ApplicationRecord - # Relacionamentos - belongs_to :template - has_many :respostas, dependent: :destroy - - # Tipos de questões disponíveis - TIPOS = { - 'texto_longo' => 'Texto Longo', - 'texto_curto' => 'Texto Curto', - 'multipla_escolha' => 'Múltipla Escolha', - 'checkbox' => 'Checkbox (Múltipla Seleção)', - 'escala' => 'Escala Likert (1-5)', - 'data' => 'Data', - 'hora' => 'Hora' - }.freeze - - # Validações - validates :enunciado, presence: true - validates :tipo, presence: true, inclusion: { in: TIPOS.keys } - validates :ordem, presence: true, numericality: { only_integer: true, greater_than: 0 } - - # Validações condicionais - validate :opcoes_requeridas_para_multipla_escolha - validate :opcoes_requeridas_para_checkbox - - # Callbacks - before_validation :definir_ordem_padrao, on: :create - after_save :reordenar_questoes - - # Métodos - def tipo_humanizado - TIPOS[tipo] || tipo - end - - def requer_opcoes? - ['multipla_escolha', 'checkbox'].include?(tipo) - end - - def lista_opcoes - return [] unless opcoes.present? - opcoes.split(';').map(&:strip) - end - - private - - def definir_ordem_padrao - if ordem.nil? && template.present? - ultima_ordem = template.questaos.maximum(:ordem) || 0 - self.ordem = ultima_ordem + 1 - end - end - - def reordenar_questoes - if ordem_changed? && template.present? - template.questaos.where.not(id: id).order(:ordem, :created_at).each_with_index do |questao, index| - questao.update_column(:ordem, index + 1) if questao.ordem != index + 1 - end - end - end - - def opcoes_requeridas_para_multipla_escolha - if tipo == 'multipla_escolha' && (opcoes.blank? || opcoes.split(';').size < 2) - errors.add(:opcoes, 'deve ter pelo menos duas opções para múltipla escolha') - end - end - - def opcoes_requeridas_para_checkbox - if tipo == 'checkbox' && (opcoes.blank? || opcoes.split(';').size < 2) - errors.add(:opcoes, 'deve ter pelo menos duas opções para checkbox') - end - end -end \ No newline at end of file diff --git a/app/controllers/migration_tabela_submissao/models_template.rb b/app/controllers/migration_tabela_submissao/models_template.rb deleted file mode 100644 index 5c0f59345f..0000000000 --- a/app/controllers/migration_tabela_submissao/models_template.rb +++ /dev/null @@ -1,55 +0,0 @@ -# app/models/template.rb -class Template < ApplicationRecord - # Relacionamentos - has_many :questaos, dependent: :destroy - has_many :formularios, dependent: :restrict_with_error - - # Validações - validates :nome, presence: true, uniqueness: { case_sensitive: false } - validates :descricao, presence: true - - # Validação customizada: não permitir template sem questões - validate :deve_ter_pelo_menos_uma_questao, on: :create - validate :nao_pode_remover_todas_questoes, on: :update - - # Aceita atributos aninhados para questões - accepts_nested_attributes_for :questaos, - allow_destroy: true, - reject_if: :all_blank - - # Método para verificar se template está em uso - def em_uso? - formularios.any? - end - - # Método para clonar template com questões - def clonar_com_questoes(novo_nome) - novo_template = dup - novo_template.nome = novo_nome - novo_template.save - - if novo_template.persisted? - questaos.each do |questao| - nova_questao = questao.dup - nova_questao.template = novo_template - nova_questao.save - end - end - - novo_template - end - - private - - def deve_ter_pelo_menos_uma_questao - if questaos.empty? || questaos.all? { |q| q.marked_for_destruction? } - errors.add(:base, "Um template deve ter pelo menos uma questão") - end - end - - def nao_pode_remover_todas_questoes - if persisted? && (questaos.empty? || questaos.all? { |q| q.marked_for_destruction? }) - errors.add(:base, "Não é possível remover todas as questões de um template existente") - end - end -end \ No newline at end of file diff --git a/app/controllers/migration_tabela_submissao/view_flash_messeges.html.erb b/app/controllers/migration_tabela_submissao/view_flash_messeges.html.erb deleted file mode 100644 index 6427587990..0000000000 --- a/app/controllers/migration_tabela_submissao/view_flash_messeges.html.erb +++ /dev/null @@ -1,19 +0,0 @@ -<%# app/views/shared/_flash_messages.html.erb %> -<% flash.each do |type, message| %> - <% alert_class = case type.to_sym - when :notice then 'bg-green-100 border-green-400 text-green-700' - when :alert, :error then 'bg-red-100 border-red-400 text-red-700' - else 'bg-blue-100 border-blue-400 text-blue-700' - end %> - - -<% end %> \ No newline at end of file diff --git a/app/controllers/migration_tabela_submissao/view_form_template_new_edit.html.erb b/app/controllers/migration_tabela_submissao/view_form_template_new_edit.html.erb deleted file mode 100644 index 24e06060d3..0000000000 --- a/app/controllers/migration_tabela_submissao/view_form_template_new_edit.html.erb +++ /dev/null @@ -1,261 +0,0 @@ -<%# app/views/templates/_form.html.erb %> -<%= form_with(model: template, local: true, class: "space-y-8") do |form| %> - <% if template.errors.any? %> -
-
-
- - - -
-
-

- <%= pluralize(template.errors.count, "erro") %> impediram este template de ser salvo: -

-
-
    - <% template.errors.each do |error| %> -
  • <%= error.full_message %>
  • - <% end %> -
-
-
-
-
- <% end %> - -
-
-

- - - - Informações do Template -

-
-
-
-
- - <%= form.text_field :nome, - class: "mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm", - placeholder: "Ex: Avaliação de Professor" %> -

Nome único para identificar o template

-
- -
- - <%= form.text_area :descricao, - rows: 2, - class: "mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm", - placeholder: "Descreva a finalidade deste template..." %> -

Breve descrição sobre o propósito do formulário

-
-
-
-
- -
-
-
-

- - - - Questões do Formulário -

- -
-
- -
-
- <%= form.fields_for :questaos do |questao_form| %> - <%= render 'questao_fields', f: questao_form %> - <% end %> -
- - <% if template.questaos.empty? %> -
-
-
- - - -
-
-

Atenção

-
-

Um template deve ter pelo menos uma questão para ser salvo.

-
-
-
-
- <% end %> -
-
- -
- <%= link_to templates_path, class: "btn-secondary flex items-center gap-2" do %> - - - - Cancelar - <% end %> - - <%= form.submit template.persisted? ? 'Atualizar Template' : 'Salvar Template', - class: "btn-primary flex items-center gap-2" do %> - - - - <% end %> -
-<% end %> - - \ No newline at end of file diff --git a/app/controllers/migration_tabela_submissao/view_layout.html.erb b/app/controllers/migration_tabela_submissao/view_layout.html.erb deleted file mode 100644 index 7616357fad..0000000000 --- a/app/controllers/migration_tabela_submissao/view_layout.html.erb +++ /dev/null @@ -1,34 +0,0 @@ -<%# app/views/layouts/application.html.erb %> - - - - Sistema de Avaliação - - <%= csrf_meta_tags %> - <%= csp_meta_tag %> - - <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> - <%= javascript_importmap_tags %> - - - - - <%= render 'shared/navbar' %> - <%= render 'shared/flash_messages' %> - -
- <%= yield %> -
- - \ No newline at end of file diff --git a/app/controllers/migration_tabela_submissao/view_show_template.html.erb b/app/controllers/migration_tabela_submissao/view_show_template.html.erb deleted file mode 100644 index 6381f44122..0000000000 --- a/app/controllers/migration_tabela_submissao/view_show_template.html.erb +++ /dev/null @@ -1,70 +0,0 @@ -<%# app/views/templates/show.html.erb %> -
-
-
-

<%= @template.nome %>

-

<%= @template.descricao %>

-
- - - - - Criado em <%= l(@template.created_at, format: :long) %> - - - - - - <%= pluralize(@template.questaos.count, 'questão', 'questões') %> - -
-
- -
- <%= link_to edit_template_path(@template), class: "btn-secondary flex items-center gap-2" do %> - - - - Editar - <% end %> - - <%= link_to templates_path, class: "btn-secondary flex items-center gap-2" do %> - - - - Voltar - <% end %> -
-
- -
-
-

Questões do Template

-
- -
- <% @template.questaos.order(:ordem).each do |questao| %> -
-
-
- - <%= questao.ordem %> - -
- -
-
-

- <%= questao.enunciado %> -

- - <%= questao.tipo_humanizado %> - -
- - <% if questao.requer_opcoes? && questao.lista_opcoes.any? %> -
-

Opções:

-
- <% questao.lista_opcoes.each do |opcao| %> - -
-
-

- - <%= f.object.ordem || 1 %> - - Questão <%= f.index + 1 %> -

-
- <%= f.hidden_field :_destroy, data: { questao_target: "destroy" } %> - -
-
- -
-
- <%= f.label :enunciado, class: "block text-sm font-medium text-gray-700 required" %> - <%= f.text_field :enunciado, - class: "mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm", - placeholder: "Digite o enunciado da questão...", - required: true %> -
- -
- <%= f.label :tipo, class: "block text-sm font-medium text-gray-700 required" %> - <%= f.select :tipo, - Questao::TIPOS.map { |key, value| [value, key] }, - { include_blank: 'Selecione...' }, - { class: "mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm", - required: true, - data: { action: "change->questao#toggleOptions" } } %> -
- -
- <%= f.label :ordem, class: "block text-sm font-medium text-gray-700" %> - <%= f.number_field :ordem, - class: "mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm", - min: 1 %> -
-
- - -
- -<%# Stimulus Controller para gerenciar questões %> - \ No newline at end of file diff --git a/app/controllers/migration_tabela_submissao/view_template_index.html.erb b/app/controllers/migration_tabela_submissao/view_template_index.html.erb deleted file mode 100644 index ccb72c33c2..0000000000 --- a/app/controllers/migration_tabela_submissao/view_template_index.html.erb +++ /dev/null @@ -1,111 +0,0 @@ -<%# app/views/templates/index.html.erb %> -
-
-
-

Templates de Formulário

-

Gerencie os modelos de formulários de avaliação

-
- <%= link_to new_template_path, class: "btn-primary flex items-center gap-2" do %> - - - - Novo Template - <% end %> -
- - <% if @templates.empty? %> -
- - - -

Nenhum template cadastrado

-

Comece criando seu primeiro template de formulário.

- <%= link_to 'Criar Primeiro Template', new_template_path, class: 'btn-primary' %> -
- <% else %> -
-
    - <% @templates.each do |template| %> -
  • -
    -
    -
    -
    -

    - <%= template.nome %> -

    - - <%= template.questaos.count %> questões - -
    -

    - <%= template.descricao %> -

    -
    - - - - Criado em <%= l(template.created_at, format: :short) %> -
    -
    -
    - <%= link_to template_path(template), class: "btn-icon text-gray-600 hover:text-blue-600", title: "Visualizar" do %> - - - - - <% end %> - - <%= link_to edit_template_path(template), class: "btn-icon text-gray-600 hover:text-yellow-600", title: "Editar" do %> - - - - <% end %> - - <%= link_to clone_template_path(template), - method: :post, - class: "btn-icon text-gray-600 hover:text-green-600", - title: "Clonar", - data: { confirm: 'Clonar este template?' } do %> - - - - <% end %> - - <%= link_to template_path(template), - method: :delete, - class: "btn-icon text-gray-600 hover:text-red-600", - title: "Excluir", - data: { - confirm: template.em_uso? ? - 'Este template está em uso e não pode ser excluído. Deseja continuar?' : - 'Tem certeza que deseja excluir este template?' - } do %> - - - - <% end %> -
    -
    -
    -
  • - <% end %> -
-
- <% end %> -
- -<%# Botões com estilos Tailwind %> - \ No newline at end of file diff --git a/app/controllers/modelos_controller.rb b/app/controllers/modelos_controller.rb index eb47f924bc..6115403c38 100644 --- a/app/controllers/modelos_controller.rb +++ b/app/controllers/modelos_controller.rb @@ -1,96 +1,96 @@ # app/controllers/modelos_controller.rb class ModelosController < ApplicationController before_action :require_admin - before_action :set_modelo, only: [:show, :edit, :update, :destroy, :clone] - + before_action :set_modelo, only: [ :show, :edit, :update, :destroy, :clone ] + # GET /modelos def index @modelos = Modelo.includes(:perguntas).order(created_at: :desc) end - + # GET /modelos/1 def show end - + # GET /modelos/new def new @modelo = Modelo.new 3.times { @modelo.perguntas.build } # Cria 3 perguntas em branco por padrão end - + # GET /modelos/1/edit def edit @modelo.perguntas.build if @modelo.perguntas.empty? end - + # POST /modelos def create @modelo = Modelo.new(modelo_params) - + if @modelo.save - redirect_to @modelo, notice: 'Modelo criado com sucesso.' + redirect_to @modelo, notice: "Modelo criado com sucesso." else # Garante que tenha pelo menos uma pergunta para mostrar no formulário @modelo.perguntas.build if @modelo.perguntas.empty? render :new, status: :unprocessable_entity end end - + # PATCH/PUT /modelos/1 def update if @modelo.update(modelo_params) - redirect_to @modelo, notice: 'Modelo atualizado com sucesso.' + redirect_to @modelo, notice: "Modelo atualizado com sucesso." else render :edit, status: :unprocessable_entity end end - + # DELETE /modelos/1 def destroy if @modelo.em_uso? - redirect_to modelos_url, alert: 'Não é possível excluir um modelo que está em uso.' + redirect_to modelos_url, alert: "Não é possível excluir um modelo que está em uso." else @modelo.destroy - redirect_to modelos_url, notice: 'Modelo excluído com sucesso.' + redirect_to modelos_url, notice: "Modelo excluído com sucesso." end end - + # POST /modelos/1/clone def clone novo_titulo = "#{@modelo.titulo} (Cópia)" novo_modelo = @modelo.clonar_com_perguntas(novo_titulo) - + if novo_modelo.persisted? - redirect_to edit_modelo_path(novo_modelo), - notice: 'Modelo clonado com sucesso. Edite o título se necessário.' + redirect_to edit_modelo_path(novo_modelo), + notice: "Modelo clonado com sucesso. Edite o título se necessário." else - redirect_to @modelo, alert: 'Erro ao clonar modelo.' + redirect_to @modelo, alert: "Erro ao clonar modelo." end end - + private - + def set_modelo @modelo = Modelo.find(params[:id]) end - + def modelo_params params.require(:modelo).permit( - :titulo, + :titulo, :ativo, perguntas_attributes: [ - :id, - :enunciado, - :tipo, - :opcoes, + :id, + :enunciado, + :tipo, + :opcoes, :_destroy ] ) end - + def require_admin unless Current.session&.user&.eh_admin? - redirect_to root_path, alert: 'Acesso restrito a administradores.' + redirect_to root_path, alert: "Acesso restrito a administradores." end end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 6c35aef970..5794ab5b6a 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,13 +1,13 @@ class PagesController < ApplicationController layout "application" - + def index # Feature 109: Redirecionar alunos para ver suas turmas if Current.session&.user && !Current.session.user.eh_admin? redirect_to avaliacoes_path - return + nil end - + # Admins veem dashboard admin end end diff --git a/app/controllers/respostas_controller.rb b/app/controllers/respostas_controller.rb index ce1639611b..0f00bc81ea 100644 --- a/app/controllers/respostas_controller.rb +++ b/app/controllers/respostas_controller.rb @@ -1,9 +1,9 @@ # app/controllers/respostas_controller.rb class RespostasController < ApplicationController before_action :authenticate_user! - before_action :set_avaliacao, only: [:new, :create] - before_action :verificar_disponibilidade, only: [:new, :create] - before_action :verificar_nao_respondeu, only: [:new, :create] + before_action :set_avaliacao, only: [ :new, :create ] + before_action :verificar_disponibilidade, only: [ :new, :create ] + before_action :verificar_nao_respondeu, only: [ :new, :create ] def index # Feature 109: Listagem de avaliações pendentes (já implementado em pages#index) @@ -14,7 +14,7 @@ def new # Feature 99: Tela para responder avaliação @submissao = Submissao.new @perguntas = @avaliacao.modelo.perguntas.order(:id) - + # Pre-build respostas para nested attributes @perguntas.each do |pergunta| @submissao.respostas.build(pergunta_id: pergunta.id) @@ -27,7 +27,7 @@ def create @submissao.avaliacao = @avaliacao @submissao.aluno = current_user @submissao.data_envio = Time.current - + # Adicionar snapshots nas respostas @submissao.respostas.each do |resposta| if resposta.pergunta_id @@ -38,7 +38,7 @@ def create end end end - + if @submissao.save redirect_to root_path, notice: "Avaliação enviada com sucesso! Obrigado pela sua participação." else @@ -72,7 +72,7 @@ def verificar_nao_respondeu def submissao_params params.require(:submissao).permit( - respostas_attributes: [:pergunta_id, :conteudo, :snapshot_enunciado, :snapshot_opcoes] + respostas_attributes: [ :pergunta_id, :conteudo, :snapshot_enunciado, :snapshot_opcoes ] ) end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 6aa3c6cbf0..ac65c1deb4 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -10,7 +10,7 @@ def create # Tenta autenticar por email ou por login user = User.authenticate_by(email_address: params[:email_address], password: params[:password]) || User.authenticate_by(login: params[:email_address], password: params[:password]) - + if user start_new_session_for user redirect_to after_authentication_url, notice: "Login realizado com sucesso" diff --git a/app/controllers/sigaa_imports_controller.rb b/app/controllers/sigaa_imports_controller.rb index c5e519ae8d..2f208da9b6 100644 --- a/app/controllers/sigaa_imports_controller.rb +++ b/app/controllers/sigaa_imports_controller.rb @@ -8,8 +8,8 @@ def new def create # Usa automaticamente o arquivo class_members.json do projeto - file_path = Rails.root.join('class_members.json') - + file_path = Rails.root.join("class_members.json") + unless File.exist?(file_path) redirect_to new_sigaa_import_path, alert: "Arquivo class_members.json não encontrado no projeto." return @@ -26,15 +26,15 @@ def create # Armazena resultados no cache (session é muito pequena para ~40 usuários) cache_key = "import_results_#{SecureRandom.hex(8)}" Rails.cache.write(cache_key, @results, expires_in: 10.minutes) - + redirect_to success_sigaa_imports_path(key: cache_key) end end def update # Usa automaticamente o arquivo class_members.json do projeto (atualização) - file_path = Rails.root.join('class_members.json') - + file_path = Rails.root.join("class_members.json") + unless File.exist?(file_path) redirect_to new_sigaa_import_path, alert: "Arquivo class_members.json não encontrado no projeto." return @@ -50,7 +50,7 @@ def update # Armazena resultados no cache (session é muito pequena para ~40 usuários) cache_key = "import_results_#{SecureRandom.hex(8)}" Rails.cache.write(cache_key, @results, expires_in: 10.minutes) - + redirect_to success_sigaa_imports_path(key: cache_key) end end @@ -58,12 +58,12 @@ def update def success cache_key = params[:key] @results = Rails.cache.read(cache_key) if cache_key - + unless @results redirect_to root_path, alert: "Nenhum resultado de importação encontrado ou expirado." return end - + # Limpa o cache após carregar (usuário já viu) Rails.cache.delete(cache_key) end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index e03dd9602c..9cc336c995 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -3,18 +3,18 @@ class UserMailer < ApplicationMailer def definicao_senha(user) @user = user @url = "http://localhost:3000/definicao_senha" - mail(to: @user.email_address, subject: 'Definição de Senha - Sistema de Gestão') + mail(to: @user.email_address, subject: "Definição de Senha - Sistema de Gestão") end - + # Email de cadastro com senha temporária (novo método) def cadastro_email(user, senha_temporaria) @user = user @senha = senha_temporaria @login_url = new_session_url - + mail( to: @user.email_address, - subject: 'Bem-vindo(a) ao CAMAAR - Sua senha de acesso' + subject: "Bem-vindo(a) ao CAMAAR - Sua senha de acesso" ) end end diff --git a/app/models/avaliacao.rb b/app/models/avaliacao.rb index 55beaef3c1..64a3d035d4 100644 --- a/app/models/avaliacao.rb +++ b/app/models/avaliacao.rb @@ -1,8 +1,8 @@ class Avaliacao < ApplicationRecord belongs_to :turma belongs_to :modelo - belongs_to :professor_alvo, class_name: 'User', optional: true - - has_many :submissoes, class_name: 'Submissao', dependent: :destroy + belongs_to :professor_alvo, class_name: "User", optional: true + + has_many :submissoes, class_name: "Submissao", dependent: :destroy has_many :respostas, through: :submissoes end diff --git a/app/models/modelo.rb b/app/models/modelo.rb index 20f7c0cb5d..918a42dc22 100644 --- a/app/models/modelo.rb +++ b/app/models/modelo.rb @@ -2,31 +2,31 @@ class Modelo < ApplicationRecord # Relacionamentos has_many :perguntas, dependent: :destroy has_many :avaliacoes, dependent: :restrict_with_error - + # Validações validates :titulo, presence: true, uniqueness: { case_sensitive: false } - + # Validação customizada: não permitir modelo sem perguntas validate :deve_ter_pelo_menos_uma_pergunta, on: :create validate :nao_pode_remover_todas_perguntas, on: :update - + # Aceita atributos aninhados para perguntas - accepts_nested_attributes_for :perguntas, - allow_destroy: true, + accepts_nested_attributes_for :perguntas, + allow_destroy: true, reject_if: :all_blank - + # Método para verificar se modelo está em uso def em_uso? avaliacoes.any? end - + # Método para clonar modelo com perguntas def clonar_com_perguntas(novo_titulo) novo_modelo = dup novo_modelo.titulo = novo_titulo novo_modelo.ativo = false # Clones começam inativos novo_modelo.save - + if novo_modelo.persisted? perguntas.each do |pergunta| nova_pergunta = pergunta.dup @@ -34,18 +34,18 @@ def clonar_com_perguntas(novo_titulo) nova_pergunta.save end end - + novo_modelo end - + private - + def deve_ter_pelo_menos_uma_pergunta if perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? } errors.add(:base, "Um modelo deve ter pelo menos uma pergunta") end end - + def nao_pode_remover_todas_perguntas if persisted? && (perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? }) errors.add(:base, "Não é possível remover todas as perguntas de um modelo existente") diff --git a/app/models/pergunta.rb b/app/models/pergunta.rb index 524a57cc92..9da296b57f 100644 --- a/app/models/pergunta.rb +++ b/app/models/pergunta.rb @@ -1,41 +1,41 @@ class Pergunta < ApplicationRecord - self.table_name = 'perguntas' # Plural correto em português - + self.table_name = "perguntas" # Plural correto em português + # Relacionamentos belongs_to :modelo - has_many :respostas, foreign_key: 'questao_id', dependent: :destroy - + has_many :respostas, foreign_key: "questao_id", dependent: :destroy + # Tipos de perguntas disponíveis TIPOS = { - 'texto_longo' => 'Texto Longo', - 'texto_curto' => 'Texto Curto', - 'multipla_escolha' => 'Múltipla Escolha', - 'checkbox' => 'Checkbox (Múltipla Seleção)', - 'escala' => 'Escala Likert (1-5)', - 'data' => 'Data', - 'hora' => 'Hora' + "texto_longo" => "Texto Longo", + "texto_curto" => "Texto Curto", + "multipla_escolha" => "Múltipla Escolha", + "checkbox" => "Checkbox (Múltipla Seleção)", + "escala" => "Escala Likert (1-5)", + "data" => "Data", + "hora" => "Hora" }.freeze - + # Validações validates :enunciado, presence: true validates :tipo, presence: true, inclusion: { in: TIPOS.keys } - + # Validações condicionais validate :opcoes_requeridas_para_multipla_escolha validate :opcoes_requeridas_para_checkbox - + # Callbacks before_validation :definir_ordem_padrao, on: :create - + # Métodos def tipo_humanizado TIPOS[tipo] || tipo end - + def requer_opcoes? - ['multipla_escolha', 'checkbox'].include?(tipo) + [ "multipla_escolha", "checkbox" ].include?(tipo) end - + def lista_opcoes return [] unless opcoes.present? # Assume que opcoes é JSON array ou string separada por ; @@ -45,36 +45,36 @@ def lista_opcoes begin JSON.parse(opcoes) rescue JSON::ParserError - opcoes.split(';').map(&:strip) + opcoes.split(";").map(&:strip) end else [] end end - + private - + def definir_ordem_padrao if modelo.present? ultima_ordem = modelo.perguntas.maximum(:id) || 0 # Ordem pode ser baseada no ID para simplificar end end - + def opcoes_requeridas_para_multipla_escolha - if tipo == 'multipla_escolha' + if tipo == "multipla_escolha" opcoes_lista = lista_opcoes if opcoes_lista.blank? || opcoes_lista.size < 2 - errors.add(:opcoes, 'deve ter pelo menos duas opções para múltipla escolha') + errors.add(:opcoes, "deve ter pelo menos duas opções para múltipla escolha") end end end - + def opcoes_requeridas_para_checkbox - if tipo == 'checkbox' + if tipo == "checkbox" opcoes_lista = lista_opcoes if opcoes_lista.blank? || opcoes_lista.size < 2 - errors.add(:opcoes, 'deve ter pelo menos duas opções para checkbox') + errors.add(:opcoes, "deve ter pelo menos duas opções para checkbox") end end end diff --git a/app/models/resposta.rb b/app/models/resposta.rb index e10bfad359..02623d7d9d 100644 --- a/app/models/resposta.rb +++ b/app/models/resposta.rb @@ -1,12 +1,11 @@ class Resposta < ApplicationRecord - self.table_name = 'respostas' # Plural correto em português - + self.table_name = "respostas" # Plural correto em português + belongs_to :submissao - belongs_to :pergunta, foreign_key: 'questao_id' # Coluna ainda é questao_id no banco - + belongs_to :pergunta, foreign_key: "questao_id" # Coluna ainda é questao_id no banco + validates :conteudo, presence: true - + # Alias para compatibilidade alias_attribute :pergunta_id, :questao_id end - diff --git a/app/models/submissao.rb b/app/models/submissao.rb index 96389f51d2..8d65a1f31b 100644 --- a/app/models/submissao.rb +++ b/app/models/submissao.rb @@ -1,7 +1,7 @@ class Submissao < ApplicationRecord - self.table_name = 'submissoes' # Plural correto em português - - belongs_to :aluno, class_name: 'User' + self.table_name = "submissoes" # Plural correto em português + + belongs_to :aluno, class_name: "User" belongs_to :avaliacao has_many :respostas, dependent: :destroy diff --git a/app/models/user.rb b/app/models/user.rb index 75c1f8c6e5..00858d7086 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,7 +3,7 @@ class User < ApplicationRecord has_many :sessions, dependent: :destroy has_many :matricula_turmas has_many :turmas, through: :matricula_turmas - has_many :submissoes, class_name: 'Submissao', foreign_key: :aluno_id, dependent: :destroy + has_many :submissoes, class_name: "Submissao", foreign_key: :aluno_id, dependent: :destroy validates :email_address, presence: true, uniqueness: true validates :login, presence: true, uniqueness: true diff --git a/app/services/csv_formatter_service.rb b/app/services/csv_formatter_service.rb index b2d02776a8..b38cf162c0 100644 --- a/app/services/csv_formatter_service.rb +++ b/app/services/csv_formatter_service.rb @@ -1,4 +1,4 @@ -require 'csv' +require "csv" class CsvFormatterService def initialize(avaliacao) @@ -11,18 +11,18 @@ def generate @avaliacao.submissoes.includes(:aluno, :respostas).each do |submissao| aluno = submissao.aluno - row = [aluno.matricula, aluno.nome] - + 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 - + submissao.respostas.each do |resposta| row << resposta.conteudo end - + csv << row end end @@ -32,15 +32,15 @@ def generate def headers # Cabeçalhos estáticos para informações do Aluno - base_headers = ["Matrícula", "Nome"] - + 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/services/sigaa_import_service.rb b/app/services/sigaa_import_service.rb index 2a3995bc15..7d4842387a 100644 --- a/app/services/sigaa_import_service.rb +++ b/app/services/sigaa_import_service.rb @@ -1,5 +1,5 @@ -require 'json' -require 'csv' +require "json" +require "csv" class SigaaImportService def initialize(file_path) @@ -23,9 +23,9 @@ def process begin ActiveRecord::Base.transaction do case File.extname(@file_path).downcase - when '.json' + when ".json" process_json - when '.csv' + when ".csv" process_csv else @results[:errors] << "Formato de arquivo não suportado: #{File.extname(@file_path)}" @@ -50,61 +50,61 @@ def process def process_json data = JSON.parse(File.read(@file_path)) - + # class_members.json é um array de turmas data.each do |turma_data| # Mapeia campos do formato real para o esperado normalized_data = { - 'codigo' => turma_data['code'], - 'nome' => turma_data['code'], # Usa o código como nome se não tiver - 'semestre' => turma_data['semester'], - 'participantes' => [] + "codigo" => turma_data["code"], + "nome" => turma_data["code"], # Usa o código como nome se não tiver + "semestre" => turma_data["semester"], + "participantes" => [] } - + # Processa dicentes (alunos) - if turma_data['dicente'] - turma_data['dicente'].each do |dicente| - normalized_data['participantes'] << { - 'nome' => dicente['nome'], - 'email' => dicente['email'], - 'matricula' => dicente['matricula'] || dicente['usuario'], - 'papel' => 'Discente' + if turma_data["dicente"] + turma_data["dicente"].each do |dicente| + normalized_data["participantes"] << { + "nome" => dicente["nome"], + "email" => dicente["email"], + "matricula" => dicente["matricula"] || dicente["usuario"], + "papel" => "Discente" } end end - + # Processa docente (professor) - if turma_data['docente'] - docente = turma_data['docente'] - normalized_data['participantes'] << { - 'nome' => docente['nome'], - 'email' => docente['email'], - 'matricula' => docente['usuario'], - 'papel' => 'Docente' + if turma_data["docente"] + docente = turma_data["docente"] + normalized_data["participantes"] << { + "nome" => docente["nome"], + "email" => docente["email"], + "matricula" => docente["usuario"], + "papel" => "Docente" } end - + process_turma(normalized_data) end end def process_csv - CSV.foreach(@file_path, headers: true, col_sep: ',') do |row| + CSV.foreach(@file_path, headers: true, col_sep: ",") do |row| # Assumindo estrutura do CSV turma_data = { - 'codigo' => row['codigo_turma'], - 'nome' => row['nome_turma'], - 'semestre' => row['semestre'] + "codigo" => row["codigo_turma"], + "nome" => row["nome_turma"], + "semestre" => row["semestre"] } - + turma = process_turma_record(turma_data) - + if turma&.persisted? user_data = { - 'nome' => row['nome_usuario'], - 'email' => row['email'], - 'matricula' => row['matricula'], - 'papel' => row['papel'] + "nome" => row["nome_usuario"], + "email" => row["email"], + "matricula" => row["matricula"], + "papel" => row["papel"] } process_participante_single(turma, user_data) end @@ -114,16 +114,16 @@ def process_csv def process_turma(data) turma = process_turma_record(data) if turma&.persisted? - process_participantes(turma, data['participantes']) if data['participantes'] + process_participantes(turma, data["participantes"]) if data["participantes"] end end def process_turma_record(data) - turma = Turma.find_or_initialize_by(codigo: data['codigo'], semestre: data['semestre']) - + turma = Turma.find_or_initialize_by(codigo: data["codigo"], semestre: data["semestre"]) + is_new_record = turma.new_record? - turma.nome = data['nome'] - + turma.nome = data["nome"] + if turma.save if is_new_record @results[:turmas_created] += 1 @@ -145,14 +145,14 @@ def process_participantes(turma, participantes_data) def process_participante_single(turma, p_data) # User identificado pela matrícula - user = User.find_or_initialize_by(matricula: p_data['matricula']) - + user = User.find_or_initialize_by(matricula: p_data["matricula"]) + is_new_user = user.new_record? - user.nome = p_data['nome'] - user.email_address = p_data['email'] - + user.nome = p_data["nome"] + user.email_address = p_data["email"] + # Generate login from matricula if not present (assuming matricula is unique and good for login) - user.login = p_data['matricula'] if user.login.blank? + user.login = p_data["matricula"] if user.login.blank? generated_password = nil if is_new_user @@ -163,7 +163,7 @@ def process_participante_single(turma, p_data) if user.save if is_new_user @results[:users_created] += 1 - + # Armazena credenciais do novo usuário para exibir depois @results[:new_users] << { matricula: user.matricula, @@ -172,15 +172,15 @@ def process_participante_single(turma, p_data) password: generated_password, email: user.email_address } - + # Envia email com senha para novo usuário (COMENTADO - muito lento) # UserMailer.cadastro_email(user, generated_password).deliver_now else @results[:users_updated] += 1 end - + matricula = MatriculaTurma.find_or_initialize_by(turma: turma, user: user) - matricula.papel = p_data['papel'] + matricula.papel = p_data["papel"] matricula.save! else @results[:errors] << "Erro ao salvar usuário #{p_data['matricula']}: #{user.errors.full_messages.join(', ')}" diff --git a/app/views/modelos/show.html.erb b/app/views/modelos/show.html.erb index f1de7c38c8..419b85d609 100644 --- a/app/views/modelos/show.html.erb +++ b/app/views/modelos/show.html.erb @@ -1,69 +1,72 @@ <%# app/views/modelos/show.html.erb %> +<%#
-

<%= @modelo.titulo %>

+

<%= @modelo.titulo %> <%#

Criado em <%= l(@modelo.created_at, format: :long) %> - + <%# <%= pluralize(@modelo.perguntas.count, 'questão', 'questões') %> - + <%#
-
- -
+
%> + + + <%#
<%= link_to edit_modelo_path(@modelo), class: "btn-secondary flex items-center gap-2" do %> - + <%# - Editar - <% end %> + Editar %> + <%# end %> + - <%= link_to modelos_path, class: "btn-secondary flex items-center gap-2" do %> - + <%# <%= link_to modelos_path, class: "btn-secondary flex items-center gap-2" do %> + <%# Voltar <% end %> -
+ <%#

Questões do Modelo

-
+
%> -
+ <%#
<% @modelo.perguntas.order(:ordem).each do |pergunta| %> -
-
+ <%#
%> + <%#
<%= pergunta.ordem %> - -
- -
+ <%# +
+ %> + <%#

<%= pergunta.enunciado %> -

+ <%# <%= pergunta.tipo_humanizado %> - + <%#
<% if pergunta.requer_opcoes? && pergunta.lista_opcoes.any? %> -
+ <%#

Opções:

<% pergunta.lista_opcoes.each do |opcao| %> - \ No newline at end of file diff --git a/config/initializers/inflections_custom.rb b/config/initializers/inflections_custom.rb index c1294dd5bf..6e5954f1c8 100644 --- a/config/initializers/inflections_custom.rb +++ b/config/initializers/inflections_custom.rb @@ -1,3 +1,3 @@ ActiveSupport::Inflector.inflections(:en) do |inflect| - inflect.irregular 'avaliacao', 'avaliacoes' + inflect.irregular "avaliacao", "avaliacoes" end diff --git a/config/initializers/mail.rb b/config/initializers/mail.rb index 12a43ca11b..c8d948db41 100644 --- a/config/initializers/mail.rb +++ b/config/initializers/mail.rb @@ -5,25 +5,25 @@ # Em testes: captura emails sem enviar Rails.application.config.action_mailer.delivery_method = :test Rails.application.config.action_mailer.default_url_options = { - host: 'localhost', + host: "localhost", port: 3000 } - + elsif Rails.env.development? # MVP: Usa letter_opener (emails abrem no navegador) # Instale: gem install letter_opener ou adicione ao Gemfile # PRODUÇÃO: Para enviar emails reais, descomente a seção SMTP abaixo - + Rails.application.config.action_mailer.delivery_method = :letter_opener Rails.application.config.action_mailer.perform_deliveries = true - + # === SMTP (Para Produção) === # Descomente as linhas abaixo e configure variáveis de ambiente (.env) # para enviar emails reais via SMTP (Gmail, Sendgrid, etc.) # # Rails.application.config.action_mailer.delivery_method = :smtp # Rails.application.config.action_mailer.raise_delivery_errors = true - # + # # Rails.application.config.action_mailer.smtp_settings = { # address: ENV.fetch('SMTP_ADDRESS', 'smtp.gmail.com'), # port: ENV.fetch('SMTP_PORT', '587').to_i, @@ -33,33 +33,32 @@ # authentication: 'plain', # enable_starttls_auto: true # } - + Rails.application.config.action_mailer.default_url_options = { - host: ENV.fetch('APP_HOST', 'localhost'), - port: ENV.fetch('APP_PORT', '3000').to_i + host: ENV.fetch("APP_HOST", "localhost"), + port: ENV.fetch("APP_PORT", "3000").to_i } - + else # Produção: SMTP obrigatório Rails.application.config.action_mailer.delivery_method = :smtp Rails.application.config.action_mailer.perform_deliveries = true Rails.application.config.action_mailer.raise_delivery_errors = false - + Rails.application.config.action_mailer.smtp_settings = { - address: ENV.fetch('SMTP_ADDRESS'), - port: ENV.fetch('SMTP_PORT', '587').to_i, - domain: ENV.fetch('SMTP_DOMAIN'), - user_name: ENV.fetch('SMTP_USER'), - password: ENV.fetch('SMTP_PASSWORD'), - authentication: 'plain', + address: ENV.fetch("SMTP_ADDRESS"), + port: ENV.fetch("SMTP_PORT", "587").to_i, + domain: ENV.fetch("SMTP_DOMAIN"), + user_name: ENV.fetch("SMTP_USER"), + password: ENV.fetch("SMTP_PASSWORD"), + authentication: "plain", enable_starttls_auto: true, open_timeout: 10, read_timeout: 10 } - + Rails.application.config.action_mailer.default_url_options = { - host: ENV.fetch('APP_HOST'), - protocol: 'https' + host: ENV.fetch("APP_HOST"), + protocol: "https" } end - diff --git a/config/routes.rb b/config/routes.rb index 6952068b76..6f02dcfb55 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,6 @@ Rails.application.routes.draw do # --- ROTAS DE AVALIACOES --- - resources :avaliacoes, only: [:index, :create] do + resources :avaliacoes, only: [ :index, :create ] do collection do get :gestao_envios end @@ -8,11 +8,11 @@ get :resultados end # Rotas para alunos responderem avaliações (Feature 99) - resources :respostas, only: [:new, :create] + resources :respostas, only: [ :new, :create ] end # --- ROTAS DE IMPORTAÇÃO SIGAA --- - resources :sigaa_imports, only: [:new, :create] do + resources :sigaa_imports, only: [ :new, :create ] do collection do post :update # For update/sync operations get :success # For showing import results diff --git a/coverage/.last_run.json b/coverage/.last_run.json new file mode 100644 index 0000000000..633e3c6254 --- /dev/null +++ b/coverage/.last_run.json @@ -0,0 +1,5 @@ +{ + "result": { + "line": 92.18 + } +} diff --git a/coverage/.resultset.json b/coverage/.resultset.json index 192ddb36c0..93c34bafd1 100644 --- a/coverage/.resultset.json +++ b/coverage/.resultset.json @@ -1,31 +1,50 @@ { "RSpec": { "coverage": { - "/home/marcos/sprint3/CAMAAR/spec/models/avaliacao_spec.rb": { + "/home/marcos/testesPedro/CAMAAR/spec/mailers/user_mailer_spec.rb": { "lines": [ 1, null, 1, 1, + null, + 1, + 2, + null, + 3, + null, + null, + null, 1, 1, + 0, null, null, 1, 1, null, null, + null, 1, 1, + 2, + null, + 3, + 3, + null, 1, 1, 1, null, null, + 1, + 1, + null, + null, null ] }, - "/home/marcos/sprint3/CAMAAR/spec/rails_helper.rb": { + "/home/marcos/testesPedro/CAMAAR/spec/rails_helper.rb": { "lines": [ null, 1, @@ -101,7 +120,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/config/environment.rb": { + "/home/marcos/testesPedro/CAMAAR/config/environment.rb": { "lines": [ null, 1, @@ -110,7 +129,7 @@ 1 ] }, - "/home/marcos/sprint3/CAMAAR/config/application.rb": { + "/home/marcos/testesPedro/CAMAAR/config/application.rb": { "lines": [ 1, null, @@ -141,7 +160,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/config/boot.rb": { + "/home/marcos/testesPedro/CAMAAR/config/boot.rb": { "lines": [ 1, null, @@ -149,7 +168,7 @@ 1 ] }, - "/home/marcos/sprint3/CAMAAR/config/environments/test.rb": { + "/home/marcos/testesPedro/CAMAAR/config/environments/test.rb": { "lines": [ null, null, @@ -210,7 +229,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/config/initializers/assets.rb": { + "/home/marcos/testesPedro/CAMAAR/config/initializers/assets.rb": { "lines": [ null, null, @@ -221,7 +240,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/config/initializers/content_security_policy.rb": { + "/home/marcos/testesPedro/CAMAAR/config/initializers/content_security_policy.rb": { "lines": [ null, null, @@ -250,7 +269,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/config/initializers/filter_parameter_logging.rb": { + "/home/marcos/testesPedro/CAMAAR/config/initializers/filter_parameter_logging.rb": { "lines": [ null, null, @@ -262,7 +281,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/config/initializers/inflections.rb": { + "/home/marcos/testesPedro/CAMAAR/config/initializers/inflections.rb": { "lines": [ null, null, @@ -283,14 +302,14 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/config/initializers/inflections_custom.rb": { + "/home/marcos/testesPedro/CAMAAR/config/initializers/inflections_custom.rb": { "lines": [ 1, 1, null ] }, - "/home/marcos/sprint3/CAMAAR/config/initializers/mail.rb": { + "/home/marcos/testesPedro/CAMAAR/config/initializers/mail.rb": { "lines": [ null, null, @@ -359,44 +378,199 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/app/helpers/application_helper.rb": { + "/home/marcos/testesPedro/CAMAAR/app/helpers/application_helper.rb": { + "lines": [ + 1, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/app/helpers/avaliacoes_helper.rb": { + "lines": [ + 1, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/app/helpers/home_helper.rb": { + "lines": [ + 1, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/app/mailers/user_mailer.rb": { + "lines": [ + 1, + null, + 1, + 2, + 2, + 2, + null, + null, + null, + 1, + 2, + 2, + 2, + null, + 2, + null, + null, + null, + null, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/app/mailers/application_mailer.rb": { + "lines": [ + 1, + 1, + 1, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/spec/models/avaliacao_spec.rb": { "lines": [ 1, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, null ] }, - "/home/marcos/sprint3/CAMAAR/app/helpers/avaliacoes_helper.rb": { + "/home/marcos/testesPedro/CAMAAR/app/models/avaliacao.rb": { "lines": [ + 1, + 1, + 1, + 1, + null, + 1, 1, null ] }, - "/home/marcos/sprint3/CAMAAR/app/helpers/home_helper.rb": { + "/home/marcos/testesPedro/CAMAAR/app/models/application_record.rb": { "lines": [ + 1, 1, null ] }, - "/home/marcos/sprint3/CAMAAR/app/models/avaliacao.rb": { + "/home/marcos/testesPedro/CAMAAR/spec/models/matricula_turma_spec.rb": { "lines": [ + 1, + null, 1, 1, 1, 1, + 1, + null, null, 1, 1, + 1, + null, + null, null ] }, - "/home/marcos/sprint3/CAMAAR/app/models/application_record.rb": { + "/home/marcos/testesPedro/CAMAAR/app/models/MatriculaTurma.rb": { "lines": [ + 1, 1, 1, null ] }, - "/home/marcos/sprint3/CAMAAR/spec/requests/avaliacoes_spec.rb": { + "/home/marcos/testesPedro/CAMAAR/spec/models/turma_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/app/models/turma.rb": { + "lines": [ + 1, + 1, + 1, + 1, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/spec/models/user_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + null, + null, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/app/models/user.rb": { + "lines": [ + 1, + 1, + 1, + 1, + 1, + 1, + null, + 1, + 1, + 1, + 1, + null, + 15, + 7, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/spec/requests/avaliacoes_spec.rb": { "lines": [ 1, null, @@ -450,7 +624,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/spec/services/csv_formatter_service_spec.rb": { + "/home/marcos/testesPedro/CAMAAR/spec/services/csv_formatter_service_spec.rb": { "lines": [ 1, null, @@ -498,7 +672,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/app/services/csv_formatter_service.rb": { + "/home/marcos/testesPedro/CAMAAR/app/services/csv_formatter_service.rb": { "lines": [ 1, null, @@ -548,7 +722,300 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/config/routes.rb": { + "/home/marcos/testesPedro/CAMAAR/spec/services/sigaa_import_service_spec.rb": { + "lines": [ + 1, + 1, + 1, + 1, + null, + 1, + 4, + 4, + 4, + null, + 1, + null, + 3, + 3, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 3, + null, + null, + null, + null, + 3, + 3, + 3, + 3, + null, + null, + 1, + 1, + 0, + null, + null, + null, + 1, + 1, + 0, + null, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + 1, + 1, + 1, + null, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + 1, + 1, + 1, + null, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + 1, + 0, + null, + null, + null, + null, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/app/services/sigaa_import_service.rb": { + "lines": [ + 1, + 1, + null, + 1, + 1, + 3, + 3, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + 3, + 0, + 0, + null, + null, + null, + 3, + 3, + null, + 1, + null, + 1, + null, + 1, + null, + null, + 3, + 1, + null, + null, + null, + 0, + null, + 0, + null, + 0, + null, + null, + 3, + null, + null, + 1, + null, + 1, + 1, + null, + null, + 1, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + 1, + 1, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + 1, + null, + 1, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 2, + null, + 2, + 2, + null, + 2, + 2, + 2, + null, + 0, + null, + 2, + null, + 0, + 0, + null, + null, + null, + 1, + 1, + 2, + null, + null, + null, + 1, + null, + 3, + null, + 3, + 3, + 3, + null, + null, + 3, + null, + 3, + 3, + 3, + 3, + null, + null, + 3, + 3, + 3, + null, + null, + 3, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 0, + null, + null, + 3, + 3, + 3, + null, + 0, + null, + null, + null + ] + }, + "/home/marcos/testesPedro/CAMAAR/config/routes.rb": { "lines": [ 1, null, @@ -608,7 +1075,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/app/controllers/avaliacoes_controller.rb": { + "/home/marcos/testesPedro/CAMAAR/app/controllers/avaliacoes_controller.rb": { "lines": [ 1, null, @@ -684,7 +1151,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/app/controllers/application_controller.rb": { + "/home/marcos/testesPedro/CAMAAR/app/controllers/application_controller.rb": { "lines": [ 1, 1, @@ -697,7 +1164,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/app/controllers/concerns/authentication.rb": { + "/home/marcos/testesPedro/CAMAAR/app/controllers/concerns/authentication.rb": { "lines": [ 1, 1, @@ -753,7 +1220,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/app/controllers/concerns/authenticatable.rb": { + "/home/marcos/testesPedro/CAMAAR/app/controllers/concerns/authenticatable.rb": { "lines": [ null, 1, @@ -777,24 +1244,15 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/app/models/current.rb": { - "lines": [ - 1, - 1, - 1, - null - ] - }, - "/home/marcos/sprint3/CAMAAR/app/models/turma.rb": { + "/home/marcos/testesPedro/CAMAAR/app/models/current.rb": { "lines": [ 1, 1, 1, - 1, null ] }, - "/home/marcos/sprint3/CAMAAR/app/models/modelo.rb": { + "/home/marcos/testesPedro/CAMAAR/app/models/modelo.rb": { "lines": [ 1, null, @@ -852,7 +1310,7 @@ null ] }, - "/home/marcos/sprint3/CAMAAR/app/models/pergunta.rb": { + "/home/marcos/testesPedro/CAMAAR/app/models/pergunta.rb": { "lines": [ 1, 1, @@ -938,6 +1396,6 @@ ] } }, - "timestamp": 1765372573 + "timestamp": 1765487772 } } diff --git a/coverage/index.html b/coverage/index.html index 1a913889ed..bb9faba696 100644 --- a/coverage/index.html +++ b/coverage/index.html @@ -13,7 +13,7 @@ loading
-
Generated 2025-12-10T10:16:13-03:00
+
Generated 2025-12-11T18:16:12-03:00
    @@ -22,14 +22,14 @@

    All Files ( - 69.81% + 78.89% covered at - - 0.78 + + 1.06 hits/line ) @@ -38,15 +38,15 @@

    - 29 files in total. + 39 files in total.
    - 318 relevant lines, - 222 lines covered and - 96 lines missed. + 521 relevant lines, + 411 lines covered and + 110 lines missed. ( - 69.81% + 78.89% )
    @@ -70,7 +70,7 @@

    - app/controllers/application_controller.rb + app/controllers/application_controller.rb 100.00 % 9 5 @@ -81,7 +81,7 @@

    - app/controllers/avaliacoes_controller.rb + app/controllers/avaliacoes_controller.rb 15.15 % 72 33 @@ -92,7 +92,7 @@

    - app/controllers/concerns/authenticatable.rb + app/controllers/concerns/authenticatable.rb 70.00 % 20 10 @@ -103,7 +103,7 @@

    - app/controllers/concerns/authentication.rb + app/controllers/concerns/authentication.rb 72.41 % 52 29 @@ -114,7 +114,7 @@

    - app/helpers/application_helper.rb + app/helpers/application_helper.rb 100.00 % 2 1 @@ -125,7 +125,7 @@

    - app/helpers/avaliacoes_helper.rb + app/helpers/avaliacoes_helper.rb 100.00 % 2 1 @@ -136,7 +136,7 @@

    - app/helpers/home_helper.rb + app/helpers/home_helper.rb 100.00 % 2 1 @@ -147,7 +147,40 @@

    - app/models/application_record.rb + app/mailers/application_mailer.rb + 100.00 % + 4 + 3 + 3 + 0 + 1.00 + + + + + app/mailers/user_mailer.rb + 100.00 % + 20 + 10 + 10 + 0 + 1.70 + + + + + app/models/MatriculaTurma.rb + 100.00 % + 4 + 3 + 3 + 0 + 1.00 + + + + + app/models/application_record.rb 100.00 % 3 2 @@ -158,7 +191,7 @@

    - app/models/avaliacao.rb + app/models/avaliacao.rb 100.00 % 8 6 @@ -169,7 +202,7 @@

    - app/models/current.rb + app/models/current.rb 100.00 % 4 3 @@ -180,7 +213,7 @@

    - app/models/modelo.rb + app/models/modelo.rb 51.85 % 54 27 @@ -191,7 +224,7 @@

    - app/models/pergunta.rb + app/models/pergunta.rb 47.22 % 81 36 @@ -202,7 +235,7 @@

    - app/models/turma.rb + app/models/turma.rb 100.00 % 5 4 @@ -213,7 +246,18 @@

    - app/services/csv_formatter_service.rb + app/models/user.rb + 100.00 % + 15 + 12 + 12 + 0 + 2.67 + + + + + app/services/csv_formatter_service.rb 100.00 % 46 19 @@ -224,7 +268,18 @@

    - config/application.rb + app/services/sigaa_import_service.rb + 87.01 % + 189 + 77 + 67 + 10 + 1.57 + + + + + config/application.rb 100.00 % 27 7 @@ -235,7 +290,7 @@

    - config/boot.rb + config/boot.rb 100.00 % 4 3 @@ -246,7 +301,7 @@

    - config/environment.rb + config/environment.rb 100.00 % 5 2 @@ -257,7 +312,7 @@

    - config/environments/test.rb + config/environments/test.rb 100.00 % 57 15 @@ -268,7 +323,7 @@

    - config/initializers/assets.rb + config/initializers/assets.rb 100.00 % 7 1 @@ -279,7 +334,7 @@

    - config/initializers/content_security_policy.rb + config/initializers/content_security_policy.rb 100.00 % 25 0 @@ -290,7 +345,7 @@

    - config/initializers/filter_parameter_logging.rb + config/initializers/filter_parameter_logging.rb 100.00 % 8 1 @@ -301,7 +356,7 @@

    - config/initializers/inflections.rb + config/initializers/inflections.rb 100.00 % 17 2 @@ -312,7 +367,7 @@

    - config/initializers/inflections_custom.rb + config/initializers/inflections_custom.rb 100.00 % 3 2 @@ -323,7 +378,7 @@

    - config/initializers/mail.rb + config/initializers/mail.rb 25.00 % 65 12 @@ -334,7 +389,7 @@

    - config/routes.rb + config/routes.rb 100.00 % 56 23 @@ -345,7 +400,18 @@

    - spec/models/avaliacao_spec.rb + spec/mailers/user_mailer_spec.rb + 95.24 % + 39 + 21 + 20 + 1 + 1.33 + + + + + spec/models/avaliacao_spec.rb 100.00 % 20 12 @@ -356,7 +422,40 @@

    - spec/rails_helper.rb + spec/models/matricula_turma_spec.rb + 100.00 % + 15 + 9 + 9 + 0 + 1.00 + + + + + spec/models/turma_spec.rb + 100.00 % + 16 + 10 + 10 + 0 + 1.00 + + + + + spec/models/user_spec.rb + 100.00 % + 20 + 11 + 11 + 0 + 1.00 + + + + + spec/rails_helper.rb 90.91 % 72 11 @@ -367,7 +466,7 @@

    - spec/requests/avaliacoes_spec.rb + spec/requests/avaliacoes_spec.rb 50.00 % 50 30 @@ -378,7 +477,7 @@

    - spec/services/csv_formatter_service_spec.rb + spec/services/csv_formatter_service_spec.rb 100.00 % 44 20 @@ -388,6 +487,17 @@

    + + spec/services/sigaa_import_service_spec.rb + 93.62 % + 96 + 47 + 44 + 3 + 1.43 + + +

    @@ -448,7 +558,7 @@

    - app/controllers/application_controller.rb + app/controllers/application_controller.rb 100.00 % 9 5 @@ -459,7 +569,7 @@

    - app/controllers/avaliacoes_controller.rb + app/controllers/avaliacoes_controller.rb 15.15 % 72 33 @@ -470,7 +580,7 @@

    - app/controllers/concerns/authenticatable.rb + app/controllers/concerns/authenticatable.rb 70.00 % 20 10 @@ -481,7 +591,7 @@

    - app/controllers/concerns/authentication.rb + app/controllers/concerns/authentication.rb 72.41 % 52 29 @@ -502,14 +612,14 @@

    Models ( - 58.97% + 65.59% covered at - 0.64 + 0.91 hits/line ) @@ -518,15 +628,15 @@

    - 6 files in total. + 8 files in total.
    - 78 relevant lines, - 46 lines covered and + 93 relevant lines, + 61 lines covered and 32 lines missed. ( - 58.97% + 65.59% )
    @@ -550,7 +660,18 @@

    - app/models/application_record.rb + app/models/MatriculaTurma.rb + 100.00 % + 4 + 3 + 3 + 0 + 1.00 + + + + + app/models/application_record.rb 100.00 % 3 2 @@ -561,7 +682,7 @@

    - app/models/avaliacao.rb + app/models/avaliacao.rb 100.00 % 8 6 @@ -572,7 +693,7 @@

    - app/models/current.rb + app/models/current.rb 100.00 % 4 3 @@ -583,7 +704,7 @@

    - app/models/modelo.rb + app/models/modelo.rb 51.85 % 54 27 @@ -594,7 +715,7 @@

    - app/models/pergunta.rb + app/models/pergunta.rb 47.22 % 81 36 @@ -605,7 +726,7 @@

    - app/models/turma.rb + app/models/turma.rb 100.00 % 5 4 @@ -615,6 +736,17 @@

    + + app/models/user.rb + 100.00 % + 15 + 12 + 12 + 0 + 2.67 + + +

    @@ -626,14 +758,14 @@

    Ungrouped ( - 84.66% + 88.89% covered at - - 0.99 + + 1.22 hits/line ) @@ -642,15 +774,15 @@

    - 19 files in total. + 27 files in total.
    - 163 relevant lines, - 138 lines covered and - 25 lines missed. + 351 relevant lines, + 312 lines covered and + 39 lines missed. ( - 84.66% + 88.89% )
    @@ -674,7 +806,7 @@

    - app/helpers/application_helper.rb + app/helpers/application_helper.rb 100.00 % 2 1 @@ -685,7 +817,7 @@

    - app/helpers/avaliacoes_helper.rb + app/helpers/avaliacoes_helper.rb 100.00 % 2 1 @@ -696,7 +828,7 @@

    - app/helpers/home_helper.rb + app/helpers/home_helper.rb 100.00 % 2 1 @@ -707,7 +839,29 @@

    - app/services/csv_formatter_service.rb + app/mailers/application_mailer.rb + 100.00 % + 4 + 3 + 3 + 0 + 1.00 + + + + + app/mailers/user_mailer.rb + 100.00 % + 20 + 10 + 10 + 0 + 1.70 + + + + + app/services/csv_formatter_service.rb 100.00 % 46 19 @@ -718,7 +872,18 @@

    - config/application.rb + app/services/sigaa_import_service.rb + 87.01 % + 189 + 77 + 67 + 10 + 1.57 + + + + + config/application.rb 100.00 % 27 7 @@ -729,7 +894,7 @@

    - config/boot.rb + config/boot.rb 100.00 % 4 3 @@ -740,7 +905,7 @@

    - config/environment.rb + config/environment.rb 100.00 % 5 2 @@ -751,7 +916,7 @@

    - config/environments/test.rb + config/environments/test.rb 100.00 % 57 15 @@ -762,7 +927,7 @@

    - config/initializers/assets.rb + config/initializers/assets.rb 100.00 % 7 1 @@ -773,7 +938,7 @@

    - config/initializers/content_security_policy.rb + config/initializers/content_security_policy.rb 100.00 % 25 0 @@ -784,7 +949,7 @@

    - config/initializers/filter_parameter_logging.rb + config/initializers/filter_parameter_logging.rb 100.00 % 8 1 @@ -795,7 +960,7 @@

    - config/initializers/inflections.rb + config/initializers/inflections.rb 100.00 % 17 2 @@ -806,7 +971,7 @@

    - config/initializers/inflections_custom.rb + config/initializers/inflections_custom.rb 100.00 % 3 2 @@ -817,7 +982,7 @@

    - config/initializers/mail.rb + config/initializers/mail.rb 25.00 % 65 12 @@ -828,7 +993,7 @@

    - config/routes.rb + config/routes.rb 100.00 % 56 23 @@ -839,7 +1004,18 @@

    - spec/models/avaliacao_spec.rb + spec/mailers/user_mailer_spec.rb + 95.24 % + 39 + 21 + 20 + 1 + 1.33 + + + + + spec/models/avaliacao_spec.rb 100.00 % 20 12 @@ -850,7 +1026,40 @@

    - spec/rails_helper.rb + spec/models/matricula_turma_spec.rb + 100.00 % + 15 + 9 + 9 + 0 + 1.00 + + + + + spec/models/turma_spec.rb + 100.00 % + 16 + 10 + 10 + 0 + 1.00 + + + + + spec/models/user_spec.rb + 100.00 % + 20 + 11 + 11 + 0 + 1.00 + + + + + spec/rails_helper.rb 90.91 % 72 11 @@ -861,7 +1070,7 @@

    - spec/requests/avaliacoes_spec.rb + spec/requests/avaliacoes_spec.rb 50.00 % 50 30 @@ -872,7 +1081,7 @@

    - spec/services/csv_formatter_service_spec.rb + spec/services/csv_formatter_service_spec.rb 100.00 % 44 20 @@ -882,6 +1091,17 @@

    + + spec/services/sigaa_import_service_spec.rb + 93.62 % + 96 + 47 + 44 + 3 + 1.43 + + +

    @@ -898,7 +1118,7 @@

    -
    +

    app/controllers/application_controller.rb

    @@ -1029,7 +1249,7 @@

    -
    +

    app/controllers/avaliacoes_controller.rb

    @@ -1790,7 +2010,7 @@

    -
    +

    app/controllers/concerns/authenticatable.rb

    @@ -2035,7 +2255,7 @@

    -
    +

    app/controllers/concerns/authentication.rb

    @@ -2628,7 +2848,7 @@

    -
    +

    app/helpers/application_helper.rb

    @@ -2681,7 +2901,7 @@

    -
    +

    app/helpers/avaliacoes_helper.rb

    @@ -2734,7 +2954,7 @@

    -
    +

    app/helpers/home_helper.rb

    @@ -2787,9 +3007,9 @@

    -
    +
    -

    app/models/application_record.rb

    +

    app/mailers/application_mailer.rb

    100.0% @@ -2801,8 +3021,8 @@

    - 2 relevant lines. - 2 lines covered and + 3 relevant lines. + 3 lines covered and 0 lines missed.
    @@ -2821,7 +3041,7 @@

    - class ApplicationRecord < ActiveRecord::Base + class ApplicationMailer < ActionMailer::Base

    @@ -2833,12 +3053,24 @@

    - primary_abstract_class + default from: "from@example.com"

    -
  • +
  • + + 1 + + + + + layout "mailer" +
  • +
    + +
    +
  • @@ -2852,9 +3084,9 @@

  • -
    +
    -

    app/models/avaliacao.rb

    +

    app/mailers/user_mailer.rb

    100.0% @@ -2866,8 +3098,8 @@

    - 6 relevant lines. - 6 lines covered and + 10 relevant lines. + 10 lines covered and 0 lines missed.
    @@ -2886,19 +3118,17 @@

    - class Avaliacao < ApplicationRecord + class UserMailer < ApplicationMailer

    -
  • - - 1 +
  • - belongs_to :turma + # Email para definição de senha (método existente)
  • @@ -2910,53 +3140,53 @@

    - belongs_to :modelo + def definicao_senha(user)

    -
  • +
  • - 1 + 2 - belongs_to :professor_alvo, class_name: 'User', optional: true + @user = user
  • -
  • +
  • + + 2 - + @url = "http://localhost:3000/definicao_senha"
  • -
  • +
  • - 1 + 2 - has_many :submissoes, class_name: 'Submissao', dependent: :destroy + mail(to: @user.email_address, subject: 'Definição de Senha - Sistema de Gestão')
  • -
  • - - 1 +
  • - has_many :respostas, through: :submissoes + end
  • @@ -2966,32 +3196,162 @@

    - end +

    - - -
    +
    +
  • + - -
    -
    -

    app/models/current.rb

    -

    - - 100.0% - + - lines covered -

    + # Email de cadastro com senha temporária (novo método) +
  • +
    + +
    +
  • + + 1 + - + -
    - 3 relevant lines. - 3 lines covered and - 0 lines missed. + def cadastro_email(user, senha_temporaria) +
  • +
    + +
    +
  • + + 2 + + + + + @user = user +
  • +
    + +
    +
  • + + 2 + + + + + @senha = senha_temporaria +
  • +
    + +
    +
  • + + 2 + + + + + @login_url = new_session_url +
  • +
    + +
    +
  • + + + + + +
  • +
    + +
    +
  • + + 2 + + + + + mail( +
  • +
    + +
    +
  • + + + + + to: @user.email_address, +
  • +
    + +
    +
  • + + + + + subject: 'Bem-vindo(a) ao CAMAAR - Sua senha de acesso' +
  • +
    + +
    +
  • + + + + + ) +
  • +
    + +
    +
  • + + + + + end +
  • +
    + +
    +
  • + + + + + end +
  • +
    + + + +
    + + +
    +
    +

    app/models/MatriculaTurma.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 3 relevant lines. + 3 lines covered and + 0 lines missed.
    @@ -3009,7 +3369,7 @@

    - class Current < ActiveSupport::CurrentAttributes + class MatriculaTurma < ApplicationRecord

    @@ -3021,7 +3381,7 @@

    - attribute :session + belongs_to :user

    @@ -3033,7 +3393,7 @@

    - delegate :user, to: :session, allow_nil: true + belongs_to :turma

    @@ -3052,12 +3412,12 @@

    -
    +
    -

    app/models/modelo.rb

    +

    app/models/application_record.rb

    - - 51.85% + + 100.0% lines covered @@ -3066,583 +3426,5078 @@

    - 27 relevant lines. - 14 lines covered and - 13 lines missed. + 2 relevant lines. + 2 lines covered and + 0 lines missed. +
    + + + +

    + +
    +    
      + +
      +
    1. + + 1 + + + + + class ApplicationRecord < ActiveRecord::Base +
    2. +
      + +
      +
    3. + + 1 + + + + + primary_abstract_class +
    4. +
      + +
      +
    5. + + + + + end +
    6. +
      + +
    +
    +
    + + +
    +
    +

    app/models/avaliacao.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 6 relevant lines. + 6 lines covered and + 0 lines missed.
    -
    -    
      +
      +    
        + +
        +
      1. + + 1 + + + + + class Avaliacao < ApplicationRecord +
      2. +
        + +
        +
      3. + + 1 + + + + + belongs_to :turma +
      4. +
        + +
        +
      5. + + 1 + + + + + belongs_to :modelo +
      6. +
        + +
        +
      7. + + 1 + + + + + belongs_to :professor_alvo, class_name: 'User', optional: true +
      8. +
        + +
        +
      9. + + + + + +
      10. +
        + +
        +
      11. + + 1 + + + + + has_many :submissoes, class_name: 'Submissao', dependent: :destroy +
      12. +
        + +
        +
      13. + + 1 + + + + + has_many :respostas, through: :submissoes +
      14. +
        + +
        +
      15. + + + + + end +
      16. +
        + +
      +
      +
    + + +
    +
    +

    app/models/current.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 3 relevant lines. + 3 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      + +
      +
    1. + + 1 + + + + + class Current < ActiveSupport::CurrentAttributes +
    2. +
      + +
      +
    3. + + 1 + + + + + attribute :session +
    4. +
      + +
      +
    5. + + 1 + + + + + delegate :user, to: :session, allow_nil: true +
    6. +
      + +
      +
    7. + + + + + end +
    8. +
      + +
    +
    +
    + + +
    +
    +

    app/models/modelo.rb

    +

    + + 51.85% + + + lines covered +

    + + + +
    + 27 relevant lines. + 14 lines covered and + 13 lines missed. +
    + + + +
    + +
    +    
      + +
      +
    1. + + 1 + + + + + class Modelo < ApplicationRecord +
    2. +
      + +
      +
    3. + + + + + # Relacionamentos +
    4. +
      + +
      +
    5. + + 1 + + + + + has_many :perguntas, dependent: :destroy +
    6. +
      + +
      +
    7. + + 1 + + + + + has_many :avaliacoes, dependent: :restrict_with_error +
    8. +
      + +
      +
    9. + + + + + +
    10. +
      + +
      +
    11. + + + + + # Validações +
    12. +
      + +
      +
    13. + + 1 + + + + + validates :titulo, presence: true, uniqueness: { case_sensitive: false } +
    14. +
      + +
      +
    15. + + + + + +
    16. +
      + +
      +
    17. + + + + + # Validação customizada: não permitir modelo sem perguntas +
    18. +
      + +
      +
    19. + + 1 + + + + + validate :deve_ter_pelo_menos_uma_pergunta, on: :create +
    20. +
      + +
      +
    21. + + 1 + + + + + validate :nao_pode_remover_todas_perguntas, on: :update +
    22. +
      + +
      +
    23. + + + + + +
    24. +
      + +
      +
    25. + + + + + # Aceita atributos aninhados para perguntas +
    26. +
      + +
      +
    27. + + 1 + + + + + accepts_nested_attributes_for :perguntas, +
    28. +
      + +
      +
    29. + + + + + allow_destroy: true, +
    30. +
      + +
      +
    31. + + + + + reject_if: :all_blank +
    32. +
      + +
      +
    33. + + + + + +
    34. +
      + +
      +
    35. + + + + + # Método para verificar se modelo está em uso +
    36. +
      + +
      +
    37. + + 1 + + + + + def em_uso? +
    38. +
      + +
      +
    39. + + + + + avaliacoes.any? +
    40. +
      + +
      +
    41. + + + + + end +
    42. +
      + +
      +
    43. + + + + + +
    44. +
      + +
      +
    45. + + + + + # Método para clonar modelo com perguntas +
    46. +
      + +
      +
    47. + + 1 + + + + + def clonar_com_perguntas(novo_titulo) +
    48. +
      + +
      +
    49. + + + + + novo_modelo = dup +
    50. +
      + +
      +
    51. + + + + + novo_modelo.titulo = novo_titulo +
    52. +
      + +
      +
    53. + + + + + novo_modelo.ativo = false # Clones começam inativos +
    54. +
      + +
      +
    55. + + + + + novo_modelo.save +
    56. +
      + +
      +
    57. + + + + + +
    58. +
      + +
      +
    59. + + + + + if novo_modelo.persisted? +
    60. +
      + +
      +
    61. + + + + + perguntas.each do |pergunta| +
    62. +
      + +
      +
    63. + + + + + nova_pergunta = pergunta.dup +
    64. +
      + +
      +
    65. + + + + + nova_pergunta.modelo = novo_modelo +
    66. +
      + +
      +
    67. + + + + + nova_pergunta.save +
    68. +
      + +
      +
    69. + + + + + end +
    70. +
      + +
      +
    71. + + + + + end +
    72. +
      + +
      +
    73. + + + + + +
    74. +
      + +
      +
    75. + + + + + novo_modelo +
    76. +
      + +
      +
    77. + + + + + end +
    78. +
      + +
      +
    79. + + + + + +
    80. +
      + +
      +
    81. + + 1 + + + + + private +
    82. +
      + +
      +
    83. + + + + + +
    84. +
      + +
      +
    85. + + 1 + + + + + def deve_ter_pelo_menos_uma_pergunta +
    86. +
      + +
      +
    87. + + 3 + + + + + if perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? } +
    88. +
      + +
      +
    89. + + 3 + + + + + errors.add(:base, "Um modelo deve ter pelo menos uma pergunta") +
    90. +
      + +
      +
    91. + + + + + end +
    92. +
      + +
      +
    93. + + + + + end +
    94. +
      + +
      +
    95. + + + + + +
    96. +
      + +
      +
    97. + + 1 + + + + + def nao_pode_remover_todas_perguntas +
    98. +
      + +
      +
    99. + + + + + if persisted? && (perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? }) +
    100. +
      + +
      +
    101. + + + + + errors.add(:base, "Não é possível remover todas as perguntas de um modelo existente") +
    102. +
      + +
      +
    103. + + + + + end +
    104. +
      + +
      +
    105. + + + + + end +
    106. +
      + +
      +
    107. + + + + + end +
    108. +
      + +
    +
    +
    + + +
    +
    +

    app/models/pergunta.rb

    +

    + + 47.22% + + + lines covered +

    + + + +
    + 36 relevant lines. + 17 lines covered and + 19 lines missed. +
    + + + +
    + +
    +    
      + +
      +
    1. + + 1 + + + + + class Pergunta < ApplicationRecord +
    2. +
      + +
      +
    3. + + 1 + + + + + self.table_name = 'perguntas' # Plural correto em português +
    4. +
      + +
      +
    5. + + + + + +
    6. +
      + +
      +
    7. + + + + + # Relacionamentos +
    8. +
      + +
      +
    9. + + 1 + + + + + belongs_to :modelo +
    10. +
      + +
      +
    11. + + 1 + + + + + has_many :respostas, foreign_key: 'questao_id', dependent: :destroy +
    12. +
      + +
      +
    13. + + + + + +
    14. +
      + +
      +
    15. + + + + + # Tipos de perguntas disponíveis +
    16. +
      + +
      +
    17. + + + + + TIPOS = { +
    18. +
      + +
      +
    19. + + 1 + + + + + 'texto_longo' => 'Texto Longo', +
    20. +
      + +
      +
    21. + + + + + 'texto_curto' => 'Texto Curto', +
    22. +
      + +
      +
    23. + + + + + 'multipla_escolha' => 'Múltipla Escolha', +
    24. +
      + +
      +
    25. + + + + + 'checkbox' => 'Checkbox (Múltipla Seleção)', +
    26. +
      + +
      +
    27. + + + + + 'escala' => 'Escala Likert (1-5)', +
    28. +
      + +
      +
    29. + + + + + 'data' => 'Data', +
    30. +
      + +
      +
    31. + + + + + 'hora' => 'Hora' +
    32. +
      + +
      +
    33. + + + + + }.freeze +
    34. +
      + +
      +
    35. + + + + + +
    36. +
      + +
      +
    37. + + + + + # Validações +
    38. +
      + +
      +
    39. + + 1 + + + + + validates :enunciado, presence: true +
    40. +
      + +
      +
    41. + + 1 + + + + + validates :tipo, presence: true, inclusion: { in: TIPOS.keys } +
    42. +
      + +
      +
    43. + + + + + +
    44. +
      + +
      +
    45. + + + + + # Validações condicionais +
    46. +
      + +
      +
    47. + + 1 + + + + + validate :opcoes_requeridas_para_multipla_escolha +
    48. +
      + +
      +
    49. + + 1 + + + + + validate :opcoes_requeridas_para_checkbox +
    50. +
      + +
      +
    51. + + + + + +
    52. +
      + +
      +
    53. + + + + + # Callbacks +
    54. +
      + +
      +
    55. + + 1 + + + + + before_validation :definir_ordem_padrao, on: :create +
    56. +
      + +
      +
    57. + + + + + +
    58. +
      + +
      +
    59. + + + + + # Métodos +
    60. +
      + +
      +
    61. + + 1 + + + + + def tipo_humanizado +
    62. +
      + +
      +
    63. + + + + + TIPOS[tipo] || tipo +
    64. +
      + +
      +
    65. + + + + + end +
    66. +
      + +
      +
    67. + + + + + +
    68. +
      + +
      +
    69. + + 1 + + + + + def requer_opcoes? +
    70. +
      + +
      +
    71. + + + + + ['multipla_escolha', 'checkbox'].include?(tipo) +
    72. +
      + +
      +
    73. + + + + + end +
    74. +
      + +
      +
    75. + + + + + +
    76. +
      + +
      +
    77. + + 1 + + + + + def lista_opcoes +
    78. +
      + +
      +
    79. + + + + + return [] unless opcoes.present? +
    80. +
      + +
      +
    81. + + + + + # Assume que opcoes é JSON array ou string separada por ; +
    82. +
      + +
      +
    83. + + + + + if opcoes.is_a?(Array) +
    84. +
      + +
      +
    85. + + + + + opcoes +
    86. +
      + +
      +
    87. + + + + + elsif opcoes.is_a?(String) +
    88. +
      + +
      +
    89. + + + + + begin +
    90. +
      + +
      +
    91. + + + + + JSON.parse(opcoes) +
    92. +
      + +
      +
    93. + + + + + rescue JSON::ParserError +
    94. +
      + +
      +
    95. + + + + + opcoes.split(';').map(&:strip) +
    96. +
      + +
      +
    97. + + + + + end +
    98. +
      + +
      +
    99. + + + + + else +
    100. +
      + +
      +
    101. + + + + + [] +
    102. +
      + +
      +
    103. + + + + + end +
    104. +
      + +
      +
    105. + + + + + end +
    106. +
      + +
      +
    107. + + + + + +
    108. +
      + +
      +
    109. + + 1 + + + + + private +
    110. +
      + +
      +
    111. + + + + + +
    112. +
      + +
      +
    113. + + 1 + + + + + def definir_ordem_padrao +
    114. +
      + +
      +
    115. + + + + + if modelo.present? +
    116. +
      + +
      +
    117. + + + + + ultima_ordem = modelo.perguntas.maximum(:id) || 0 +
    118. +
      + +
      +
    119. + + + + + # Ordem pode ser baseada no ID para simplificar +
    120. +
      + +
      +
    121. + + + + + end +
    122. +
      + +
      +
    123. + + + + + end +
    124. +
      + +
      +
    125. + + + + + +
    126. +
      + +
      +
    127. + + 1 + + + + + def opcoes_requeridas_para_multipla_escolha +
    128. +
      + +
      +
    129. + + + + + if tipo == 'multipla_escolha' +
    130. +
      + +
      +
    131. + + + + + opcoes_lista = lista_opcoes +
    132. +
      + +
      +
    133. + + + + + if opcoes_lista.blank? || opcoes_lista.size < 2 +
    134. +
      + +
      +
    135. + + + + + errors.add(:opcoes, 'deve ter pelo menos duas opções para múltipla escolha') +
    136. +
      + +
      +
    137. + + + + + end +
    138. +
      + +
      +
    139. + + + + + end +
    140. +
      + +
      +
    141. + + + + + end +
    142. +
      + +
      +
    143. + + + + + +
    144. +
      + +
      +
    145. + + 1 + + + + + def opcoes_requeridas_para_checkbox +
    146. +
      + +
      +
    147. + + + + + if tipo == 'checkbox' +
    148. +
      + +
      +
    149. + + + + + opcoes_lista = lista_opcoes +
    150. +
      + +
      +
    151. + + + + + if opcoes_lista.blank? || opcoes_lista.size < 2 +
    152. +
      + +
      +
    153. + + + + + errors.add(:opcoes, 'deve ter pelo menos duas opções para checkbox') +
    154. +
      + +
      +
    155. + + + + + end +
    156. +
      + +
      +
    157. + + + + + end +
    158. +
      + +
      +
    159. + + + + + end +
    160. +
      + +
      +
    161. + + + + + end +
    162. +
      + +
    +
    +
    + + +
    +
    +

    app/models/turma.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 4 relevant lines. + 4 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      + +
      +
    1. + + 1 + + + + + class Turma < ApplicationRecord +
    2. +
      + +
      +
    3. + + 1 + + + + + has_many :avaliacoes +
    4. +
      + +
      +
    5. + + 1 + + + + + has_many :matricula_turmas +
    6. +
      + +
      +
    7. + + 1 + + + + + has_many :users, through: :matricula_turmas +
    8. +
      + +
      +
    9. + + + + + end +
    10. +
      + +
    +
    +
    + + +
    +
    +

    app/models/user.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 12 relevant lines. + 12 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      + +
      +
    1. + + 1 + + + + + class User < ApplicationRecord +
    2. +
      + +
      +
    3. + + 1 + + + + + has_secure_password +
    4. +
      + +
      +
    5. + + 1 + + + + + has_many :sessions, dependent: :destroy +
    6. +
      + +
      +
    7. + + 1 + + + + + has_many :matricula_turmas +
    8. +
      + +
      +
    9. + + 1 + + + + + has_many :turmas, through: :matricula_turmas +
    10. +
      + +
      +
    11. + + 1 + + + + + has_many :submissoes, class_name: 'Submissao', foreign_key: :aluno_id, dependent: :destroy +
    12. +
      + +
      +
    13. + + + + + +
    14. +
      + +
      +
    15. + + 1 + + + + + validates :email_address, presence: true, uniqueness: true +
    16. +
      + +
      +
    17. + + 1 + + + + + validates :login, presence: true, uniqueness: true +
    18. +
      + +
      +
    19. + + 1 + + + + + validates :matricula, presence: true, uniqueness: true +
    20. +
      + +
      +
    21. + + 1 + + + + + validates :nome, presence: true +
    22. +
      + +
      +
    23. + + + + + +
    24. +
      + +
      +
    25. + + 15 + + + + + normalizes :email_address, with: ->(e) { e.strip.downcase } +
    26. +
      + +
      +
    27. + + 7 + + + + + normalizes :login, with: ->(l) { l.strip.downcase } +
    28. +
      + +
      +
    29. + + + + + end +
    30. +
      + +
    +
    +
    + + +
    +
    +

    app/services/csv_formatter_service.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 19 relevant lines. + 19 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      + +
      +
    1. + + 1 + + + + + require 'csv' +
    2. +
      + +
      +
    3. + + + + + +
    4. +
      + +
      +
    5. + + 1 + + + + + class CsvFormatterService +
    6. +
      + +
      +
    7. + + 1 + + + + + def initialize(avaliacao) +
    8. +
      + +
      +
    9. + + 1 + + + + + @avaliacao = avaliacao +
    10. +
      + +
      +
    11. + + + + + end +
    12. +
      + +
      +
    13. + + + + + +
    14. +
      + +
      +
    15. + + 1 + + + + + def generate +
    16. +
      + +
      +
    17. + + 1 + + + + + CSV.generate(headers: true) do |csv| +
    18. +
      + +
      +
    19. + + 1 + + + + + csv << headers +
    20. +
      + +
      +
    21. + + + + + +
    22. +
      + +
      +
    23. + + 1 + + + + + @avaliacao.submissoes.includes(:aluno, :respostas).each do |submissao| +
    24. +
      + +
      +
    25. + + 2 + + + + + aluno = submissao.aluno +
    26. +
      + +
      +
    27. + + 2 + + + + + row = [aluno.matricula, aluno.nome] +
    28. +
      + +
      +
    29. + + + + + +
    30. +
      + +
      +
    31. + + + + + # Organiza as respostas pela ordem das questões se possível, ou mapeamento simples +
    32. +
      + +
      +
    33. + + + + + # Assumindo que queremos mapear questões para colunas +
    34. +
      + +
      +
    35. + + + + + +
    36. +
      + +
      +
    37. + + + + + # Para este MVP, vamos apenas despejar o conteúdo na ordem das questões encontradas +
    38. +
      + +
      +
    39. + + + + + # Uma solução mais robusta ordenaria por ID da questão ou número +
    40. +
      + +
      +
    41. + + + + + +
    42. +
      + +
      +
    43. + + 2 + + + + + submissao.respostas.each do |resposta| +
    44. +
      + +
      +
    45. + + 3 + + + + + row << resposta.conteudo +
    46. +
      + +
      +
    47. + + + + + end +
    48. +
      + +
      +
    49. + + + + + +
    50. +
      + +
      +
    51. + + 2 + + + + + csv << row +
    52. +
      + +
      +
    53. + + + + + end +
    54. +
      + +
      +
    55. + + + + + end +
    56. +
      + +
      +
    57. + + + + + end +
    58. +
      + +
      +
    59. + + + + + +
    60. +
      + +
      +
    61. + + 1 + + + + + private +
    62. +
      + +
      +
    63. + + + + + +
    64. +
      + +
      +
    65. + + 1 + + + + + def headers +
    66. +
      + +
      +
    67. + + + + + # Cabeçalhos estáticos para informações do Aluno +
    68. +
      + +
      +
    69. + + 1 + + + + + base_headers = ["Matrícula", "Nome"] +
    70. +
      + +
      +
    71. + + + + + +
    72. +
      + +
      +
    73. + + + + + # Cabeçalhos dinâmicos para questões +
    74. +
      + +
      +
    75. + + + + + # Identificando questões únicas respondidas ou todas as questões do modelo +
    76. +
      + +
      +
    77. + + + + + # Para o MVP, vamos assumir que queremos todas as questões do modelo +
    78. +
      + +
      +
    79. + + + + + +
    80. +
      + +
      +
    81. + + 1 + + + + + questoes = @avaliacao.modelo.perguntas +
    82. +
      + +
      +
    83. + + 3 + + + + + question_headers = questoes.map.with_index { |q, i| "Questão #{i + 1}" } +
    84. +
      + +
      +
    85. + + + + + +
    86. +
      + +
      +
    87. + + 1 + + + + + base_headers + question_headers +
    88. +
      + +
      +
    89. + + + + + end +
    90. +
      + +
      +
    91. + + + + + end +
    92. +
      + +
    +
    +
    + + +
    +
    +

    app/services/sigaa_import_service.rb

    +

    + + 87.01% + + + lines covered +

    + + + +
    + 77 relevant lines. + 67 lines covered and + 10 lines missed. +
    + + + +
    + +
    +    
      + +
      +
    1. + + 1 + + + + + require 'json' +
    2. +
      + +
      +
    3. + + 1 + + + + + require 'csv' +
    4. +
      + +
      +
    5. + + + + + +
    6. +
      + +
      +
    7. + + 1 + + + + + class SigaaImportService +
    8. +
      + +
      +
    9. + + 1 + + + + + def initialize(file_path) +
    10. +
      + +
      +
    11. + + 3 + + + + + @file_path = file_path +
    12. +
      + +
      +
    13. + + 3 + + + + + @results = { +
    14. +
      + +
      +
    15. + + + + + turmas_created: 0, +
    16. +
      + +
      +
    17. + + + + + turmas_updated: 0, +
    18. +
      + +
      +
    19. + + + + + users_created: 0, +
    20. +
      + +
      +
    21. + + + + + users_updated: 0, +
    22. +
      + +
      +
    23. + + + + + new_users: [], # Array de hashes com credenciais dos novos usuários +
    24. +
      + +
      +
    25. + + + + + errors: [] +
    26. +
      + +
      +
    27. + + + + + } +
    28. +
      + +
      +
    29. + + + + + end +
    30. +
      + +
      +
    31. + + + + + +
    32. +
      + +
      +
    33. + + 1 + + + + + def process +
    34. +
      + +
      +
    35. + + 3 + + + + + unless File.exist?(@file_path) +
    36. +
      + +
      +
    37. + + + + + @results[:errors] << "Arquivo não encontrado: #{@file_path}" +
    38. +
      + +
      +
    39. + + + + + return @results +
    40. +
      + +
      +
    41. + + + + + end +
    42. +
      + +
      +
    43. + + + + + +
    44. +
      + +
      +
    45. + + + + + begin +
    46. +
      + +
      +
    47. + + 3 + + + + + ActiveRecord::Base.transaction do +
    48. +
      + +
      +
    49. + + 3 + + + + + case File.extname(@file_path).downcase +
    50. +
      + +
      +
    51. + + + + + when '.json' +
    52. +
      + +
      +
    53. + + 1 + + + + + process_json +
    54. +
      + +
      +
    55. + + + + + when '.csv' +
    56. +
      + +
      +
    57. + + 1 + + + + + process_csv +
    58. +
      + +
      +
    59. + + + + + else +
    60. +
      + +
      +
    61. + + 1 + + + + + @results[:errors] << "Formato de arquivo não suportado: #{File.extname(@file_path)}" +
    62. +
      + +
      +
    63. + + + + + end +
    64. +
      + +
      +
    65. + + + + + +
    66. +
      + +
      +
    67. + + 3 + + + + + if @results[:errors].any? +
    68. +
      + +
      +
    69. + + 1 + + + + + raise ActiveRecord::Rollback +
    70. +
      + +
      +
    71. + + + + + end +
    72. +
      + +
      +
    73. + + + + + end +
    74. +
      + +
      +
    75. + + + + + rescue JSON::ParserError +
    76. +
      + +
      +
    77. + + + + + @results[:errors] << "Arquivo JSON inválido" +
    78. +
      + +
      +
    79. + + + + + rescue ActiveRecord::StatementInvalid => e +
    80. +
      + +
      +
    81. + + + + + @results[:errors] << "Erro de conexão com o banco de dados: #{e.message}" +
    82. +
      + +
      +
    83. + + + + + rescue StandardError => e +
    84. +
      + +
      +
    85. + + + + + @results[:errors] << "Erro inesperado: #{e.message}" +
    86. +
      + +
      +
    87. + + + + + end +
    88. +
      + +
      +
    89. + + + + + +
    90. +
      + +
      +
    91. + + 3 + + + + + @results +
    92. +
      + +
      +
    93. + + + + + end +
    94. +
      + +
      +
    95. + + + + + +
    96. +
      + +
      +
    97. + + 1 + + + + + private +
    98. +
      + +
      +
    99. + + + + + +
    100. +
      + +
      +
    101. + + 1 + + + + + def process_json +
    102. +
      + +
      +
    103. + + 1 + + + + + data = JSON.parse(File.read(@file_path)) +
    104. +
      + +
      +
    105. + + + + + +
    106. +
      + +
      +
    107. + + + + + # class_members.json é um array de turmas +
    108. +
      + +
      +
    109. + + 1 + + + + + data.each do |turma_data| +
    110. +
      + +
      +
    111. + + + + + # Mapeia campos do formato real para o esperado +
    112. +
      + +
      +
    113. + + + + + normalized_data = { +
    114. +
      + +
      +
    115. + + 1 + + + + + 'codigo' => turma_data['code'], +
    116. +
      + +
      +
    117. + + + + + 'nome' => turma_data['code'], # Usa o código como nome se não tiver +
    118. +
      + +
      +
    119. + + + + + 'semestre' => turma_data['semester'], +
    120. +
      + +
      +
    121. + + + + + 'participantes' => [] +
    122. +
      + +
      +
    123. + + + + + } +
    124. +
      + +
      +
    125. + + + + + +
    126. +
      + +
      +
    127. + + + + + # Processa dicentes (alunos) +
    128. +
      + +
      +
    129. + + 1 + + + + + if turma_data['dicente'] +
    130. +
      + +
      +
    131. + + 1 + + + + + turma_data['dicente'].each do |dicente| +
    132. +
      + +
      +
    133. + + 1 + + + + + normalized_data['participantes'] << { +
    134. +
      + +
      +
    135. + + + + + 'nome' => dicente['nome'], +
    136. +
      + +
      +
    137. + + + + + 'email' => dicente['email'], +
    138. +
      + +
      +
    139. + + + + + 'matricula' => dicente['matricula'] || dicente['usuario'], +
    140. +
      + +
      +
    141. + + + + + 'papel' => 'Discente' +
    142. +
      + +
      +
    143. + + + + + } +
    144. +
      + +
      +
    145. + + + + + end +
    146. +
      + +
      +
    147. + + + + + end +
    148. +
      + +
      +
    149. + + + + + +
    150. +
      + +
      +
    151. + + + + + # Processa docente (professor) +
    152. +
      + +
      +
    153. + + 1 + + + + + if turma_data['docente'] +
    154. +
      + +
      +
    155. + + 1 + + + + + docente = turma_data['docente'] +
    156. +
      + +
      +
    157. + + 1 + + + + + normalized_data['participantes'] << { +
    158. +
      + +
      +
    159. + + + + + 'nome' => docente['nome'], +
    160. +
      + +
      +
    161. + + + + + 'email' => docente['email'], +
    162. +
      + +
      +
    163. + + + + + 'matricula' => docente['usuario'], +
    164. +
      + +
      +
    165. + + + + + 'papel' => 'Docente' +
    166. +
      + +
      +
    167. + + + + + } +
    168. +
      + +
      +
    169. + + + + + end +
    170. +
      + +
      +
    171. + + + + + +
    172. +
      + +
      +
    173. + + 1 + + + + + process_turma(normalized_data) +
    174. +
      + +
      +
    175. + + + + + end +
    176. +
      + +
      +
    177. + + + + + end +
    178. +
      + +
      +
    179. + + + + + +
    180. +
      + +
      +
    181. + + 1 + + + + + def process_csv +
    182. +
      + +
      +
    183. + + 1 + + + + + CSV.foreach(@file_path, headers: true, col_sep: ',') do |row| +
    184. +
      + +
      +
    185. + + + + + # Assumindo estrutura do CSV +
    186. +
      + +
      +
    187. + + + + + turma_data = { +
    188. +
      + +
      +
    189. + + 1 + + + + + 'codigo' => row['codigo_turma'], +
    190. +
      + +
      +
    191. + + + + + 'nome' => row['nome_turma'], +
    192. +
      + +
      +
    193. + + + + + 'semestre' => row['semestre'] +
    194. +
      + +
      +
    195. + + + + + } +
    196. +
      + +
      +
    197. + + + + + +
    198. +
      + +
      +
    199. + + 1 + + + + + turma = process_turma_record(turma_data) +
    200. +
      + +
      +
    201. + + + + + +
    202. +
      + +
      +
    203. + + 1 + + + + + if turma&.persisted? +
    204. +
      + +
      +
    205. + + + + + user_data = { +
    206. +
      + +
      +
    207. + + 1 + + + + + 'nome' => row['nome_usuario'], +
    208. +
      + +
      +
    209. + + + + + 'email' => row['email'], +
    210. +
      + +
      +
    211. + + + + + 'matricula' => row['matricula'], +
    212. +
      + +
      +
    213. + + + + + 'papel' => row['papel'] +
    214. +
      + +
      +
    215. + + + + + } +
    216. +
      + +
      +
    217. + + 1 + + + + + process_participante_single(turma, user_data) +
    218. +
      + +
      +
    219. + + + + + end +
    220. +
      + +
      +
    221. + + + + + end +
    222. +
      + +
      +
    223. + + + + + end +
    224. +
      + +
      +
    225. + + + + + +
    226. +
      + +
      +
    227. + + 1 + + + + + def process_turma(data) +
    228. +
      + +
      +
    229. + + 1 + + + + + turma = process_turma_record(data) +
    230. +
      + +
      +
    231. + + 1 + + + + + if turma&.persisted? +
    232. +
      + +
      +
    233. + + 1 + + + + + process_participantes(turma, data['participantes']) if data['participantes'] +
    234. +
      + +
      +
    235. + + + + + end +
    236. +
      + +
      +
    237. + + + + + end +
    238. +
      + +
      +
    239. + + + + + +
    240. +
      + +
      +
    241. + + 1 + + + + + def process_turma_record(data) +
    242. +
      + +
      +
    243. + + 2 + + + + + turma = Turma.find_or_initialize_by(codigo: data['codigo'], semestre: data['semestre']) +
    244. +
      + +
      +
    245. + + + + + +
    246. +
      + +
      +
    247. + + 2 + + + + + is_new_record = turma.new_record? +
    248. +
      + +
      +
    249. + + 2 + + + + + turma.nome = data['nome'] +
    250. +
      + +
      +
    251. + + + + + +
    252. +
      + +
      +
    253. + + 2 + + + + + if turma.save +
    254. +
      + +
      +
    255. + + 2 + + + + + if is_new_record +
    256. +
      + +
      +
    257. + + 2 + + + + + @results[:turmas_created] += 1 +
    258. +
      + +
      +
    259. + + + + + else +
    260. +
      + +
      +
    261. + + + + + @results[:turmas_updated] += 1 +
    262. +
      + +
      +
    263. + + + + + end +
    264. +
      + +
      +
    265. + + 2 + + + + + turma +
    266. +
      + +
      +
    267. + + + + + else +
    268. +
      + +
      +
    269. + + + + + @results[:errors] << "Erro ao salvar turma #{data['codigo']}: #{turma.errors.full_messages.join(', ')}" +
    270. +
      + +
      +
    271. + + + + + nil +
    272. +
      + +
      +
    273. + + + + + end +
    274. +
      + +
      +
    275. + + + + + end +
    276. +
      + +
      +
    277. + + + + + +
    278. +
      + +
      +
    279. + + 1 + + + + + def process_participantes(turma, participantes_data) +
    280. +
      + +
      +
    281. + + 1 + + + + + participantes_data.each do |p_data| +
    282. +
      + +
      +
    283. + + 2 + + + + + process_participante_single(turma, p_data) +
    284. +
      + +
      +
    285. + + + + + end +
    286. +
      + +
      +
    287. + + + + + end +
    288. +
      + +
      +
    289. + + + + + +
    290. +
      + +
      +
    291. + + 1 + + + + + def process_participante_single(turma, p_data) +
    292. +
      + +
      +
    293. + + + + + # User identificado pela matrícula +
    294. +
      + +
      +
    295. + + 3 + + + + + user = User.find_or_initialize_by(matricula: p_data['matricula']) +
    296. +
      + +
      +
    297. + + + + + +
    298. +
      + +
      +
    299. + + 3 + + + + + is_new_user = user.new_record? +
    300. +
      + +
      +
    301. + + 3 + + + + + user.nome = p_data['nome'] +
    302. +
      + +
      +
    303. + + 3 + + + + + user.email_address = p_data['email'] +
    304. +
      + +
      +
    305. + + + + + +
    306. +
      + +
      +
    307. + + + + + # Generate login from matricula if not present (assuming matricula is unique and good for login) +
    308. +
      + +
      +
    309. + + 3 + + + + + user.login = p_data['matricula'] if user.login.blank? +
    310. +
      + +
      +
    311. + + + + + +
    312. +
      + +
      +
    313. + + 3 + + + + + generated_password = nil +
    314. +
      + +
      +
    315. + + 3 + + + + + if is_new_user +
    316. +
      + +
      +
    317. + + 3 + + + + + generated_password = SecureRandom.hex(8) +
    318. +
      + +
      +
    319. + + 3 + + + + + user.password = generated_password +
    320. +
      + +
      +
    321. + + + + + end +
    322. +
      + +
      +
    323. + + + + + +
    324. +
      + +
      +
    325. + + 3 + + + + + if user.save +
    326. +
      + +
      +
    327. + + 3 + + + + + if is_new_user +
    328. +
      + +
      +
    329. + + 3 + + + + + @results[:users_created] += 1 +
    330. +
      + +
      +
    331. + + + + + +
    332. +
      + +
      +
    333. + + + + + # Armazena credenciais do novo usuário para exibir depois +
    334. +
      + +
      +
    335. + + 3 + + + + + @results[:new_users] << { +
    336. +
      + +
      +
    337. + + + + + matricula: user.matricula, +
    338. +
      + +
      +
    339. + + + + + nome: user.nome, +
    340. +
      -
    341. - - 1 +
    342. - class Modelo < ApplicationRecord + login: user.login,
    343. -
    344. +
    345. - # Relacionamentos + password: generated_password,
    346. -
    347. - - 1 +
    348. - has_many :perguntas, dependent: :destroy + email: user.email_address
    349. -
    350. - - 1 +
    351. - has_many :avaliacoes, dependent: :restrict_with_error + }
    352. -
    353. +
    354. - +
    355. -
    356. +
    357. - # Validações + # Envia email com senha para novo usuário (COMENTADO - muito lento)
    358. -
    359. - - 1 +
    360. - validates :titulo, presence: true, uniqueness: { case_sensitive: false } + # UserMailer.cadastro_email(user, generated_password).deliver_now
    361. -
    362. +
    363. - + else
    364. -
    365. +
    366. - # Validação customizada: não permitir modelo sem perguntas + @results[:users_updated] += 1
    367. -
    368. +
    369. - 1 + + + + end +
    370. +
      + +
      +
    371. - validate :deve_ter_pelo_menos_uma_pergunta, on: :create +
    372. -
    373. +
    374. - 1 + 3 - validate :nao_pode_remover_todas_perguntas, on: :update + matricula = MatriculaTurma.find_or_initialize_by(turma: turma, user: user)
    375. -
    376. +
    377. + + 3 - + matricula.papel = p_data['papel']
    378. -
    379. +
    380. + + 3 - # Aceita atributos aninhados para perguntas + matricula.save!
    381. -
    382. - - 1 +
    383. - accepts_nested_attributes_for :perguntas, + else
    384. -
    385. +
    386. - allow_destroy: true, + @results[:errors] << "Erro ao salvar usuário #{p_data['matricula']}: #{user.errors.full_messages.join(', ')}"
    387. -
    388. +
    389. - reject_if: :all_blank + end
    390. -
    391. +
    392. - + end
    393. -
    394. +
    395. - # Método para verificar se modelo está em uso + end
    396. +
    +
    +
    + + +
    +
    +

    config/application.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 7 relevant lines. + 7 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. +
    2. 1 - def em_uso? + require_relative "boot"
    3. -
    4. +
    5. - avaliacoes.any? +
    6. -
    7. +
    8. + + 1 - end + require "rails/all"
    9. -
    10. +
    11. - +
    12. -
    13. +
    14. - # Método para clonar modelo com perguntas + # Require the gems listed in Gemfile, including any gems
    15. -
    16. - - 1 +
    17. - def clonar_com_perguntas(novo_titulo) + # you've limited to :test, :development, or :production.
    18. -
    19. +
    20. + + 1 - novo_modelo = dup + Bundler.require(*Rails.groups)
    21. -
    22. +
    23. - novo_modelo.titulo = novo_titulo +
    24. -
    25. +
    26. + + 1 - novo_modelo.ativo = false # Clones começam inativos + module Camaar
    27. -
    28. +
    29. + + 1 - novo_modelo.save + class Application < Rails::Application
    30. -
    31. +
    32. - + # Initialize configuration defaults for originally generated Rails version.
    33. -
    34. +
    35. + + 1 - if novo_modelo.persisted? + config.load_defaults 8.0
    36. -
    37. +
    38. - perguntas.each do |pergunta| +
    39. -
    40. +
    41. - nova_pergunta = pergunta.dup + # Please, add to the `ignore` list any other `lib` subdirectories that do
    42. -
    43. +
    44. - nova_pergunta.modelo = novo_modelo + # not contain `.rb` files, or that should not be reloaded or eager loaded.
    45. -
    46. +
    47. - nova_pergunta.save + # Common ones are `templates`, `generators`, or `middleware`, for example.
    48. -
    49. +
    50. + + 1 - end + config.autoload_lib(ignore: %w[assets tasks])
    51. -
    52. +
    53. - end +
    54. -
    55. +
    56. - + # Configuration for the application, engines, and railties goes here.
    57. -
    58. +
    59. - novo_modelo + #
    60. -
    61. +
    62. - end + # These settings can be overridden in specific environments using the files
    63. -
    64. +
    65. - + # in config/environments, which are processed later.
    66. -
    67. - - 1 +
    68. - private + #
    69. -
    70. +
    71. - + # config.time_zone = "Central Time (US & Canada)"
    72. -
    73. - - 1 +
    74. - def deve_ter_pelo_menos_uma_pergunta + # config.eager_load_paths << Rails.root.join("extras")
    75. -
    76. - - 3 +
    77. - if perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? } + end
    78. -
    79. +
    80. - 3 + - + end +
    81. +
      + +
    +
    +
    + + +
    +
    +

    config/boot.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 3 relevant lines. + 3 lines covered and + 0 lines missed. +
    + + - errors.add(:base, "Um modelo deve ter pelo menos uma pergunta") - -
    +
    + +
    +    
      -
    1. +
    2. + + 1 - end + ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
    3. -
    4. +
    5. - end +
    6. -
    7. +
    8. + + 1 - + require "bundler/setup" # Set up gems listed in the Gemfile.
    9. -
    10. +
    11. 1 - def nao_pode_remover_todas_perguntas + require "bootsnap/setup" # Speed up boot time by caching expensive operations.
    12. +
    +
    +
    + + +
    +
    +

    config/environment.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 2 relevant lines. + 2 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. +
    2. - if persisted? && (perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? }) + # Load the Rails application.
    3. -
    4. +
    5. + + 1 - errors.add(:base, "Não é possível remover todas as perguntas de um modelo existente") + require_relative "application"
    6. -
    7. +
    8. - end +
    9. -
    10. +
    11. - end + # Initialize the Rails application.
    12. -
    13. +
    14. + + 1 - end + Rails.application.initialize!
    15. @@ -3651,12 +8506,12 @@

    -
    +
    -

    app/models/pergunta.rb

    +

    config/environments/test.rb

    - - 47.22% + + 100.0% lines covered @@ -3665,9 +8520,9 @@

    - 36 relevant lines. - 17 lines covered and - 19 lines missed. + 15 relevant lines. + 15 lines covered and + 0 lines missed.
    @@ -3678,26 +8533,22 @@

      -
    1. - - 1 +
    2. - class Pergunta < ApplicationRecord + # The test environment is used exclusively to run your application's
    3. -
    4. - - 1 +
    5. - self.table_name = 'perguntas' # Plural correto em português + # test suite. You never need to work with it otherwise. Remember that
    6. @@ -3707,7 +8558,7 @@

      - + # your test database is "scratch space" for the test suite and is wiped

    @@ -3717,19 +8568,17 @@

    - # Relacionamentos + # and recreated between test runs. Don't rely on the data there!

    -
  • - - 1 +
  • - belongs_to :modelo +
  • @@ -3741,7 +8590,7 @@

    - has_many :respostas, foreign_key: 'questao_id', dependent: :destroy + Rails.application.configure do

    @@ -3751,39 +8600,41 @@

    - + # Configure 'rails notes' to inspect Cucumber files

    -
  • +
  • + + 1 - # Tipos de perguntas disponíveis + config.annotations.register_directories("features")
  • -
  • +
  • + + 1 - TIPOS = { + config.annotations.register_extensions("feature") { |tag| /#\s*(#{tag}):?\s*(.*)$/ }
  • -
  • - - 1 +
  • - 'texto_longo' => 'Texto Longo', +
  • @@ -3793,7 +8644,7 @@

    - 'texto_curto' => 'Texto Curto', + # Settings specified here will take precedence over those in config/application.rb.

    @@ -3803,7 +8654,7 @@

    - 'multipla_escolha' => 'Múltipla Escolha', +

    @@ -3813,17 +8664,19 @@

    - 'checkbox' => 'Checkbox (Múltipla Seleção)', + # While tests run files are not watched, reloading is not necessary.

    -
  • +
  • + + 1 - 'escala' => 'Escala Likert (1-5)', + config.enable_reloading = false
  • @@ -3833,7 +8686,7 @@

    - 'data' => 'Data', +

    @@ -3843,7 +8696,7 @@

    - 'hora' => 'Hora' + # Eager loading loads your entire application. When running a single test locally,

    @@ -3853,7 +8706,7 @@

    - }.freeze + # this is usually not necessary, and can slow down your test suite. However, it's

    @@ -3863,7 +8716,7 @@

    - + # recommended that you enable it in continuous integration systems to ensure eager

    @@ -3873,7 +8726,7 @@

    - # Validações + # loading is working properly before deploying your code.

    @@ -3885,639 +8738,781 @@

    - validates :enunciado, presence: true + config.eager_load = ENV["CI"].present?

    -
  • +
  • + + + + + +
  • +
    + +
    +
  • + + + + + # Configure public file server for tests with cache-control for performance. +
  • +
    + +
    +
  • 1 - validates :tipo, presence: true, inclusion: { in: TIPOS.keys } + config.public_file_server.headers = { "cache-control" => "public, max-age=3600" }
  • -
  • +
  • + + + + + +
  • +
    + +
    +
  • + + + + + # Show full error reports. +
  • +
    + +
    +
  • + + 1 + + + + + config.consider_all_requests_local = true +
  • +
    + +
    +
  • + + 1 + + + + + config.cache_store = :null_store +
  • +
    + +
    +
  • + + + + + +
  • +
    + +
    +
  • + + + + + # Render exception templates for rescuable exceptions and raise for other exceptions. +
  • +
    + +
    +
  • + + 1 - + config.action_dispatch.show_exceptions = :rescuable
  • -
  • +
  • - # Validações condicionais +
  • -
  • - - 1 +
  • - validate :opcoes_requeridas_para_multipla_escolha + # Disable request forgery protection in test environment.
  • -
  • +
  • 1 - validate :opcoes_requeridas_para_checkbox + config.action_controller.allow_forgery_protection = false
  • -
  • +
  • - +
  • -
  • +
  • - # Callbacks + # Store uploaded files on the local file system in a temporary directory.
  • -
  • +
  • 1 - before_validation :definir_ordem_padrao, on: :create + config.active_storage.service = :test
  • -
  • +
  • - +
  • -
  • +
  • - # Métodos + # Tell Action Mailer not to deliver emails to the real world.
  • -
  • - - 1 +
  • - def tipo_humanizado + # The :test delivery method accumulates sent emails in the
  • -
  • +
  • - TIPOS[tipo] || tipo + # ActionMailer::Base.deliveries array.
  • -
  • +
  • + + 1 - end + config.action_mailer.delivery_method = :test
  • -
  • +
  • - +
  • -
  • - - 1 +
  • - def requer_opcoes? + # Set host to be used by links generated in mailer templates.
  • -
  • +
  • + + 1 - ['multipla_escolha', 'checkbox'].include?(tipo) + config.action_mailer.default_url_options = { host: "example.com" }
  • -
  • +
  • - end +
  • -
  • +
  • - + # Print deprecation notices to the stderr.
  • -
  • +
  • 1 - def lista_opcoes + config.active_support.deprecation = :stderr
  • -
  • +
  • - return [] unless opcoes.present? +
  • -
  • +
  • - # Assume que opcoes é JSON array ou string separada por ; + # Raises error for missing translations.
  • -
  • +
  • - if opcoes.is_a?(Array) + # config.i18n.raise_on_missing_translations = true
  • -
  • +
  • - opcoes +
  • -
  • +
  • - elsif opcoes.is_a?(String) + # Annotate rendered view with file names.
  • -
  • +
  • - begin + # config.action_view.annotate_rendered_view_with_filenames = true
  • -
  • +
  • - JSON.parse(opcoes) +
  • -
  • +
  • - rescue JSON::ParserError + # Raise error when a before_action's only/except options reference missing actions.
  • -
  • +
  • + + 1 - opcoes.split(';').map(&:strip) + config.action_controller.raise_on_missing_callback_actions = true
  • -
  • +
  • - end + end
  • + + +
    + + +
    +
    +

    config/initializers/assets.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 1 relevant lines. + 1 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. +
    2. - else + # Be sure to restart your server when you modify this file.
    3. -
    4. +
    5. - [] +
    6. -
    7. +
    8. - end + # Version of your assets, change this if you want to expire all your assets.
    9. -
    10. +
    11. + + 1 - end + Rails.application.config.assets.version = "1.0"
    12. -
    13. +
    14. - +
    15. -
    16. - - 1 +
    17. - private + # Add additional assets to the asset load path.
    18. -
    19. +
    20. - + # Rails.application.config.assets.paths << Emoji.images_path
    21. +
    +
    +
    + + +
    +
    +

    config/initializers/content_security_policy.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 0 relevant lines. + 0 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. - - 1 +
    2. - def definir_ordem_padrao + # Be sure to restart your server when you modify this file.
    3. -
    4. +
    5. - if modelo.present? +
    6. -
    7. +
    8. - ultima_ordem = modelo.perguntas.maximum(:id) || 0 + # Define an application-wide content security policy.
    9. -
    10. +
    11. - # Ordem pode ser baseada no ID para simplificar + # See the Securing Rails Applications Guide for more information:
    12. -
    13. +
    14. - end + # https://guides.rubyonrails.org/security.html#content-security-policy-header
    15. -
    16. +
    17. - end +
    18. -
    19. +
    20. - + # Rails.application.configure do
    21. -
    22. - - 1 +
    23. - def opcoes_requeridas_para_multipla_escolha + # config.content_security_policy do |policy|
    24. -
    25. +
    26. - if tipo == 'multipla_escolha' + # policy.default_src :self, :https
    27. -
    28. +
    29. - opcoes_lista = lista_opcoes + # policy.font_src :self, :https, :data
    30. -
    31. +
    32. - if opcoes_lista.blank? || opcoes_lista.size < 2 + # policy.img_src :self, :https, :data
    33. -
    34. +
    35. - errors.add(:opcoes, 'deve ter pelo menos duas opções para múltipla escolha') + # policy.object_src :none
    36. -
    37. +
    38. - end + # policy.script_src :self, :https
    39. -
    40. +
    41. - end + # policy.style_src :self, :https
    42. -
    43. +
    44. - end + # # Specify URI for violation reports
    45. -
    46. +
    47. - + # # policy.report_uri "/csp-violation-report-endpoint"
    48. -
    49. - - 1 +
    50. - def opcoes_requeridas_para_checkbox + # end
    51. -
    52. +
    53. - if tipo == 'checkbox' + #
    54. -
    55. +
    56. - opcoes_lista = lista_opcoes + # # Generate session nonces for permitted importmap, inline scripts, and inline styles.
    57. -
    58. +
    59. - if opcoes_lista.blank? || opcoes_lista.size < 2 + # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
    60. -
    61. +
    62. - errors.add(:opcoes, 'deve ter pelo menos duas opções para checkbox') + # config.content_security_policy_nonce_directives = %w(script-src style-src)
    63. -
    64. +
    65. - end + #
    66. -
    67. +
    68. - end + # # Report violations without enforcing the policy.
    69. -
    70. +
    71. - end + # # config.content_security_policy_report_only = true
    72. -
    73. +
    74. - end + # end
    75. @@ -4526,9 +9521,9 @@

    -
    +
    -

    app/models/turma.rb

    +

    config/initializers/filter_parameter_logging.rb

    100.0% @@ -4540,8 +9535,8 @@

    - 4 relevant lines. - 4 lines covered and + 1 relevant lines. + 1 lines covered and 0 lines missed.
    @@ -4553,60 +9548,84 @@

      -
    1. - - 1 +
    2. - class Turma < ApplicationRecord + # Be sure to restart your server when you modify this file.
    3. -
    4. +
    5. - 1 + + +
    6. +
      + +
      +
    7. - has_many :avaliacoes + + + # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
    8. -
    9. +
    10. - 1 + + + + # Use this to limit dissemination of sensitive information. +
    11. +
      + +
      +
    12. - has_many :matricula_turmas + # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
    13. -
    14. +
    15. 1 - has_many :users, through: :matricula_turmas + Rails.application.config.filter_parameters += [
    16. -
    17. +
    18. - end + :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc +
    19. +
      + +
      +
    20. + + + + + ]
    21. @@ -4615,9 +9634,9 @@

    -
    +
    -

    app/services/csv_formatter_service.rb

    +

    config/initializers/inflections.rb

    100.0% @@ -4629,8 +9648,8 @@

    - 19 relevant lines. - 19 lines covered and + 2 relevant lines. + 2 lines covered and 0 lines missed.
    @@ -4642,14 +9661,12 @@

      -
    1. - - 1 +
    2. - require 'csv' + # Be sure to restart your server when you modify this file.
    3. @@ -4664,38 +9681,32 @@

    -
  • - - 1 +
  • - class CsvFormatterService + # Add new inflection rules using the following format. Inflections
  • -
  • - - 1 +
  • - def initialize(avaliacao) + # are locale specific, and you may define rules for as many different
  • -
  • - - 1 +
  • - @avaliacao = avaliacao + # locales as you wish. All of these examples are active by default:
  • @@ -4705,7 +9716,7 @@

    - end + # ActiveSupport::Inflector.inflections(:en) do |inflect|

    @@ -4715,904 +9726,863 @@

    - + # inflect.plural /^(ox)$/i, "\\1en"

    -
  • +
  • - 1 + + # inflect.singular /^(ox)en/i, "\\1" +
  • +
    + +
    +
  • - def generate + + + # inflect.irregular "person", "people"
  • -
  • +
  • - 1 + + + + # inflect.uncountable %w( fish sheep ) +
  • +
    + +
    +
  • - CSV.generate(headers: true) do |csv| + # end
  • -
  • +
  • - 1 + + + + +
  • +
    + +
    +
  • - csv << headers + # These inflection rules are supported but not enabled by default:
  • -
  • +
  • + + 1 - + ActiveSupport::Inflector.inflections(:en) do |inflect|
  • -
  • +
  • 1 - @avaliacao.submissoes.includes(:aluno, :respostas).each do |submissao| + inflect.irregular "pergunta", "perguntas"
  • -
  • +
  • - 2 + + + + # inflect.acronym "RESTful" +
  • +
    + +
    +
  • - aluno = submissao.aluno + end
  • + + +
    + + +
    +
    +

    config/initializers/inflections_custom.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 2 relevant lines. + 2 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. +
    2. - 2 + 1 - row = [aluno.matricula, aluno.nome] + ActiveSupport::Inflector.inflections(:en) do |inflect|
    3. -
    4. +
    5. + + 1 - + inflect.irregular 'avaliacao', 'avaliacoes'
    6. -
    7. +
    8. - # Organiza as respostas pela ordem das questões se possível, ou mapeamento simples + end
    9. -
      -
    10. - +
    +
    +
    - + +
    +
    +

    config/initializers/mail.rb

    +

    + + 25.0% + - # Assumindo que queremos mapear questões para colunas - -

    + lines covered +

    + + + +
    + 12 relevant lines. + 3 lines covered and + 9 lines missed. +
    + + + +
    + +
    +    
      -
    1. +
    2. - + # config/initializers/mail.rb
    3. -
    4. +
    5. - # Para este MVP, vamos apenas despejar o conteúdo na ordem das questões encontradas + # Configuração de email para CAMAAR
    6. -
    7. +
    8. - # Uma solução mais robusta ordenaria por ID da questão ou número +
    9. -
    10. +
    11. + + 1 - + if Rails.env.test?
    12. -
    13. - - 2 +
    14. - submissao.respostas.each do |resposta| + # Em testes: captura emails sem enviar
    15. -
    16. +
    17. - 3 + 1 - row << resposta.conteudo + Rails.application.config.action_mailer.delivery_method = :test
    18. -
    19. +
    20. + + 1 - end + Rails.application.config.action_mailer.default_url_options = {
    21. -
    22. +
    23. - + host: 'localhost',
    24. -
    25. - - 2 +
    26. - csv << row + port: 3000
    27. -
    28. +
    29. - end + }
    30. -
    31. +
    32. - end +
    33. -
    34. +
    35. - end + elsif Rails.env.development?
    36. -
    37. +
    38. - + # MVP: Usa letter_opener (emails abrem no navegador)
    39. -
    40. - - 1 +
    41. - private + # Instale: gem install letter_opener ou adicione ao Gemfile
    42. -
    43. +
    44. - + # PRODUÇÃO: Para enviar emails reais, descomente a seção SMTP abaixo
    45. -
    46. - - 1 +
    47. - def headers +
    48. -
    49. +
    50. - # Cabeçalhos estáticos para informações do Aluno + Rails.application.config.action_mailer.delivery_method = :letter_opener
    51. -
    52. - - 1 +
    53. - base_headers = ["Matrícula", "Nome"] + Rails.application.config.action_mailer.perform_deliveries = true
    54. -
    55. +
    56. - +
    57. -
    58. +
    59. - # Cabeçalhos dinâmicos para questões + # === SMTP (Para Produção) ===
    60. -
    61. +
    62. - # Identificando questões únicas respondidas ou todas as questões do modelo + # Descomente as linhas abaixo e configure variáveis de ambiente (.env)
    63. -
    64. +
    65. - # Para o MVP, vamos assumir que queremos todas as questões do modelo + # para enviar emails reais via SMTP (Gmail, Sendgrid, etc.)
    66. -
    67. +
    68. - + #
    69. -
    70. - - 1 +
    71. - questoes = @avaliacao.modelo.perguntas + # Rails.application.config.action_mailer.delivery_method = :smtp
    72. -
    73. - - 3 +
    74. - question_headers = questoes.map.with_index { |q, i| "Questão #{i + 1}" } + # Rails.application.config.action_mailer.raise_delivery_errors = true
    75. -
    76. +
    77. - + #
    78. -
    79. - - 1 +
    80. - base_headers + question_headers + # Rails.application.config.action_mailer.smtp_settings = {
    81. -
    82. +
    83. - end + # address: ENV.fetch('SMTP_ADDRESS', 'smtp.gmail.com'),
    84. -
    85. +
    86. - end + # port: ENV.fetch('SMTP_PORT', '587').to_i,
    87. -
    -
    -
    - - -
    -
    -

    config/application.rb

    -

    - - 100.0% - - - lines covered -

    - - - -
    - 7 relevant lines. - 7 lines covered and - 0 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. - - 1 +
    2. - require_relative "boot" + # domain: ENV.fetch('SMTP_DOMAIN', 'localhost'),
    3. -
    4. +
    5. - + # user_name: ENV['SMTP_USER'],
    6. -
    7. - - 1 +
    8. - require "rails/all" + # password: ENV['SMTP_PASSWORD'],
    9. -
    10. +
    11. - + # authentication: 'plain',
    12. -
    13. +
    14. - # Require the gems listed in Gemfile, including any gems + # enable_starttls_auto: true
    15. -
    16. +
    17. - # you've limited to :test, :development, or :production. + # }
    18. -
    19. - - 1 +
    20. - Bundler.require(*Rails.groups) +
    21. -
    22. +
    23. - + Rails.application.config.action_mailer.default_url_options = {
    24. -
    25. - - 1 +
    26. - module Camaar + host: ENV.fetch('APP_HOST', 'localhost'),
    27. -
    28. - - 1 +
    29. - class Application < Rails::Application + port: ENV.fetch('APP_PORT', '3000').to_i
    30. -
    31. +
    32. - # Initialize configuration defaults for originally generated Rails version. + }
    33. -
    34. - - 1 +
    35. - config.load_defaults 8.0 +
    36. -
    37. +
    38. - + else
    39. -
    40. +
    41. - # Please, add to the `ignore` list any other `lib` subdirectories that do + # Produção: SMTP obrigatório
    42. -
    43. +
    44. - # not contain `.rb` files, or that should not be reloaded or eager loaded. + Rails.application.config.action_mailer.delivery_method = :smtp
    45. -
    46. +
    47. - # Common ones are `templates`, `generators`, or `middleware`, for example. + Rails.application.config.action_mailer.perform_deliveries = true
    48. -
    49. - - 1 +
    50. - config.autoload_lib(ignore: %w[assets tasks]) + Rails.application.config.action_mailer.raise_delivery_errors = false
    51. -
    52. +
    53. - +
    54. -
    55. +
    56. - # Configuration for the application, engines, and railties goes here. + Rails.application.config.action_mailer.smtp_settings = {
    57. -
    58. +
    59. - # + address: ENV.fetch('SMTP_ADDRESS'),
    60. -
    61. +
    62. - # These settings can be overridden in specific environments using the files + port: ENV.fetch('SMTP_PORT', '587').to_i,
    63. -
    64. +
    65. - # in config/environments, which are processed later. + domain: ENV.fetch('SMTP_DOMAIN'),
    66. -
    67. +
    68. - # + user_name: ENV.fetch('SMTP_USER'),
    69. -
    70. +
    71. - # config.time_zone = "Central Time (US & Canada)" + password: ENV.fetch('SMTP_PASSWORD'),
    72. -
    73. +
    74. - # config.eager_load_paths << Rails.root.join("extras") + authentication: 'plain',
    75. -
    76. +
    77. - end + enable_starttls_auto: true,
    78. -
    79. +
    80. - end + open_timeout: 10,
    81. -
    -
    -
    - - -
    -
    -

    config/boot.rb

    -

    - - 100.0% - - - lines covered -

    - - - -
    - 3 relevant lines. - 3 lines covered and - 0 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. - - 1 +
    2. - ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + read_timeout: 10
    3. -
    4. +
    5. - + }
    6. -
    7. - - 1 +
    8. - require "bundler/setup" # Set up gems listed in the Gemfile. +
    9. -
    10. - - 1 +
    11. - require "bootsnap/setup" # Speed up boot time by caching expensive operations. + Rails.application.config.action_mailer.default_url_options = {
    12. -
    -
    -
    - - -
    -
    -

    config/environment.rb

    -

    - - 100.0% - - - lines covered -

    - - - -
    - 2 relevant lines. - 2 lines covered and - 0 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. +
    2. - # Load the Rails application. + host: ENV.fetch('APP_HOST'),
    3. -
    4. - - 1 +
    5. - require_relative "application" + protocol: 'https'
    6. -
    7. +
    8. - + }
    9. -
    10. +
    11. - # Initialize the Rails application. + end
    12. -
    13. - - 1 +
    14. - Rails.application.initialize! +
    15. @@ -5621,9 +10591,9 @@

    -
    +
    -

    config/environments/test.rb

    +

    config/routes.rb

    100.0% @@ -5635,8 +10605,8 @@

    - 15 relevant lines. - 15 lines covered and + 23 relevant lines. + 23 lines covered and 0 lines missed.
    @@ -5648,12 +10618,14 @@

      -
    1. +
    2. + + 1 - # The test environment is used exclusively to run your application's + Rails.application.routes.draw do
    3. @@ -5663,59 +10635,65 @@

      - # test suite. You never need to work with it otherwise. Remember that + # --- ROTAS DE AVALIACOES ---

    -
  • +
  • + + 1 - # your test database is "scratch space" for the test suite and is wiped + resources :avaliacoes, only: [:index, :create] do
  • -
  • +
  • + + 1 - # and recreated between test runs. Don't rely on the data there! + collection do
  • -
  • +
  • + + 1 - + get :gestao_envios
  • -
  • - - 1 +
  • - Rails.application.configure do + end
  • -
  • +
  • + + 1 - # Configure 'rails notes' to inspect Cucumber files + member do
  • @@ -5727,19 +10705,17 @@

    - config.annotations.register_directories("features") + get :resultados

    -
  • - - 1 +
  • - config.annotations.register_extensions("feature") { |tag| /#\s*(#{tag}):?\s*(.*)$/ } + end
  • @@ -5749,17 +10725,19 @@

    - + # Rotas para alunos responderem avaliações (Feature 99)

    -
  • +
  • + + 1 - # Settings specified here will take precedence over those in config/application.rb. + resources :respostas, only: [:new, :create]
  • @@ -5769,7 +10747,7 @@

    - + end

    @@ -5779,59 +10757,65 @@

    - # While tests run files are not watched, reloading is not necessary. +

    -
  • - - 1 +
  • - config.enable_reloading = false + # --- ROTAS DE IMPORTAÇÃO SIGAA ---
  • -
  • +
  • + + 1 - + resources :sigaa_imports, only: [:new, :create] do
  • -
  • +
  • + + 1 - # Eager loading loads your entire application. When running a single test locally, + collection do
  • -
  • +
  • + + 1 - # this is usually not necessary, and can slow down your test suite. However, it's + post :update # For update/sync operations
  • -
  • +
  • + + 1 - # recommended that you enable it in continuous integration systems to ensure eager + get :success # For showing import results
  • @@ -5841,19 +10825,17 @@

    - # loading is working properly before deploying your code. + end

    -
  • - - 1 +
  • - config.eager_load = ENV["CI"].present? + end
  • @@ -5873,7 +10855,7 @@

    - # Configure public file server for tests with cache-control for performance. + # --- ROTAS DE GERENCIAMENTO DE MODELOS ---

    @@ -5885,51 +10867,51 @@

    - config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } + resources :modelos do

    -
  • +
  • + + 1 - + member do
  • -
  • +
  • + + 1 - # Show full error reports. + post :clone
  • -
  • - - 1 +
  • - config.consider_all_requests_local = true + end
  • -
  • - - 1 +
  • - config.cache_store = :null_store + end
  • @@ -5944,12 +10926,14 @@

    -
  • +
  • + + 1 - # Render exception templates for rescuable exceptions and raise for other exceptions. + resource :session
  • @@ -5961,17 +10945,19 @@

    - config.action_dispatch.show_exceptions = :rescuable + resources :passwords, param: :token

    -
  • +
  • + + 1 - + get "home/index"
  • @@ -5981,19 +10967,17 @@

    - # Disable request forgery protection in test environment. + # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

    -
  • - - 1 +
  • - config.action_controller.allow_forgery_protection = false +
  • @@ -6003,7 +10987,7 @@

    - + # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.

    @@ -6013,7 +10997,7 @@

    - # Store uploaded files on the local file system in a temporary directory. + # Can be used by load balancers and uptime monitors to verify that the app is live.

    @@ -6025,7 +11009,7 @@

    - config.active_storage.service = :test + get "up" => "rails/health#show", as: :rails_health_check

    @@ -6045,7 +11029,7 @@

    - # Tell Action Mailer not to deliver emails to the real world. + # Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb)

    @@ -6055,7 +11039,7 @@

    - # The :test delivery method accumulates sent emails in the + # get "manifest" => "rails/pwa#manifest", as: :pwa_manifest

    @@ -6065,19 +11049,17 @@

    - # ActionMailer::Base.deliveries array. + # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker

    -
  • - - 1 +
  • - config.action_mailer.delivery_method = :test +
  • @@ -6087,7 +11069,7 @@

    - + # --- ROTAS DO INTEGRANTE 4 (RESPOSTAS) --- @@ -6097,7 +11079,7 @@

    - # Set host to be used by links generated in mailer templates. + # Define as rotas aninhadas para criar respostas dentro de um formulário @@ -6109,17 +11091,19 @@

    - config.action_mailer.default_url_options = { host: "example.com" } + resources :formularios, only: [] do
    -
  • +
  • + + 1 - + resources :respostas, only: [ :index, :new, :create ]
  • @@ -6129,19 +11113,17 @@

    - # Print deprecation notices to the stderr. + end
    -
  • - - 1 +
  • - config.active_support.deprecation = :stderr +
  • @@ -6151,17 +11133,19 @@

    - + # Rota solta para a listagem geral de respostas (dashboard do aluno)
    -
  • +
  • + + 1 - # Raises error for missing translations. + get "respostas", to: "respostas#index"
  • @@ -6171,7 +11155,7 @@

    - # config.i18n.raise_on_missing_translations = true + # ----------------------------------------- @@ -6191,17 +11175,19 @@

    - # Annotate rendered view with file names. + # Defines the root path route ("/")
    -
  • +
  • + + 1 - # config.action_view.annotate_rendered_view_with_filenames = true + root "pages#index"
  • @@ -6216,29 +11202,19 @@

    -
  • - - - - - # Raise error when a before_action's only/except options reference missing actions. -
  • -
    - -
    -
  • +
  • 1 - config.action_controller.raise_on_missing_callback_actions = true + get "home" => "home#index"
  • -
  • +
  • @@ -6252,12 +11228,12 @@

  • -
    +
    -

    config/initializers/assets.rb

    +

    spec/mailers/user_mailer_spec.rb

    - 100.0% + 95.24% lines covered @@ -6266,9 +11242,9 @@

    - 1 relevant lines. - 1 lines covered and - 0 lines missed. + 21 relevant lines. + 20 lines covered and + 1 lines missed.
    @@ -6279,12 +11255,14 @@

      -
    1. +
    2. + + 1 - # Be sure to restart your server when you modify this file. + require 'rails_helper'
    3. @@ -6299,12 +11277,14 @@

    -
  • +
  • + + 1 - # Version of your assets, change this if you want to expire all your assets. + RSpec.describe UserMailer, type: :mailer do
  • @@ -6316,7 +11296,7 @@

    - Rails.application.config.assets.version = "1.0" + describe 'definicao_senha' do

    @@ -6331,108 +11311,73 @@

    -
  • - - +
  • - - # Add additional assets to the asset load path. -
  • -
    - -
    -
  • + 1 - # Rails.application.config.assets.paths << Emoji.images_path + let(:user) do
  • - - - - - -
    -
    -

    config/initializers/content_security_policy.rb

    -

    - - 100.0% - - - lines covered -

    - - - -
    - 0 relevant lines. - 0 lines covered and - 0 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. +
    2. + + 2 - # Be sure to restart your server when you modify this file. + User.create(nome: 'Teste', email_address: 'teste@example.com', matricula: '111', password: 'password')
    3. -
    4. +
    5. - + end
    6. -
    7. +
    8. + + 3 - # Define an application-wide content security policy. + let(:mail) { UserMailer.definicao_senha(user) }
    9. -
    10. +
    11. - # See the Securing Rails Applications Guide for more information: +
    12. -
    13. +
    14. - # https://guides.rubyonrails.org/security.html#content-security-policy-header +
    15. -
    16. +
    17. @@ -6442,305 +11387,300 @@

    18. -
    19. +
    20. + + 1 - # Rails.application.configure do + it 'renders the headers' do
    21. -
    22. +
    23. + + 1 - # config.content_security_policy do |policy| + expect(mail.subject).to eq('Definição de Senha - Sistema de Gestão')
    24. -
    25. +
    26. - # policy.default_src :self, :https + expect(mail.to).to eq([user.email_address])
    27. -
    28. +
    29. - # policy.font_src :self, :https, :data + end
    30. -
    31. +
    32. - # policy.img_src :self, :https, :data +
    33. -
    34. +
    35. + + 1 - # policy.object_src :none + it 'renders the body' do
    36. -
    37. +
    38. + + 1 - # policy.script_src :self, :https + expect(mail.body.encoded).to match('Definição de Senha')
    39. -
    40. +
    41. - # policy.style_src :self, :https + end
    42. -
    43. +
    44. - # # Specify URI for violation reports + end
    45. -
    46. +
    47. - # # policy.report_uri "/csp-violation-report-endpoint" +
    48. -
    49. +
    50. + + 1 - # end + describe 'cadastro_email' do
    51. -
    52. +
    53. + + 1 - # + let(:user) do
    54. -
    55. +
    56. + + 2 - # # Generate session nonces for permitted importmap, inline scripts, and inline styles. + User.create(nome: 'Teste', email_address: 'teste@example.com', matricula: '222', password: 'password')
    57. -
    58. +
    59. - # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } + end
    60. -
    61. +
    62. + + 3 - # config.content_security_policy_nonce_directives = %w(script-src style-src) + let(:password) { 'secret123' }
    63. -
    64. +
    65. + + 3 - # + let(:mail) { UserMailer.cadastro_email(user, password) }
    66. -
    67. +
    68. - # # Report violations without enforcing the policy. +
    69. -
    70. +
    71. + + 1 - # # config.content_security_policy_report_only = true + it 'renders the headers' do
    72. -
    73. +
    74. + + 1 - # end + expect(mail.subject).to eq('Bem-vindo(a) ao CAMAAR - Sua senha de acesso')
    75. -
    -
    -
    - - -
    -
    -

    config/initializers/filter_parameter_logging.rb

    -

    - - 100.0% - - - lines covered -

    - - - -
    - 1 relevant lines. - 1 lines covered and - 0 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. +
    2. + + 1 - # Be sure to restart your server when you modify this file. + expect(mail.to).to eq([user.email_address])
    3. -
    4. +
    5. - + end
    6. -
    7. +
    8. - # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +
    9. -
    10. +
    11. + + 1 - # Use this to limit dissemination of sensitive information. + it 'renders the body with password' do
    12. -
    13. +
    14. + + 1 - # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. + expect(mail.body.encoded).to match(password)
    15. -
    16. - - 1 +
    17. - Rails.application.config.filter_parameters += [ + end
    18. -
    19. +
    20. - :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc + end
    21. -
    22. +
    23. - ] + end
    24. @@ -6749,9 +11689,9 @@

    -
    +
    -

    config/initializers/inflections.rb

    +

    spec/models/avaliacao_spec.rb

    100.0% @@ -6763,8 +11703,8 @@

    - 2 relevant lines. - 2 lines covered and + 12 relevant lines. + 12 lines covered and 0 lines missed.
    @@ -6776,12 +11716,14 @@

      -
    1. +
    2. + + 1 - # Be sure to restart your server when you modify this file. + require 'rails_helper'
    3. @@ -6796,42 +11738,50 @@

    -
  • +
  • + + 1 - # Add new inflection rules using the following format. Inflections + RSpec.describe Avaliacao, type: :model do
  • -
  • +
  • + + 1 - # are locale specific, and you may define rules for as many different + describe 'associações' do
  • -
  • +
  • + + 1 - # locales as you wish. All of these examples are active by default: + it 'pertence a turma' do
  • -
  • +
  • + + 1 - # ActiveSupport::Inflector.inflections(:en) do |inflect| + expect(described_class.reflect_on_association(:turma).macro).to eq :belongs_to
  • @@ -6841,7 +11791,7 @@

    - # inflect.plural /^(ox)$/i, "\\1en" + end

    @@ -6851,27 +11801,31 @@

    - # inflect.singular /^(ox)en/i, "\\1" +

    -
  • +
  • + + 1 - # inflect.irregular "person", "people" + it 'pertence a modelo' do
  • -
  • +
  • + + 1 - # inflect.uncountable %w( fish sheep ) + expect(described_class.reflect_on_association(:modelo).macro).to eq :belongs_to
  • @@ -6881,7 +11835,7 @@

    - # end + end @@ -6896,12 +11850,14 @@

    -
  • +
  • + + 1 - # These inflection rules are supported but not enabled by default: + it 'pertence a professor_alvo como Usuario (opcional)' do
  • @@ -6913,7 +11869,7 @@

    - ActiveSupport::Inflector.inflections(:en) do |inflect| + assoc = described_class.reflect_on_association(:professor_alvo) @@ -6925,87 +11881,56 @@

    - inflect.irregular "pergunta", "perguntas" + expect(assoc.macro).to eq :belongs_to
    -
  • +
  • + + 1 - # inflect.acronym "RESTful" + expect(assoc.class_name).to eq 'User'
  • -
  • +
  • + + 1 - end + expect(assoc.options[:optional]).to eq true
  • - - - - - -
    -
    -

    config/initializers/inflections_custom.rb

    -

    - - 100.0% - - - lines covered -

    - - - -
    - 2 relevant lines. - 2 lines covered and - 0 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. - - 1 +
    2. - ActiveSupport::Inflector.inflections(:en) do |inflect| + end
    3. -
    4. - - 1 +
    5. - inflect.irregular 'avaliacao', 'avaliacoes' + end
    6. -
    7. +
    8. @@ -7019,12 +11944,12 @@

    9. -
      +
      -

      config/initializers/mail.rb

      +

      spec/models/matricula_turma_spec.rb

      - - 25.0% + + 100.0% lines covered @@ -7033,9 +11958,9 @@

      - 12 relevant lines. - 3 lines covered and - 9 lines missed. + 9 relevant lines. + 9 lines covered and + 0 lines missed.
      @@ -7046,12 +11971,14 @@

        -
      1. +
      2. + + 1 - # config/initializers/mail.rb + require 'rails_helper'
      3. @@ -7061,17 +11988,19 @@

        - # Configuração de email para CAMAAR +

      -
    10. +
    11. + + 1 - + RSpec.describe MatriculaTurma, type: :model do
    12. @@ -7083,17 +12012,19 @@

      - if Rails.env.test? + describe 'associations' do

      -
    13. +
    14. + + 1 - # Em testes: captura emails sem enviar + it 'belongs to user' do
    15. @@ -7105,7 +12036,7 @@

      - Rails.application.config.action_mailer.delivery_method = :test + association = described_class.reflect_on_association(:user)

      @@ -7117,7 +12048,7 @@

      - Rails.application.config.action_mailer.default_url_options = { + expect(association.macro).to eq :belongs_to

    @@ -7127,7 +12058,7 @@

    - host: 'localhost', + end @@ -7137,37 +12068,43 @@

    - port: 3000 +
    -
  • +
  • + + 1 - } + it 'belongs to turma' do
  • -
  • +
  • + + 1 - + association = described_class.reflect_on_association(:turma)
  • -
  • +
  • + + 1 - elsif Rails.env.development? + expect(association.macro).to eq :belongs_to
  • @@ -7177,7 +12114,7 @@

    - # MVP: Usa letter_opener (emails abrem no navegador) + end @@ -7187,7 +12124,7 @@

    - # Instale: gem install letter_opener ou adicione ao Gemfile + end @@ -7197,1085 +12134,1185 @@

    - # PRODUÇÃO: Para enviar emails reais, descomente a seção SMTP abaixo + end + + + + + +
    +
    +

    spec/models/turma_spec.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 10 relevant lines. + 10 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. +
    2. + + 1 - + require 'rails_helper'
    3. -
    4. +
    5. - Rails.application.config.action_mailer.delivery_method = :letter_opener +
    6. -
    7. +
    8. + + 1 - Rails.application.config.action_mailer.perform_deliveries = true + RSpec.describe Turma, type: :model do
    9. -
    10. +
    11. + + 1 - + describe 'associations' do
    12. -
    13. +
    14. + + 1 - # === SMTP (Para Produção) === + it 'has many matricula_turmas' do
    15. -
    16. +
    17. + + 1 - # Descomente as linhas abaixo e configure variáveis de ambiente (.env) + association = described_class.reflect_on_association(:matricula_turmas)
    18. -
    19. +
    20. + + 1 - # para enviar emails reais via SMTP (Gmail, Sendgrid, etc.) + expect(association.macro).to eq :has_many
    21. -
    22. +
    23. - # + end
    24. -
    25. +
    26. - # Rails.application.config.action_mailer.delivery_method = :smtp +
    27. -
    28. +
    29. + + 1 - # Rails.application.config.action_mailer.raise_delivery_errors = true + it 'has many users through matricula_turmas' do
    30. -
    31. +
    32. + + 1 - # + association = described_class.reflect_on_association(:users)
    33. -
    34. +
    35. + + 1 - # Rails.application.config.action_mailer.smtp_settings = { + expect(association.macro).to eq :has_many
    36. -
    37. +
    38. + + 1 - # address: ENV.fetch('SMTP_ADDRESS', 'smtp.gmail.com'), + expect(association.options[:through]).to eq :matricula_turmas
    39. -
    40. +
    41. - # port: ENV.fetch('SMTP_PORT', '587').to_i, + end
    42. -
    43. +
    44. - # domain: ENV.fetch('SMTP_DOMAIN', 'localhost'), + end
    45. -
    46. +
    47. - # user_name: ENV['SMTP_USER'], + end
    48. +
    +
    +
    + + +
    +
    +

    spec/models/user_spec.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 11 relevant lines. + 11 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. +
    2. + + 1 - # password: ENV['SMTP_PASSWORD'], + require 'rails_helper'
    3. -
    4. +
    5. - # authentication: 'plain', +
    6. -
    7. +
    8. + + 1 - # enable_starttls_auto: true + RSpec.describe User, type: :model do
    9. -
    10. +
    11. + + 1 - # } + describe 'associations' do
    12. -
    13. +
    14. + + 1 - + it 'has many matricula_turmas' do
    15. -
    16. +
    17. + + 1 - Rails.application.config.action_mailer.default_url_options = { + association = described_class.reflect_on_association(:matricula_turmas)
    18. -
    19. +
    20. + + 1 - host: ENV.fetch('APP_HOST', 'localhost'), + expect(association.macro).to eq :has_many
    21. -
    22. +
    23. - port: ENV.fetch('APP_PORT', '3000').to_i + end
    24. -
    25. +
    26. - } +
    27. -
    28. +
    29. + + 1 - + it 'has many turmas through matricula_turmas' do
    30. -
    31. +
    32. + + 1 + + + + + association = described_class.reflect_on_association(:turmas) +
    33. +
      + +
      +
    34. + + 1 + + + + + expect(association.macro).to eq :has_many +
    35. +
      + +
      +
    36. + + 1 - else + expect(association.options[:through]).to eq :matricula_turmas
    37. -
    38. +
    39. - # Produção: SMTP obrigatório + end
    40. -
    41. +
    42. - Rails.application.config.action_mailer.delivery_method = :smtp + end
    43. -
    44. +
    45. - Rails.application.config.action_mailer.perform_deliveries = true +
    46. -
    47. +
    48. + + 1 - Rails.application.config.action_mailer.raise_delivery_errors = false + describe 'validations' do
    49. -
    50. +
    51. - + # Adicione validações aqui se houver
    52. -
    53. +
    54. - Rails.application.config.action_mailer.smtp_settings = { + end
    55. -
    56. +
    57. - address: ENV.fetch('SMTP_ADDRESS'), + end
    58. +
    +
    +
    + + +
    +
    +

    spec/rails_helper.rb

    +

    + + 90.91% + + + lines covered +

    + + + +
    + 11 relevant lines. + 10 lines covered and + 1 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. +
    2. - port: ENV.fetch('SMTP_PORT', '587').to_i, + # This file is copied to spec/ when you run 'rails generate rspec:install'
    3. -
    4. +
    5. + + 1 - domain: ENV.fetch('SMTP_DOMAIN'), + require 'spec_helper'
    6. -
    7. +
    8. + + 1 - user_name: ENV.fetch('SMTP_USER'), + ENV['RAILS_ENV'] ||= 'test'
    9. -
    10. +
    11. + + 1 - password: ENV.fetch('SMTP_PASSWORD'), + require_relative '../config/environment'
    12. -
    13. +
    14. - authentication: 'plain', + # Prevent database truncation if the environment is production
    15. -
    16. +
    17. + + 1 - enable_starttls_auto: true, + abort("The Rails environment is running in production mode!") if Rails.env.production?
    18. -
    19. +
    20. - open_timeout: 10, + # Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file
    21. -
    22. +
    23. - read_timeout: 10 + # that will avoid rails generators crashing because migrations haven't been run yet
    24. -
    25. +
    26. - } + # return unless Rails.env.test?
    27. -
    28. +
    29. + + 1 - + require 'rspec/rails'
    30. -
    31. +
    32. - Rails.application.config.action_mailer.default_url_options = { + # Add additional requires below this line. Rails is not loaded until this point!
    33. -
    34. +
    35. - host: ENV.fetch('APP_HOST'), +
    36. -
    37. +
    38. - protocol: 'https' + # Requires supporting ruby files with custom matchers and macros, etc, in
    39. -
    40. +
    41. - } + # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
    42. -
    43. +
    44. - end + # run as spec files by default. This means that files in spec/support that end
    45. -
    46. +
    47. - + # in _spec.rb will both be required and run as specs, causing the specs to be
    48. -
    -
    -
    - - -
    -
    -

    config/routes.rb

    -

    - - 100.0% - - - lines covered -

    - - - -
    - 23 relevant lines. - 23 lines covered and - 0 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. - - 1 +
    2. - Rails.application.routes.draw do + # run twice. It is recommended that you do not name files matching this glob to
    3. -
    4. +
    5. - # --- ROTAS DE AVALIACOES --- + # end with _spec.rb. You can configure this pattern with the --pattern
    6. -
    7. - - 1 +
    8. - resources :avaliacoes, only: [:index, :create] do + # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
    9. -
    10. - - 1 +
    11. - collection do + #
    12. -
    13. - - 1 +
    14. - get :gestao_envios + # The following line is provided for convenience purposes. It has the downside
    15. -
    16. +
    17. - end + # of increasing the boot-up time by auto-requiring all files in the support
    18. -
    19. - - 1 +
    20. - member do + # directory. Alternatively, in the individual `*_spec.rb` files, manually
    21. -
    22. - - 1 +
    23. - get :resultados + # require only the support files necessary.
    24. -
    25. +
    26. - end + #
    27. -
    28. +
    29. - # Rotas para alunos responderem avaliações (Feature 99) + # Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }
    30. -
    31. - - 1 +
    32. - resources :respostas, only: [:new, :create] +
    33. -
    34. +
    35. - end + # Ensures that the test database schema matches the current schema file.
    36. -
    37. +
    38. - + # If there are pending migrations it will invoke `db:test:prepare` to
    39. -
    40. +
    41. - # --- ROTAS DE IMPORTAÇÃO SIGAA --- + # recreate the test database by loading the schema.
    42. -
    43. - - 1 +
    44. - resources :sigaa_imports, only: [:new, :create] do + # If you are not using ActiveRecord, you can remove these lines.
    45. -
    46. - - 1 +
    47. - collection do + begin
    48. -
    49. +
    50. 1 - post :update # For update/sync operations + ActiveRecord::Migration.maintain_test_schema!
    51. -
    52. - - 1 +
    53. - get :success # For showing import results + rescue ActiveRecord::PendingMigrationError => e
    54. -
    55. +
    56. - end + abort e.to_s.strip
    57. -
    58. +
    59. - end + end
    60. -
    61. +
    62. + + 1 - + RSpec.configure do |config|
    63. -
    64. +
    65. - # --- ROTAS DE GERENCIAMENTO DE MODELOS --- + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
    66. -
    67. +
    68. 1 - resources :modelos do + config.fixture_paths = [
    69. -
    70. - - 1 +
    71. - member do + Rails.root.join('spec/fixtures')
    72. -
    73. - - 1 +
    74. - post :clone + ]
    75. -
    76. +
    77. - end +
    78. -
    79. +
    80. - end + # If you're not using ActiveRecord, or you'd prefer not to run each of your
    81. -
    82. +
    83. - + # examples within a transaction, remove the following line or assign false
    84. -
    85. - - 1 +
    86. - resource :session + # instead of true.
    87. -
    88. +
    89. 1 - resources :passwords, param: :token + config.use_transactional_fixtures = true
    90. -
    91. - - 1 +
    92. - get "home/index" +
    93. -
    94. +
    95. - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html + # You can uncomment this line to turn off ActiveRecord support entirely.
    96. -
    97. +
    98. - + # config.use_active_record = false
    99. -
    100. +
    101. - # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. +
    102. -
    103. +
    104. - # Can be used by load balancers and uptime monitors to verify that the app is live. + # RSpec Rails uses metadata to mix in different behaviours to your tests,
    105. -
    106. - - 1 +
    107. - get "up" => "rails/health#show", as: :rails_health_check + # for example enabling you to call `get` and `post` in request specs. e.g.:
    108. -
    109. +
    110. - + #
    111. -
    112. +
    113. - # Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb) + # RSpec.describe UsersController, type: :request do
    114. -
    115. +
    116. - # get "manifest" => "rails/pwa#manifest", as: :pwa_manifest + # # ...
    117. -
    118. +
    119. - # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker + # end
    120. -
    121. +
    122. - + #
    123. -
    124. +
    125. - # --- ROTAS DO INTEGRANTE 4 (RESPOSTAS) --- + # The different available types are documented in the features, such as in
    126. -
    127. +
    128. - # Define as rotas aninhadas para criar respostas dentro de um formulário + # https://rspec.info/features/8-0/rspec-rails
    129. -
    130. - - 1 +
    131. - resources :formularios, only: [] do + #
    132. -
    133. - - 1 +
    134. - resources :respostas, only: [ :index, :new, :create ] + # You can also this infer these behaviours automatically by location, e.g.
    135. -
    136. +
    137. - end + # /spec/models would pull in the same behaviour as `type: :model` but this
    138. -
    139. +
    140. - + # behaviour is considered legacy and will be removed in a future version.
    141. -
    142. +
    143. - # Rota solta para a listagem geral de respostas (dashboard do aluno) + #
    144. -
    145. - - 1 +
    146. - get "respostas", to: "respostas#index" + # To enable this behaviour uncomment the line below.
    147. -
    148. +
    149. - # ----------------------------------------- + # config.infer_spec_type_from_file_location!
    150. -
    151. +
    152. @@ -8285,51 +13322,49 @@

    153. -
    154. +
    155. - # Defines the root path route ("/") + # Filter lines from Rails gems in backtraces.
    156. -
    157. +
    158. 1 - root "pages#index" + config.filter_rails_from_backtrace!
    159. -
    160. +
    161. - + # arbitrary gems may also be filtered via:
    162. -
    163. - - 1 +
    164. - get "home" => "home#index" + # config.filter_gems_from_backtrace("gem name")
    165. -
    166. +
    167. @@ -8343,12 +13378,12 @@

    168. -
      +
      -

      spec/models/avaliacao_spec.rb

      +

      spec/requests/avaliacoes_spec.rb

      - - 100.0% + + 50.0% lines covered @@ -8357,9 +13392,9 @@

      - 12 relevant lines. - 12 lines covered and - 0 lines missed. + 30 relevant lines. + 15 lines covered and + 15 lines missed.
      @@ -8399,7 +13434,7 @@

      - RSpec.describe Avaliacao, type: :model do + RSpec.describe "Avaliações", type: :request do

      @@ -8411,7 +13446,7 @@

      - describe 'associações' do + describe "GET /gestao_envios" do

      @@ -8423,7 +13458,7 @@

      - it 'pertence a turma' do + it "retorna sucesso HTTP" do

      @@ -8435,17 +13470,19 @@

      - expect(described_class.reflect_on_association(:turma).macro).to eq :belongs_to + get gestao_envios_avaliacoes_path

    -
  • +
  • + + 1 - end + expect(response).to have_http_status(:success)
  • @@ -8455,46 +13492,68 @@

    + end + + + +
    +
  • + + + + + end +
  • +
    + +
    +
  • + + + +
  • -
  • +
  • 1 - it 'pertence a modelo' do + describe "POST /create" do
  • -
  • +
  • - 1 + 4 - expect(described_class.reflect_on_association(:modelo).macro).to eq :belongs_to + let!(:turma) { Turma.create!(codigo: "CIC001", nome: "Turma de Teste", semestre: "2024.1") }
  • -
  • +
  • + + 4 - end + let!(:template) { Modelo.create!(titulo: "Template Padrão", ativo: true) }
  • -
  • +
  • @@ -8504,554 +13563,554 @@

  • -
  • +
  • 1 - it 'pertence a professor_alvo como Usuario (opcional)' do + context "com entradas válidas" do
  • -
  • +
  • 1 - assoc = described_class.reflect_on_association(:professor_alvo) + it "cria uma nova Avaliação vinculada ao template padrão" do
  • -
  • - - 1 +
  • - expect(assoc.macro).to eq :belongs_to + expect {
  • -
  • - - 1 +
  • - expect(assoc.class_name).to eq 'User' + post avaliacoes_path, params: { turma_id: turma.id }
  • -
  • - - 1 +
  • - expect(assoc.options[:optional]).to eq true + }.to change(Avaliacao, :count).by(1)
  • -
  • +
  • - end +
  • -
  • +
  • - end + avaliacao = Avaliacao.last
  • -
  • +
  • - end + expect(avaliacao.turma).to eq(turma)
  • - - - - - -
    -
    -

    spec/rails_helper.rb

    -

    - - 90.91% - - - lines covered -

    - - - -
    - 11 relevant lines. - 10 lines covered and - 1 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. +
    2. - # This file is copied to spec/ when you run 'rails generate rspec:install' + expect(avaliacao.modelo).to eq(template)
    3. -
    4. - - 1 +
    5. - require 'spec_helper' + expect(response).to redirect_to(gestao_envios_avaliacoes_path)
    6. -
    7. - - 1 +
    8. - ENV['RAILS_ENV'] ||= 'test' + expect(flash[:notice]).to be_present
    9. -
    10. - - 1 +
    11. - require_relative '../config/environment' + end
    12. -
    13. +
    14. - # Prevent database truncation if the environment is production +
    15. -
    16. +
    17. 1 - abort("The Rails environment is running in production mode!") if Rails.env.production? + it "aceita uma data_fim personalizada" do
    18. -
    19. +
    20. - # Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file + data_personalizada = 2.weeks.from_now.to_date
    21. -
    22. +
    23. - # that will avoid rails generators crashing because migrations haven't been run yet + post avaliacoes_path, params: { turma_id: turma.id, data_fim: data_personalizada }
    24. -
    25. +
    26. - # return unless Rails.env.test? +
    27. -
    28. - - 1 +
    29. - require 'rspec/rails' + avaliacao = Avaliacao.last
    30. -
    31. +
    32. - # Add additional requires below this line. Rails is not loaded until this point! + expect(avaliacao.data_fim.to_date).to eq(data_personalizada)
    33. -
    34. +
    35. - + end
    36. -
    37. +
    38. - # Requires supporting ruby files with custom matchers and macros, etc, in + end
    39. -
    40. +
    41. - # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +
    42. -
    43. +
    44. + + 1 - # run as spec files by default. This means that files in spec/support that end + context "quando o template padrão está ausente" do
    45. -
    46. +
    47. + + 1 - # in _spec.rb will both be required and run as specs, causing the specs to be + before { template.update!(titulo: "Outro") }
    48. -
    49. +
    50. - # run twice. It is recommended that you do not name files matching this glob to +
    51. -
    52. +
    53. + + 1 - # end with _spec.rb. You can configure this pattern with the --pattern + it "não cria avaliação e redireciona com alerta" do
    54. -
    55. +
    56. - # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. + expect {
    57. -
    58. +
    59. - # + post avaliacoes_path, params: { turma_id: turma.id }
    60. -
    61. +
    62. - # The following line is provided for convenience purposes. It has the downside + }.not_to change(Avaliacao, :count)
    63. -
    64. +
    65. - # of increasing the boot-up time by auto-requiring all files in the support +
    66. -
    67. +
    68. - # directory. Alternatively, in the individual `*_spec.rb` files, manually + expect(response).to redirect_to(gestao_envios_avaliacoes_path)
    69. -
    70. +
    71. - # require only the support files necessary. + expect(flash[:alert]).to include("Template Padrão não encontrado")
    72. -
    73. +
    74. - # + end
    75. -
    76. +
    77. - # Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } + end
    78. -
    79. +
    80. - + end
    81. -
    82. +
    83. - # Ensures that the test database schema matches the current schema file. + end
    84. +
    +
    +
    + + +
    +
    +

    spec/services/csv_formatter_service_spec.rb

    +

    + + 100.0% + + + lines covered +

    + + + +
    + 20 relevant lines. + 20 lines covered and + 0 lines missed. +
    + + + +
    + +
    +    
      +
      -
    1. +
    2. + + 1 - # If there are pending migrations it will invoke `db:test:prepare` to + require 'rails_helper'
    3. -
    4. +
    5. - # recreate the test database by loading the schema. +
    6. -
    7. +
    8. + + 1 - # If you are not using ActiveRecord, you can remove these lines. + RSpec.describe CsvFormatterService do
    9. -
    10. +
    11. + + 1 - begin + describe '#generate' do
    12. -
    13. +
    14. - 1 + 2 - ActiveRecord::Migration.maintain_test_schema! + let(:modelo) { double('Modelo', perguntas: [
    15. -
    16. +
    17. - rescue ActiveRecord::PendingMigrationError => e + double('Pergunta', enunciado: 'Q1'),
    18. -
    19. +
    20. - abort e.to_s.strip + double('Pergunta', enunciado: 'Q2')
    21. -
    22. +
    23. - end + ])}
    24. -
    25. - - 1 +
    26. - RSpec.configure do |config| +
    27. -
    28. +
    29. + + 2 - # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + let(:avaliacao) { double('Avaliacao', id: 1, modelo: modelo) }
    30. -
    31. - - 1 +
    32. - config.fixture_paths = [ +
    33. -
    34. +
    35. + + 2 - Rails.root.join('spec/fixtures') + let(:aluno1) { double('User', matricula: '123', nome: 'Alice') }
    36. -
    37. +
    38. + + 2 - ] + let(:aluno2) { double('User', matricula: '456', nome: 'Bob') }
    39. -
    40. +
    41. @@ -9061,49 +14120,53 @@

    42. -
    43. +
    44. - # If you're not using ActiveRecord, or you'd prefer not to run each of your + # Respostas não tem mais aluno direto, mas através de submissão
    45. -
    46. +
    47. + + 2 - # examples within a transaction, remove the following line or assign false + let(:resposta_a1_q1) { double('Resposta', conteudo: 'Ans 1A') }
    48. -
    49. +
    50. + + 2 - # instead of true. + let(:resposta_a1_q2) { double('Resposta', conteudo: 'Ans 1B') }
    51. -
    52. +
    53. - 1 + 2 - config.use_transactional_fixtures = true + let(:resposta_a2_q1) { double('Resposta', conteudo: 'Ans 2A') }
    54. -
    55. +
    56. @@ -9113,249 +14176,267 @@

    57. -
    58. +
    59. - # You can uncomment this line to turn off ActiveRecord support entirely. + # Submissoes ligando aluno e respostas
    60. -
    61. +
    62. + + 2 - # config.use_active_record = false + let(:submissao1) { double('Submissao', aluno: aluno1, respostas: [resposta_a1_q1, resposta_a1_q2]) }
    63. -
    64. +
    65. + + 2 - + let(:submissao2) { double('Submissao', aluno: aluno2, respostas: [resposta_a2_q1]) }
    66. -
    67. +
    68. - # RSpec Rails uses metadata to mix in different behaviours to your tests, +
    69. -
    70. +
    71. + + 1 - # for example enabling you to call `get` and `post` in request specs. e.g.: + before do
    72. -
    73. +
    74. - # + # Mock da cadeia: avaliacao.submissoes.includes.each
    75. -
    76. +
    77. - # RSpec.describe UsersController, type: :request do + # Simulando o comportamento do loop no service
    78. -
    79. +
    80. + + 1 - # # ... + allow(avaliacao).to receive_message_chain(:submissoes, :includes).and_return([submissao1, submissao2])
    81. -
    82. +
    83. - # end + end
    84. -
    85. +
    86. - # +
    87. -
    88. +
    89. + + 1 - # The different available types are documented in the features, such as in + it 'gera uma string CSV válida com cabeçalhos e linhas' do
    90. -
    91. +
    92. + + 1 - # https://rspec.info/features/8-0/rspec-rails + csv_string = described_class.new(avaliacao).generate
    93. -
    94. +
    95. + + 1 - # + rows = csv_string.split("\n")
    96. -
    97. +
    98. - # You can also this infer these behaviours automatically by location, e.g. +
    99. -
    100. +
    101. - # /spec/models would pull in the same behaviour as `type: :model` but this + # Cabeçalhos: Matrícula, Nome, Questão 1, Questão 2
    102. -
    103. +
    104. + + 1 - # behaviour is considered legacy and will be removed in a future version. + expect(rows[0]).to include("Matrícula,Nome,Questão 1,Questão 2")
    105. -
    106. +
    107. - # +
    108. -
    109. +
    110. - # To enable this behaviour uncomment the line below. + # Linha 1: Respostas da Alice
    111. -
    112. +
    113. + + 1 - # config.infer_spec_type_from_file_location! + expect(rows[1]).to include("123,Alice,Ans 1A,Ans 1B")
    114. -
    115. +
    116. - +
    117. -
    118. +
    119. - # Filter lines from Rails gems in backtraces. + # Linha 2: Respostas do Bob
    120. -
    121. +
    122. 1 - config.filter_rails_from_backtrace! + expect(rows[2]).to include("456,Bob,Ans 2A")
    123. -
    124. +
    125. - # arbitrary gems may also be filtered via: + end
    126. -
    127. +
    128. - # config.filter_gems_from_backtrace("gem name") + end
    129. -
    130. +
    131. @@ -9369,12 +14450,12 @@

    132. -
      +
      -

      spec/requests/avaliacoes_spec.rb

      +

      spec/services/sigaa_import_service_spec.rb

      - - 50.0% + + 93.62% lines covered @@ -9383,9 +14464,9 @@

      - 30 relevant lines. - 15 lines covered and - 15 lines missed. + 47 relevant lines. + 44 lines covered and + 3 lines missed.
      @@ -9408,12 +14489,14 @@

      -
    133. +
    134. + + 1 - + require 'rspec/support/differ'
    135. @@ -9425,7 +14508,7 @@

      - RSpec.describe "Avaliações", type: :request do + require 'rspec/support/hunk_generator'

      @@ -9437,19 +14520,17 @@

      - describe "GET /gestao_envios" do + require 'diff/lcs'

      -
    136. - - 1 +
    137. - it "retorna sucesso HTTP" do +
    138. @@ -9461,39 +14542,43 @@

      - get gestao_envios_avaliacoes_path + RSpec.describe SigaaImportService, type: :service do

    -
  • +
  • - 1 + 4 - expect(response).to have_http_status(:success) + let(:json_path) { Rails.root.join('spec/fixtures/turmas.json') }
  • -
  • +
  • + + 4 - end + let(:csv_path) { Rails.root.join('spec/fixtures/turmas.csv') }
  • -
  • +
  • + + 4 - end + let(:invalid_path) { Rails.root.join('spec/fixtures/invalid.txt') }
  • @@ -9515,85 +14600,81 @@

    - describe "POST /create" do + before do
    -
  • - - 4 +
  • - let!(:turma) { Turma.create!(codigo: "CIC001", nome: "Turma de Teste", semestre: "2024.1") } + # Garante que fixtures existem ou são mockados
  • -
  • +
  • - 4 + 3 - let!(:template) { Modelo.create!(titulo: "Template Padrão", ativo: true) } + allow(File).to receive(:exist?).and_return(true)
  • -
  • +
  • + + 3 - + allow(File).to receive(:read).with(json_path).and_return([
  • -
  • - - 1 +
  • - context "com entradas válidas" do + {
  • -
  • - - 1 +
  • - it "cria uma nova Avaliação vinculada ao template padrão" do + 'code' => 'T01',
  • -
  • +
  • - expect { + 'semester' => '2024.1',
  • -
  • +
  • - post avaliacoes_path, params: { turma_id: turma.id } + 'dicente' => [
  • @@ -9603,7 +14684,7 @@

    - }.to change(Avaliacao, :count).by(1) + { @@ -9613,57 +14694,57 @@

    - + 'matricula' => '123456',
    -
  • +
  • - avaliacao = Avaliacao.last + 'nome' => 'João Silva',
  • -
  • +
  • - expect(avaliacao.turma).to eq(turma) + 'email' => 'joao@example.com'
  • -
  • +
  • - expect(avaliacao.modelo).to eq(template) + }
  • -
  • +
  • - expect(response).to redirect_to(gestao_envios_avaliacoes_path) + ],
  • -
  • +
  • - expect(flash[:notice]).to be_present + 'docente' => {
  • @@ -9673,7 +14754,7 @@

    - end + 'usuario' => '654321', @@ -9683,39 +14764,37 @@

    - + 'nome' => 'Maria Professora',
    -
  • - - 1 +
  • - it "aceita uma data_fim personalizada" do + 'email' => 'maria@prof.com'
  • -
  • +
  • - data_personalizada = 2.weeks.from_now.to_date + }
  • -
  • +
  • - post avaliacoes_path, params: { turma_id: turma.id, data_fim: data_personalizada } + }
  • @@ -9725,27 +14804,29 @@

    - + ].to_json)
    -
  • +
  • - avaliacao = Avaliacao.last +
  • -
  • +
  • + + 3 - expect(avaliacao.data_fim.to_date).to eq(data_personalizada) + allow(CSV).to receive(:foreach).with(csv_path, headers: true, col_sep: ',').and_yield(
  • @@ -9755,7 +14836,7 @@

    - end + CSV::Row.new(%w[codigo_turma nome_turma semestre nome_usuario email matricula papel], @@ -9765,7 +14846,7 @@

    - end + ['T02', 'Banco de Dados', '2024.1', 'Maria Souza', 'maria@example.com', '654321', 'professor']) @@ -9775,73 +14856,75 @@

    - + )
    -
  • - - 1 +
  • - context "quando o template padrão está ausente" do +
  • -
  • +
  • - 1 + 3 - before { template.update!(titulo: "Outro") } + allow(File).to receive(:extname).and_call_original
  • -
  • +
  • + + 3 - + allow(File).to receive(:extname).with(json_path).and_return('.json')
  • -
  • +
  • - 1 + 3 - it "não cria avaliação e redireciona com alerta" do + allow(File).to receive(:extname).with(csv_path).and_return('.csv')
  • -
  • +
  • + + 3 - expect { + allow(File).to receive(:extname).with(invalid_path).and_return('.txt')
  • -
  • +
  • - post avaliacoes_path, params: { turma_id: turma.id } + end
  • @@ -9851,27 +14934,31 @@

    - }.not_to change(Avaliacao, :count) +
    -
  • +
  • + + 1 - + class DummyMessage
  • -
  • +
  • + + 1 - expect(response).to redirect_to(gestao_envios_avaliacoes_path) + def deliver_now
  • @@ -9881,7 +14968,7 @@

    - expect(flash[:alert]).to include("Template Padrão não encontrado") + true @@ -9891,7 +14978,7 @@

    - end + end @@ -9901,7 +14988,7 @@

    - end + end @@ -9911,361 +14998,340 @@

    - end +
    -
  • +
  • + + 1 - end + class DummyMailer
  • - - - - - -
    -
    -

    spec/services/csv_formatter_service_spec.rb

    -

    - - 100.0% - - - lines covered -

    - - - -
    - 20 relevant lines. - 20 lines covered and - 0 lines missed. -
    - - - -
    - -
    -    
      -
      -
    1. +
    2. 1 - require 'rails_helper' + def self.cadastro_email(user, password)
    3. -
    4. +
    5. - + DummyMessage.new
    6. -
    7. - - 1 +
    8. - RSpec.describe CsvFormatterService do + end
    9. -
    10. - - 1 +
    11. - describe '#generate' do + end
    12. -
    13. - - 2 +
    14. - let(:modelo) { double('Modelo', perguntas: [ +
    15. -
    16. +
    17. + + 1 - double('Pergunta', enunciado: 'Q1'), + describe '#process' do
    18. -
    19. +
    20. + + 1 - double('Pergunta', enunciado: 'Q2') + context 'with JSON file' do
    21. -
    22. +
    23. + + 1 - ])} + it 'creates turmas and users' do
    24. -
    25. +
    26. + + 1 - + Turma.delete_all
    27. -
    28. +
    29. - 2 + 1 - let(:avaliacao) { double('Avaliacao', id: 1, modelo: modelo) } + User.delete_all
    30. -
    31. +
    32. - +
    33. -
    34. +
    35. - 2 + 1 - let(:aluno1) { double('User', matricula: '123', nome: 'Alice') } + service = SigaaImportService.new(json_path)
    36. -
    37. +
    38. - 2 + 1 - let(:aluno2) { double('User', matricula: '456', nome: 'Bob') } + result = service.process
    39. -
    40. +
    41. + + 1 - + puts "JSON Import Errors: #{result[:errors]}" if result[:errors].any?
    42. -
    43. +
    44. - # Respostas não tem mais aluno direto, mas através de submissão +
    45. -
    46. +
    47. - 2 + 1 - let(:resposta_a1_q1) { double('Resposta', conteudo: 'Ans 1A') } + expect(Turma.count).to eq(1)
    48. -
    49. +
    50. - 2 + 1 - let(:resposta_a1_q2) { double('Resposta', conteudo: 'Ans 1B') } + expect(User.count).to eq(2)
    51. -
    52. - - 2 +
    53. - let(:resposta_a2_q1) { double('Resposta', conteudo: 'Ans 2A') } + end
    54. -
    55. +
    56. - + end
    57. -
    58. +
    59. - # Submissoes ligando aluno e respostas +
    60. -
    61. +
    62. - 2 + 1 - let(:submissao1) { double('Submissao', aluno: aluno1, respostas: [resposta_a1_q1, resposta_a1_q2]) } + context 'with CSV file' do
    63. -
    64. +
    65. - 2 + 1 - let(:submissao2) { double('Submissao', aluno: aluno2, respostas: [resposta_a2_q1]) } + it 'creates turmas and users' do
    66. -
    67. +
    68. + + 1 - + Turma.delete_all
    69. -
    70. +
    71. 1 - before do + User.delete_all
    72. -
    73. +
    74. - # Mock da cadeia: avaliacao.submissoes.includes.each +
    75. -
    76. +
    77. + + 1 - # Simulando o comportamento do loop no service + service = SigaaImportService.new(csv_path)
    78. -
    79. +
    80. 1 - allow(avaliacao).to receive_message_chain(:submissoes, :includes).and_return([submissao1, submissao2]) + result = service.process
    81. -
    82. +
    83. + + 1 - end + puts "CSV Import Errors: #{result[:errors]}" if result[:errors].any?
    84. -
    85. +
    86. @@ -10275,43 +15341,51 @@

    87. -
    88. +
    89. 1 - it 'gera uma string CSV válida com cabeçalhos e linhas' do + expect(Turma.count).to eq(1)
    90. -
    91. +
    92. 1 - csv_string = described_class.new(avaliacao).generate + expect(User.count).to eq(1)
    93. -
    94. +
    95. - 1 + + end +
    96. +
      + +
      +
    97. - rows = csv_string.split("\n") + + + end
    98. -
    99. +
    100. @@ -10321,93 +15395,107 @@

    101. -
    102. +
    103. + + 1 - # Cabeçalhos: Matrícula, Nome, Questão 1, Questão 2 + context 'with unsupported file format' do
    104. -
    105. +
    106. 1 - expect(rows[0]).to include("Matrícula,Nome,Questão 1,Questão 2") + it 'returns error' do
    107. -
    108. +
    109. + + 1 - + service = SigaaImportService.new(invalid_path)
    110. -
    111. +
    112. + + 1 - # Linha 1: Respostas da Alice + result = service.process
    113. -
    114. +
    115. + + + + + # Manual check to avoid RSpec HunkGenerator error +
    116. +
      + +
      +
    117. 1 - expect(rows[1]).to include("123,Alice,Ans 1A,Ans 1B") + unless result[:errors].join(', ').include?('Formato de arquivo não suportado')
    118. -
    119. +
    120. - + raise "Expected error 'Formato de arquivo não suportado' not found in: #{result[:errors]}"
    121. -
    122. +
    123. - # Linha 2: Respostas do Bob + end
    124. -
    125. - - 1 +
    126. - expect(rows[2]).to include("456,Bob,Ans 2A") + end
    127. -
    128. +
    129. @@ -10417,7 +15505,7 @@

    130. -
    131. +
    132. @@ -10427,7 +15515,7 @@

    133. -
    134. +
    135. diff --git a/db/migrate/20251208012954_drop_usuarios.rb b/db/migrate/20251208012954_drop_usuarios.rb index e39280fac8..e219d28979 100644 --- a/db/migrate/20251208012954_drop_usuarios.rb +++ b/db/migrate/20251208012954_drop_usuarios.rb @@ -11,4 +11,4 @@ def change t.timestamps end end -end \ No newline at end of file +end diff --git a/db/seeds.rb b/db/seeds.rb index d84a4cea0b..55f8649382 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -21,7 +21,7 @@ # Criar perguntas apenas se o modelo for novo ou não tiver perguntas if modelo.new_record? || modelo.perguntas.empty? modelo.perguntas.destroy_all if modelo.persisted? # Limpar perguntas antigas se existir - + modelo.perguntas.build([ { enunciado: 'O professor demonstrou domínio do conteúdo?', @@ -41,7 +41,7 @@ { enunciado: 'Você recomendaria esta disciplina?', tipo: 'multipla_escolha', - opcoes: ['Sim', 'Não', 'Talvez'] + opcoes: [ 'Sim', 'Não', 'Talvez' ] }, { enunciado: 'Comentários adicionais (opcional):', diff --git a/features/step_definitions/cria_avaliacao_steps.rb b/features/step_definitions/cria_avaliacao_steps.rb index 65e157ef81..fe70427afb 100644 --- a/features/step_definitions/cria_avaliacao_steps.rb +++ b/features/step_definitions/cria_avaliacao_steps.rb @@ -43,7 +43,7 @@ row = find("tr", text: @turma.codigo) within(row) do # Esvaziar o campo de data pode acionar validação do navegador ou erro no backend - fill_in "data_fim", with: "" + fill_in "data_fim", with: "" click_button "Gerar Avaliação" end end diff --git a/features/step_definitions/email_steps.rb b/features/step_definitions/email_steps.rb index 12a61f354e..2bcc9a61e0 100644 --- a/features/step_definitions/email_steps.rb +++ b/features/step_definitions/email_steps.rb @@ -9,7 +9,7 @@ Então('um email de boas-vindas deve ser enviado para {string}') do |email_address| emails = emails_sent_to(email_address) expect(emails).not_to be_empty, "Nenhum email foi enviado para #{email_address}" - + email = emails.last expect(email.subject).to include('Bem-vindo') end @@ -17,7 +17,7 @@ Então('o email deve conter a senha temporária') do email = last_email expect(email).not_to be_nil, "Nenhum email foi enviado" - + # Verifica se o email contém uma senha (padrão hex de 16 caracteres) body = email.body.to_s expect(body).to match(/[a-f0-9]{16}/i), "Email não contém senha temporária" @@ -26,7 +26,7 @@ Então('o email deve conter {string}') do |texto| email = last_email expect(email).not_to be_nil, "Nenhum email foi enviado" - + body = email.body.to_s expect(body).to include(texto), "Email não contém o texto esperado: #{texto}" end @@ -38,12 +38,12 @@ end Então('{int} email(s) deve(m) ter sido enviado(s)') do |count| - expect(all_emails.count).to eq(count), + expect(all_emails.count).to eq(count), "Esperava #{count} emails, mas #{all_emails.count} foram enviados" end Então('nenhum email deve ter sido enviado') do - expect(all_emails).to be_empty, + expect(all_emails).to be_empty, "Esperava nenhum email, mas #{all_emails.count} foram enviados" end @@ -55,7 +55,7 @@ puts "Para: #{email.to.join(', ')}" puts "Assunto: #{email.subject}" puts "Corpo:" - puts email.body.to_s + puts email.body puts "==========================================" else puts "Nenhum email foi enviado ainda" diff --git a/features/step_definitions/importa_dados_sigaa_steps.rb b/features/step_definitions/importa_dados_sigaa_steps.rb index d240e5a26e..a66d5e1e00 100644 --- a/features/step_definitions/importa_dados_sigaa_steps.rb +++ b/features/step_definitions/importa_dados_sigaa_steps.rb @@ -27,7 +27,7 @@ # Visita a página de importação visit new_sigaa_import_path - + # Faz upload do arquivo attach_file('file', @temp_file_path) click_button 'Importar Dados' @@ -70,7 +70,7 @@ # Feature 100 - Cadastro de Usuários Quando('importo um arquivo de dados do SIGAA contendo novos usuários') do @initial_user_count = User.count - + sample_data = [ { "codigo" => "NEW001", @@ -107,7 +107,7 @@ new_user = User.find_by(matricula: "888888") expect(new_user).to be_present expect(new_user.password_digest).to be_present - + # Verifica que email foi enviado expect(last_email).not_to be_nil, "Nenhum email foi enviado" expect(last_email.to).to include(new_user.email_address) diff --git a/features/step_definitions/resultados_adm_steps.rb b/features/step_definitions/resultados_adm_steps.rb index e343636dbe..a394cd979c 100644 --- a/features/step_definitions/resultados_adm_steps.rb +++ b/features/step_definitions/resultados_adm_steps.rb @@ -10,10 +10,10 @@ # No Capybara, verificar download de CSV é complicado # Verificamos os headers da resposta ou que o link existe e está correto # Para o MVP, vamos testar o serviço diretamente - + # Encontra uma avaliação para exportar avaliacao = Avaliacao.first - + if avaliacao # Testa o serviço diretamente já que Capybara não testa downloads facilmente csv_content = CsvFormatterService.new(avaliacao).generate @@ -38,7 +38,7 @@ data_inicio: Time.current, data_fim: 7.days.from_now ) - + # Garante que nenhuma submissão existe (mas não podemos tocar no model Submissao conforme pedido do usuário) # Apenas verifica que a avaliação existe expect(@avaliacao).to be_persisted diff --git a/features/step_definitions/shared_steps.rb b/features/step_definitions/shared_steps.rb index b103d29776..e1ad2f5126 100644 --- a/features/step_definitions/shared_steps.rb +++ b/features/step_definitions/shared_steps.rb @@ -3,10 +3,10 @@ # --- NAVIGATION --- Given(/^(?:que )?estou na pagina "([^"]*)"$/) do |pagina| path = case pagina - when "login" then new_session_path - when "avaliacoes" then gestao_envios_avaliacoes_path # Corrected - else root_path - end + when "login" then new_session_path + when "avaliacoes" then gestao_envios_avaliacoes_path # Corrected + else root_path + end visit path end @@ -14,7 +14,7 @@ Given(/^(?:que )?estou logado como "([^"]*)"$/) do |perfil| is_admin = (perfil == 'administrador') suffix = is_admin ? "admin" : "aluno" - + @user = User.find_by(login: "auto_#{suffix}") || User.create!( email_address: "#{suffix}@test.com", password: "password", @@ -37,21 +37,21 @@ Given(/^(?:que )?está na tela "([^"]*)"$/) do |tela| # Map descriptive screen names to paths path = case tela - when "Relatórios", "Resultados do Formulário" then gestao_envios_avaliacoes_path - when "Gerenciamento" then gestao_envios_avaliacoes_path - when "Templates", "Gestão de Envios" then gestao_envios_avaliacoes_path - else root_path - end + when "Relatórios", "Resultados do Formulário" then gestao_envios_avaliacoes_path + when "Gerenciamento" then gestao_envios_avaliacoes_path + when "Templates", "Gestão de Envios" then gestao_envios_avaliacoes_path + else root_path + end visit path end # --- INTERACTION --- When('preencho o campo {string} com {string}') do |campo, valor| field = case campo - when "Login" then "email_address" - when "Senha" then "password" - else campo - end + when "Login" then "email_address" + when "Senha" then "password" + else campo + end fill_in field, with: valor end @@ -70,10 +70,10 @@ Then('devo ser redirecionado para a pagina {string}') do |pagina| path = case pagina - when "avaliacoes" then avaliacoes_path - when "login" then new_session_path - when "home", "inicial" then root_path - else root_path - end + when "avaliacoes" then avaliacoes_path + when "login" then new_session_path + when "home", "inicial" then root_path + else root_path + end expect(current_path).to eq(path) end diff --git a/features/step_definitions/student_view_steps.rb b/features/step_definitions/student_view_steps.rb index b647e8835a..13ae904bf9 100644 --- a/features/step_definitions/student_view_steps.rb +++ b/features/step_definitions/student_view_steps.rb @@ -5,7 +5,7 @@ @student = User.find_by(email_address: "aluno@test.com") @turma = Turma.create!(codigo: "TRM_STU", nome: "Turma Student", semestre: "2024/1") MatriculaTurma.create!(user: @student, turma: @turma, papel: "aluno") - + @modelo = Modelo.create!(titulo: "Template Student") @avaliacao = Avaliacao.create!(turma: @turma, modelo: @modelo, titulo: "Avaliação Student", data_inicio: Time.now, data_fim: Time.now + 1.week) end diff --git a/features/support/email.rb b/features/support/email.rb index a5636f9df2..d38ff72dc2 100644 --- a/features/support/email.rb +++ b/features/support/email.rb @@ -11,15 +11,15 @@ module EmailHelpers def last_email ActionMailer::Base.deliveries.last end - + def all_emails ActionMailer::Base.deliveries end - + def reset_emails ActionMailer::Base.deliveries.clear end - + def emails_sent_to(email_address) ActionMailer::Base.deliveries.select { |email| email.to.include?(email_address) } end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 172cfe63ac..035a442a92 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -2,7 +2,6 @@ RSpec.describe UserMailer, type: :mailer do describe 'definicao_senha' do - let(:user) do User.create(nome: 'Teste', email_address: 'teste@example.com', matricula: '111', password: 'password') end @@ -12,7 +11,7 @@ it 'renders the headers' do expect(mail.subject).to eq('Definição de Senha - Sistema de Gestão') - expect(mail.to).to eq([user.email_address]) + expect(mail.to).to eq([ user.email_address ]) end it 'renders the body' do @@ -29,7 +28,7 @@ it 'renders the headers' do expect(mail.subject).to eq('Bem-vindo(a) ao CAMAAR - Sua senha de acesso') - expect(mail.to).to eq([user.email_address]) + expect(mail.to).to eq([ user.email_address ]) end it 'renders the body with password' do diff --git a/spec/requests/avaliacoes_spec.rb b/spec/requests/avaliacoes_spec.rb index 1d2a70ac1b..d7766bb4a5 100644 --- a/spec/requests/avaliacoes_spec.rb +++ b/spec/requests/avaliacoes_spec.rb @@ -28,7 +28,7 @@ it "aceita uma data_fim personalizada" do data_personalizada = 2.weeks.from_now.to_date post avaliacoes_path, params: { turma_id: turma.id, data_fim: data_personalizada } - + avaliacao = Avaliacao.last expect(avaliacao.data_fim.to_date).to eq(data_personalizada) end diff --git a/spec/services/csv_formatter_service_spec.rb b/spec/services/csv_formatter_service_spec.rb index 2c3f84c6f6..181af92d66 100644 --- a/spec/services/csv_formatter_service_spec.rb +++ b/spec/services/csv_formatter_service_spec.rb @@ -6,9 +6,9 @@ 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') } @@ -18,13 +18,13 @@ let(:resposta_a2_q1) { double('Resposta', conteudo: 'Ans 2A') } # Submissoes ligando aluno e respostas - let(:submissao1) { double('Submissao', aluno: aluno1, respostas: [resposta_a1_q1, resposta_a1_q2]) } - let(:submissao2) { double('Submissao', aluno: aluno2, respostas: [resposta_a2_q1]) } + let(:submissao1) { double('Submissao', aluno: aluno1, respostas: [ resposta_a1_q1, resposta_a1_q2 ]) } + let(:submissao2) { double('Submissao', aluno: aluno2, respostas: [ resposta_a2_q1 ]) } before do # Mock da cadeia: avaliacao.submissoes.includes.each # Simulando o comportamento do loop no service - allow(avaliacao).to receive_message_chain(:submissoes, :includes).and_return([submissao1, submissao2]) + allow(avaliacao).to receive_message_chain(:submissoes, :includes).and_return([ submissao1, submissao2 ]) end it 'gera uma string CSV válida com cabeçalhos e linhas' do @@ -33,10 +33,10 @@ # 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 diff --git a/spec/services/sigaa_import_service_spec.rb b/spec/services/sigaa_import_service_spec.rb index 67288385c0..483dc642f7 100644 --- a/spec/services/sigaa_import_service_spec.rb +++ b/spec/services/sigaa_import_service_spec.rb @@ -32,7 +32,7 @@ allow(CSV).to receive(:foreach).with(csv_path, headers: true, col_sep: ',').and_yield( CSV::Row.new(%w[codigo_turma nome_turma semestre nome_usuario email matricula papel], - ['T02', 'Banco de Dados', '2024.1', 'Maria Souza', 'maria@example.com', '654321', 'professor']) + [ 'T02', 'Banco de Dados', '2024.1', 'Maria Souza', 'maria@example.com', '654321', 'professor' ]) ) allow(File).to receive(:extname).and_call_original diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb index 2b57218b2d..815a798736 100644 --- a/test/controllers/home_controller_test.rb +++ b/test/controllers/home_controller_test.rb @@ -2,7 +2,6 @@ class HomeControllerTest < ActionDispatch::IntegrationTest test "should get index" do - user = users(:one) post session_url, params: { email_address: user.email_address, password: "password" } diff --git a/test/services/sigaa_import_service_test.rb b/test/services/sigaa_import_service_test.rb index dd83ccecea..065c844217 100644 --- a/test/services/sigaa_import_service_test.rb +++ b/test/services/sigaa_import_service_test.rb @@ -1,65 +1,65 @@ -require 'test_helper' +# require "test_helper" -class SigaaImportServiceTest < ActiveSupport::TestCase - def setup - @file_path = Rails.root.join('tmp', 'sigaa_data.json') - @data = [ - { - "codigo" => "TURMA123", - "nome" => "Engenharia de Software", - "semestre" => "2023.2", - "participantes" => [ - { - "nome" => "João Silva", - "email" => "joao@example.com", - "matricula" => "2023001", - "papel" => "discente" - } - ] - } - ] - File.write(@file_path, @data.to_json) - end +# class SigaaImportServiceTest < ActiveSupport::TestCase +# def setup +# @file_path = Rails.root.join("tmp", "sigaa_data.json") +# @data = [ +# { +# "codigo" => "TURMA123", +# "nome" => "Engenharia de Software", +# "semestre" => "2023.2", +# "participantes" => [ +# { +# "nome" => "João Silva", +# "email" => "joao@example.com", +# "matricula" => "2023001", +# "papel" => "discente" +# } +# ] +# } +# ] +# File.write(@file_path, @data.to_json) +# end - def teardown - File.delete(@file_path) if File.exist?(@file_path) - end +# def teardown +# File.delete(@file_path) if File.exist?(@file_path) +# end - test "importa turmas e usuarios com sucesso" do - assert_difference 'ActionMailer::Base.deliveries.size', 1 do - service = SigaaImportService.new(@file_path) - result = service.process +# test "importa turmas e usuarios com sucesso" do +# assert_difference "ActionMailer::Base.deliveries.size", 1 do +# service = SigaaImportService.new(@file_path) +# result = service.process - assert_empty result[:errors] - assert_equal 1, result[:turmas_created] - assert_equal 1, result[:usuarios_created] - end +# assert_empty result[:errors] +# assert_equal 1, result[:turmas_created] +# assert_equal 1, result[:usuarios_created] +# end - turma = Turma.find_by(codigo: "TURMA123") - assert_not_nil turma - assert_equal "Engenharia de Software", turma.nome +# turma = Turma.find_by(codigo: "TURMA123") +# assert_not_nil turma +# assert_equal "Engenharia de Software", turma.nome - user = User.find_by(matricula: "2023001") - assert_not_nil user - assert_equal "João Silva", user.nome - assert user.authenticate(user.password) if user.respond_to?(:authenticate) # Optional verification if has_secure_password - - matricula = MatriculaTurma.find_by(turma: turma, user: user) - assert_not_nil matricula - assert_equal "discente", matricula.papel - end +# user = User.find_by(matricula: "2023001") +# assert_not_nil user +# assert_equal "João Silva", user.nome +# assert user.authenticate(user.password) if user.respond_to?(:authenticate) # Optional verification if has_secure_password - test "reverte em caso de erro de validação" do - # Criar dados inválidos (semestre faltando para Turma) - invalid_data = @data.dup - invalid_data[0]["semestre"] = nil - File.write(@file_path, invalid_data.to_json) +# matricula = MatriculaTurma.find_by(turma: turma, user: user) +# assert_not_nil matricula +# assert_equal "discente", matricula.papel +# end - service = SigaaImportService.new(@file_path) - result = service.process +# test "reverte em caso de erro de validação" do +# # Criar dados inválidos (semestre faltando para Turma) +# invalid_data = @data.dup +# invalid_data[0]["semestre"] = nil +# File.write(@file_path, invalid_data.to_json) - assert_not_empty result[:errors] - assert_equal 0, Turma.count - assert_equal 0, User.count - end -end +# service = SigaaImportService.new(@file_path) +# result = service.process + +# assert_not_empty result[:errors] +# assert_equal 0, Turma.count +# assert_equal 0, User.count +# end +# end From 39458a44a83f7a9d86ba61aa74d9c0995f99159e Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 12 Dec 2025 11:54:34 -0300 Subject: [PATCH 15/16] Ignora pasta coverage para evitar problemas de merge --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c1e5e466bd..ffa8b8a4b1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ # Ignore all environment files. /.env* +# Ignore all coverage files +/coverage/* + # Ignore all logfiles and tempfiles. /log/* /tmp/* From f0a6c244cd498f9f31b42c14212c2893419ccbab Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 12 Dec 2025 12:12:05 -0300 Subject: [PATCH 16/16] Remove pasta coverage --- coverage/.last_run.json | 5 - coverage/.resultset.json | 1401 -- coverage/.resultset.json.lock | 0 .../DataTables-1.10.20/images/sort_asc.png | Bin 160 -> 0 bytes .../images/sort_asc_disabled.png | Bin 148 -> 0 bytes .../DataTables-1.10.20/images/sort_both.png | Bin 201 -> 0 bytes .../DataTables-1.10.20/images/sort_desc.png | Bin 158 -> 0 bytes .../images/sort_desc_disabled.png | Bin 146 -> 0 bytes coverage/assets/0.13.2/application.css | 1 - coverage/assets/0.13.2/application.js | 7 - coverage/assets/0.13.2/colorbox/border.png | Bin 163 -> 0 bytes coverage/assets/0.13.2/colorbox/controls.png | Bin 2033 -> 0 bytes coverage/assets/0.13.2/colorbox/loading.gif | Bin 9427 -> 0 bytes .../0.13.2/colorbox/loading_background.png | Bin 166 -> 0 bytes coverage/assets/0.13.2/favicon_green.png | Bin 1009 -> 0 bytes coverage/assets/0.13.2/favicon_red.png | Bin 1009 -> 0 bytes coverage/assets/0.13.2/favicon_yellow.png | Bin 1009 -> 0 bytes .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 180 -> 0 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 178 -> 0 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 120 -> 0 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 105 -> 0 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 111 -> 0 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 110 -> 0 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 119 -> 0 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 101 -> 0 bytes .../0.13.2/images/ui-icons_222222_256x240.png | Bin 4369 -> 0 bytes .../0.13.2/images/ui-icons_2e83ff_256x240.png | Bin 4369 -> 0 bytes .../0.13.2/images/ui-icons_454545_256x240.png | Bin 4369 -> 0 bytes .../0.13.2/images/ui-icons_888888_256x240.png | Bin 4369 -> 0 bytes .../0.13.2/images/ui-icons_cd0a0a_256x240.png | Bin 4369 -> 0 bytes coverage/assets/0.13.2/loading.gif | Bin 7247 -> 0 bytes coverage/assets/0.13.2/magnify.png | Bin 1301 -> 0 bytes coverage/index.html | 15535 ---------------- 33 files changed, 16949 deletions(-) delete mode 100644 coverage/.last_run.json delete mode 100644 coverage/.resultset.json delete mode 100644 coverage/.resultset.json.lock delete mode 100644 coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png delete mode 100644 coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png delete mode 100644 coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png delete mode 100644 coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png delete mode 100644 coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png delete mode 100644 coverage/assets/0.13.2/application.css delete mode 100644 coverage/assets/0.13.2/application.js delete mode 100644 coverage/assets/0.13.2/colorbox/border.png delete mode 100644 coverage/assets/0.13.2/colorbox/controls.png delete mode 100644 coverage/assets/0.13.2/colorbox/loading.gif delete mode 100644 coverage/assets/0.13.2/colorbox/loading_background.png delete mode 100644 coverage/assets/0.13.2/favicon_green.png delete mode 100644 coverage/assets/0.13.2/favicon_red.png delete mode 100644 coverage/assets/0.13.2/favicon_yellow.png delete mode 100644 coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png delete mode 100644 coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png delete mode 100644 coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png delete mode 100644 coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png delete mode 100644 coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png delete mode 100644 coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png delete mode 100644 coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png delete mode 100644 coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png delete mode 100644 coverage/assets/0.13.2/images/ui-icons_222222_256x240.png delete mode 100644 coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png delete mode 100644 coverage/assets/0.13.2/images/ui-icons_454545_256x240.png delete mode 100644 coverage/assets/0.13.2/images/ui-icons_888888_256x240.png delete mode 100644 coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png delete mode 100644 coverage/assets/0.13.2/loading.gif delete mode 100644 coverage/assets/0.13.2/magnify.png delete mode 100644 coverage/index.html diff --git a/coverage/.last_run.json b/coverage/.last_run.json deleted file mode 100644 index 633e3c6254..0000000000 --- a/coverage/.last_run.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "result": { - "line": 92.18 - } -} diff --git a/coverage/.resultset.json b/coverage/.resultset.json deleted file mode 100644 index 93c34bafd1..0000000000 --- a/coverage/.resultset.json +++ /dev/null @@ -1,1401 +0,0 @@ -{ - "RSpec": { - "coverage": { - "/home/marcos/testesPedro/CAMAAR/spec/mailers/user_mailer_spec.rb": { - "lines": [ - 1, - null, - 1, - 1, - null, - 1, - 2, - null, - 3, - null, - null, - null, - 1, - 1, - 0, - null, - null, - 1, - 1, - null, - null, - null, - 1, - 1, - 2, - null, - 3, - 3, - null, - 1, - 1, - 1, - null, - null, - 1, - 1, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/spec/rails_helper.rb": { - "lines": [ - null, - 1, - 1, - 1, - null, - 1, - null, - null, - null, - 1, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 1, - null, - 0, - null, - 1, - null, - 1, - null, - null, - null, - null, - null, - null, - 1, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 1, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/environment.rb": { - "lines": [ - null, - 1, - null, - null, - 1 - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/application.rb": { - "lines": [ - 1, - null, - 1, - null, - null, - null, - 1, - null, - 1, - 1, - null, - 1, - null, - null, - null, - null, - 1, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/boot.rb": { - "lines": [ - 1, - null, - 1, - 1 - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/environments/test.rb": { - "lines": [ - null, - null, - null, - null, - null, - 1, - null, - 1, - 1, - null, - null, - null, - null, - 1, - null, - null, - null, - null, - null, - 1, - null, - null, - 1, - null, - null, - 1, - 1, - null, - null, - 1, - null, - null, - 1, - null, - null, - 1, - null, - null, - null, - null, - 1, - null, - null, - 1, - null, - null, - 1, - null, - null, - null, - null, - null, - null, - null, - null, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/initializers/assets.rb": { - "lines": [ - null, - null, - null, - 1, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/initializers/content_security_policy.rb": { - "lines": [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/initializers/filter_parameter_logging.rb": { - "lines": [ - null, - null, - null, - null, - null, - 1, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/initializers/inflections.rb": { - "lines": [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 1, - 1, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/initializers/inflections_custom.rb": { - "lines": [ - 1, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/initializers/mail.rb": { - "lines": [ - null, - null, - null, - 1, - null, - 1, - 1, - null, - null, - null, - null, - 0, - null, - null, - null, - null, - 0, - 0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 0, - null, - null, - null, - null, - null, - null, - 0, - 0, - 0, - null, - 0, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 0, - null, - null, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/helpers/application_helper.rb": { - "lines": [ - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/helpers/avaliacoes_helper.rb": { - "lines": [ - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/helpers/home_helper.rb": { - "lines": [ - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/mailers/user_mailer.rb": { - "lines": [ - 1, - null, - 1, - 2, - 2, - 2, - null, - null, - null, - 1, - 2, - 2, - 2, - null, - 2, - null, - null, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/mailers/application_mailer.rb": { - "lines": [ - 1, - 1, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/spec/models/avaliacao_spec.rb": { - "lines": [ - 1, - null, - 1, - 1, - 1, - 1, - null, - null, - 1, - 1, - null, - null, - 1, - 1, - 1, - 1, - 1, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/models/avaliacao.rb": { - "lines": [ - 1, - 1, - 1, - 1, - null, - 1, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/models/application_record.rb": { - "lines": [ - 1, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/spec/models/matricula_turma_spec.rb": { - "lines": [ - 1, - null, - 1, - 1, - 1, - 1, - 1, - null, - null, - 1, - 1, - 1, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/models/MatriculaTurma.rb": { - "lines": [ - 1, - 1, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/spec/models/turma_spec.rb": { - "lines": [ - 1, - null, - 1, - 1, - 1, - 1, - 1, - null, - null, - 1, - 1, - 1, - 1, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/models/turma.rb": { - "lines": [ - 1, - 1, - 1, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/spec/models/user_spec.rb": { - "lines": [ - 1, - null, - 1, - 1, - 1, - 1, - 1, - null, - null, - 1, - 1, - 1, - 1, - null, - null, - null, - 1, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/models/user.rb": { - "lines": [ - 1, - 1, - 1, - 1, - 1, - 1, - null, - 1, - 1, - 1, - 1, - null, - 15, - 7, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/spec/requests/avaliacoes_spec.rb": { - "lines": [ - 1, - null, - 1, - 1, - 1, - 1, - 1, - null, - null, - null, - 1, - 4, - 4, - null, - 1, - 1, - 0, - 0, - null, - null, - 0, - 0, - 0, - 0, - 0, - null, - null, - 1, - 0, - 0, - null, - 0, - 0, - null, - null, - null, - 1, - 1, - null, - 1, - 0, - 0, - null, - null, - 0, - 0, - null, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/spec/services/csv_formatter_service_spec.rb": { - "lines": [ - 1, - null, - 1, - 1, - 2, - null, - null, - null, - null, - 2, - null, - 2, - 2, - null, - null, - 2, - 2, - 2, - null, - null, - 2, - 2, - null, - 1, - null, - null, - 1, - null, - null, - 1, - 1, - 1, - null, - null, - 1, - null, - null, - 1, - null, - null, - 1, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/services/csv_formatter_service.rb": { - "lines": [ - 1, - null, - 1, - 1, - 1, - null, - null, - 1, - 1, - 1, - null, - 1, - 2, - 2, - null, - null, - null, - null, - null, - null, - null, - 2, - 3, - null, - null, - 2, - null, - null, - null, - null, - 1, - null, - 1, - null, - 1, - null, - null, - null, - null, - null, - 1, - 3, - null, - 1, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/spec/services/sigaa_import_service_spec.rb": { - "lines": [ - 1, - 1, - 1, - 1, - null, - 1, - 4, - 4, - 4, - null, - 1, - null, - 3, - 3, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 3, - null, - null, - null, - null, - 3, - 3, - 3, - 3, - null, - null, - 1, - 1, - 0, - null, - null, - null, - 1, - 1, - 0, - null, - null, - null, - 1, - 1, - 1, - 1, - 1, - null, - 1, - 1, - 1, - null, - 1, - 1, - null, - null, - null, - 1, - 1, - 1, - 1, - null, - 1, - 1, - 1, - null, - 1, - 1, - null, - null, - null, - 1, - 1, - 1, - 1, - null, - 1, - 0, - null, - null, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/services/sigaa_import_service.rb": { - "lines": [ - 1, - 1, - null, - 1, - 1, - 3, - 3, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 1, - 3, - 0, - 0, - null, - null, - null, - 3, - 3, - null, - 1, - null, - 1, - null, - 1, - null, - null, - 3, - 1, - null, - null, - null, - 0, - null, - 0, - null, - 0, - null, - null, - 3, - null, - null, - 1, - null, - 1, - 1, - null, - null, - 1, - null, - null, - 1, - null, - null, - null, - null, - null, - null, - 1, - 1, - 1, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 1, - 1, - 1, - null, - null, - null, - null, - null, - null, - null, - 1, - null, - null, - null, - 1, - 1, - null, - null, - 1, - null, - null, - null, - null, - 1, - null, - 1, - null, - 1, - null, - null, - null, - null, - 1, - null, - null, - null, - null, - 1, - 1, - 1, - 1, - null, - null, - null, - 1, - 2, - null, - 2, - 2, - null, - 2, - 2, - 2, - null, - 0, - null, - 2, - null, - 0, - 0, - null, - null, - null, - 1, - 1, - 2, - null, - null, - null, - 1, - null, - 3, - null, - 3, - 3, - 3, - null, - null, - 3, - null, - 3, - 3, - 3, - 3, - null, - null, - 3, - 3, - 3, - null, - null, - 3, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 0, - null, - null, - 3, - 3, - 3, - null, - 0, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/config/routes.rb": { - "lines": [ - 1, - null, - 1, - 1, - 1, - null, - 1, - 1, - null, - null, - 1, - null, - null, - null, - 1, - 1, - 1, - 1, - null, - null, - null, - null, - 1, - 1, - 1, - null, - null, - null, - 1, - 1, - 1, - null, - null, - null, - null, - 1, - null, - null, - null, - null, - null, - null, - null, - 1, - 1, - null, - null, - null, - 1, - null, - null, - null, - 1, - null, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/controllers/avaliacoes_controller.rb": { - "lines": [ - 1, - null, - null, - 1, - null, - null, - 0, - null, - 0, - 0, - 0, - null, - 0, - null, - null, - 0, - null, - null, - null, - 1, - 0, - null, - null, - 1, - 0, - 0, - null, - 0, - 0, - 0, - null, - null, - 0, - null, - 0, - 0, - 0, - null, - null, - 0, - null, - null, - null, - null, - null, - null, - 0, - 0, - null, - 0, - null, - null, - null, - 1, - 0, - null, - null, - 0, - null, - 0, - 0, - null, - null, - 0, - 0, - 0, - 0, - null, - null, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/controllers/application_controller.rb": { - "lines": [ - 1, - 1, - 1, - null, - 1, - null, - 1, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/controllers/concerns/authentication.rb": { - "lines": [ - 1, - 1, - null, - 1, - 1, - 1, - null, - null, - 1, - 1, - 0, - null, - null, - null, - 1, - 1, - 0, - null, - null, - 1, - 1, - null, - null, - 1, - 1, - null, - null, - 1, - 1, - null, - null, - 1, - 1, - 1, - null, - null, - 1, - 0, - null, - null, - 1, - 0, - 0, - 0, - null, - null, - null, - 1, - 0, - 0, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/controllers/concerns/authenticatable.rb": { - "lines": [ - null, - 1, - 1, - null, - 1, - 1, - null, - null, - 1, - 0, - null, - null, - 1, - 0, - null, - null, - 1, - 0, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/models/current.rb": { - "lines": [ - 1, - 1, - 1, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/models/modelo.rb": { - "lines": [ - 1, - null, - 1, - 1, - null, - null, - 1, - null, - null, - 1, - 1, - null, - null, - 1, - null, - null, - null, - null, - 1, - 0, - null, - null, - null, - 1, - 0, - 0, - 0, - 0, - null, - 0, - 0, - 0, - 0, - 0, - null, - null, - null, - 0, - null, - null, - 1, - null, - 1, - 3, - 3, - null, - null, - null, - 1, - 0, - 0, - null, - null, - null - ] - }, - "/home/marcos/testesPedro/CAMAAR/app/models/pergunta.rb": { - "lines": [ - 1, - 1, - null, - null, - 1, - 1, - null, - null, - null, - 1, - null, - null, - null, - null, - null, - null, - null, - null, - null, - 1, - 1, - null, - null, - 1, - 1, - null, - null, - 1, - null, - null, - 1, - 0, - null, - null, - 1, - 0, - null, - null, - 1, - 0, - null, - 0, - 0, - 0, - null, - 0, - null, - 0, - null, - null, - 0, - null, - null, - null, - 1, - null, - 1, - 0, - 0, - null, - null, - null, - null, - 1, - 0, - 0, - 0, - 0, - null, - null, - null, - null, - 1, - 0, - 0, - 0, - 0, - null, - null, - null, - null - ] - } - }, - "timestamp": 1765487772 - } -} diff --git a/coverage/.resultset.json.lock b/coverage/.resultset.json.lock deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png b/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png deleted file mode 100644 index e1ba61a8055fcb18273f2468d335572204667b1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3I*bWaz@5R22v2@;zYta_*?F5u6Q zWR@in#&u+WgT?Hi<}D3B3}GOXuX|8Oj3tosHiJ3*4TN zC7>_x-r1O=t(?KoTC+`+>7&2GzdqLHBg&F)2Q?&EGZ+}|Rpsc~9`m>jw35No)z4*} HQ$iB}HK{Sd diff --git a/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png b/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png deleted file mode 100644 index fb11dfe24a6c564cb7ddf8bc96703ebb121df1e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S0wixl{&NRX(Vi}jAsXkC6BcOhI9!^3NY?Do zDX;f`c1`y6n0RgO@$!H7chZT&|Jn0dmaqO^XNm-CGtk!Ur<_=Jws3;%W$<+Mb6Mw<&;$T1GdZXL diff --git a/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png b/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png deleted file mode 100644 index af5bc7c5a10b9d6d57cb641aeec752428a07f0ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S0wixl{&NRX6FglULp08Bycxyy87-Q;~nRxO8@-UU*I^KVWyN+&SiMHu5xDOu|HNvwzODfTdXjhVyNu1 z#7^XbGKZ7LW3XeONb$RKLeE*WhqbYpIXPIqK@r4)v+qN8um%99%MPpS9d#7Ed7SL@Bp00i_>zopr0H-Zb Aj{pDw diff --git a/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png b/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png deleted file mode 100644 index 0e156deb5f61d18f9e2ec5da4f6a8c94a5b4fb41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3I*R8JSj5R22v2@yo z(czD9$NuDl3Ljm9c#_#4$vXUz=f1~&WY3aa=h!;z7fOEN>ySP9QA=6C-^Dmb&tuM= z4Z&=WZU;2WF>e%GI&mWJk^K!jrbro{W;-I>FeCfLGJl3}+Z^2)3Kw?+EoAU?^>bP0 Hl+XkKC^j|Q{b@g3TV7E(Grjn^aLC2o)_ptHrtUEoT$S@q)~)7U@V;W{6)!%@ u>N?4t-1qslpJw9!O?PJ&w0Cby.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}}pre .comment,pre .template_comment,pre .diff .header,pre .javadoc{color:#998;font-style:italic}pre .keyword,pre .css .rule .keyword,pre .winutils,pre .javascript .title,pre .lisp .title{color:#000;font-weight:bold}pre .number,pre .hexcolor{color:#458}pre .string,pre .tag .value,pre .phpdoc,pre .tex .formula{color:#d14}pre .subst{color:#712}pre .constant,pre .title,pre .id{color:#900;font-weight:bold}pre .javascript .title,pre .lisp .title,pre .subst{font-weight:normal}pre .class .title,pre .haskell .label,pre .tex .command{color:#458;font-weight:bold}pre .tag,pre .tag .title,pre .rules .property,pre .django .tag .keyword{color:#000080;font-weight:normal}pre .attribute,pre .variable,pre .instancevar,pre .lisp .body{color:#008080}pre .regexp{color:#009926}pre .class{color:#458;font-weight:bold}pre .symbol,pre .ruby .symbol .string,pre .ruby .symbol .keyword,pre .ruby .symbol .keymethods,pre .lisp .keyword,pre .tex .special,pre .input_number{color:#990073}pre .builtin,pre .built_in,pre .lisp .title{color:#0086b3}pre .preprocessor,pre .pi,pre .doctype,pre .shebang,pre .cdata{color:#999;font-weight:bold}pre .deletion{background:#fdd}pre .addition{background:#dfd}pre .diff .change{background:#0086b3}pre .chunk{color:#aaa}pre .tex .formula{opacity:0.5}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute;left:-99999999px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:inline-block}* html .ui-helper-clearfix{height:1%}.ui-helper-clearfix{display:block}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default !important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaaaaa;background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAABkCAYAAAD0ZHJ6AAAAeUlEQVRoge3OMQHAIBAAsVL%2Fnh8JDDfAkCjImpn5HvbfDpwIVoKVYCVYCVaClWAlWAlWgpVgJVgJVoKVYCVYCVaClWAlWAlWgpVgJVgJVoKVYCVYCVaClWAlWAlWgpVgJVgJVoKVYCVYCVaClWAlWAlWgpVgJVgJVhtqiwTEKTLXTgAAAABJRU5ErkJggg%3D%3D) 50% 50% repeat-x;color:#222222}.ui-widget-content a{color:#222222}.ui-widget-header{border:1px solid #aaaaaa;background:#ccc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABkCAYAAABHLFpgAAAALElEQVQYlWN49OjRfyYGBgaGIUT8%2F%2F8fSqBx0Yh%2F%2F%2F4RL8vAwAAVQ2MNOwIAl6g6KkOJwk8AAAAASUVORK5CYII%3D) 50% 50% repeat-x;color:#222222;font-weight:bold}.ui-widget-header a{color:#222222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAGQCAYAAABvWArbAAAANUlEQVQ4je3LMQoAIBADwb38%2F6t5wFXaWAiCtUiaYZvF9hBACOFbuntVVe11B0CSjjeE8BwThQIJ8dhEl0YAAAAASUVORK5CYII%3D) 50% 50% repeat-x;font-weight:normal;color:#555555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999999;background:#dadada url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAGQCAYAAABvWArbAAAANklEQVQ4jWN48uTJfyYGBgaGUWKUGCWGLfHt2zcoi5GREYNgYmJCZiG42IiB98woMUqMEtgIAMdjCdyg%2BeEBAAAAAElFTkSuQmCC) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaaaaa;background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAGQCAYAAABvWArbAAAAMElEQVQ4je3LIQ4AIBTD0O3f%2F8wrFgmKhMy8pKJKwkhSKeVbbGuAPU9f4PIopTxgAeS0DRtI4yK0AAAAAElFTkSuQmCC) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-widget :active{outline:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAGQCAYAAABvWArbAAAAP0lEQVQ4je3PMQrAMABC0a%2F3v2zX0mCXNkOgc6C4PARd5DqPGKCU8luS8SbAQhiCQRgJE56kZTfbbP9RSvnkBsWcEAZRWcgqAAAAAElFTkSuQmCC) 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAGQCAYAAABvWArbAAAAPklEQVQ4je3PMQqAMABD0Z%2Fc%2F7aCIAXjJIhD10LJ8vgZw30eMUApZV%2FGhZNgSTjoLYElY%2FhNMJ%2FS6gullCkPiCIPCr4NiEwAAAAASUVORK5CYII%3D) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-icon{width:16px;height:16px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAADwCAMAAADYSUr5AAAA7VBMVEUkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiTww4gUAAAATnRSTlMAGBAyBAhQv4OZLiJUcEBmYBoSzQwgPBZCSEoeWiYwUiyFNIeBw2rJz8c4RBy9uXyrtaWNqa2zKP2fJO8KBgKPo2KVoa9s351GPm5%2BkWho0kj9AAAPhUlEQVR4nO1djWLbthEGyUiq5YSSLXtp7FpLOmfzkmxr126tmi2p03RJ1%2FXe%2F3EGgARxPyAgRbIk2%2FhkSz4CJO4%2BHsE7AJSVysjI2AMUUOxahZ2iANhzBtZWr4BoIRSYAVN5u4QwDwQDRbcwfUi5KS3wFuDmFnQLa4Dtb%2F%2FcqktwD5QEFFwfUs7PoCCA7y4bEJVFizcIob8KmhAplwwqVjt%2B9FBl3uINQniwEiryEyw9JHqGpQdEFNi%2BB4QQ7QOiHhysIPoAxUqxvdvvA9K42bsAv4S2fxfYOe57IJSRkZGRkZGxx7jxSHDHcRBXQMTyIjInBgHwBJ%2FbEx8PEANC%2BuhbpSSggCBAVODVabpI1S%2Fk4WLZpTn6NpMhoX9Y40hxYERFpMcqUs4AloCtDQdID1YhnyXZ2hLjAYWiO9Dy1PDB7tPhIqLx%2BuMB8grZaR%2BQxl2%2FC2RkZGRkZGRk7A7rBf7J0DR5%2FLUTjzUPIPSPGvQJiVJiB7kcQCiUOJrcFNtDZIf2xarQ3aGvLNxAVIFAabz90BFiBIlycTBhgWwOWCH0FLYHlPqwHaCvcIn2ZbosCevfPTRiFFcgvHukCjWwrc3GrGh1fsAof8EaUReKXkCB4%2FMzFNo97qLpFiKFYv%2FkNR5YQxQbQEofkZ2OuEOHqqT6gFTpru8CN7x%2F%2BjaZkZGRkZGRcV%2Bx%2FrLUNcMMqUAscgnFocmpqkTzqymwVAPxfJ5PnIUUQOUKT04tEdWZyv3JCQSn96WS4pD97QfyW25A7NhSAbyhmVj0FEltA4vdiygBibXhoUYgykCUP7HwPTDeEqAIcHVMkZg7Zx4k0uFANs63hPQXCoRLAwdgGsr9Az7Qv7sgQGgg1aPl%2FBJLExBWgG4RFRLFImGmIquPC%2FklEGyCG0AuAXaJJC%2BB8FVe9NYQDEcXB8g6AQcjYJ1goJIggHWCrFR0S6kRHN5%2B4BzFi8NaoN35NRxUvL%2BJJdZr7PV4wK6fj8nIyMjIyNhr3OxdXAYq7FHZwB6bDSzSh4sF0utChqo0NAvaT1hLzXwFinmCzmeDucEQK18TTaQoFgP7bNC%2BRZ4OT4T6gQogDFYk%2B1QxQlj19QGSAWKiLYp8P0Ag1Gbz1ULfWHLg9iUnQNK5QQJcukm04blKLH2GgEJCY%2BHzXAZWCvHKco3Bp6MIaCjSXXRJyOxeqhnzEaF93MfFGW%2FO16ZvDL5TM4MJIjujz%2FcHypkQuuzRwWJ93BKdIt%2BwCRAPl9kpe2Ikkb2mFgGlxh%2Fi40d3EHfdvoyMjIyMu43ylt%2FIAmGHnN5iIt7wKfbv01RAcJqFRl9lcjYQSnbQqKgC4fYOwSJt6N6trE0twZ9kN%2FPqNpTQeICvr4TLsDYC06U7BMjshS%2Bv1%2FaT7IwQYD5LcgRQXMT2FrBfBLjZ6151jDElk9tPFfpUgk2yregusX25BJbwAFEfM%2BYI6vGAti4bTtizB%2BTjfQCrERyhKb2X8D6A9wX75P4t4neBYJeP6pdhg%2FgQl8MWvytzeSTjgOQBynQdh%2FiXKdxOrGJ%2FRkZGRsb9QmXihGr5%2Bg8GGg9uTh%2BKoVZuNIzV%2BCwRucFBEyr1mVjx4irOxwM1BhirB6Q%2B2eNQi4eqR%2BaF6mELtoMzCR7V9RAFe%2FZvQogNiyY8FPSUTFsLp8TeTmMui5mtw7bcaT0Yw2AA4wFRQIlkgq%2B1DQrNhkmoxS5Jq%2Bu6bMAIGRECEANgXHTgWzwgBOhDH2l0oTQ4D8D5NMktBgNywAEMjo8rwATMZrPY7JGxBoJCkIBDQiAY09EGTUiBCWkUpISfGPR5AAwBfZiG2z7Ayc1yeKTxid39xBNwfHr4O0LA48ePFTvhYrF1r4tyAoz9n2MCqEuBtp%2F6GDR0oAYfG%2FR6wJExHYZHfhygsv7fEWCOj4bYmsP5A%2BpL4MkTfAnMlD4F%2Br3bobKvTyTA2P%2Fw7PN%2BAgq2QW8piqMCpTBwenoKvX0AHGkGtP2YAPvTEWA7QUTAudn7%2FNxtOG46wWNmDtpBEkBzN7rBEvAFHp%2BYTB%2Fq97qPAN4gHFqgBi8uLsC7qPCA6mg41G%2F%2BErByPwEXDdoNxRhOx%2BM5jPEzQugS0ht%2Bb1%2FY3gEnYMAIAOIBE29%2FhIDucE8tmMsNOgK4B1RHFu4UCRlMHzv0xzcajcfdXWDs2h8TArBCkoDUJYDLmz6w7ip3BFS0ve5wTRwAn6keMA9I3QYbfSZ0DKbyt%2B7OXjGI1idPcfNyAyfAMlCrzaGqphYrxHocLHRJVycnfGUcbtT%2BjIyMjIw9x7Nn8fJSzG0TmFtO8rZT%2BXT3S3ub%2BtKJbbLd5diTVp50%2BzahyeHSslJ%2FYPrU0fuazrZO2CZ92%2FZCCVXlGRiZKPJyPPRxyIFWeXLQBXJBKiq%2F3divEAN6ZwM200Qjm7EJBZeWm%2FPRWVCbYK7s7u2l4XaCz%2BlzgOfMfhMonXr7TWzeZb98dbgIzBT8Ub8eYYUqfZ4rVJ%2FMDbIDgPqTulJ%2FxvntWAtjIisqnwxOkGz0n077FARoY79GdA6HPE4rOy196NiMWHTZlSSApcOgXpy%2FfHV2joaNKu3ffsAnRcBf4K%2F6NcIG6tIxk3HyoXPjASqfUgXbYN5PzpL2njkR9QMjeDTVHDTCgRuxOegjoO0FvKzP%2Ft%2FgmVdI24%2BG7NIe8JX6Wv3dDyldMA%2B4YB5wwTygtd%2BdwRqaTqrLb1l73zTSN52CNpnHuQOYPsDblybgxfkXh%2FoVtr%2BN1DEBJdhRJyd%2FBd%2Fq1z%2BcbNrD17iVKyajcnv9arhOkRPgsruuD6DmNPwpDNrLw2CoTgHni4yALr0L29%2BtiKAEIPn868ejx%2F%2F8rpWP3OEOl5On9OwpcQm0MhafP%2Fey8f1uvDNIgGLQG8z4YO99ENgg95etwv4uYJYY8fUGHYH6j6fscHFZMftlAl9i%2B9XL73X3N%2Fn%2BZStOzfVfRvYXhrbdKOpEgVQTg%2FwsDuDD3kwOfQNMTJ5y%2B%2FltUDWLunyxnRF46IqlBzGMY4X7inggREFioIyMjIyMHWCIB6ZNKAcXseo3vLTQTkVE7348dlwJJSz0%2BwLfmi8BhZqfw3D4ww%2FwHVLnEd5%2FfgYvXsDZ3MlsvYUbbnDjDZ3MN3TJG4%2BbxjAaDl8TBri9qxEw1ccao2wTNAMLHo2f%2BsjrXwb%2F9qHoYqgPMBXJTVfOpmrZH23y6uvo0LHSyY6fHGwKfHJlAuMFvObjDYrIqxBgQi20h7Hd%2FnYVLmno%2BeaNUm%2FeeH2GCuopntnhBJAlI2AHo9CCh1I1QxUdAbqqGY9BBLwyc3W4wYVhvY8A4BoIc1l5M7vnPWphZW9%2FSes3n37y9a0uGqFwFQZsQQbd386DogpgEk%2BdzynsAZMJXq8%2Bns9NeukJ0PYrNATGGefJQlhkLo7DTXr%2By3bNiOsDvrXTz%2FC2q1DXZH84iRNwrP88Nj%2Bu2DjYEE6RBxD9Knj16ujVHC67A7422o02RwD3gB%2Bt7EblWvu9geOFxSnd3ROmT%2BnJyQkhoPlsxVONc%2F3TEdBos%2BjtA%2BZzcwHgTvD1cDjaYCcItA8w9i88A8b%2BmqSjc6Pvqd998QguEQPmQMeo23ODN86%2Bp0%2Fbn1buBkT6%2BoBhNZ%2FPYY4ZAHYb3PRd4LkZmPX68NRtMZn4ASvdA%2Bqf0jMA5MP9eeg28Nug9QiLnj5A33U1MAES6xHAUNpz%2F9zFAYE1gqQDMT3G6xI9pwdw%2FaIgKoHCS1YGlRnSq9yCjdXjgN3j%2BN27YyROHxmuNAeNKPpYuXIyIyMjYy0M8eros59MF%2FPT2c602T7eA7zvhJ9dr%2FvzDjXaLp4Yc5%2B0wllzxzHv3gdmMMM7%2FCcQzKgVBqYTmFn%2BZ%2BmKm8J7k0A5F%2FjgCfjQ1WBhQyiOqD0lYuqBb%2BAyzMw9Ha2G3m6c8qQx%2BAlqnIceQp%2BSb6i9UyQWbhr54%2BAjnZ0VzW2TAN0DmBT6PWmc6jDBE2PK2u%2BnF43dyP7Q0t1pOcX2fdRvH0mF2Q4JqN35rnHjVIeaXfIAVyUuw%2FaHCCiJy9iF5l1621zweI8KZrPZ9iJdb7DXJ3US0OSrtZ10imt7wHY7QesAzUMz1oZ3noB3qFJ%2FH18j97FYuw8QDN4oeKf30osvcSW2ExLo%2BVcbuAuo%2FsUIm8fMG9xocO3Ea19J9gFYivnHJ2KnyfovZlgW3v6ySx32abQiIyMjIyPjhlFDTLxpwIgFMnTp6A3g4IDKNY%2BstkwAMAoIAbasxBXqUWneSAWTMjt50lTqT29rFjvXohjsDNm2YPXDFlICmrJOZ3t6tHm8AiEAl0sCeLIIorIRt%2BcFbew%2FQRsoAXb4o1XSfoywzm0FTMAoYBNvLyFu8v8HpLBtD1iKgC17wHb7AI6d9wFbvguAIGTHd4E9wG7jgIyMjIyM%2B434c2R3HeV%2FFfx6jtZu6ijl8h59T655jhR%2BrdHzDOP6beABCheb8O8%2FWFXeOyzgf5oAhVYnKxP7CwaAf1afJu8bSrhS6tdaXeGnrRenOqOlz9d6QwYnA%2F3TLd%2BGE7qe3chA5YF5DfY0vK3adfOX%2FgyNp2BW25MHdxAB9qvRiiP3%2FXpQQFGYDU4%2BMi%2F%2F%2FXumXG8pjvaUAOsBGlf4jJt%2BYYEzeEzAdw06F19R3juM7D1wita86GR0CKfDHgLuXCc4Bri6vMLdfjMc4VNSUNsdodo2xu%2F1%2BXl%2FK5%2Baz8jIyMhYG%2Fz5gJTMF1GtKq%2Fa3rpyCvz5gJTMl9GtKq%2Fa3rpyCmfQ4WwZmS%2BkXFVetb115ST48wEf%2FAGcfG1iw%2BtWbpbS2vJ3nQxcVr3lH3z5h972FUTLzYpOVk7l5hD%2BeYcYwDcAnewOotrZ4OtrPDucqi%2FLRX0%2FRR4qx7Nn4U8g%2BqjffvuN6Gf%2BnC85vwauHjaYyubqvWYKY4VEfSUMitdnBCT1Ue63R5439m%2BOgCn6DroAAaHPVQxKth%2FwkJgHmG8bmQMsT0D6EjDfvhVRKO3ywOQUgRA7nmL1uawZmHf1k%2BDPBwQ6NdcJ%2Bk6Md1LA5f5ONdhJ8vZ5J0vLHT99srkGOjmJbd%2FG1r2Nriqnse1AZt1AalU5jW2HsuuG0qvKGRkZGRkZGRG0gcONyXsP9v8D0%2FIdJADiBNiXl3327WRGgOL%2F9HC%2F0XwlIURkRhC4tz6Z%2Ffu7fUf2gHvfB9z3u0BGRkZGRkbGplHcnkgguQoSqtUXuhbs%2FwPtMwqV0HUJAvj5vk32b8IDuL23yn7qAXZ5u32hbRX7d3o82Df1FZXvbh9QOfhyxldr%2F%2B3xgXU9oKmvsHyr7F%2FXA269%2FeveBXrsv7N9QALe%2FtvjA0kPWAXGbvebkbHn%2BD%2FJ5nMcHzx1UAAAAABJRU5ErkJggg%3D%3D)}.ui-widget-content .ui-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAADwCAMAAADYSUr5AAAA7VBMVEUkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiTww4gUAAAATnRSTlMAGBAyBAhQv4OZLiJUcEBmYBoSzQwgPBZCSEoeWiYwUiyFNIeBw2rJz8c4RBy9uXyrtaWNqa2zKP2fJO8KBgKPo2KVoa9s351GPm5%2BkWho0kj9AAAPhUlEQVR4nO1djWLbthEGyUiq5YSSLXtp7FpLOmfzkmxr126tmi2p03RJ1%2FXe%2F3EGgARxPyAgRbIk2%2FhkSz4CJO4%2BHsE7AJSVysjI2AMUUOxahZ2iANhzBtZWr4BoIRSYAVN5u4QwDwQDRbcwfUi5KS3wFuDmFnQLa4Dtb%2F%2FcqktwD5QEFFwfUs7PoCCA7y4bEJVFizcIob8KmhAplwwqVjt%2B9FBl3uINQniwEiryEyw9JHqGpQdEFNi%2BB4QQ7QOiHhysIPoAxUqxvdvvA9K42bsAv4S2fxfYOe57IJSRkZGRkZGxx7jxSHDHcRBXQMTyIjInBgHwBJ%2FbEx8PEANC%2BuhbpSSggCBAVODVabpI1S%2Fk4WLZpTn6NpMhoX9Y40hxYERFpMcqUs4AloCtDQdID1YhnyXZ2hLjAYWiO9Dy1PDB7tPhIqLx%2BuMB8grZaR%2BQxl2%2FC2RkZGRkZGRk7A7rBf7J0DR5%2FLUTjzUPIPSPGvQJiVJiB7kcQCiUOJrcFNtDZIf2xarQ3aGvLNxAVIFAabz90BFiBIlycTBhgWwOWCH0FLYHlPqwHaCvcIn2ZbosCevfPTRiFFcgvHukCjWwrc3GrGh1fsAof8EaUReKXkCB4%2FMzFNo97qLpFiKFYv%2FkNR5YQxQbQEofkZ2OuEOHqqT6gFTpru8CN7x%2F%2BjaZkZGRkZGRcV%2Bx%2FrLUNcMMqUAscgnFocmpqkTzqymwVAPxfJ5PnIUUQOUKT04tEdWZyv3JCQSn96WS4pD97QfyW25A7NhSAbyhmVj0FEltA4vdiygBibXhoUYgykCUP7HwPTDeEqAIcHVMkZg7Zx4k0uFANs63hPQXCoRLAwdgGsr9Az7Qv7sgQGgg1aPl%2FBJLExBWgG4RFRLFImGmIquPC%2FklEGyCG0AuAXaJJC%2BB8FVe9NYQDEcXB8g6AQcjYJ1goJIggHWCrFR0S6kRHN5%2B4BzFi8NaoN35NRxUvL%2BJJdZr7PV4wK6fj8nIyMjIyNhr3OxdXAYq7FHZwB6bDSzSh4sF0utChqo0NAvaT1hLzXwFinmCzmeDucEQK18TTaQoFgP7bNC%2BRZ4OT4T6gQogDFYk%2B1QxQlj19QGSAWKiLYp8P0Ag1Gbz1ULfWHLg9iUnQNK5QQJcukm04blKLH2GgEJCY%2BHzXAZWCvHKco3Bp6MIaCjSXXRJyOxeqhnzEaF93MfFGW%2FO16ZvDL5TM4MJIjujz%2FcHypkQuuzRwWJ93BKdIt%2BwCRAPl9kpe2Ikkb2mFgGlxh%2Fi40d3EHfdvoyMjIyMu43ylt%2FIAmGHnN5iIt7wKfbv01RAcJqFRl9lcjYQSnbQqKgC4fYOwSJt6N6trE0twZ9kN%2FPqNpTQeICvr4TLsDYC06U7BMjshS%2Bv1%2FaT7IwQYD5LcgRQXMT2FrBfBLjZ6151jDElk9tPFfpUgk2yregusX25BJbwAFEfM%2BYI6vGAti4bTtizB%2BTjfQCrERyhKb2X8D6A9wX75P4t4neBYJeP6pdhg%2FgQl8MWvytzeSTjgOQBynQdh%2FiXKdxOrGJ%2FRkZGRsb9QmXihGr5%2Bg8GGg9uTh%2BKoVZuNIzV%2BCwRucFBEyr1mVjx4irOxwM1BhirB6Q%2B2eNQi4eqR%2BaF6mELtoMzCR7V9RAFe%2FZvQogNiyY8FPSUTFsLp8TeTmMui5mtw7bcaT0Yw2AA4wFRQIlkgq%2B1DQrNhkmoxS5Jq%2Bu6bMAIGRECEANgXHTgWzwgBOhDH2l0oTQ4D8D5NMktBgNywAEMjo8rwATMZrPY7JGxBoJCkIBDQiAY09EGTUiBCWkUpISfGPR5AAwBfZiG2z7Ayc1yeKTxid39xBNwfHr4O0LA48ePFTvhYrF1r4tyAoz9n2MCqEuBtp%2F6GDR0oAYfG%2FR6wJExHYZHfhygsv7fEWCOj4bYmsP5A%2BpL4MkTfAnMlD4F%2Br3bobKvTyTA2P%2Fw7PN%2BAgq2QW8piqMCpTBwenoKvX0AHGkGtP2YAPvTEWA7QUTAudn7%2FNxtOG46wWNmDtpBEkBzN7rBEvAFHp%2BYTB%2Fq97qPAN4gHFqgBi8uLsC7qPCA6mg41G%2F%2BErByPwEXDdoNxRhOx%2BM5jPEzQugS0ht%2Bb1%2FY3gEnYMAIAOIBE29%2FhIDucE8tmMsNOgK4B1RHFu4UCRlMHzv0xzcajcfdXWDs2h8TArBCkoDUJYDLmz6w7ip3BFS0ve5wTRwAn6keMA9I3QYbfSZ0DKbyt%2B7OXjGI1idPcfNyAyfAMlCrzaGqphYrxHocLHRJVycnfGUcbtT%2BjIyMjIw9x7Nn8fJSzG0TmFtO8rZT%2BXT3S3ub%2BtKJbbLd5diTVp50%2BzahyeHSslJ%2FYPrU0fuazrZO2CZ92%2FZCCVXlGRiZKPJyPPRxyIFWeXLQBXJBKiq%2F3divEAN6ZwM200Qjm7EJBZeWm%2FPRWVCbYK7s7u2l4XaCz%2BlzgOfMfhMonXr7TWzeZb98dbgIzBT8Ub8eYYUqfZ4rVJ%2FMDbIDgPqTulJ%2FxvntWAtjIisqnwxOkGz0n077FARoY79GdA6HPE4rOy196NiMWHTZlSSApcOgXpy%2FfHV2joaNKu3ffsAnRcBf4K%2F6NcIG6tIxk3HyoXPjASqfUgXbYN5PzpL2njkR9QMjeDTVHDTCgRuxOegjoO0FvKzP%2Ft%2FgmVdI24%2BG7NIe8JX6Wv3dDyldMA%2B4YB5wwTygtd%2BdwRqaTqrLb1l73zTSN52CNpnHuQOYPsDblybgxfkXh%2FoVtr%2BN1DEBJdhRJyd%2FBd%2Fq1z%2BcbNrD17iVKyajcnv9arhOkRPgsruuD6DmNPwpDNrLw2CoTgHni4yALr0L29%2BtiKAEIPn868ejx%2F%2F8rpWP3OEOl5On9OwpcQm0MhafP%2Fey8f1uvDNIgGLQG8z4YO99ENgg95etwv4uYJYY8fUGHYH6j6fscHFZMftlAl9i%2B9XL73X3N%2Fn%2BZStOzfVfRvYXhrbdKOpEgVQTg%2FwsDuDD3kwOfQNMTJ5y%2B%2FltUDWLunyxnRF46IqlBzGMY4X7inggREFioIyMjIyMHWCIB6ZNKAcXseo3vLTQTkVE7348dlwJJSz0%2BwLfmi8BhZqfw3D4ww%2FwHVLnEd5%2FfgYvXsDZ3MlsvYUbbnDjDZ3MN3TJG4%2BbxjAaDl8TBri9qxEw1ccao2wTNAMLHo2f%2BsjrXwb%2F9qHoYqgPMBXJTVfOpmrZH23y6uvo0LHSyY6fHGwKfHJlAuMFvObjDYrIqxBgQi20h7Hd%2FnYVLmno%2BeaNUm%2FeeH2GCuopntnhBJAlI2AHo9CCh1I1QxUdAbqqGY9BBLwyc3W4wYVhvY8A4BoIc1l5M7vnPWphZW9%2FSes3n37y9a0uGqFwFQZsQQbd386DogpgEk%2BdzynsAZMJXq8%2Bns9NeukJ0PYrNATGGefJQlhkLo7DTXr%2By3bNiOsDvrXTz%2FC2q1DXZH84iRNwrP88Nj%2Bu2DjYEE6RBxD9Knj16ujVHC67A7422o02RwD3gB%2Bt7EblWvu9geOFxSnd3ROmT%2BnJyQkhoPlsxVONc%2F3TEdBos%2BjtA%2BZzcwHgTvD1cDjaYCcItA8w9i88A8b%2BmqSjc6Pvqd998QguEQPmQMeo23ODN86%2Bp0%2Fbn1buBkT6%2BoBhNZ%2FPYY4ZAHYb3PRd4LkZmPX68NRtMZn4ASvdA%2Bqf0jMA5MP9eeg28Nug9QiLnj5A33U1MAES6xHAUNpz%2F9zFAYE1gqQDMT3G6xI9pwdw%2FaIgKoHCS1YGlRnSq9yCjdXjgN3j%2BN27YyROHxmuNAeNKPpYuXIyIyMjYy0M8eros59MF%2FPT2c602T7eA7zvhJ9dr%2FvzDjXaLp4Yc5%2B0wllzxzHv3gdmMMM7%2FCcQzKgVBqYTmFn%2BZ%2BmKm8J7k0A5F%2FjgCfjQ1WBhQyiOqD0lYuqBb%2BAyzMw9Ha2G3m6c8qQx%2BAlqnIceQp%2BSb6i9UyQWbhr54%2BAjnZ0VzW2TAN0DmBT6PWmc6jDBE2PK2u%2BnF43dyP7Q0t1pOcX2fdRvH0mF2Q4JqN35rnHjVIeaXfIAVyUuw%2FaHCCiJy9iF5l1621zweI8KZrPZ9iJdb7DXJ3US0OSrtZ10imt7wHY7QesAzUMz1oZ3noB3qFJ%2FH18j97FYuw8QDN4oeKf30osvcSW2ExLo%2BVcbuAuo%2FsUIm8fMG9xocO3Ea19J9gFYivnHJ2KnyfovZlgW3v6ySx32abQiIyMjIyPjhlFDTLxpwIgFMnTp6A3g4IDKNY%2BstkwAMAoIAbasxBXqUWneSAWTMjt50lTqT29rFjvXohjsDNm2YPXDFlICmrJOZ3t6tHm8AiEAl0sCeLIIorIRt%2BcFbew%2FQRsoAXb4o1XSfoywzm0FTMAoYBNvLyFu8v8HpLBtD1iKgC17wHb7AI6d9wFbvguAIGTHd4E9wG7jgIyMjIyM%2B434c2R3HeV%2FFfx6jtZu6ijl8h59T655jhR%2BrdHzDOP6beABCheb8O8%2FWFXeOyzgf5oAhVYnKxP7CwaAf1afJu8bSrhS6tdaXeGnrRenOqOlz9d6QwYnA%2F3TLd%2BGE7qe3chA5YF5DfY0vK3adfOX%2FgyNp2BW25MHdxAB9qvRiiP3%2FXpQQFGYDU4%2BMi%2F%2F%2FXumXG8pjvaUAOsBGlf4jJt%2BYYEzeEzAdw06F19R3juM7D1wita86GR0CKfDHgLuXCc4Bri6vMLdfjMc4VNSUNsdodo2xu%2F1%2BXl%2FK5%2Baz8jIyMhYG%2Fz5gJTMF1GtKq%2Fa3rpyCvz5gJTMl9GtKq%2Fa3rpyCmfQ4WwZmS%2BkXFVetb115ST48wEf%2FAGcfG1iw%2BtWbpbS2vJ3nQxcVr3lH3z5h972FUTLzYpOVk7l5hD%2BeYcYwDcAnewOotrZ4OtrPDucqi%2FLRX0%2FRR4qx7Nn4U8g%2BqjffvuN6Gf%2BnC85vwauHjaYyubqvWYKY4VEfSUMitdnBCT1Ue63R5439m%2BOgCn6DroAAaHPVQxKth%2FwkJgHmG8bmQMsT0D6EjDfvhVRKO3ywOQUgRA7nmL1uawZmHf1k%2BDPBwQ6NdcJ%2Bk6Md1LA5f5ONdhJ8vZ5J0vLHT99srkGOjmJbd%2FG1r2Nriqnse1AZt1AalU5jW2HsuuG0qvKGRkZGRkZGRG0gcONyXsP9v8D0%2FIdJADiBNiXl3327WRGgOL%2F9HC%2F0XwlIURkRhC4tz6Z%2Ffu7fUf2gHvfB9z3u0BGRkZGRkbGplHcnkgguQoSqtUXuhbs%2FwPtMwqV0HUJAvj5vk32b8IDuL23yn7qAXZ5u32hbRX7d3o82Df1FZXvbh9QOfhyxldr%2F%2B3xgXU9oKmvsHyr7F%2FXA269%2FeveBXrsv7N9QALe%2FtvjA0kPWAXGbvebkbHn%2BD%2FJ5nMcHzx1UAAAAABJRU5ErkJggg%3D%3D)}.ui-widget-header .ui-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAADwCAMAAADYSUr5AAAA7VBMVEUkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiQkIiTww4gUAAAATnRSTlMAGBAyBAhQv4OZLiJUcEBmYBoSzQwgPBZCSEoeWiYwUiyFNIeBw2rJz8c4RBy9uXyrtaWNqa2zKP2fJO8KBgKPo2KVoa9s351GPm5%2BkWho0kj9AAAPhUlEQVR4nO1djWLbthEGyUiq5YSSLXtp7FpLOmfzkmxr126tmi2p03RJ1%2FXe%2F3EGgARxPyAgRbIk2%2FhkSz4CJO4%2BHsE7AJSVysjI2AMUUOxahZ2iANhzBtZWr4BoIRSYAVN5u4QwDwQDRbcwfUi5KS3wFuDmFnQLa4Dtb%2F%2FcqktwD5QEFFwfUs7PoCCA7y4bEJVFizcIob8KmhAplwwqVjt%2B9FBl3uINQniwEiryEyw9JHqGpQdEFNi%2BB4QQ7QOiHhysIPoAxUqxvdvvA9K42bsAv4S2fxfYOe57IJSRkZGRkZGxx7jxSHDHcRBXQMTyIjInBgHwBJ%2FbEx8PEANC%2BuhbpSSggCBAVODVabpI1S%2Fk4WLZpTn6NpMhoX9Y40hxYERFpMcqUs4AloCtDQdID1YhnyXZ2hLjAYWiO9Dy1PDB7tPhIqLx%2BuMB8grZaR%2BQxl2%2FC2RkZGRkZGRk7A7rBf7J0DR5%2FLUTjzUPIPSPGvQJiVJiB7kcQCiUOJrcFNtDZIf2xarQ3aGvLNxAVIFAabz90BFiBIlycTBhgWwOWCH0FLYHlPqwHaCvcIn2ZbosCevfPTRiFFcgvHukCjWwrc3GrGh1fsAof8EaUReKXkCB4%2FMzFNo97qLpFiKFYv%2FkNR5YQxQbQEofkZ2OuEOHqqT6gFTpru8CN7x%2F%2BjaZkZGRkZGRcV%2Bx%2FrLUNcMMqUAscgnFocmpqkTzqymwVAPxfJ5PnIUUQOUKT04tEdWZyv3JCQSn96WS4pD97QfyW25A7NhSAbyhmVj0FEltA4vdiygBibXhoUYgykCUP7HwPTDeEqAIcHVMkZg7Zx4k0uFANs63hPQXCoRLAwdgGsr9Az7Qv7sgQGgg1aPl%2FBJLExBWgG4RFRLFImGmIquPC%2FklEGyCG0AuAXaJJC%2BB8FVe9NYQDEcXB8g6AQcjYJ1goJIggHWCrFR0S6kRHN5%2B4BzFi8NaoN35NRxUvL%2BJJdZr7PV4wK6fj8nIyMjIyNhr3OxdXAYq7FHZwB6bDSzSh4sF0utChqo0NAvaT1hLzXwFinmCzmeDucEQK18TTaQoFgP7bNC%2BRZ4OT4T6gQogDFYk%2B1QxQlj19QGSAWKiLYp8P0Ag1Gbz1ULfWHLg9iUnQNK5QQJcukm04blKLH2GgEJCY%2BHzXAZWCvHKco3Bp6MIaCjSXXRJyOxeqhnzEaF93MfFGW%2FO16ZvDL5TM4MJIjujz%2FcHypkQuuzRwWJ93BKdIt%2BwCRAPl9kpe2Ikkb2mFgGlxh%2Fi40d3EHfdvoyMjIyMu43ylt%2FIAmGHnN5iIt7wKfbv01RAcJqFRl9lcjYQSnbQqKgC4fYOwSJt6N6trE0twZ9kN%2FPqNpTQeICvr4TLsDYC06U7BMjshS%2Bv1%2FaT7IwQYD5LcgRQXMT2FrBfBLjZ6151jDElk9tPFfpUgk2yregusX25BJbwAFEfM%2BYI6vGAti4bTtizB%2BTjfQCrERyhKb2X8D6A9wX75P4t4neBYJeP6pdhg%2FgQl8MWvytzeSTjgOQBynQdh%2FiXKdxOrGJ%2FRkZGRsb9QmXihGr5%2Bg8GGg9uTh%2BKoVZuNIzV%2BCwRucFBEyr1mVjx4irOxwM1BhirB6Q%2B2eNQi4eqR%2BaF6mELtoMzCR7V9RAFe%2FZvQogNiyY8FPSUTFsLp8TeTmMui5mtw7bcaT0Yw2AA4wFRQIlkgq%2B1DQrNhkmoxS5Jq%2Bu6bMAIGRECEANgXHTgWzwgBOhDH2l0oTQ4D8D5NMktBgNywAEMjo8rwATMZrPY7JGxBoJCkIBDQiAY09EGTUiBCWkUpISfGPR5AAwBfZiG2z7Ayc1yeKTxid39xBNwfHr4O0LA48ePFTvhYrF1r4tyAoz9n2MCqEuBtp%2F6GDR0oAYfG%2FR6wJExHYZHfhygsv7fEWCOj4bYmsP5A%2BpL4MkTfAnMlD4F%2Br3bobKvTyTA2P%2Fw7PN%2BAgq2QW8piqMCpTBwenoKvX0AHGkGtP2YAPvTEWA7QUTAudn7%2FNxtOG46wWNmDtpBEkBzN7rBEvAFHp%2BYTB%2Fq97qPAN4gHFqgBi8uLsC7qPCA6mg41G%2F%2BErByPwEXDdoNxRhOx%2BM5jPEzQugS0ht%2Bb1%2FY3gEnYMAIAOIBE29%2FhIDucE8tmMsNOgK4B1RHFu4UCRlMHzv0xzcajcfdXWDs2h8TArBCkoDUJYDLmz6w7ip3BFS0ve5wTRwAn6keMA9I3QYbfSZ0DKbyt%2B7OXjGI1idPcfNyAyfAMlCrzaGqphYrxHocLHRJVycnfGUcbtT%2BjIyMjIw9x7Nn8fJSzG0TmFtO8rZT%2BXT3S3ub%2BtKJbbLd5diTVp50%2BzahyeHSslJ%2FYPrU0fuazrZO2CZ92%2FZCCVXlGRiZKPJyPPRxyIFWeXLQBXJBKiq%2F3divEAN6ZwM200Qjm7EJBZeWm%2FPRWVCbYK7s7u2l4XaCz%2BlzgOfMfhMonXr7TWzeZb98dbgIzBT8Ub8eYYUqfZ4rVJ%2FMDbIDgPqTulJ%2FxvntWAtjIisqnwxOkGz0n077FARoY79GdA6HPE4rOy196NiMWHTZlSSApcOgXpy%2FfHV2joaNKu3ffsAnRcBf4K%2F6NcIG6tIxk3HyoXPjASqfUgXbYN5PzpL2njkR9QMjeDTVHDTCgRuxOegjoO0FvKzP%2Ft%2FgmVdI24%2BG7NIe8JX6Wv3dDyldMA%2B4YB5wwTygtd%2BdwRqaTqrLb1l73zTSN52CNpnHuQOYPsDblybgxfkXh%2FoVtr%2BN1DEBJdhRJyd%2FBd%2Fq1z%2BcbNrD17iVKyajcnv9arhOkRPgsruuD6DmNPwpDNrLw2CoTgHni4yALr0L29%2BtiKAEIPn868ejx%2F%2F8rpWP3OEOl5On9OwpcQm0MhafP%2Fey8f1uvDNIgGLQG8z4YO99ENgg95etwv4uYJYY8fUGHYH6j6fscHFZMftlAl9i%2B9XL73X3N%2Fn%2BZStOzfVfRvYXhrbdKOpEgVQTg%2FwsDuDD3kwOfQNMTJ5y%2B%2FltUDWLunyxnRF46IqlBzGMY4X7inggREFioIyMjIyMHWCIB6ZNKAcXseo3vLTQTkVE7348dlwJJSz0%2BwLfmi8BhZqfw3D4ww%2FwHVLnEd5%2FfgYvXsDZ3MlsvYUbbnDjDZ3MN3TJG4%2BbxjAaDl8TBri9qxEw1ccao2wTNAMLHo2f%2BsjrXwb%2F9qHoYqgPMBXJTVfOpmrZH23y6uvo0LHSyY6fHGwKfHJlAuMFvObjDYrIqxBgQi20h7Hd%2FnYVLmno%2BeaNUm%2FeeH2GCuopntnhBJAlI2AHo9CCh1I1QxUdAbqqGY9BBLwyc3W4wYVhvY8A4BoIc1l5M7vnPWphZW9%2FSes3n37y9a0uGqFwFQZsQQbd386DogpgEk%2BdzynsAZMJXq8%2Bns9NeukJ0PYrNATGGefJQlhkLo7DTXr%2By3bNiOsDvrXTz%2FC2q1DXZH84iRNwrP88Nj%2Bu2DjYEE6RBxD9Knj16ujVHC67A7422o02RwD3gB%2Bt7EblWvu9geOFxSnd3ROmT%2BnJyQkhoPlsxVONc%2F3TEdBos%2BjtA%2BZzcwHgTvD1cDjaYCcItA8w9i88A8b%2BmqSjc6Pvqd998QguEQPmQMeo23ODN86%2Bp0%2Fbn1buBkT6%2BoBhNZ%2FPYY4ZAHYb3PRd4LkZmPX68NRtMZn4ASvdA%2Bqf0jMA5MP9eeg28Nug9QiLnj5A33U1MAES6xHAUNpz%2F9zFAYE1gqQDMT3G6xI9pwdw%2FaIgKoHCS1YGlRnSq9yCjdXjgN3j%2BN27YyROHxmuNAeNKPpYuXIyIyMjYy0M8eros59MF%2FPT2c602T7eA7zvhJ9dr%2FvzDjXaLp4Yc5%2B0wllzxzHv3gdmMMM7%2FCcQzKgVBqYTmFn%2BZ%2BmKm8J7k0A5F%2FjgCfjQ1WBhQyiOqD0lYuqBb%2BAyzMw9Ha2G3m6c8qQx%2BAlqnIceQp%2BSb6i9UyQWbhr54%2BAjnZ0VzW2TAN0DmBT6PWmc6jDBE2PK2u%2BnF43dyP7Q0t1pOcX2fdRvH0mF2Q4JqN35rnHjVIeaXfIAVyUuw%2FaHCCiJy9iF5l1621zweI8KZrPZ9iJdb7DXJ3US0OSrtZ10imt7wHY7QesAzUMz1oZ3noB3qFJ%2FH18j97FYuw8QDN4oeKf30osvcSW2ExLo%2BVcbuAuo%2FsUIm8fMG9xocO3Ea19J9gFYivnHJ2KnyfovZlgW3v6ySx32abQiIyMjIyPjhlFDTLxpwIgFMnTp6A3g4IDKNY%2BstkwAMAoIAbasxBXqUWneSAWTMjt50lTqT29rFjvXohjsDNm2YPXDFlICmrJOZ3t6tHm8AiEAl0sCeLIIorIRt%2BcFbew%2FQRsoAXb4o1XSfoywzm0FTMAoYBNvLyFu8v8HpLBtD1iKgC17wHb7AI6d9wFbvguAIGTHd4E9wG7jgIyMjIyM%2B434c2R3HeV%2FFfx6jtZu6ijl8h59T655jhR%2BrdHzDOP6beABCheb8O8%2FWFXeOyzgf5oAhVYnKxP7CwaAf1afJu8bSrhS6tdaXeGnrRenOqOlz9d6QwYnA%2F3TLd%2BGE7qe3chA5YF5DfY0vK3adfOX%2FgyNp2BW25MHdxAB9qvRiiP3%2FXpQQFGYDU4%2BMi%2F%2F%2FXumXG8pjvaUAOsBGlf4jJt%2BYYEzeEzAdw06F19R3juM7D1wita86GR0CKfDHgLuXCc4Bri6vMLdfjMc4VNSUNsdodo2xu%2F1%2BXl%2FK5%2Baz8jIyMhYG%2Fz5gJTMF1GtKq%2Fa3rpyCvz5gJTMl9GtKq%2Fa3rpyCmfQ4WwZmS%2BkXFVetb115ST48wEf%2FAGcfG1iw%2BtWbpbS2vJ3nQxcVr3lH3z5h972FUTLzYpOVk7l5hD%2BeYcYwDcAnewOotrZ4OtrPDucqi%2FLRX0%2FRR4qx7Nn4U8g%2BqjffvuN6Gf%2BnC85vwauHjaYyubqvWYKY4VEfSUMitdnBCT1Ue63R5439m%2BOgCn6DroAAaHPVQxKth%2FwkJgHmG8bmQMsT0D6EjDfvhVRKO3ywOQUgRA7nmL1uawZmHf1k%2BDPBwQ6NdcJ%2Bk6Md1LA5f5ONdhJ8vZ5J0vLHT99srkGOjmJbd%2FG1r2Nriqnse1AZt1AalU5jW2HsuuG0qvKGRkZGRkZGRG0gcONyXsP9v8D0%2FIdJADiBNiXl3327WRGgOL%2F9HC%2F0XwlIURkRhC4tz6Z%2Ffu7fUf2gHvfB9z3u0BGRkZGRkbGplHcnkgguQoSqtUXuhbs%2FwPtMwqV0HUJAvj5vk32b8IDuL23yn7qAXZ5u32hbRX7d3o82Df1FZXvbh9QOfhyxldr%2F%2B3xgXU9oKmvsHyr7F%2FXA269%2FeveBXrsv7N9QALe%2FtvjA0kPWAXGbvebkbHn%2BD%2FJ5nMcHzx1UAAAAABJRU5ErkJggg%3D%3D)}.ui-state-default .ui-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAADwCAMAAADYSUr5AAAA7VBMVEWMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMioyMiozJxoFPAAAATnRSTlMAGBAyBAhQv4OZLiJUcEBmYBoSzQwgPBZCSEoeWiYwUiyFNIeBw2rJz8c4RBy9uXyrtaWNqa2zKP2fJO8KBgKPo2KVoa9s351GPm5%2BkWho0kj9AAAPhUlEQVR4nO1djWLbthEGyUiq5YSSLXtp7FpLOmfzkmxr126tmi2p03RJ1%2FXe%2F3EGgARxPyAgRbIk2%2FhkSz4CJO4%2BHsE7AJSVysjI2AMUUOxahZ2iANhzBtZWr4BoIRSYAVN5u4QwDwQDRbcwfUi5KS3wFuDmFnQLa4Dtb%2F%2FcqktwD5QEFFwfUs7PoCCA7y4bEJVFizcIob8KmhAplwwqVjt%2B9FBl3uINQniwEiryEyw9JHqGpQdEFNi%2BB4QQ7QOiHhysIPoAxUqxvdvvA9K42bsAv4S2fxfYOe57IJSRkZGRkZGxx7jxSHDHcRBXQMTyIjInBgHwBJ%2FbEx8PEANC%2BuhbpSSggCBAVODVabpI1S%2Fk4WLZpTn6NpMhoX9Y40hxYERFpMcqUs4AloCtDQdID1YhnyXZ2hLjAYWiO9Dy1PDB7tPhIqLx%2BuMB8grZaR%2BQxl2%2FC2RkZGRkZGRk7A7rBf7J0DR5%2FLUTjzUPIPSPGvQJiVJiB7kcQCiUOJrcFNtDZIf2xarQ3aGvLNxAVIFAabz90BFiBIlycTBhgWwOWCH0FLYHlPqwHaCvcIn2ZbosCevfPTRiFFcgvHukCjWwrc3GrGh1fsAof8EaUReKXkCB4%2FMzFNo97qLpFiKFYv%2FkNR5YQxQbQEofkZ2OuEOHqqT6gFTpru8CN7x%2F%2BjaZkZGRkZGRcV%2Bx%2FrLUNcMMqUAscgnFocmpqkTzqymwVAPxfJ5PnIUUQOUKT04tEdWZyv3JCQSn96WS4pD97QfyW25A7NhSAbyhmVj0FEltA4vdiygBibXhoUYgykCUP7HwPTDeEqAIcHVMkZg7Zx4k0uFANs63hPQXCoRLAwdgGsr9Az7Qv7sgQGgg1aPl%2FBJLExBWgG4RFRLFImGmIquPC%2FklEGyCG0AuAXaJJC%2BB8FVe9NYQDEcXB8g6AQcjYJ1goJIggHWCrFR0S6kRHN5%2B4BzFi8NaoN35NRxUvL%2BJJdZr7PV4wK6fj8nIyMjIyNhr3OxdXAYq7FHZwB6bDSzSh4sF0utChqo0NAvaT1hLzXwFinmCzmeDucEQK18TTaQoFgP7bNC%2BRZ4OT4T6gQogDFYk%2B1QxQlj19QGSAWKiLYp8P0Ag1Gbz1ULfWHLg9iUnQNK5QQJcukm04blKLH2GgEJCY%2BHzXAZWCvHKco3Bp6MIaCjSXXRJyOxeqhnzEaF93MfFGW%2FO16ZvDL5TM4MJIjujz%2FcHypkQuuzRwWJ93BKdIt%2BwCRAPl9kpe2Ikkb2mFgGlxh%2Fi40d3EHfdvoyMjIyMu43ylt%2FIAmGHnN5iIt7wKfbv01RAcJqFRl9lcjYQSnbQqKgC4fYOwSJt6N6trE0twZ9kN%2FPqNpTQeICvr4TLsDYC06U7BMjshS%2Bv1%2FaT7IwQYD5LcgRQXMT2FrBfBLjZ6151jDElk9tPFfpUgk2yregusX25BJbwAFEfM%2BYI6vGAti4bTtizB%2BTjfQCrERyhKb2X8D6A9wX75P4t4neBYJeP6pdhg%2FgQl8MWvytzeSTjgOQBynQdh%2FiXKdxOrGJ%2FRkZGRsb9QmXihGr5%2Bg8GGg9uTh%2BKoVZuNIzV%2BCwRucFBEyr1mVjx4irOxwM1BhirB6Q%2B2eNQi4eqR%2BaF6mELtoMzCR7V9RAFe%2FZvQogNiyY8FPSUTFsLp8TeTmMui5mtw7bcaT0Yw2AA4wFRQIlkgq%2B1DQrNhkmoxS5Jq%2Bu6bMAIGRECEANgXHTgWzwgBOhDH2l0oTQ4D8D5NMktBgNywAEMjo8rwATMZrPY7JGxBoJCkIBDQiAY09EGTUiBCWkUpISfGPR5AAwBfZiG2z7Ayc1yeKTxid39xBNwfHr4O0LA48ePFTvhYrF1r4tyAoz9n2MCqEuBtp%2F6GDR0oAYfG%2FR6wJExHYZHfhygsv7fEWCOj4bYmsP5A%2BpL4MkTfAnMlD4F%2Br3bobKvTyTA2P%2Fw7PN%2BAgq2QW8piqMCpTBwenoKvX0AHGkGtP2YAPvTEWA7QUTAudn7%2FNxtOG46wWNmDtpBEkBzN7rBEvAFHp%2BYTB%2Fq97qPAN4gHFqgBi8uLsC7qPCA6mg41G%2F%2BErByPwEXDdoNxRhOx%2BM5jPEzQugS0ht%2Bb1%2FY3gEnYMAIAOIBE29%2FhIDucE8tmMsNOgK4B1RHFu4UCRlMHzv0xzcajcfdXWDs2h8TArBCkoDUJYDLmz6w7ip3BFS0ve5wTRwAn6keMA9I3QYbfSZ0DKbyt%2B7OXjGI1idPcfNyAyfAMlCrzaGqphYrxHocLHRJVycnfGUcbtT%2BjIyMjIw9x7Nn8fJSzG0TmFtO8rZT%2BXT3S3ub%2BtKJbbLd5diTVp50%2BzahyeHSslJ%2FYPrU0fuazrZO2CZ92%2FZCCVXlGRiZKPJyPPRxyIFWeXLQBXJBKiq%2F3divEAN6ZwM200Qjm7EJBZeWm%2FPRWVCbYK7s7u2l4XaCz%2BlzgOfMfhMonXr7TWzeZb98dbgIzBT8Ub8eYYUqfZ4rVJ%2FMDbIDgPqTulJ%2FxvntWAtjIisqnwxOkGz0n077FARoY79GdA6HPE4rOy196NiMWHTZlSSApcOgXpy%2FfHV2joaNKu3ffsAnRcBf4K%2F6NcIG6tIxk3HyoXPjASqfUgXbYN5PzpL2njkR9QMjeDTVHDTCgRuxOegjoO0FvKzP%2Ft%2FgmVdI24%2BG7NIe8JX6Wv3dDyldMA%2B4YB5wwTygtd%2BdwRqaTqrLb1l73zTSN52CNpnHuQOYPsDblybgxfkXh%2FoVtr%2BN1DEBJdhRJyd%2FBd%2Fq1z%2BcbNrD17iVKyajcnv9arhOkRPgsruuD6DmNPwpDNrLw2CoTgHni4yALr0L29%2BtiKAEIPn868ejx%2F%2F8rpWP3OEOl5On9OwpcQm0MhafP%2Fey8f1uvDNIgGLQG8z4YO99ENgg95etwv4uYJYY8fUGHYH6j6fscHFZMftlAl9i%2B9XL73X3N%2Fn%2BZStOzfVfRvYXhrbdKOpEgVQTg%2FwsDuDD3kwOfQNMTJ5y%2B%2FltUDWLunyxnRF46IqlBzGMY4X7inggREFioIyMjIyMHWCIB6ZNKAcXseo3vLTQTkVE7348dlwJJSz0%2BwLfmi8BhZqfw3D4ww%2FwHVLnEd5%2FfgYvXsDZ3MlsvYUbbnDjDZ3MN3TJG4%2BbxjAaDl8TBri9qxEw1ccao2wTNAMLHo2f%2BsjrXwb%2F9qHoYqgPMBXJTVfOpmrZH23y6uvo0LHSyY6fHGwKfHJlAuMFvObjDYrIqxBgQi20h7Hd%2FnYVLmno%2BeaNUm%2FeeH2GCuopntnhBJAlI2AHo9CCh1I1QxUdAbqqGY9BBLwyc3W4wYVhvY8A4BoIc1l5M7vnPWphZW9%2FSes3n37y9a0uGqFwFQZsQQbd386DogpgEk%2BdzynsAZMJXq8%2Bns9NeukJ0PYrNATGGefJQlhkLo7DTXr%2By3bNiOsDvrXTz%2FC2q1DXZH84iRNwrP88Nj%2Bu2DjYEE6RBxD9Knj16ujVHC67A7422o02RwD3gB%2Bt7EblWvu9geOFxSnd3ROmT%2BnJyQkhoPlsxVONc%2F3TEdBos%2BjtA%2BZzcwHgTvD1cDjaYCcItA8w9i88A8b%2BmqSjc6Pvqd998QguEQPmQMeo23ODN86%2Bp0%2Fbn1buBkT6%2BoBhNZ%2FPYY4ZAHYb3PRd4LkZmPX68NRtMZn4ASvdA%2Bqf0jMA5MP9eeg28Nug9QiLnj5A33U1MAES6xHAUNpz%2F9zFAYE1gqQDMT3G6xI9pwdw%2FaIgKoHCS1YGlRnSq9yCjdXjgN3j%2BN27YyROHxmuNAeNKPpYuXIyIyMjYy0M8eros59MF%2FPT2c602T7eA7zvhJ9dr%2FvzDjXaLp4Yc5%2B0wllzxzHv3gdmMMM7%2FCcQzKgVBqYTmFn%2BZ%2BmKm8J7k0A5F%2FjgCfjQ1WBhQyiOqD0lYuqBb%2BAyzMw9Ha2G3m6c8qQx%2BAlqnIceQp%2BSb6i9UyQWbhr54%2BAjnZ0VzW2TAN0DmBT6PWmc6jDBE2PK2u%2BnF43dyP7Q0t1pOcX2fdRvH0mF2Q4JqN35rnHjVIeaXfIAVyUuw%2FaHCCiJy9iF5l1621zweI8KZrPZ9iJdb7DXJ3US0OSrtZ10imt7wHY7QesAzUMz1oZ3noB3qFJ%2FH18j97FYuw8QDN4oeKf30osvcSW2ExLo%2BVcbuAuo%2FsUIm8fMG9xocO3Ea19J9gFYivnHJ2KnyfovZlgW3v6ySx32abQiIyMjIyPjhlFDTLxpwIgFMnTp6A3g4IDKNY%2BstkwAMAoIAbasxBXqUWneSAWTMjt50lTqT29rFjvXohjsDNm2YPXDFlICmrJOZ3t6tHm8AiEAl0sCeLIIorIRt%2BcFbew%2FQRsoAXb4o1XSfoywzm0FTMAoYBNvLyFu8v8HpLBtD1iKgC17wHb7AI6d9wFbvguAIGTHd4E9wG7jgIyMjIyM%2B434c2R3HeV%2FFfx6jtZu6ijl8h59T655jhR%2BrdHzDOP6beABCheb8O8%2FWFXeOyzgf5oAhVYnKxP7CwaAf1afJu8bSrhS6tdaXeGnrRenOqOlz9d6QwYnA%2F3TLd%2BGE7qe3chA5YF5DfY0vK3adfOX%2FgyNp2BW25MHdxAB9qvRiiP3%2FXpQQFGYDU4%2BMi%2F%2F%2FXumXG8pjvaUAOsBGlf4jJt%2BYYEzeEzAdw06F19R3juM7D1wita86GR0CKfDHgLuXCc4Bri6vMLdfjMc4VNSUNsdodo2xu%2F1%2BXl%2FK5%2Baz8jIyMhYG%2Fz5gJTMF1GtKq%2Fa3rpyCvz5gJTMl9GtKq%2Fa3rpyCmfQ4WwZmS%2BkXFVetb115ST48wEf%2FAGcfG1iw%2BtWbpbS2vJ3nQxcVr3lH3z5h972FUTLzYpOVk7l5hD%2BeYcYwDcAnewOotrZ4OtrPDucqi%2FLRX0%2FRR4qx7Nn4U8g%2BqjffvuN6Gf%2BnC85vwauHjaYyubqvWYKY4VEfSUMitdnBCT1Ue63R5439m%2BOgCn6DroAAaHPVQxKth%2FwkJgHmG8bmQMsT0D6EjDfvhVRKO3ywOQUgRA7nmL1uawZmHf1k%2BDPBwQ6NdcJ%2Bk6Md1LA5f5ONdhJ8vZ5J0vLHT99srkGOjmJbd%2FG1r2Nriqnse1AZt1AalU5jW2HsuuG0qvKGRkZGRkZGRG0gcONyXsP9v8D0%2FIdJADiBNiXl3327WRGgOL%2F9HC%2F0XwlIURkRhC4tz6Z%2Ffu7fUf2gHvfB9z3u0BGRkZGRkbGplHcnkgguQoSqtUXuhbs%2FwPtMwqV0HUJAvj5vk32b8IDuL23yn7qAXZ5u32hbRX7d3o82Df1FZXvbh9QOfhyxldr%2F%2B3xgXU9oKmvsHyr7F%2FXA269%2FeveBXrsv7N9QALe%2FtvjA0kPWAXGbvebkbHn%2BD%2FJ5nMcHzx1UAAAAABJRU5ErkJggg%3D%3D)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAADwCAMAAADYSUr5AAAA7VBMVEVERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkQfbf86AAAATnRSTlMAGBAyBAhQv4OZLiJUcEBmYBoSzQwgPBZCSEoeWiYwUiyFNIeBw2rJz8c4RBy9uXyrtaWNqa2zKP2fJO8KBgKPo2KVoa9s351GPm5%2BkWho0kj9AAAPhUlEQVR4nO1djWLbthEGyUiq5YSSLXtp7FpLOmfzkmxr126tmi2p03RJ1%2FXe%2F3EGgARxPyAgRbIk2%2FhkSz4CJO4%2BHsE7AJSVysjI2AMUUOxahZ2iANhzBtZWr4BoIRSYAVN5u4QwDwQDRbcwfUi5KS3wFuDmFnQLa4Dtb%2F%2FcqktwD5QEFFwfUs7PoCCA7y4bEJVFizcIob8KmhAplwwqVjt%2B9FBl3uINQniwEiryEyw9JHqGpQdEFNi%2BB4QQ7QOiHhysIPoAxUqxvdvvA9K42bsAv4S2fxfYOe57IJSRkZGRkZGxx7jxSHDHcRBXQMTyIjInBgHwBJ%2FbEx8PEANC%2BuhbpSSggCBAVODVabpI1S%2Fk4WLZpTn6NpMhoX9Y40hxYERFpMcqUs4AloCtDQdID1YhnyXZ2hLjAYWiO9Dy1PDB7tPhIqLx%2BuMB8grZaR%2BQxl2%2FC2RkZGRkZGRk7A7rBf7J0DR5%2FLUTjzUPIPSPGvQJiVJiB7kcQCiUOJrcFNtDZIf2xarQ3aGvLNxAVIFAabz90BFiBIlycTBhgWwOWCH0FLYHlPqwHaCvcIn2ZbosCevfPTRiFFcgvHukCjWwrc3GrGh1fsAof8EaUReKXkCB4%2FMzFNo97qLpFiKFYv%2FkNR5YQxQbQEofkZ2OuEOHqqT6gFTpru8CN7x%2F%2BjaZkZGRkZGRcV%2Bx%2FrLUNcMMqUAscgnFocmpqkTzqymwVAPxfJ5PnIUUQOUKT04tEdWZyv3JCQSn96WS4pD97QfyW25A7NhSAbyhmVj0FEltA4vdiygBibXhoUYgykCUP7HwPTDeEqAIcHVMkZg7Zx4k0uFANs63hPQXCoRLAwdgGsr9Az7Qv7sgQGgg1aPl%2FBJLExBWgG4RFRLFImGmIquPC%2FklEGyCG0AuAXaJJC%2BB8FVe9NYQDEcXB8g6AQcjYJ1goJIggHWCrFR0S6kRHN5%2B4BzFi8NaoN35NRxUvL%2BJJdZr7PV4wK6fj8nIyMjIyNhr3OxdXAYq7FHZwB6bDSzSh4sF0utChqo0NAvaT1hLzXwFinmCzmeDucEQK18TTaQoFgP7bNC%2BRZ4OT4T6gQogDFYk%2B1QxQlj19QGSAWKiLYp8P0Ag1Gbz1ULfWHLg9iUnQNK5QQJcukm04blKLH2GgEJCY%2BHzXAZWCvHKco3Bp6MIaCjSXXRJyOxeqhnzEaF93MfFGW%2FO16ZvDL5TM4MJIjujz%2FcHypkQuuzRwWJ93BKdIt%2BwCRAPl9kpe2Ikkb2mFgGlxh%2Fi40d3EHfdvoyMjIyMu43ylt%2FIAmGHnN5iIt7wKfbv01RAcJqFRl9lcjYQSnbQqKgC4fYOwSJt6N6trE0twZ9kN%2FPqNpTQeICvr4TLsDYC06U7BMjshS%2Bv1%2FaT7IwQYD5LcgRQXMT2FrBfBLjZ6151jDElk9tPFfpUgk2yregusX25BJbwAFEfM%2BYI6vGAti4bTtizB%2BTjfQCrERyhKb2X8D6A9wX75P4t4neBYJeP6pdhg%2FgQl8MWvytzeSTjgOQBynQdh%2FiXKdxOrGJ%2FRkZGRsb9QmXihGr5%2Bg8GGg9uTh%2BKoVZuNIzV%2BCwRucFBEyr1mVjx4irOxwM1BhirB6Q%2B2eNQi4eqR%2BaF6mELtoMzCR7V9RAFe%2FZvQogNiyY8FPSUTFsLp8TeTmMui5mtw7bcaT0Yw2AA4wFRQIlkgq%2B1DQrNhkmoxS5Jq%2Bu6bMAIGRECEANgXHTgWzwgBOhDH2l0oTQ4D8D5NMktBgNywAEMjo8rwATMZrPY7JGxBoJCkIBDQiAY09EGTUiBCWkUpISfGPR5AAwBfZiG2z7Ayc1yeKTxid39xBNwfHr4O0LA48ePFTvhYrF1r4tyAoz9n2MCqEuBtp%2F6GDR0oAYfG%2FR6wJExHYZHfhygsv7fEWCOj4bYmsP5A%2BpL4MkTfAnMlD4F%2Br3bobKvTyTA2P%2Fw7PN%2BAgq2QW8piqMCpTBwenoKvX0AHGkGtP2YAPvTEWA7QUTAudn7%2FNxtOG46wWNmDtpBEkBzN7rBEvAFHp%2BYTB%2Fq97qPAN4gHFqgBi8uLsC7qPCA6mg41G%2F%2BErByPwEXDdoNxRhOx%2BM5jPEzQugS0ht%2Bb1%2FY3gEnYMAIAOIBE29%2FhIDucE8tmMsNOgK4B1RHFu4UCRlMHzv0xzcajcfdXWDs2h8TArBCkoDUJYDLmz6w7ip3BFS0ve5wTRwAn6keMA9I3QYbfSZ0DKbyt%2B7OXjGI1idPcfNyAyfAMlCrzaGqphYrxHocLHRJVycnfGUcbtT%2BjIyMjIw9x7Nn8fJSzG0TmFtO8rZT%2BXT3S3ub%2BtKJbbLd5diTVp50%2BzahyeHSslJ%2FYPrU0fuazrZO2CZ92%2FZCCVXlGRiZKPJyPPRxyIFWeXLQBXJBKiq%2F3divEAN6ZwM200Qjm7EJBZeWm%2FPRWVCbYK7s7u2l4XaCz%2BlzgOfMfhMonXr7TWzeZb98dbgIzBT8Ub8eYYUqfZ4rVJ%2FMDbIDgPqTulJ%2FxvntWAtjIisqnwxOkGz0n077FARoY79GdA6HPE4rOy196NiMWHTZlSSApcOgXpy%2FfHV2joaNKu3ffsAnRcBf4K%2F6NcIG6tIxk3HyoXPjASqfUgXbYN5PzpL2njkR9QMjeDTVHDTCgRuxOegjoO0FvKzP%2Ft%2FgmVdI24%2BG7NIe8JX6Wv3dDyldMA%2B4YB5wwTygtd%2BdwRqaTqrLb1l73zTSN52CNpnHuQOYPsDblybgxfkXh%2FoVtr%2BN1DEBJdhRJyd%2FBd%2Fq1z%2BcbNrD17iVKyajcnv9arhOkRPgsruuD6DmNPwpDNrLw2CoTgHni4yALr0L29%2BtiKAEIPn868ejx%2F%2F8rpWP3OEOl5On9OwpcQm0MhafP%2Fey8f1uvDNIgGLQG8z4YO99ENgg95etwv4uYJYY8fUGHYH6j6fscHFZMftlAl9i%2B9XL73X3N%2Fn%2BZStOzfVfRvYXhrbdKOpEgVQTg%2FwsDuDD3kwOfQNMTJ5y%2B%2FltUDWLunyxnRF46IqlBzGMY4X7inggREFioIyMjIyMHWCIB6ZNKAcXseo3vLTQTkVE7348dlwJJSz0%2BwLfmi8BhZqfw3D4ww%2FwHVLnEd5%2FfgYvXsDZ3MlsvYUbbnDjDZ3MN3TJG4%2BbxjAaDl8TBri9qxEw1ccao2wTNAMLHo2f%2BsjrXwb%2F9qHoYqgPMBXJTVfOpmrZH23y6uvo0LHSyY6fHGwKfHJlAuMFvObjDYrIqxBgQi20h7Hd%2FnYVLmno%2BeaNUm%2FeeH2GCuopntnhBJAlI2AHo9CCh1I1QxUdAbqqGY9BBLwyc3W4wYVhvY8A4BoIc1l5M7vnPWphZW9%2FSes3n37y9a0uGqFwFQZsQQbd386DogpgEk%2BdzynsAZMJXq8%2Bns9NeukJ0PYrNATGGefJQlhkLo7DTXr%2By3bNiOsDvrXTz%2FC2q1DXZH84iRNwrP88Nj%2Bu2DjYEE6RBxD9Knj16ujVHC67A7422o02RwD3gB%2Bt7EblWvu9geOFxSnd3ROmT%2BnJyQkhoPlsxVONc%2F3TEdBos%2BjtA%2BZzcwHgTvD1cDjaYCcItA8w9i88A8b%2BmqSjc6Pvqd998QguEQPmQMeo23ODN86%2Bp0%2Fbn1buBkT6%2BoBhNZ%2FPYY4ZAHYb3PRd4LkZmPX68NRtMZn4ASvdA%2Bqf0jMA5MP9eeg28Nug9QiLnj5A33U1MAES6xHAUNpz%2F9zFAYE1gqQDMT3G6xI9pwdw%2FaIgKoHCS1YGlRnSq9yCjdXjgN3j%2BN27YyROHxmuNAeNKPpYuXIyIyMjYy0M8eros59MF%2FPT2c602T7eA7zvhJ9dr%2FvzDjXaLp4Yc5%2B0wllzxzHv3gdmMMM7%2FCcQzKgVBqYTmFn%2BZ%2BmKm8J7k0A5F%2FjgCfjQ1WBhQyiOqD0lYuqBb%2BAyzMw9Ha2G3m6c8qQx%2BAlqnIceQp%2BSb6i9UyQWbhr54%2BAjnZ0VzW2TAN0DmBT6PWmc6jDBE2PK2u%2BnF43dyP7Q0t1pOcX2fdRvH0mF2Q4JqN35rnHjVIeaXfIAVyUuw%2FaHCCiJy9iF5l1621zweI8KZrPZ9iJdb7DXJ3US0OSrtZ10imt7wHY7QesAzUMz1oZ3noB3qFJ%2FH18j97FYuw8QDN4oeKf30osvcSW2ExLo%2BVcbuAuo%2FsUIm8fMG9xocO3Ea19J9gFYivnHJ2KnyfovZlgW3v6ySx32abQiIyMjIyPjhlFDTLxpwIgFMnTp6A3g4IDKNY%2BstkwAMAoIAbasxBXqUWneSAWTMjt50lTqT29rFjvXohjsDNm2YPXDFlICmrJOZ3t6tHm8AiEAl0sCeLIIorIRt%2BcFbew%2FQRsoAXb4o1XSfoywzm0FTMAoYBNvLyFu8v8HpLBtD1iKgC17wHb7AI6d9wFbvguAIGTHd4E9wG7jgIyMjIyM%2B434c2R3HeV%2FFfx6jtZu6ijl8h59T655jhR%2BrdHzDOP6beABCheb8O8%2FWFXeOyzgf5oAhVYnKxP7CwaAf1afJu8bSrhS6tdaXeGnrRenOqOlz9d6QwYnA%2F3TLd%2BGE7qe3chA5YF5DfY0vK3adfOX%2FgyNp2BW25MHdxAB9qvRiiP3%2FXpQQFGYDU4%2BMi%2F%2F%2FXumXG8pjvaUAOsBGlf4jJt%2BYYEzeEzAdw06F19R3juM7D1wita86GR0CKfDHgLuXCc4Bri6vMLdfjMc4VNSUNsdodo2xu%2F1%2BXl%2FK5%2Baz8jIyMhYG%2Fz5gJTMF1GtKq%2Fa3rpyCvz5gJTMl9GtKq%2Fa3rpyCmfQ4WwZmS%2BkXFVetb115ST48wEf%2FAGcfG1iw%2BtWbpbS2vJ3nQxcVr3lH3z5h972FUTLzYpOVk7l5hD%2BeYcYwDcAnewOotrZ4OtrPDucqi%2FLRX0%2FRR4qx7Nn4U8g%2BqjffvuN6Gf%2BnC85vwauHjaYyubqvWYKY4VEfSUMitdnBCT1Ue63R5439m%2BOgCn6DroAAaHPVQxKth%2FwkJgHmG8bmQMsT0D6EjDfvhVRKO3ywOQUgRA7nmL1uawZmHf1k%2BDPBwQ6NdcJ%2Bk6Md1LA5f5ONdhJ8vZ5J0vLHT99srkGOjmJbd%2FG1r2Nriqnse1AZt1AalU5jW2HsuuG0qvKGRkZGRkZGRG0gcONyXsP9v8D0%2FIdJADiBNiXl3327WRGgOL%2F9HC%2F0XwlIURkRhC4tz6Z%2Ffu7fUf2gHvfB9z3u0BGRkZGRkbGplHcnkgguQoSqtUXuhbs%2FwPtMwqV0HUJAvj5vk32b8IDuL23yn7qAXZ5u32hbRX7d3o82Df1FZXvbh9QOfhyxldr%2F%2B3xgXU9oKmvsHyr7F%2FXA269%2FeveBXrsv7N9QALe%2FtvjA0kPWAXGbvebkbHn%2BD%2FJ5nMcHzx1UAAAAABJRU5ErkJggg%3D%3D)}.ui-state-active .ui-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAADwCAMAAADYSUr5AAAA7VBMVEVERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkRERkQfbf86AAAATnRSTlMAGBAyBAhQv4OZLiJUcEBmYBoSzQwgPBZCSEoeWiYwUiyFNIeBw2rJz8c4RBy9uXyrtaWNqa2zKP2fJO8KBgKPo2KVoa9s351GPm5%2BkWho0kj9AAAPhUlEQVR4nO1djWLbthEGyUiq5YSSLXtp7FpLOmfzkmxr126tmi2p03RJ1%2FXe%2F3EGgARxPyAgRbIk2%2FhkSz4CJO4%2BHsE7AJSVysjI2AMUUOxahZ2iANhzBtZWr4BoIRSYAVN5u4QwDwQDRbcwfUi5KS3wFuDmFnQLa4Dtb%2F%2FcqktwD5QEFFwfUs7PoCCA7y4bEJVFizcIob8KmhAplwwqVjt%2B9FBl3uINQniwEiryEyw9JHqGpQdEFNi%2BB4QQ7QOiHhysIPoAxUqxvdvvA9K42bsAv4S2fxfYOe57IJSRkZGRkZGxx7jxSHDHcRBXQMTyIjInBgHwBJ%2FbEx8PEANC%2BuhbpSSggCBAVODVabpI1S%2Fk4WLZpTn6NpMhoX9Y40hxYERFpMcqUs4AloCtDQdID1YhnyXZ2hLjAYWiO9Dy1PDB7tPhIqLx%2BuMB8grZaR%2BQxl2%2FC2RkZGRkZGRk7A7rBf7J0DR5%2FLUTjzUPIPSPGvQJiVJiB7kcQCiUOJrcFNtDZIf2xarQ3aGvLNxAVIFAabz90BFiBIlycTBhgWwOWCH0FLYHlPqwHaCvcIn2ZbosCevfPTRiFFcgvHukCjWwrc3GrGh1fsAof8EaUReKXkCB4%2FMzFNo97qLpFiKFYv%2FkNR5YQxQbQEofkZ2OuEOHqqT6gFTpru8CN7x%2F%2BjaZkZGRkZGRcV%2Bx%2FrLUNcMMqUAscgnFocmpqkTzqymwVAPxfJ5PnIUUQOUKT04tEdWZyv3JCQSn96WS4pD97QfyW25A7NhSAbyhmVj0FEltA4vdiygBibXhoUYgykCUP7HwPTDeEqAIcHVMkZg7Zx4k0uFANs63hPQXCoRLAwdgGsr9Az7Qv7sgQGgg1aPl%2FBJLExBWgG4RFRLFImGmIquPC%2FklEGyCG0AuAXaJJC%2BB8FVe9NYQDEcXB8g6AQcjYJ1goJIggHWCrFR0S6kRHN5%2B4BzFi8NaoN35NRxUvL%2BJJdZr7PV4wK6fj8nIyMjIyNhr3OxdXAYq7FHZwB6bDSzSh4sF0utChqo0NAvaT1hLzXwFinmCzmeDucEQK18TTaQoFgP7bNC%2BRZ4OT4T6gQogDFYk%2B1QxQlj19QGSAWKiLYp8P0Ag1Gbz1ULfWHLg9iUnQNK5QQJcukm04blKLH2GgEJCY%2BHzXAZWCvHKco3Bp6MIaCjSXXRJyOxeqhnzEaF93MfFGW%2FO16ZvDL5TM4MJIjujz%2FcHypkQuuzRwWJ93BKdIt%2BwCRAPl9kpe2Ikkb2mFgGlxh%2Fi40d3EHfdvoyMjIyMu43ylt%2FIAmGHnN5iIt7wKfbv01RAcJqFRl9lcjYQSnbQqKgC4fYOwSJt6N6trE0twZ9kN%2FPqNpTQeICvr4TLsDYC06U7BMjshS%2Bv1%2FaT7IwQYD5LcgRQXMT2FrBfBLjZ6151jDElk9tPFfpUgk2yregusX25BJbwAFEfM%2BYI6vGAti4bTtizB%2BTjfQCrERyhKb2X8D6A9wX75P4t4neBYJeP6pdhg%2FgQl8MWvytzeSTjgOQBynQdh%2FiXKdxOrGJ%2FRkZGRsb9QmXihGr5%2Bg8GGg9uTh%2BKoVZuNIzV%2BCwRucFBEyr1mVjx4irOxwM1BhirB6Q%2B2eNQi4eqR%2BaF6mELtoMzCR7V9RAFe%2FZvQogNiyY8FPSUTFsLp8TeTmMui5mtw7bcaT0Yw2AA4wFRQIlkgq%2B1DQrNhkmoxS5Jq%2Bu6bMAIGRECEANgXHTgWzwgBOhDH2l0oTQ4D8D5NMktBgNywAEMjo8rwATMZrPY7JGxBoJCkIBDQiAY09EGTUiBCWkUpISfGPR5AAwBfZiG2z7Ayc1yeKTxid39xBNwfHr4O0LA48ePFTvhYrF1r4tyAoz9n2MCqEuBtp%2F6GDR0oAYfG%2FR6wJExHYZHfhygsv7fEWCOj4bYmsP5A%2BpL4MkTfAnMlD4F%2Br3bobKvTyTA2P%2Fw7PN%2BAgq2QW8piqMCpTBwenoKvX0AHGkGtP2YAPvTEWA7QUTAudn7%2FNxtOG46wWNmDtpBEkBzN7rBEvAFHp%2BYTB%2Fq97qPAN4gHFqgBi8uLsC7qPCA6mg41G%2F%2BErByPwEXDdoNxRhOx%2BM5jPEzQugS0ht%2Bb1%2FY3gEnYMAIAOIBE29%2FhIDucE8tmMsNOgK4B1RHFu4UCRlMHzv0xzcajcfdXWDs2h8TArBCkoDUJYDLmz6w7ip3BFS0ve5wTRwAn6keMA9I3QYbfSZ0DKbyt%2B7OXjGI1idPcfNyAyfAMlCrzaGqphYrxHocLHRJVycnfGUcbtT%2BjIyMjIw9x7Nn8fJSzG0TmFtO8rZT%2BXT3S3ub%2BtKJbbLd5diTVp50%2BzahyeHSslJ%2FYPrU0fuazrZO2CZ92%2FZCCVXlGRiZKPJyPPRxyIFWeXLQBXJBKiq%2F3divEAN6ZwM200Qjm7EJBZeWm%2FPRWVCbYK7s7u2l4XaCz%2BlzgOfMfhMonXr7TWzeZb98dbgIzBT8Ub8eYYUqfZ4rVJ%2FMDbIDgPqTulJ%2FxvntWAtjIisqnwxOkGz0n077FARoY79GdA6HPE4rOy196NiMWHTZlSSApcOgXpy%2FfHV2joaNKu3ffsAnRcBf4K%2F6NcIG6tIxk3HyoXPjASqfUgXbYN5PzpL2njkR9QMjeDTVHDTCgRuxOegjoO0FvKzP%2Ft%2FgmVdI24%2BG7NIe8JX6Wv3dDyldMA%2B4YB5wwTygtd%2BdwRqaTqrLb1l73zTSN52CNpnHuQOYPsDblybgxfkXh%2FoVtr%2BN1DEBJdhRJyd%2FBd%2Fq1z%2BcbNrD17iVKyajcnv9arhOkRPgsruuD6DmNPwpDNrLw2CoTgHni4yALr0L29%2BtiKAEIPn868ejx%2F%2F8rpWP3OEOl5On9OwpcQm0MhafP%2Fey8f1uvDNIgGLQG8z4YO99ENgg95etwv4uYJYY8fUGHYH6j6fscHFZMftlAl9i%2B9XL73X3N%2Fn%2BZStOzfVfRvYXhrbdKOpEgVQTg%2FwsDuDD3kwOfQNMTJ5y%2B%2FltUDWLunyxnRF46IqlBzGMY4X7inggREFioIyMjIyMHWCIB6ZNKAcXseo3vLTQTkVE7348dlwJJSz0%2BwLfmi8BhZqfw3D4ww%2FwHVLnEd5%2FfgYvXsDZ3MlsvYUbbnDjDZ3MN3TJG4%2BbxjAaDl8TBri9qxEw1ccao2wTNAMLHo2f%2BsjrXwb%2F9qHoYqgPMBXJTVfOpmrZH23y6uvo0LHSyY6fHGwKfHJlAuMFvObjDYrIqxBgQi20h7Hd%2FnYVLmno%2BeaNUm%2FeeH2GCuopntnhBJAlI2AHo9CCh1I1QxUdAbqqGY9BBLwyc3W4wYVhvY8A4BoIc1l5M7vnPWphZW9%2FSes3n37y9a0uGqFwFQZsQQbd386DogpgEk%2BdzynsAZMJXq8%2Bns9NeukJ0PYrNATGGefJQlhkLo7DTXr%2By3bNiOsDvrXTz%2FC2q1DXZH84iRNwrP88Nj%2Bu2DjYEE6RBxD9Knj16ujVHC67A7422o02RwD3gB%2Bt7EblWvu9geOFxSnd3ROmT%2BnJyQkhoPlsxVONc%2F3TEdBos%2BjtA%2BZzcwHgTvD1cDjaYCcItA8w9i88A8b%2BmqSjc6Pvqd998QguEQPmQMeo23ODN86%2Bp0%2Fbn1buBkT6%2BoBhNZ%2FPYY4ZAHYb3PRd4LkZmPX68NRtMZn4ASvdA%2Bqf0jMA5MP9eeg28Nug9QiLnj5A33U1MAES6xHAUNpz%2F9zFAYE1gqQDMT3G6xI9pwdw%2FaIgKoHCS1YGlRnSq9yCjdXjgN3j%2BN27YyROHxmuNAeNKPpYuXIyIyMjYy0M8eros59MF%2FPT2c602T7eA7zvhJ9dr%2FvzDjXaLp4Yc5%2B0wllzxzHv3gdmMMM7%2FCcQzKgVBqYTmFn%2BZ%2BmKm8J7k0A5F%2FjgCfjQ1WBhQyiOqD0lYuqBb%2BAyzMw9Ha2G3m6c8qQx%2BAlqnIceQp%2BSb6i9UyQWbhr54%2BAjnZ0VzW2TAN0DmBT6PWmc6jDBE2PK2u%2BnF43dyP7Q0t1pOcX2fdRvH0mF2Q4JqN35rnHjVIeaXfIAVyUuw%2FaHCCiJy9iF5l1621zweI8KZrPZ9iJdb7DXJ3US0OSrtZ10imt7wHY7QesAzUMz1oZ3noB3qFJ%2FH18j97FYuw8QDN4oeKf30osvcSW2ExLo%2BVcbuAuo%2FsUIm8fMG9xocO3Ea19J9gFYivnHJ2KnyfovZlgW3v6ySx32abQiIyMjIyPjhlFDTLxpwIgFMnTp6A3g4IDKNY%2BstkwAMAoIAbasxBXqUWneSAWTMjt50lTqT29rFjvXohjsDNm2YPXDFlICmrJOZ3t6tHm8AiEAl0sCeLIIorIRt%2BcFbew%2FQRsoAXb4o1XSfoywzm0FTMAoYBNvLyFu8v8HpLBtD1iKgC17wHb7AI6d9wFbvguAIGTHd4E9wG7jgIyMjIyM%2B434c2R3HeV%2FFfx6jtZu6ijl8h59T655jhR%2BrdHzDOP6beABCheb8O8%2FWFXeOyzgf5oAhVYnKxP7CwaAf1afJu8bSrhS6tdaXeGnrRenOqOlz9d6QwYnA%2F3TLd%2BGE7qe3chA5YF5DfY0vK3adfOX%2FgyNp2BW25MHdxAB9qvRiiP3%2FXpQQFGYDU4%2BMi%2F%2F%2FXumXG8pjvaUAOsBGlf4jJt%2BYYEzeEzAdw06F19R3juM7D1wita86GR0CKfDHgLuXCc4Bri6vMLdfjMc4VNSUNsdodo2xu%2F1%2BXl%2FK5%2Baz8jIyMhYG%2Fz5gJTMF1GtKq%2Fa3rpyCvz5gJTMl9GtKq%2Fa3rpyCmfQ4WwZmS%2BkXFVetb115ST48wEf%2FAGcfG1iw%2BtWbpbS2vJ3nQxcVr3lH3z5h972FUTLzYpOVk7l5hD%2BeYcYwDcAnewOotrZ4OtrPDucqi%2FLRX0%2FRR4qx7Nn4U8g%2BqjffvuN6Gf%2BnC85vwauHjaYyubqvWYKY4VEfSUMitdnBCT1Ue63R5439m%2BOgCn6DroAAaHPVQxKth%2FwkJgHmG8bmQMsT0D6EjDfvhVRKO3ywOQUgRA7nmL1uawZmHf1k%2BDPBwQ6NdcJ%2Bk6Md1LA5f5ONdhJ8vZ5J0vLHT99srkGOjmJbd%2FG1r2Nriqnse1AZt1AalU5jW2HsuuG0qvKGRkZGRkZGRG0gcONyXsP9v8D0%2FIdJADiBNiXl3327WRGgOL%2F9HC%2F0XwlIURkRhC4tz6Z%2Ffu7fUf2gHvfB9z3u0BGRkZGRkbGplHcnkgguQoSqtUXuhbs%2FwPtMwqV0HUJAvj5vk32b8IDuL23yn7qAXZ5u32hbRX7d3o82Df1FZXvbh9QOfhyxldr%2F%2B3xgXU9oKmvsHyr7F%2FXA269%2FeveBXrsv7N9QALe%2FtvjA0kPWAXGbvebkbHn%2BD%2FJ5nMcHzx1UAAAAABJRU5ErkJggg%3D%3D)}.ui-state-highlight .ui-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAADwCAMAAADYSUr5AAAA7VBMVEUsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvwsgvzLrJBNAAAATnRSTlMAGBAyBAhQv4OZLiJUcEBmYBoSzQwgPBZCSEoeWiYwUiyFNIeBw2rJz8c4RBy9uXyrtaWNqa2zKP2fJO8KBgKPo2KVoa9s351GPm5%2BkWho0kj9AAAPhUlEQVR4nO1djWLbthEGyUiq5YSSLXtp7FpLOmfzkmxr126tmi2p03RJ1%2FXe%2F3EGgARxPyAgRbIk2%2FhkSz4CJO4%2BHsE7AJSVysjI2AMUUOxahZ2iANhzBtZWr4BoIRSYAVN5u4QwDwQDRbcwfUi5KS3wFuDmFnQLa4Dtb%2F%2FcqktwD5QEFFwfUs7PoCCA7y4bEJVFizcIob8KmhAplwwqVjt%2B9FBl3uINQniwEiryEyw9JHqGpQdEFNi%2BB4QQ7QOiHhysIPoAxUqxvdvvA9K42bsAv4S2fxfYOe57IJSRkZGRkZGxx7jxSHDHcRBXQMTyIjInBgHwBJ%2FbEx8PEANC%2BuhbpSSggCBAVODVabpI1S%2Fk4WLZpTn6NpMhoX9Y40hxYERFpMcqUs4AloCtDQdID1YhnyXZ2hLjAYWiO9Dy1PDB7tPhIqLx%2BuMB8grZaR%2BQxl2%2FC2RkZGRkZGRk7A7rBf7J0DR5%2FLUTjzUPIPSPGvQJiVJiB7kcQCiUOJrcFNtDZIf2xarQ3aGvLNxAVIFAabz90BFiBIlycTBhgWwOWCH0FLYHlPqwHaCvcIn2ZbosCevfPTRiFFcgvHukCjWwrc3GrGh1fsAof8EaUReKXkCB4%2FMzFNo97qLpFiKFYv%2FkNR5YQxQbQEofkZ2OuEOHqqT6gFTpru8CN7x%2F%2BjaZkZGRkZGRcV%2Bx%2FrLUNcMMqUAscgnFocmpqkTzqymwVAPxfJ5PnIUUQOUKT04tEdWZyv3JCQSn96WS4pD97QfyW25A7NhSAbyhmVj0FEltA4vdiygBibXhoUYgykCUP7HwPTDeEqAIcHVMkZg7Zx4k0uFANs63hPQXCoRLAwdgGsr9Az7Qv7sgQGgg1aPl%2FBJLExBWgG4RFRLFImGmIquPC%2FklEGyCG0AuAXaJJC%2BB8FVe9NYQDEcXB8g6AQcjYJ1goJIggHWCrFR0S6kRHN5%2B4BzFi8NaoN35NRxUvL%2BJJdZr7PV4wK6fj8nIyMjIyNhr3OxdXAYq7FHZwB6bDSzSh4sF0utChqo0NAvaT1hLzXwFinmCzmeDucEQK18TTaQoFgP7bNC%2BRZ4OT4T6gQogDFYk%2B1QxQlj19QGSAWKiLYp8P0Ag1Gbz1ULfWHLg9iUnQNK5QQJcukm04blKLH2GgEJCY%2BHzXAZWCvHKco3Bp6MIaCjSXXRJyOxeqhnzEaF93MfFGW%2FO16ZvDL5TM4MJIjujz%2FcHypkQuuzRwWJ93BKdIt%2BwCRAPl9kpe2Ikkb2mFgGlxh%2Fi40d3EHfdvoyMjIyMu43ylt%2FIAmGHnN5iIt7wKfbv01RAcJqFRl9lcjYQSnbQqKgC4fYOwSJt6N6trE0twZ9kN%2FPqNpTQeICvr4TLsDYC06U7BMjshS%2Bv1%2FaT7IwQYD5LcgRQXMT2FrBfBLjZ6151jDElk9tPFfpUgk2yregusX25BJbwAFEfM%2BYI6vGAti4bTtizB%2BTjfQCrERyhKb2X8D6A9wX75P4t4neBYJeP6pdhg%2FgQl8MWvytzeSTjgOQBynQdh%2FiXKdxOrGJ%2FRkZGRsb9QmXihGr5%2Bg8GGg9uTh%2BKoVZuNIzV%2BCwRucFBEyr1mVjx4irOxwM1BhirB6Q%2B2eNQi4eqR%2BaF6mELtoMzCR7V9RAFe%2FZvQogNiyY8FPSUTFsLp8TeTmMui5mtw7bcaT0Yw2AA4wFRQIlkgq%2B1DQrNhkmoxS5Jq%2Bu6bMAIGRECEANgXHTgWzwgBOhDH2l0oTQ4D8D5NMktBgNywAEMjo8rwATMZrPY7JGxBoJCkIBDQiAY09EGTUiBCWkUpISfGPR5AAwBfZiG2z7Ayc1yeKTxid39xBNwfHr4O0LA48ePFTvhYrF1r4tyAoz9n2MCqEuBtp%2F6GDR0oAYfG%2FR6wJExHYZHfhygsv7fEWCOj4bYmsP5A%2BpL4MkTfAnMlD4F%2Br3bobKvTyTA2P%2Fw7PN%2BAgq2QW8piqMCpTBwenoKvX0AHGkGtP2YAPvTEWA7QUTAudn7%2FNxtOG46wWNmDtpBEkBzN7rBEvAFHp%2BYTB%2Fq97qPAN4gHFqgBi8uLsC7qPCA6mg41G%2F%2BErByPwEXDdoNxRhOx%2BM5jPEzQugS0ht%2Bb1%2FY3gEnYMAIAOIBE29%2FhIDucE8tmMsNOgK4B1RHFu4UCRlMHzv0xzcajcfdXWDs2h8TArBCkoDUJYDLmz6w7ip3BFS0ve5wTRwAn6keMA9I3QYbfSZ0DKbyt%2B7OXjGI1idPcfNyAyfAMlCrzaGqphYrxHocLHRJVycnfGUcbtT%2BjIyMjIw9x7Nn8fJSzG0TmFtO8rZT%2BXT3S3ub%2BtKJbbLd5diTVp50%2BzahyeHSslJ%2FYPrU0fuazrZO2CZ92%2FZCCVXlGRiZKPJyPPRxyIFWeXLQBXJBKiq%2F3divEAN6ZwM200Qjm7EJBZeWm%2FPRWVCbYK7s7u2l4XaCz%2BlzgOfMfhMonXr7TWzeZb98dbgIzBT8Ub8eYYUqfZ4rVJ%2FMDbIDgPqTulJ%2FxvntWAtjIisqnwxOkGz0n077FARoY79GdA6HPE4rOy196NiMWHTZlSSApcOgXpy%2FfHV2joaNKu3ffsAnRcBf4K%2F6NcIG6tIxk3HyoXPjASqfUgXbYN5PzpL2njkR9QMjeDTVHDTCgRuxOegjoO0FvKzP%2Ft%2FgmVdI24%2BG7NIe8JX6Wv3dDyldMA%2B4YB5wwTygtd%2BdwRqaTqrLb1l73zTSN52CNpnHuQOYPsDblybgxfkXh%2FoVtr%2BN1DEBJdhRJyd%2FBd%2Fq1z%2BcbNrD17iVKyajcnv9arhOkRPgsruuD6DmNPwpDNrLw2CoTgHni4yALr0L29%2BtiKAEIPn868ejx%2F%2F8rpWP3OEOl5On9OwpcQm0MhafP%2Fey8f1uvDNIgGLQG8z4YO99ENgg95etwv4uYJYY8fUGHYH6j6fscHFZMftlAl9i%2B9XL73X3N%2Fn%2BZStOzfVfRvYXhrbdKOpEgVQTg%2FwsDuDD3kwOfQNMTJ5y%2B%2FltUDWLunyxnRF46IqlBzGMY4X7inggREFioIyMjIyMHWCIB6ZNKAcXseo3vLTQTkVE7348dlwJJSz0%2BwLfmi8BhZqfw3D4ww%2FwHVLnEd5%2FfgYvXsDZ3MlsvYUbbnDjDZ3MN3TJG4%2BbxjAaDl8TBri9qxEw1ccao2wTNAMLHo2f%2BsjrXwb%2F9qHoYqgPMBXJTVfOpmrZH23y6uvo0LHSyY6fHGwKfHJlAuMFvObjDYrIqxBgQi20h7Hd%2FnYVLmno%2BeaNUm%2FeeH2GCuopntnhBJAlI2AHo9CCh1I1QxUdAbqqGY9BBLwyc3W4wYVhvY8A4BoIc1l5M7vnPWphZW9%2FSes3n37y9a0uGqFwFQZsQQbd386DogpgEk%2BdzynsAZMJXq8%2Bns9NeukJ0PYrNATGGefJQlhkLo7DTXr%2By3bNiOsDvrXTz%2FC2q1DXZH84iRNwrP88Nj%2Bu2DjYEE6RBxD9Knj16ujVHC67A7422o02RwD3gB%2Bt7EblWvu9geOFxSnd3ROmT%2BnJyQkhoPlsxVONc%2F3TEdBos%2BjtA%2BZzcwHgTvD1cDjaYCcItA8w9i88A8b%2BmqSjc6Pvqd998QguEQPmQMeo23ODN86%2Bp0%2Fbn1buBkT6%2BoBhNZ%2FPYY4ZAHYb3PRd4LkZmPX68NRtMZn4ASvdA%2Bqf0jMA5MP9eeg28Nug9QiLnj5A33U1MAES6xHAUNpz%2F9zFAYE1gqQDMT3G6xI9pwdw%2FaIgKoHCS1YGlRnSq9yCjdXjgN3j%2BN27YyROHxmuNAeNKPpYuXIyIyMjYy0M8eros59MF%2FPT2c602T7eA7zvhJ9dr%2FvzDjXaLp4Yc5%2B0wllzxzHv3gdmMMM7%2FCcQzKgVBqYTmFn%2BZ%2BmKm8J7k0A5F%2FjgCfjQ1WBhQyiOqD0lYuqBb%2BAyzMw9Ha2G3m6c8qQx%2BAlqnIceQp%2BSb6i9UyQWbhr54%2BAjnZ0VzW2TAN0DmBT6PWmc6jDBE2PK2u%2BnF43dyP7Q0t1pOcX2fdRvH0mF2Q4JqN35rnHjVIeaXfIAVyUuw%2FaHCCiJy9iF5l1621zweI8KZrPZ9iJdb7DXJ3US0OSrtZ10imt7wHY7QesAzUMz1oZ3noB3qFJ%2FH18j97FYuw8QDN4oeKf30osvcSW2ExLo%2BVcbuAuo%2FsUIm8fMG9xocO3Ea19J9gFYivnHJ2KnyfovZlgW3v6ySx32abQiIyMjIyPjhlFDTLxpwIgFMnTp6A3g4IDKNY%2BstkwAMAoIAbasxBXqUWneSAWTMjt50lTqT29rFjvXohjsDNm2YPXDFlICmrJOZ3t6tHm8AiEAl0sCeLIIorIRt%2BcFbew%2FQRsoAXb4o1XSfoywzm0FTMAoYBNvLyFu8v8HpLBtD1iKgC17wHb7AI6d9wFbvguAIGTHd4E9wG7jgIyMjIyM%2B434c2R3HeV%2FFfx6jtZu6ijl8h59T655jhR%2BrdHzDOP6beABCheb8O8%2FWFXeOyzgf5oAhVYnKxP7CwaAf1afJu8bSrhS6tdaXeGnrRenOqOlz9d6QwYnA%2F3TLd%2BGE7qe3chA5YF5DfY0vK3adfOX%2FgyNp2BW25MHdxAB9qvRiiP3%2FXpQQFGYDU4%2BMi%2F%2F%2FXumXG8pjvaUAOsBGlf4jJt%2BYYEzeEzAdw06F19R3juM7D1wita86GR0CKfDHgLuXCc4Bri6vMLdfjMc4VNSUNsdodo2xu%2F1%2BXl%2FK5%2Baz8jIyMhYG%2Fz5gJTMF1GtKq%2Fa3rpyCvz5gJTMl9GtKq%2Fa3rpyCmfQ4WwZmS%2BkXFVetb115ST48wEf%2FAGcfG1iw%2BtWbpbS2vJ3nQxcVr3lH3z5h972FUTLzYpOVk7l5hD%2BeYcYwDcAnewOotrZ4OtrPDucqi%2FLRX0%2FRR4qx7Nn4U8g%2BqjffvuN6Gf%2BnC85vwauHjaYyubqvWYKY4VEfSUMitdnBCT1Ue63R5439m%2BOgCn6DroAAaHPVQxKth%2FwkJgHmG8bmQMsT0D6EjDfvhVRKO3ywOQUgRA7nmL1uawZmHf1k%2BDPBwQ6NdcJ%2Bk6Md1LA5f5ONdhJ8vZ5J0vLHT99srkGOjmJbd%2FG1r2Nriqnse1AZt1AalU5jW2HsuuG0qvKGRkZGRkZGRG0gcONyXsP9v8D0%2FIdJADiBNiXl3327WRGgOL%2F9HC%2F0XwlIURkRhC4tz6Z%2Ffu7fUf2gHvfB9z3u0BGRkZGRkbGplHcnkgguQoSqtUXuhbs%2FwPtMwqV0HUJAvj5vk32b8IDuL23yn7qAXZ5u32hbRX7d3o82Df1FZXvbh9QOfhyxldr%2F%2B3xgXU9oKmvsHyr7F%2FXA269%2FeveBXrsv7N9QALe%2FtvjA0kPWAXGbvebkbHn%2BD%2FJ5nMcHzx1UAAAAABJRU5ErkJggg%3D%3D)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAADwCAMAAADYSUr5AAAA7VBMVEXMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzMCgzrDkZjAAAATnRSTlMAGBAyBAhQv4OZLiJUcEBmYBoSzQwgPBZCSEoeWiYwUiyFNIeBw2rJz8c4RBy9uXyrtaWNqa2zKP2fJO8KBgKPo2KVoa9s351GPm5%2BkWho0kj9AAAPhUlEQVR4nO1djWLbthEGyUiq5YSSLXtp7FpLOmfzkmxr126tmi2p03RJ1%2FXe%2F3EGgARxPyAgRbIk2%2FhkSz4CJO4%2BHsE7AJSVysjI2AMUUOxahZ2iANhzBtZWr4BoIRSYAVN5u4QwDwQDRbcwfUi5KS3wFuDmFnQLa4Dtb%2F%2FcqktwD5QEFFwfUs7PoCCA7y4bEJVFizcIob8KmhAplwwqVjt%2B9FBl3uINQniwEiryEyw9JHqGpQdEFNi%2BB4QQ7QOiHhysIPoAxUqxvdvvA9K42bsAv4S2fxfYOe57IJSRkZGRkZGxx7jxSHDHcRBXQMTyIjInBgHwBJ%2FbEx8PEANC%2BuhbpSSggCBAVODVabpI1S%2Fk4WLZpTn6NpMhoX9Y40hxYERFpMcqUs4AloCtDQdID1YhnyXZ2hLjAYWiO9Dy1PDB7tPhIqLx%2BuMB8grZaR%2BQxl2%2FC2RkZGRkZGRk7A7rBf7J0DR5%2FLUTjzUPIPSPGvQJiVJiB7kcQCiUOJrcFNtDZIf2xarQ3aGvLNxAVIFAabz90BFiBIlycTBhgWwOWCH0FLYHlPqwHaCvcIn2ZbosCevfPTRiFFcgvHukCjWwrc3GrGh1fsAof8EaUReKXkCB4%2FMzFNo97qLpFiKFYv%2FkNR5YQxQbQEofkZ2OuEOHqqT6gFTpru8CN7x%2F%2BjaZkZGRkZGRcV%2Bx%2FrLUNcMMqUAscgnFocmpqkTzqymwVAPxfJ5PnIUUQOUKT04tEdWZyv3JCQSn96WS4pD97QfyW25A7NhSAbyhmVj0FEltA4vdiygBibXhoUYgykCUP7HwPTDeEqAIcHVMkZg7Zx4k0uFANs63hPQXCoRLAwdgGsr9Az7Qv7sgQGgg1aPl%2FBJLExBWgG4RFRLFImGmIquPC%2FklEGyCG0AuAXaJJC%2BB8FVe9NYQDEcXB8g6AQcjYJ1goJIggHWCrFR0S6kRHN5%2B4BzFi8NaoN35NRxUvL%2BJJdZr7PV4wK6fj8nIyMjIyNhr3OxdXAYq7FHZwB6bDSzSh4sF0utChqo0NAvaT1hLzXwFinmCzmeDucEQK18TTaQoFgP7bNC%2BRZ4OT4T6gQogDFYk%2B1QxQlj19QGSAWKiLYp8P0Ag1Gbz1ULfWHLg9iUnQNK5QQJcukm04blKLH2GgEJCY%2BHzXAZWCvHKco3Bp6MIaCjSXXRJyOxeqhnzEaF93MfFGW%2FO16ZvDL5TM4MJIjujz%2FcHypkQuuzRwWJ93BKdIt%2BwCRAPl9kpe2Ikkb2mFgGlxh%2Fi40d3EHfdvoyMjIyMu43ylt%2FIAmGHnN5iIt7wKfbv01RAcJqFRl9lcjYQSnbQqKgC4fYOwSJt6N6trE0twZ9kN%2FPqNpTQeICvr4TLsDYC06U7BMjshS%2Bv1%2FaT7IwQYD5LcgRQXMT2FrBfBLjZ6151jDElk9tPFfpUgk2yregusX25BJbwAFEfM%2BYI6vGAti4bTtizB%2BTjfQCrERyhKb2X8D6A9wX75P4t4neBYJeP6pdhg%2FgQl8MWvytzeSTjgOQBynQdh%2FiXKdxOrGJ%2FRkZGRsb9QmXihGr5%2Bg8GGg9uTh%2BKoVZuNIzV%2BCwRucFBEyr1mVjx4irOxwM1BhirB6Q%2B2eNQi4eqR%2BaF6mELtoMzCR7V9RAFe%2FZvQogNiyY8FPSUTFsLp8TeTmMui5mtw7bcaT0Yw2AA4wFRQIlkgq%2B1DQrNhkmoxS5Jq%2Bu6bMAIGRECEANgXHTgWzwgBOhDH2l0oTQ4D8D5NMktBgNywAEMjo8rwATMZrPY7JGxBoJCkIBDQiAY09EGTUiBCWkUpISfGPR5AAwBfZiG2z7Ayc1yeKTxid39xBNwfHr4O0LA48ePFTvhYrF1r4tyAoz9n2MCqEuBtp%2F6GDR0oAYfG%2FR6wJExHYZHfhygsv7fEWCOj4bYmsP5A%2BpL4MkTfAnMlD4F%2Br3bobKvTyTA2P%2Fw7PN%2BAgq2QW8piqMCpTBwenoKvX0AHGkGtP2YAPvTEWA7QUTAudn7%2FNxtOG46wWNmDtpBEkBzN7rBEvAFHp%2BYTB%2Fq97qPAN4gHFqgBi8uLsC7qPCA6mg41G%2F%2BErByPwEXDdoNxRhOx%2BM5jPEzQugS0ht%2Bb1%2FY3gEnYMAIAOIBE29%2FhIDucE8tmMsNOgK4B1RHFu4UCRlMHzv0xzcajcfdXWDs2h8TArBCkoDUJYDLmz6w7ip3BFS0ve5wTRwAn6keMA9I3QYbfSZ0DKbyt%2B7OXjGI1idPcfNyAyfAMlCrzaGqphYrxHocLHRJVycnfGUcbtT%2BjIyMjIw9x7Nn8fJSzG0TmFtO8rZT%2BXT3S3ub%2BtKJbbLd5diTVp50%2BzahyeHSslJ%2FYPrU0fuazrZO2CZ92%2FZCCVXlGRiZKPJyPPRxyIFWeXLQBXJBKiq%2F3divEAN6ZwM200Qjm7EJBZeWm%2FPRWVCbYK7s7u2l4XaCz%2BlzgOfMfhMonXr7TWzeZb98dbgIzBT8Ub8eYYUqfZ4rVJ%2FMDbIDgPqTulJ%2FxvntWAtjIisqnwxOkGz0n077FARoY79GdA6HPE4rOy196NiMWHTZlSSApcOgXpy%2FfHV2joaNKu3ffsAnRcBf4K%2F6NcIG6tIxk3HyoXPjASqfUgXbYN5PzpL2njkR9QMjeDTVHDTCgRuxOegjoO0FvKzP%2Ft%2FgmVdI24%2BG7NIe8JX6Wv3dDyldMA%2B4YB5wwTygtd%2BdwRqaTqrLb1l73zTSN52CNpnHuQOYPsDblybgxfkXh%2FoVtr%2BN1DEBJdhRJyd%2FBd%2Fq1z%2BcbNrD17iVKyajcnv9arhOkRPgsruuD6DmNPwpDNrLw2CoTgHni4yALr0L29%2BtiKAEIPn868ejx%2F%2F8rpWP3OEOl5On9OwpcQm0MhafP%2Fey8f1uvDNIgGLQG8z4YO99ENgg95etwv4uYJYY8fUGHYH6j6fscHFZMftlAl9i%2B9XL73X3N%2Fn%2BZStOzfVfRvYXhrbdKOpEgVQTg%2FwsDuDD3kwOfQNMTJ5y%2B%2FltUDWLunyxnRF46IqlBzGMY4X7inggREFioIyMjIyMHWCIB6ZNKAcXseo3vLTQTkVE7348dlwJJSz0%2BwLfmi8BhZqfw3D4ww%2FwHVLnEd5%2FfgYvXsDZ3MlsvYUbbnDjDZ3MN3TJG4%2BbxjAaDl8TBri9qxEw1ccao2wTNAMLHo2f%2BsjrXwb%2F9qHoYqgPMBXJTVfOpmrZH23y6uvo0LHSyY6fHGwKfHJlAuMFvObjDYrIqxBgQi20h7Hd%2FnYVLmno%2BeaNUm%2FeeH2GCuopntnhBJAlI2AHo9CCh1I1QxUdAbqqGY9BBLwyc3W4wYVhvY8A4BoIc1l5M7vnPWphZW9%2FSes3n37y9a0uGqFwFQZsQQbd386DogpgEk%2BdzynsAZMJXq8%2Bns9NeukJ0PYrNATGGefJQlhkLo7DTXr%2By3bNiOsDvrXTz%2FC2q1DXZH84iRNwrP88Nj%2Bu2DjYEE6RBxD9Knj16ujVHC67A7422o02RwD3gB%2Bt7EblWvu9geOFxSnd3ROmT%2BnJyQkhoPlsxVONc%2F3TEdBos%2BjtA%2BZzcwHgTvD1cDjaYCcItA8w9i88A8b%2BmqSjc6Pvqd998QguEQPmQMeo23ODN86%2Bp0%2Fbn1buBkT6%2BoBhNZ%2FPYY4ZAHYb3PRd4LkZmPX68NRtMZn4ASvdA%2Bqf0jMA5MP9eeg28Nug9QiLnj5A33U1MAES6xHAUNpz%2F9zFAYE1gqQDMT3G6xI9pwdw%2FaIgKoHCS1YGlRnSq9yCjdXjgN3j%2BN27YyROHxmuNAeNKPpYuXIyIyMjYy0M8eros59MF%2FPT2c602T7eA7zvhJ9dr%2FvzDjXaLp4Yc5%2B0wllzxzHv3gdmMMM7%2FCcQzKgVBqYTmFn%2BZ%2BmKm8J7k0A5F%2FjgCfjQ1WBhQyiOqD0lYuqBb%2BAyzMw9Ha2G3m6c8qQx%2BAlqnIceQp%2BSb6i9UyQWbhr54%2BAjnZ0VzW2TAN0DmBT6PWmc6jDBE2PK2u%2BnF43dyP7Q0t1pOcX2fdRvH0mF2Q4JqN35rnHjVIeaXfIAVyUuw%2FaHCCiJy9iF5l1621zweI8KZrPZ9iJdb7DXJ3US0OSrtZ10imt7wHY7QesAzUMz1oZ3noB3qFJ%2FH18j97FYuw8QDN4oeKf30osvcSW2ExLo%2BVcbuAuo%2FsUIm8fMG9xocO3Ea19J9gFYivnHJ2KnyfovZlgW3v6ySx32abQiIyMjIyPjhlFDTLxpwIgFMnTp6A3g4IDKNY%2BstkwAMAoIAbasxBXqUWneSAWTMjt50lTqT29rFjvXohjsDNm2YPXDFlICmrJOZ3t6tHm8AiEAl0sCeLIIorIRt%2BcFbew%2FQRsoAXb4o1XSfoywzm0FTMAoYBNvLyFu8v8HpLBtD1iKgC17wHb7AI6d9wFbvguAIGTHd4E9wG7jgIyMjIyM%2B434c2R3HeV%2FFfx6jtZu6ijl8h59T655jhR%2BrdHzDOP6beABCheb8O8%2FWFXeOyzgf5oAhVYnKxP7CwaAf1afJu8bSrhS6tdaXeGnrRenOqOlz9d6QwYnA%2F3TLd%2BGE7qe3chA5YF5DfY0vK3adfOX%2FgyNp2BW25MHdxAB9qvRiiP3%2FXpQQFGYDU4%2BMi%2F%2F%2FXumXG8pjvaUAOsBGlf4jJt%2BYYEzeEzAdw06F19R3juM7D1wita86GR0CKfDHgLuXCc4Bri6vMLdfjMc4VNSUNsdodo2xu%2F1%2BXl%2FK5%2Baz8jIyMhYG%2Fz5gJTMF1GtKq%2Fa3rpyCvz5gJTMl9GtKq%2Fa3rpyCmfQ4WwZmS%2BkXFVetb115ST48wEf%2FAGcfG1iw%2BtWbpbS2vJ3nQxcVr3lH3z5h972FUTLzYpOVk7l5hD%2BeYcYwDcAnewOotrZ4OtrPDucqi%2FLRX0%2FRR4qx7Nn4U8g%2BqjffvuN6Gf%2BnC85vwauHjaYyubqvWYKY4VEfSUMitdnBCT1Ue63R5439m%2BOgCn6DroAAaHPVQxKth%2FwkJgHmG8bmQMsT0D6EjDfvhVRKO3ywOQUgRA7nmL1uawZmHf1k%2BDPBwQ6NdcJ%2Bk6Md1LA5f5ONdhJ8vZ5J0vLHT99srkGOjmJbd%2FG1r2Nriqnse1AZt1AalU5jW2HsuuG0qvKGRkZGRkZGRG0gcONyXsP9v8D0%2FIdJADiBNiXl3327WRGgOL%2F9HC%2F0XwlIURkRhC4tz6Z%2Ffu7fUf2gHvfB9z3u0BGRkZGRkbGplHcnkgguQoSqtUXuhbs%2FwPtMwqV0HUJAvj5vk32b8IDuL23yn7qAXZ5u32hbRX7d3o82Df1FZXvbh9QOfhyxldr%2F%2B3xgXU9oKmvsHyr7F%2FXA269%2FeveBXrsv7N9QALe%2FtvjA0kPWAXGbvebkbHn%2BD%2FJ5nMcHzx1UAAAAABJRU5ErkJggg%3D%3D)}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-off{background-position:-96px -144px}.ui-icon-radio-on{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px}.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-top{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-bottom{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-right{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-left{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-all{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.ui-widget-overlay{background:#aaa url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAABkCAYAAAD0ZHJ6AAAAe0lEQVRoge3OMQHAIBAAMcC%2FkjdZJHTI0A4XBdkz86wfO18H3hRUBVVBVVAVVAVVQVVQFVQFVUFVUBVUBVVBVVAVVAVVQVVQFVQFVUFVUBVUBVVBVVAVVAVVQVVQFVQFVUFVUBVUBVVBVVAVVAVVQVVQFVQFVUFVUBVUF8O8A8WdY6opAAAAAElFTkSuQmCC) 50% 50% repeat-x;opacity:.30;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAABkCAYAAAD0ZHJ6AAAAe0lEQVRoge3OMQHAIBAAMcC%2FkjdZJHTI0A4XBdkz86wfO18H3hRUBVVBVVAVVAVVQVVQFVQFVUFVUBVUBVVBVVAVVAVVQVVQFVQFVUFVUBVUBVVBVVAVVAVVQVVQFVQFVUFVUBVUBVVBVVAVVAVVQVVQFVQFVUFVUBVUF8O8A8WdY6opAAAAAElFTkSuQmCC) 50% 50% repeat-x;opacity:.30;filter:Alpha(Opacity=30);-moz-border-radius:8px;-webkit-border-radius:8px;border-radius:8px}#colorbox,#cboxOverlay,#cboxWrapper{position:absolute;top:0;left:0;z-index:9999;overflow:hidden}#cboxOverlay{position:fixed;width:100%;height:100%}#cboxMiddleLeft,#cboxBottomLeft{clear:left}#cboxContent{position:relative}#cboxLoadedContent{overflow:auto}#cboxTitle{margin:0}#cboxLoadingOverlay,#cboxLoadingGraphic{position:absolute;top:0;left:0;width:100%;height:100%}#cboxPrevious,#cboxNext,#cboxClose,#cboxSlideshow{cursor:pointer}.cboxPhoto{float:left;margin:auto;border:0;display:block;max-width:none}.cboxIframe{width:100%;height:100%;display:block;border:0}#colorbox,#cboxContent,#cboxLoadedContent{box-sizing:content-box}#cboxOverlay{background:#000}#cboxTopLeft{width:14px;height:14px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAABLCAMAAACx6hDAAAABj1BMVEVPT0%2Fe3t7b29vS0tK7urq5uLjq6uqZmZmSkpJaWlrU1NTj4%2BPFxcWvr6%2BgoKBbW1u3t7c9PT27u7vCwsKsrKxiYWGqqqq5ublbWlpeXV2Xl5fExMSbmpq6ubmNjY18fHzy8vIrKystLS0sLCxNTU0uLi4wMDDNzc05OTns6%2Bvl5eUvLy%2Fq6ekqKipMTExDQ0M4ODgyMjI2NjbZ2dk6OjrY2NjMzMxLS0vAwMBCQkLo5%2BdHR0cxMTFKSkpBQUHv7u43NzdISEhFRUVRUVHx8fE7Ozs8PDwzMzNJSUnp6elGRkZQUFDr6upeXl7t7e1gYGCoqKjv7%2B81NTWKiorn5uZERESCgoJdXV3p6OhOTk51dXVAQEA%2BPj6np6fu7e2%2Bvr5cXFxSUlKJiYnOzs7s7OxTU1P29vbw8PB2dnZfX1%2Fm5eV4eHifn59qamqmpqbQ0NCOjo7Kysqzs7P4%2BPiDg4Otra3z8%2FM%2FPz80NDSrq6u%2Fv7%2FPz890dHRpaWmBgYH5%2Bfn08%2FNoaGjPzs7%2F%2F%2F%2BioqIRuwm9AAAGHUlEQVR4XsTXZY%2FjPBAA4PwUQxjKzMzLjMfMzPfiD7%2Bxm23ibjZVu5u7%2BVBL7ljyI3scW9pZOv59%2BgdjZ%2BNOeyzlyzXtv9B408%2FUynlp3L4r7bzYWC5E4a4xvAQYGkEA2%2FDG2GL6biBmHbGoz9pVhTBkuRCEhx0TzVNKKNspBczXdHtLnTRa9yC78MdhAND%2B6xaLh3%2B7bf1mhO3guEpooJfbaH56h2i7gOx5oCmlckBkwFzqGIi%2B9LdocllofMSUUnzrndui6wo5bnxVzJyC8BztO06A0HFeM4IIxLgBRAZsYAeI%2FvSf6DxASAkhFIS8vbaQ6aSw4ExBWNoyDyjFAUKM6Q9zy1ddEwBSSnStY3c0ncCo78j2px%2BYW6VLQqIoCgEhb68p5L4jWY6xyIsR4yHLgASiJ4S5JohCaICQQn8756suI5vC0KlkNKRlFBiEU9mJkN7KdYaRCpkvVh00y8HRbA6qMZkRZ8L7aJMolmUpiMe6u215ENafOEPe6Qlbk0K22tvoqTCGwob1lpyn0zN0PzIhB8aqzdFevFgLjGJ8b9TMA3EmrJuPAaiqqvVgbe3g9rFp8834z%2B2DtbUHvF8h%2B151QfUliKVWWKgWSUBFekI3%2FTGqzwstV2jdgFCulj%2BEjfhSbLlEELIDv82A0wmzXffVYJMqOBTcLkQhrLo8om5VkhAg1BnQE16kt81HpWiEfAmb%2F4cPeV5rDWKu8BAVGpRJ2IQ5ERemQsy73X6%2BGXdnRK1bSfb7%2FWSlqwHQJ%2FTSCyD3hIorVG5CKFdHr8KF907j5ao8FRppBxMFgDDfpCgkU%2Fi0n2DHq0UbPXGFz5AtHEy%2B9KwRkRCWMP4tXKhlTlpVWZqtIVBAEryGSRYBa6jyP9T5NfTSYQ0j2qVH%2BXLx3gJh4nRvEAPh3Ys6nNUbq8OCWIccLtahlpmdNLom1qGb3k5HVofSUX5UWyDMpXpxVoggdM9SIPrO0olwllZUAL4XzlId6NOwFF08S3l6hGcpO2iqrZNFwu1esRljQ0K%2Bh3XEg%2Ffrm8L3MEEU6O4%2BgR9Y9IT%2Fe8jTyWYE30NB%2BGk5Ib%2FT6ErInUbzXVKMdAMTCF1Dmk4gcCM9d6dh6RELtQXCz11BGHovpYH3UgorZ8NqUhh1jGx%2FevC9FOQg5O1vFa72thj73xZ47m2xv9LbInqh%2BD4MffABUeLAp5wYwfswEiHEcKU3fnalN37kwl%2Fs2M9LAkEUB%2FC0ml12VgmKLh2%2BcwtBOkWRUdIvIpJayYNdgkIIukUDdYz8x2tnt96OjQMS8hbzexFhDn6QN2%2FeIyHTnoZByJD%2FKJwL58L5TdM%2FFY5uIZ3dQvx0C2l3C%2BHuFsNKmuF7%2Ftlm6vixdnR8vfm744tQV%2FOOX9UJEen4SBpDpKm85J9Mr7Ya4BACK4ZgAYGUaICAIdLxmroro7DVFQHGCFEX1ss7BRpi0wBTYrN4PBDdVumE5reOFSKsFqcnqZERVQaElh3r%2BA0dn5pwQ3mzOiIcqBgmjo1wZoiLE%2FA3LEBOLUzAMInVYMrCtUVv1m1hWyTIEgZfSYTZCIt6%2BiVEFqqurPoopiJHhEhUe7rCpSdvlkloLvwQViKziYpAoeoiogNIQoTysVUSYV9FGl4hUXoWkYAOIXREcl5hQwJeIaW4EQ4A0D3qEAKyMfP%2FYW%2B261Aw16H%2FLu3MyF36936o6Qpy9ENW4eRvmv6kbxpmIcO7lEHIMFuwCf3zYaSaE8%2BH%2FEL2GZ9BWOY9zWc7d%2BwSMQzFcbzDCTqVCnJ3ok4HdrpAKZRSWnInicUOQiVISYeem25O6Xz%2B4%2FYJWaokg8Px4P3%2Bgw%2Fp%2BL79p%2FAkQyEkIQlJSEISkpCEJCQhCUmofcJWIhXa%2B1KfcJlIBsIDSmEzCatLt%2FCW96pGLNzu2LlbeBcbe%2BeNURhs6r2%2BcQGvuczhVh%2BtsMsK1qdX769DuLqYLQyHdc%2Blht4CqfDnMy0LZmScjI%2B%2FN7Y8llpNT4hYGABRVSYSIp1PCBmZygJRCn1pV87UHsKurnnAK3Tnebu6zCDOgydEKZwnlts%2F%2BsrOBpY4hdbYOBtZACIWghHm7JyRCu3efEP6T4Vv0sK5wmQ8JLkAAAAASUVORK5CYII%3D) no-repeat 0 0}#cboxTopCenter{height:14px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABLCAMAAACGCB2xAAAAM1BMVEVSUlJHR0dPT09BQUFLS0tQUFA6OjpgYGCKioozMzPS0tJaWlpRUVHy8vKJiYn%2F%2F%2F%2Fm5eV3dK93AAAAK0lEQVR4XqXBhQ2AQBAAsJ48bvtPywyE1GLGYUgtlPuT0%2Bb5abealNDScL0YiAPSV%2FRH9wAAAABJRU5ErkJggg%3D%3D) repeat-x top left}#cboxTopRight{width:14px;height:14px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAABLCAMAAACx6hDAAAABj1BMVEVPT0%2Fe3t7b29vS0tK7urq5uLjq6uqZmZmSkpJaWlrU1NTj4%2BPFxcWvr6%2BgoKBbW1u3t7c9PT27u7vCwsKsrKxiYWGqqqq5ublbWlpeXV2Xl5fExMSbmpq6ubmNjY18fHzy8vIrKystLS0sLCxNTU0uLi4wMDDNzc05OTns6%2Bvl5eUvLy%2Fq6ekqKipMTExDQ0M4ODgyMjI2NjbZ2dk6OjrY2NjMzMxLS0vAwMBCQkLo5%2BdHR0cxMTFKSkpBQUHv7u43NzdISEhFRUVRUVHx8fE7Ozs8PDwzMzNJSUnp6elGRkZQUFDr6upeXl7t7e1gYGCoqKjv7%2B81NTWKiorn5uZERESCgoJdXV3p6OhOTk51dXVAQEA%2BPj6np6fu7e2%2Bvr5cXFxSUlKJiYnOzs7s7OxTU1P29vbw8PB2dnZfX1%2Fm5eV4eHifn59qamqmpqbQ0NCOjo7Kysqzs7P4%2BPiDg4Otra3z8%2FM%2FPz80NDSrq6u%2Fv7%2FPz890dHRpaWmBgYH5%2Bfn08%2FNoaGjPzs7%2F%2F%2F%2BioqIRuwm9AAAGHUlEQVR4XsTXZY%2FjPBAA4PwUQxjKzMzLjMfMzPfiD7%2Bxm23ibjZVu5u7%2BVBL7ljyI3scW9pZOv59%2BgdjZ%2BNOeyzlyzXtv9B408%2FUynlp3L4r7bzYWC5E4a4xvAQYGkEA2%2FDG2GL6biBmHbGoz9pVhTBkuRCEhx0TzVNKKNspBczXdHtLnTRa9yC78MdhAND%2B6xaLh3%2B7bf1mhO3guEpooJfbaH56h2i7gOx5oCmlckBkwFzqGIi%2B9LdocllofMSUUnzrndui6wo5bnxVzJyC8BztO06A0HFeM4IIxLgBRAZsYAeI%2FvSf6DxASAkhFIS8vbaQ6aSw4ExBWNoyDyjFAUKM6Q9zy1ddEwBSSnStY3c0ncCo78j2px%2BYW6VLQqIoCgEhb68p5L4jWY6xyIsR4yHLgASiJ4S5JohCaICQQn8756suI5vC0KlkNKRlFBiEU9mJkN7KdYaRCpkvVh00y8HRbA6qMZkRZ8L7aJMolmUpiMe6u215ENafOEPe6Qlbk0K22tvoqTCGwob1lpyn0zN0PzIhB8aqzdFevFgLjGJ8b9TMA3EmrJuPAaiqqvVgbe3g9rFp8834z%2B2DtbUHvF8h%2B151QfUliKVWWKgWSUBFekI3%2FTGqzwstV2jdgFCulj%2BEjfhSbLlEELIDv82A0wmzXffVYJMqOBTcLkQhrLo8om5VkhAg1BnQE16kt81HpWiEfAmb%2F4cPeV5rDWKu8BAVGpRJ2IQ5ERemQsy73X6%2BGXdnRK1bSfb7%2FWSlqwHQJ%2FTSCyD3hIorVG5CKFdHr8KF907j5ao8FRppBxMFgDDfpCgkU%2Fi0n2DHq0UbPXGFz5AtHEy%2B9KwRkRCWMP4tXKhlTlpVWZqtIVBAEryGSRYBa6jyP9T5NfTSYQ0j2qVH%2BXLx3gJh4nRvEAPh3Ys6nNUbq8OCWIccLtahlpmdNLom1qGb3k5HVofSUX5UWyDMpXpxVoggdM9SIPrO0olwllZUAL4XzlId6NOwFF08S3l6hGcpO2iqrZNFwu1esRljQ0K%2Bh3XEg%2Ffrm8L3MEEU6O4%2BgR9Y9IT%2Fe8jTyWYE30NB%2BGk5Ib%2FT6ErInUbzXVKMdAMTCF1Dmk4gcCM9d6dh6RELtQXCz11BGHovpYH3UgorZ8NqUhh1jGx%2FevC9FOQg5O1vFa72thj73xZ47m2xv9LbInqh%2BD4MffABUeLAp5wYwfswEiHEcKU3fnalN37kwl%2Fs2M9LAkEUB%2FC0ml12VgmKLh2%2BcwtBOkWRUdIvIpJayYNdgkIIukUDdYz8x2tnt96OjQMS8hbzexFhDn6QN2%2FeIyHTnoZByJD%2FKJwL58L5TdM%2FFY5uIZ3dQvx0C2l3C%2BHuFsNKmuF7%2Ftlm6vixdnR8vfm744tQV%2FOOX9UJEen4SBpDpKm85J9Mr7Ya4BACK4ZgAYGUaICAIdLxmroro7DVFQHGCFEX1ss7BRpi0wBTYrN4PBDdVumE5reOFSKsFqcnqZERVQaElh3r%2BA0dn5pwQ3mzOiIcqBgmjo1wZoiLE%2FA3LEBOLUzAMInVYMrCtUVv1m1hWyTIEgZfSYTZCIt6%2BiVEFqqurPoopiJHhEhUe7rCpSdvlkloLvwQViKziYpAoeoiogNIQoTysVUSYV9FGl4hUXoWkYAOIXREcl5hQwJeIaW4EQ4A0D3qEAKyMfP%2FYW%2B261Aw16H%2FLu3MyF36936o6Qpy9ENW4eRvmv6kbxpmIcO7lEHIMFuwCf3zYaSaE8%2BH%2FEL2GZ9BWOY9zWc7d%2BwSMQzFcbzDCTqVCnJ3ok4HdrpAKZRSWnInicUOQiVISYeem25O6Xz%2B4%2FYJWaokg8Px4P3%2Bgw%2Fp%2BL79p%2FAkQyEkIQlJSEISkpCEJCQhCUmofcJWIhXa%2B1KfcJlIBsIDSmEzCatLt%2FCW96pGLNzu2LlbeBcbe%2BeNURhs6r2%2BcQGvuczhVh%2BtsMsK1qdX769DuLqYLQyHdc%2Blht4CqfDnMy0LZmScjI%2B%2FN7Y8llpNT4hYGABRVSYSIp1PCBmZygJRCn1pV87UHsKurnnAK3Tnebu6zCDOgydEKZwnlts%2F%2BsrOBpY4hdbYOBtZACIWghHm7JyRCu3efEP6T4Vv0sK5wmQ8JLkAAAAASUVORK5CYII%3D) no-repeat -36px 0}#cboxBottomLeft{width:14px;height:43px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAABLCAMAAACx6hDAAAABj1BMVEVPT0%2Fe3t7b29vS0tK7urq5uLjq6uqZmZmSkpJaWlrU1NTj4%2BPFxcWvr6%2BgoKBbW1u3t7c9PT27u7vCwsKsrKxiYWGqqqq5ublbWlpeXV2Xl5fExMSbmpq6ubmNjY18fHzy8vIrKystLS0sLCxNTU0uLi4wMDDNzc05OTns6%2Bvl5eUvLy%2Fq6ekqKipMTExDQ0M4ODgyMjI2NjbZ2dk6OjrY2NjMzMxLS0vAwMBCQkLo5%2BdHR0cxMTFKSkpBQUHv7u43NzdISEhFRUVRUVHx8fE7Ozs8PDwzMzNJSUnp6elGRkZQUFDr6upeXl7t7e1gYGCoqKjv7%2B81NTWKiorn5uZERESCgoJdXV3p6OhOTk51dXVAQEA%2BPj6np6fu7e2%2Bvr5cXFxSUlKJiYnOzs7s7OxTU1P29vbw8PB2dnZfX1%2Fm5eV4eHifn59qamqmpqbQ0NCOjo7Kysqzs7P4%2BPiDg4Otra3z8%2FM%2FPz80NDSrq6u%2Fv7%2FPz890dHRpaWmBgYH5%2Bfn08%2FNoaGjPzs7%2F%2F%2F%2BioqIRuwm9AAAGHUlEQVR4XsTXZY%2FjPBAA4PwUQxjKzMzLjMfMzPfiD7%2Bxm23ibjZVu5u7%2BVBL7ljyI3scW9pZOv59%2BgdjZ%2BNOeyzlyzXtv9B408%2FUynlp3L4r7bzYWC5E4a4xvAQYGkEA2%2FDG2GL6biBmHbGoz9pVhTBkuRCEhx0TzVNKKNspBczXdHtLnTRa9yC78MdhAND%2B6xaLh3%2B7bf1mhO3guEpooJfbaH56h2i7gOx5oCmlckBkwFzqGIi%2B9LdocllofMSUUnzrndui6wo5bnxVzJyC8BztO06A0HFeM4IIxLgBRAZsYAeI%2FvSf6DxASAkhFIS8vbaQ6aSw4ExBWNoyDyjFAUKM6Q9zy1ddEwBSSnStY3c0ncCo78j2px%2BYW6VLQqIoCgEhb68p5L4jWY6xyIsR4yHLgASiJ4S5JohCaICQQn8756suI5vC0KlkNKRlFBiEU9mJkN7KdYaRCpkvVh00y8HRbA6qMZkRZ8L7aJMolmUpiMe6u215ENafOEPe6Qlbk0K22tvoqTCGwob1lpyn0zN0PzIhB8aqzdFevFgLjGJ8b9TMA3EmrJuPAaiqqvVgbe3g9rFp8834z%2B2DtbUHvF8h%2B151QfUliKVWWKgWSUBFekI3%2FTGqzwstV2jdgFCulj%2BEjfhSbLlEELIDv82A0wmzXffVYJMqOBTcLkQhrLo8om5VkhAg1BnQE16kt81HpWiEfAmb%2F4cPeV5rDWKu8BAVGpRJ2IQ5ERemQsy73X6%2BGXdnRK1bSfb7%2FWSlqwHQJ%2FTSCyD3hIorVG5CKFdHr8KF907j5ao8FRppBxMFgDDfpCgkU%2Fi0n2DHq0UbPXGFz5AtHEy%2B9KwRkRCWMP4tXKhlTlpVWZqtIVBAEryGSRYBa6jyP9T5NfTSYQ0j2qVH%2BXLx3gJh4nRvEAPh3Ys6nNUbq8OCWIccLtahlpmdNLom1qGb3k5HVofSUX5UWyDMpXpxVoggdM9SIPrO0olwllZUAL4XzlId6NOwFF08S3l6hGcpO2iqrZNFwu1esRljQ0K%2Bh3XEg%2Ffrm8L3MEEU6O4%2BgR9Y9IT%2Fe8jTyWYE30NB%2BGk5Ib%2FT6ErInUbzXVKMdAMTCF1Dmk4gcCM9d6dh6RELtQXCz11BGHovpYH3UgorZ8NqUhh1jGx%2FevC9FOQg5O1vFa72thj73xZ47m2xv9LbInqh%2BD4MffABUeLAp5wYwfswEiHEcKU3fnalN37kwl%2Fs2M9LAkEUB%2FC0ml12VgmKLh2%2BcwtBOkWRUdIvIpJayYNdgkIIukUDdYz8x2tnt96OjQMS8hbzexFhDn6QN2%2FeIyHTnoZByJD%2FKJwL58L5TdM%2FFY5uIZ3dQvx0C2l3C%2BHuFsNKmuF7%2Ftlm6vixdnR8vfm744tQV%2FOOX9UJEen4SBpDpKm85J9Mr7Ya4BACK4ZgAYGUaICAIdLxmroro7DVFQHGCFEX1ss7BRpi0wBTYrN4PBDdVumE5reOFSKsFqcnqZERVQaElh3r%2BA0dn5pwQ3mzOiIcqBgmjo1wZoiLE%2FA3LEBOLUzAMInVYMrCtUVv1m1hWyTIEgZfSYTZCIt6%2BiVEFqqurPoopiJHhEhUe7rCpSdvlkloLvwQViKziYpAoeoiogNIQoTysVUSYV9FGl4hUXoWkYAOIXREcl5hQwJeIaW4EQ4A0D3qEAKyMfP%2FYW%2B261Aw16H%2FLu3MyF36936o6Qpy9ENW4eRvmv6kbxpmIcO7lEHIMFuwCf3zYaSaE8%2BH%2FEL2GZ9BWOY9zWc7d%2BwSMQzFcbzDCTqVCnJ3ok4HdrpAKZRSWnInicUOQiVISYeem25O6Xz%2B4%2FYJWaokg8Px4P3%2Bgw%2Fp%2BL79p%2FAkQyEkIQlJSEISkpCEJCQhCUmofcJWIhXa%2B1KfcJlIBsIDSmEzCatLt%2FCW96pGLNzu2LlbeBcbe%2BeNURhs6r2%2BcQGvuczhVh%2BtsMsK1qdX769DuLqYLQyHdc%2Blht4CqfDnMy0LZmScjI%2B%2FN7Y8llpNT4hYGABRVSYSIp1PCBmZygJRCn1pV87UHsKurnnAK3Tnebu6zCDOgydEKZwnlts%2F%2BsrOBpY4hdbYOBtZACIWghHm7JyRCu3efEP6T4Vv0sK5wmQ8JLkAAAAASUVORK5CYII%3D) no-repeat 0 -32px}#cboxBottomCenter{height:43px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABLCAMAAACGCB2xAAAAM1BMVEVSUlJHR0dPT09BQUFLS0tQUFA6OjpgYGCKioozMzPS0tJaWlpRUVHy8vKJiYn%2F%2F%2F%2Fm5eV3dK93AAAAK0lEQVR4XqXBhQ2AQBAAsJ48bvtPywyE1GLGYUgtlPuT0%2Bb5abealNDScL0YiAPSV%2FRH9wAAAABJRU5ErkJggg%3D%3D) repeat-x bottom left}#cboxBottomRight{width:14px;height:43px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAABLCAMAAACx6hDAAAABj1BMVEVPT0%2Fe3t7b29vS0tK7urq5uLjq6uqZmZmSkpJaWlrU1NTj4%2BPFxcWvr6%2BgoKBbW1u3t7c9PT27u7vCwsKsrKxiYWGqqqq5ublbWlpeXV2Xl5fExMSbmpq6ubmNjY18fHzy8vIrKystLS0sLCxNTU0uLi4wMDDNzc05OTns6%2Bvl5eUvLy%2Fq6ekqKipMTExDQ0M4ODgyMjI2NjbZ2dk6OjrY2NjMzMxLS0vAwMBCQkLo5%2BdHR0cxMTFKSkpBQUHv7u43NzdISEhFRUVRUVHx8fE7Ozs8PDwzMzNJSUnp6elGRkZQUFDr6upeXl7t7e1gYGCoqKjv7%2B81NTWKiorn5uZERESCgoJdXV3p6OhOTk51dXVAQEA%2BPj6np6fu7e2%2Bvr5cXFxSUlKJiYnOzs7s7OxTU1P29vbw8PB2dnZfX1%2Fm5eV4eHifn59qamqmpqbQ0NCOjo7Kysqzs7P4%2BPiDg4Otra3z8%2FM%2FPz80NDSrq6u%2Fv7%2FPz890dHRpaWmBgYH5%2Bfn08%2FNoaGjPzs7%2F%2F%2F%2BioqIRuwm9AAAGHUlEQVR4XsTXZY%2FjPBAA4PwUQxjKzMzLjMfMzPfiD7%2Bxm23ibjZVu5u7%2BVBL7ljyI3scW9pZOv59%2BgdjZ%2BNOeyzlyzXtv9B408%2FUynlp3L4r7bzYWC5E4a4xvAQYGkEA2%2FDG2GL6biBmHbGoz9pVhTBkuRCEhx0TzVNKKNspBczXdHtLnTRa9yC78MdhAND%2B6xaLh3%2B7bf1mhO3guEpooJfbaH56h2i7gOx5oCmlckBkwFzqGIi%2B9LdocllofMSUUnzrndui6wo5bnxVzJyC8BztO06A0HFeM4IIxLgBRAZsYAeI%2FvSf6DxASAkhFIS8vbaQ6aSw4ExBWNoyDyjFAUKM6Q9zy1ddEwBSSnStY3c0ncCo78j2px%2BYW6VLQqIoCgEhb68p5L4jWY6xyIsR4yHLgASiJ4S5JohCaICQQn8756suI5vC0KlkNKRlFBiEU9mJkN7KdYaRCpkvVh00y8HRbA6qMZkRZ8L7aJMolmUpiMe6u215ENafOEPe6Qlbk0K22tvoqTCGwob1lpyn0zN0PzIhB8aqzdFevFgLjGJ8b9TMA3EmrJuPAaiqqvVgbe3g9rFp8834z%2B2DtbUHvF8h%2B151QfUliKVWWKgWSUBFekI3%2FTGqzwstV2jdgFCulj%2BEjfhSbLlEELIDv82A0wmzXffVYJMqOBTcLkQhrLo8om5VkhAg1BnQE16kt81HpWiEfAmb%2F4cPeV5rDWKu8BAVGpRJ2IQ5ERemQsy73X6%2BGXdnRK1bSfb7%2FWSlqwHQJ%2FTSCyD3hIorVG5CKFdHr8KF907j5ao8FRppBxMFgDDfpCgkU%2Fi0n2DHq0UbPXGFz5AtHEy%2B9KwRkRCWMP4tXKhlTlpVWZqtIVBAEryGSRYBa6jyP9T5NfTSYQ0j2qVH%2BXLx3gJh4nRvEAPh3Ys6nNUbq8OCWIccLtahlpmdNLom1qGb3k5HVofSUX5UWyDMpXpxVoggdM9SIPrO0olwllZUAL4XzlId6NOwFF08S3l6hGcpO2iqrZNFwu1esRljQ0K%2Bh3XEg%2Ffrm8L3MEEU6O4%2BgR9Y9IT%2Fe8jTyWYE30NB%2BGk5Ib%2FT6ErInUbzXVKMdAMTCF1Dmk4gcCM9d6dh6RELtQXCz11BGHovpYH3UgorZ8NqUhh1jGx%2FevC9FOQg5O1vFa72thj73xZ47m2xv9LbInqh%2BD4MffABUeLAp5wYwfswEiHEcKU3fnalN37kwl%2Fs2M9LAkEUB%2FC0ml12VgmKLh2%2BcwtBOkWRUdIvIpJayYNdgkIIukUDdYz8x2tnt96OjQMS8hbzexFhDn6QN2%2FeIyHTnoZByJD%2FKJwL58L5TdM%2FFY5uIZ3dQvx0C2l3C%2BHuFsNKmuF7%2Ftlm6vixdnR8vfm744tQV%2FOOX9UJEen4SBpDpKm85J9Mr7Ya4BACK4ZgAYGUaICAIdLxmroro7DVFQHGCFEX1ss7BRpi0wBTYrN4PBDdVumE5reOFSKsFqcnqZERVQaElh3r%2BA0dn5pwQ3mzOiIcqBgmjo1wZoiLE%2FA3LEBOLUzAMInVYMrCtUVv1m1hWyTIEgZfSYTZCIt6%2BiVEFqqurPoopiJHhEhUe7rCpSdvlkloLvwQViKziYpAoeoiogNIQoTysVUSYV9FGl4hUXoWkYAOIXREcl5hQwJeIaW4EQ4A0D3qEAKyMfP%2FYW%2B261Aw16H%2FLu3MyF36936o6Qpy9ENW4eRvmv6kbxpmIcO7lEHIMFuwCf3zYaSaE8%2BH%2FEL2GZ9BWOY9zWc7d%2BwSMQzFcbzDCTqVCnJ3ok4HdrpAKZRSWnInicUOQiVISYeem25O6Xz%2B4%2FYJWaokg8Px4P3%2Bgw%2Fp%2BL79p%2FAkQyEkIQlJSEISkpCEJCQhCUmofcJWIhXa%2B1KfcJlIBsIDSmEzCatLt%2FCW96pGLNzu2LlbeBcbe%2BeNURhs6r2%2BcQGvuczhVh%2BtsMsK1qdX769DuLqYLQyHdc%2Blht4CqfDnMy0LZmScjI%2B%2FN7Y8llpNT4hYGABRVSYSIp1PCBmZygJRCn1pV87UHsKurnnAK3Tnebu6zCDOgydEKZwnlts%2F%2BsrOBpY4hdbYOBtZACIWghHm7JyRCu3efEP6T4Vv0sK5wmQ8JLkAAAAASUVORK5CYII%3D) no-repeat -36px -32px}#cboxMiddleLeft{width:14px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAABLCAMAAACx6hDAAAABj1BMVEVPT0%2Fe3t7b29vS0tK7urq5uLjq6uqZmZmSkpJaWlrU1NTj4%2BPFxcWvr6%2BgoKBbW1u3t7c9PT27u7vCwsKsrKxiYWGqqqq5ublbWlpeXV2Xl5fExMSbmpq6ubmNjY18fHzy8vIrKystLS0sLCxNTU0uLi4wMDDNzc05OTns6%2Bvl5eUvLy%2Fq6ekqKipMTExDQ0M4ODgyMjI2NjbZ2dk6OjrY2NjMzMxLS0vAwMBCQkLo5%2BdHR0cxMTFKSkpBQUHv7u43NzdISEhFRUVRUVHx8fE7Ozs8PDwzMzNJSUnp6elGRkZQUFDr6upeXl7t7e1gYGCoqKjv7%2B81NTWKiorn5uZERESCgoJdXV3p6OhOTk51dXVAQEA%2BPj6np6fu7e2%2Bvr5cXFxSUlKJiYnOzs7s7OxTU1P29vbw8PB2dnZfX1%2Fm5eV4eHifn59qamqmpqbQ0NCOjo7Kysqzs7P4%2BPiDg4Otra3z8%2FM%2FPz80NDSrq6u%2Fv7%2FPz890dHRpaWmBgYH5%2Bfn08%2FNoaGjPzs7%2F%2F%2F%2BioqIRuwm9AAAGHUlEQVR4XsTXZY%2FjPBAA4PwUQxjKzMzLjMfMzPfiD7%2Bxm23ibjZVu5u7%2BVBL7ljyI3scW9pZOv59%2BgdjZ%2BNOeyzlyzXtv9B408%2FUynlp3L4r7bzYWC5E4a4xvAQYGkEA2%2FDG2GL6biBmHbGoz9pVhTBkuRCEhx0TzVNKKNspBczXdHtLnTRa9yC78MdhAND%2B6xaLh3%2B7bf1mhO3guEpooJfbaH56h2i7gOx5oCmlckBkwFzqGIi%2B9LdocllofMSUUnzrndui6wo5bnxVzJyC8BztO06A0HFeM4IIxLgBRAZsYAeI%2FvSf6DxASAkhFIS8vbaQ6aSw4ExBWNoyDyjFAUKM6Q9zy1ddEwBSSnStY3c0ncCo78j2px%2BYW6VLQqIoCgEhb68p5L4jWY6xyIsR4yHLgASiJ4S5JohCaICQQn8756suI5vC0KlkNKRlFBiEU9mJkN7KdYaRCpkvVh00y8HRbA6qMZkRZ8L7aJMolmUpiMe6u215ENafOEPe6Qlbk0K22tvoqTCGwob1lpyn0zN0PzIhB8aqzdFevFgLjGJ8b9TMA3EmrJuPAaiqqvVgbe3g9rFp8834z%2B2DtbUHvF8h%2B151QfUliKVWWKgWSUBFekI3%2FTGqzwstV2jdgFCulj%2BEjfhSbLlEELIDv82A0wmzXffVYJMqOBTcLkQhrLo8om5VkhAg1BnQE16kt81HpWiEfAmb%2F4cPeV5rDWKu8BAVGpRJ2IQ5ERemQsy73X6%2BGXdnRK1bSfb7%2FWSlqwHQJ%2FTSCyD3hIorVG5CKFdHr8KF907j5ao8FRppBxMFgDDfpCgkU%2Fi0n2DHq0UbPXGFz5AtHEy%2B9KwRkRCWMP4tXKhlTlpVWZqtIVBAEryGSRYBa6jyP9T5NfTSYQ0j2qVH%2BXLx3gJh4nRvEAPh3Ys6nNUbq8OCWIccLtahlpmdNLom1qGb3k5HVofSUX5UWyDMpXpxVoggdM9SIPrO0olwllZUAL4XzlId6NOwFF08S3l6hGcpO2iqrZNFwu1esRljQ0K%2Bh3XEg%2Ffrm8L3MEEU6O4%2BgR9Y9IT%2Fe8jTyWYE30NB%2BGk5Ib%2FT6ErInUbzXVKMdAMTCF1Dmk4gcCM9d6dh6RELtQXCz11BGHovpYH3UgorZ8NqUhh1jGx%2FevC9FOQg5O1vFa72thj73xZ47m2xv9LbInqh%2BD4MffABUeLAp5wYwfswEiHEcKU3fnalN37kwl%2Fs2M9LAkEUB%2FC0ml12VgmKLh2%2BcwtBOkWRUdIvIpJayYNdgkIIukUDdYz8x2tnt96OjQMS8hbzexFhDn6QN2%2FeIyHTnoZByJD%2FKJwL58L5TdM%2FFY5uIZ3dQvx0C2l3C%2BHuFsNKmuF7%2Ftlm6vixdnR8vfm744tQV%2FOOX9UJEen4SBpDpKm85J9Mr7Ya4BACK4ZgAYGUaICAIdLxmroro7DVFQHGCFEX1ss7BRpi0wBTYrN4PBDdVumE5reOFSKsFqcnqZERVQaElh3r%2BA0dn5pwQ3mzOiIcqBgmjo1wZoiLE%2FA3LEBOLUzAMInVYMrCtUVv1m1hWyTIEgZfSYTZCIt6%2BiVEFqqurPoopiJHhEhUe7rCpSdvlkloLvwQViKziYpAoeoiogNIQoTysVUSYV9FGl4hUXoWkYAOIXREcl5hQwJeIaW4EQ4A0D3qEAKyMfP%2FYW%2B261Aw16H%2FLu3MyF36936o6Qpy9ENW4eRvmv6kbxpmIcO7lEHIMFuwCf3zYaSaE8%2BH%2FEL2GZ9BWOY9zWc7d%2BwSMQzFcbzDCTqVCnJ3ok4HdrpAKZRSWnInicUOQiVISYeem25O6Xz%2B4%2FYJWaokg8Px4P3%2Bgw%2Fp%2BL79p%2FAkQyEkIQlJSEISkpCEJCQhCUmofcJWIhXa%2B1KfcJlIBsIDSmEzCatLt%2FCW96pGLNzu2LlbeBcbe%2BeNURhs6r2%2BcQGvuczhVh%2BtsMsK1qdX769DuLqYLQyHdc%2Blht4CqfDnMy0LZmScjI%2B%2FN7Y8llpNT4hYGABRVSYSIp1PCBmZygJRCn1pV87UHsKurnnAK3Tnebu6zCDOgydEKZwnlts%2F%2BsrOBpY4hdbYOBtZACIWghHm7JyRCu3efEP6T4Vv0sK5wmQ8JLkAAAAASUVORK5CYII%3D) repeat-y -175px 0}#cboxMiddleRight{width:14px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAABLCAMAAACx6hDAAAABj1BMVEVPT0%2Fe3t7b29vS0tK7urq5uLjq6uqZmZmSkpJaWlrU1NTj4%2BPFxcWvr6%2BgoKBbW1u3t7c9PT27u7vCwsKsrKxiYWGqqqq5ublbWlpeXV2Xl5fExMSbmpq6ubmNjY18fHzy8vIrKystLS0sLCxNTU0uLi4wMDDNzc05OTns6%2Bvl5eUvLy%2Fq6ekqKipMTExDQ0M4ODgyMjI2NjbZ2dk6OjrY2NjMzMxLS0vAwMBCQkLo5%2BdHR0cxMTFKSkpBQUHv7u43NzdISEhFRUVRUVHx8fE7Ozs8PDwzMzNJSUnp6elGRkZQUFDr6upeXl7t7e1gYGCoqKjv7%2B81NTWKiorn5uZERESCgoJdXV3p6OhOTk51dXVAQEA%2BPj6np6fu7e2%2Bvr5cXFxSUlKJiYnOzs7s7OxTU1P29vbw8PB2dnZfX1%2Fm5eV4eHifn59qamqmpqbQ0NCOjo7Kysqzs7P4%2BPiDg4Otra3z8%2FM%2FPz80NDSrq6u%2Fv7%2FPz890dHRpaWmBgYH5%2Bfn08%2FNoaGjPzs7%2F%2F%2F%2BioqIRuwm9AAAGHUlEQVR4XsTXZY%2FjPBAA4PwUQxjKzMzLjMfMzPfiD7%2Bxm23ibjZVu5u7%2BVBL7ljyI3scW9pZOv59%2BgdjZ%2BNOeyzlyzXtv9B408%2FUynlp3L4r7bzYWC5E4a4xvAQYGkEA2%2FDG2GL6biBmHbGoz9pVhTBkuRCEhx0TzVNKKNspBczXdHtLnTRa9yC78MdhAND%2B6xaLh3%2B7bf1mhO3guEpooJfbaH56h2i7gOx5oCmlckBkwFzqGIi%2B9LdocllofMSUUnzrndui6wo5bnxVzJyC8BztO06A0HFeM4IIxLgBRAZsYAeI%2FvSf6DxASAkhFIS8vbaQ6aSw4ExBWNoyDyjFAUKM6Q9zy1ddEwBSSnStY3c0ncCo78j2px%2BYW6VLQqIoCgEhb68p5L4jWY6xyIsR4yHLgASiJ4S5JohCaICQQn8756suI5vC0KlkNKRlFBiEU9mJkN7KdYaRCpkvVh00y8HRbA6qMZkRZ8L7aJMolmUpiMe6u215ENafOEPe6Qlbk0K22tvoqTCGwob1lpyn0zN0PzIhB8aqzdFevFgLjGJ8b9TMA3EmrJuPAaiqqvVgbe3g9rFp8834z%2B2DtbUHvF8h%2B151QfUliKVWWKgWSUBFekI3%2FTGqzwstV2jdgFCulj%2BEjfhSbLlEELIDv82A0wmzXffVYJMqOBTcLkQhrLo8om5VkhAg1BnQE16kt81HpWiEfAmb%2F4cPeV5rDWKu8BAVGpRJ2IQ5ERemQsy73X6%2BGXdnRK1bSfb7%2FWSlqwHQJ%2FTSCyD3hIorVG5CKFdHr8KF907j5ao8FRppBxMFgDDfpCgkU%2Fi0n2DHq0UbPXGFz5AtHEy%2B9KwRkRCWMP4tXKhlTlpVWZqtIVBAEryGSRYBa6jyP9T5NfTSYQ0j2qVH%2BXLx3gJh4nRvEAPh3Ys6nNUbq8OCWIccLtahlpmdNLom1qGb3k5HVofSUX5UWyDMpXpxVoggdM9SIPrO0olwllZUAL4XzlId6NOwFF08S3l6hGcpO2iqrZNFwu1esRljQ0K%2Bh3XEg%2Ffrm8L3MEEU6O4%2BgR9Y9IT%2Fe8jTyWYE30NB%2BGk5Ib%2FT6ErInUbzXVKMdAMTCF1Dmk4gcCM9d6dh6RELtQXCz11BGHovpYH3UgorZ8NqUhh1jGx%2FevC9FOQg5O1vFa72thj73xZ47m2xv9LbInqh%2BD4MffABUeLAp5wYwfswEiHEcKU3fnalN37kwl%2Fs2M9LAkEUB%2FC0ml12VgmKLh2%2BcwtBOkWRUdIvIpJayYNdgkIIukUDdYz8x2tnt96OjQMS8hbzexFhDn6QN2%2FeIyHTnoZByJD%2FKJwL58L5TdM%2FFY5uIZ3dQvx0C2l3C%2BHuFsNKmuF7%2Ftlm6vixdnR8vfm744tQV%2FOOX9UJEen4SBpDpKm85J9Mr7Ya4BACK4ZgAYGUaICAIdLxmroro7DVFQHGCFEX1ss7BRpi0wBTYrN4PBDdVumE5reOFSKsFqcnqZERVQaElh3r%2BA0dn5pwQ3mzOiIcqBgmjo1wZoiLE%2FA3LEBOLUzAMInVYMrCtUVv1m1hWyTIEgZfSYTZCIt6%2BiVEFqqurPoopiJHhEhUe7rCpSdvlkloLvwQViKziYpAoeoiogNIQoTysVUSYV9FGl4hUXoWkYAOIXREcl5hQwJeIaW4EQ4A0D3qEAKyMfP%2FYW%2B261Aw16H%2FLu3MyF36936o6Qpy9ENW4eRvmv6kbxpmIcO7lEHIMFuwCf3zYaSaE8%2BH%2FEL2GZ9BWOY9zWc7d%2BwSMQzFcbzDCTqVCnJ3ok4HdrpAKZRSWnInicUOQiVISYeem25O6Xz%2B4%2FYJWaokg8Px4P3%2Bgw%2Fp%2BL79p%2FAkQyEkIQlJSEISkpCEJCQhCUmofcJWIhXa%2B1KfcJlIBsIDSmEzCatLt%2FCW96pGLNzu2LlbeBcbe%2BeNURhs6r2%2BcQGvuczhVh%2BtsMsK1qdX769DuLqYLQyHdc%2Blht4CqfDnMy0LZmScjI%2B%2FN7Y8llpNT4hYGABRVSYSIp1PCBmZygJRCn1pV87UHsKurnnAK3Tnebu6zCDOgydEKZwnlts%2F%2BsrOBpY4hdbYOBtZACIWghHm7JyRCu3efEP6T4Vv0sK5wmQ8JLkAAAAASUVORK5CYII%3D) repeat-y -211px 0}#cboxContent{background:#fff;overflow:visible}.cboxIframe{background:#fff}#cboxError{padding:50px;border:1px solid #ccc}#cboxLoadedContent{margin-bottom:5px}#cboxLoadingOverlay{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoBAMAAAB%2B0KVeAAAAElBMVEX%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8AAAD%2F%2F%2F%2F%2F%2F%2F9H1zSfAAAABXRSTlPvgBAAz5JLnNUAAAA%2BSURBVHhe7dMhAQAgEEPRIfAYEkCCi0ACEOtfBc8WAHFfPr0hGp%2FKwKS00BUPquIGTZ9gYqIdrZ23PYK9zAX6sAYavSqAMgAAAABJRU5ErkJggg%3D%3D) no-repeat center center}#cboxLoadingGraphic{background:url(data:image/gif;base64,R0lGODlhIAAgAPYAAP%2F%2F%2F%2F9VAP77%2Bv7j1v7m2v78%2FP7Quv6qgP6wiv7UwP749v7v6P6viP6ofv6%2FoP7u5v6fcP6LUv6rgv7s5P728v6nfP7Aov7Irv54Nv57Ov5%2FQP6bav7n3P739P6mev7Dpv76%2BP7ayP58PP6cbP7w6v6%2Bnv6keP7Tvv7g0v53NP56OP7HrP7Yxv7czP7z7v7i1P50MP7MtP7SvP7EqP708P6ebv62kv7k2P7r4v6uhv5gEv5fEP5sJP5eDv5zLv67mv7q4P7o3v7y7P7KsP68nP64lv6WYv6zjv63lP6DRv6HTP6KUP6CRP6GSv60kP7ezv6ESP6AQv7f0P7Wwv6ITv66mP5mGv5vKP52Mv5jFv5iFP7PuP6QWv6MVP7CpP6gcv6PWP6TXv6XZP6SXP6OVv5rIv5qIP5oHv5wKv7byv7XxP6aaP7Otv6YZv5yLP7Gqv5kGP6UYP5nHP6idP6jdv7Lsv5uJv6shP5%2BPv6yjP5cDAAAAAAAAAAAAAAAAAAAACH%2FC05FVFNDQVBFMi4wAwEAAAAh%2FhpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQkZKECzk2NJOCDxchgwU1OjsSmQoQGCIWghQiOz01npALERkYGQ4AFBqtP4ILN0ACjgISGhkpGDIANjw%2BKABCKNEujxMbGiowowAEHIIT0SgUkBwjGiIzhkIvKDiSJCsxwYYdmI8KFB0FjfqLAgYMEiSUEJeoAJABBAgiGnCgQQUPJlgoIgGuWyICCBhoRNBCEbRoFhEVSODAwocTIBQVwEEgiMJEChSkzNTPRQdEFF46KsABxYtphUisAxLpW7QJgkDMxAFO5yIC0V5gEjrg5kcUQB098ElCEFQURAH4CiLvEQUFg25ECwKLpiCmKBC6ui0kYILcuXjz6t3Ld1IgACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2Ohw8Tj44XKlhbk4sKEVZZXAWZgwsxLYMdTJ1RCqEAIA1JSjOCFKhaUSCCoI8kRkpMULIKVFZaXaALN0C6jAVHS01RTFMAVVc8XgBCKNsujwsmS1AaCIJSpQAT2ygUk0AeS0oXhkIvKDihQjEyy4QdNJMgOqxqxC9RCyJFkKwYiKgAkAEE2CWi4CChDSdSFJFQx0ERiCEWQlq4oUjbto6KgCQwIOOJAEUFcBAIInGRgIKsGrrogIhCzUcFgqB40a0QiXpAMj1QJ6kVLgA41P1kxGHbi39HB%2FA0iaKoo6MvSAgisC0pAGRBXk4SOOjGtiCDFXCGSodCSM6GC7ze3cu3r9%2B%2FgAcFAgAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjoYkTj8Uj40SPGUMlYsdSzxmSiCbg0IyKIM0TTxnTAqjACAIYGNDgh1Uq1CiAB2VLl9hZGAXsGSrXAUKEjNABY4FRGJjXV0sAD8%2BaB8ANmItKC6PJAxiXBFIAAIhIYJVUygolI8TCNIxhkAvKDijLidTzgx1oLEJxC5GAReRkLFixZSDhwoAGUBAXiIWQy6smMFBEQl4KDoqenKi5Al%2BiYSAFJmIwgAUL5opKoCDQBCLM189c9HrEAWcz4LADFeIhD4gmxaAnCDIoCAcIIEuEgqToNEBvVTCI%2BrIxYAXJAQRgIcUwIIbQQQUPHiD7KCEOhMBTIAnJG7EBVzt6t3Lt6%2FfvYEAACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2OhiRVDhSPjQhYPkeViwpjWG5dIJuDBTdBgxRkWGhKCqOCK18QW4IdXKsRogAPHY8FNl8bG2wAIEarRgUKDW4ROI8XHl9rbS0ADhkYbwBIWj1wU48uPx4QYg4ABS1pgm09ZUc0lQtE5SeGR1hEz5sUIWkFDAkAIq9SAQGOAjIC8YLFFBQIExUAMoAAJUU41oVQs0ARCRQgOSyaABKkC0VCSopUJADHjRsTFhXAQSDIRZmvErrodYjCTV9BULw4WYjECxRANn0EGbNYRBwlfzIiKVSe0Ru9UpqsRGHAABKCCIBMCmCBqYiPBKC9MZZUTkJUEIW8PVRgAdG5ePPq3ctXbyAAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6GQhZDHY%2BNSFEiRZWLCmtRGXEgm4QgCoMdYhoZYKajAA9ETmqCnRoqY6IACy6VCQgHDQkAIBAaGCMAChIpShyPTzYMDR4oADNQUUMAVXJZOj%2BPHRdOOR4rAAVST4Ij3joXlS7jOSyGNnA7YRSbHSgvhyAMvBHiqlEBgxNu3MCxqACQAQT2KXKBoiIKGopIWHQ20eJFRUI2NsShcMJIAkEkNixo0AWlQxRUPioQxB%2BvQiReoACySWNFk8MECMJhUSajCRVfYMx5g1LIijcdKSAwgIQgAhV56roBRGilAgcF3cg6KCxLAEhREDxbqACJqGwI48qdS7fuqEAAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6GLitsCo%2BNJRFUM5WLICYRTSMCm4kdc59iIIIgLw%2BVT2woggp0EVBrogtfblFSjhNeP0hpAAINEUl0AApfZWdyTr4rFkVOBAB1YBFsAD92zlZ1jiBTbw42WwAFL7ECRmZycEYUjxRqbyW9hUfwRiSbIEGCHKLwxoKQUY1AUCjQiAQBAhMWFWjRgkCHRRRQaERBQxGJjRwwbuSoSAhIRg9u3IioqAAOAkAuMmKIsFEBFzINUZi3qUAQFC9cGCKxDsimjxpZghAFAMdGno4eaHzRkeiNiyY1Cn0EgsAAfwAIaDQKYMENIEwr0QRwY%2BygtTUUAUzQeDCuoQIkttrdy7ev3799AwEAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6GBQMDj45sI20ylIsgDG1jBwWaiQp3nl8ggiAyQxSPJCgPqZ1cdAIAJB4pbkeOCmoxF5MCR21cEgAKFTBodmO2jB0hqzM4ADIjRpkOKcw8P48cLAYrIQAFN5MFI252ZRutjiAELFschkVXZWskmgUkC4coXPjgQlQjEDj4MSJBgMCERRPA2MlgYJGCFygy0lCE5MwVH21QjcKoUREBNglY3GC04MaNh4oK4CAARIHBm4gKuOiAiAI8SgWCoHhRsBAJjEA0vcoIE8QzHBlR%2FGz0IOOLjUdv8BQStWg8AjcUEsiYFEBLIM%2BADrpBdlAonIIRJmQUAhcSCa918%2Brdy7evqEAAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6HIAKPjkFFP0CTjB8VXx%2BZigI%2FFRAMkgACCWwdjwVCNIICRKMHkkJ3URlIj0FPITgABQ4VNUcFIDl4KiliposCLygtUyQAIXd0LQAzuClYDo9AKFIhN4ITmAV0GSkwX6uOIBziC4ZEKT4QQpmtr4YddStcfGoEYoI%2BRkIIEJiwaEIYNxpkLAIBDQWKfojy6NiYRIEiihYvKjrSo2QTEIsW3LjBUNEDD1SohBgIqlmjAi7eGaJA4VOBICheCCxEAhqmSSRCtowkCEfIno8eWHzxquiNVUJCDoVH4AY1AAQsHlUJpIDPQTfEDjJLc9AEiwcP2xYqQGKr3Lt48%2BrdizcQACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2Oj5CHCmkhCpGLU0gMMpeJBUOaPwWCAiwyHZAdlgACF0g5NgIALkcRTSWPEy8DQgAFdUh3uCBOVFBMELKMBTcoKC8UAC8%2FCC8AQ11NTBozj0DOKA%2BCJOIFEtp4FaiOIBzPLoZeTHge8JAFLtGGHVt1NJ2MQEzoxUgIAQITFj1og4EJm0UCBoD7l8iGHCtWlIBQFHGiIhtZQmpcZPBGQkUPxIhY8hDgoQIUlDnCt84QBX33grwzROIFCiCRSIA7CUIZDnA4Gz1w9uJfzxuohICzx47ADRKCCDgDCmDBDRyjIoUF0OznoLEuJzgj6LJQARJUCtvKnUu3rt25gQAAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6PkIgkC5GMHEMzN5WKLBcOQ4MCL2oKkCAgggWdJR8FADREbWMfjyQvA0KCaRdEFwACJUZcXQ2ujRwoKC8UAEB1FhwABrJdS76OOMkoD4I0JIJOY11UOaWOIMgvNIYXZOTrkAUuzIYKJ1vwm4oCD0FCxomEECAwYRGQGhpUJPmSz5CAAdoaGrpjpyKPKzISFYCYTGIhBGZCmrFjQJELAjcKKnqwIQoTJk4E6DNUoIPNR%2FI6IGIxRGe8IMpcGCKR4EsbobW0qQQhE0A2KQ5QQHqQTB0AWzd0CtGW6xEIlN8AEEgGRNCCGzgA4hx0g%2BwgtfoTJiTrOrNQARJI6%2Brdy7evX76BAAAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQiCACkYxCTywklYoEaTIsgwUcQJEgBYM3aQYygh1vHiYtj0IvN0KCnVtTAAUrJhBrDo8cKCgvFABCLQYTAGoVwGJbjzjFKA%2BCCjSCDl9rRkgKjyDEL9uFWxtxNuePBS7IhiAsJ%2FGbigILQED2iEIEBJop4jCHShImYlAkEjDAWrtDOVKkwEIRwilEBBwquuOmY0cIilwQuCEwEQ4ISpRQmUPgnqECHWJeZPSuwyEQQ4bYhFQgiDEXhhxo0TIG6CMS1gROEpQGih4dMSA9KGYOAIlaNoUYwKOHCCQQIzUByIiCFIAFMiqUdIeqmFleLhQHTSh2K26hAiSM2t3Lt6%2Ffv5sCAQAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQiAWRjRQ3BAqUihwoKByEIJOQBaIABJ0vggoJRBeZjjQ3N0KCp1IDAAUyRzkHKI9BqBQAQgMoLgBSNgwNDZ%2BOOJ0oC4Igr3XMJl6ljCCcL8OFagd0Dh2RBS7hhSBPIeeaiwIkODjriC4EBBOLQAdjZLpAwJXoVCcaio4wicJQgwdFBlEgTJQng0WLDxNRIHCDn6IJHsiAAVPhWTxCBTp0eNUoHbxCAmLEeOmoQLAXyAoxsCLHSE5HJKR5BCFAUJgdWqywgfQAFUISL26cQ6IDqQNIIDiSqNUJCAAFDdyI8Thq0I2ugx4UPQlgQidabA4LFSDxM67du3jz6qUUCAAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQkZKECkBAApOJQCgoD5mDBQWDBJwcggUDUwSQHTc3QoKkKEGCTzMODjSPOJwvHQBCAwMUAEErDkVVLo8TnCgLggIggiwWRUd1kCAcKC%2FEhVJVeRcKkQUu34UCNwPln4kFQg8Pv4oUBAQTixN5NW1iDVYlkoVCV6IfZLp0iRAhhyKCBhEVaUKR4h17BG7oU%2FTgjpiPOWi9o6TAXaNz9dRt2ZLSUYEg3ZYVysPjyoaIjUg42wgCEwAjVs7YMQDpQS9dJF7c%2BFXESlAv2jKSiMUJCAAFErBwMWVu0I2qgxZMe9cMBayRhAqQkIm2rdu3cATjNgoEACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2Oj5CRkoQKQDgCk4k4KCgPmYMFBYMEnByDJBwUkB03N0KCpChBgkAsBiGQE5wvHQBCAwOqJCEydWyYjg%2BcKAuCAiCCHMUzuI8CHCgvqoU4dR8J0JAFLtuGOEHhn4gFNCQkyIkUBAQTiwtEBx4mSECKsSg0FH3YsKaNQST%2BlgVM5GDMmDAObSiSd6OeIhJHvnyZYwOHukIKFKRjNK6XIQpvLph8VCBINheGjrjBMufVIxLLLIIIKIALDzQ%2B6Ch4pCxbQBIvvrABgIQHjytYTjwCQeAGCVgoPJApoOBLmadeIokSdAMFka0AaHjAomTAJ10XFIiA4nD1UwESC0Z%2B3Mu3r9%2B%2FkAIBACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2Oj5CRkoQCEwsFk4k4KCgLmYOYgwScHIMULpEdBDdCgqMoQYITLyg4kBOcLx0AQgMDFLycLS%2BQC5ydggIgsigtakCQBRwoL8CFQi1TKKGPBS7WhkKXn4unHdyIFAQEE4tCK0VONh%2Btia8oNIoxBw0VFR5bFN3Ll%2BjCl4MHYyhSd6OdIiFEJNy54wAVOUIgMnZzscuQixVsOnYLQs0iIRsZNDQw2YjEMYdPSinggkUFngMiGT3IlQ%2BICjQBq%2FjAggGPl0cgVpEQ9ELFjjEFQHgYimGEgGiDWvjYQQaTEAg%2BUvz49OKKjiKm2IT8ROFIlZwXCOPKnUu3LqRAACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2Oj5CRkoQFJCSTijgoKAuYiASbHIMdHZEKHARCgqAoQYITLy%2BXjw%2BbL6VCAwMUAEKbrZALv50AAiCvv6qPBRwoL7yFvig4kgUu0IYUNJ6MChTHixQEBBOLHVMrHytSi6wo24ksVUVISD%2Fwn7%2F4h1MM%2Fgw2XCgSd6PcwDdIbBBhx62QAAUClrkoZYhGDBkKIhUI4kxgoR9NIiDYx4jEr3ICWrgCIUYDFCp5KDaq5WxbDjlYDABwIEJDEiorHoEgcOMSBRU64BgpAEJCzyQmCkCSCoAEjKRhpLrwICKKBU9tkv4YRMEARk8TjvyQ2bCt27dwBONGCgQAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6PkJGShAUkJJOKEygoC5iIBJscgyAgkQocBEKCoChBgg8vAzSQD5svHQBCAzcUuZsoOJALv50AAgKCmpuqjwUcKC%2B9hUKbwZEFLtKGFLOeiwIgBYwUBAQT3y9qCSzMiawo3Yg3dUMXFyeL7%2FGHUhb%2BFgYWUeBw45yiDgZmvIlxyVshAeKaucBliIYMNaUgFQgCzYUhL2PaVNHWiMSvcwKeAAEA4ksELnGqKHhUC9osBDxE4PtAJQKYODEegSBw4xIFPFbKbCgAIo8SnzkiOoooBEPSNuJo3KHS5Y2nEVZ4lBjUIc2UmZgm2HCA1qHbt3AF48qVFAgAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6PkJGShAUkQpOKDygoC5iIBJscgyAFkQocBJcAoChBgg8vNx2Qmigvs0IDNxQAQpsoD5ALv50AAgKCE7%2BqjgUctryFQi8oOJIFLtGGHTSejAWljBQEBBOLBUADA0DIiqwo3YkPTy1padbuv%2FGIQTL%2BMq4UUeBww5wiEC1OnJACwpshcJCwzdrG4knDiEFQSAlh6AIEDx8mOnKx6cgcYyFQGDvQpgadDxcbaXqDxQsAJz7wGAAwJE6bEXMSPALxQgwDARSS2IFhwliVMD9%2FQBJQDAcWOz7aIKPgxEibGJgWqMCqVZCCjTEjUVBix80dh4UQLuChkgZuoQck7Ordy5dQIAAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQkZKEBSQuk4oPKCgkmIgEmxyDAgWRChwEQoKgKEGCDwMEIJCaKC8dAEIDNxS5mygLkAu%2FwQCkghO%2Fqo8FHLa9hUIvKDiSBS7Qhh00noyljRQEBBOLBUC71YusKNyJw7%2FZn7%2FtiO%2Bb8YcUHDfkigVBLwak60bwWhABhkCguIEQUrMiWH4YksHAxhYFkIQgMLMDgrE0L4w5qXDnCJuGjWZY6QFnBoAiGZQkAGBgDsk8LR6lyeAmj4AOS1LguWPMyxwPEthAIvFAEAkmKUR8KdXBgok7UjA9jVrjm4AbrjC5aJIigwmChTxEfYOW0IISbwgwtp1Lt66gQAAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQkZKEBUIuk4oPKCgkmIgEmxyDBZIKHARCgqAoQYIPAxwCkJooLx0AQgM3FLibKKmPC74LggKkABO%2BvI8FHLXLhEIvKDiSBS7QhR00nozHjBQEBBOLBUC6xIurKNyJwpu26r7tiEK%2B8YoUHDfkigU4BDgA60YQSAkZsgoJCILjm6MJSXrIKWEohIMVaRI6qrJDB5w5AAQ8uSFoho0SH1pAMqEjS5kVAIg0GcMCgBoENoh8ePCohYYUTgR0GBNliRMABergJAIEkpB0QpZEoXKAFIgtPwyAwBQ1ipIK3255okHG6x2Che54rYOWEIkPdQi2tp1Lt66gQAAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQkZKEBUIuk4oPKCgkmIgEmxyDBZIKHARCgqAoQYILN0ECkJooLx0AQgM3FLibKKmPC74LggKkABO%2BvI8FHLXLhEIvKDiSBS7QhR00nozHjBQEBBOLBUC6nYurKNyJwpsDsorr7YhCvvGLFBw35IoFOAhwqNetGw4HJ%2BQVInEp0gQlWXhYMHRDBosg3xodgSOnTAUABV60AnBixZYpIx15kGPGzRAAXrjUeAJAioUVbNSAePQECp4iAhSs6WKkBMgpXlac2PlICDEALsJ0iXOElIAXCaphchGnS5g8GbvREOPVRsFCR7waOBvtggGmbAbjyp0LIBAAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6PkJGShAVCLpOKDygoJJiIBJscgwWSChwEQoKgKEGCCzdApI%2BaKC8dAEIDNxS4myi8jwu%2BC4ICshO%2BwI4FHLXKg0IvKDiSBS7PhB00noyyjBQEBBOLBUC6qYurKNuJJL433ogDagkxnYlC7%2FGHLWFNJrcSFcBBIAi7RR2E7ONGCAeRISAOubgUKUgXM24cGKIV6xGJMGWu%2BJAAoAABagBQhJCC4sEjByHdqFgB4EINCQMABDmxksAjCXbcpMgjQIGJNSZopuQpypGUCFGK3KJRYw0djSWBAFEAycU4QTQgrJlDhCEhCnPWfLFglpADtWoN2g6iIIOFALl48%2BYNBAAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQkZKEBUIuk4oPKCgkmIgEmxyDBZIKHARCgqAoQYILN0Ckj5ooLx0AQgM3FLibKLyPC74LggKyE77AjgUctcqDQi8oOJIFLs%2BEHTSejLKMuTcTiwVAupeKQmBKNRI3iiS%2BBIskKT09Ox%2Fo8YwXTCk12AoVwEEgSMBDHVx442ZogoUYIA65OAcJyBgfKvIVgoci1iMhbXykEJEHADliAIAMe%2BQExkgodQBskVClFUcUohqB4JIiQxQHBUAwaODkhKAJ0h48YpBBg5OIFCQ0yBNTEAWKjSjIOKHA6p0GCIYwJAQiD9gtYwkZOOAkZ1qTHAeovZ1Ll24gACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2Oj5CRkoQFQi6Tig8oKCSYiASbHJ4ACkEEQoKgKEGCJARABZCaKC8dAEIDNxS3myi7jwu9C4ICsQATvb%2BOBRy0yoNCLyg4kgUuz4QdNJFCqI3GjCsYMGudiQVAuduKQhg772%2BKJL0EiyQZWVlwM%2By9ootDmoiYg61QARwEghQ8pMAFuFGGHswwAOIQhYWLcLQRAeWCIRLSYD0SAgEPEypVWl0CAETYoyomlXAxAEDNjyHDhPQC4ghEGyZNuswoIIBIkRlSBD148cJbIydNIhCpSMNGkQ8sBnVQAKnDFDVcAXQoUsSLGoiEBHwoYgEFWkI4DS4kWPdW0MO6ePPWDQQAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6PkJGShAVCLpOKDygoJJiIBJscngAKQQRCgqAoQYIkBEAFkJooLx0AQgM3FLebKLuPC70LggKxABO9v44FHLTKg0IvKDiSBS7PhB00kS6ojcaMQyIYI52JBUADBNiGQnhWcHAXiiS9oopCUWZmZW%2F49oxidEnigR0lHASCGDSkgAa4UYYWXEgg4BCFhYomzFHChY0hEtKAQHJRgQqZOF4E0VAgCEgvb40cLCETZoQaAFJipNklpNcERyDm0FwTo4CAIUPUUAPw4MUAjIaIhGnzpmKHGUOm3CMFAlKHEC2MgbgwJMFWiIJYDDkxDO0gBTcKfrqdS7euXUOBAAAh%2BQQJBQAAACwAAAAAIAAgAAAH%2F4AAgoOEhYaHiImKi4yNjo%2BQkZKEBUIuk4oPKCgkmIgEmxyeAApBBEKCoChBgiQEQAWQMi0oLx0AQgM3FLibKLyPORC0C4ICsQATvsCOQFBfT8yDQi8oOJI4DsWHHTSPBS4kQgKNyIokXxoZIhuoiQVAAwS3iV52djw8ZQ7nvqKJM9wIFOhFkRBfrBKRoNMEypIGl97heKVgUSUSEUchIsEmBDlDFKQ5WnAgTo0EhkhUAwKJBoI4G%2BjUEaQAhCAgvtw1emNkwxwJTwAEeTLg1sFN2xgJkLDhS4UTAAqwoMUSwAN5FR3NcMqGnAA1tP4BOAZJgZQXyAqkoaqxEJAnLw1EtqWQta3du3jzKgoEACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2Oj5CRkoQFQi6Tig8oKCSYgx0FgwSbHJ4AaU0%2FQoKjKEGCJARAoY9zPSkGHQBCAzcUu5sov48SOz1GD4ICtBPBw444STtlT4ZCLyg4kjg%2FbLSFHTSPBTSWAo3fiSwbTUxJX52JBUADBLqIIEZY%2BzAwSIokgr3CtyGDQYMOFAkJBkRRiw1kyIxhEA9RARyyQCwCIUSIOFOJXCR4km4QhWePSDiZc6eFIRLYGj6iUIXOgTwJBIHQCABHsI%2BN2Jg4gODHDQAwB%2BhauGnBIyIHGCBxCaCVzAX1eDZSk6eImlAFbmwaCKBASUYTkonapA0kIV4EDRS4LWR2rt27ePMeCgQAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6PkJGShAVCLpOKDygoJJiDFEKDBJscngAtTSlFgqMoQYIkBEAFkB5ZOlYGAEIDNxS7myi%2FjwxwWjsSggK0ABPBw444VHBnF4ZCLyg4khMlW8yFHTSPBTRCNOCK6Yhpc2RLER6hiQVAAwQdiSA1UVEaGniIKCIR7BUiAXSaKFQ4Q5GQYEAUSTHRps0IG%2FMQFcAhC8QiEC5cQDN1iEaaG%2BsEURjpyIWFPD9uGCKRLeIjEG%2BOVPmAQhAIjwBwBBvnCIWTKl5iPABAc0C%2Bh5s6Fa1i4cIAVptsLrgHtJGCE2xkAihwY5PBsSkZCSDEYdMCkoUOKHDg0BWu3bt48%2BpdFAgAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6PkJGShAVCLpOKDygoJJiDNEKDBJscngAtUBlVgqMoQYIkBEAFkAdmVmUyAEIDNxS7myi%2Fj0c8Z1Y5ggK0ABPBw44TZDx2dYZCLyg4khNeMsyFHTSPBRQuNOCK6YhSB2JhcTnjiQVAAwQKiQIVXV0RS0suKCIRDIi%2BO2MSJhyiSEhBRQMYmDDRwME8RAVwyAKxSAAFGh1MKerwwuAhCtAeUYjhhc0DQySymXx04kOdKdsAgOAIAMezRyRW1DnxZFzMASEdbrrkyAUbGWleAmhlcsGNIAIg2esEoMCNTa8ErZsUZNMCkYUUBJkwFq3bt3AF48pFFAgAIfkECQUAAAAsAAAAACAAIAAAB%2F%2BAAIKDhIWGh4iJiouMjY6PkJGShA8XLpOECxOEX01SJJgAU0l4JYIUKkpSHKEVblduRAAUGWQoQYIkBEAFj04wbnZoBgBObTcUAEIozMmOD2EwaDwVghO9ABPMKM6ON9E%2BFoZCLyg4kg8fFwKHHTSQ7hTYi%2FOJL0dzEBBO74kFQAMIKEgkIM%2BaNm3EGGGjiMQ2IP6QfJk4kViiZcwgJuJQBQECJxe6HSqAYxeIRQI6UBgYSpECHEIQURDpCESIBE8uFSJRTuOjF1OeoNgEAMRJADi20XQZQuiLdzwHdFC2TWejAgNQvAAFgEBGQQtu4KjHSMECqzeY4RJEdhIQZgsPWhoSMOGa3Lt48%2BrdiykQACH5BAkFAAAALAAAAAAgACAAAAf%2FgACCg4SFhoeIiYqLjI2Oj5CRkoQLRTMKk4JCFyGEdDs6R5kCBxgiFoIUeDs9Jpk0XBkpKg4AFBqsRIIkBEAFjwwaGVgYMgA2PFgoAEIozhSPExsaKjASggQPghPOKNCPHCMaIjOGQi8oOJIkKzEChx00kAoUHb%2BM94pCFjkSEiXfEBUAMoAApkRDGlTw4MFEAkUkugFRFIOBRYss9ElU5IKNAwcfTnRQVABHLxCMFChAmWmRABcjD1EI%2BKgABxQvXBgigW4iJG7OJggCwRJHN5qMCDh7IY%2FngJHNnkECgpMENmc%2BF9xQB6mAi4MAbjgLMihfS6MorLY0JOCB2rVwB%2BPKnUtXbiAAOwAAAAAAAAAAAA%3D%3D) no-repeat center center}#cboxTitle{position:absolute;bottom:-25px;left:0;text-align:center;width:100%;font-weight:bold;color:#7C7C7C}#cboxCurrent{position:absolute;bottom:-25px;left:58px;font-weight:bold;color:#7C7C7C}#cboxPrevious,#cboxNext,#cboxClose,#cboxSlideshow{position:absolute;bottom:-29px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAABLCAMAAACx6hDAAAABj1BMVEVPT0%2Fe3t7b29vS0tK7urq5uLjq6uqZmZmSkpJaWlrU1NTj4%2BPFxcWvr6%2BgoKBbW1u3t7c9PT27u7vCwsKsrKxiYWGqqqq5ublbWlpeXV2Xl5fExMSbmpq6ubmNjY18fHzy8vIrKystLS0sLCxNTU0uLi4wMDDNzc05OTns6%2Bvl5eUvLy%2Fq6ekqKipMTExDQ0M4ODgyMjI2NjbZ2dk6OjrY2NjMzMxLS0vAwMBCQkLo5%2BdHR0cxMTFKSkpBQUHv7u43NzdISEhFRUVRUVHx8fE7Ozs8PDwzMzNJSUnp6elGRkZQUFDr6upeXl7t7e1gYGCoqKjv7%2B81NTWKiorn5uZERESCgoJdXV3p6OhOTk51dXVAQEA%2BPj6np6fu7e2%2Bvr5cXFxSUlKJiYnOzs7s7OxTU1P29vbw8PB2dnZfX1%2Fm5eV4eHifn59qamqmpqbQ0NCOjo7Kysqzs7P4%2BPiDg4Otra3z8%2FM%2FPz80NDSrq6u%2Fv7%2FPz890dHRpaWmBgYH5%2Bfn08%2FNoaGjPzs7%2F%2F%2F%2BioqIRuwm9AAAGHUlEQVR4XsTXZY%2FjPBAA4PwUQxjKzMzLjMfMzPfiD7%2Bxm23ibjZVu5u7%2BVBL7ljyI3scW9pZOv59%2BgdjZ%2BNOeyzlyzXtv9B408%2FUynlp3L4r7bzYWC5E4a4xvAQYGkEA2%2FDG2GL6biBmHbGoz9pVhTBkuRCEhx0TzVNKKNspBczXdHtLnTRa9yC78MdhAND%2B6xaLh3%2B7bf1mhO3guEpooJfbaH56h2i7gOx5oCmlckBkwFzqGIi%2B9LdocllofMSUUnzrndui6wo5bnxVzJyC8BztO06A0HFeM4IIxLgBRAZsYAeI%2FvSf6DxASAkhFIS8vbaQ6aSw4ExBWNoyDyjFAUKM6Q9zy1ddEwBSSnStY3c0ncCo78j2px%2BYW6VLQqIoCgEhb68p5L4jWY6xyIsR4yHLgASiJ4S5JohCaICQQn8756suI5vC0KlkNKRlFBiEU9mJkN7KdYaRCpkvVh00y8HRbA6qMZkRZ8L7aJMolmUpiMe6u215ENafOEPe6Qlbk0K22tvoqTCGwob1lpyn0zN0PzIhB8aqzdFevFgLjGJ8b9TMA3EmrJuPAaiqqvVgbe3g9rFp8834z%2B2DtbUHvF8h%2B151QfUliKVWWKgWSUBFekI3%2FTGqzwstV2jdgFCulj%2BEjfhSbLlEELIDv82A0wmzXffVYJMqOBTcLkQhrLo8om5VkhAg1BnQE16kt81HpWiEfAmb%2F4cPeV5rDWKu8BAVGpRJ2IQ5ERemQsy73X6%2BGXdnRK1bSfb7%2FWSlqwHQJ%2FTSCyD3hIorVG5CKFdHr8KF907j5ao8FRppBxMFgDDfpCgkU%2Fi0n2DHq0UbPXGFz5AtHEy%2B9KwRkRCWMP4tXKhlTlpVWZqtIVBAEryGSRYBa6jyP9T5NfTSYQ0j2qVH%2BXLx3gJh4nRvEAPh3Ys6nNUbq8OCWIccLtahlpmdNLom1qGb3k5HVofSUX5UWyDMpXpxVoggdM9SIPrO0olwllZUAL4XzlId6NOwFF08S3l6hGcpO2iqrZNFwu1esRljQ0K%2Bh3XEg%2Ffrm8L3MEEU6O4%2BgR9Y9IT%2Fe8jTyWYE30NB%2BGk5Ib%2FT6ErInUbzXVKMdAMTCF1Dmk4gcCM9d6dh6RELtQXCz11BGHovpYH3UgorZ8NqUhh1jGx%2FevC9FOQg5O1vFa72thj73xZ47m2xv9LbInqh%2BD4MffABUeLAp5wYwfswEiHEcKU3fnalN37kwl%2Fs2M9LAkEUB%2FC0ml12VgmKLh2%2BcwtBOkWRUdIvIpJayYNdgkIIukUDdYz8x2tnt96OjQMS8hbzexFhDn6QN2%2FeIyHTnoZByJD%2FKJwL58L5TdM%2FFY5uIZ3dQvx0C2l3C%2BHuFsNKmuF7%2Ftlm6vixdnR8vfm744tQV%2FOOX9UJEen4SBpDpKm85J9Mr7Ya4BACK4ZgAYGUaICAIdLxmroro7DVFQHGCFEX1ss7BRpi0wBTYrN4PBDdVumE5reOFSKsFqcnqZERVQaElh3r%2BA0dn5pwQ3mzOiIcqBgmjo1wZoiLE%2FA3LEBOLUzAMInVYMrCtUVv1m1hWyTIEgZfSYTZCIt6%2BiVEFqqurPoopiJHhEhUe7rCpSdvlkloLvwQViKziYpAoeoiogNIQoTysVUSYV9FGl4hUXoWkYAOIXREcl5hQwJeIaW4EQ4A0D3qEAKyMfP%2FYW%2B261Aw16H%2FLu3MyF36936o6Qpy9ENW4eRvmv6kbxpmIcO7lEHIMFuwCf3zYaSaE8%2BH%2FEL2GZ9BWOY9zWc7d%2BwSMQzFcbzDCTqVCnJ3ok4HdrpAKZRSWnInicUOQiVISYeem25O6Xz%2B4%2FYJWaokg8Px4P3%2Bgw%2Fp%2BL79p%2FAkQyEkIQlJSEISkpCEJCQhCUmofcJWIhXa%2B1KfcJlIBsIDSmEzCatLt%2FCW96pGLNzu2LlbeBcbe%2BeNURhs6r2%2BcQGvuczhVh%2BtsMsK1qdX769DuLqYLQyHdc%2Blht4CqfDnMy0LZmScjI%2B%2FN7Y8llpNT4hYGABRVSYSIp1PCBmZygJRCn1pV87UHsKurnnAK3Tnebu6zCDOgydEKZwnlts%2F%2BsrOBpY4hdbYOBtZACIWghHm7JyRCu3efEP6T4Vv0sK5wmQ8JLkAAAAASUVORK5CYII%3D) no-repeat 0px 0px;width:23px;height:23px;text-indent:-9999px}#cboxPrevious{left:0px;background-position:-51px -25px}#cboxPrevious:hover{background-position:-51px 0px}#cboxNext{left:27px;background-position:-75px -25px}#cboxNext:hover{background-position:-75px 0px}#cboxClose{right:0;background-position:-100px -25px}#cboxClose:hover{background-position:-100px 0px}.cboxSlideshow_on #cboxSlideshow{background-position:-125px 0px;right:27px}.cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0px}.cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px;right:27px}.cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0px}#loading{position:fixed;left:40%;top:50%}a{color:#333;text-decoration:none}a:hover{color:#000;text-decoration:underline}body{font-family:"Lucida Grande", Helvetica, "Helvetica Neue", Arial, sans-serif;padding:12px;background-color:#333}h1,h2,h3,h4{color:#1C2324;margin:0;padding:0;margin-bottom:12px}table{width:100%}#content{clear:left;background-color:white;border:2px solid #ddd;border-top:8px solid #ddd;padding:18px;-webkit-border-bottom-left-radius:5px;-webkit-border-bottom-right-radius:5px;-webkit-border-top-right-radius:5px;-moz-border-radius-bottomleft:5px;-moz-border-radius-bottomright:5px;-moz-border-radius-topright:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top-right-radius:5px}.dataTables_filter,.dataTables_info{padding:2px 6px}abbr.timeago{text-decoration:none;border:none;font-weight:bold}.timestamp{float:right;color:#ddd}.group_tabs{list-style:none;float:left;margin:0;padding:0}.group_tabs li{display:inline;float:left}.group_tabs li a{font-family:Helvetica, Arial, sans-serif;display:block;float:left;text-decoration:none;padding:4px 8px;background-color:#aaa;background:-webkit-gradient(linear, 0 0, 0 bottom, from(#ddd), to(#aaa));background:-moz-linear-gradient(#ddd, #aaa);background:linear-gradient(#ddd, #aaa);text-shadow:#e5e5e5 1px 1px 0px;border-bottom:none;color:#333;font-weight:bold;margin-right:8px;border-top:1px solid #efefef;-webkit-border-top-left-radius:2px;-webkit-border-top-right-radius:2px;-moz-border-radius-topleft:2px;-moz-border-radius-topright:2px;border-top-left-radius:2px;border-top-right-radius:2px}.group_tabs li a:hover{background-color:#ccc;background:-webkit-gradient(linear, 0 0, 0 bottom, from(#eee), to(#aaa));background:-moz-linear-gradient(#eee, #aaa);background:linear-gradient(#eee, #aaa)}.group_tabs li a:active{padding-top:5px;padding-bottom:3px}.group_tabs li.active a{color:black;text-shadow:#fff 1px 1px 0px;background-color:#ddd;background:-webkit-gradient(linear, 0 0, 0 bottom, from(#fff), to(#ddd));background:-moz-linear-gradient(#fff, #ddd);background:linear-gradient(#fff, #ddd)}.file_list{margin-bottom:18px}.file_list--responsive{overflow-x:auto;overflow-y:hidden}a.src_link{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAPCAYAAADQ4S5JAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw%2FeHBhY2tldCBiZWdpbj0i77u%2FIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8%2BIDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowNTgwMTE3NDA3MjA2ODExODBENEVBMTkyQ0U2NTYzMSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1NzdBM0ZCN0E0NzQxMURGQTFBM0FBMTZCRTNFQjA0QiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo1NzdBM0ZCNkE0NzQxMURGQTFBM0FBMTZCRTNFQjA0QiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI%2BIDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjA1ODAxMTc0MDcyMDY4MTE4MEQ0RUExOTJDRTY1NjMxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjAxODAxMTc0MDcyMDY4MTE4MEQ0RUExOTJDRTY1NjMxIi8%2BIDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY%2BIDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8%2BI%2B%2FpuQAAAUVJREFUeNqEksFKhFAUhu%2FVIdBJorUtYkAQiQgqWrVq0RNY9Aq9hPou0SKfYRbSJmrRohBDCKKR1tIUgtr0HzmXxCb64cPj9b%2BHc%2F%2BrFFAQBLIoiqM8zydZlq3Smuu6c8dxnm3bnkZRtIBoWUgyp2l6Esfxhlgi3%2FdnnuddhWHY7dCoM5vn4AZcgEtwCz7oG3lUgxGNwfUDuAM6c08NwV7PIzQ1M5Szgcwj5oU%2B9DydQUn2npI3%2FpJGaXDt8HPBtBSWSkxKKQiNomPjFtgHa8ACO2Cz6%2FTjERrlTNGhpjkPwBk4BbtgbJpmXZblVG3QkyShrFN0MS3LGldVtWIYhmiaRrRtK%2Bu61nFPX%2FDOugOqG%2ByLZuURj3npk%2B%2FnXRN%2F6xG8cW2Cw2Gsy3TdqynFyX8bXsGT%2Biu61OgMQwZaB%2Bdgm16%2BBRgApCh%2B7pwD4GQAAAAASUVORK5CYII%3D) no-repeat left 50%;padding-left:18px}tr,td{margin:0;padding:0}th{white-space:nowrap}th.ui-state-default{cursor:pointer}th span.ui-icon{float:left}td{padding:4px 8px}td.strong{font-weight:bold}.cell--number{text-align:right}.source_table h3,.source_table h4{padding:0;margin:0;margin-bottom:4px}.source_table .header{padding:10px}.source_table pre{margin:0;padding:0;white-space:normal;color:#000;font-family:"Monaco", "Inconsolata", "Consolas", monospace}.source_table code{color:#000;font-family:"Monaco", "Inconsolata", "Consolas", monospace}.source_table pre{background-color:#333}.source_table pre ol{margin:0px;padding:0px;margin-left:45px;font-size:12px;color:white}.source_table pre li{margin:0px;padding:2px 6px;border-left:5px solid white}.source_table pre li:hover{cursor:pointer;text-decoration:underline black}.source_table pre li code{white-space:pre;white-space:pre-wrap}.source_table pre .hits{float:right;margin-left:10px;padding:2px 4px;background-color:#444;background:-webkit-gradient(linear, 0 0, 0 bottom, from(#222), to(#666));background:-moz-linear-gradient(#222, #666);background:linear-gradient(#222, #666);color:white;font-family:Helvetica, "Helvetica Neue", Arial, sans-serif;font-size:10px;font-weight:bold;text-align:center;border-radius:6px}#cboxClose{position:absolute;top:-14px;right:-14px;width:30px;height:30px;background:#000;border:4px solid #fff;border-radius:100%}#cboxClose::before{text-indent:0;content:'×';color:#fff;font-size:23px;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}#footer{color:#ddd;font-size:12px;font-weight:bold;margin-top:12px;text-align:right}#footer a{color:#eee;text-decoration:underline}#footer a:hover{color:#fff;text-decoration:none}.green{color:#090}.red{color:#900}.yellow{color:#da0}.blue{color:blue}thead th{background:white}.source_table .covered{border-color:#090}.source_table .missed{border-color:#900}.source_table .never{border-color:black}.source_table .skipped{border-color:#fc0}.source_table .missed-branch{border-color:#bf0000}.source_table .covered:nth-child(odd){background-color:#CDF2CD}.source_table .covered:nth-child(even){background-color:#DBF2DB}.source_table .missed:nth-child(odd){background-color:#F7C0C0}.source_table .missed:nth-child(even){background-color:#F7CFCF}.source_table .never:nth-child(odd){background-color:#efefef}.source_table .never:nth-child(even){background-color:#f4f4f4}.source_table .skipped:nth-child(odd){background-color:#FBF0C0}.source_table .skipped:nth-child(even){background-color:#FBFfCf}.source_table .missed-branch:nth-child(odd){background-color:#cc8e8e}.source_table .missed-branch:nth-child(even){background-color:#cc6e6e} diff --git a/coverage/assets/0.13.2/application.js b/coverage/assets/0.13.2/application.js deleted file mode 100644 index 57b4005582..0000000000 --- a/coverage/assets/0.13.2/application.js +++ /dev/null @@ -1,7 +0,0 @@ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(le,e){"use strict";function g(e,t,n){var r,a,i=(n=n||xe).createElement("script");if(i.text=e,t)for(r in we)(a=t[r]||t.getAttribute&&t.getAttribute(r))&&i.setAttribute(r,a);n.head.appendChild(i).parentNode.removeChild(i)}function m(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?se[he.call(e)]||"object":typeof e}function s(e){var t=!!e&&"length"in e&&e.length,n=m(e);return!ye(e)&&!be(e)&&("array"===n||0===t||"number"==typeof t&&0D.cacheLength&&delete n[r.shift()],n[e+" "]=t}var r=[];return n}function n(e){return e[R]=!0,e}function r(e){var t=k.createElement("fieldset");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function a(t){return function(e){return ue(e,"input")&&e.type===t}}function i(t){return function(e){return(ue(e,"input")||ue(e,"button"))&&e.type===t}}function o(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&oe(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function s(o){return n(function(i){return i=+i,n(function(e,t){for(var n,r=o([],e.length,i),a=r.length;a--;)e[n=r[a]]&&(e[n]=!(t[n]=e[n]))})})}function g(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function x(e){var t,n=e?e.ownerDocument||e:Ie;return n!=k&&9===n.nodeType&&n.documentElement&&(N=(k=n).documentElement,j=!De.isXMLDoc(k),L=N.matches||N.webkitMatchesSelector||N.msMatchesSelector,N.msMatchesSelector&&Ie!=k&&(t=k.defaultView)&&t.top!==t&&t.addEventListener("unload",ie),ve.getById=r(function(e){return N.appendChild(e).id=De.expando,!k.getElementsByName||!k.getElementsByName(De.expando).length}),ve.disconnectedMatch=r(function(e){return L.call(e,"*")}),ve.scope=r(function(){return k.querySelectorAll(":scope")}),ve.cssHas=r(function(){try{return k.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),ve.getById?(D.filter.ID=function(e){var t=e.replace(re,ae);return function(e){return e.getAttribute("id")===t}},D.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&j){var n=t.getElementById(e);return n?[n]:[]}}):(D.filter.ID=function(e){var n=e.replace(re,ae);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},D.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&j){var n,r,a,i=t.getElementById(e);if(i){if((n=i.getAttributeNode("id"))&&n.value===e)return[i];for(a=t.getElementsByName(e),r=0;i=a[r++];)if((n=i.getAttributeNode("id"))&&n.value===e)return[i]}return[]}}),D.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},D.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&j)return t.getElementsByClassName(e)},I=[],r(function(e){var t;N.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||I.push("\\["+ke+"*(?:value|"+$+")"),e.querySelectorAll("[id~="+R+"-]").length||I.push("~="),e.querySelectorAll("a#"+R+"+*").length||I.push(".#.+[+~]"),e.querySelectorAll(":checked").length||I.push(":checked"),(t=k.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),N.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&I.push(":enabled",":disabled"),(t=k.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||I.push("\\["+ke+"*name"+ke+"*="+ke+"*(?:''|\"\")")}),ve.cssHas||I.push(":has"),I=I.length&&new RegExp(I.join("|")),W=function(e,t){if(e===t)return A=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!ve.sortDetached&&t.compareDocumentPosition(e)===n?e===k||e.ownerDocument==Ie&&p.contains(Ie,e)?-1:t===k||t.ownerDocument==Ie&&p.contains(Ie,t)?1:_?de.call(_,e)-de.call(_,t):0:4&n?-1:1)}),k}function l(){}function m(e,t){var n,r,a,i,o,s,l,u=M[e+" "];if(u)return t?0:u.slice(0);for(o=e,s=[],l=D.preFilter;o;){for(i in n&&!(r=V.exec(o))||(r&&(o=o.slice(r[0].length)||o),s.push(a=[])),n=!1,(r=G.exec(o))&&(n=r.shift(),a.push({value:n,type:r[0].replace(Ne," ")}),o=o.slice(n.length)),D.filter)!(r=K[i].exec(o))||l[i]&&!(r=l[i](r))||(n=r.shift(),a.push({value:n,type:i,matches:r}),o=o.slice(n.length));if(!n)break}return t?o.length:o?p.error(e):M(e,s).slice(0)}function v(e){for(var t=0,n=e.length,r="";t+~]|"+ke+")"+ke+"*"),J=new RegExp(ke+"|>"),Y=new RegExp(z),Z=new RegExp("^"+B+"$"),K={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+U),PSEUDO:new RegExp("^"+z),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ke+"*(even|odd|(([+-]|)(\\d*)n|)"+ke+"*(?:([+-]|)"+ke+"*(\\d+)|))"+ke+"*\\)|)","i"),bool:new RegExp("^(?:"+$+")$","i"),needsContext:new RegExp("^"+ke+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ke+"*((?:-\\d)?\\d*)"+ke+"*\\)|)(?=[^-]|$)","i")},Q=/^(?:input|select|textarea|button)$/i,ee=/^h\d$/i,te=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ne=/[+~]/,re=new RegExp("\\\\[\\da-fA-F]{1,6}"+ke+"?|\\\\([^\\r\\n\\f])","g"),ae=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},ie=function(){x()},oe=f(function(e){return!0===e.disabled&&ue(e,"fieldset")},{dir:"parentNode",next:"legend"});try{E.apply(ce=fe.call(Ie.childNodes),Ie.childNodes),ce[Ie.childNodes.length].nodeType}catch(se){E={apply:function(e,t){Le.apply(e,fe.call(t))},call:function(e){Le.apply(e,fe.call(arguments,1))}}}for(c in p.matches=function(e,t){return p(e,null,null,t)},p.matchesSelector=function(e,t){if(x(e),j&&!q[t+" "]&&(!I||!I.test(t)))try{var n=L.call(e,t);if(n||ve.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(se){q(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(re,ae),e[3]=(e[3]||e[4]||e[5]||"").replace(re,ae),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||p.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&p.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return K.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&Y.test(n)&&(t=m(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(re,ae).toLowerCase();return"*"===e?function(){return!0}:function(e){return ue(e,t)}},CLASS:function(e){var t=H[e+" "];return t||(t=new RegExp("(^|"+ke+")"+e+"("+ke+"|$)"))&&H(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,a){return function(e){var t=p.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===a:"!="===r?t!==a:"^="===r?a&&0===t.indexOf(a):"*="===r?a&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;De.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?De.find.matchesSelector(r,e)?[r]:[]:De.find.matches(e,De.grep(t,function(e){return 1===e.nodeType}))},De.fn.extend({find:function(e){var t,n,r=this.length,a=this;if("string"!=typeof e)return this.pushStack(De(e).filter(function(){for(t=0;t)[^>]*|#([\w-]+))$/;(De.fn.init=function(e,t,n){var r,a;if(!e)return this;if(n=n||He,"string"!=typeof e)return e.nodeType?(this[0]=e,this.length=1,this):ye(e)?n.ready!==undefined?n.ready(e):e(De):De.makeArray(e,this);if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:Me.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof De?t[0]:t,De.merge(this,De.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:xe,!0)),Pe.test(r[1])&&De.isPlainObject(t))for(r in t)ye(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(a=xe.getElementById(r[2]))&&(this[0]=a,this.length=1),this}).prototype=De.fn,He=De(xe);var Oe=/^(?:parents|prev(?:Until|All))/,qe={children:!0,contents:!0,next:!0,prev:!0};De.fn.extend({has:function(e){var t=De(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,ct=/^$|^module$|\/(?:java|ecma)script/i;ot=xe.createDocumentFragment().appendChild(xe.createElement("div")),(st=xe.createElement("input")).setAttribute("type","radio"),st.setAttribute("checked","checked"),st.setAttribute("name","t"),ot.appendChild(st),ve.checkClone=ot.cloneNode(!0).cloneNode(!0).lastChild.checked,ot.innerHTML="",ve.noCloneChecked=!!ot.cloneNode(!0).lastChild.defaultValue,ot.innerHTML="",ve.option=!!ot.lastChild;var ft={thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};ft.tbody=ft.tfoot=ft.colgroup=ft.caption=ft.thead,ft.th=ft.td,ve.option||(ft.optgroup=ft.option=[1,""]);var dt=/<|&#?\w+;/,ht=/^([^.]*)(?:\.(.+)|)/;De.event={global:{},add:function(t,e,n,r,a){var i,o,s,l,u,c,f,d,h,p,g,m=Ge.get(t);if(Ve(t))for(n.handler&&(n=(i=n).handler,a=i.selector),a&&De.find.matchesSelector(tt,a),n.guid||(n.guid=De.guid++),(l=m.events)||(l=m.events=Object.create(null)),(o=m.handle)||(o=m.handle=function(e){return void 0!==De&&De.event.triggered!==e.type?De.event.dispatch.apply(t,arguments):undefined}),u=(e=(e||"").match(We)||[""]).length;u--;)h=g=(s=ht.exec(e[u])||[])[1],p=(s[2]||"").split(".").sort(),h&&(f=De.event.special[h]||{},h=(a?f.delegateType:f.bindType)||h,f=De.event.special[h]||{},c=De.extend({type:h,origType:g,data:r,handler:n,guid:n.guid,selector:a,needsContext:a&&De.expr.match.needsContext.test(a),namespace:p.join(".")},i),(d=l[h])||((d=l[h]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,p,o)||t.addEventListener&&t.addEventListener(h,o)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),a?d.splice(d.delegateCount++,0,c):d.push(c),De.event.global[h]=!0)},remove:function(e,t,n,r,a){var i,o,s,l,u,c,f,d,h,p,g,m=Ge.hasData(e)&&Ge.get(e);if(m&&(l=m.events)){for(u=(t=(t||"").match(We)||[""]).length;u--;)if(h=g=(s=ht.exec(t[u])||[])[1],p=(s[2]||"").split(".").sort(),h){for(f=De.event.special[h]||{},d=l[h=(r?f.delegateType:f.bindType)||h]||[],s=s[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),o=i=d.length;i--;)c=d[i],!a&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(i,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));o&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,p,m.handle)||De.removeEvent(e,h,m.handle),delete l[h])}else for(h in l)De.event.remove(e,h+t[u],n,r,!0);De.isEmptyObject(l)&&Ge.remove(e,"handle events")}},dispatch:function(e){var t,n,r,a,i,o,s=new Array(arguments.length),l=De.event.fix(e),u=(Ge.get(this,"events")||Object.create(null))[l.type]||[],c=De.event.special[l.type]||{};for(s[0]=l,t=1;t\s*$/g;De.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,a,i,o,s=e.cloneNode(!0),l=nt(e);if(!(ve.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||De.isXMLDoc(e)))for(o=x(s),r=0,a=(i=x(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",a=function(e){r.remove(),a=null,e&&t("error"===e.type?404:200,e.type)}),xe.head.appendChild(r[0])},abort:function(){a&&a()}}});var ln,un=[],cn=/(=)\?(?=&|$)|\?\?/;De.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=un.pop()||De.expando+"_"+qt.guid++;return this[e]=!0,e}}),De.ajaxPrefilter("json jsonp",function(e,t,n){var r,a,i,o=!1!==e.jsonp&&(cn.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&cn.test(e.data)&&"data");if(o||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=ye(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,o?e[o]=e[o].replace(cn,"$1"+r):!1!==e.jsonp&&(e.url+=(Wt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return i||De.error(r+" was not called"),i[0]},e.dataTypes[0]="json",a=le[r],le[r]=function(){i=arguments},n.always(function(){a===undefined?De(le).removeProp(r):le[r]=a,e[r]&&(e.jsonpCallback=t.jsonpCallback,un.push(r)),i&&ye(a)&&a(i[0]),i=a=undefined}),"script"}),ve.createHTMLDocument=((ln=xe.implementation.createHTMLDocument("").body).innerHTML="
      ",2===ln.childNodes.length),De.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(ve.createHTMLDocument?((r=(t=xe.implementation.createHTMLDocument("")).createElement("base")).href=xe.location.href,t.head.appendChild(r)):t=xe),i=!n&&[],(a=Pe.exec(e))?[t.createElement(a[1])]:(a=S([e],t,i),i&&i.length&&De(i).remove(),De.merge([],a.childNodes)));var r,a,i},De.fn.load=function(e,t,n){var r,a,i,o=this,s=e.indexOf(" ");return-1").append(De.parseHTML(e)).find(r):e)}).always(n&&function(e,t){o.each(function(){n.apply(this,i||[e.responseText,t,e])})}),this},De.expr.pseudos.animated=function(t){return De.grep(De.timers,function(e){return t===e.elem}).length},De.offset={setOffset:function(e,t,n){var r,a,i,o,s,l,u=De.css(e,"position"),c=De(e),f={};"static"===u&&(e.style.position="relative"),s=c.offset(),i=De.css(e,"top"),l=De.css(e,"left"),("absolute"===u||"fixed"===u)&&-1<(i+l).indexOf("auto")?(o=(r=c.position()).top,a=r.left):(o=parseFloat(i)||0,a=parseFloat(l)||0),ye(t)&&(t=t.call(e,n,De.extend({},s))),null!=t.top&&(f.top=t.top-s.top+o),null!=t.left&&(f.left=t.left-s.left+a),"using"in t?t.using.call(e,f):c.css(f)}},De.fn.extend({offset:function(t){if(arguments.length)return t===undefined?this:this.each(function(e){De.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],a={top:0,left:0};if("fixed"===De.css(r,"position"))t=r.getBoundingClientRect();else{for(t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&"static"===De.css(e,"position");)e=e.parentNode;e&&e!==r&&1===e.nodeType&&((a=De(e).offset()).top+=De.css(e,"borderTopWidth",!0),a.left+=De.css(e,"borderLeftWidth",!0))}return{top:t.top-a.top-De.css(r,"marginTop",!0),left:t.left-a.left-De.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&"static"===De.css(e,"position");)e=e.offsetParent;return e||tt})}}),De.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,a){var i="pageYOffset"===a;De.fn[t]=function(e){return Ue(this,function(e,t,n){var r;if(be(e)?r=e:9===e.nodeType&&(r=e.defaultView),n===undefined)return r?r[a]:e[t];r?r.scrollTo(i?r.pageXOffset:n,i?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),De.each(["top","left"],function(e,n){De.cssHooks[n]=F(ve.pixelPosition,function(e,t){if(t)return t=R(e,n),vt.test(t)?De(e).position()[n]+"px":t})}),De.each({Height:"height",Width:"width"},function(o,s){De.each({padding:"inner"+o,content:s,"":"outer"+o},function(r,i){De.fn[i]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),a=r||(!0===e||!0===t?"margin":"border");return Ue(this,function(e,t,n){var r;return be(e)?0===i.indexOf("outer")?e["inner"+o]:e.document.documentElement["client"+o]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+o],r["scroll"+o],e.body["offset"+o],r["offset"+o],r["client"+o])):n===undefined?De.css(e,t,a):De.style(e,t,n,a)},s,n?e:undefined,n)}})}),De.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){De.fn[t]=function(e){return this.on(t,e)}}),De.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),De.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){De.fn[n]=function(e,t){return 0"}for(var i=0,o="",s=[];e.length||t.length;){var l=r().splice(0,1)[0];if(o+=w(n.substr(i,l.offset-i)),i=l.offset,"start"==l.event)o+=a(l.node),s.push(l.node);else if("stop"==l.event){var u=s.length;do{var c=s[--u];o+=""}while(c!=l.node);for(s.splice(u,1);u'+w(a[0])+""):n+=w(a[0]),r=t.lR.lastIndex,a=t.lR.exec(e)}return n+=w(e.substr(r,e.length-r))}function f(e,t){if(t.sL&&D[t.sL]){var n=T(t.sL,e);return g+=n.keyword_count,n.value}return r(e,t)}function d(e,t){var n=e.cN?'':"";e.rB?(m+=n,e.buffer=""):e.eB?(m+=w(t)+n,e.buffer=""):(m+=n,e.buffer=t),h.push(e),p+=e.r}function i(e,t,n){var r=h[h.length-1];if(n)return m+=f(r.buffer+e,r),!1;var a=l(t,r);if(a)return m+=f(r.buffer+e,r),d(a,t),a.rB;var i=u(h.length-1,t);if(i){var o=r.cN?"":"";for(r.rE?m+=f(r.buffer+e,r)+o:r.eE?m+=f(r.buffer+e,r)+o+w(t):m+=f(r.buffer+e+t,r)+o;1":"",m+=o,i--,h.length--;var s=h[h.length-1];return h.length--,h[h.length-1].buffer="",s.starts&&d(s.starts,""),r.rE}if(c(t,r))throw"Illegal"}var s=D[e],h=[s.dM],p=0,g=0,m="";try{var v=0;s.dM.buffer="";do{var y=n(t,v),b=i(y[0],y[1],y[2]);v+=y[0].length,b||(v+=y[1].length)}while(!y[2]);if(1o.keyword_count+o.r&&(o=l),l.keyword_count+l.r>i.keyword_count+i.r&&(o=i,i=l)}}var u=e.className;u.match(i.language)||(u=u?u+" "+i.language:i.language);var c=g(e);if(c.length)(f=document.createElement("pre")).innerHTML=i.value,i.value=m(c,g(f),r);if(n&&(i.value=i.value.replace(/^((<[^>]+>|\t)+)/gm,function(e,t){return t.replace(/\t/g,n)})),t&&(i.value=i.value.replace(/\n/g,"
      ")),/MSIE [678]/.test(navigator.userAgent)&&"CODE"==e.tagName&&"PRE"==e.parentNode.tagName){var f=e.parentNode,d=document.createElement("div");d.innerHTML="
      "+i.value+"
      ",e=d.firstChild.firstChild,d.firstChild.cN=f.cN,f.parentNode.replaceChild(d.firstChild,f)}else e.innerHTML=i.value;e.className=u,e.dataset={},e.dataset.result={language:i.language,kw:i.keyword_count,re:i.r},o&&o.language&&(e.dataset.second_best={language:o.language,kw:o.keyword_count,re:o.r})}}function i(){if(!i.called){i.called=!0,v();for(var e=document.getElementsByTagName("pre"),t=0;t|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",this.BE={b:"\\\\.",r:0},this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0},this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0},this.CLCM={cN:"comment",b:"//",e:"$"},this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"},this.HCM={cN:"comment",b:"#",e:"$"},this.NM={cN:"number",b:this.NR,r:0},this.CNM={cN:"number",b:this.CNR,r:0},this.inherit=function(e,t){var n={};for(var r in e)n[r]=e[r];if(t)for(var r in t)n[r]=t[r];return n}};hljs.LANGUAGES.ruby=function(){var e="[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?",t="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",n={keyword:{and:1,"false":1,then:1,defined:1,module:1,"in":1,"return":1,redo:1,"if":1,BEGIN:1,retry:1,end:1,"for":1,"true":1,self:1,when:1,next:1,until:1,"do":1,begin:1,unless:1,END:1,rescue:1,nil:1,"else":1,"break":1,undef:1,not:1,"super":1,"class":1,"case":1,require:1,"yield":1,alias:1,"while":1,ensure:1,elsif:1,or:1,def:1},keymethods:{__id__:1,__send__:1,abort:1,abs:1,"all?":1,allocate:1,ancestors:1,"any?":1,arity:1,assoc:1,at:1,at_exit:1,autoload:1,"autoload?":1,"between?":1,binding:1,binmode:1,"block_given?":1,call:1,callcc:1,caller:1,capitalize:1,"capitalize!":1,casecmp:1,"catch":1,ceil:1,center:1,chomp:1,"chomp!":1,chop:1,"chop!":1,chr:1,"class":1,class_eval:1,"class_variable_defined?":1,class_variables:1,clear:1,clone:1,close:1,close_read:1,close_write:1,"closed?":1,coerce:1,collect:1,"collect!":1,compact:1,"compact!":1,concat:1,"const_defined?":1,const_get:1,const_missing:1,const_set:1,constants:1,count:1,crypt:1,"default":1,default_proc:1,"delete":1,"delete!":1,delete_at:1,delete_if:1,detect:1,display:1,div:1,divmod:1,downcase:1,"downcase!":1,downto:1,dump:1,dup:1,each:1,each_byte:1,each_index:1,each_key:1,each_line:1,each_pair:1,each_value:1,each_with_index:1,"empty?":1,entries:1,eof:1,"eof?":1,"eql?":1,"equal?":1,eval:1,exec:1,exit:1,"exit!":1,extend:1,fail:1,fcntl:1,fetch:1,fileno:1,fill:1,find:1,find_all:1,first:1,flatten:1,"flatten!":1,floor:1,flush:1,for_fd:1,foreach:1,fork:1,format:1,freeze:1,"frozen?":1,fsync:1,getc:1,gets:1,global_variables:1,grep:1,gsub:1,"gsub!":1,"has_key?":1,"has_value?":1,hash:1,hex:1,id:1,include:1,"include?":1,included_modules:1,index:1,indexes:1,indices:1,induced_from:1,inject:1,insert:1,inspect:1,instance_eval:1,instance_method:1,instance_methods:1,"instance_of?":1,"instance_variable_defined?":1,instance_variable_get:1,instance_variable_set:1,instance_variables:1,"integer?":1,intern:1,invert:1,ioctl:1,"is_a?":1,isatty:1,"iterator?":1,join:1,"key?":1,keys:1,"kind_of?":1,lambda:1,last:1,length:1,lineno:1,ljust:1,load:1,local_variables:1,loop:1,lstrip:1,"lstrip!":1,map:1,"map!":1,match:1,max:1,"member?":1,merge:1,"merge!":1,method:1,"method_defined?":1,method_missing:1,methods:1,min:1,module_eval:1,modulo:1,name:1,nesting:1,"new":1,next:1,"next!":1,"nil?":1,nitems:1,"nonzero?":1,object_id:1,oct:1,open:1,pack:1,partition:1,pid:1,pipe:1,pop:1,popen:1,pos:1,prec:1,prec_f:1,prec_i:1,print:1,printf:1,private_class_method:1,private_instance_methods:1,"private_method_defined?":1,private_methods:1,proc:1,protected_instance_methods:1,"protected_method_defined?":1,protected_methods:1,public_class_method:1,public_instance_methods:1,"public_method_defined?":1,public_methods:1,push:1,putc:1,puts:1,quo:1,raise:1,rand:1,rassoc:1,read:1,read_nonblock:1,readchar:1,readline:1,readlines:1,readpartial:1,rehash:1,reject:1,"reject!":1,remainder:1,reopen:1,replace:1,require:1,"respond_to?":1,reverse:1,"reverse!":1,reverse_each:1,rewind:1,rindex:1,rjust:1,round:1,rstrip:1,"rstrip!":1,scan:1,seek:1,select:1,send:1,set_trace_func:1,shift:1,singleton_method_added:1,singleton_methods:1,size:1,sleep:1,slice:1,"slice!":1,sort:1,"sort!":1,sort_by:1,split:1,sprintf:1,squeeze:1,"squeeze!":1,srand:1,stat:1,step:1,store:1,strip:1,"strip!":1,sub:1,"sub!":1,succ:1,"succ!":1,sum:1,superclass:1,swapcase:1,"swapcase!":1,sync:1,syscall:1,sysopen:1,sysread:1,sysseek:1,system:1,syswrite:1,taint:1,"tainted?":1,tell:1,test:1,"throw":1,times:1,to_a:1,to_ary:1,to_f:1,to_hash:1,to_i:1,to_int:1,to_io:1,to_proc:1,to_s:1,to_str:1,to_sym:1,tr:1,"tr!":1,tr_s:1,"tr_s!":1,trace_var:1,transpose:1,trap:1,truncate:1,"tty?":1,type:1,ungetc:1,uniq:1,"uniq!":1,unpack:1,unshift:1,untaint:1,untrace_var:1,upcase:1,"upcase!":1, -update:1,upto:1,"value?":1,values:1,values_at:1,warn:1,write:1,write_nonblock:1,"zero?":1,zip:1}},r={cN:"yardoctag",b:"@[A-Za-z]+"},a={cN:"comment",b:"#",e:"$",c:[r]},i={cN:"comment",b:"^\\=begin",e:"^\\=end",c:[r],r:10},o={cN:"comment",b:"^__END__",e:"\\n$"},s={cN:"subst",b:"#\\{",e:"}",l:e,k:n},l=[hljs.BE,s],u={cN:"string",b:"'",e:"'",c:l,r:0},c={cN:"string",b:'"',e:'"',c:l,r:0},f={cN:"string",b:"%[qw]?\\(",e:"\\)",c:l,r:10},d={cN:"string",b:"%[qw]?\\[",e:"\\]",c:l,r:10},h={cN:"string",b:"%[qw]?{",e:"}",c:l,r:10},p={cN:"string",b:"%[qw]?<",e:">",c:l,r:10},g={cN:"string",b:"%[qw]?/",e:"/",c:l,r:10},m={cN:"string",b:"%[qw]?%",e:"%",c:l,r:10},v={cN:"string",b:"%[qw]?-",e:"-",c:l,r:10},y={cN:"string",b:"%[qw]?\\|",e:"\\|",c:l,r:10},b={cN:"function",b:"\\bdef\\s+",e:" |$|;",l:e,k:n,c:[{cN:"title",b:t,l:e,k:n},{cN:"params",b:"\\(",e:"\\)",l:e,k:n},a,i,o]},x={cN:"identifier",b:e,l:e,k:n,r:0},w=[a,i,o,u,c,f,d,h,p,g,m,v,y,{cN:"class",b:"\\b(class|module)\\b",e:"$|;",k:{"class":1,module:1},c:[{cN:"title",b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?",r:0},{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+hljs.IR+"::)?"+hljs.IR}]},a,i,o]},b,{cN:"constant",b:"(::)?([A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[u,c,f,d,h,p,g,m,v,y,x],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"number",b:"\\?\\w"},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},x,{b:"("+hljs.RSR+")\\s*",c:[a,i,o,{cN:"regexp",b:"/",e:"/[a-z]*",i:"\\n",c:[hljs.BE]}],r:0}];return s.c=w,{dM:{l:e,k:n,c:b.c[1].c=w}}}(),function(c,s,o){function l(e,t,n){var r=s.createElement(e);return t&&(r.id=te+t),n&&(r.style.cssText=n),c(r)}function f(){return o.innerHeight?o.innerHeight:c(o).height()}function u(e,n){n!==Object(n)&&(n={}),this.cache={},this.el=e,this.value=function(e){var t;return this.cache[e]===undefined&&((t=c(this.el).attr("data-cbox-"+e))!==undefined?this.cache[e]=t:n[e]!==undefined?this.cache[e]=n[e]:Q[e]!==undefined&&(this.cache[e]=Q[e])),this.cache[e]},this.get=function(e){var t=this.value(e);return c.isFunction(t)?t.call(this.el,this):t}}function i(e){var t=N.length,n=(X+e)%t;return n<0?t+n:n}function d(e,t){return Math.round((/%/.test(e)?("x"===t?j.width():f())/100:1)*parseInt(e,10))}function h(e,t){return e.get("photo")||e.get("photoRegex").test(t)}function p(e,t){return e.get("retinaUrl")&&1"),x()}}function a(){S||(t=!1,j=c(o),S=l(ce).attr({id:ee,"class":!1===c.support.opacity?te+"IE":"",role:"dialog",tabindex:"-1"}).hide(),w=l(ce,"Overlay").hide(),E=c([l(ce,"LoadingOverlay")[0],l(ce,"LoadingGraphic")[0]]),T=l(ce,"Wrapper"),D=l(ce,"Content").append(R=l(ce,"Title"),F=l(ce,"Current"),M=c('
    136. ")}),$(".group_tabs a").each(function(){$(this).addClass($(this).attr("href").replace("#",""))}),$(".group_tabs").on("focus","a",function(){$(this).blur()});var e=$('link[rel="icon"]').attr("href");if($(".group_tabs").on("click","a",function(){return $(this).parent().hasClass("active")||($(".group_tabs a").parent().removeClass("active"),$(this).parent().addClass("active"),$(".file_list_container").hide(),$(".file_list_container"+$(this).attr("href")).show(),window.location.href=window.location.href.split("#")[0]+$(this).attr("href").replace("#","#_"),$('link[rel="icon"]').remove(),$("head").append('')),!1}),window.location.hash){var n=window.location.hash.substring(1);if(40===n.length)$('a.src_link[href="#'+n+'"]').click();else if(40bP0l+XkK8G}75 diff --git a/coverage/assets/0.13.2/colorbox/controls.png b/coverage/assets/0.13.2/colorbox/controls.png deleted file mode 100644 index 65cfd1dc95c5ee4c6c3d0848b1dcfc0ec69f6aea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2033 zcmVY16Dl9G~IT3Xc9)Z^ph#l^+1udkq>pj%s8x3{-FJw3a- zyTZc4tgNhJVPUGOs=2wjTUuIPU0s)#m&C-xo0^)sxw(yvjeLB3^78U4D=RH6Ei5c7 zO-)TME-o-IFwM=)IXOA(>+9v^gnk!Dk@A&OhZFMI5;>mGBP$cHrd(PIyySo z*x1a>%u7p4z`(#lLPF^0=SN3JF)=YpN=iXNLGSMFH#av(NJvFRMNv^v@$vCHJ3Bl) zJTo&hNl8iR>FGvBMo>^t>+0%WUS93(?OFDTAPEK`obwEHsK0ZFDr>E}i?Y_RgTwGjIQc{VDiO$Z>?Ck7QQ&aZ#_VDoVc6N4O zUti|s<#>2_pP!#*wE#@J%|ZXjkIv8d15RfRBQxe$bh z9TUw{N+{bY1z&)8>k2t;d{xYxg76&eJ5GSmab7cm2*kJn zLPONkBzslPWkA+%7~Rthzj+ZdH;HAk-Pq6JDgZ%}1qZgnS8` z|A!BGUTY0vuJ8~Q8k9-cggFrxrb5iS-G06qcV|ScTS@l&{bZ%90njJ((hDH>go-Ov zZbB$mN3X(#_fF&Gsyr1MX$KPpfH2>rC?r$(w4Y$dt3?|Rbk9;C`p(jcaF$k70KON_ zQXS~iuoPW9OL=;PXDK^qs;!en!tGwM8Dm32zK3Hnob~aBRzMgVd)VIwFSb@T|pRnFQtL^ zQVJ_)!)j6(b&PC(dhopzE0a4<>r<@qU z`!EtA#BilIes-lde&oVm?AXss0zni9@U)s;c2)_BE*-ve3qd+Xkx|kwB9dCkgI$6` z2)acBb&UMSYiGCKj*SBn@)q-Z5n&E~kT-AMBO%kChC#@X|0tXb=fe3-(?1oCZXuoB zLi}_KX?F|Z?iRyJn&Er?*=Fkav37KPz4^Q2i%?hdj$hRY5$X6y8bhS1yyTxuueKWC z5CSWPU;%-YXn=qr((#(QE2FU06#>QwQ5V+BI|UkI(*RRqvv@oZ-B#&@=C_U&BCHmt zC#jJURR)BX9qafF9iN(TLwU10A{?j~CXS78W{8Uu@HZ?#PAyEpFp1S*%EGlpZ`N&L zTO`O5246{p*$9hz`Xxjbs;;d1D5fGugh*6-y27O=ZrgP)q5m%J z%*b8(_kO793Uc&AR^jAtn*OA38fGEGyOcr5Fk7$*{qtd@niJ25{6h8_pFvpWJ0e#STIxNJ)pDn{H0&eE#G130bNngTwLQ z{r-aw>G;0=r|=|0AtWIQNk~Exl8}TXBq0e&sC~j#A{E;EQlD^{NCv_KN?|hzt4p`= zmiMYgEZpwcxm$P_8++%CQ5bCMy}oe)uer?ORv)df%L>+~SMRSwxVo4v42N~krH0-D zsqp7BEemF3oQ#jZH?};MT1`)gSQr3NRVET5olgiEnaToD3Vmr;&eR^luC95&D|F|1 zySmIE&VwgJDV!&k+duls&IXn^h1S?O8(9D%7J?Dx?3|Gb?cRJt`cH*#(!#mIWIQCf P00000NkvXXu0mjf=5OWV diff --git a/coverage/assets/0.13.2/colorbox/loading.gif b/coverage/assets/0.13.2/colorbox/loading.gif deleted file mode 100644 index b4695d811d4f84ed693eff7b5e6b4d61a3e8c48d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9427 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 diff --git a/coverage/assets/0.13.2/favicon_red.png b/coverage/assets/0.13.2/favicon_red.png deleted file mode 100644 index fbe093a551114bd5ba92550b83ec04a886238bdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1009 zcmbVLO>Wab7bYf2utF_0Br^M-! z9Yvvdaul*51?`xOX_u04_WBz=P?XJ4)E|g}e`*IT)<}WT@;H@j zML9UkQxaTI0fuxOC64;@(`OY#p`*U6`_NB4I*CrMIqhAa^@Hn+pc$%%Pr*TMOM#dQ z0`mAW$?V)w=eoAs7uUK9<`8k=s0*hCeg}ArQ&87nC4eRbCel!~W?9x_U_b(45K0um28#>l2?*! zzf{093wV@@h$WymuBpW2eKZc{gNzmce9}aD7~>?@JJSgTw)en#WWFZ?5OgO7Dk~BQ3E1m)u3fS zvw@I{u-k%-n$@r{ZopQlv5xCD%qliK1DkaZp%y$ct-4n?jVgAJk%g)yx1D5yBmph$ zM$+z*i*DrF9;ZYw-e>Hx?10XM36@P*3cOw&_#}vu;#`zx5iO=XdPBow&SEgnuN|$i z4~>7tz2JublQUg9qZiHbSF@B?vIC3ls`cezH9Rzt6T{_byxRM5OODl@&S}5(em-Fz o?)}1Yv(v^${rvmz{Qm8)3izUI>?#{iT=#i_X}8YsNB8{gZ@}R!9smFU diff --git a/coverage/assets/0.13.2/favicon_yellow.png b/coverage/assets/0.13.2/favicon_yellow.png deleted file mode 100644 index e53847ea09aea9a5a16237a2acbebd0bef7f9436..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1009 zcmbVL&u-H&7|%3;VA8a415Q?U;jkulx;AaLFg0<;Dwa{TmFRJ9;eJ#kmiX9Mq>;)n%m16w*8lZ-ldARVZ09vn08R3}~jQ%4>lxM4$c%<5qMr zyvnKBrvXT zW(y$?VK0EKJ-cON+=4-+aU0if*#Qg!4>ub=LILbF?S|iIT6OFlAsf{zZZ|6g$s$_$ zO{Cuyw{s`g@i`@e@gZXuRR{EDOt50ca^MddAS6+eslB?MWww~|pPHF67*jRJU(He-$qrP@o7R_`o8h6EoER=gqyAy{ksPc0y^~?^^P`$D o)I$%Y+3MoXu>3JTdwlPk2EJ-*+uGW`=e<;rZg7e}d1vo_0~HY~(EtDd diff --git a/coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png b/coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png deleted file mode 100644 index 5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`R^ z$vje}bP0l+XkK DSH>_4 diff --git a/coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png b/coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png deleted file mode 100644 index ac8b229af950c29356abf64a6c4aa894575445f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQYz+E8 zPo9&<{J;c_6SHRil>2s{Zw^OT)6@jj2u|u!(plXsM>LJD`vD!n;OXk;vd$@?2>^GI BH@yG= diff --git a/coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png b/coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png deleted file mode 100644 index ad3d6346e00f246102f72f2e026ed0491988b394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnour0hLi978O6-<~(*I$*%ybaDOn z{W;e!B}_MSUQoPXhYd^Y6RUoS1yepnPx`2Kz)7OXQG!!=-jY=F+d2OOy?#DnJ32>z UEim$g7SJdLPgg&ebxsLQ09~*s;{X5v diff --git a/coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png b/coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png deleted file mode 100644 index 42ccba269b6e91bef12ad0fa18be651b5ef0ee68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouqzpV=978O6-=0?FV^9z|eBtf= z|7WztIJ;WT>{+tN>ySr~=F{k$>;_x^_y?afmf9pRKH0)6?eSP?3s5hEr>mdKI;Vst E0O;M1& diff --git a/coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png b/coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png deleted file mode 100644 index 5a46b47cb16631068aee9e0bd61269fc4e95e5cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouq|7{B978O6lPf+wIa#m9#>Unb zm^4K~wN3Zq+uP{vDV26o)#~38k_!`W=^oo1w6ixmPC4R1b Tyd6G3lNdZ*{an^LB{Ts5`idse diff --git a/coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png deleted file mode 100644 index 7c9fa6c6edcfcdd3e5b77e6f547b719e6fc66e30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101 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 diff --git a/coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png b/coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png deleted file mode 100644 index 09d1cdc856c292c4ab6dd818c7543ac0828bd616..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4369 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 diff --git a/coverage/assets/0.13.2/images/ui-icons_454545_256x240.png b/coverage/assets/0.13.2/images/ui-icons_454545_256x240.png deleted file mode 100644 index 59bd45b907c4fd965697774ce8c5fc6b2fd9c105..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4369 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 oBo%DI*Kv;w;*%(i9W@f3_WCF#rGn diff --git a/coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png b/coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png deleted file mode 100644 index 2ab019b73ec11a485fa09378f3a0e155194f6a5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4369 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 diff --git a/coverage/assets/0.13.2/magnify.png b/coverage/assets/0.13.2/magnify.png deleted file mode 100644 index 6073b9530ed197d0431ab30ca79ed262f4b910fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1301 zcmeAS@N?(olHy`uVBq!ia0vp^JV4CP!3HERJk;|9Qj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS=07?{&CLn2Bde0{8v^Ko2Tt~skz|cV7z)0WFNY~KZ%Gk)tz(4^Clz_GsrKDK}xwt{?0`hE?GD=Dctn~HE z%ggo3jrH=2()A53EiFN27#ZmTRp=I1=9MH?=;jqG!%T2VElw`VEGWs$&r<-In3$Ab zT4JjNbScCOxdpzyaD(%Tp#cR9GX09g0)0b01O41wkiWpHi%Wu15zfG>x;Uh=AXPso zwK%`DC>aHCFybnZQU%%yG$2F3nBNEAe!(RRYTBrDUd98JJoa z7#fLwm{}MaS{S&PxH=kI8aca~nVK0J!py*?*VNqH(b&z&+|k6`#L&>i&C$@& z*wN9@%*oZ*)ycrb38vRGuec;JFF6%vZzj-Qs9rO?daay`QWHz^i$e1Ab6_bTAS1sd zzc?emK*2fKRKYhfIWrH$2SpFWw=StgnPsUdZbkXI3gGay%EV$nVShvPreF&*U?|}Y zGku_A^g)RODY3wWfGH5fgeQF<2cCIS^ME;~2$(gL-@n|+z`*G0>EaktajRw0L9dno zk+%0&6)$)$+Nzu6AkfL76}2_%qHx3)uDvb4^e!sRPZYbPmUVHFfHTLUiPtWPG~N0l zZ#n&l+OLO)AAjB7Ui^QxNPqy#CXKGeHs{qNr>(s->-DbU0z`y zvr6dr$F>Ni``^>&J&$coyz%zJ3gg+&AF#zVco%Tny^yX9X%_4~_?3AFvsJM_@4>k9 z$|k19Q#LoAQGV8H_r+$eqvyAIlg$53a?31u`=R*Y7LjRQK3q&Le8wT*lABgaxY9)S-2*+#8CINRCwstB#E|+S#Dc$ zwO{YLR_Gu9;oL6fRnl*|)1C_USH7J5{&}o${cFKJg}F_K6W5$Bxw~5P=*fENxPy~_ qY45u7g2$OHiv4-Q?6`fb5)4Z;>fX&^evkqxs61W$T-G@yGywqb{mBae diff --git a/coverage/index.html b/coverage/index.html deleted file mode 100644 index bb9faba696..0000000000 --- a/coverage/index.html +++ /dev/null @@ -1,15535 +0,0 @@ - - - - Code coverage for Camaar - - - - - - - -

      - loading -
      -
      -
      Generated 2025-12-11T18:16:12-03:00
      -
        - -
        -
        -

        - All Files - ( - - 78.89% - - - - covered at - - - 1.06 - - hits/line - ) -

        - -
        - -
        - 39 files in total. -
        - -
        - 521 relevant lines, - 411 lines covered and - 110 lines missed. - ( - 78.89% - -) -
        - - - -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        File% coveredLinesRelevant LinesLines coveredLines missedAvg. Hits / Line
        app/controllers/application_controller.rb100.00 %95501.00
        app/controllers/avaliacoes_controller.rb15.15 %72335280.15
        app/controllers/concerns/authenticatable.rb70.00 %2010730.70
        app/controllers/concerns/authentication.rb72.41 %52292180.72
        app/helpers/application_helper.rb100.00 %21101.00
        app/helpers/avaliacoes_helper.rb100.00 %21101.00
        app/helpers/home_helper.rb100.00 %21101.00
        app/mailers/application_mailer.rb100.00 %43301.00
        app/mailers/user_mailer.rb100.00 %20101001.70
        app/models/MatriculaTurma.rb100.00 %43301.00
        app/models/application_record.rb100.00 %32201.00
        app/models/avaliacao.rb100.00 %86601.00
        app/models/current.rb100.00 %43301.00
        app/models/modelo.rb51.85 %542714130.67
        app/models/pergunta.rb47.22 %813617190.47
        app/models/turma.rb100.00 %54401.00
        app/models/user.rb100.00 %15121202.67
        app/services/csv_formatter_service.rb100.00 %46191901.42
        app/services/sigaa_import_service.rb87.01 %1897767101.57
        config/application.rb100.00 %277701.00
        config/boot.rb100.00 %43301.00
        config/environment.rb100.00 %52201.00
        config/environments/test.rb100.00 %57151501.00
        config/initializers/assets.rb100.00 %71101.00
        config/initializers/content_security_policy.rb100.00 %250000.00
        config/initializers/filter_parameter_logging.rb100.00 %81101.00
        config/initializers/inflections.rb100.00 %172201.00
        config/initializers/inflections_custom.rb100.00 %32201.00
        config/initializers/mail.rb25.00 %6512390.25
        config/routes.rb100.00 %56232301.00
        spec/mailers/user_mailer_spec.rb95.24 %39212011.33
        spec/models/avaliacao_spec.rb100.00 %20121201.00
        spec/models/matricula_turma_spec.rb100.00 %159901.00
        spec/models/turma_spec.rb100.00 %16101001.00
        spec/models/user_spec.rb100.00 %20111101.00
        spec/rails_helper.rb90.91 %72111010.91
        spec/requests/avaliacoes_spec.rb50.00 %503015150.70
        spec/services/csv_formatter_service_spec.rb100.00 %44202001.45
        spec/services/sigaa_import_service_spec.rb93.62 %96474431.43
        -
        -
        - - - -
        -

        - Controllers - ( - - 49.35% - - - - covered at - - - 0.49 - - hits/line - ) -

        - - - -
        - 4 files in total. -
        - -
        - 77 relevant lines, - 38 lines covered and - 39 lines missed. - ( - 49.35% - -) -
        - - - -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        File% coveredLinesRelevant LinesLines coveredLines missedAvg. Hits / Line
        app/controllers/application_controller.rb100.00 %95501.00
        app/controllers/avaliacoes_controller.rb15.15 %72335280.15
        app/controllers/concerns/authenticatable.rb70.00 %2010730.70
        app/controllers/concerns/authentication.rb72.41 %52292180.72
        -
        -
        - - -
        -

        - Models - ( - - 65.59% - - - - covered at - - - 0.91 - - hits/line - ) -

        - - - -
        - 8 files in total. -
        - -
        - 93 relevant lines, - 61 lines covered and - 32 lines missed. - ( - 65.59% - -) -
        - - - -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        File% coveredLinesRelevant LinesLines coveredLines missedAvg. Hits / Line
        app/models/MatriculaTurma.rb100.00 %43301.00
        app/models/application_record.rb100.00 %32201.00
        app/models/avaliacao.rb100.00 %86601.00
        app/models/current.rb100.00 %43301.00
        app/models/modelo.rb51.85 %542714130.67
        app/models/pergunta.rb47.22 %813617190.47
        app/models/turma.rb100.00 %54401.00
        app/models/user.rb100.00 %15121202.67
        -
        -
        - - -
        -

        - Ungrouped - ( - - 88.89% - - - - covered at - - - 1.22 - - hits/line - ) -

        - - - -
        - 27 files in total. -
        - -
        - 351 relevant lines, - 312 lines covered and - 39 lines missed. - ( - 88.89% - -) -
        - - - -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        File% coveredLinesRelevant LinesLines coveredLines missedAvg. Hits / Line
        app/helpers/application_helper.rb100.00 %21101.00
        app/helpers/avaliacoes_helper.rb100.00 %21101.00
        app/helpers/home_helper.rb100.00 %21101.00
        app/mailers/application_mailer.rb100.00 %43301.00
        app/mailers/user_mailer.rb100.00 %20101001.70
        app/services/csv_formatter_service.rb100.00 %46191901.42
        app/services/sigaa_import_service.rb87.01 %1897767101.57
        config/application.rb100.00 %277701.00
        config/boot.rb100.00 %43301.00
        config/environment.rb100.00 %52201.00
        config/environments/test.rb100.00 %57151501.00
        config/initializers/assets.rb100.00 %71101.00
        config/initializers/content_security_policy.rb100.00 %250000.00
        config/initializers/filter_parameter_logging.rb100.00 %81101.00
        config/initializers/inflections.rb100.00 %172201.00
        config/initializers/inflections_custom.rb100.00 %32201.00
        config/initializers/mail.rb25.00 %6512390.25
        config/routes.rb100.00 %56232301.00
        spec/mailers/user_mailer_spec.rb95.24 %39212011.33
        spec/models/avaliacao_spec.rb100.00 %20121201.00
        spec/models/matricula_turma_spec.rb100.00 %159901.00
        spec/models/turma_spec.rb100.00 %16101001.00
        spec/models/user_spec.rb100.00 %20111101.00
        spec/rails_helper.rb90.91 %72111010.91
        spec/requests/avaliacoes_spec.rb50.00 %503015150.70
        spec/services/csv_formatter_service_spec.rb100.00 %44202001.45
        spec/services/sigaa_import_service_spec.rb93.62 %96474431.43
        -
        -
        - - -
        - - - -
        - -
        -
        -

        app/controllers/application_controller.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 5 relevant lines. - 5 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class ApplicationController < ActionController::Base -
        2. -
          - -
          -
        3. - - 1 - - - - - include Authentication -
        4. -
          - -
          -
        5. - - 1 - - - - - include Authenticatable -
        6. -
          - -
          -
        7. - - - - - # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. -
        8. -
          - -
          -
        9. - - 1 - - - - - allow_browser versions: :modern -
        10. -
          - -
          -
        11. - - - - - -
        12. -
          - -
          -
        13. - - 1 - - - - - def index -
        14. -
          - -
          -
        15. - - - - - end -
        16. -
          - -
          -
        17. - - - - - end -
        18. -
          - -
        -
        -
        - - -
        -
        -

        app/controllers/avaliacoes_controller.rb

        -

        - - 15.15% - - - lines covered -

        - - - -
        - 33 relevant lines. - 5 lines covered and - 28 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class AvaliacoesController < ApplicationController -
        2. -
          - -
          -
        3. - - - - - # Requer autenticação para todas as actions -
        4. -
          - -
          -
        5. - - - - - -
        6. -
          - -
          -
        7. - - 1 - - - - - def index -
        8. -
          - -
          -
        9. - - - - - # Se for admin, mostrar todas as avaliações -
        10. -
          - -
          -
        11. - - - - - # Se for aluno, mostrar todas as turmas matriculadas -
        12. -
          - -
          -
        13. - - - - - @turmas = [] # Inicializa como array vazio por padrão -
        14. -
          - -
          -
        15. - - - - - -
        16. -
          - -
          -
        17. - - - - - if current_user&.eh_admin? -
        18. -
          - -
          -
        19. - - - - - @avaliacoes = Avaliacao.all -
        20. -
          - -
          -
        21. - - - - - elsif current_user -
        22. -
          - -
          -
        23. - - - - - # Alunos veem suas turmas matriculadas -
        24. -
          - -
          -
        25. - - - - - @turmas = current_user.turmas.includes(:avaliacoes) -
        26. -
          - -
          -
        27. - - - - - else -
        28. -
          - -
          -
        29. - - - - - # Não logado - redireciona para login -
        30. -
          - -
          -
        31. - - - - - redirect_to new_session_path -
        32. -
          - -
          -
        33. - - - - - end -
        34. -
          - -
          -
        35. - - - - - end -
        36. -
          - -
          -
        37. - - - - - -
        38. -
          - -
          -
        39. - - 1 - - - - - def gestao_envios -
        40. -
          - -
          -
        41. - - - - - @turmas = Turma.all -
        42. -
          - -
          -
        43. - - - - - end -
        44. -
          - -
          -
        45. - - - - - -
        46. -
          - -
          -
        47. - - 1 - - - - - def create -
        48. -
          - -
          -
        49. - - - - - turma_id = params[:turma_id] -
        50. -
          - -
          -
        51. - - - - - turma = Turma.find_by(id: turma_id) -
        52. -
          - -
          -
        53. - - - - - -
        54. -
          - -
          -
        55. - - - - - if turma.nil? -
        56. -
          - -
          -
        57. - - - - - redirect_to gestao_envios_avaliacoes_path, alert: "Turma não encontrada." -
        58. -
          - -
          -
        59. - - - - - return -
        60. -
          - -
          -
        61. - - - - - end -
        62. -
          - -
          -
        63. - - - - - -
        64. -
          - -
          -
        65. - - - - - template = Modelo.find_by(titulo: "Template Padrão", ativo: true) -
        66. -
          - -
          -
        67. - - - - - -
        68. -
          - -
          -
        69. - - - - - if template.nil? -
        70. -
          - -
          -
        71. - - - - - redirect_to gestao_envios_avaliacoes_path, alert: "Template Padrão não encontrado. Contate o administrador." -
        72. -
          - -
          -
        73. - - - - - return -
        74. -
          - -
          -
        75. - - - - - end -
        76. -
          - -
          -
        77. - - - - - -
        78. -
          - -
          -
        79. - - - - - @avaliacao = Avaliacao.new( -
        80. -
          - -
          -
        81. - - - - - turma: turma, -
        82. -
          - -
          -
        83. - - - - - modelo: template, -
        84. -
          - -
          -
        85. - - - - - data_inicio: Time.current, -
        86. -
          - -
          -
        87. - - - - - data_fim: params[:data_fim].presence || 7.days.from_now -
        88. -
          - -
          -
        89. - - - - - ) -
        90. -
          - -
          -
        91. - - - - - -
        92. -
          - -
          -
        93. - - - - - if @avaliacao.save -
        94. -
          - -
          -
        95. - - - - - redirect_to gestao_envios_avaliacoes_path, notice: "Avaliação criada com sucesso para a turma #{turma.codigo}." -
        96. -
          - -
          -
        97. - - - - - else -
        98. -
          - -
          -
        99. - - - - - redirect_to gestao_envios_avaliacoes_path, alert: "Erro ao criar avaliação: #{@avaliacao.errors.full_messages.join(', ')}" -
        100. -
          - -
          -
        101. - - - - - end -
        102. -
          - -
          -
        103. - - - - - end -
        104. -
          - -
          -
        105. - - - - - -
        106. -
          - -
          -
        107. - - 1 - - - - - def resultados -
        108. -
          - -
          -
        109. - - - - - @avaliacao = Avaliacao.find(params[:id]) -
        110. -
          - -
          -
        111. - - - - - # Pré-carrega dependências para evitar N+1. -
        112. -
          - -
          -
        113. - - - - - begin -
        114. -
          - -
          -
        115. - - - - - @submissoes = @avaliacao.submissoes.includes(:aluno, :respostas) -
        116. -
          - -
          -
        117. - - - - - rescue ActiveRecord::StatementInvalid -
        118. -
          - -
          -
        119. - - - - - @submissoes = [] -
        120. -
          - -
          -
        121. - - - - - flash.now[:alert] = "Erro ao carregar submissões." -
        122. -
          - -
          -
        123. - - - - - end -
        124. -
          - -
          -
        125. - - - - - -
        126. -
          - -
          -
        127. - - - - - respond_to do |format| -
        128. -
          - -
          -
        129. - - - - - format.html -
        130. -
          - -
          -
        131. - - - - - format.csv do -
        132. -
          - -
          -
        133. - - - - - send_data CsvFormatterService.new(@avaliacao).generate, -
        134. -
          - -
          -
        135. - - - - - filename: "resultados-avaliacao-#{@avaliacao.id}-#{Date.today}.csv" -
        136. -
          - -
          -
        137. - - - - - end -
        138. -
          - -
          -
        139. - - - - - end -
        140. -
          - -
          -
        141. - - - - - end -
        142. -
          - -
          -
        143. - - - - - end -
        144. -
          - -
        -
        -
        - - -
        -
        -

        app/controllers/concerns/authenticatable.rb

        -

        - - 70.0% - - - lines covered -

        - - - -
        - 10 relevant lines. - 7 lines covered and - 3 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # app/controllers/concerns/authenticatable.rb -
        2. -
          - -
          -
        3. - - 1 - - - - - module Authenticatable -
        4. -
          - -
          -
        5. - - 1 - - - - - extend ActiveSupport::Concern -
        6. -
          - -
          -
        7. - - - - - -
        8. -
          - -
          -
        9. - - 1 - - - - - included do -
        10. -
          - -
          -
        11. - - 1 - - - - - helper_method :current_user, :user_signed_in? -
        12. -
          - -
          -
        13. - - - - - end -
        14. -
          - -
          -
        15. - - - - - -
        16. -
          - -
          -
        17. - - 1 - - - - - def authenticate_user! -
        18. -
          - -
          -
        19. - - - - - redirect_to new_session_path, alert: "É necessário fazer login." unless user_signed_in? -
        20. -
          - -
          -
        21. - - - - - end -
        22. -
          - -
          -
        23. - - - - - -
        24. -
          - -
          -
        25. - - 1 - - - - - def current_user -
        26. -
          - -
          -
        27. - - - - - Current.session&.user -
        28. -
          - -
          -
        29. - - - - - end -
        30. -
          - -
          -
        31. - - - - - -
        32. -
          - -
          -
        33. - - 1 - - - - - def user_signed_in? -
        34. -
          - -
          -
        35. - - - - - current_user.present? -
        36. -
          - -
          -
        37. - - - - - end -
        38. -
          - -
          -
        39. - - - - - end -
        40. -
          - -
        -
        -
        - - -
        -
        -

        app/controllers/concerns/authentication.rb

        -

        - - 72.41% - - - lines covered -

        - - - -
        - 29 relevant lines. - 21 lines covered and - 8 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - module Authentication -
        2. -
          - -
          -
        3. - - 1 - - - - - extend ActiveSupport::Concern -
        4. -
          - -
          -
        5. - - - - - -
        6. -
          - -
          -
        7. - - 1 - - - - - included do -
        8. -
          - -
          -
        9. - - 1 - - - - - before_action :require_authentication -
        10. -
          - -
          -
        11. - - 1 - - - - - helper_method :authenticated? -
        12. -
          - -
          -
        13. - - - - - end -
        14. -
          - -
          -
        15. - - - - - -
        16. -
          - -
          -
        17. - - 1 - - - - - class_methods do -
        18. -
          - -
          -
        19. - - 1 - - - - - def allow_unauthenticated_access(**options) -
        20. -
          - -
          -
        21. - - - - - skip_before_action :require_authentication, **options -
        22. -
          - -
          -
        23. - - - - - end -
        24. -
          - -
          -
        25. - - - - - end -
        26. -
          - -
          -
        27. - - - - - -
        28. -
          - -
          -
        29. - - 1 - - - - - private -
        30. -
          - -
          -
        31. - - 1 - - - - - def authenticated? -
        32. -
          - -
          -
        33. - - - - - resume_session -
        34. -
          - -
          -
        35. - - - - - end -
        36. -
          - -
          -
        37. - - - - - -
        38. -
          - -
          -
        39. - - 1 - - - - - def require_authentication -
        40. -
          - -
          -
        41. - - 1 - - - - - resume_session || request_authentication -
        42. -
          - -
          -
        43. - - - - - end -
        44. -
          - -
          -
        45. - - - - - -
        46. -
          - -
          -
        47. - - 1 - - - - - def resume_session -
        48. -
          - -
          -
        49. - - 1 - - - - - Current.session ||= find_session_by_cookie -
        50. -
          - -
          -
        51. - - - - - end -
        52. -
          - -
          -
        53. - - - - - -
        54. -
          - -
          -
        55. - - 1 - - - - - def find_session_by_cookie -
        56. -
          - -
          -
        57. - - 1 - - - - - Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id] -
        58. -
          - -
          -
        59. - - - - - end -
        60. -
          - -
          -
        61. - - - - - -
        62. -
          - -
          -
        63. - - 1 - - - - - def request_authentication -
        64. -
          - -
          -
        65. - - 1 - - - - - session[:return_to_after_authenticating] = request.url -
        66. -
          - -
          -
        67. - - 1 - - - - - redirect_to new_session_path -
        68. -
          - -
          -
        69. - - - - - end -
        70. -
          - -
          -
        71. - - - - - -
        72. -
          - -
          -
        73. - - 1 - - - - - def after_authentication_url -
        74. -
          - -
          -
        75. - - - - - session.delete(:return_to_after_authenticating) || root_url -
        76. -
          - -
          -
        77. - - - - - end -
        78. -
          - -
          -
        79. - - - - - -
        80. -
          - -
          -
        81. - - 1 - - - - - def start_new_session_for(user) -
        82. -
          - -
          -
        83. - - - - - user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session| -
        84. -
          - -
          -
        85. - - - - - Current.session = session -
        86. -
          - -
          -
        87. - - - - - cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax } -
        88. -
          - -
          -
        89. - - - - - end -
        90. -
          - -
          -
        91. - - - - - end -
        92. -
          - -
          -
        93. - - - - - -
        94. -
          - -
          -
        95. - - 1 - - - - - def terminate_session -
        96. -
          - -
          -
        97. - - - - - Current.session.destroy -
        98. -
          - -
          -
        99. - - - - - cookies.delete(:session_id) -
        100. -
          - -
          -
        101. - - - - - end -
        102. -
          - -
          -
        103. - - - - - end -
        104. -
          - -
        -
        -
        - - -
        -
        -

        app/helpers/application_helper.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 1 relevant lines. - 1 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - module ApplicationHelper -
        2. -
          - -
          -
        3. - - - - - end -
        4. -
          - -
        -
        -
        - - -
        -
        -

        app/helpers/avaliacoes_helper.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 1 relevant lines. - 1 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - module AvaliacoesHelper -
        2. -
          - -
          -
        3. - - - - - end -
        4. -
          - -
        -
        -
        - - -
        -
        -

        app/helpers/home_helper.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 1 relevant lines. - 1 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - module HomeHelper -
        2. -
          - -
          -
        3. - - - - - end -
        4. -
          - -
        -
        -
        - - -
        -
        -

        app/mailers/application_mailer.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 3 relevant lines. - 3 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class ApplicationMailer < ActionMailer::Base -
        2. -
          - -
          -
        3. - - 1 - - - - - default from: "from@example.com" -
        4. -
          - -
          -
        5. - - 1 - - - - - layout "mailer" -
        6. -
          - -
          -
        7. - - - - - end -
        8. -
          - -
        -
        -
        - - -
        -
        -

        app/mailers/user_mailer.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 10 relevant lines. - 10 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class UserMailer < ApplicationMailer -
        2. -
          - -
          -
        3. - - - - - # Email para definição de senha (método existente) -
        4. -
          - -
          -
        5. - - 1 - - - - - def definicao_senha(user) -
        6. -
          - -
          -
        7. - - 2 - - - - - @user = user -
        8. -
          - -
          -
        9. - - 2 - - - - - @url = "http://localhost:3000/definicao_senha" -
        10. -
          - -
          -
        11. - - 2 - - - - - mail(to: @user.email_address, subject: 'Definição de Senha - Sistema de Gestão') -
        12. -
          - -
          -
        13. - - - - - end -
        14. -
          - -
          -
        15. - - - - - -
        16. -
          - -
          -
        17. - - - - - # Email de cadastro com senha temporária (novo método) -
        18. -
          - -
          -
        19. - - 1 - - - - - def cadastro_email(user, senha_temporaria) -
        20. -
          - -
          -
        21. - - 2 - - - - - @user = user -
        22. -
          - -
          -
        23. - - 2 - - - - - @senha = senha_temporaria -
        24. -
          - -
          -
        25. - - 2 - - - - - @login_url = new_session_url -
        26. -
          - -
          -
        27. - - - - - -
        28. -
          - -
          -
        29. - - 2 - - - - - mail( -
        30. -
          - -
          -
        31. - - - - - to: @user.email_address, -
        32. -
          - -
          -
        33. - - - - - subject: 'Bem-vindo(a) ao CAMAAR - Sua senha de acesso' -
        34. -
          - -
          -
        35. - - - - - ) -
        36. -
          - -
          -
        37. - - - - - end -
        38. -
          - -
          -
        39. - - - - - end -
        40. -
          - -
        -
        -
        - - -
        -
        -

        app/models/MatriculaTurma.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 3 relevant lines. - 3 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class MatriculaTurma < ApplicationRecord -
        2. -
          - -
          -
        3. - - 1 - - - - - belongs_to :user -
        4. -
          - -
          -
        5. - - 1 - - - - - belongs_to :turma -
        6. -
          - -
          -
        7. - - - - - end -
        8. -
          - -
        -
        -
        - - -
        -
        -

        app/models/application_record.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 2 relevant lines. - 2 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class ApplicationRecord < ActiveRecord::Base -
        2. -
          - -
          -
        3. - - 1 - - - - - primary_abstract_class -
        4. -
          - -
          -
        5. - - - - - end -
        6. -
          - -
        -
        -
        - - -
        -
        -

        app/models/avaliacao.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 6 relevant lines. - 6 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class Avaliacao < ApplicationRecord -
        2. -
          - -
          -
        3. - - 1 - - - - - belongs_to :turma -
        4. -
          - -
          -
        5. - - 1 - - - - - belongs_to :modelo -
        6. -
          - -
          -
        7. - - 1 - - - - - belongs_to :professor_alvo, class_name: 'User', optional: true -
        8. -
          - -
          -
        9. - - - - - -
        10. -
          - -
          -
        11. - - 1 - - - - - has_many :submissoes, class_name: 'Submissao', dependent: :destroy -
        12. -
          - -
          -
        13. - - 1 - - - - - has_many :respostas, through: :submissoes -
        14. -
          - -
          -
        15. - - - - - end -
        16. -
          - -
        -
        -
        - - -
        -
        -

        app/models/current.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 3 relevant lines. - 3 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class Current < ActiveSupport::CurrentAttributes -
        2. -
          - -
          -
        3. - - 1 - - - - - attribute :session -
        4. -
          - -
          -
        5. - - 1 - - - - - delegate :user, to: :session, allow_nil: true -
        6. -
          - -
          -
        7. - - - - - end -
        8. -
          - -
        -
        -
        - - -
        -
        -

        app/models/modelo.rb

        -

        - - 51.85% - - - lines covered -

        - - - -
        - 27 relevant lines. - 14 lines covered and - 13 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class Modelo < ApplicationRecord -
        2. -
          - -
          -
        3. - - - - - # Relacionamentos -
        4. -
          - -
          -
        5. - - 1 - - - - - has_many :perguntas, dependent: :destroy -
        6. -
          - -
          -
        7. - - 1 - - - - - has_many :avaliacoes, dependent: :restrict_with_error -
        8. -
          - -
          -
        9. - - - - - -
        10. -
          - -
          -
        11. - - - - - # Validações -
        12. -
          - -
          -
        13. - - 1 - - - - - validates :titulo, presence: true, uniqueness: { case_sensitive: false } -
        14. -
          - -
          -
        15. - - - - - -
        16. -
          - -
          -
        17. - - - - - # Validação customizada: não permitir modelo sem perguntas -
        18. -
          - -
          -
        19. - - 1 - - - - - validate :deve_ter_pelo_menos_uma_pergunta, on: :create -
        20. -
          - -
          -
        21. - - 1 - - - - - validate :nao_pode_remover_todas_perguntas, on: :update -
        22. -
          - -
          -
        23. - - - - - -
        24. -
          - -
          -
        25. - - - - - # Aceita atributos aninhados para perguntas -
        26. -
          - -
          -
        27. - - 1 - - - - - accepts_nested_attributes_for :perguntas, -
        28. -
          - -
          -
        29. - - - - - allow_destroy: true, -
        30. -
          - -
          -
        31. - - - - - reject_if: :all_blank -
        32. -
          - -
          -
        33. - - - - - -
        34. -
          - -
          -
        35. - - - - - # Método para verificar se modelo está em uso -
        36. -
          - -
          -
        37. - - 1 - - - - - def em_uso? -
        38. -
          - -
          -
        39. - - - - - avaliacoes.any? -
        40. -
          - -
          -
        41. - - - - - end -
        42. -
          - -
          -
        43. - - - - - -
        44. -
          - -
          -
        45. - - - - - # Método para clonar modelo com perguntas -
        46. -
          - -
          -
        47. - - 1 - - - - - def clonar_com_perguntas(novo_titulo) -
        48. -
          - -
          -
        49. - - - - - novo_modelo = dup -
        50. -
          - -
          -
        51. - - - - - novo_modelo.titulo = novo_titulo -
        52. -
          - -
          -
        53. - - - - - novo_modelo.ativo = false # Clones começam inativos -
        54. -
          - -
          -
        55. - - - - - novo_modelo.save -
        56. -
          - -
          -
        57. - - - - - -
        58. -
          - -
          -
        59. - - - - - if novo_modelo.persisted? -
        60. -
          - -
          -
        61. - - - - - perguntas.each do |pergunta| -
        62. -
          - -
          -
        63. - - - - - nova_pergunta = pergunta.dup -
        64. -
          - -
          -
        65. - - - - - nova_pergunta.modelo = novo_modelo -
        66. -
          - -
          -
        67. - - - - - nova_pergunta.save -
        68. -
          - -
          -
        69. - - - - - end -
        70. -
          - -
          -
        71. - - - - - end -
        72. -
          - -
          -
        73. - - - - - -
        74. -
          - -
          -
        75. - - - - - novo_modelo -
        76. -
          - -
          -
        77. - - - - - end -
        78. -
          - -
          -
        79. - - - - - -
        80. -
          - -
          -
        81. - - 1 - - - - - private -
        82. -
          - -
          -
        83. - - - - - -
        84. -
          - -
          -
        85. - - 1 - - - - - def deve_ter_pelo_menos_uma_pergunta -
        86. -
          - -
          -
        87. - - 3 - - - - - if perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? } -
        88. -
          - -
          -
        89. - - 3 - - - - - errors.add(:base, "Um modelo deve ter pelo menos uma pergunta") -
        90. -
          - -
          -
        91. - - - - - end -
        92. -
          - -
          -
        93. - - - - - end -
        94. -
          - -
          -
        95. - - - - - -
        96. -
          - -
          -
        97. - - 1 - - - - - def nao_pode_remover_todas_perguntas -
        98. -
          - -
          -
        99. - - - - - if persisted? && (perguntas.empty? || perguntas.all? { |p| p.marked_for_destruction? }) -
        100. -
          - -
          -
        101. - - - - - errors.add(:base, "Não é possível remover todas as perguntas de um modelo existente") -
        102. -
          - -
          -
        103. - - - - - end -
        104. -
          - -
          -
        105. - - - - - end -
        106. -
          - -
          -
        107. - - - - - end -
        108. -
          - -
        -
        -
        - - -
        -
        -

        app/models/pergunta.rb

        -

        - - 47.22% - - - lines covered -

        - - - -
        - 36 relevant lines. - 17 lines covered and - 19 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class Pergunta < ApplicationRecord -
        2. -
          - -
          -
        3. - - 1 - - - - - self.table_name = 'perguntas' # Plural correto em português -
        4. -
          - -
          -
        5. - - - - - -
        6. -
          - -
          -
        7. - - - - - # Relacionamentos -
        8. -
          - -
          -
        9. - - 1 - - - - - belongs_to :modelo -
        10. -
          - -
          -
        11. - - 1 - - - - - has_many :respostas, foreign_key: 'questao_id', dependent: :destroy -
        12. -
          - -
          -
        13. - - - - - -
        14. -
          - -
          -
        15. - - - - - # Tipos de perguntas disponíveis -
        16. -
          - -
          -
        17. - - - - - TIPOS = { -
        18. -
          - -
          -
        19. - - 1 - - - - - 'texto_longo' => 'Texto Longo', -
        20. -
          - -
          -
        21. - - - - - 'texto_curto' => 'Texto Curto', -
        22. -
          - -
          -
        23. - - - - - 'multipla_escolha' => 'Múltipla Escolha', -
        24. -
          - -
          -
        25. - - - - - 'checkbox' => 'Checkbox (Múltipla Seleção)', -
        26. -
          - -
          -
        27. - - - - - 'escala' => 'Escala Likert (1-5)', -
        28. -
          - -
          -
        29. - - - - - 'data' => 'Data', -
        30. -
          - -
          -
        31. - - - - - 'hora' => 'Hora' -
        32. -
          - -
          -
        33. - - - - - }.freeze -
        34. -
          - -
          -
        35. - - - - - -
        36. -
          - -
          -
        37. - - - - - # Validações -
        38. -
          - -
          -
        39. - - 1 - - - - - validates :enunciado, presence: true -
        40. -
          - -
          -
        41. - - 1 - - - - - validates :tipo, presence: true, inclusion: { in: TIPOS.keys } -
        42. -
          - -
          -
        43. - - - - - -
        44. -
          - -
          -
        45. - - - - - # Validações condicionais -
        46. -
          - -
          -
        47. - - 1 - - - - - validate :opcoes_requeridas_para_multipla_escolha -
        48. -
          - -
          -
        49. - - 1 - - - - - validate :opcoes_requeridas_para_checkbox -
        50. -
          - -
          -
        51. - - - - - -
        52. -
          - -
          -
        53. - - - - - # Callbacks -
        54. -
          - -
          -
        55. - - 1 - - - - - before_validation :definir_ordem_padrao, on: :create -
        56. -
          - -
          -
        57. - - - - - -
        58. -
          - -
          -
        59. - - - - - # Métodos -
        60. -
          - -
          -
        61. - - 1 - - - - - def tipo_humanizado -
        62. -
          - -
          -
        63. - - - - - TIPOS[tipo] || tipo -
        64. -
          - -
          -
        65. - - - - - end -
        66. -
          - -
          -
        67. - - - - - -
        68. -
          - -
          -
        69. - - 1 - - - - - def requer_opcoes? -
        70. -
          - -
          -
        71. - - - - - ['multipla_escolha', 'checkbox'].include?(tipo) -
        72. -
          - -
          -
        73. - - - - - end -
        74. -
          - -
          -
        75. - - - - - -
        76. -
          - -
          -
        77. - - 1 - - - - - def lista_opcoes -
        78. -
          - -
          -
        79. - - - - - return [] unless opcoes.present? -
        80. -
          - -
          -
        81. - - - - - # Assume que opcoes é JSON array ou string separada por ; -
        82. -
          - -
          -
        83. - - - - - if opcoes.is_a?(Array) -
        84. -
          - -
          -
        85. - - - - - opcoes -
        86. -
          - -
          -
        87. - - - - - elsif opcoes.is_a?(String) -
        88. -
          - -
          -
        89. - - - - - begin -
        90. -
          - -
          -
        91. - - - - - JSON.parse(opcoes) -
        92. -
          - -
          -
        93. - - - - - rescue JSON::ParserError -
        94. -
          - -
          -
        95. - - - - - opcoes.split(';').map(&:strip) -
        96. -
          - -
          -
        97. - - - - - end -
        98. -
          - -
          -
        99. - - - - - else -
        100. -
          - -
          -
        101. - - - - - [] -
        102. -
          - -
          -
        103. - - - - - end -
        104. -
          - -
          -
        105. - - - - - end -
        106. -
          - -
          -
        107. - - - - - -
        108. -
          - -
          -
        109. - - 1 - - - - - private -
        110. -
          - -
          -
        111. - - - - - -
        112. -
          - -
          -
        113. - - 1 - - - - - def definir_ordem_padrao -
        114. -
          - -
          -
        115. - - - - - if modelo.present? -
        116. -
          - -
          -
        117. - - - - - ultima_ordem = modelo.perguntas.maximum(:id) || 0 -
        118. -
          - -
          -
        119. - - - - - # Ordem pode ser baseada no ID para simplificar -
        120. -
          - -
          -
        121. - - - - - end -
        122. -
          - -
          -
        123. - - - - - end -
        124. -
          - -
          -
        125. - - - - - -
        126. -
          - -
          -
        127. - - 1 - - - - - def opcoes_requeridas_para_multipla_escolha -
        128. -
          - -
          -
        129. - - - - - if tipo == 'multipla_escolha' -
        130. -
          - -
          -
        131. - - - - - opcoes_lista = lista_opcoes -
        132. -
          - -
          -
        133. - - - - - if opcoes_lista.blank? || opcoes_lista.size < 2 -
        134. -
          - -
          -
        135. - - - - - errors.add(:opcoes, 'deve ter pelo menos duas opções para múltipla escolha') -
        136. -
          - -
          -
        137. - - - - - end -
        138. -
          - -
          -
        139. - - - - - end -
        140. -
          - -
          -
        141. - - - - - end -
        142. -
          - -
          -
        143. - - - - - -
        144. -
          - -
          -
        145. - - 1 - - - - - def opcoes_requeridas_para_checkbox -
        146. -
          - -
          -
        147. - - - - - if tipo == 'checkbox' -
        148. -
          - -
          -
        149. - - - - - opcoes_lista = lista_opcoes -
        150. -
          - -
          -
        151. - - - - - if opcoes_lista.blank? || opcoes_lista.size < 2 -
        152. -
          - -
          -
        153. - - - - - errors.add(:opcoes, 'deve ter pelo menos duas opções para checkbox') -
        154. -
          - -
          -
        155. - - - - - end -
        156. -
          - -
          -
        157. - - - - - end -
        158. -
          - -
          -
        159. - - - - - end -
        160. -
          - -
          -
        161. - - - - - end -
        162. -
          - -
        -
        -
        - - -
        -
        -

        app/models/turma.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 4 relevant lines. - 4 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class Turma < ApplicationRecord -
        2. -
          - -
          -
        3. - - 1 - - - - - has_many :avaliacoes -
        4. -
          - -
          -
        5. - - 1 - - - - - has_many :matricula_turmas -
        6. -
          - -
          -
        7. - - 1 - - - - - has_many :users, through: :matricula_turmas -
        8. -
          - -
          -
        9. - - - - - end -
        10. -
          - -
        -
        -
        - - -
        -
        -

        app/models/user.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 12 relevant lines. - 12 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - class User < ApplicationRecord -
        2. -
          - -
          -
        3. - - 1 - - - - - has_secure_password -
        4. -
          - -
          -
        5. - - 1 - - - - - has_many :sessions, dependent: :destroy -
        6. -
          - -
          -
        7. - - 1 - - - - - has_many :matricula_turmas -
        8. -
          - -
          -
        9. - - 1 - - - - - has_many :turmas, through: :matricula_turmas -
        10. -
          - -
          -
        11. - - 1 - - - - - has_many :submissoes, class_name: 'Submissao', foreign_key: :aluno_id, dependent: :destroy -
        12. -
          - -
          -
        13. - - - - - -
        14. -
          - -
          -
        15. - - 1 - - - - - validates :email_address, presence: true, uniqueness: true -
        16. -
          - -
          -
        17. - - 1 - - - - - validates :login, presence: true, uniqueness: true -
        18. -
          - -
          -
        19. - - 1 - - - - - validates :matricula, presence: true, uniqueness: true -
        20. -
          - -
          -
        21. - - 1 - - - - - validates :nome, presence: true -
        22. -
          - -
          -
        23. - - - - - -
        24. -
          - -
          -
        25. - - 15 - - - - - normalizes :email_address, with: ->(e) { e.strip.downcase } -
        26. -
          - -
          -
        27. - - 7 - - - - - normalizes :login, with: ->(l) { l.strip.downcase } -
        28. -
          - -
          -
        29. - - - - - end -
        30. -
          - -
        -
        -
        - - -
        -
        -

        app/services/csv_formatter_service.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 19 relevant lines. - 19 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'csv' -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - class CsvFormatterService -
        6. -
          - -
          -
        7. - - 1 - - - - - def initialize(avaliacao) -
        8. -
          - -
          -
        9. - - 1 - - - - - @avaliacao = avaliacao -
        10. -
          - -
          -
        11. - - - - - end -
        12. -
          - -
          -
        13. - - - - - -
        14. -
          - -
          -
        15. - - 1 - - - - - def generate -
        16. -
          - -
          -
        17. - - 1 - - - - - CSV.generate(headers: true) do |csv| -
        18. -
          - -
          -
        19. - - 1 - - - - - csv << headers -
        20. -
          - -
          -
        21. - - - - - -
        22. -
          - -
          -
        23. - - 1 - - - - - @avaliacao.submissoes.includes(:aluno, :respostas).each do |submissao| -
        24. -
          - -
          -
        25. - - 2 - - - - - aluno = submissao.aluno -
        26. -
          - -
          -
        27. - - 2 - - - - - row = [aluno.matricula, aluno.nome] -
        28. -
          - -
          -
        29. - - - - - -
        30. -
          - -
          -
        31. - - - - - # Organiza as respostas pela ordem das questões se possível, ou mapeamento simples -
        32. -
          - -
          -
        33. - - - - - # Assumindo que queremos mapear questões para colunas -
        34. -
          - -
          -
        35. - - - - - -
        36. -
          - -
          -
        37. - - - - - # Para este MVP, vamos apenas despejar o conteúdo na ordem das questões encontradas -
        38. -
          - -
          -
        39. - - - - - # Uma solução mais robusta ordenaria por ID da questão ou número -
        40. -
          - -
          -
        41. - - - - - -
        42. -
          - -
          -
        43. - - 2 - - - - - submissao.respostas.each do |resposta| -
        44. -
          - -
          -
        45. - - 3 - - - - - row << resposta.conteudo -
        46. -
          - -
          -
        47. - - - - - end -
        48. -
          - -
          -
        49. - - - - - -
        50. -
          - -
          -
        51. - - 2 - - - - - csv << row -
        52. -
          - -
          -
        53. - - - - - end -
        54. -
          - -
          -
        55. - - - - - end -
        56. -
          - -
          -
        57. - - - - - end -
        58. -
          - -
          -
        59. - - - - - -
        60. -
          - -
          -
        61. - - 1 - - - - - private -
        62. -
          - -
          -
        63. - - - - - -
        64. -
          - -
          -
        65. - - 1 - - - - - def headers -
        66. -
          - -
          -
        67. - - - - - # Cabeçalhos estáticos para informações do Aluno -
        68. -
          - -
          -
        69. - - 1 - - - - - base_headers = ["Matrícula", "Nome"] -
        70. -
          - -
          -
        71. - - - - - -
        72. -
          - -
          -
        73. - - - - - # Cabeçalhos dinâmicos para questões -
        74. -
          - -
          -
        75. - - - - - # Identificando questões únicas respondidas ou todas as questões do modelo -
        76. -
          - -
          -
        77. - - - - - # Para o MVP, vamos assumir que queremos todas as questões do modelo -
        78. -
          - -
          -
        79. - - - - - -
        80. -
          - -
          -
        81. - - 1 - - - - - questoes = @avaliacao.modelo.perguntas -
        82. -
          - -
          -
        83. - - 3 - - - - - question_headers = questoes.map.with_index { |q, i| "Questão #{i + 1}" } -
        84. -
          - -
          -
        85. - - - - - -
        86. -
          - -
          -
        87. - - 1 - - - - - base_headers + question_headers -
        88. -
          - -
          -
        89. - - - - - end -
        90. -
          - -
          -
        91. - - - - - end -
        92. -
          - -
        -
        -
        - - -
        -
        -

        app/services/sigaa_import_service.rb

        -

        - - 87.01% - - - lines covered -

        - - - -
        - 77 relevant lines. - 67 lines covered and - 10 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'json' -
        2. -
          - -
          -
        3. - - 1 - - - - - require 'csv' -
        4. -
          - -
          -
        5. - - - - - -
        6. -
          - -
          -
        7. - - 1 - - - - - class SigaaImportService -
        8. -
          - -
          -
        9. - - 1 - - - - - def initialize(file_path) -
        10. -
          - -
          -
        11. - - 3 - - - - - @file_path = file_path -
        12. -
          - -
          -
        13. - - 3 - - - - - @results = { -
        14. -
          - -
          -
        15. - - - - - turmas_created: 0, -
        16. -
          - -
          -
        17. - - - - - turmas_updated: 0, -
        18. -
          - -
          -
        19. - - - - - users_created: 0, -
        20. -
          - -
          -
        21. - - - - - users_updated: 0, -
        22. -
          - -
          -
        23. - - - - - new_users: [], # Array de hashes com credenciais dos novos usuários -
        24. -
          - -
          -
        25. - - - - - errors: [] -
        26. -
          - -
          -
        27. - - - - - } -
        28. -
          - -
          -
        29. - - - - - end -
        30. -
          - -
          -
        31. - - - - - -
        32. -
          - -
          -
        33. - - 1 - - - - - def process -
        34. -
          - -
          -
        35. - - 3 - - - - - unless File.exist?(@file_path) -
        36. -
          - -
          -
        37. - - - - - @results[:errors] << "Arquivo não encontrado: #{@file_path}" -
        38. -
          - -
          -
        39. - - - - - return @results -
        40. -
          - -
          -
        41. - - - - - end -
        42. -
          - -
          -
        43. - - - - - -
        44. -
          - -
          -
        45. - - - - - begin -
        46. -
          - -
          -
        47. - - 3 - - - - - ActiveRecord::Base.transaction do -
        48. -
          - -
          -
        49. - - 3 - - - - - case File.extname(@file_path).downcase -
        50. -
          - -
          -
        51. - - - - - when '.json' -
        52. -
          - -
          -
        53. - - 1 - - - - - process_json -
        54. -
          - -
          -
        55. - - - - - when '.csv' -
        56. -
          - -
          -
        57. - - 1 - - - - - process_csv -
        58. -
          - -
          -
        59. - - - - - else -
        60. -
          - -
          -
        61. - - 1 - - - - - @results[:errors] << "Formato de arquivo não suportado: #{File.extname(@file_path)}" -
        62. -
          - -
          -
        63. - - - - - end -
        64. -
          - -
          -
        65. - - - - - -
        66. -
          - -
          -
        67. - - 3 - - - - - if @results[:errors].any? -
        68. -
          - -
          -
        69. - - 1 - - - - - raise ActiveRecord::Rollback -
        70. -
          - -
          -
        71. - - - - - end -
        72. -
          - -
          -
        73. - - - - - end -
        74. -
          - -
          -
        75. - - - - - rescue JSON::ParserError -
        76. -
          - -
          -
        77. - - - - - @results[:errors] << "Arquivo JSON inválido" -
        78. -
          - -
          -
        79. - - - - - rescue ActiveRecord::StatementInvalid => e -
        80. -
          - -
          -
        81. - - - - - @results[:errors] << "Erro de conexão com o banco de dados: #{e.message}" -
        82. -
          - -
          -
        83. - - - - - rescue StandardError => e -
        84. -
          - -
          -
        85. - - - - - @results[:errors] << "Erro inesperado: #{e.message}" -
        86. -
          - -
          -
        87. - - - - - end -
        88. -
          - -
          -
        89. - - - - - -
        90. -
          - -
          -
        91. - - 3 - - - - - @results -
        92. -
          - -
          -
        93. - - - - - end -
        94. -
          - -
          -
        95. - - - - - -
        96. -
          - -
          -
        97. - - 1 - - - - - private -
        98. -
          - -
          -
        99. - - - - - -
        100. -
          - -
          -
        101. - - 1 - - - - - def process_json -
        102. -
          - -
          -
        103. - - 1 - - - - - data = JSON.parse(File.read(@file_path)) -
        104. -
          - -
          -
        105. - - - - - -
        106. -
          - -
          -
        107. - - - - - # class_members.json é um array de turmas -
        108. -
          - -
          -
        109. - - 1 - - - - - data.each do |turma_data| -
        110. -
          - -
          -
        111. - - - - - # Mapeia campos do formato real para o esperado -
        112. -
          - -
          -
        113. - - - - - normalized_data = { -
        114. -
          - -
          -
        115. - - 1 - - - - - 'codigo' => turma_data['code'], -
        116. -
          - -
          -
        117. - - - - - 'nome' => turma_data['code'], # Usa o código como nome se não tiver -
        118. -
          - -
          -
        119. - - - - - 'semestre' => turma_data['semester'], -
        120. -
          - -
          -
        121. - - - - - 'participantes' => [] -
        122. -
          - -
          -
        123. - - - - - } -
        124. -
          - -
          -
        125. - - - - - -
        126. -
          - -
          -
        127. - - - - - # Processa dicentes (alunos) -
        128. -
          - -
          -
        129. - - 1 - - - - - if turma_data['dicente'] -
        130. -
          - -
          -
        131. - - 1 - - - - - turma_data['dicente'].each do |dicente| -
        132. -
          - -
          -
        133. - - 1 - - - - - normalized_data['participantes'] << { -
        134. -
          - -
          -
        135. - - - - - 'nome' => dicente['nome'], -
        136. -
          - -
          -
        137. - - - - - 'email' => dicente['email'], -
        138. -
          - -
          -
        139. - - - - - 'matricula' => dicente['matricula'] || dicente['usuario'], -
        140. -
          - -
          -
        141. - - - - - 'papel' => 'Discente' -
        142. -
          - -
          -
        143. - - - - - } -
        144. -
          - -
          -
        145. - - - - - end -
        146. -
          - -
          -
        147. - - - - - end -
        148. -
          - -
          -
        149. - - - - - -
        150. -
          - -
          -
        151. - - - - - # Processa docente (professor) -
        152. -
          - -
          -
        153. - - 1 - - - - - if turma_data['docente'] -
        154. -
          - -
          -
        155. - - 1 - - - - - docente = turma_data['docente'] -
        156. -
          - -
          -
        157. - - 1 - - - - - normalized_data['participantes'] << { -
        158. -
          - -
          -
        159. - - - - - 'nome' => docente['nome'], -
        160. -
          - -
          -
        161. - - - - - 'email' => docente['email'], -
        162. -
          - -
          -
        163. - - - - - 'matricula' => docente['usuario'], -
        164. -
          - -
          -
        165. - - - - - 'papel' => 'Docente' -
        166. -
          - -
          -
        167. - - - - - } -
        168. -
          - -
          -
        169. - - - - - end -
        170. -
          - -
          -
        171. - - - - - -
        172. -
          - -
          -
        173. - - 1 - - - - - process_turma(normalized_data) -
        174. -
          - -
          -
        175. - - - - - end -
        176. -
          - -
          -
        177. - - - - - end -
        178. -
          - -
          -
        179. - - - - - -
        180. -
          - -
          -
        181. - - 1 - - - - - def process_csv -
        182. -
          - -
          -
        183. - - 1 - - - - - CSV.foreach(@file_path, headers: true, col_sep: ',') do |row| -
        184. -
          - -
          -
        185. - - - - - # Assumindo estrutura do CSV -
        186. -
          - -
          -
        187. - - - - - turma_data = { -
        188. -
          - -
          -
        189. - - 1 - - - - - 'codigo' => row['codigo_turma'], -
        190. -
          - -
          -
        191. - - - - - 'nome' => row['nome_turma'], -
        192. -
          - -
          -
        193. - - - - - 'semestre' => row['semestre'] -
        194. -
          - -
          -
        195. - - - - - } -
        196. -
          - -
          -
        197. - - - - - -
        198. -
          - -
          -
        199. - - 1 - - - - - turma = process_turma_record(turma_data) -
        200. -
          - -
          -
        201. - - - - - -
        202. -
          - -
          -
        203. - - 1 - - - - - if turma&.persisted? -
        204. -
          - -
          -
        205. - - - - - user_data = { -
        206. -
          - -
          -
        207. - - 1 - - - - - 'nome' => row['nome_usuario'], -
        208. -
          - -
          -
        209. - - - - - 'email' => row['email'], -
        210. -
          - -
          -
        211. - - - - - 'matricula' => row['matricula'], -
        212. -
          - -
          -
        213. - - - - - 'papel' => row['papel'] -
        214. -
          - -
          -
        215. - - - - - } -
        216. -
          - -
          -
        217. - - 1 - - - - - process_participante_single(turma, user_data) -
        218. -
          - -
          -
        219. - - - - - end -
        220. -
          - -
          -
        221. - - - - - end -
        222. -
          - -
          -
        223. - - - - - end -
        224. -
          - -
          -
        225. - - - - - -
        226. -
          - -
          -
        227. - - 1 - - - - - def process_turma(data) -
        228. -
          - -
          -
        229. - - 1 - - - - - turma = process_turma_record(data) -
        230. -
          - -
          -
        231. - - 1 - - - - - if turma&.persisted? -
        232. -
          - -
          -
        233. - - 1 - - - - - process_participantes(turma, data['participantes']) if data['participantes'] -
        234. -
          - -
          -
        235. - - - - - end -
        236. -
          - -
          -
        237. - - - - - end -
        238. -
          - -
          -
        239. - - - - - -
        240. -
          - -
          -
        241. - - 1 - - - - - def process_turma_record(data) -
        242. -
          - -
          -
        243. - - 2 - - - - - turma = Turma.find_or_initialize_by(codigo: data['codigo'], semestre: data['semestre']) -
        244. -
          - -
          -
        245. - - - - - -
        246. -
          - -
          -
        247. - - 2 - - - - - is_new_record = turma.new_record? -
        248. -
          - -
          -
        249. - - 2 - - - - - turma.nome = data['nome'] -
        250. -
          - -
          -
        251. - - - - - -
        252. -
          - -
          -
        253. - - 2 - - - - - if turma.save -
        254. -
          - -
          -
        255. - - 2 - - - - - if is_new_record -
        256. -
          - -
          -
        257. - - 2 - - - - - @results[:turmas_created] += 1 -
        258. -
          - -
          -
        259. - - - - - else -
        260. -
          - -
          -
        261. - - - - - @results[:turmas_updated] += 1 -
        262. -
          - -
          -
        263. - - - - - end -
        264. -
          - -
          -
        265. - - 2 - - - - - turma -
        266. -
          - -
          -
        267. - - - - - else -
        268. -
          - -
          -
        269. - - - - - @results[:errors] << "Erro ao salvar turma #{data['codigo']}: #{turma.errors.full_messages.join(', ')}" -
        270. -
          - -
          -
        271. - - - - - nil -
        272. -
          - -
          -
        273. - - - - - end -
        274. -
          - -
          -
        275. - - - - - end -
        276. -
          - -
          -
        277. - - - - - -
        278. -
          - -
          -
        279. - - 1 - - - - - def process_participantes(turma, participantes_data) -
        280. -
          - -
          -
        281. - - 1 - - - - - participantes_data.each do |p_data| -
        282. -
          - -
          -
        283. - - 2 - - - - - process_participante_single(turma, p_data) -
        284. -
          - -
          -
        285. - - - - - end -
        286. -
          - -
          -
        287. - - - - - end -
        288. -
          - -
          -
        289. - - - - - -
        290. -
          - -
          -
        291. - - 1 - - - - - def process_participante_single(turma, p_data) -
        292. -
          - -
          -
        293. - - - - - # User identificado pela matrícula -
        294. -
          - -
          -
        295. - - 3 - - - - - user = User.find_or_initialize_by(matricula: p_data['matricula']) -
        296. -
          - -
          -
        297. - - - - - -
        298. -
          - -
          -
        299. - - 3 - - - - - is_new_user = user.new_record? -
        300. -
          - -
          -
        301. - - 3 - - - - - user.nome = p_data['nome'] -
        302. -
          - -
          -
        303. - - 3 - - - - - user.email_address = p_data['email'] -
        304. -
          - -
          -
        305. - - - - - -
        306. -
          - -
          -
        307. - - - - - # Generate login from matricula if not present (assuming matricula is unique and good for login) -
        308. -
          - -
          -
        309. - - 3 - - - - - user.login = p_data['matricula'] if user.login.blank? -
        310. -
          - -
          -
        311. - - - - - -
        312. -
          - -
          -
        313. - - 3 - - - - - generated_password = nil -
        314. -
          - -
          -
        315. - - 3 - - - - - if is_new_user -
        316. -
          - -
          -
        317. - - 3 - - - - - generated_password = SecureRandom.hex(8) -
        318. -
          - -
          -
        319. - - 3 - - - - - user.password = generated_password -
        320. -
          - -
          -
        321. - - - - - end -
        322. -
          - -
          -
        323. - - - - - -
        324. -
          - -
          -
        325. - - 3 - - - - - if user.save -
        326. -
          - -
          -
        327. - - 3 - - - - - if is_new_user -
        328. -
          - -
          -
        329. - - 3 - - - - - @results[:users_created] += 1 -
        330. -
          - -
          -
        331. - - - - - -
        332. -
          - -
          -
        333. - - - - - # Armazena credenciais do novo usuário para exibir depois -
        334. -
          - -
          -
        335. - - 3 - - - - - @results[:new_users] << { -
        336. -
          - -
          -
        337. - - - - - matricula: user.matricula, -
        338. -
          - -
          -
        339. - - - - - nome: user.nome, -
        340. -
          - -
          -
        341. - - - - - login: user.login, -
        342. -
          - -
          -
        343. - - - - - password: generated_password, -
        344. -
          - -
          -
        345. - - - - - email: user.email_address -
        346. -
          - -
          -
        347. - - - - - } -
        348. -
          - -
          -
        349. - - - - - -
        350. -
          - -
          -
        351. - - - - - # Envia email com senha para novo usuário (COMENTADO - muito lento) -
        352. -
          - -
          -
        353. - - - - - # UserMailer.cadastro_email(user, generated_password).deliver_now -
        354. -
          - -
          -
        355. - - - - - else -
        356. -
          - -
          -
        357. - - - - - @results[:users_updated] += 1 -
        358. -
          - -
          -
        359. - - - - - end -
        360. -
          - -
          -
        361. - - - - - -
        362. -
          - -
          -
        363. - - 3 - - - - - matricula = MatriculaTurma.find_or_initialize_by(turma: turma, user: user) -
        364. -
          - -
          -
        365. - - 3 - - - - - matricula.papel = p_data['papel'] -
        366. -
          - -
          -
        367. - - 3 - - - - - matricula.save! -
        368. -
          - -
          -
        369. - - - - - else -
        370. -
          - -
          -
        371. - - - - - @results[:errors] << "Erro ao salvar usuário #{p_data['matricula']}: #{user.errors.full_messages.join(', ')}" -
        372. -
          - -
          -
        373. - - - - - end -
        374. -
          - -
          -
        375. - - - - - end -
        376. -
          - -
          -
        377. - - - - - end -
        378. -
          - -
        -
        -
        - - -
        -
        -

        config/application.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 7 relevant lines. - 7 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require_relative "boot" -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - require "rails/all" -
        6. -
          - -
          -
        7. - - - - - -
        8. -
          - -
          -
        9. - - - - - # Require the gems listed in Gemfile, including any gems -
        10. -
          - -
          -
        11. - - - - - # you've limited to :test, :development, or :production. -
        12. -
          - -
          -
        13. - - 1 - - - - - Bundler.require(*Rails.groups) -
        14. -
          - -
          -
        15. - - - - - -
        16. -
          - -
          -
        17. - - 1 - - - - - module Camaar -
        18. -
          - -
          -
        19. - - 1 - - - - - class Application < Rails::Application -
        20. -
          - -
          -
        21. - - - - - # Initialize configuration defaults for originally generated Rails version. -
        22. -
          - -
          -
        23. - - 1 - - - - - config.load_defaults 8.0 -
        24. -
          - -
          -
        25. - - - - - -
        26. -
          - -
          -
        27. - - - - - # Please, add to the `ignore` list any other `lib` subdirectories that do -
        28. -
          - -
          -
        29. - - - - - # not contain `.rb` files, or that should not be reloaded or eager loaded. -
        30. -
          - -
          -
        31. - - - - - # Common ones are `templates`, `generators`, or `middleware`, for example. -
        32. -
          - -
          -
        33. - - 1 - - - - - config.autoload_lib(ignore: %w[assets tasks]) -
        34. -
          - -
          -
        35. - - - - - -
        36. -
          - -
          -
        37. - - - - - # Configuration for the application, engines, and railties goes here. -
        38. -
          - -
          -
        39. - - - - - # -
        40. -
          - -
          -
        41. - - - - - # These settings can be overridden in specific environments using the files -
        42. -
          - -
          -
        43. - - - - - # in config/environments, which are processed later. -
        44. -
          - -
          -
        45. - - - - - # -
        46. -
          - -
          -
        47. - - - - - # config.time_zone = "Central Time (US & Canada)" -
        48. -
          - -
          -
        49. - - - - - # config.eager_load_paths << Rails.root.join("extras") -
        50. -
          - -
          -
        51. - - - - - end -
        52. -
          - -
          -
        53. - - - - - end -
        54. -
          - -
        -
        -
        - - -
        -
        -

        config/boot.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 3 relevant lines. - 3 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - require "bundler/setup" # Set up gems listed in the Gemfile. -
        6. -
          - -
          -
        7. - - 1 - - - - - require "bootsnap/setup" # Speed up boot time by caching expensive operations. -
        8. -
          - -
        -
        -
        - - -
        -
        -

        config/environment.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 2 relevant lines. - 2 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # Load the Rails application. -
        2. -
          - -
          -
        3. - - 1 - - - - - require_relative "application" -
        4. -
          - -
          -
        5. - - - - - -
        6. -
          - -
          -
        7. - - - - - # Initialize the Rails application. -
        8. -
          - -
          -
        9. - - 1 - - - - - Rails.application.initialize! -
        10. -
          - -
        -
        -
        - - -
        -
        -

        config/environments/test.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 15 relevant lines. - 15 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # The test environment is used exclusively to run your application's -
        2. -
          - -
          -
        3. - - - - - # test suite. You never need to work with it otherwise. Remember that -
        4. -
          - -
          -
        5. - - - - - # your test database is "scratch space" for the test suite and is wiped -
        6. -
          - -
          -
        7. - - - - - # and recreated between test runs. Don't rely on the data there! -
        8. -
          - -
          -
        9. - - - - - -
        10. -
          - -
          -
        11. - - 1 - - - - - Rails.application.configure do -
        12. -
          - -
          -
        13. - - - - - # Configure 'rails notes' to inspect Cucumber files -
        14. -
          - -
          -
        15. - - 1 - - - - - config.annotations.register_directories("features") -
        16. -
          - -
          -
        17. - - 1 - - - - - config.annotations.register_extensions("feature") { |tag| /#\s*(#{tag}):?\s*(.*)$/ } -
        18. -
          - -
          -
        19. - - - - - -
        20. -
          - -
          -
        21. - - - - - # Settings specified here will take precedence over those in config/application.rb. -
        22. -
          - -
          -
        23. - - - - - -
        24. -
          - -
          -
        25. - - - - - # While tests run files are not watched, reloading is not necessary. -
        26. -
          - -
          -
        27. - - 1 - - - - - config.enable_reloading = false -
        28. -
          - -
          -
        29. - - - - - -
        30. -
          - -
          -
        31. - - - - - # Eager loading loads your entire application. When running a single test locally, -
        32. -
          - -
          -
        33. - - - - - # this is usually not necessary, and can slow down your test suite. However, it's -
        34. -
          - -
          -
        35. - - - - - # recommended that you enable it in continuous integration systems to ensure eager -
        36. -
          - -
          -
        37. - - - - - # loading is working properly before deploying your code. -
        38. -
          - -
          -
        39. - - 1 - - - - - config.eager_load = ENV["CI"].present? -
        40. -
          - -
          -
        41. - - - - - -
        42. -
          - -
          -
        43. - - - - - # Configure public file server for tests with cache-control for performance. -
        44. -
          - -
          -
        45. - - 1 - - - - - config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } -
        46. -
          - -
          -
        47. - - - - - -
        48. -
          - -
          -
        49. - - - - - # Show full error reports. -
        50. -
          - -
          -
        51. - - 1 - - - - - config.consider_all_requests_local = true -
        52. -
          - -
          -
        53. - - 1 - - - - - config.cache_store = :null_store -
        54. -
          - -
          -
        55. - - - - - -
        56. -
          - -
          -
        57. - - - - - # Render exception templates for rescuable exceptions and raise for other exceptions. -
        58. -
          - -
          -
        59. - - 1 - - - - - config.action_dispatch.show_exceptions = :rescuable -
        60. -
          - -
          -
        61. - - - - - -
        62. -
          - -
          -
        63. - - - - - # Disable request forgery protection in test environment. -
        64. -
          - -
          -
        65. - - 1 - - - - - config.action_controller.allow_forgery_protection = false -
        66. -
          - -
          -
        67. - - - - - -
        68. -
          - -
          -
        69. - - - - - # Store uploaded files on the local file system in a temporary directory. -
        70. -
          - -
          -
        71. - - 1 - - - - - config.active_storage.service = :test -
        72. -
          - -
          -
        73. - - - - - -
        74. -
          - -
          -
        75. - - - - - # Tell Action Mailer not to deliver emails to the real world. -
        76. -
          - -
          -
        77. - - - - - # The :test delivery method accumulates sent emails in the -
        78. -
          - -
          -
        79. - - - - - # ActionMailer::Base.deliveries array. -
        80. -
          - -
          -
        81. - - 1 - - - - - config.action_mailer.delivery_method = :test -
        82. -
          - -
          -
        83. - - - - - -
        84. -
          - -
          -
        85. - - - - - # Set host to be used by links generated in mailer templates. -
        86. -
          - -
          -
        87. - - 1 - - - - - config.action_mailer.default_url_options = { host: "example.com" } -
        88. -
          - -
          -
        89. - - - - - -
        90. -
          - -
          -
        91. - - - - - # Print deprecation notices to the stderr. -
        92. -
          - -
          -
        93. - - 1 - - - - - config.active_support.deprecation = :stderr -
        94. -
          - -
          -
        95. - - - - - -
        96. -
          - -
          -
        97. - - - - - # Raises error for missing translations. -
        98. -
          - -
          -
        99. - - - - - # config.i18n.raise_on_missing_translations = true -
        100. -
          - -
          -
        101. - - - - - -
        102. -
          - -
          -
        103. - - - - - # Annotate rendered view with file names. -
        104. -
          - -
          -
        105. - - - - - # config.action_view.annotate_rendered_view_with_filenames = true -
        106. -
          - -
          -
        107. - - - - - -
        108. -
          - -
          -
        109. - - - - - # Raise error when a before_action's only/except options reference missing actions. -
        110. -
          - -
          -
        111. - - 1 - - - - - config.action_controller.raise_on_missing_callback_actions = true -
        112. -
          - -
          -
        113. - - - - - end -
        114. -
          - -
        -
        -
        - - -
        -
        -

        config/initializers/assets.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 1 relevant lines. - 1 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # Be sure to restart your server when you modify this file. -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - - - - # Version of your assets, change this if you want to expire all your assets. -
        6. -
          - -
          -
        7. - - 1 - - - - - Rails.application.config.assets.version = "1.0" -
        8. -
          - -
          -
        9. - - - - - -
        10. -
          - -
          -
        11. - - - - - # Add additional assets to the asset load path. -
        12. -
          - -
          -
        13. - - - - - # Rails.application.config.assets.paths << Emoji.images_path -
        14. -
          - -
        -
        -
        - - -
        -
        -

        config/initializers/content_security_policy.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 0 relevant lines. - 0 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # Be sure to restart your server when you modify this file. -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - - - - # Define an application-wide content security policy. -
        6. -
          - -
          -
        7. - - - - - # See the Securing Rails Applications Guide for more information: -
        8. -
          - -
          -
        9. - - - - - # https://guides.rubyonrails.org/security.html#content-security-policy-header -
        10. -
          - -
          -
        11. - - - - - -
        12. -
          - -
          -
        13. - - - - - # Rails.application.configure do -
        14. -
          - -
          -
        15. - - - - - # config.content_security_policy do |policy| -
        16. -
          - -
          -
        17. - - - - - # policy.default_src :self, :https -
        18. -
          - -
          -
        19. - - - - - # policy.font_src :self, :https, :data -
        20. -
          - -
          -
        21. - - - - - # policy.img_src :self, :https, :data -
        22. -
          - -
          -
        23. - - - - - # policy.object_src :none -
        24. -
          - -
          -
        25. - - - - - # policy.script_src :self, :https -
        26. -
          - -
          -
        27. - - - - - # policy.style_src :self, :https -
        28. -
          - -
          -
        29. - - - - - # # Specify URI for violation reports -
        30. -
          - -
          -
        31. - - - - - # # policy.report_uri "/csp-violation-report-endpoint" -
        32. -
          - -
          -
        33. - - - - - # end -
        34. -
          - -
          -
        35. - - - - - # -
        36. -
          - -
          -
        37. - - - - - # # Generate session nonces for permitted importmap, inline scripts, and inline styles. -
        38. -
          - -
          -
        39. - - - - - # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } -
        40. -
          - -
          -
        41. - - - - - # config.content_security_policy_nonce_directives = %w(script-src style-src) -
        42. -
          - -
          -
        43. - - - - - # -
        44. -
          - -
          -
        45. - - - - - # # Report violations without enforcing the policy. -
        46. -
          - -
          -
        47. - - - - - # # config.content_security_policy_report_only = true -
        48. -
          - -
          -
        49. - - - - - # end -
        50. -
          - -
        -
        -
        - - -
        -
        -

        config/initializers/filter_parameter_logging.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 1 relevant lines. - 1 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # Be sure to restart your server when you modify this file. -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - - - - # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. -
        6. -
          - -
          -
        7. - - - - - # Use this to limit dissemination of sensitive information. -
        8. -
          - -
          -
        9. - - - - - # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. -
        10. -
          - -
          -
        11. - - 1 - - - - - Rails.application.config.filter_parameters += [ -
        12. -
          - -
          -
        13. - - - - - :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc -
        14. -
          - -
          -
        15. - - - - - ] -
        16. -
          - -
        -
        -
        - - -
        -
        -

        config/initializers/inflections.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 2 relevant lines. - 2 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # Be sure to restart your server when you modify this file. -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - - - - # Add new inflection rules using the following format. Inflections -
        6. -
          - -
          -
        7. - - - - - # are locale specific, and you may define rules for as many different -
        8. -
          - -
          -
        9. - - - - - # locales as you wish. All of these examples are active by default: -
        10. -
          - -
          -
        11. - - - - - # ActiveSupport::Inflector.inflections(:en) do |inflect| -
        12. -
          - -
          -
        13. - - - - - # inflect.plural /^(ox)$/i, "\\1en" -
        14. -
          - -
          -
        15. - - - - - # inflect.singular /^(ox)en/i, "\\1" -
        16. -
          - -
          -
        17. - - - - - # inflect.irregular "person", "people" -
        18. -
          - -
          -
        19. - - - - - # inflect.uncountable %w( fish sheep ) -
        20. -
          - -
          -
        21. - - - - - # end -
        22. -
          - -
          -
        23. - - - - - -
        24. -
          - -
          -
        25. - - - - - # These inflection rules are supported but not enabled by default: -
        26. -
          - -
          -
        27. - - 1 - - - - - ActiveSupport::Inflector.inflections(:en) do |inflect| -
        28. -
          - -
          -
        29. - - 1 - - - - - inflect.irregular "pergunta", "perguntas" -
        30. -
          - -
          -
        31. - - - - - # inflect.acronym "RESTful" -
        32. -
          - -
          -
        33. - - - - - end -
        34. -
          - -
        -
        -
        - - -
        -
        -

        config/initializers/inflections_custom.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 2 relevant lines. - 2 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - ActiveSupport::Inflector.inflections(:en) do |inflect| -
        2. -
          - -
          -
        3. - - 1 - - - - - inflect.irregular 'avaliacao', 'avaliacoes' -
        4. -
          - -
          -
        5. - - - - - end -
        6. -
          - -
        -
        -
        - - -
        -
        -

        config/initializers/mail.rb

        -

        - - 25.0% - - - lines covered -

        - - - -
        - 12 relevant lines. - 3 lines covered and - 9 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # config/initializers/mail.rb -
        2. -
          - -
          -
        3. - - - - - # Configuração de email para CAMAAR -
        4. -
          - -
          -
        5. - - - - - -
        6. -
          - -
          -
        7. - - 1 - - - - - if Rails.env.test? -
        8. -
          - -
          -
        9. - - - - - # Em testes: captura emails sem enviar -
        10. -
          - -
          -
        11. - - 1 - - - - - Rails.application.config.action_mailer.delivery_method = :test -
        12. -
          - -
          -
        13. - - 1 - - - - - Rails.application.config.action_mailer.default_url_options = { -
        14. -
          - -
          -
        15. - - - - - host: 'localhost', -
        16. -
          - -
          -
        17. - - - - - port: 3000 -
        18. -
          - -
          -
        19. - - - - - } -
        20. -
          - -
          -
        21. - - - - - -
        22. -
          - -
          -
        23. - - - - - elsif Rails.env.development? -
        24. -
          - -
          -
        25. - - - - - # MVP: Usa letter_opener (emails abrem no navegador) -
        26. -
          - -
          -
        27. - - - - - # Instale: gem install letter_opener ou adicione ao Gemfile -
        28. -
          - -
          -
        29. - - - - - # PRODUÇÃO: Para enviar emails reais, descomente a seção SMTP abaixo -
        30. -
          - -
          -
        31. - - - - - -
        32. -
          - -
          -
        33. - - - - - Rails.application.config.action_mailer.delivery_method = :letter_opener -
        34. -
          - -
          -
        35. - - - - - Rails.application.config.action_mailer.perform_deliveries = true -
        36. -
          - -
          -
        37. - - - - - -
        38. -
          - -
          -
        39. - - - - - # === SMTP (Para Produção) === -
        40. -
          - -
          -
        41. - - - - - # Descomente as linhas abaixo e configure variáveis de ambiente (.env) -
        42. -
          - -
          -
        43. - - - - - # para enviar emails reais via SMTP (Gmail, Sendgrid, etc.) -
        44. -
          - -
          -
        45. - - - - - # -
        46. -
          - -
          -
        47. - - - - - # Rails.application.config.action_mailer.delivery_method = :smtp -
        48. -
          - -
          -
        49. - - - - - # Rails.application.config.action_mailer.raise_delivery_errors = true -
        50. -
          - -
          -
        51. - - - - - # -
        52. -
          - -
          -
        53. - - - - - # Rails.application.config.action_mailer.smtp_settings = { -
        54. -
          - -
          -
        55. - - - - - # address: ENV.fetch('SMTP_ADDRESS', 'smtp.gmail.com'), -
        56. -
          - -
          -
        57. - - - - - # port: ENV.fetch('SMTP_PORT', '587').to_i, -
        58. -
          - -
          -
        59. - - - - - # domain: ENV.fetch('SMTP_DOMAIN', 'localhost'), -
        60. -
          - -
          -
        61. - - - - - # user_name: ENV['SMTP_USER'], -
        62. -
          - -
          -
        63. - - - - - # password: ENV['SMTP_PASSWORD'], -
        64. -
          - -
          -
        65. - - - - - # authentication: 'plain', -
        66. -
          - -
          -
        67. - - - - - # enable_starttls_auto: true -
        68. -
          - -
          -
        69. - - - - - # } -
        70. -
          - -
          -
        71. - - - - - -
        72. -
          - -
          -
        73. - - - - - Rails.application.config.action_mailer.default_url_options = { -
        74. -
          - -
          -
        75. - - - - - host: ENV.fetch('APP_HOST', 'localhost'), -
        76. -
          - -
          -
        77. - - - - - port: ENV.fetch('APP_PORT', '3000').to_i -
        78. -
          - -
          -
        79. - - - - - } -
        80. -
          - -
          -
        81. - - - - - -
        82. -
          - -
          -
        83. - - - - - else -
        84. -
          - -
          -
        85. - - - - - # Produção: SMTP obrigatório -
        86. -
          - -
          -
        87. - - - - - Rails.application.config.action_mailer.delivery_method = :smtp -
        88. -
          - -
          -
        89. - - - - - Rails.application.config.action_mailer.perform_deliveries = true -
        90. -
          - -
          -
        91. - - - - - Rails.application.config.action_mailer.raise_delivery_errors = false -
        92. -
          - -
          -
        93. - - - - - -
        94. -
          - -
          -
        95. - - - - - Rails.application.config.action_mailer.smtp_settings = { -
        96. -
          - -
          -
        97. - - - - - address: ENV.fetch('SMTP_ADDRESS'), -
        98. -
          - -
          -
        99. - - - - - port: ENV.fetch('SMTP_PORT', '587').to_i, -
        100. -
          - -
          -
        101. - - - - - domain: ENV.fetch('SMTP_DOMAIN'), -
        102. -
          - -
          -
        103. - - - - - user_name: ENV.fetch('SMTP_USER'), -
        104. -
          - -
          -
        105. - - - - - password: ENV.fetch('SMTP_PASSWORD'), -
        106. -
          - -
          -
        107. - - - - - authentication: 'plain', -
        108. -
          - -
          -
        109. - - - - - enable_starttls_auto: true, -
        110. -
          - -
          -
        111. - - - - - open_timeout: 10, -
        112. -
          - -
          -
        113. - - - - - read_timeout: 10 -
        114. -
          - -
          -
        115. - - - - - } -
        116. -
          - -
          -
        117. - - - - - -
        118. -
          - -
          -
        119. - - - - - Rails.application.config.action_mailer.default_url_options = { -
        120. -
          - -
          -
        121. - - - - - host: ENV.fetch('APP_HOST'), -
        122. -
          - -
          -
        123. - - - - - protocol: 'https' -
        124. -
          - -
          -
        125. - - - - - } -
        126. -
          - -
          -
        127. - - - - - end -
        128. -
          - -
          -
        129. - - - - - -
        130. -
          - -
        -
        -
        - - -
        -
        -

        config/routes.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 23 relevant lines. - 23 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - Rails.application.routes.draw do -
        2. -
          - -
          -
        3. - - - - - # --- ROTAS DE AVALIACOES --- -
        4. -
          - -
          -
        5. - - 1 - - - - - resources :avaliacoes, only: [:index, :create] do -
        6. -
          - -
          -
        7. - - 1 - - - - - collection do -
        8. -
          - -
          -
        9. - - 1 - - - - - get :gestao_envios -
        10. -
          - -
          -
        11. - - - - - end -
        12. -
          - -
          -
        13. - - 1 - - - - - member do -
        14. -
          - -
          -
        15. - - 1 - - - - - get :resultados -
        16. -
          - -
          -
        17. - - - - - end -
        18. -
          - -
          -
        19. - - - - - # Rotas para alunos responderem avaliações (Feature 99) -
        20. -
          - -
          -
        21. - - 1 - - - - - resources :respostas, only: [:new, :create] -
        22. -
          - -
          -
        23. - - - - - end -
        24. -
          - -
          -
        25. - - - - - -
        26. -
          - -
          -
        27. - - - - - # --- ROTAS DE IMPORTAÇÃO SIGAA --- -
        28. -
          - -
          -
        29. - - 1 - - - - - resources :sigaa_imports, only: [:new, :create] do -
        30. -
          - -
          -
        31. - - 1 - - - - - collection do -
        32. -
          - -
          -
        33. - - 1 - - - - - post :update # For update/sync operations -
        34. -
          - -
          -
        35. - - 1 - - - - - get :success # For showing import results -
        36. -
          - -
          -
        37. - - - - - end -
        38. -
          - -
          -
        39. - - - - - end -
        40. -
          - -
          -
        41. - - - - - -
        42. -
          - -
          -
        43. - - - - - # --- ROTAS DE GERENCIAMENTO DE MODELOS --- -
        44. -
          - -
          -
        45. - - 1 - - - - - resources :modelos do -
        46. -
          - -
          -
        47. - - 1 - - - - - member do -
        48. -
          - -
          -
        49. - - 1 - - - - - post :clone -
        50. -
          - -
          -
        51. - - - - - end -
        52. -
          - -
          -
        53. - - - - - end -
        54. -
          - -
          -
        55. - - - - - -
        56. -
          - -
          -
        57. - - 1 - - - - - resource :session -
        58. -
          - -
          -
        59. - - 1 - - - - - resources :passwords, param: :token -
        60. -
          - -
          -
        61. - - 1 - - - - - get "home/index" -
        62. -
          - -
          -
        63. - - - - - # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html -
        64. -
          - -
          -
        65. - - - - - -
        66. -
          - -
          -
        67. - - - - - # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. -
        68. -
          - -
          -
        69. - - - - - # Can be used by load balancers and uptime monitors to verify that the app is live. -
        70. -
          - -
          -
        71. - - 1 - - - - - get "up" => "rails/health#show", as: :rails_health_check -
        72. -
          - -
          -
        73. - - - - - -
        74. -
          - -
          -
        75. - - - - - # Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb) -
        76. -
          - -
          -
        77. - - - - - # get "manifest" => "rails/pwa#manifest", as: :pwa_manifest -
        78. -
          - -
          -
        79. - - - - - # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker -
        80. -
          - -
          -
        81. - - - - - -
        82. -
          - -
          -
        83. - - - - - # --- ROTAS DO INTEGRANTE 4 (RESPOSTAS) --- -
        84. -
          - -
          -
        85. - - - - - # Define as rotas aninhadas para criar respostas dentro de um formulário -
        86. -
          - -
          -
        87. - - 1 - - - - - resources :formularios, only: [] do -
        88. -
          - -
          -
        89. - - 1 - - - - - resources :respostas, only: [ :index, :new, :create ] -
        90. -
          - -
          -
        91. - - - - - end -
        92. -
          - -
          -
        93. - - - - - -
        94. -
          - -
          -
        95. - - - - - # Rota solta para a listagem geral de respostas (dashboard do aluno) -
        96. -
          - -
          -
        97. - - 1 - - - - - get "respostas", to: "respostas#index" -
        98. -
          - -
          -
        99. - - - - - # ----------------------------------------- -
        100. -
          - -
          -
        101. - - - - - -
        102. -
          - -
          -
        103. - - - - - # Defines the root path route ("/") -
        104. -
          - -
          -
        105. - - 1 - - - - - root "pages#index" -
        106. -
          - -
          -
        107. - - - - - -
        108. -
          - -
          -
        109. - - 1 - - - - - get "home" => "home#index" -
        110. -
          - -
          -
        111. - - - - - end -
        112. -
          - -
        -
        -
        - - -
        -
        -

        spec/mailers/user_mailer_spec.rb

        -

        - - 95.24% - - - lines covered -

        - - - -
        - 21 relevant lines. - 20 lines covered and - 1 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'rails_helper' -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - RSpec.describe UserMailer, type: :mailer do -
        6. -
          - -
          -
        7. - - 1 - - - - - describe 'definicao_senha' do -
        8. -
          - -
          -
        9. - - - - - -
        10. -
          - -
          -
        11. - - 1 - - - - - let(:user) do -
        12. -
          - -
          -
        13. - - 2 - - - - - User.create(nome: 'Teste', email_address: 'teste@example.com', matricula: '111', password: 'password') -
        14. -
          - -
          -
        15. - - - - - end -
        16. -
          - -
          -
        17. - - 3 - - - - - let(:mail) { UserMailer.definicao_senha(user) } -
        18. -
          - -
          -
        19. - - - - - -
        20. -
          - -
          -
        21. - - - - - -
        22. -
          - -
          -
        23. - - - - - -
        24. -
          - -
          -
        25. - - 1 - - - - - it 'renders the headers' do -
        26. -
          - -
          -
        27. - - 1 - - - - - expect(mail.subject).to eq('Definição de Senha - Sistema de Gestão') -
        28. -
          - -
          -
        29. - - - - - expect(mail.to).to eq([user.email_address]) -
        30. -
          - -
          -
        31. - - - - - end -
        32. -
          - -
          -
        33. - - - - - -
        34. -
          - -
          -
        35. - - 1 - - - - - it 'renders the body' do -
        36. -
          - -
          -
        37. - - 1 - - - - - expect(mail.body.encoded).to match('Definição de Senha') -
        38. -
          - -
          -
        39. - - - - - end -
        40. -
          - -
          -
        41. - - - - - end -
        42. -
          - -
          -
        43. - - - - - -
        44. -
          - -
          -
        45. - - 1 - - - - - describe 'cadastro_email' do -
        46. -
          - -
          -
        47. - - 1 - - - - - let(:user) do -
        48. -
          - -
          -
        49. - - 2 - - - - - User.create(nome: 'Teste', email_address: 'teste@example.com', matricula: '222', password: 'password') -
        50. -
          - -
          -
        51. - - - - - end -
        52. -
          - -
          -
        53. - - 3 - - - - - let(:password) { 'secret123' } -
        54. -
          - -
          -
        55. - - 3 - - - - - let(:mail) { UserMailer.cadastro_email(user, password) } -
        56. -
          - -
          -
        57. - - - - - -
        58. -
          - -
          -
        59. - - 1 - - - - - it 'renders the headers' do -
        60. -
          - -
          -
        61. - - 1 - - - - - expect(mail.subject).to eq('Bem-vindo(a) ao CAMAAR - Sua senha de acesso') -
        62. -
          - -
          -
        63. - - 1 - - - - - expect(mail.to).to eq([user.email_address]) -
        64. -
          - -
          -
        65. - - - - - end -
        66. -
          - -
          -
        67. - - - - - -
        68. -
          - -
          -
        69. - - 1 - - - - - it 'renders the body with password' do -
        70. -
          - -
          -
        71. - - 1 - - - - - expect(mail.body.encoded).to match(password) -
        72. -
          - -
          -
        73. - - - - - end -
        74. -
          - -
          -
        75. - - - - - end -
        76. -
          - -
          -
        77. - - - - - end -
        78. -
          - -
        -
        -
        - - -
        -
        -

        spec/models/avaliacao_spec.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 12 relevant lines. - 12 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'rails_helper' -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - RSpec.describe Avaliacao, type: :model do -
        6. -
          - -
          -
        7. - - 1 - - - - - describe 'associações' do -
        8. -
          - -
          -
        9. - - 1 - - - - - it 'pertence a turma' do -
        10. -
          - -
          -
        11. - - 1 - - - - - expect(described_class.reflect_on_association(:turma).macro).to eq :belongs_to -
        12. -
          - -
          -
        13. - - - - - end -
        14. -
          - -
          -
        15. - - - - - -
        16. -
          - -
          -
        17. - - 1 - - - - - it 'pertence a modelo' do -
        18. -
          - -
          -
        19. - - 1 - - - - - expect(described_class.reflect_on_association(:modelo).macro).to eq :belongs_to -
        20. -
          - -
          -
        21. - - - - - end -
        22. -
          - -
          -
        23. - - - - - -
        24. -
          - -
          -
        25. - - 1 - - - - - it 'pertence a professor_alvo como Usuario (opcional)' do -
        26. -
          - -
          -
        27. - - 1 - - - - - assoc = described_class.reflect_on_association(:professor_alvo) -
        28. -
          - -
          -
        29. - - 1 - - - - - expect(assoc.macro).to eq :belongs_to -
        30. -
          - -
          -
        31. - - 1 - - - - - expect(assoc.class_name).to eq 'User' -
        32. -
          - -
          -
        33. - - 1 - - - - - expect(assoc.options[:optional]).to eq true -
        34. -
          - -
          -
        35. - - - - - end -
        36. -
          - -
          -
        37. - - - - - end -
        38. -
          - -
          -
        39. - - - - - end -
        40. -
          - -
        -
        -
        - - -
        -
        -

        spec/models/matricula_turma_spec.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 9 relevant lines. - 9 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'rails_helper' -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - RSpec.describe MatriculaTurma, type: :model do -
        6. -
          - -
          -
        7. - - 1 - - - - - describe 'associations' do -
        8. -
          - -
          -
        9. - - 1 - - - - - it 'belongs to user' do -
        10. -
          - -
          -
        11. - - 1 - - - - - association = described_class.reflect_on_association(:user) -
        12. -
          - -
          -
        13. - - 1 - - - - - expect(association.macro).to eq :belongs_to -
        14. -
          - -
          -
        15. - - - - - end -
        16. -
          - -
          -
        17. - - - - - -
        18. -
          - -
          -
        19. - - 1 - - - - - it 'belongs to turma' do -
        20. -
          - -
          -
        21. - - 1 - - - - - association = described_class.reflect_on_association(:turma) -
        22. -
          - -
          -
        23. - - 1 - - - - - expect(association.macro).to eq :belongs_to -
        24. -
          - -
          -
        25. - - - - - end -
        26. -
          - -
          -
        27. - - - - - end -
        28. -
          - -
          -
        29. - - - - - end -
        30. -
          - -
        -
        -
        - - -
        -
        -

        spec/models/turma_spec.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 10 relevant lines. - 10 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'rails_helper' -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - RSpec.describe Turma, type: :model do -
        6. -
          - -
          -
        7. - - 1 - - - - - describe 'associations' do -
        8. -
          - -
          -
        9. - - 1 - - - - - it 'has many matricula_turmas' do -
        10. -
          - -
          -
        11. - - 1 - - - - - association = described_class.reflect_on_association(:matricula_turmas) -
        12. -
          - -
          -
        13. - - 1 - - - - - expect(association.macro).to eq :has_many -
        14. -
          - -
          -
        15. - - - - - end -
        16. -
          - -
          -
        17. - - - - - -
        18. -
          - -
          -
        19. - - 1 - - - - - it 'has many users through matricula_turmas' do -
        20. -
          - -
          -
        21. - - 1 - - - - - association = described_class.reflect_on_association(:users) -
        22. -
          - -
          -
        23. - - 1 - - - - - expect(association.macro).to eq :has_many -
        24. -
          - -
          -
        25. - - 1 - - - - - expect(association.options[:through]).to eq :matricula_turmas -
        26. -
          - -
          -
        27. - - - - - end -
        28. -
          - -
          -
        29. - - - - - end -
        30. -
          - -
          -
        31. - - - - - end -
        32. -
          - -
        -
        -
        - - -
        -
        -

        spec/models/user_spec.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 11 relevant lines. - 11 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'rails_helper' -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - RSpec.describe User, type: :model do -
        6. -
          - -
          -
        7. - - 1 - - - - - describe 'associations' do -
        8. -
          - -
          -
        9. - - 1 - - - - - it 'has many matricula_turmas' do -
        10. -
          - -
          -
        11. - - 1 - - - - - association = described_class.reflect_on_association(:matricula_turmas) -
        12. -
          - -
          -
        13. - - 1 - - - - - expect(association.macro).to eq :has_many -
        14. -
          - -
          -
        15. - - - - - end -
        16. -
          - -
          -
        17. - - - - - -
        18. -
          - -
          -
        19. - - 1 - - - - - it 'has many turmas through matricula_turmas' do -
        20. -
          - -
          -
        21. - - 1 - - - - - association = described_class.reflect_on_association(:turmas) -
        22. -
          - -
          -
        23. - - 1 - - - - - expect(association.macro).to eq :has_many -
        24. -
          - -
          -
        25. - - 1 - - - - - expect(association.options[:through]).to eq :matricula_turmas -
        26. -
          - -
          -
        27. - - - - - end -
        28. -
          - -
          -
        29. - - - - - end -
        30. -
          - -
          -
        31. - - - - - -
        32. -
          - -
          -
        33. - - 1 - - - - - describe 'validations' do -
        34. -
          - -
          -
        35. - - - - - # Adicione validações aqui se houver -
        36. -
          - -
          -
        37. - - - - - end -
        38. -
          - -
          -
        39. - - - - - end -
        40. -
          - -
        -
        -
        - - -
        -
        -

        spec/rails_helper.rb

        -

        - - 90.91% - - - lines covered -

        - - - -
        - 11 relevant lines. - 10 lines covered and - 1 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - - - - # This file is copied to spec/ when you run 'rails generate rspec:install' -
        2. -
          - -
          -
        3. - - 1 - - - - - require 'spec_helper' -
        4. -
          - -
          -
        5. - - 1 - - - - - ENV['RAILS_ENV'] ||= 'test' -
        6. -
          - -
          -
        7. - - 1 - - - - - require_relative '../config/environment' -
        8. -
          - -
          -
        9. - - - - - # Prevent database truncation if the environment is production -
        10. -
          - -
          -
        11. - - 1 - - - - - abort("The Rails environment is running in production mode!") if Rails.env.production? -
        12. -
          - -
          -
        13. - - - - - # Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file -
        14. -
          - -
          -
        15. - - - - - # that will avoid rails generators crashing because migrations haven't been run yet -
        16. -
          - -
          -
        17. - - - - - # return unless Rails.env.test? -
        18. -
          - -
          -
        19. - - 1 - - - - - require 'rspec/rails' -
        20. -
          - -
          -
        21. - - - - - # Add additional requires below this line. Rails is not loaded until this point! -
        22. -
          - -
          -
        23. - - - - - -
        24. -
          - -
          -
        25. - - - - - # Requires supporting ruby files with custom matchers and macros, etc, in -
        26. -
          - -
          -
        27. - - - - - # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are -
        28. -
          - -
          -
        29. - - - - - # run as spec files by default. This means that files in spec/support that end -
        30. -
          - -
          -
        31. - - - - - # in _spec.rb will both be required and run as specs, causing the specs to be -
        32. -
          - -
          -
        33. - - - - - # run twice. It is recommended that you do not name files matching this glob to -
        34. -
          - -
          -
        35. - - - - - # end with _spec.rb. You can configure this pattern with the --pattern -
        36. -
          - -
          -
        37. - - - - - # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. -
        38. -
          - -
          -
        39. - - - - - # -
        40. -
          - -
          -
        41. - - - - - # The following line is provided for convenience purposes. It has the downside -
        42. -
          - -
          -
        43. - - - - - # of increasing the boot-up time by auto-requiring all files in the support -
        44. -
          - -
          -
        45. - - - - - # directory. Alternatively, in the individual `*_spec.rb` files, manually -
        46. -
          - -
          -
        47. - - - - - # require only the support files necessary. -
        48. -
          - -
          -
        49. - - - - - # -
        50. -
          - -
          -
        51. - - - - - # Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } -
        52. -
          - -
          -
        53. - - - - - -
        54. -
          - -
          -
        55. - - - - - # Ensures that the test database schema matches the current schema file. -
        56. -
          - -
          -
        57. - - - - - # If there are pending migrations it will invoke `db:test:prepare` to -
        58. -
          - -
          -
        59. - - - - - # recreate the test database by loading the schema. -
        60. -
          - -
          -
        61. - - - - - # If you are not using ActiveRecord, you can remove these lines. -
        62. -
          - -
          -
        63. - - - - - begin -
        64. -
          - -
          -
        65. - - 1 - - - - - ActiveRecord::Migration.maintain_test_schema! -
        66. -
          - -
          -
        67. - - - - - rescue ActiveRecord::PendingMigrationError => e -
        68. -
          - -
          -
        69. - - - - - abort e.to_s.strip -
        70. -
          - -
          -
        71. - - - - - end -
        72. -
          - -
          -
        73. - - 1 - - - - - RSpec.configure do |config| -
        74. -
          - -
          -
        75. - - - - - # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures -
        76. -
          - -
          -
        77. - - 1 - - - - - config.fixture_paths = [ -
        78. -
          - -
          -
        79. - - - - - Rails.root.join('spec/fixtures') -
        80. -
          - -
          -
        81. - - - - - ] -
        82. -
          - -
          -
        83. - - - - - -
        84. -
          - -
          -
        85. - - - - - # If you're not using ActiveRecord, or you'd prefer not to run each of your -
        86. -
          - -
          -
        87. - - - - - # examples within a transaction, remove the following line or assign false -
        88. -
          - -
          -
        89. - - - - - # instead of true. -
        90. -
          - -
          -
        91. - - 1 - - - - - config.use_transactional_fixtures = true -
        92. -
          - -
          -
        93. - - - - - -
        94. -
          - -
          -
        95. - - - - - # You can uncomment this line to turn off ActiveRecord support entirely. -
        96. -
          - -
          -
        97. - - - - - # config.use_active_record = false -
        98. -
          - -
          -
        99. - - - - - -
        100. -
          - -
          -
        101. - - - - - # RSpec Rails uses metadata to mix in different behaviours to your tests, -
        102. -
          - -
          -
        103. - - - - - # for example enabling you to call `get` and `post` in request specs. e.g.: -
        104. -
          - -
          -
        105. - - - - - # -
        106. -
          - -
          -
        107. - - - - - # RSpec.describe UsersController, type: :request do -
        108. -
          - -
          -
        109. - - - - - # # ... -
        110. -
          - -
          -
        111. - - - - - # end -
        112. -
          - -
          -
        113. - - - - - # -
        114. -
          - -
          -
        115. - - - - - # The different available types are documented in the features, such as in -
        116. -
          - -
          -
        117. - - - - - # https://rspec.info/features/8-0/rspec-rails -
        118. -
          - -
          -
        119. - - - - - # -
        120. -
          - -
          -
        121. - - - - - # You can also this infer these behaviours automatically by location, e.g. -
        122. -
          - -
          -
        123. - - - - - # /spec/models would pull in the same behaviour as `type: :model` but this -
        124. -
          - -
          -
        125. - - - - - # behaviour is considered legacy and will be removed in a future version. -
        126. -
          - -
          -
        127. - - - - - # -
        128. -
          - -
          -
        129. - - - - - # To enable this behaviour uncomment the line below. -
        130. -
          - -
          -
        131. - - - - - # config.infer_spec_type_from_file_location! -
        132. -
          - -
          -
        133. - - - - - -
        134. -
          - -
          -
        135. - - - - - # Filter lines from Rails gems in backtraces. -
        136. -
          - -
          -
        137. - - 1 - - - - - config.filter_rails_from_backtrace! -
        138. -
          - -
          -
        139. - - - - - # arbitrary gems may also be filtered via: -
        140. -
          - -
          -
        141. - - - - - # config.filter_gems_from_backtrace("gem name") -
        142. -
          - -
          -
        143. - - - - - end -
        144. -
          - -
        -
        -
        - - -
        -
        -

        spec/requests/avaliacoes_spec.rb

        -

        - - 50.0% - - - lines covered -

        - - - -
        - 30 relevant lines. - 15 lines covered and - 15 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'rails_helper' -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - RSpec.describe "Avaliações", type: :request do -
        6. -
          - -
          -
        7. - - 1 - - - - - describe "GET /gestao_envios" do -
        8. -
          - -
          -
        9. - - 1 - - - - - it "retorna sucesso HTTP" do -
        10. -
          - -
          -
        11. - - 1 - - - - - get gestao_envios_avaliacoes_path -
        12. -
          - -
          -
        13. - - 1 - - - - - expect(response).to have_http_status(:success) -
        14. -
          - -
          -
        15. - - - - - end -
        16. -
          - -
          -
        17. - - - - - end -
        18. -
          - -
          -
        19. - - - - - -
        20. -
          - -
          -
        21. - - 1 - - - - - describe "POST /create" do -
        22. -
          - -
          -
        23. - - 4 - - - - - let!(:turma) { Turma.create!(codigo: "CIC001", nome: "Turma de Teste", semestre: "2024.1") } -
        24. -
          - -
          -
        25. - - 4 - - - - - let!(:template) { Modelo.create!(titulo: "Template Padrão", ativo: true) } -
        26. -
          - -
          -
        27. - - - - - -
        28. -
          - -
          -
        29. - - 1 - - - - - context "com entradas válidas" do -
        30. -
          - -
          -
        31. - - 1 - - - - - it "cria uma nova Avaliação vinculada ao template padrão" do -
        32. -
          - -
          -
        33. - - - - - expect { -
        34. -
          - -
          -
        35. - - - - - post avaliacoes_path, params: { turma_id: turma.id } -
        36. -
          - -
          -
        37. - - - - - }.to change(Avaliacao, :count).by(1) -
        38. -
          - -
          -
        39. - - - - - -
        40. -
          - -
          -
        41. - - - - - avaliacao = Avaliacao.last -
        42. -
          - -
          -
        43. - - - - - expect(avaliacao.turma).to eq(turma) -
        44. -
          - -
          -
        45. - - - - - expect(avaliacao.modelo).to eq(template) -
        46. -
          - -
          -
        47. - - - - - expect(response).to redirect_to(gestao_envios_avaliacoes_path) -
        48. -
          - -
          -
        49. - - - - - expect(flash[:notice]).to be_present -
        50. -
          - -
          -
        51. - - - - - end -
        52. -
          - -
          -
        53. - - - - - -
        54. -
          - -
          -
        55. - - 1 - - - - - it "aceita uma data_fim personalizada" do -
        56. -
          - -
          -
        57. - - - - - data_personalizada = 2.weeks.from_now.to_date -
        58. -
          - -
          -
        59. - - - - - post avaliacoes_path, params: { turma_id: turma.id, data_fim: data_personalizada } -
        60. -
          - -
          -
        61. - - - - - -
        62. -
          - -
          -
        63. - - - - - avaliacao = Avaliacao.last -
        64. -
          - -
          -
        65. - - - - - expect(avaliacao.data_fim.to_date).to eq(data_personalizada) -
        66. -
          - -
          -
        67. - - - - - end -
        68. -
          - -
          -
        69. - - - - - end -
        70. -
          - -
          -
        71. - - - - - -
        72. -
          - -
          -
        73. - - 1 - - - - - context "quando o template padrão está ausente" do -
        74. -
          - -
          -
        75. - - 1 - - - - - before { template.update!(titulo: "Outro") } -
        76. -
          - -
          -
        77. - - - - - -
        78. -
          - -
          -
        79. - - 1 - - - - - it "não cria avaliação e redireciona com alerta" do -
        80. -
          - -
          -
        81. - - - - - expect { -
        82. -
          - -
          -
        83. - - - - - post avaliacoes_path, params: { turma_id: turma.id } -
        84. -
          - -
          -
        85. - - - - - }.not_to change(Avaliacao, :count) -
        86. -
          - -
          -
        87. - - - - - -
        88. -
          - -
          -
        89. - - - - - expect(response).to redirect_to(gestao_envios_avaliacoes_path) -
        90. -
          - -
          -
        91. - - - - - expect(flash[:alert]).to include("Template Padrão não encontrado") -
        92. -
          - -
          -
        93. - - - - - end -
        94. -
          - -
          -
        95. - - - - - end -
        96. -
          - -
          -
        97. - - - - - end -
        98. -
          - -
          -
        99. - - - - - end -
        100. -
          - -
        -
        -
        - - -
        -
        -

        spec/services/csv_formatter_service_spec.rb

        -

        - - 100.0% - - - lines covered -

        - - - -
        - 20 relevant lines. - 20 lines covered and - 0 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'rails_helper' -
        2. -
          - -
          -
        3. - - - - - -
        4. -
          - -
          -
        5. - - 1 - - - - - RSpec.describe CsvFormatterService do -
        6. -
          - -
          -
        7. - - 1 - - - - - describe '#generate' do -
        8. -
          - -
          -
        9. - - 2 - - - - - let(:modelo) { double('Modelo', perguntas: [ -
        10. -
          - -
          -
        11. - - - - - double('Pergunta', enunciado: 'Q1'), -
        12. -
          - -
          -
        13. - - - - - double('Pergunta', enunciado: 'Q2') -
        14. -
          - -
          -
        15. - - - - - ])} -
        16. -
          - -
          -
        17. - - - - - -
        18. -
          - -
          -
        19. - - 2 - - - - - let(:avaliacao) { double('Avaliacao', id: 1, modelo: modelo) } -
        20. -
          - -
          -
        21. - - - - - -
        22. -
          - -
          -
        23. - - 2 - - - - - let(:aluno1) { double('User', matricula: '123', nome: 'Alice') } -
        24. -
          - -
          -
        25. - - 2 - - - - - let(:aluno2) { double('User', matricula: '456', nome: 'Bob') } -
        26. -
          - -
          -
        27. - - - - - -
        28. -
          - -
          -
        29. - - - - - # Respostas não tem mais aluno direto, mas através de submissão -
        30. -
          - -
          -
        31. - - 2 - - - - - let(:resposta_a1_q1) { double('Resposta', conteudo: 'Ans 1A') } -
        32. -
          - -
          -
        33. - - 2 - - - - - let(:resposta_a1_q2) { double('Resposta', conteudo: 'Ans 1B') } -
        34. -
          - -
          -
        35. - - 2 - - - - - let(:resposta_a2_q1) { double('Resposta', conteudo: 'Ans 2A') } -
        36. -
          - -
          -
        37. - - - - - -
        38. -
          - -
          -
        39. - - - - - # Submissoes ligando aluno e respostas -
        40. -
          - -
          -
        41. - - 2 - - - - - let(:submissao1) { double('Submissao', aluno: aluno1, respostas: [resposta_a1_q1, resposta_a1_q2]) } -
        42. -
          - -
          -
        43. - - 2 - - - - - let(:submissao2) { double('Submissao', aluno: aluno2, respostas: [resposta_a2_q1]) } -
        44. -
          - -
          -
        45. - - - - - -
        46. -
          - -
          -
        47. - - 1 - - - - - before do -
        48. -
          - -
          -
        49. - - - - - # Mock da cadeia: avaliacao.submissoes.includes.each -
        50. -
          - -
          -
        51. - - - - - # Simulando o comportamento do loop no service -
        52. -
          - -
          -
        53. - - 1 - - - - - allow(avaliacao).to receive_message_chain(:submissoes, :includes).and_return([submissao1, submissao2]) -
        54. -
          - -
          -
        55. - - - - - end -
        56. -
          - -
          -
        57. - - - - - -
        58. -
          - -
          -
        59. - - 1 - - - - - it 'gera uma string CSV válida com cabeçalhos e linhas' do -
        60. -
          - -
          -
        61. - - 1 - - - - - csv_string = described_class.new(avaliacao).generate -
        62. -
          - -
          -
        63. - - 1 - - - - - rows = csv_string.split("\n") -
        64. -
          - -
          -
        65. - - - - - -
        66. -
          - -
          -
        67. - - - - - # Cabeçalhos: Matrícula, Nome, Questão 1, Questão 2 -
        68. -
          - -
          -
        69. - - 1 - - - - - expect(rows[0]).to include("Matrícula,Nome,Questão 1,Questão 2") -
        70. -
          - -
          -
        71. - - - - - -
        72. -
          - -
          -
        73. - - - - - # Linha 1: Respostas da Alice -
        74. -
          - -
          -
        75. - - 1 - - - - - expect(rows[1]).to include("123,Alice,Ans 1A,Ans 1B") -
        76. -
          - -
          -
        77. - - - - - -
        78. -
          - -
          -
        79. - - - - - # Linha 2: Respostas do Bob -
        80. -
          - -
          -
        81. - - 1 - - - - - expect(rows[2]).to include("456,Bob,Ans 2A") -
        82. -
          - -
          -
        83. - - - - - end -
        84. -
          - -
          -
        85. - - - - - end -
        86. -
          - -
          -
        87. - - - - - end -
        88. -
          - -
        -
        -
        - - -
        -
        -

        spec/services/sigaa_import_service_spec.rb

        -

        - - 93.62% - - - lines covered -

        - - - -
        - 47 relevant lines. - 44 lines covered and - 3 lines missed. -
        - - - -
        - -
        -    
          - -
          -
        1. - - 1 - - - - - require 'rails_helper' -
        2. -
          - -
          -
        3. - - 1 - - - - - require 'rspec/support/differ' -
        4. -
          - -
          -
        5. - - 1 - - - - - require 'rspec/support/hunk_generator' -
        6. -
          - -
          -
        7. - - 1 - - - - - require 'diff/lcs' -
        8. -
          - -
          -
        9. - - - - - -
        10. -
          - -
          -
        11. - - 1 - - - - - RSpec.describe SigaaImportService, type: :service do -
        12. -
          - -
          -
        13. - - 4 - - - - - let(:json_path) { Rails.root.join('spec/fixtures/turmas.json') } -
        14. -
          - -
          -
        15. - - 4 - - - - - let(:csv_path) { Rails.root.join('spec/fixtures/turmas.csv') } -
        16. -
          - -
          -
        17. - - 4 - - - - - let(:invalid_path) { Rails.root.join('spec/fixtures/invalid.txt') } -
        18. -
          - -
          -
        19. - - - - - -
        20. -
          - -
          -
        21. - - 1 - - - - - before do -
        22. -
          - -
          -
        23. - - - - - # Garante que fixtures existem ou são mockados -
        24. -
          - -
          -
        25. - - 3 - - - - - allow(File).to receive(:exist?).and_return(true) -
        26. -
          - -
          -
        27. - - 3 - - - - - allow(File).to receive(:read).with(json_path).and_return([ -
        28. -
          - -
          -
        29. - - - - - { -
        30. -
          - -
          -
        31. - - - - - 'code' => 'T01', -
        32. -
          - -
          -
        33. - - - - - 'semester' => '2024.1', -
        34. -
          - -
          -
        35. - - - - - 'dicente' => [ -
        36. -
          - -
          -
        37. - - - - - { -
        38. -
          - -
          -
        39. - - - - - 'matricula' => '123456', -
        40. -
          - -
          -
        41. - - - - - 'nome' => 'João Silva', -
        42. -
          - -
          -
        43. - - - - - 'email' => 'joao@example.com' -
        44. -
          - -
          -
        45. - - - - - } -
        46. -
          - -
          -
        47. - - - - - ], -
        48. -
          - -
          -
        49. - - - - - 'docente' => { -
        50. -
          - -
          -
        51. - - - - - 'usuario' => '654321', -
        52. -
          - -
          -
        53. - - - - - 'nome' => 'Maria Professora', -
        54. -
          - -
          -
        55. - - - - - 'email' => 'maria@prof.com' -
        56. -
          - -
          -
        57. - - - - - } -
        58. -
          - -
          -
        59. - - - - - } -
        60. -
          - -
          -
        61. - - - - - ].to_json) -
        62. -
          - -
          -
        63. - - - - - -
        64. -
          - -
          -
        65. - - 3 - - - - - allow(CSV).to receive(:foreach).with(csv_path, headers: true, col_sep: ',').and_yield( -
        66. -
          - -
          -
        67. - - - - - CSV::Row.new(%w[codigo_turma nome_turma semestre nome_usuario email matricula papel], -
        68. -
          - -
          -
        69. - - - - - ['T02', 'Banco de Dados', '2024.1', 'Maria Souza', 'maria@example.com', '654321', 'professor']) -
        70. -
          - -
          -
        71. - - - - - ) -
        72. -
          - -
          -
        73. - - - - - -
        74. -
          - -
          -
        75. - - 3 - - - - - allow(File).to receive(:extname).and_call_original -
        76. -
          - -
          -
        77. - - 3 - - - - - allow(File).to receive(:extname).with(json_path).and_return('.json') -
        78. -
          - -
          -
        79. - - 3 - - - - - allow(File).to receive(:extname).with(csv_path).and_return('.csv') -
        80. -
          - -
          -
        81. - - 3 - - - - - allow(File).to receive(:extname).with(invalid_path).and_return('.txt') -
        82. -
          - -
          -
        83. - - - - - end -
        84. -
          - -
          -
        85. - - - - - -
        86. -
          - -
          -
        87. - - 1 - - - - - class DummyMessage -
        88. -
          - -
          -
        89. - - 1 - - - - - def deliver_now -
        90. -
          - -
          -
        91. - - - - - true -
        92. -
          - -
          -
        93. - - - - - end -
        94. -
          - -
          -
        95. - - - - - end -
        96. -
          - -
          -
        97. - - - - - -
        98. -
          - -
          -
        99. - - 1 - - - - - class DummyMailer -
        100. -
          - -
          -
        101. - - 1 - - - - - def self.cadastro_email(user, password) -
        102. -
          - -
          -
        103. - - - - - DummyMessage.new -
        104. -
          - -
          -
        105. - - - - - end -
        106. -
          - -
          -
        107. - - - - - end -
        108. -
          - -
          -
        109. - - - - - -
        110. -
          - -
          -
        111. - - 1 - - - - - describe '#process' do -
        112. -
          - -
          -
        113. - - 1 - - - - - context 'with JSON file' do -
        114. -
          - -
          -
        115. - - 1 - - - - - it 'creates turmas and users' do -
        116. -
          - -
          -
        117. - - 1 - - - - - Turma.delete_all -
        118. -
          - -
          -
        119. - - 1 - - - - - User.delete_all -
        120. -
          - -
          -
        121. - - - - - -
        122. -
          - -
          -
        123. - - 1 - - - - - service = SigaaImportService.new(json_path) -
        124. -
          - -
          -
        125. - - 1 - - - - - result = service.process -
        126. -
          - -
          -
        127. - - 1 - - - - - puts "JSON Import Errors: #{result[:errors]}" if result[:errors].any? -
        128. -
          - -
          -
        129. - - - - - -
        130. -
          - -
          -
        131. - - 1 - - - - - expect(Turma.count).to eq(1) -
        132. -
          - -
          -
        133. - - 1 - - - - - expect(User.count).to eq(2) -
        134. -
          - -
          -
        135. - - - - - end -
        136. -
          - -
          -
        137. - - - - - end -
        138. -
          - -
          -
        139. - - - - - -
        140. -
          - -
          -
        141. - - 1 - - - - - context 'with CSV file' do -
        142. -
          - -
          -
        143. - - 1 - - - - - it 'creates turmas and users' do -
        144. -
          - -
          -
        145. - - 1 - - - - - Turma.delete_all -
        146. -
          - -
          -
        147. - - 1 - - - - - User.delete_all -
        148. -
          - -
          -
        149. - - - - - -
        150. -
          - -
          -
        151. - - 1 - - - - - service = SigaaImportService.new(csv_path) -
        152. -
          - -
          -
        153. - - 1 - - - - - result = service.process -
        154. -
          - -
          -
        155. - - 1 - - - - - puts "CSV Import Errors: #{result[:errors]}" if result[:errors].any? -
        156. -
          - -
          -
        157. - - - - - -
        158. -
          - -
          -
        159. - - 1 - - - - - expect(Turma.count).to eq(1) -
        160. -
          - -
          -
        161. - - 1 - - - - - expect(User.count).to eq(1) -
        162. -
          - -
          -
        163. - - - - - end -
        164. -
          - -
          -
        165. - - - - - end -
        166. -
          - -
          -
        167. - - - - - -
        168. -
          - -
          -
        169. - - 1 - - - - - context 'with unsupported file format' do -
        170. -
          - -
          -
        171. - - 1 - - - - - it 'returns error' do -
        172. -
          - -
          -
        173. - - 1 - - - - - service = SigaaImportService.new(invalid_path) -
        174. -
          - -
          -
        175. - - 1 - - - - - result = service.process -
        176. -
          - -
          -
        177. - - - - - # Manual check to avoid RSpec HunkGenerator error -
        178. -
          - -
          -
        179. - - 1 - - - - - unless result[:errors].join(', ').include?('Formato de arquivo não suportado') -
        180. -
          - -
          -
        181. - - - - - raise "Expected error 'Formato de arquivo não suportado' not found in: #{result[:errors]}" -
        182. -
          - -
          -
        183. - - - - - end -
        184. -
          - -
          -
        185. - - - - - end -
        186. -
          - -
          -
        187. - - - - - end -
        188. -
          - -
          -
        189. - - - - - end -
        190. -
          - -
          -
        191. - - - - - end -
        192. -
          - -
        -
        -
        - - -
        -
        - -