diff --git a/app/Aluno/AlunoHome.tsx b/app/Aluno/AlunoHome.tsx index 996f506..c170bee 100644 --- a/app/Aluno/AlunoHome.tsx +++ b/app/Aluno/AlunoHome.tsx @@ -6,7 +6,7 @@ import * as DocumentPicker from 'expo-document-picker'; import * as FileSystem from 'expo-file-system/legacy'; import * as Location from 'expo-location'; import { useRouter } from 'expo-router'; -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useMemo, useRef, useState } from 'react'; import { ActivityIndicator, Animated, @@ -50,14 +50,12 @@ const AlunoHome = memo(() => { const hojeStr = new Date().toISOString().split('T')[0]; const scrollViewRef = useRef(null); - // ESTADO DOS SEPARADORES - const [activeTab, setActiveTab] = useState<'horas' | 'horario' | 'info'>('horas'); - + const [activeTab, setActiveTab] = useState<'assiduidade' | 'horario' | 'info'>('assiduidade'); const [selectedDate, setSelectedDate] = useState(hojeStr); + + const [userRole, setUserRole] = useState('aluno'); const [estagioDetalhes, setEstagioDetalhes] = useState(null); const [horariosEstagio, setHorariosEstagio] = useState([]); - - // 🟢 O NOVO MOTOR CENTRAL (Substitui as 5 variáveis antigas) const [registosDiarios, setRegistosDiarios] = useState>({}); const [statsFaltas, setStatsFaltas] = useState({ justificadas: 0, injustificadas: 0, totalPresencas: 0 }); @@ -73,19 +71,16 @@ const AlunoHome = memo(() => { const [alertConfig, setAlertConfig] = useState<{ msg: string, type: 'success' | 'error' | 'info' } | null>(null); const alertOpacity = useMemo(() => new Animated.Value(0), []); - const azulPetroleo = '#2390a6'; - const laranjaEPVC = '#dd8707'; - const themeStyles = useMemo(() => ({ fundo: isDarkMode ? '#0F0F0F' : '#F8FAFC', card: isDarkMode ? '#1A1A1A' : '#FFFFFF', texto: isDarkMode ? '#F8FAFC' : '#1E293B', textoSecundario: isDarkMode ? '#94A3B8' : '#64748B', borda: isDarkMode ? '#2D2D2D' : '#E2E8F0', - azul: azulPetroleo, - laranja: laranjaEPVC, - amarelo: '#F59E0B', // Adicionado para as faltas por confirmar - cinzento: '#94A3B8', // Adicionado para presenças por confirmar + azul: '#2390a6', + laranja: '#dd8707', + amarelo: '#F59E0B', + cinzento: '#94A3B8', verde: '#10B981', vermelho: '#EF4444', azulSuave: isDarkMode ? 'rgba(35, 144, 166, 0.15)' : 'rgba(35, 144, 166, 0.1)', @@ -109,9 +104,12 @@ const AlunoHome = memo(() => { const { data: { user } } = await supabase.auth.getUser(); if (!user) return; + const { data: profile } = await supabase.from('profiles').select('tipo').eq('id', user.id).single(); + if (profile) setUserRole(profile.tipo); + const { data: eData } = await supabase .from('estagios') - .select('id, data_inicio, data_fim, horas_totais, horas_concluidas, horas_diarias, empresas(nome, tutor_nome, tutor_telefone)') + .select('id, data_inicio, data_fim, horas_diarias, empresas(nome, tutor_nome, tutor_telefone)') .eq('aluno_id', user.id) .order('data_fim', { ascending: false }) .limit(1) @@ -120,11 +118,7 @@ const AlunoHome = memo(() => { setEstagioDetalhes(eData || null); if (eData && eData.id) { - const { data: hData } = await supabase - .from('horarios_estagio') - .select('periodo, hora_inicio, hora_fim') - .eq('estagio_id', eData.id); - + const { data: hData } = await supabase.from('horarios_estagio').select('periodo, hora_inicio, hora_fim').eq('estagio_id', eData.id); setHorariosEstagio(hData || []); } else { setHorariosEstagio([]); @@ -145,22 +139,17 @@ const AlunoHome = memo(() => { data?.forEach(item => { novosRegistos[item.data] = item; - if (item.estado === 'presente' && item.estado_tutor === 'aprovado') { countPresencasAprovadas++; } else if (item.estado === 'faltou') { - if (item.estado_tutor === 'aprovado' && item.justificacao_url) { - countJustificadas++; - } else if (item.estado_tutor === 'aprovado' || item.estado_tutor === 'rejeitado') { - countInjustificadas++; - } + if (item.estado_tutor === 'aprovado' && item.justificacao_url) countJustificadas++; + else if (item.estado_tutor === 'aprovado' || item.estado_tutor === 'rejeitado') countInjustificadas++; } }); setRegistosDiarios(novosRegistos); setStatsFaltas({ justificadas: countJustificadas, injustificadas: countInjustificadas, totalPresencas: countPresencasAprovadas }); - // Garante que o input do sumário reflete o dia atual if (novosRegistos[selectedDate]) setSumarioInput(novosRegistos[selectedDate].sumario || ""); else setSumarioInput(""); @@ -168,7 +157,6 @@ const AlunoHome = memo(() => { setRegistosDiarios({}); setStatsFaltas({ justificadas: 0, injustificadas: 0, totalPresencas: 0 }); } - } catch (error) { console.error(error); } finally { @@ -178,23 +166,6 @@ const AlunoHome = memo(() => { useFocusEffect(useCallback(() => { fetchDadosSupabase(); }, [selectedDate])); - useEffect(() => { - const estagiosSubscription = supabase - .channel('estagios_changes') - .on('postgres_changes', { event: '*', schema: 'public', table: 'estagios' }, () => fetchDadosSupabase()) - .subscribe(); - - const presencasSubscription = supabase - .channel('presencas_changes') - .on('postgres_changes', { event: '*', schema: 'public', table: 'presencas' }, () => fetchDadosSupabase()) - .subscribe(); - - return () => { - supabase.removeChannel(estagiosSubscription); - supabase.removeChannel(presencasSubscription); - }; - }, []); - const onRefresh = useCallback(async () => { setRefreshing(true); await fetchDadosSupabase(true); @@ -211,24 +182,20 @@ const AlunoHome = memo(() => { }, [estagioDetalhes, hojeStr]); const infoData = useMemo(() => { - const data = new Date(selectedDate); - const diaSemana = data.getDay(); const nomeFeriado = feriadosMap[selectedDate]; - const temEstagio = !!estagioDetalhes && estagioDetalhes.data_inicio && estagioDetalhes.data_fim; const antesDoInicio = temEstagio && selectedDate < estagioDetalhes.data_inicio; const depoisDoFim = temEstagio && selectedDate > estagioDetalhes.data_fim; const estagioAtivo = statusEstagio === 'ativo'; return { - valida: estagioAtivo && diaSemana !== 0 && diaSemana !== 6 && !antesDoInicio && !depoisDoFim && !nomeFeriado, + valida: estagioAtivo && !antesDoInicio && !depoisDoFim && !nomeFeriado, podeMarcar: estagioAtivo && selectedDate === hojeStr && !antesDoInicio && !depoisDoFim && !nomeFeriado, nomeFeriado, antesDoInicio, depoisDoFim, foraDeRange: !temEstagio || antesDoInicio || depoisDoFim, temEstagio, estagioAtivo }; }, [selectedDate, estagioDetalhes, hojeStr, feriadosMap, statusEstagio]); - // Modificado para ver no "cofre" em vez das antigas variáveis const isDiaMarcado = () => !!registosDiarios[selectedDate]; const savePresencaData = async (payload: any, successMessage: string) => { @@ -280,7 +247,7 @@ const AlunoHome = memo(() => { lat: loc.coords.latitude, lng: loc.coords.longitude, estado_tutor: 'pendente' - }, "Presença marcada! A aguardar aprovação da empresa."); + }, "Presença registada! A aguardar aprovação da entidade."); } catch (e: any) { showAlert(e.message, "error"); @@ -296,7 +263,7 @@ const AlunoHome = memo(() => { await savePresencaData({ estado: 'faltou', estado_tutor: 'pendente' - }, "Falta registada e enviada para a entidade."); + }, "Falta registada e notificada à entidade."); }; const selecionarDocumento = async () => { @@ -316,11 +283,7 @@ const AlunoHome = memo(() => { const { data: { publicUrl } } = supabase.storage.from('justificacoes').getPublicUrl(fileName); - await savePresencaData({ - justificacao_url: publicUrl, - estado_tutor: 'pendente' - }, "Justificativo enviado à entidade com sucesso!"); - + await savePresencaData({ justificacao_url: publicUrl, estado_tutor: 'pendente' }, "Documento submetido à entidade com sucesso!"); setPdf(null); } catch (e) { showAlert("Erro no upload do documento.", "error"); @@ -330,49 +293,37 @@ const AlunoHome = memo(() => { }; const guardarSumario = async () => { - await savePresencaData({ - sumario: sumarioInput, - estado_tutor: 'pendente' - }, "Sumário submetido para validação!"); + await savePresencaData({ sumario: sumarioInput, estado_tutor: 'pendente' }, "Atividades submetidas para validação!"); setEditandoSumario(false); }; -// 🟢 AS CORES AGORA REFLETEM AS TUAS REGRAS EXATAS const gerarMarcacoesCalendario = () => { const marcacoes: any = {}; - - // Feriados ficam com o azul principal da app Object.keys(feriadosMap).forEach(d => { marcacoes[d] = { marked: true, dotColor: '#000000b7' }; }); - // Pontinhos Azuis: Identificar os dias que já passaram e que estão sem nada if (estagioDetalhes?.data_inicio) { const start = new Date(estagioDetalhes.data_inicio); const limit = new Date(hojeStr) < new Date(estagioDetalhes.data_fim) ? new Date(hojeStr) : new Date(estagioDetalhes.data_fim); - for (let d = new Date(start); d <= limit; d.setDate(d.getDate() + 1)) { const dateStr = d.toISOString().split('T')[0]; - const diaSemana = d.getDay(); - if (diaSemana !== 0 && diaSemana !== 6 && !feriadosMap[dateStr]) { - if (!registosDiarios[dateStr]) { - marcacoes[dateStr] = { marked: true, dotColor: '#0947f1b7' }; // 🔵 Azul: Sem nada - } + if (!feriadosMap[dateStr] && !registosDiarios[dateStr]) { + marcacoes[dateStr] = { marked: true, dotColor: '#0947f1b7' }; } } } - // Regras de Cores para dias com Registo Object.values(registosDiarios).forEach(reg => { let cor = themeStyles.azul; const temSumario = reg.sumario && reg.sumario.trim() !== ''; if (reg.estado === 'presente') { - if (reg.estado_tutor === 'aprovado' && temSumario) cor = themeStyles.verde; // 🟢 Verde: Confirmada e com sumário - else if (!temSumario) cor = themeStyles.amarelo; // 🟡 Amarelo: Presença sem sumário - else cor = themeStyles.azul; // 🔵 Azul: Tem sumário mas falta validar ("Sem nada" da empresa) + if (reg.estado_tutor === 'aprovado' && temSumario) cor = themeStyles.verde; + else if (!temSumario) cor = themeStyles.amarelo; + else cor = themeStyles.azul; } else if (reg.estado === 'faltou') { - if (reg.estado_tutor === 'aprovado') cor = themeStyles.cinzento; // 🔘 Cinzento: Falta confirmada - else if (reg.justificacao_url) cor = themeStyles.amarelo; // 🟡 Amarelo: Falta justificada (ainda não aprovada) - else cor = themeStyles.vermelho; // 🔴 Vermelho: Falta injustificada + if (reg.estado_tutor === 'aprovado') cor = themeStyles.cinzento; + else if (reg.justificacao_url) cor = themeStyles.amarelo; + else cor = themeStyles.vermelho; } marcacoes[reg.data] = { marked: true, dotColor: cor }; }); @@ -384,42 +335,25 @@ const AlunoHome = memo(() => { const getBadgeStyle = () => { if (statusEstagio === 'concluido') return { bg: '#E2E8F0', text: '#475569', label: 'CONCLUÍDO' }; if (statusEstagio === 'agendado') return { bg: '#FEF3C7', text: '#D97706', label: 'AGENDADO' }; - return { bg: themeStyles.verde + '20', text: themeStyles.verde, label: 'A DECORRER' }; + return { bg: themeStyles.verde + '20', text: themeStyles.verde, label: 'EM CURSO' }; }; -const badgeObj = getBadgeStyle(); + const badgeObj = getBadgeStyle(); const renderAvisoEstadoDia = () => { const reg = registosDiarios[selectedDate]; - - // Configuração base (Azul - Sem nada) - let config = { - icon: 'information-circle', - cor: themeStyles.azul, - bg: themeStyles.azulSuave, - texto: 'Sem Registo (Sem Nada)' - }; + let config = { icon: 'information-circle', cor: themeStyles.azul, bg: themeStyles.azulSuave, texto: 'Sem Registo de Atividade' }; if (!reg) { - // Se o dia não for válido para estágio ou for no futuro, não mostra nada if (infoData.foraDeRange || !infoData.valida || selectedDate > hojeStr) return null; } else if (reg.estado === 'presente') { const temSumario = reg.sumario && reg.sumario.trim() !== ''; - - if (reg.estado_tutor === 'aprovado' && temSumario) { - config = { icon: 'checkmark-circle', cor: themeStyles.verde, bg: themeStyles.verde + '20', texto: 'Presença Confirmada e com Sumário' }; - } else if (!temSumario) { - config = { icon: 'warning', cor: themeStyles.amarelo, bg: themeStyles.amarelo + '20', texto: 'Presença Sem Sumário' }; - } else { - config = { icon: 'time', cor: themeStyles.azul, bg: themeStyles.azulSuave, texto: 'Presença Pendente de Aprovação' }; - } + if (reg.estado_tutor === 'aprovado' && temSumario) config = { icon: 'checkmark-circle', cor: themeStyles.verde, bg: themeStyles.verde + '20', texto: 'Presença Confirmada e Validada' }; + else if (!temSumario) config = { icon: 'warning', cor: themeStyles.amarelo, bg: themeStyles.amarelo + '20', texto: 'Presença Requer Submissão de Atividades' }; + else config = { icon: 'time', cor: themeStyles.azul, bg: themeStyles.azulSuave, texto: 'Presença Pendente de Validação' }; } else { - if (reg.estado_tutor === 'aprovado') { - config = { icon: 'checkmark-done-circle', cor: themeStyles.cinzento, bg: themeStyles.cinzento + '20', texto: 'Falta Confirmada pela Entidade' }; - } else if (reg.justificacao_url) { - config = { icon: 'document-text', cor: themeStyles.amarelo, bg: themeStyles.amarelo + '20', texto: 'Falta Justificada (Em Análise)' }; - } else { - config = { icon: 'close-circle', cor: themeStyles.vermelho, bg: themeStyles.vermelhoSuave, texto: 'Falta Injustificada' }; - } + if (reg.estado_tutor === 'aprovado') config = { icon: 'checkmark-done-circle', cor: themeStyles.cinzento, bg: themeStyles.cinzento + '20', texto: 'Falta Confirmada pela Entidade' }; + else if (reg.justificacao_url) config = { icon: 'document-text', cor: themeStyles.amarelo, bg: themeStyles.amarelo + '20', texto: 'Documento em Análise pela Entidade' }; + else config = { icon: 'close-circle', cor: themeStyles.vermelho, bg: themeStyles.vermelhoSuave, texto: 'Falta Injustificada' }; } return ( @@ -430,10 +364,6 @@ const badgeObj = getBadgeStyle(); ); }; - const horasTotais = Number(estagioDetalhes?.horas_totais) || 0; - const horasConcluidas = Number(estagioDetalhes?.horas_concluidas) || 0; - const horasEmFalta = Math.max(0, horasTotais - horasConcluidas); - const regSelecionado = registosDiarios[selectedDate]; return ( @@ -447,12 +377,12 @@ const badgeObj = getBadgeStyle(); - Confirmar Local + Verificação Geográfica - Precisamos de validar a tua localização para confirmar que estás no estágio. + Para registar a presença, é necessário validar as coordenadas geográficas da entidade de acolhimento. - Confirmar e Marcar + Autorizar e Registar setShowLocationModal(false)}> Cancelar @@ -467,175 +397,154 @@ const badgeObj = getBadgeStyle(); )} - } - > + }> + Estágios+ - router.push('/Aluno/definicoes')} style={{ marginRight: 15 }}> - - - router.push('/Aluno/perfil')}> - - + router.push('/Aluno/definicoes')} style={{ marginRight: 15 }}> + router.push('/Aluno/perfil')}> - setActiveTab('horas')} activeOpacity={0.7} - > - - Horas + setActiveTab('assiduidade')} activeOpacity={0.7}> + + Assiduidade - setActiveTab('horario')} activeOpacity={0.7} - > - + setActiveTab('horario')} activeOpacity={0.7}> + Horário - setActiveTab('info')} activeOpacity={0.7} - > - - Info + setActiveTab('info')} activeOpacity={0.7}> + + Detalhes {!isLoadingDB && ( estagioDetalhes ? ( - + // ======================================================== + // CAIXA MESTRA DO CONTEÚDO DA TAB + // Adicionado minHeight: 160 e justifyContent para fixar tamanho! + // ======================================================== + - - - - {estagioDetalhes.empresas?.nome || "Empresa não definida"} - - - {badgeObj.label} - - - - - - {activeTab === 'horas' && ( - - {/* LINHA DE CIMA: CÁLCULO DAS HORAS */} - - - REALIZADAS - {horasConcluidas}h + {/* O Cabeçalho (Empresa) só aparece para Professores e Entidades */} + {userRole !== 'aluno' && ( + <> + + + + {estagioDetalhes.empresas?.nome || "Entidade Não Atribuída"} - - - EM FALTA - {horasEmFalta}h - - - - TOTAIS - {horasTotais}h + + {badgeObj.label} - - - - {/* LINHA DE BAIXO: ESTATÍSTICA DE PRESENÇAS E FALTAS */} - - - {/* Corrigido para PRESENÇAS */} - PRESENÇAS - {statsFaltas.totalPresencas} - - - - FALTAS JUST. - {statsFaltas.justificadas} - - - - FALTAS INJ. - {statsFaltas.injustificadas} - - - + + )} - {activeTab === 'horario' && ( - - - Carga Horária - - {estagioDetalhes.horas_diarias ? estagioDetalhes.horas_diarias + '/dia' : 'Não definido'} - + {/* CONTEÚDO DAS TABS COM ALTURA FIXA */} + + {/* ABA: ASSIDUIDADE */} + {activeTab === 'assiduidade' && ( + + {userRole === 'aluno' && ( + + As Minhas Estatísticas + + )} + + + PRESENÇAS + {statsFaltas.totalPresencas} + + + + FALTAS JUST. + {statsFaltas.justificadas} + + + + FALTAS INJ. + {statsFaltas.injustificadas} + + + + )} - {horariosEstagio.length > 0 && ( - - - {horariosEstagio.map((h, index) => ( - - - - {h.periodo} + {/* ABA: HORÁRIO */} + {activeTab === 'horario' && ( + + + Carga Horária Definida + + {estagioDetalhes.horas_diarias ? estagioDetalhes.horas_diarias + ' Horas/Dia' : 'Não definido'} + + + {horariosEstagio.length > 0 && ( + + + {horariosEstagio.map((h, index) => ( + + + + {h.periodo} + + + {h.hora_inicio?.slice(0, 5)} - {h.hora_fim?.slice(0, 5)} + - - {h.hora_inicio?.slice(0, 5)} - {h.hora_fim?.slice(0, 5)} - - - ))} - - )} - - )} + ))} + + )} + + )} - {activeTab === 'info' && ( - - - - - Tutor da Empresa - {estagioDetalhes.empresas?.tutor_nome || "N/A"} + {/* ABA: DETALHES DA ENTIDADE */} + {activeTab === 'info' && ( + + + + + Tutor da Entidade + {estagioDetalhes.empresas?.tutor_nome || "N/A"} + + + + + + + Contacto Oficial + {estagioDetalhes.empresas?.tutor_telefone || "N/A"} + + + + + + + + Início do Processo + {estagioDetalhes.data_inicio} + + + Término Previsto + {estagioDetalhes.data_fim} + - - - - - Contacto - {estagioDetalhes.empresas?.tutor_telefone || "N/A"} - - - - - - - - Data de Início - {estagioDetalhes.data_inicio} - - - Data de Fim (Prevista) - {estagioDetalhes.data_fim} - - - - )} - + )} + ) : ( - Sem estágio atribuído no sistema. Aguarda indicação do teu professor. + Processo de estágio ainda não atribuído. Aguarda notificação da coordenação. ) @@ -647,14 +556,14 @@ const badgeObj = getBadgeStyle(); onPress={handlePresencaClick} disabled={!infoData.podeMarcar || isDiaMarcado() || isLocating} > - {isLocating ? : Marcar Presença} + {isLocating ? : Registar Presença} - Marcar Falta + Declarar Falta @@ -678,44 +587,41 @@ const badgeObj = getBadgeStyle(); 🎉 {infoData.nomeFeriado} )} - {/* 🟢 MOSTRA O AVISO DO ESTADO DO TUTOR */} {renderAvisoEstadoDia()} - {/* SE O ALUNO ESTIVER PRESENTE, MOSTRA O SUMÁRIO */} {regSelecionado?.estado === 'presente' && ( - Sumário + Relatório de Atividades setEditandoSumario(true)}> {editandoSumario && Submeter para Validação} )} - {/* SE O ALUNO FALTOU, MOSTRA O UPLOAD DE JUSTIFICAÇÃO */} {regSelecionado?.estado === 'faltou' && ( - Justificar Falta + Documento de Justificação {regSelecionado.justificacao_url ? ( - Justificativo Enviado à Entidade + Documento Submetido à Entidade ) : ( <> - {pdf ? pdf.name : "Selecionar Documento PDF"} + {pdf ? pdf.name : "Anexar Documento PDF"} {pdf && ( - {isUploading ? : Submeter Documento} + {isUploading ? : Submeter Anexo} )} @@ -734,10 +640,10 @@ const styles = StyleSheet.create({ topIcons: { flexDirection: 'row', alignItems: 'center' }, title: { fontSize: 26, fontWeight: '900' }, - quickActionsContainer: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20, borderRadius: 20, padding: 6 }, - quickActionBtn: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 12, borderRadius: 16, gap: 6, elevation: 0, shadowOpacity: 0, borderWidth: 0 }, + quickActionsContainer: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20, borderRadius: 20, padding: 6, gap: 5 }, + quickActionBtn: { flex: 1, flexDirection: 'column', alignItems: 'center', justifyContent: 'center', paddingVertical: 10, borderRadius: 16, gap: 4, elevation: 0, shadowOpacity: 0, borderWidth: 0 }, quickActionBtnActive: { elevation: 0, shadowOpacity: 0, borderWidth: 0 }, - quickActionText: { fontSize: 13, fontWeight: '800' }, + quickActionText: { fontSize: 11, fontWeight: '800', textAlign: 'center' }, dashboardCard: { padding: 18, borderRadius: 20, borderWidth: 1, borderLeftWidth: 5, marginBottom: 20, elevation: 2, shadowOpacity: 0.05, shadowRadius: 8 }, dashHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }, diff --git a/app/Professor/Alunos/relatorios.tsx b/app/Professor/Alunos/relatorios.tsx index 563b852..9478003 100644 --- a/app/Professor/Alunos/relatorios.tsx +++ b/app/Professor/Alunos/relatorios.tsx @@ -8,16 +8,16 @@ import * as Sharing from 'expo-sharing'; import * as WebBrowser from 'expo-web-browser'; import { useCallback, useMemo, useState } from 'react'; import { - ActivityIndicator, - Alert, - RefreshControl, - SafeAreaView, - ScrollView, - StatusBar, - StyleSheet, - Text, - TouchableOpacity, - View + ActivityIndicator, + Alert, + RefreshControl, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View } from 'react-native'; import { supabase } from '../../../lib/supabase'; import { useTheme } from '../../../themecontext'; @@ -361,7 +361,7 @@ export default function GestaoRelatorios() { {/* 3. DIÁRIO DE BORDO (PDF) - AGORA COM O ALUNO_ID! */} - 3. Diário de Bordo + 3. Registos Diários {r.horas_concluidas}h Registadas