fix(products): ordenar produtos do maior para o menor preco, permitir limpar input de preco/stock sem ficar negativo, e impedir adicionar mais quantidade ao carrinho do que o stock disponivel

This commit is contained in:
2026-06-22 17:09:17 +01:00
parent 6c93af609f
commit 7b8e8d6150
18 changed files with 1216 additions and 10 deletions

93
FLUXOGRAMA.md Normal file
View File

@@ -0,0 +1,93 @@
# Fluxograma - Smart Agenda
```mermaid
flowchart TD
A([Abrir app Smart Agenda]) --> B{Utilizador autenticado?}
B -- Nao --> C[Login]
C --> D{Acao escolhida}
D -- Entrar --> E{Credenciais validas?}
E -- Nao --> C
E -- Sim --> F{Tipo de conta}
D -- Criar conta --> G[Registo]
G --> H{Perfil selecionado}
H -- Cliente --> C
H -- Barbearia --> C
D -- Explorar sem conta --> I[Explorar barbearias]
B -- Sim --> F
F -- Cliente --> J[Area do cliente]
F -- Barbearia --> K[Painel da barbearia]
subgraph Cliente
J --> J1[Explorar]
J --> J2[Carrinho]
J --> J3[Perfil]
I --> L[Detalhes da barbearia]
J1 --> L
L --> M{Acao na barbearia}
M -- Agendar servico --> N{Tem sessao?}
N -- Nao --> C
N -- Sim --> O[Escolher servico]
O --> P[Escolher profissional]
P --> Q[Escolher dia e hora]
Q --> R[Confirmar lembrete]
R --> S[Criar agendamento]
S --> T[Agendamento guardado]
T --> J1
M -- Adicionar produto --> U{Tem sessao?}
U -- Nao --> C
U -- Sim --> V[Adicionar ao carrinho]
V --> J2
J2 --> W[Rever itens por barbearia]
W --> X{Finalizar compra}
X -- Sem sessao --> C
X -- Com sessao --> Y[Criar pedido]
Y --> Z[Pedido guardado]
J3 --> AA[Ver historico e favoritos]
J3 --> AB[Criar evento pessoal]
AA --> L
AB --> J3
end
subgraph Barbearia
K --> K1[Agenda]
K --> K2[Servicos]
K --> K3[Produtos]
K --> K4[Equipa]
K --> K5[Definicoes]
K1 --> K1A[Ver marcacoes por data]
K1A --> K1B[Atualizar estado da marcacao]
K2 --> K2A[Criar / editar / apagar servicos]
K3 --> K3A[Criar / editar / apagar produtos e stock]
K4 --> K4A[Criar / editar / apagar barbeiros]
K5 --> K5A[Atualizar dados, contactos, horarios e imagem]
K1B --> DB[(Supabase)]
K2A --> DB
K3A --> DB
K4A --> DB
K5A --> DB
end
S --> DB
Y --> DB
V --> LS[(Armazenamento local)]
AA --> DB
C --> AUTH[(Supabase Auth)]
G --> AUTH
AUTH --> DB
```
## Resumo do fluxo
- Visitantes podem fazer login, criar conta ou explorar barbearias sem conta.
- Clientes autenticados podem explorar barbearias, marcar servicos, adicionar produtos ao carrinho, finalizar pedidos e consultar o perfil.
- Barbearias autenticadas entram no painel para gerir agenda, servicos, produtos, equipa e definicoes.
- Autenticacao e dados persistentes passam pelo Supabase; favoritos/carrinho tambem usam armazenamento local por utilizador.

View File

