+
Configuracoes
+
Gerencie seus dados e preferencias
+
+
+
+
+
+
+
+
+
+
Seus Dados (LGPD)
+
+ Conforme a Lei Geral de Protecao de Dados (Lei n 13.709/2018),
+ voce tem direito de acessar, exportar e excluir seus dados pessoais.
+
+
+
+
+
Exportar meus dados
+
Baixe um arquivo JSON com todas as suas tarefas, eventos e disciplinas.
+
+
Exportar JSON
+
+
+
+
Politica de Privacidade
+
Veja como seus dados sao tratados e quais sao seus direitos.
+
+
Ver politica
+
+
+
+
+
+
Zona de Perigo
+
+ Ao excluir sua conta, exercendo o direito previsto no Art. 18, VI da LGPD,
+ todos os seus dados pessoais (tarefas, eventos, disciplinas e notificacoes)
+ serao removidos permanentemente e imediatamente dos nossos servidores.
+ Esta acao e irreversivel.
+
+
Excluir Minha Conta
+
+
+
+
+
+
diff --git a/src/main/resources/static/imagens/engrenagem.png b/src/main/resources/static/imagens/engrenagem.png
new file mode 100644
index 0000000..108ff43
Binary files /dev/null and b/src/main/resources/static/imagens/engrenagem.png differ
diff --git a/src/main/resources/static/imagens/icone.png b/src/main/resources/static/imagens/icone.png
new file mode 100644
index 0000000..4eb80c9
Binary files /dev/null and b/src/main/resources/static/imagens/icone.png differ
diff --git a/src/main/resources/static/imagens/moon-svgrepo-com.svg b/src/main/resources/static/imagens/moon-svgrepo-com.svg
new file mode 100644
index 0000000..1479a6d
--- /dev/null
+++ b/src/main/resources/static/imagens/moon-svgrepo-com.svg
@@ -0,0 +1,4 @@
+
+
+
← Voltar
+
Politica de Privacidade
+
Versao 1.0 - Vigente a partir de maio de 2026
+
+
1. Quem somos
+
FocusAgenda e um sistema de agenda estudantil desenvolvido como Trabalho de Conclusao de Curso (TCC) no curso Tecnico em Desenvolvimento de Sistemas - ETEC Pedro D'Arcadia Neto.
+
+
2. Quais dados coletamos e por que (Art. 9, LGPD)
+
+ Nome completo: identificacao na plataforma
+ Endereco de email: autenticacao e comunicacao
+ Senha: armazenada com criptografia BCrypt (nunca em texto simples)
+ Curso e periodo: personalizao da experiencia
+ Tarefas, eventos e disciplinas: funcionalidade principal do servico
+
+
+
3. Base legal para o tratamento (Art. 7, I, LGPD)
+
Consentimento livre, informado e inequivoco do titular. Ao criar uma conta no FocusAgenda, voce aceita explicitamente o tratamento dos seus dados pessoais conforme descrito nesta politica.
+
+
4. Como protegemos seus dados (Art. 46, LGPD)
+
+ Senhas criptografadas com BCrypt
+ Comunicacao protegida por HTTPS
+ Autenticacao via JWT com expiracao de 24 horas
+ Acesso restrito aos dados do proprio usuario autenticado
+
+
+
5. Seus direitos como titular (Art. 18, LGPD)
+
+ Acesso: visualize seus dados em Configuracoes
+ Portabilidade: exporte todos os seus dados em JSON via Configuracoes
+ Correcao: edite seus dados em Configuracoes > Dados Pessoais
+ Eliminacao: exclua sua conta em Configuracoes > Zona de Perigo (todos os dados sao removidos permanentemente e imediatamente)
+
+
+
6. Retencao de dados
+
Os dados sao mantidos enquanto a conta estiver ativa. Apos a exclusao da conta, todos os dados sao removidos imediatamente e de forma irreversivel.
+
+
7. Contato
+
Para duvidas sobre privacidade, entre em contato com a equipe do FocusAgenda.
+
+
8. Versao e vigencia
+
Versao 1.0 - vigente a partir de maio de 2026.
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css
new file mode 100644
index 0000000..216ef65
--- /dev/null
+++ b/src/main/resources/static/style.css
@@ -0,0 +1,124 @@
+* {
+ padding: 0;
+ margin: 0;
+}
+
+p,
+h1,
+h2,
+h3 {
+ color: black;
+ margin: 0;
+ font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif
+}
+
+label{
+display: block;
+}
+
+body {
+ height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+@media (max-width: 768px) {
+body {
+ padding-left: 20px;
+ padding-right: 20px;
+ justify-content: center;
+}
+}
+
+@media (max-width: 480px) {
+#log {
+ width: 100% !important;
+ max-width: 320px;
+}
+}
+#topo {
+ width: 100%;
+ height: 50px;
+ background-color: #111;
+ position: fixed;
+ top: 0;
+ left: 0;
+ background: linear-gradient(to right, #C0392B 47%, #7A4951 73%, #114455 87%);
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+#textotop {
+ padding-left: 20px;
+ font-size: 38px;
+ margin: 0;
+ font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
+}
+
+#log {
+ width: 350px;
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ color: white;
+ margin-top: 70px;
+}
+#campo {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ align-self: center;
+}
+#menslog{
+font-size: 20px;
+}
+
+#emailid, #senhaid {
+height: 50px;
+width: 100%;
+padding: 10px;
+font-size: 16px;
+border: 1px solid #ccc;
+border-radius: 4px;
+box-sizing: border-box;
+}
+
+label {
+display: block;
+margin-bottom: 5px;
+font-weight: bold;
+color: white;
+}
+
+form {
+display: flex;
+flex-direction: column;
+gap: 15px;
+width: 100%;
+}
+
+#logbtn {
+align-self: center;
+width: 50%;
+padding: 12px;
+font-size: 18px;
+font-weight: bold;
+background-color: #C0392B;
+color: white;
+border: none;
+border-radius: 4px;
+cursor: pointer;
+transition: background-color 0.3s;
+}
+
+#logbtn:hover {
+background-color: #A03224;
+}
+.mens {
+align-self: center;
+}
+#linkcada{
+ color: #111;
+}
\ No newline at end of file
diff --git a/src/main/resources/static/theme.js b/src/main/resources/static/theme.js
new file mode 100644
index 0000000..9c3ea89
--- /dev/null
+++ b/src/main/resources/static/theme.js
@@ -0,0 +1,36 @@
+(function() {
+ var saved = localStorage.getItem('fa_theme');
+ var theme = saved || 'light';
+ document.documentElement.setAttribute('data-theme', theme);
+})();
+
+function toggleTheme() {
+ var current = document.documentElement.getAttribute('data-theme');
+ var next = current === 'dark' ? 'light' : 'dark';
+ document.documentElement.setAttribute('data-theme', next);
+ localStorage.setItem('fa_theme', next);
+ updateThemeButtons();
+}
+
+function updateThemeButtons() {
+ var theme = document.documentElement.getAttribute('data-theme');
+ var moonSrc = 'imagens/moon-svgrepo-com.svg';
+ var sunSrc = 'imagens/sun-svgrepo-com.svg';
+
+ var icons = document.querySelectorAll('.theme-icon');
+ for (var i = 0; i < icons.length; i++) {
+ icons[i].src = theme === 'dark' ? sunSrc : moonSrc;
+ icons[i].alt = theme === 'dark' ? 'Tema Claro' : 'Tema Escuro';
+ }
+
+ var btns = document.querySelectorAll('.theme-toggle-btn');
+ for (var j = 0; j < btns.length; j++) {
+ btns[j].title = theme === 'dark' ? 'Tema Claro' : 'Tema Escuro';
+ }
+}
+
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', updateThemeButtons);
+} else {
+ updateThemeButtons();
+}
diff --git a/src/main/resources/static/utils.js b/src/main/resources/static/utils.js
new file mode 100644
index 0000000..466999d
--- /dev/null
+++ b/src/main/resources/static/utils.js
@@ -0,0 +1,83 @@
+async function apiFetch(url, options = {}) {
+ const token = localStorage.getItem('fa_token');
+ const headers = { 'Content-Type': 'application/json', ...(options.headers || {}) };
+ if (token) headers['Authorization'] = 'Bearer ' + token;
+
+ const res = await fetch(url, { ...options, headers });
+
+ if (res.status === 401) {
+ localStorage.clear();
+ window.location.href = '/login.html?sessao=expirada';
+ return;
+ }
+
+ const data = await res.json();
+ if (!res.ok) throw new Error(data.message || 'Erro desconhecido');
+ return data;
+}
+
+function showToast(mensagem, tipo = 'info') {
+ const existing = document.querySelector('.fa-toast');
+ if (existing) existing.remove();
+
+ const styleId = 'fa-toast-style';
+ if (!document.getElementById(styleId)) {
+ const style = document.createElement('style');
+ style.id = styleId;
+ style.textContent = `
+ .fa-toast {
+ position: fixed; bottom: 24px; right: 24px; z-index: 99999;
+ padding: 12px 18px; border-radius: 8px; font-size: 14px;
+ font-family: 'Poppins', sans-serif; max-width: 340px;
+ animation: faSlideIn 0.3s ease forwards;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ }
+ .fa-toast.fa-toast-sai { animation: faSlideOut 0.3s ease forwards; }
+ .fa-toast-sucesso { background: #114455; color: #fff; }
+ .fa-toast-erro { background: #c0392b; color: #fff; }
+ .fa-toast-info { background: #f5f5f5; color: #1f2937; border: 1px solid #c0392b; }
+ @keyframes faSlideIn { from { transform: translateY(30px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
+ @keyframes faSlideOut { from { transform: translateY(0); opacity: 1; } to { transform: translateY(30px); opacity: 0; } }
+ `;
+ document.head.appendChild(style);
+ }
+
+ const toast = document.createElement('div');
+ toast.className = 'fa-toast fa-toast-' + tipo;
+ toast.textContent = mensagem;
+ document.body.appendChild(toast);
+
+ setTimeout(() => {
+ toast.classList.add('fa-toast-sai');
+ toast.addEventListener('animationend', () => toast.remove());
+ }, 3000);
+}
+
+function setButtonLoading(btn, loading, textoOriginal) {
+ if (loading) {
+ btn.disabled = true;
+ btn.dataset.textoOriginal = textoOriginal || btn.textContent;
+ btn.textContent = 'Salvando...';
+ } else {
+ btn.disabled = false;
+ if (btn.dataset.textoOriginal) {
+ btn.textContent = btn.dataset.textoOriginal;
+ }
+ }
+}
+
+(function() {
+ window.addEventListener('offline', () => {
+ let banner = document.getElementById('fa-offline-banner');
+ if (!banner) {
+ banner = document.createElement('div');
+ banner.id = 'fa-offline-banner';
+ banner.textContent = 'Sem conexao — algumas funcoes podem nao funcionar';
+ banner.style.cssText = 'position:fixed;top:0;left:0;width:100%;background:#c0392b;color:#fff;text-align:center;padding:10px;font-family:Poppins,sans-serif;font-size:14px;z-index:9999;';
+ document.body.prepend(banner);
+ }
+ });
+ window.addEventListener('online', () => {
+ document.getElementById('fa-offline-banner')?.remove();
+ });
+})();
diff --git a/src/test/java/com/agendaestudantil/servico/EstudanteServicoTest.java b/src/test/java/com/agendaestudantil/servico/EstudanteServicoTest.java
new file mode 100644
index 0000000..1fa471c
--- /dev/null
+++ b/src/test/java/com/agendaestudantil/servico/EstudanteServicoTest.java
@@ -0,0 +1,107 @@
+package com.agendaestudantil.servico;
+
+import com.agendaestudantil.dto.RequisicaoCadastroDTO;
+import com.agendaestudantil.dto.RespostaEstudanteDTO;
+import com.agendaestudantil.dto.RequisicaoLoginDTO;
+import com.agendaestudantil.dto.RespostaLoginDTO;
+import com.agendaestudantil.entidade.Estudante;
+import com.agendaestudantil.excecao.ExcecaoNegocio;
+import com.agendaestudantil.repositorio.EstudanteRepositorio;
+import com.agendaestudantil.utilitario.UtilJwt;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class EstudanteServicoTest {
+
+ @Mock
+ private EstudanteRepositorio estudanteRepositorio;
+
+ @Mock
+ private PasswordEncoder passwordEncoder;
+
+ @Mock
+ private UtilJwt utilJwt;
+
+ @InjectMocks
+ private EstudanteServico estudanteServico;
+
+ private RequisicaoCadastroDTO requisicaoCadastro;
+ private RequisicaoLoginDTO requisicaoLogin;
+ private Estudante estudante;
+
+ @BeforeEach
+ void setUp() {
+ requisicaoCadastro = new RequisicaoCadastroDTO("Teste", "teste@teste.com", "123456", "Curso", 1, true);
+ requisicaoLogin = new RequisicaoLoginDTO("teste@teste.com", "123456");
+
+ estudante = new Estudante();
+ estudante.setId("1");
+ estudante.setNome("Teste");
+ estudante.setEmail("teste@teste.com");
+ estudante.setSenha("encodedPassword");
+ estudante.setCurso("Curso");
+ estudante.setPeriodo(1);
+ }
+
+ @Test
+ void deveCadastrarEstudanteComSucesso() {
+ when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.empty());
+ when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword");
+ when(estudanteRepositorio.save(any(Estudante.class))).thenReturn(estudante);
+
+ RespostaEstudanteDTO resposta = estudanteServico.cadastrar(requisicaoCadastro);
+
+ assertNotNull(resposta);
+ assertEquals("Teste", resposta.nome());
+ verify(estudanteRepositorio, times(1)).save(any(Estudante.class));
+ }
+
+ @Test
+ void deveLancarExcecaoAoCadastrarEmailExistente() {
+ when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
+
+ assertThrows(ExcecaoNegocio.class, () -> estudanteServico.cadastrar(requisicaoCadastro));
+ verify(estudanteRepositorio, never()).save(any(Estudante.class));
+ }
+
+ @Test
+ void deveRealizarLoginComSucesso() {
+ when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
+ when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true);
+ when(utilJwt.generateToken(anyString())).thenReturn("token123");
+
+ RespostaLoginDTO resposta = estudanteServico.login(requisicaoLogin);
+
+ assertNotNull(resposta);
+ assertEquals("token123", resposta.token());
+ assertEquals("Teste", resposta.estudante().nome());
+ }
+
+ @Test
+ void deveRejeitarSenhaInvalida() {
+ when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.of(estudante));
+ when(passwordEncoder.matches(anyString(), anyString())).thenReturn(false);
+
+ assertThrows(ResponseStatusException.class, () -> estudanteServico.login(requisicaoLogin));
+ }
+
+ @Test
+ void deveRejeitarEmailNaoEncontrado() {
+ when(estudanteRepositorio.findByEmail(anyString())).thenReturn(Optional.empty());
+
+ assertThrows(ResponseStatusException.class, () -> estudanteServico.login(requisicaoLogin));
+ }
+}
diff --git a/src/test/java/com/agendaestudantil/servico/TarefaServicoTest.java b/src/test/java/com/agendaestudantil/servico/TarefaServicoTest.java
new file mode 100644
index 0000000..51139d8
--- /dev/null
+++ b/src/test/java/com/agendaestudantil/servico/TarefaServicoTest.java
@@ -0,0 +1,130 @@
+package com.agendaestudantil.servico;
+
+import com.agendaestudantil.dto.RequisicaoTarefaDTO;
+import com.agendaestudantil.dto.RespostaTarefaDTO;
+import com.agendaestudantil.entidade.Tarefa;
+import com.agendaestudantil.excecao.ExcecaoNegocio;
+import com.agendaestudantil.excecao.ExcecaoRecursoNaoEncontrado;
+import com.agendaestudantil.repositorio.TarefaRepositorio;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class TarefaServicoTest {
+
+ @Mock
+ private TarefaRepositorio tarefaRepositorio;
+
+ @Mock
+ private SecurityContext securityContext;
+
+ @Mock
+ private Authentication authentication;
+
+ @InjectMocks
+ private TarefaServico tarefaServico;
+
+ private RequisicaoTarefaDTO requisicaoTarefa;
+ private Tarefa tarefa;
+ private final String estudanteId = "estudante123";
+
+ @BeforeEach
+ void setUp() {
+ requisicaoTarefa = new RequisicaoTarefaDTO(
+ "Estudar Spring",
+ "Aprender Spring Boot 3",
+ Tarefa.Prioridade.ALTA,
+ Tarefa.StatusTarefa.PENDENTE,
+ LocalDate.now(),
+ null);
+
+ tarefa = new Tarefa();
+ tarefa.setId("tarefa1");
+ tarefa.setTitulo("Estudar Spring");
+ tarefa.setDescricao("Aprender Spring Boot 3");
+ tarefa.setPrioridade(Tarefa.Prioridade.ALTA);
+ tarefa.setStatus(Tarefa.StatusTarefa.PENDENTE);
+ tarefa.setDataEntrega(LocalDate.now());
+ tarefa.setEstudanteId(estudanteId);
+ }
+
+ private void mockAuthentication(String user) {
+ when(securityContext.getAuthentication()).thenReturn(authentication);
+ when(authentication.getName()).thenReturn(user);
+ SecurityContextHolder.setContext(securityContext);
+ }
+
+ @Test
+ void deveCriarTarefaComSucesso() {
+ mockAuthentication(estudanteId);
+ when(tarefaRepositorio.save(any(Tarefa.class))).thenReturn(tarefa);
+
+ RespostaTarefaDTO resposta = tarefaServico.criarTarefa(requisicaoTarefa);
+
+ assertNotNull(resposta);
+ assertEquals("Estudar Spring", resposta.titulo());
+ verify(tarefaRepositorio, times(1)).save(any(Tarefa.class));
+ }
+
+ @Test
+ void deveLancarExcecaoAoCriarTarefaParaOutroUsuario() {
+ String outroId = "outroEstudante";
+ mockAuthentication(outroId);
+ when(tarefaRepositorio.save(any(Tarefa.class))).thenAnswer(inv -> {
+ Tarefa t = inv.getArgument(0);
+ t.setId("tarefa2");
+ return t;
+ });
+
+ RespostaTarefaDTO resposta = tarefaServico.criarTarefa(requisicaoTarefa);
+
+ assertNotNull(resposta);
+ assertEquals(outroId, resposta.estudanteId());
+ }
+
+ @Test
+ void deveListarTarefasPorEstudanteComSucesso() {
+ mockAuthentication(estudanteId);
+ when(tarefaRepositorio.findByEstudanteId(estudanteId)).thenReturn(List.of(tarefa));
+
+ List