/projects/ulbra-atende
← Todos os projetosUlbra Atende
Service desk de TI de uma universidade, no lugar do GLPI.
Design & implementação · Trabalho profissional

Visão geral
O service desk de TI da ULBRA — um monólito modular em .NET 10 que substituiu o GLPI como canal único de entrada da TI da universidade, levando um pedido do chamado ao SLA à pesquisa de satisfação.
O que eu fiz
Autor principal, do zero — a arquitetura, o backend, o front-end e o deploy.
- O monólito modular e as fronteiras entre seus contextos.
- O motor de SLA, incluindo pausas que registram quem interrompeu a contagem e por quê.
- O outbox transacional e o fan-out de notificação que ele alimenta.
- O servidor de autorização OAuth e o servidor MCP atrás da sua tela de consentimento.
- O front-end em React e o deploy em Docker Swarm.
O problema
A TI da ULBRA recebia demanda por GLPI, e-mail e mensagem direta ao mesmo tempo. Não havia SLA por time, nem rastro de aprovação, nem como saber se alguém ficou satisfeito com o resultado. O Ulbra Atende substitui o GLPI como canal único de entrada e torna cada uma dessas coisas mensurável — três meses depois, a mediana de fechamento é de cerca de uma hora e meia.
Em números
em ~3 meses de produção
Arquitetura
Um monólito modular em .NET 10: um único deploy, contextos delimitados separados — Core, Identity, Notifications e MCP — cada um em camadas Domain → Application → Infrastructure com seu próprio schema no Postgres. Eventos de integração passam pelo RabbitMQ através de um outbox transacional do EF. Anexos ficam em S3/MinIO, cache em Redis, tracing por OpenTelemetry; os testes de integração rodam contra Postgres, RabbitMQ e MinIO reais via Testcontainers.
- React 19 SPATanStack Router e Query sobre um design system em Tailwind.
- .NET 10 APIMonólito modular — quatro contextos delimitados num único deploy.
- PostgreSQL 17Um schema por módulo; migrations do EF Core aplicadas no startup.
- RabbitMQEventos de integração publicados por um outbox transacional do EF.
- Slack · Google Chat · e-mailFan-out de notificação consumindo esses eventos.
A vida de um chamado
O relógio do SLA é o fio que atravessa tudo. Ele começa pela política do time que recebe, para quando o chamado depende de alguém fora do time, e é contra ele que as metas de resposta e resolução são medidas. Um chamado também pode terminar cancelado, e trabalho que exige aval espera uma aprovação antes de começar.
- OpenO relógio começa contra a política de SLA do time que recebe, e a triagem faz o roteamento para um time e uma categoria.
- InProgressAlguém assume. A primeira resposta já foi medida a esta altura.
- PausedEsperando quem abriu ou um terceiro. O relógio para, e quem pausou e por quê fica registrado como uma entrada própria.
- CompletedO trabalho acabou e quem abriu é convidado a avaliar — que é de onde vem a nota de satisfação.
O que faz
- SLA por time, com pausas que registram quem parou o relógio e por quê.
- Templates de chamado multi-etapa, então um pedido recorrente já chega dividido em passos.
- Fluxo de aprovação — trabalho que exige aval não começa sem ele.
- Chamados pai/filho e dependências explícitas entre eles.
- Notificações se espalham por Slack, Google Chat e e-mail, conforme a preferência de cada usuário.
- Um dashboard cujos cards abrem exatamente a listagem que resumem.
- Pesquisa de satisfação em todo chamado concluído.
Decisões de engenharia
Monólito modular, não microsserviços
Um time, um deploy. A fronteira que importa é o módulo — garantida por referências de projeto e um schema por contexto — não a rede. Distribuir teria comprado uma independência de deploy que ninguém precisava, pagando em latência, falha parcial e dificuldade de depurar.
Outbox transacional para todo evento de integração
A linha do evento é escrita na mesma transação da mudança de negócio. Uma notificação nunca dispara para um chamado que não commitou, e nunca some porque o broker estava fora naquele instante — o relay entrega assim que a transação fecha.
IDs fortemente tipados por source generator
Cada entidade tem seu próprio struct de ID, escrito como ti_…, tm_…, us_…. Passar um ID de time onde se espera um de chamado para de compilar. Uma classe inteira de bug sai do runtime e vai para o build, e o ID diz o que é em log e em URL.
Servidor OAuth próprio, e um servidor MCP atrás dele
O OpenIddict emite os tokens; o servidor MCP expõe ferramentas de leitura, escrita e consulta de chamados. A pessoa conecta o Claude ou o ChatGPT à própria conta por uma tela de consentimento e trabalha os chamados em linguagem natural — com exatamente as permissões que já tem na interface, e a mesma checagem de escopo em cada chamada de ferramenta.