@@ -0,0 +1,318 @@
\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[portuguese]{babel}
\usepackage{lmodern}
\usepackage{geometry}
\usepackage{setspace}
\usepackage{hyperref}
\usepackage{graphicx}
\usepackage{array}
\usepackage{longtable}
\usepackage{enumitem}
\usepackage{fancyhdr}
\geometry{
left=3cm,
right=2.5cm,
top=2.5cm,
bottom=2.5cm
}
\onehalfspacing
\setlength{\parindent}{1.25cm}
\setlength{\parskip}{0.2cm}
\hypersetup{
colorlinks=true,
linkcolor=black,
urlcolor=blue,
pdftitle={PAP - Smart Agenda},
pdfauthor={Rodrigo Santos}
}
\newcommand{\appname}{Smart Agenda}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[R]{\small \appname}
\fancyfoot[R]{\thepage}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0pt}
\begin{document}
% Capa
\begin{titlepage}
\thispagestyle{empty}
\centering
\vspace*{2cm}
{\Large \textbf{Prova de Aptidão Profissional}\par}
\vspace{1.5cm}
{\Huge \textbf{\appname}\par}
\vspace{0.8cm}
{\Large Aplicação para Gestão de Agendamentos em Barbearias\par}
\vspace{2.5cm}
\begin{flushleft}
\large
\textbf{Aluno:} Rodrigo Santos\\[0.35cm]
\textbf{Curso/Turma:} \underline{\hspace{7cm}}\\[0.35cm]
\textbf{Escola:} \underline{\hspace{8cm}}\\[0.35cm]
\textbf{Ano letivo:} 2025/2026\\[0.35cm]
\textbf{Professor orientador:} Professor João
\end{flushleft}
\vfill
{\large \today\par}
\end{titlepage}
\pagenumbering{roman}
\pagestyle{fancy}
% Agradecimentos
\section*{Agradecimentos}
Gostaria de agradecer, em primeiro lugar, aos professores em geral, por todo o apoio, acompanhamento e conhecimentos transmitidos ao longo do meu percurso escolar. O contributo de cada professor foi importante para que eu pudesse desenvolver as competências necessárias para realizar este projeto e ultrapassar as dificuldades encontradas durante o seu desenvolvimento.
Agradeço especialmente ao Professor João, pela orientação, ajuda e disponibilidade ao longo da realização da minha Prova de Aptidão Profissional. O seu acompanhamento foi essencial para manter o projeto organizado, melhorar o trabalho desenvolvido e encontrar soluções para os problemas que foram surgindo.
Quero também agradecer à Professora Helena, pelo apoio, acompanhamento e disponibilidade demonstrados durante este percurso. A sua ajuda foi importante para a concretização deste trabalho e para a minha evolução enquanto aluno.
Agradeço ainda à Diretora Beatriz, pelo incentivo, apoio e acompanhamento durante o meu percurso escolar. O seu contributo foi importante para criar um ambiente de responsabilidade, motivação e dedicação.
Aos meus colegas, deixo também o meu agradecimento pela colaboração, troca de ideias, ajuda e espírito de entreajuda. Ao longo do desenvolvimento do projeto, o apoio dos colegas foi importante para esclarecer dúvidas, partilhar opiniões e manter a motivação.
Por fim, agradeço à minha família, pelo apoio, paciência, motivação e confiança ao longo deste trabalho. A sua presença foi fundamental para continuar a esforçar-me e concluir este projeto da melhor forma possível.
\newpage
% Glossário
\section*{Glossário}
\textbf{API:} Interface de Programação de Aplicações. Permite que diferentes sistemas comuniquem entre si.
\textbf{App:} Abreviatura de aplicação. Neste relatório, refere-se à aplicação móvel \appname.
\textbf{AsyncStorage:} Sistema de armazenamento local usado em aplicações React Native para guardar pequenas informações no dispositivo.
\textbf{Autenticação:} Processo que permite verificar a identidade de um utilizador através de dados como email e palavra-passe.
\textbf{Base de Dados:} Sistema usado para guardar e organizar informação. No projeto, é utilizada para armazenar utilizadores, barbearias, serviços, produtos, marcações e pedidos.
\textbf{CRUD:} Sigla para Create, Read, Update e Delete. Representa as operações de criar, ler, atualizar e apagar dados.
\textbf{Dashboard:} Painel de gestão onde a barbearia pode consultar e administrar informação importante do negócio.
\textbf{Expo:} Plataforma usada para facilitar o desenvolvimento, teste e execução de aplicações React Native.
\textbf{React Native:} Tecnologia utilizada para desenvolver aplicações móveis com JavaScript e React.
\textbf{Supabase:} Plataforma usada no projeto para autenticação, base de dados e armazenamento de imagens.
\textbf{TypeScript:} Linguagem baseada em JavaScript que permite usar tipos, tornando o código mais organizado e seguro.
\newpage
% Índice
\renewcommand{\contentsname}{Conteúdo}
\tableofcontents
\newpage
\pagenumbering{arabic}
\section{Introdução}
A presente Prova de Aptidão Profissional tem como tema o desenvolvimento da aplicação \appname, uma solução digital criada para facilitar a gestão de agendamentos em barbearias. O projeto foi desenvolvido por Rodrigo Santos e tem como principal objetivo responder a uma necessidade comum em muitos estabelecimentos: organizar marcações, serviços, barbeiros, produtos, horários e pedidos de uma forma mais simples, rápida e eficiente.
Atualmente, muitas barbearias ainda recorrem a métodos manuais para gerir as suas marcações, como chamadas telefónicas, mensagens, redes sociais ou agendas em papel. Apesar de estes métodos continuarem a ser usados, podem causar vários problemas, como esquecimentos, marcações sobrepostas, dificuldade em consultar horários disponíveis e perda de tempo tanto para os clientes como para os profissionais.
O \appname{} surge como uma solução para modernizar este processo. A aplicação permite que os clientes pesquisem barbearias, consultem serviços, escolham barbeiros, selecionem datas e horários, façam marcações e acompanhem os seus pedidos. Ao mesmo tempo, permite que as barbearias tenham acesso a uma área de gestão onde podem controlar a agenda, os serviços, os produtos, a equipa, os contactos, os horários e outras informações importantes do estabelecimento.
Este projeto permitiu aplicar conhecimentos adquiridos ao longo do curso, nomeadamente na área da programação, desenvolvimento mobile, bases de dados, autenticação, navegação entre ecrãs, gestão de estado e criação de interfaces. Para além da componente técnica, o projeto também exigiu organização, planeamento, pesquisa e capacidade de resolver problemas.
\subsection{Planeamento do Projeto}
O planeamento do projeto foi uma etapa essencial para definir o funcionamento da aplicação e organizar o desenvolvimento. Antes da implementação, foi necessário identificar o problema a resolver, pensar nos utilizadores da aplicação e definir as funcionalidades mais importantes para cada tipo de conta.
Inicialmente, foi definido que a aplicação teria dois tipos principais de utilizadores: cliente e barbearia. O cliente teria acesso à pesquisa de barbearias, consulta de serviços, criação de marcações, carrinho de produtos e perfil pessoal. A barbearia teria acesso a uma área de gestão, onde poderia consultar marcações, gerir serviços, produtos, barbeiros, horários, contactos e dados do estabelecimento.
Depois desta definição, o trabalho foi dividido em várias fases. A primeira fase consistiu na criação da estrutura do projeto em React Native com Expo e TypeScript. De seguida, foi criada a navegação entre ecrãs, separando a área de autenticação, a área do cliente e a área da barbearia. Posteriormente, foi feita a integração com o Supabase, responsável pela autenticação, base de dados e armazenamento de imagens.
Durante o planeamento, também foi necessário pensar na organização dos dados. Foram identificadas entidades como utilizadores, perfis, barbearias, serviços, barbeiros, produtos, marcações, pedidos, avaliações, notificações e lista de espera. Esta estrutura permitiu desenvolver uma aplicação mais completa e próxima de uma solução real.
\subsection{Apresentação da Empresa (opcional)}
No contexto deste projeto, não existiu uma empresa externa responsável pelo desenvolvimento da aplicação. O \appname{} foi desenvolvido como projeto individual no âmbito da Prova de Aptidão Profissional, tendo como objetivo simular uma solução útil para barbearias e clientes.
Apesar de não estar associado a uma empresa real, o projeto foi pensado como se pudesse ser aplicado num contexto profissional. A aplicação foi idealizada para pequenos negócios, especialmente barbearias, que necessitam de ferramentas simples e acessíveis para organizar o seu dia a dia. Desta forma, o \appname{} funciona como uma proposta de produto digital orientado para a gestão de serviços locais.
A solução procura responder a necessidades reais, como a marcação de serviços, organização da agenda, gestão de profissionais, controlo de produtos e melhoria da comunicação com os clientes. Assim, mesmo não existindo uma empresa envolvida, o projeto aproxima-se de um cenário de trabalho real, onde é necessário desenvolver uma aplicação funcional, organizada e útil.
\section{Contexto}
O \appname{} enquadra-se na área das aplicações móveis e da digitalização de serviços. Cada vez mais os clientes procuram soluções rápidas e acessíveis através do telemóvel, evitando deslocações ou contactos demorados. Ao mesmo tempo, os pequenos negócios precisam de ferramentas que lhes permitam organizar melhor o seu funcionamento e melhorar o atendimento.
As barbearias são um exemplo de negócio onde a gestão de horários é muito importante. Um erro numa marcação pode causar atrasos, insatisfação dos clientes ou perda de tempo para os profissionais. Quando existem vários barbeiros, diferentes serviços e produtos para venda, a organização torna-se ainda mais importante.
Neste contexto, o \appname{} pretende centralizar numa única aplicação as principais tarefas relacionadas com a marcação e gestão de serviços de barbearia. A aplicação foi pensada para ser usada tanto por clientes como por barbearias, criando uma ligação direta entre quem procura o serviço e quem o presta.
\subsection{Caraterização da Empresa (opcional)}
Como o projeto não foi desenvolvido dentro de uma empresa específica, esta secção é adaptada à caraterização do produto criado. O \appname{} pode ser entendido como uma solução digital direcionada para barbearias, funcionando como uma plataforma de apoio à gestão do negócio.
A aplicação foi pensada para estabelecimentos que prestam serviços como cortes de cabelo, barba, lavagem, tratamentos e outros serviços relacionados. Para além disso, também permite a venda de produtos, como artigos de cuidado capilar ou produtos de barba, através de um carrinho integrado.
O público-alvo da aplicação divide-se em dois grupos. O primeiro grupo é composto pelos clientes, que procuram marcar serviços de forma rápida. O segundo grupo é composto pelas barbearias, que precisam de gerir marcações, equipa, serviços, produtos e informações do estabelecimento.
\subsubsection{Caraterização do Polo da Empresa}
No âmbito deste projeto, o ``polo'' pode ser interpretado como o ambiente onde a aplicação seria utilizada. Esse ambiente corresponde ao funcionamento diário de uma barbearia, onde existem clientes, profissionais, horários, serviços e produtos.
Numa barbearia, é necessário saber que serviços estão disponíveis, quais os barbeiros que os realizam, que horários existem, que marcações estão pendentes ou confirmadas e que produtos se encontram em stock. A aplicação procura organizar estes elementos de forma simples, permitindo que a barbearia tenha maior controlo sobre a sua atividade.
O \appname{} foi desenvolvido para funcionar como uma ferramenta de apoio neste contexto. Através do painel de gestão, a barbearia pode consultar a agenda, alterar estados de marcações, criar serviços, adicionar produtos, gerir barbeiros e atualizar os seus dados. Para o cliente, a aplicação disponibiliza uma experiência mais direta, centrada na pesquisa e marcação.
\subsection{Perfil de Desempenho do Técnico de Informática de Gestão}
O desenvolvimento do \appname{} está relacionado com várias competências do perfil de um Técnico de Informática de Gestão. Este perfil exige capacidade para analisar problemas, planear soluções, desenvolver aplicações, trabalhar com bases de dados, criar interfaces e garantir que os sistemas respondem às necessidades dos utilizadores.
Durante o projeto, foi necessário aplicar conhecimentos de programação, nomeadamente através de React Native, Expo e TypeScript. Também foi necessário compreender a organização de uma aplicação por componentes, páginas, contexto global, navegação e tipos de dados. Esta organização é importante para garantir que o projeto seja mais fácil de manter e evoluir.
Outra competência importante foi a integração com uma base de dados. O projeto utiliza Supabase para autenticação, armazenamento e gestão de dados. Esta integração exigiu compreender como criar, consultar, atualizar e apagar informações relacionadas com perfis, barbearias, serviços, barbeiros, produtos, marcações, pedidos, notificações e avaliações.
O projeto também exigiu competências de análise e resolução de problemas. Ao longo do desenvolvimento surgiram dificuldades relacionadas com a navegação, autenticação, atualização de dados, gestão do estado da aplicação e organização da interface. Resolver estes problemas contribuiu para desenvolver autonomia, responsabilidade e capacidade de pesquisa.
\subsection{Objetivos da Formação em Contexto de Trabalho}
Apesar de o projeto estar associado à Prova de Aptidão Profissional, os seus objetivos relacionam-se com a aplicação prática de conhecimentos em contexto de trabalho. O desenvolvimento do \appname{} permitiu simular um processo real de criação de uma aplicação, desde a identificação do problema até à implementação da solução.
Um dos principais objetivos foi desenvolver uma aplicação funcional que respondesse a uma necessidade concreta. Para isso, foi necessário pensar no problema da gestão manual de marcações e criar uma solução que facilitasse tanto o trabalho das barbearias como a experiência dos clientes.
Outro objetivo foi consolidar conhecimentos técnicos. O projeto permitiu trabalhar com desenvolvimento mobile, navegação, autenticação, bases de dados, armazenamento, notificações, gestão de estado e design de interface. Estes conhecimentos são importantes para um Técnico de Informática de Gestão, pois estão relacionados com o desenvolvimento e manutenção de soluções digitais.
Também foi objetivo desenvolver competências pessoais e profissionais, como organização, planeamento, autonomia, persistência e capacidade de resolver problemas. Estas competências são fundamentais em qualquer contexto de trabalho, especialmente na área da informática, onde é frequente encontrar desafios técnicos que exigem pesquisa e adaptação.
\subsection{Funções Exercidas na Empresa (opcional)}
Como não existiu uma empresa externa associada ao projeto, as funções exercidas correspondem às tarefas realizadas durante o desenvolvimento da aplicação. Ao longo do projeto, assumi funções semelhantes às de um programador e analista de software.
Entre as principais funções realizadas estiveram a análise do problema, definição dos requisitos, planeamento das funcionalidades, criação da estrutura do projeto, desenvolvimento dos ecrãs, implementação da navegação, integração com Supabase, criação das funcionalidades de cliente e barbearia, testes e correção de erros.
Também foram realizadas tarefas relacionadas com a experiência do utilizador, como a organização dos ecrãs, criação de botões, formulários, cartões, filtros e separadores. O objetivo foi garantir que a aplicação fosse simples de usar, tanto para clientes como para barbearias.
Estas funções permitiram compreender melhor o processo de desenvolvimento de uma aplicação. Foi necessário pensar não só no código, mas também na utilidade da solução, na organização dos dados e na forma como o utilizador interage com a aplicação.
\section{Descrição Técnica}
O \appname{} foi desenvolvido com React Native, Expo e TypeScript. A escolha destas tecnologias permitiu criar uma aplicação mobile organizada e preparada para funcionar em diferentes plataformas. O Expo facilitou o processo de desenvolvimento e teste, enquanto o TypeScript ajudou a tornar o código mais seguro através da definição de tipos.
A aplicação está organizada em várias pastas. A pasta \texttt{components} contém elementos reutilizáveis da interface, como botões, cartões, campos de texto e badges. A pasta \texttt{pages} contém os principais ecrãs da aplicação, como login, registo, exploração, detalhes da barbearia, marcação, carrinho, perfil e dashboard. A pasta \texttt{context} contém o \texttt{AppContext}, responsável pela gestão global dos dados. A pasta \texttt{navigation} contém a configuração das rotas e separadores da aplicação. A pasta \texttt{lib} contém funções auxiliares e a configuração do Supabase. O ficheiro \texttt{types.ts} define os principais tipos de dados usados no projeto.
O estado global da aplicação é gerido através do \texttt{AppContext}. Este contexto guarda informação como utilizador autenticado, barbearias, carrinho, favoritos, marcações, pedidos, lista de espera e notificações. Também disponibiliza funções para login, registo, logout, criação de marcações, gestão de pedidos, serviços, produtos, barbeiros, avaliações e notificações.
O Supabase é utilizado como base de dados, autenticação e armazenamento. A aplicação consulta tabelas como \texttt{profiles}, \texttt{shops}, \texttt{services}, \texttt{barbers}, \texttt{products}, \texttt{appointments}, \texttt{orders}, \texttt{reviews}, \texttt{notifications} e \texttt{waitlist}. Esta estrutura permite guardar os dados de forma centralizada e atualizar a informação apresentada na aplicação.
\subsection{Manual de Funcionamento}
Para utilizar o \appname, o utilizador deve começar por criar uma conta ou iniciar sessão. Durante o registo, pode escolher entre uma conta de cliente ou uma conta de barbearia. Esta escolha define a área da aplicação que será apresentada após o login.
Se o utilizador for cliente, tem acesso à área de exploração. Nesta área pode pesquisar barbearias por nome, morada ou serviço. Também pode utilizar filtros para encontrar barbearias com melhor avaliação, mais serviços ou categorias específicas. Ao selecionar uma barbearia, o cliente pode consultar os seus detalhes, incluindo serviços, barbeiros, produtos, contactos, horários e avaliação.
Para efetuar uma marcação, o cliente escolhe uma barbearia, seleciona um serviço, escolhe um barbeiro, define a data e o horário pretendido e confirma a marcação. A marcação fica registada com o estado inicial pendente. Posteriormente, a barbearia pode confirmar, concluir ou cancelar essa marcação.
O cliente também pode adicionar produtos ao carrinho. Depois de escolher os produtos, pode finalizar o pedido, que fica associado à barbearia correspondente. Além disso, pode consultar o perfil, favoritos, histórico de marcações, pedidos e notificações.
Se o utilizador for barbearia, tem acesso ao dashboard. Neste painel, pode consultar as marcações por data, alterar o estado das marcações, gerir serviços, gerir produtos, gerir barbeiros e editar as informações do estabelecimento. A barbearia pode também definir contactos, redes sociais, métodos de pagamento, horários e imagens.
O funcionamento da aplicação foi pensado para ser simples. O cliente encontra rapidamente o serviço que pretende e a barbearia consegue gerir as principais informações do negócio através de uma interface organizada por separadores.
\subsection{Implementação do Projeto}
A implementação do projeto começou pela criação da estrutura base da aplicação em React Native com Expo. De seguida, foram criados os ecrãs principais e os componentes reutilizáveis. A navegação foi implementada com React Navigation, utilizando uma estrutura diferente para clientes e barbearias.
Na área de autenticação, foi implementado o registo e login através do Supabase. Depois do login, a aplicação consulta o perfil do utilizador e identifica o seu papel. Se o utilizador for cliente, é encaminhado para os separadores de explorar, carrinho e perfil. Se for barbearia, é encaminhado para o painel de gestão.
Na área do cliente, foram implementadas funcionalidades de pesquisa, filtros, consulta de detalhes da barbearia, criação de marcações, carrinho de produtos, favoritos, perfil e notificações. Estas funcionalidades comunicam com o estado global e com a base de dados para apresentar e guardar a informação correta.
Na área da barbearia, foi implementado o dashboard com separadores para agenda, serviços, produtos, equipa e definições. A barbearia pode criar e apagar serviços, adicionar produtos, controlar stock, gerir barbeiros, alterar estados de marcações e atualizar dados do estabelecimento.
Também foram implementadas funcionalidades adicionais, como avaliações, lista de espera e notificações. Quando uma marcação é cancelada, a aplicação pode avisar utilizadores que estejam na lista de espera para aquele barbeiro e data. O projeto inclui ainda funções Supabase preparadas para envio de notificações push e lembretes de agendamentos.
Durante a implementação, uma das partes mais importantes foi garantir que os dados fossem atualizados corretamente depois de cada ação. Para isso, a aplicação utiliza funções de atualização que recarregam as informações do Supabase e mantêm o estado global sincronizado.
\section{Conclusões}
O desenvolvimento do \appname{} permitiu criar uma aplicação funcional para a gestão de agendamentos em barbearias. O projeto cumpriu o objetivo principal de apresentar uma solução digital capaz de ajudar clientes a fazer marcações e barbearias a gerir o seu funcionamento diário.
A aplicação permite pesquisar barbearias, consultar detalhes, escolher serviços, selecionar barbeiros, criar marcações, adicionar produtos ao carrinho, gerir pedidos, guardar favoritos e consultar notificações. Para as barbearias, permite gerir agenda, serviços, produtos, barbeiros, horários, contactos, redes sociais, métodos de pagamento e imagens.
Do ponto de vista técnico, o projeto foi importante para consolidar conhecimentos de desenvolvimento mobile, React Native, Expo, TypeScript, React Navigation, Supabase, autenticação, base de dados, armazenamento e gestão de estado. A utilização destas tecnologias tornou o projeto mais completo e aproximou-o de uma solução real.
Conclui-se que o \appname{} é uma aplicação útil, organizada e com potencial de evolução. Para além de responder a uma necessidade real, o projeto contribuiu para o meu desenvolvimento académico e pessoal, permitindo-me aplicar conhecimentos, enfrentar dificuldades e melhorar as minhas capacidades técnicas.
\subsection{Limitações do Trabalho}
Apesar de o \appname{} apresentar várias funcionalidades, existem algumas limitações que devem ser consideradas. Uma das limitações é o facto de a aplicação ainda não incluir pagamentos online. Esta funcionalidade poderia ser útil para permitir pagamentos antecipados, reservas com sinal ou compra direta de produtos.
Outra limitação está relacionada com as notificações. Embora o projeto tenha suporte para notificações e lembretes, esta área poderia ser melhorada com avisos mais completos, como confirmações automáticas, lembretes por email ou SMS e mensagens personalizadas para os clientes.
A aplicação também poderia ter estatísticas mais avançadas para as barbearias. No futuro, seria útil apresentar dados sobre faturação, serviços mais pedidos, produtos mais vendidos, horários com maior procura e desempenho dos barbeiros.
Outra limitação é a ausência de publicação oficial nas lojas de aplicações. Para que o \appname{} pudesse ser usado por um público real, seria necessário preparar melhor a aplicação, reforçar a segurança, realizar mais testes e publicar na Play Store e na App Store.
Apesar destas limitações, o projeto cumpre os seus objetivos principais e apresenta uma base sólida para futuras melhorias.
\subsection{Apreciação Final}
O desenvolvimento do \appname{} foi uma experiência muito importante e enriquecedora para mim. Ao longo deste projeto, tive a oportunidade de aplicar vários conhecimentos adquiridos durante o meu percurso académico e compreender melhor como se desenvolve uma aplicação funcional.
Durante o trabalho, percebi que criar uma aplicação não consiste apenas em escrever código. É necessário planear, organizar ideias, pensar nos utilizadores, estruturar os ecrãs, definir funcionalidades, ligar a aplicação a uma base de dados e testar o funcionamento. Cada uma destas etapas foi importante para chegar ao resultado final.
Encontrei algumas dificuldades, principalmente na integração com o Supabase, na gestão do estado global da aplicação, na separação entre cliente e barbearia e na organização da interface. No entanto, essas dificuldades ajudaram-me a evoluir, a pesquisar soluções e a melhorar a minha autonomia.
Considero que este projeto foi importante para o meu crescimento pessoal e académico. Ajudou-me a desenvolver responsabilidade, persistência, organização e capacidade de resolução de problemas. Também me permitiu perceber a importância de criar soluções úteis e adaptadas a necessidades reais.
Fico satisfeito com o resultado final, porque consegui desenvolver uma aplicação com funcionalidades reais e com potencial para ser melhorada no futuro. O \appname{} pode ajudar clientes a fazer marcações de forma mais simples e pode ajudar barbearias a gerir melhor o seu dia a dia. No futuro, gostaria de ver a aplicação evoluir com pagamentos online, notificações mais avançadas, estatísticas completas e publicação nas lojas de aplicações.
\newpage
\section*{Webgrafia}
Para o desenvolvimento do projeto e para a realização deste relatório, foram consultadas fontes relacionadas com as tecnologias utilizadas:
\begin{itemize}
\item Documentação oficial do React Native: \url{https://reactnative.dev/}
\item Documentação oficial do Expo: \url{https://docs.expo.dev/}
\item Documentação oficial do TypeScript: \url{https://www.typescriptlang.org/docs/}
\item Documentação oficial do React Navigation: \url{https://reactnavigation.org/docs/getting-started}
\item Documentação oficial do Supabase: \url{https://supabase.com/docs}
\item Documentação oficial do AsyncStorage: \url{https://react-native-async-storage.github.io/async-storage/}
\end{itemize}
\newpage
\section*{Anexos}
Nesta secção podem ser inseridas imagens da aplicação, capturas de ecrã dos principais menus, esquemas da base de dados ou outros elementos de apoio à apresentação do projeto.
Como anexos recomendados para o \appname, podem ser incluídos:
\begin{itemize}
\item Ecrã de login e registo.
\item Ecrã de exploração de barbearias.
\item Página de detalhes da barbearia.
\item Ecrã de marcação de serviço.
\item Carrinho de produtos.
\item Perfil do cliente.
\item Dashboard da barbearia.
\item Gestão de serviços, produtos e barbeiros.
\item Estrutura da base de dados no Supabase.
\end{itemize}
\end{document}

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View File

@@ -0,0 +1,79 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" viewBox="0 0 1200 1200" role="img" aria-labelledby="title desc">
<title id="title">Fluxograma simples Smart Agenda</title>
<desc id="desc">Fluxograma simplificado do funcionamento principal da aplicacao Smart Agenda.</desc>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#1f2937"/>
</marker>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#111827" flood-opacity="0.12"/>
</filter>
<style>
.bg { fill: #f8fafc; }
.title { font: 800 44px Arial, sans-serif; fill: #111827; letter-spacing: 0; }
.subtitle { font: 500 20px Arial, sans-serif; fill: #64748b; letter-spacing: 0; }
.box { fill: #ffffff; stroke: #cbd5e1; stroke-width: 3; filter: url(#shadow); }
.start { fill: #e0f2fe; stroke: #38bdf8; stroke-width: 3; filter: url(#shadow); }
.decision { fill: #fff7ed; stroke: #fb923c; stroke-width: 3; filter: url(#shadow); }
.client { fill: #ecfdf5; stroke: #34d399; stroke-width: 3; filter: url(#shadow); }
.barber { fill: #f5f3ff; stroke: #a78bfa; stroke-width: 3; filter: url(#shadow); }
.data { fill: #f1f5f9; stroke: #64748b; stroke-width: 3; filter: url(#shadow); }
.text { font: 800 24px Arial, sans-serif; fill: #111827; letter-spacing: 0; }
.small { font: 600 17px Arial, sans-serif; fill: #475569; letter-spacing: 0; }
.label { font: 800 17px Arial, sans-serif; fill: #334155; letter-spacing: 0; }
.arrow { fill: none; stroke: #1f2937; stroke-width: 3.2; marker-end: url(#arrow); }
.arrow-soft { fill: none; stroke: #64748b; stroke-width: 2.8; marker-end: url(#arrow); }
</style>
</defs>
<rect class="bg" width="1200" height="1200"/>
<text x="70" y="85" class="title">Fluxograma Smart Agenda</text>
<text x="70" y="122" class="subtitle">Versao simplificada do fluxo principal da aplicacao.</text>
<rect x="455" y="175" width="290" height="80" rx="40" class="start"/>
<text x="600" y="225" text-anchor="middle" class="text">Abrir aplicacao</text>
<polygon points="600,315 750,395 600,475 450,395" class="decision"/>
<text x="600" y="390" text-anchor="middle" class="text">Entrar</text>
<text x="600" y="416" text-anchor="middle" class="small">login ou registo</text>
<rect x="135" y="570" width="340" height="95" rx="18" class="client"/>
<text x="305" y="610" text-anchor="middle" class="text">Cliente</text>
<text x="305" y="640" text-anchor="middle" class="small">explora e faz reservas</text>
<rect x="725" y="570" width="340" height="95" rx="18" class="barber"/>
<text x="895" y="610" text-anchor="middle" class="text">Barbearia</text>
<text x="895" y="640" text-anchor="middle" class="small">gere o negocio</text>
<rect x="115" y="755" width="380" height="115" rx="18" class="box"/>
<text x="305" y="798" text-anchor="middle" class="text">Escolher barbearia</text>
<text x="305" y="830" text-anchor="middle" class="small">servico, barbeiro, dia e hora</text>
<rect x="115" y="960" width="380" height="115" rx="18" class="client"/>
<text x="305" y="1003" text-anchor="middle" class="text">Confirmar</text>
<text x="305" y="1035" text-anchor="middle" class="small">agendamento ou compra</text>
<rect x="705" y="755" width="380" height="115" rx="18" class="box"/>
<text x="895" y="798" text-anchor="middle" class="text">Painel</text>
<text x="895" y="830" text-anchor="middle" class="small">agenda, servicos e produtos</text>
<rect x="705" y="960" width="380" height="115" rx="18" class="barber"/>
<text x="895" y="1003" text-anchor="middle" class="text">Atualizar dados</text>
<text x="895" y="1035" text-anchor="middle" class="small">marcacoes, equipa e perfil</text>
<rect x="455" y="1090" width="290" height="78" rx="18" class="data"/>
<text x="600" y="1123" text-anchor="middle" class="text">Supabase</text>
<text x="600" y="1152" text-anchor="middle" class="small">guarda os dados</text>
<path d="M 600 255 L 600 315" class="arrow"/>
<path d="M 505 445 C 420 505 360 520 305 570" class="arrow"/>
<path d="M 695 445 C 780 505 840 520 895 570" class="arrow"/>
<path d="M 305 665 L 305 755" class="arrow"/>
<path d="M 305 870 L 305 960" class="arrow"/>
<path d="M 895 665 L 895 755" class="arrow"/>
<path d="M 895 870 L 895 960" class="arrow"/>
<path d="M 405 1075 C 440 1118 440 1128 455 1128" class="arrow-soft"/>
<path d="M 795 1075 C 760 1118 760 1128 745 1128" class="arrow-soft"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
fluxograma-smart-agenda.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

BIN
fluxograma-smart-agenda.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 KiB

208
fluxograma-smart-agenda.svg Normal file
View File

@@ -0,0 +1,208 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1800" height="1800" viewBox="0 0 1800 1800" role="img" aria-labelledby="title desc">
<title id="title">Fluxograma Smart Agenda</title>
<desc id="desc">Fluxograma do fluxo de autenticacao, cliente, barbearia e persistencia de dados da aplicacao Smart Agenda.</desc>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="9" markerHeight="9" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#334155"/>
</marker>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="10" stdDeviation="10" flood-color="#0f172a" flood-opacity="0.12"/>
</filter>
<style>
.bg { fill: #f8fafc; }
.title { font: 800 42px Arial, sans-serif; fill: #0f172a; letter-spacing: 0; }
.subtitle { font: 500 18px Arial, sans-serif; fill: #64748b; letter-spacing: 0; }
.lane-title { font: 800 22px Arial, sans-serif; fill: #0f172a; letter-spacing: 0; }
.lane { fill: #ffffff; stroke: #e2e8f0; stroke-width: 2; rx: 18; }
.node { fill: #ffffff; stroke: #cbd5e1; stroke-width: 2; filter: url(#shadow); }
.node-blue { fill: #eef6ff; stroke: #60a5fa; stroke-width: 2; filter: url(#shadow); }
.node-green { fill: #ecfdf5; stroke: #34d399; stroke-width: 2; filter: url(#shadow); }
.node-orange { fill: #fff7ed; stroke: #fb923c; stroke-width: 2; filter: url(#shadow); }
.node-purple { fill: #f5f3ff; stroke: #a78bfa; stroke-width: 2; filter: url(#shadow); }
.node-red { fill: #fef2f2; stroke: #f87171; stroke-width: 2; filter: url(#shadow); }
.store { fill: #f1f5f9; stroke: #64748b; stroke-width: 2; filter: url(#shadow); }
.text { font: 700 18px Arial, sans-serif; fill: #0f172a; letter-spacing: 0; }
.small { font: 600 14px Arial, sans-serif; fill: #475569; letter-spacing: 0; }
.label { font: 700 14px Arial, sans-serif; fill: #334155; letter-spacing: 0; }
.arrow { fill: none; stroke: #334155; stroke-width: 2.4; marker-end: url(#arrow); }
.arrow-soft { fill: none; stroke: #64748b; stroke-width: 2.1; marker-end: url(#arrow); }
.dash { fill: none; stroke: #64748b; stroke-width: 2.1; stroke-dasharray: 8 8; marker-end: url(#arrow); }
</style>
</defs>
<rect class="bg" width="1800" height="1800"/>
<text x="80" y="70" class="title">Fluxograma Smart Agenda</text>
<text x="80" y="104" class="subtitle">Navegacao principal do app: visitante, cliente, barbearia e persistencia no Supabase.</text>
<rect x="60" y="145" width="390" height="970" class="lane"/>
<rect x="490" y="145" width="570" height="970" class="lane"/>
<rect x="1100" y="145" width="420" height="970" class="lane"/>
<rect x="1560" y="145" width="180" height="970" class="lane"/>
<text x="90" y="188" class="lane-title">Autenticacao</text>
<text x="520" y="188" class="lane-title">Cliente</text>
<text x="1130" y="188" class="lane-title">Barbearia</text>
<text x="1590" y="188" class="lane-title">Dados</text>
<!-- Autenticacao -->
<rect x="165" y="230" width="180" height="60" rx="30" class="node-blue"/>
<text x="255" y="267" text-anchor="middle" class="text">Abrir app</text>
<polygon points="255,335 360,395 255,455 150,395" class="node-orange"/>
<text x="255" y="390" text-anchor="middle" class="text">Utilizador</text>
<text x="255" y="413" text-anchor="middle" class="text">autenticado?</text>
<rect x="140" y="510" width="230" height="76" rx="14" class="node"/>
<text x="255" y="540" text-anchor="middle" class="text">Login</text>
<text x="255" y="563" text-anchor="middle" class="small">ou registo</text>
<polygon points="255,640 355,700 255,760 155,700" class="node-orange"/>
<text x="255" y="695" text-anchor="middle" class="text">Tipo de</text>
<text x="255" y="718" text-anchor="middle" class="text">conta?</text>
<rect x="95" y="830" width="150" height="60" rx="14" class="node-green"/>
<text x="170" y="867" text-anchor="middle" class="text">Cliente</text>
<rect x="265" y="830" width="150" height="60" rx="14" class="node-purple"/>
<text x="340" y="867" text-anchor="middle" class="text">Barbearia</text>
<rect x="110" y="965" width="290" height="72" rx="14" class="node"/>
<text x="255" y="994" text-anchor="middle" class="text">Explorar sem conta</text>
<text x="255" y="1017" text-anchor="middle" class="small">apenas consulta publica</text>
<!-- Cliente -->
<rect x="620" y="230" width="220" height="70" rx="14" class="node-green"/>
<text x="730" y="258" text-anchor="middle" class="text">Area do cliente</text>
<text x="730" y="281" text-anchor="middle" class="small">Explorar | Carrinho | Perfil</text>
<rect x="610" y="360" width="240" height="66" rx="14" class="node"/>
<text x="730" y="399" text-anchor="middle" class="text">Explorar barbearias</text>
<rect x="610" y="485" width="240" height="66" rx="14" class="node"/>
<text x="730" y="524" text-anchor="middle" class="text">Detalhes da barbearia</text>
<polygon points="730,600 830,660 730,720 630,660" class="node-orange"/>
<text x="730" y="655" text-anchor="middle" class="text">Acao</text>
<text x="730" y="678" text-anchor="middle" class="text">escolhida?</text>
<rect x="520" y="780" width="190" height="70" rx="14" class="node-blue"/>
<text x="615" y="808" text-anchor="middle" class="text">Agendar</text>
<text x="615" y="831" text-anchor="middle" class="small">servico</text>
<rect x="520" y="900" width="190" height="92" rx="14" class="node-blue"/>
<text x="615" y="928" text-anchor="middle" class="text">Escolher</text>
<text x="615" y="951" text-anchor="middle" class="small">servico, profissional,</text>
<text x="615" y="972" text-anchor="middle" class="small">dia, hora e lembrete</text>
<rect x="735" y="900" width="220" height="92" rx="14" class="node-green"/>
<text x="845" y="936" text-anchor="middle" class="text">Confirmar</text>
<text x="845" y="959" text-anchor="middle" class="small">criar agendamento</text>
<rect x="785" y="780" width="190" height="70" rx="14" class="node-purple"/>
<text x="880" y="808" text-anchor="middle" class="text">Produto</text>
<text x="880" y="831" text-anchor="middle" class="small">adicionar ao carrinho</text>
<rect x="785" y="1030" width="190" height="70" rx="14" class="node-green"/>
<text x="880" y="1058" text-anchor="middle" class="text">Finalizar</text>
<text x="880" y="1081" text-anchor="middle" class="small">criar pedido</text>
<rect x="520" y="1030" width="190" height="70" rx="14" class="node"/>
<text x="615" y="1058" text-anchor="middle" class="text">Perfil</text>
<text x="615" y="1081" text-anchor="middle" class="small">historico e favoritos</text>
<!-- Barbearia -->
<rect x="1190" y="230" width="240" height="70" rx="14" class="node-purple"/>
<text x="1310" y="258" text-anchor="middle" class="text">Painel da barbearia</text>
<text x="1310" y="281" text-anchor="middle" class="small">gestao operacional</text>
<rect x="1165" y="360" width="290" height="70" rx="14" class="node"/>
<text x="1310" y="388" text-anchor="middle" class="text">Agenda</text>
<text x="1310" y="411" text-anchor="middle" class="small">ver marcacoes por data</text>
<rect x="1165" y="480" width="290" height="70" rx="14" class="node-green"/>
<text x="1310" y="508" text-anchor="middle" class="text">Atualizar estado</text>
<text x="1310" y="531" text-anchor="middle" class="small">pendente, confirmado, concluido</text>
<rect x="1165" y="630" width="290" height="76" rx="14" class="node-blue"/>
<text x="1310" y="660" text-anchor="middle" class="text">Servicos</text>
<text x="1310" y="683" text-anchor="middle" class="small">criar, editar e apagar</text>
<rect x="1165" y="750" width="290" height="76" rx="14" class="node-blue"/>
<text x="1310" y="780" text-anchor="middle" class="text">Produtos</text>
<text x="1310" y="803" text-anchor="middle" class="small">preco e stock</text>
<rect x="1165" y="870" width="290" height="76" rx="14" class="node-blue"/>
<text x="1310" y="900" text-anchor="middle" class="text">Equipa</text>
<text x="1310" y="923" text-anchor="middle" class="small">barbeiros e especialidades</text>
<rect x="1165" y="990" width="290" height="76" rx="14" class="node-blue"/>
<text x="1310" y="1020" text-anchor="middle" class="text">Definicoes</text>
<text x="1310" y="1043" text-anchor="middle" class="small">dados, contactos e horarios</text>
<!-- Dados -->
<path d="M 1605 365 C 1605 340 1695 340 1695 365 L 1695 440 C 1695 465 1605 465 1605 440 Z" class="store"/>
<ellipse cx="1650" cy="365" rx="45" ry="18" class="store"/>
<text x="1650" y="405" text-anchor="middle" class="text">Supabase</text>
<text x="1650" y="428" text-anchor="middle" class="small">Auth + DB</text>
<path d="M 1605 680 C 1605 655 1695 655 1695 680 L 1695 755 C 1695 780 1605 780 1605 755 Z" class="store"/>
<ellipse cx="1650" cy="680" rx="45" ry="18" class="store"/>
<text x="1650" y="720" text-anchor="middle" class="text">Local</text>
<text x="1650" y="743" text-anchor="middle" class="small">carrinho</text>
<path d="M 1605 900 C 1605 875 1695 875 1695 900 L 1695 975 C 1695 1000 1605 1000 1605 975 Z" class="store"/>
<ellipse cx="1650" cy="900" rx="45" ry="18" class="store"/>
<text x="1650" y="940" text-anchor="middle" class="text">Storage</text>
<text x="1650" y="963" text-anchor="middle" class="small">imagens</text>
<!-- Arrows authentication -->
<path d="M 255 290 L 255 335" class="arrow"/>
<path d="M 255 455 L 255 510" class="arrow"/>
<text x="275" y="488" class="label">Nao</text>
<path d="M 255 586 L 255 640" class="arrow"/>
<path d="M 255 760 L 185 830" class="arrow"/>
<path d="M 255 760 L 325 830" class="arrow"/>
<path d="M 360 395 C 480 395 480 265 620 265" class="arrow"/>
<text x="382" y="382" class="label">Sim</text>
<path d="M 140 548 C 80 548 80 1000 110 1000" class="dash"/>
<text x="80" y="792" class="label">modo visitante</text>
<!-- Arrows client -->
<path d="M 245 860 C 430 860 430 265 620 265" class="arrow"/>
<path d="M 730 300 L 730 360" class="arrow"/>
<path d="M 730 426 L 730 485" class="arrow"/>
<path d="M 730 551 L 730 600" class="arrow"/>
<path d="M 110 1000 C 470 1000 470 393 610 393" class="arrow-soft"/>
<path d="M 670 708 L 615 780" class="arrow"/>
<text x="584" y="747" class="label">agendar</text>
<path d="M 615 850 L 615 900" class="arrow"/>
<path d="M 710 946 L 735 946" class="arrow"/>
<path d="M 790 708 L 880 780" class="arrow"/>
<text x="838" y="747" class="label">produto</text>
<path d="M 880 850 L 880 1030" class="arrow"/>
<path d="M 705 687 C 545 735 525 970 615 1030" class="arrow-soft"/>
<text x="535" y="772" class="label">perfil</text>
<!-- Arrows barber -->
<path d="M 415 860 C 790 860 790 265 1190 265" class="arrow"/>
<path d="M 1310 300 L 1310 360" class="arrow"/>
<path d="M 1310 430 L 1310 480" class="arrow"/>
<path d="M 1310 300 C 1500 330 1500 668 1455 668" class="arrow-soft"/>
<path d="M 1310 300 C 1500 340 1500 788 1455 788" class="arrow-soft"/>
<path d="M 1310 300 C 1500 350 1500 908 1455 908" class="arrow-soft"/>
<path d="M 1310 300 C 1500 360 1500 1028 1455 1028" class="arrow-soft"/>
<!-- Arrows data -->
<path d="M 370 548 C 970 548 970 402 1605 402" class="arrow-soft"/>
<path d="M 955 946 C 1240 946 1240 402 1605 402" class="arrow-soft"/>
<path d="M 975 1065 C 1270 1065 1270 418 1605 418" class="arrow-soft"/>
<path d="M 1455 515 C 1525 515 1525 430 1605 430" class="arrow-soft"/>
<path d="M 1455 668 C 1530 668 1530 445 1605 445" class="arrow-soft"/>
<path d="M 1455 788 C 1530 788 1530 445 1605 445" class="arrow-soft"/>
<path d="M 1455 908 C 1530 908 1530 445 1605 445" class="arrow-soft"/>
<path d="M 1455 1028 C 1535 1028 1535 955 1605 955" class="arrow-soft"/>
<path d="M 880 850 C 1110 850 1110 720 1605 720" class="arrow-soft"/>
<rect x="80" y="1160" width="1660" height="55" rx="12" fill="#ffffff" stroke="#e2e8f0"/>
<text x="105" y="1195" class="small">Legenda: setas continuas indicam navegacao/acesso; setas cinzentas indicam persistencia ou sincronizacao de dados.</text>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

318
main.tex Normal file
View File

@@ -0,0 +1,318 @@
\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[portuguese]{babel}
\usepackage{lmodern}
\usepackage{geometry}
\usepackage{setspace}
\usepackage{hyperref}
\usepackage{graphicx}
\usepackage{array}
\usepackage{longtable}
\usepackage{enumitem}
\usepackage{fancyhdr}
\geometry{
left=3cm,
right=2.5cm,
top=2.5cm,
bottom=2.5cm
}
\onehalfspacing
\setlength{\parindent}{1.25cm}
\setlength{\parskip}{0.2cm}
\hypersetup{
colorlinks=true,
linkcolor=black,
urlcolor=blue,
pdftitle={PAP - Smart Agenda},
pdfauthor={Rodrigo Santos}
}
\newcommand{\appname}{Smart Agenda}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[R]{\small \appname}
\fancyfoot[R]{\thepage}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0pt}
\begin{document}
% Capa
\begin{titlepage}
\thispagestyle{empty}
\centering
\vspace*{2cm}
{\Large \textbf{Prova de Aptidão Profissional}\par}
\vspace{1.5cm}
{\Huge \textbf{\appname}\par}
\vspace{0.8cm}
{\Large Aplicação para Gestão de Agendamentos em Barbearias\par}
\vspace{2.5cm}
\begin{flushleft}
\large
\textbf{Aluno:} Rodrigo Santos\\[0.35cm]
\textbf{Curso/Turma:} \underline{\hspace{7cm}}\\[0.35cm]
\textbf{Escola:} \underline{\hspace{8cm}}\\[0.35cm]
\textbf{Ano letivo:} 2025/2026\\[0.35cm]
\textbf{Professor orientador:} Professor João
\end{flushleft}
\vfill
{\large \today\par}
\end{titlepage}
\pagenumbering{roman}
\pagestyle{fancy}
% Agradecimentos
\section*{Agradecimentos}
Gostaria de agradecer, em primeiro lugar, aos professores em geral, por todo o apoio, acompanhamento e conhecimentos transmitidos ao longo do meu percurso escolar. O contributo de cada professor foi importante para que eu pudesse desenvolver as competências necessárias para realizar este projeto e ultrapassar as dificuldades encontradas durante o seu desenvolvimento.
Agradeço especialmente ao Professor João, pela orientação, ajuda e disponibilidade ao longo da realização da minha Prova de Aptidão Profissional. O seu acompanhamento foi essencial para manter o projeto organizado, melhorar o trabalho desenvolvido e encontrar soluções para os problemas que foram surgindo.
Quero também agradecer à Professora Helena, pelo apoio, acompanhamento e disponibilidade demonstrados durante este percurso. A sua ajuda foi importante para a concretização deste trabalho e para a minha evolução enquanto aluno.
Agradeço ainda à Diretora Beatriz, pelo incentivo, apoio e acompanhamento durante o meu percurso escolar. O seu contributo foi importante para criar um ambiente de responsabilidade, motivação e dedicação.
Aos meus colegas, deixo também o meu agradecimento pela colaboração, troca de ideias, ajuda e espírito de entreajuda. Ao longo do desenvolvimento do projeto, o apoio dos colegas foi importante para esclarecer dúvidas, partilhar opiniões e manter a motivação.
Por fim, agradeço à minha família, pelo apoio, paciência, motivação e confiança ao longo deste trabalho. A sua presença foi fundamental para continuar a esforçar-me e concluir este projeto da melhor forma possível.
\newpage
% Glossário
\section*{Glossário}
\textbf{API:} Interface de Programação de Aplicações. Permite que diferentes sistemas comuniquem entre si.
\textbf{App:} Abreviatura de aplicação. Neste relatório, refere-se à aplicação móvel \appname.
\textbf{AsyncStorage:} Sistema de armazenamento local usado em aplicações React Native para guardar pequenas informações no dispositivo.
\textbf{Autenticação:} Processo que permite verificar a identidade de um utilizador através de dados como email e palavra-passe.
\textbf{Base de Dados:} Sistema usado para guardar e organizar informação. No projeto, é utilizada para armazenar utilizadores, barbearias, serviços, produtos, marcações e pedidos.
\textbf{CRUD:} Sigla para Create, Read, Update e Delete. Representa as operações de criar, ler, atualizar e apagar dados.
\textbf{Dashboard:} Painel de gestão onde a barbearia pode consultar e administrar informação importante do negócio.
\textbf{Expo:} Plataforma usada para facilitar o desenvolvimento, teste e execução de aplicações React Native.
\textbf{React Native:} Tecnologia utilizada para desenvolver aplicações móveis com JavaScript e React.
\textbf{Supabase:} Plataforma usada no projeto para autenticação, base de dados e armazenamento de imagens.
\textbf{TypeScript:} Linguagem baseada em JavaScript que permite usar tipos, tornando o código mais organizado e seguro.
\newpage
% Índice
\renewcommand{\contentsname}{Conteúdo}
\tableofcontents
\newpage
\pagenumbering{arabic}
\section{Introdução}
A presente Prova de Aptidão Profissional tem como tema o desenvolvimento da aplicação \appname, uma solução digital criada para facilitar a gestão de agendamentos em barbearias. O projeto foi desenvolvido por Rodrigo Santos e tem como principal objetivo responder a uma necessidade comum em muitos estabelecimentos: organizar marcações, serviços, barbeiros, produtos, horários e pedidos de uma forma mais simples, rápida e eficiente.
Atualmente, muitas barbearias ainda recorrem a métodos manuais para gerir as suas marcações, como chamadas telefónicas, mensagens, redes sociais ou agendas em papel. Apesar de estes métodos continuarem a ser usados, podem causar vários problemas, como esquecimentos, marcações sobrepostas, dificuldade em consultar horários disponíveis e perda de tempo tanto para os clientes como para os profissionais.
O \appname{} surge como uma solução para modernizar este processo. A aplicação permite que os clientes pesquisem barbearias, consultem serviços, escolham barbeiros, selecionem datas e horários, façam marcações e acompanhem os seus pedidos. Ao mesmo tempo, permite que as barbearias tenham acesso a uma área de gestão onde podem controlar a agenda, os serviços, os produtos, a equipa, os contactos, os horários e outras informações importantes do estabelecimento.
Este projeto permitiu aplicar conhecimentos adquiridos ao longo do curso, nomeadamente na área da programação, desenvolvimento mobile, bases de dados, autenticação, navegação entre ecrãs, gestão de estado e criação de interfaces. Para além da componente técnica, o projeto também exigiu organização, planeamento, pesquisa e capacidade de resolver problemas.
\subsection{Planeamento do Projeto}
O planeamento do projeto foi uma etapa essencial para definir o funcionamento da aplicação e organizar o desenvolvimento. Antes da implementação, foi necessário identificar o problema a resolver, pensar nos utilizadores da aplicação e definir as funcionalidades mais importantes para cada tipo de conta.
Inicialmente, foi definido que a aplicação teria dois tipos principais de utilizadores: cliente e barbearia. O cliente teria acesso à pesquisa de barbearias, consulta de serviços, criação de marcações, carrinho de produtos e perfil pessoal. A barbearia teria acesso a uma área de gestão, onde poderia consultar marcações, gerir serviços, produtos, barbeiros, horários, contactos e dados do estabelecimento.
Depois desta definição, o trabalho foi dividido em várias fases. A primeira fase consistiu na criação da estrutura do projeto em React Native com Expo e TypeScript. De seguida, foi criada a navegação entre ecrãs, separando a área de autenticação, a área do cliente e a área da barbearia. Posteriormente, foi feita a integração com o Supabase, responsável pela autenticação, base de dados e armazenamento de imagens.
Durante o planeamento, também foi necessário pensar na organização dos dados. Foram identificadas entidades como utilizadores, perfis, barbearias, serviços, barbeiros, produtos, marcações, pedidos, avaliações, notificações e lista de espera. Esta estrutura permitiu desenvolver uma aplicação mais completa e próxima de uma solução real.
\subsection{Apresentação da Empresa (opcional)}
No contexto deste projeto, não existiu uma empresa externa responsável pelo desenvolvimento da aplicação. O \appname{} foi desenvolvido como projeto individual no âmbito da Prova de Aptidão Profissional, tendo como objetivo simular uma solução útil para barbearias e clientes.
Apesar de não estar associado a uma empresa real, o projeto foi pensado como se pudesse ser aplicado num contexto profissional. A aplicação foi idealizada para pequenos negócios, especialmente barbearias, que necessitam de ferramentas simples e acessíveis para organizar o seu dia a dia. Desta forma, o \appname{} funciona como uma proposta de produto digital orientado para a gestão de serviços locais.
A solução procura responder a necessidades reais, como a marcação de serviços, organização da agenda, gestão de profissionais, controlo de produtos e melhoria da comunicação com os clientes. Assim, mesmo não existindo uma empresa envolvida, o projeto aproxima-se de um cenário de trabalho real, onde é necessário desenvolver uma aplicação funcional, organizada e útil.
\section{Contexto}
O \appname{} enquadra-se na área das aplicações móveis e da digitalização de serviços. Cada vez mais os clientes procuram soluções rápidas e acessíveis através do telemóvel, evitando deslocações ou contactos demorados. Ao mesmo tempo, os pequenos negócios precisam de ferramentas que lhes permitam organizar melhor o seu funcionamento e melhorar o atendimento.
As barbearias são um exemplo de negócio onde a gestão de horários é muito importante. Um erro numa marcação pode causar atrasos, insatisfação dos clientes ou perda de tempo para os profissionais. Quando existem vários barbeiros, diferentes serviços e produtos para venda, a organização torna-se ainda mais importante.
Neste contexto, o \appname{} pretende centralizar numa única aplicação as principais tarefas relacionadas com a marcação e gestão de serviços de barbearia. A aplicação foi pensada para ser usada tanto por clientes como por barbearias, criando uma ligação direta entre quem procura o serviço e quem o presta.
\subsection{Caraterização da Empresa (opcional)}
Como o projeto não foi desenvolvido dentro de uma empresa específica, esta secção é adaptada à caraterização do produto criado. O \appname{} pode ser entendido como uma solução digital direcionada para barbearias, funcionando como uma plataforma de apoio à gestão do negócio.
A aplicação foi pensada para estabelecimentos que prestam serviços como cortes de cabelo, barba, lavagem, tratamentos e outros serviços relacionados. Para além disso, também permite a venda de produtos, como artigos de cuidado capilar ou produtos de barba, através de um carrinho integrado.
O público-alvo da aplicação divide-se em dois grupos. O primeiro grupo é composto pelos clientes, que procuram marcar serviços de forma rápida. O segundo grupo é composto pelas barbearias, que precisam de gerir marcações, equipa, serviços, produtos e informações do estabelecimento.
\subsubsection{Caraterização do Polo da Empresa}
No âmbito deste projeto, o ``polo'' pode ser interpretado como o ambiente onde a aplicação seria utilizada. Esse ambiente corresponde ao funcionamento diário de uma barbearia, onde existem clientes, profissionais, horários, serviços e produtos.
Numa barbearia, é necessário saber que serviços estão disponíveis, quais os barbeiros que os realizam, que horários existem, que marcações estão pendentes ou confirmadas e que produtos se encontram em stock. A aplicação procura organizar estes elementos de forma simples, permitindo que a barbearia tenha maior controlo sobre a sua atividade.
O \appname{} foi desenvolvido para funcionar como uma ferramenta de apoio neste contexto. Através do painel de gestão, a barbearia pode consultar a agenda, alterar estados de marcações, criar serviços, adicionar produtos, gerir barbeiros e atualizar os seus dados. Para o cliente, a aplicação disponibiliza uma experiência mais direta, centrada na pesquisa e marcação.
\subsection{Perfil de Desempenho do Técnico de Informática de Gestão}
O desenvolvimento do \appname{} está relacionado com várias competências do perfil de um Técnico de Informática de Gestão. Este perfil exige capacidade para analisar problemas, planear soluções, desenvolver aplicações, trabalhar com bases de dados, criar interfaces e garantir que os sistemas respondem às necessidades dos utilizadores.
Durante o projeto, foi necessário aplicar conhecimentos de programação, nomeadamente através de React Native, Expo e TypeScript. Também foi necessário compreender a organização de uma aplicação por componentes, páginas, contexto global, navegação e tipos de dados. Esta organização é importante para garantir que o projeto seja mais fácil de manter e evoluir.
Outra competência importante foi a integração com uma base de dados. O projeto utiliza Supabase para autenticação, armazenamento e gestão de dados. Esta integração exigiu compreender como criar, consultar, atualizar e apagar informações relacionadas com perfis, barbearias, serviços, barbeiros, produtos, marcações, pedidos, notificações e avaliações.
O projeto também exigiu competências de análise e resolução de problemas. Ao longo do desenvolvimento surgiram dificuldades relacionadas com a navegação, autenticação, atualização de dados, gestão do estado da aplicação e organização da interface. Resolver estes problemas contribuiu para desenvolver autonomia, responsabilidade e capacidade de pesquisa.
\subsection{Objetivos da Formação em Contexto de Trabalho}
Apesar de o projeto estar associado à Prova de Aptidão Profissional, os seus objetivos relacionam-se com a aplicação prática de conhecimentos em contexto de trabalho. O desenvolvimento do \appname{} permitiu simular um processo real de criação de uma aplicação, desde a identificação do problema até à implementação da solução.
Um dos principais objetivos foi desenvolver uma aplicação funcional que respondesse a uma necessidade concreta. Para isso, foi necessário pensar no problema da gestão manual de marcações e criar uma solução que facilitasse tanto o trabalho das barbearias como a experiência dos clientes.
Outro objetivo foi consolidar conhecimentos técnicos. O projeto permitiu trabalhar com desenvolvimento mobile, navegação, autenticação, bases de dados, armazenamento, notificações, gestão de estado e design de interface. Estes conhecimentos são importantes para um Técnico de Informática de Gestão, pois estão relacionados com o desenvolvimento e manutenção de soluções digitais.
Também foi objetivo desenvolver competências pessoais e profissionais, como organização, planeamento, autonomia, persistência e capacidade de resolver problemas. Estas competências são fundamentais em qualquer contexto de trabalho, especialmente na área da informática, onde é frequente encontrar desafios técnicos que exigem pesquisa e adaptação.
\subsection{Funções Exercidas na Empresa (opcional)}
Como não existiu uma empresa externa associada ao projeto, as funções exercidas correspondem às tarefas realizadas durante o desenvolvimento da aplicação. Ao longo do projeto, assumi funções semelhantes às de um programador e analista de software.
Entre as principais funções realizadas estiveram a análise do problema, definição dos requisitos, planeamento das funcionalidades, criação da estrutura do projeto, desenvolvimento dos ecrãs, implementação da navegação, integração com Supabase, criação das funcionalidades de cliente e barbearia, testes e correção de erros.
Também foram realizadas tarefas relacionadas com a experiência do utilizador, como a organização dos ecrãs, criação de botões, formulários, cartões, filtros e separadores. O objetivo foi garantir que a aplicação fosse simples de usar, tanto para clientes como para barbearias.
Estas funções permitiram compreender melhor o processo de desenvolvimento de uma aplicação. Foi necessário pensar não só no código, mas também na utilidade da solução, na organização dos dados e na forma como o utilizador interage com a aplicação.
\section{Descrição Técnica}
O \appname{} foi desenvolvido com React Native, Expo e TypeScript. A escolha destas tecnologias permitiu criar uma aplicação mobile organizada e preparada para funcionar em diferentes plataformas. O Expo facilitou o processo de desenvolvimento e teste, enquanto o TypeScript ajudou a tornar o código mais seguro através da definição de tipos.
A aplicação está organizada em várias pastas. A pasta \texttt{components} contém elementos reutilizáveis da interface, como botões, cartões, campos de texto e badges. A pasta \texttt{pages} contém os principais ecrãs da aplicação, como login, registo, exploração, detalhes da barbearia, marcação, carrinho, perfil e dashboard. A pasta \texttt{context} contém o \texttt{AppContext}, responsável pela gestão global dos dados. A pasta \texttt{navigation} contém a configuração das rotas e separadores da aplicação. A pasta \texttt{lib} contém funções auxiliares e a configuração do Supabase. O ficheiro \texttt{types.ts} define os principais tipos de dados usados no projeto.
O estado global da aplicação é gerido através do \texttt{AppContext}. Este contexto guarda informação como utilizador autenticado, barbearias, carrinho, favoritos, marcações, pedidos, lista de espera e notificações. Também disponibiliza funções para login, registo, logout, criação de marcações, gestão de pedidos, serviços, produtos, barbeiros, avaliações e notificações.
O Supabase é utilizado como base de dados, autenticação e armazenamento. A aplicação consulta tabelas como \texttt{profiles}, \texttt{shops}, \texttt{services}, \texttt{barbers}, \texttt{products}, \texttt{appointments}, \texttt{orders}, \texttt{reviews}, \texttt{notifications} e \texttt{waitlist}. Esta estrutura permite guardar os dados de forma centralizada e atualizar a informação apresentada na aplicação.
\subsection{Manual de Funcionamento}
Para utilizar o \appname, o utilizador deve começar por criar uma conta ou iniciar sessão. Durante o registo, pode escolher entre uma conta de cliente ou uma conta de barbearia. Esta escolha define a área da aplicação que será apresentada após o login.
Se o utilizador for cliente, tem acesso à área de exploração. Nesta área pode pesquisar barbearias por nome, morada ou serviço. Também pode utilizar filtros para encontrar barbearias com melhor avaliação, mais serviços ou categorias específicas. Ao selecionar uma barbearia, o cliente pode consultar os seus detalhes, incluindo serviços, barbeiros, produtos, contactos, horários e avaliação.
Para efetuar uma marcação, o cliente escolhe uma barbearia, seleciona um serviço, escolhe um barbeiro, define a data e o horário pretendido e confirma a marcação. A marcação fica registada com o estado inicial pendente. Posteriormente, a barbearia pode confirmar, concluir ou cancelar essa marcação.
O cliente também pode adicionar produtos ao carrinho. Depois de escolher os produtos, pode finalizar o pedido, que fica associado à barbearia correspondente. Além disso, pode consultar o perfil, favoritos, histórico de marcações, pedidos e notificações.
Se o utilizador for barbearia, tem acesso ao dashboard. Neste painel, pode consultar as marcações por data, alterar o estado das marcações, gerir serviços, gerir produtos, gerir barbeiros e editar as informações do estabelecimento. A barbearia pode também definir contactos, redes sociais, métodos de pagamento, horários e imagens.
O funcionamento da aplicação foi pensado para ser simples. O cliente encontra rapidamente o serviço que pretende e a barbearia consegue gerir as principais informações do negócio através de uma interface organizada por separadores.
\subsection{Implementação do Projeto}
A implementação do projeto começou pela criação da estrutura base da aplicação em React Native com Expo. De seguida, foram criados os ecrãs principais e os componentes reutilizáveis. A navegação foi implementada com React Navigation, utilizando uma estrutura diferente para clientes e barbearias.
Na área de autenticação, foi implementado o registo e login através do Supabase. Depois do login, a aplicação consulta o perfil do utilizador e identifica o seu papel. Se o utilizador for cliente, é encaminhado para os separadores de explorar, carrinho e perfil. Se for barbearia, é encaminhado para o painel de gestão.
Na área do cliente, foram implementadas funcionalidades de pesquisa, filtros, consulta de detalhes da barbearia, criação de marcações, carrinho de produtos, favoritos, perfil e notificações. Estas funcionalidades comunicam com o estado global e com a base de dados para apresentar e guardar a informação correta.
Na área da barbearia, foi implementado o dashboard com separadores para agenda, serviços, produtos, equipa e definições. A barbearia pode criar e apagar serviços, adicionar produtos, controlar stock, gerir barbeiros, alterar estados de marcações e atualizar dados do estabelecimento.
Também foram implementadas funcionalidades adicionais, como avaliações, lista de espera e notificações. Quando uma marcação é cancelada, a aplicação pode avisar utilizadores que estejam na lista de espera para aquele barbeiro e data. O projeto inclui ainda funções Supabase preparadas para envio de notificações push e lembretes de agendamentos.
Durante a implementação, uma das partes mais importantes foi garantir que os dados fossem atualizados corretamente depois de cada ação. Para isso, a aplicação utiliza funções de atualização que recarregam as informações do Supabase e mantêm o estado global sincronizado.
\section{Conclusões}
O desenvolvimento do \appname{} permitiu criar uma aplicação funcional para a gestão de agendamentos em barbearias. O projeto cumpriu o objetivo principal de apresentar uma solução digital capaz de ajudar clientes a fazer marcações e barbearias a gerir o seu funcionamento diário.
A aplicação permite pesquisar barbearias, consultar detalhes, escolher serviços, selecionar barbeiros, criar marcações, adicionar produtos ao carrinho, gerir pedidos, guardar favoritos e consultar notificações. Para as barbearias, permite gerir agenda, serviços, produtos, barbeiros, horários, contactos, redes sociais, métodos de pagamento e imagens.
Do ponto de vista técnico, o projeto foi importante para consolidar conhecimentos de desenvolvimento mobile, React Native, Expo, TypeScript, React Navigation, Supabase, autenticação, base de dados, armazenamento e gestão de estado. A utilização destas tecnologias tornou o projeto mais completo e aproximou-o de uma solução real.
Conclui-se que o \appname{} é uma aplicação útil, organizada e com potencial de evolução. Para além de responder a uma necessidade real, o projeto contribuiu para o meu desenvolvimento académico e pessoal, permitindo-me aplicar conhecimentos, enfrentar dificuldades e melhorar as minhas capacidades técnicas.
\subsection{Limitações do Trabalho}
Apesar de o \appname{} apresentar várias funcionalidades, existem algumas limitações que devem ser consideradas. Uma das limitações é o facto de a aplicação ainda não incluir pagamentos online. Esta funcionalidade poderia ser útil para permitir pagamentos antecipados, reservas com sinal ou compra direta de produtos.
Outra limitação está relacionada com as notificações. Embora o projeto tenha suporte para notificações e lembretes, esta área poderia ser melhorada com avisos mais completos, como confirmações automáticas, lembretes por email ou SMS e mensagens personalizadas para os clientes.
A aplicação também poderia ter estatísticas mais avançadas para as barbearias. No futuro, seria útil apresentar dados sobre faturação, serviços mais pedidos, produtos mais vendidos, horários com maior procura e desempenho dos barbeiros.
Outra limitação é a ausência de publicação oficial nas lojas de aplicações. Para que o \appname{} pudesse ser usado por um público real, seria necessário preparar melhor a aplicação, reforçar a segurança, realizar mais testes e publicar na Play Store e na App Store.
Apesar destas limitações, o projeto cumpre os seus objetivos principais e apresenta uma base sólida para futuras melhorias.
\subsection{Apreciação Final}
O desenvolvimento do \appname{} foi uma experiência muito importante e enriquecedora para mim. Ao longo deste projeto, tive a oportunidade de aplicar vários conhecimentos adquiridos durante o meu percurso académico e compreender melhor como se desenvolve uma aplicação funcional.
Durante o trabalho, percebi que criar uma aplicação não consiste apenas em escrever código. É necessário planear, organizar ideias, pensar nos utilizadores, estruturar os ecrãs, definir funcionalidades, ligar a aplicação a uma base de dados e testar o funcionamento. Cada uma destas etapas foi importante para chegar ao resultado final.
Encontrei algumas dificuldades, principalmente na integração com o Supabase, na gestão do estado global da aplicação, na separação entre cliente e barbearia e na organização da interface. No entanto, essas dificuldades ajudaram-me a evoluir, a pesquisar soluções e a melhorar a minha autonomia.
Considero que este projeto foi importante para o meu crescimento pessoal e académico. Ajudou-me a desenvolver responsabilidade, persistência, organização e capacidade de resolução de problemas. Também me permitiu perceber a importância de criar soluções úteis e adaptadas a necessidades reais.
Fico satisfeito com o resultado final, porque consegui desenvolver uma aplicação com funcionalidades reais e com potencial para ser melhorada no futuro. O \appname{} pode ajudar clientes a fazer marcações de forma mais simples e pode ajudar barbearias a gerir melhor o seu dia a dia. No futuro, gostaria de ver a aplicação evoluir com pagamentos online, notificações mais avançadas, estatísticas completas e publicação nas lojas de aplicações.
\newpage
\section*{Webgrafia}
Para o desenvolvimento do projeto e para a realização deste relatório, foram consultadas fontes relacionadas com as tecnologias utilizadas:
\begin{itemize}
\item Documentação oficial do React Native: \url{https://reactnative.dev/}
\item Documentação oficial do Expo: \url{https://docs.expo.dev/}
\item Documentação oficial do TypeScript: \url{https://www.typescriptlang.org/docs/}
\item Documentação oficial do React Navigation: \url{https://reactnavigation.org/docs/getting-started}
\item Documentação oficial do Supabase: \url{https://supabase.com/docs}
\item Documentação oficial do AsyncStorage: \url{https://react-native-async-storage.github.io/async-storage/}
\end{itemize}
\newpage
\section*{Anexos}
Nesta secção podem ser inseridas imagens da aplicação, capturas de ecrã dos principais menus, esquemas da base de dados ou outros elementos de apoio à apresentação do projeto.
Como anexos recomendados para o \appname, podem ser incluídos:
\begin{itemize}
\item Ecrã de login e registo.
\item Ecrã de exploração de barbearias.
\item Página de detalhes da barbearia.
\item Ecrã de marcação de serviço.
\item Carrinho de produtos.
\item Perfil do cliente.
\item Dashboard da barbearia.
\item Gestão de serviços, produtos e barbeiros.
\item Estrutura da base de dados no Supabase.
\end{itemize}
\end{document}

View File

@@ -371,6 +371,34 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
};
const addToCart = (item: CartItem) => {
if (item.type === 'product') {
const shop = shops.find((shp) => shp.id === item.shopId);
const prod = shop?.products.find((p) => p.id === item.refId);
if (prod) {
const maxStock = prod.stock;
const existing = cart.find((i) => i.shopId === item.shopId && i.type === 'product' && i.refId === item.refId);
const currentQty = existing ? existing.qty : 0;
if (currentQty >= maxStock) {
Alert.alert('Limite de Stock', `Não é possível adicionar mais unidades. Apenas ${maxStock} unidades em stock.`);
return;
}
if (currentQty + item.qty > maxStock) {
const allowedQty = maxStock - currentQty;
setCart((prev: CartItem[]) => {
const next = [...prev];
const existingItem = next.find((i) => i.shopId === item.shopId && i.type === item.type && i.refId === item.refId);
if (existingItem) existingItem.qty = maxStock;
else next.push({ ...item, qty: allowedQty });
return next;
});
Alert.alert('Limite de Stock', `Adicionado apenas ${allowedQty} unidades. Limite de stock atingido.`);
return;
}
}
}
setCart((prev: CartItem[]) => {
const next = [...prev];
const existing = next.find((i) => i.shopId === item.shopId && i.type === item.type && i.refId === item.refId);

View File

@@ -244,7 +244,7 @@ export default function ShopDetails() {
{tab === 'produtos' && (
<View style={styles.grid}>
{shop.products.length > 0 ? (
shop.products.map((p) => (
[...shop.products].sort((a, b) => b.price - a.price).map((p) => (
<Card key={p.id} style={styles.productCard}>
<View style={styles.prodHeader}>
<Text style={styles.prodName} numberOfLines={1}>{p.name}</Text>

30
test_date.mjs Normal file
View File

@@ -0,0 +1,30 @@
const date = '2026-06-11';
const slots = ['09:00', '10:00', '11:00', '12:00'];
const bookedSlots = [];
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
const isToday = date === todayStr;
const currentHour = today.getHours();
const currentMinute = today.getMinutes();
console.log("todayStr:", todayStr);
console.log("date:", date);
console.log("isToday:", isToday);
console.log("currentHour:", currentHour);
console.log("currentMinute:", currentMinute);
const processedSlots = slots.map(time => {
let isPast = false;
if (isToday) {
const [h, m] = time.split(':').map(Number);
if (h < currentHour || (h === currentHour && m <= currentMinute)) {
isPast = true;
}
}
const isBooked = bookedSlots.includes(time) || isPast;
return { time, isBooked };
});
console.log(processedSlots);

24
test_supabase.js Normal file
View File

@@ -0,0 +1,24 @@
import { createClient } from '@supabase/supabase-js';
import dotenv from 'dotenv';
dotenv.config({ path: './web/.env' });
const supabaseUrl = process.env.VITE_SUPABASE_URL;
const supabaseKey = process.env.VITE_SUPABASE_ANON_KEY;
if (!supabaseUrl || !supabaseKey) {
console.log("Missing Supabase credentials in .env");
process.exit(1);
}
const supabase = createClient(supabaseUrl, supabaseKey);
async function test() {
console.log("Checking notifications table...");
const { data, error } = await supabase.from('notifications').select('*').limit(1);
if (error) {
console.error("Error accessing notifications table:", error.message);
} else {
console.log("Notifications table exists!");
}
}
test();

View File

@@ -0,0 +1,26 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://jqklhhpyykzrktikjnmb.supabase.co'
const supabaseAnonKey =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Impxa2xoaHB5eWt6cmt0aWtqbm1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzODQ0MDgsImV4cCI6MjA4Mzk2MDQwOH0.QsPuBnyUtRPSavlqKj3IGR9c8juT02LY_hSi-j3c6M0'
const supabase = createClient(supabaseUrl, supabaseAnonKey)
async function test() {
console.log('Testing notifications table insert...')
const { data, error } = await supabase.from('notifications').insert([
{
user_id: '00000000-0000-0000-0000-000000000000',
message: 'test message'
}
]).select()
if (error) {
console.error('Insert error:', error)
} else {
console.log('Insert success:', data)
}
}
test()

19
web/check_profiles.js Normal file
View File

@@ -0,0 +1,19 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = 'https://jqklhhpyykzrktikjnmb.supabase.co';
const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Impxa2xoaHB5eWt6cmt0aWtqbm1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzODQ0MDgsImV4cCI6MjA4Mzk2MDQwOH0.QsPuBnyUtRPSavlqKj3IGR9c8juT02LY_hSi-j3c6M0';
const supabase = createClient(supabaseUrl, supabaseAnonKey);
async function check() {
const { data: testData, error: testError } = await supabase.from('profiles').select('*').limit(1);
if (testError) {
console.log("SELECT ERROR:", testError.message);
} else {
console.log("COLUMNS EXIST IN PROFILES:", testData && testData.length > 0 ? Object.keys(testData[0]) : "No rows");
console.log("SAMPLE ROW:", testData);
}
process.exit(0);
}
check();

View File

@@ -13,7 +13,7 @@ export const ProductList = ({
onAdd?: (id: string) => void;
}) => (
<div className="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-6">
{products.map((p) => {
{[...products].sort((a, b) => b.price - a.price).map((p) => {
const lowStock = p.stock <= 3;
return (
<Card key={p.id} className="relative overflow-hidden border-none glass-card rounded-2xl sm:rounded-[2rem] group hover:shadow-2xl transition-all duration-300 flex flex-col">

View File

@@ -500,6 +500,34 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
};
const addToCart: AppContextValue['addToCart'] = (item) => {
if (item.type === 'product') {
const shop = state.shops.find((shp) => shp.id === item.shopId);
const prod = shop?.products.find((p) => p.id === item.refId);
if (prod) {
const maxStock = prod.stock;
const existingItem = state.cart.find((c) => c.refId === item.refId && c.type === 'product' && c.shopId === item.shopId);
const currentQty = existingItem ? existingItem.qty : 0;
if (currentQty >= maxStock) {
addToast(`Não é possível adicionar mais unidades. Apenas ${maxStock} unidades em stock.`, 'info');
return;
}
if (currentQty + item.qty > maxStock) {
const allowedQty = maxStock - currentQty;
setState((s) => {
const cart = [...s.cart];
const idx = cart.findIndex((c) => c.refId === item.refId && c.type === item.type && c.shopId === item.shopId);
if (idx >= 0) cart[idx].qty = maxStock;
else cart.push({ ...item, qty: allowedQty });
return { ...s, cart };
});
addToast(`Adicionado apenas ${allowedQty} unidades. Limite de stock atingido.`, 'info');
return;
}
}
}
setState((s) => {
const cart = [...s.cart];
const idx = cart.findIndex((c) => c.refId === item.refId && c.type === item.type && c.shopId === item.shopId);

View File

@@ -141,8 +141,8 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
const [svcDuration, setSvcDuration] = useState<number>(30);
const [prodName, setProdName] = useState('');
const [prodPrice, setProdPrice] = useState<number>(30);
const [prodStock, setProdStock] = useState<number>(10);
const [prodPrice, setProdPrice] = useState<string>('30');
const [prodStock, setProdStock] = useState<string>('10');
const [barberName, setBarberName] = useState('');
const [barberSpecs, setBarberSpecs] = useState('');
@@ -296,10 +296,20 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
const addNewProduct = () => {
if (!prodName.trim()) return;
addProduct(shop.id, { name: prodName, price: Number(prodPrice) || 0, stock: Number(prodStock) || 0 });
const priceNum = parseFloat(prodPrice);
const stockNum = parseInt(prodStock, 10);
if (isNaN(priceNum) || priceNum < 0) {
alert('O preço deve ser um número positivo.');
return;
}
if (isNaN(stockNum) || stockNum < 0) {
alert('O stock deve ser um número positivo.');
return;
}
addProduct(shop.id, { name: prodName, price: priceNum, stock: stockNum });
setProdName('');
setProdPrice(30);
setProdStock(10);
setProdPrice('30');
setProdStock('10');
};
const addNewBarber = () => {
@@ -1116,7 +1126,7 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
</div>
)}
<div className="space-y-3 mb-6">
{shop.products.map((p) => (
{[...shop.products].sort((a, b) => b.price - a.price).map((p) => (
<div
key={p.id}
className={`flex items-center justify-between p-4 border rounded-lg ${p.stock <= 3 ? 'border-indigo-300 bg-indigo-50' : 'border-slate-200'
@@ -1150,8 +1160,33 @@ function DashboardInner({ shop }: { shop: BarberShop }) {
<h3 className="text-base font-semibold text-slate-900 mb-3">Adicionar novo produto</h3>
<div className="grid md:grid-cols-4 gap-3">
<Input label="Nome" placeholder="Ex: Pomada" value={prodName} onChange={(e) => setProdName(e.target.value)} />
<Input label="Preço" type="number" placeholder="30" value={prodPrice} onChange={(e) => setProdPrice(Number(e.target.value))} />
<Input label="Stock inicial" type="number" placeholder="10" value={prodStock} onChange={(e) => setProdStock(Number(e.target.value))} />
<Input
label="Preço"
type="number"
placeholder="30"
min="0"
step="0.01"
value={prodPrice}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setProdPrice(val);
}
}}
/>
<Input
label="Stock inicial"
type="number"
placeholder="10"
min="0"
value={prodStock}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseInt(val, 10) >= 0) {
setProdStock(val);
}
}}
/>
<div className="flex items-end">
<Button onClick={addNewProduct} className="w-full">
<PlusIcon size={16} className="mr-2" />