Student Planning and Academic Management System
UNPHU Academic Planner is a web application that allows students from Universidad Nacional Pedro Henríquez Ureña to plan their course selection before the official enrollment period. Students can create schedule drafts, compare alternatives, share them with classmates, and consult professor and course statistics to make more informed decisions when selecting classes. The platform also includes administrative modules that allow academic managers to handle seat requests, analyze demand, and review the academic offering.
Course selection at UNPHU has a particular context: not all students register at the same time. Sections are opened in waves depending on the student's major and GPA, which means students with lower academic indexes face greater uncertainty about whether they will secure a seat in the sections they need. When a student cannot obtain a seat, they must submit an informal request to their academic school — with no guarantee of approval, since opening a new section depends on professor availability and a minimum student quorum.
This is further complicated by the lack of accessible information about professors and degree curricula, preventing students from estimating their academic workload in advance. This lack of structured planning often results in course withdrawals, failed classes, and delays in graduation.
The application integrates with the university’s student information system — not directly, but through a custom synchronization pipeline (see Architecture) — and was built using Next.js on the frontend, ASP.NET Core (C#) on the backend, and Azure services for hosting, database management, Service Bus messaging, and asynchronous processing with Azure Functions, following an Event-Driven Architecture.
It is a complete rewrite of a previous version using a different stack and architecture focused on improving maintainability and scalability. Co-developed with my brother Jorge Lorenzo as our undergraduate thesis project at UNPHU.
Students at Universidad Nacional Pedro Henríquez Ureña (UNPHU) had no institutional tool to plan their academic schedule before the official enrollment period. The planning process was entirely manual — students had to cross-reference multiple disconnected sources to identify pending courses, verify prerequisites, evaluate available sections, and detect schedule conflicts, with no way to simulate scenarios or compare alternatives beforehand.
A survey conducted as part of this project revealed the concrete consequences of this gap: 78.3% of students had withdrawn from at least one course during their academic career, and 52.2% had failed at least one. Among those who withdrew or failed, 56.5% attributed it to poor workload management and 65.2% to dissatisfaction with the assigned professor — both factors that informed pre-enrollment planning could have mitigated. Additionally, 87% of students had no graduation plan, reflecting a largely reactive approach to academic progression.
On the institutional side, the problem was equally structural. 78.3% of students reported having been unable to enroll in a desired course at least once due to full sections, and 60.9% had requested new section openings because no sections existed for courses they needed. Despite this, the university had no centralized mechanism to capture or act on unmet academic demand — section opening requests were handled through an isolated, reactive process with no consolidated data to inform decisions about course offerings, classroom allocation, or faculty assignment.
The core gap was the absence of an integrated platform connecting both sides: one that empowers students to make informed enrollment decisions through draft scheduling and contextual data, while simultaneously feeding the administration with reliable, early-stage demand signals to drive academic planning.
The system was designed following a high-availability and decoupled architecture approach, where each component can evolve independently. The infrastructure is fully deployed on Microsoft Azure (East US region), prioritizing low latency and continuous availability during periods of high academic concurrency. The architecture is divided into well-defined responsibility layers: user interface, business logic, internal messaging, asynchronous processing, and data persistence.
Built with Next.js, TypeScript, and Tailwind CSS using a hybrid rendering model —
static views are server-side rendered (SSR) while interactive interfaces such as the
schedule editor are client-side rendered (CSR). Global state management is handled with
Zustand, and server requests are managed using TanStack Query. Authentication is
implemented through Google OAuth 2.0 / OpenID Connect, restricted to institutional
@unphu.edu.do accounts.
Built with ASP.NET Core (.NET 10) following Clean Architecture, organized into four layers with unidirectional dependencies toward the core:
CQRS separates writes (Commands) from reads (Queries): writes go through validation using FluentValidation, while complex reads are executed with Dapper for performance close to native SQL. The primary data access layer uses Entity Framework Core with a Code First approach and versioned migrations.
Integration with UNPHUSIST is implemented through the Adapter pattern —
IUnphuApiAdapter — which decouples the system core from the university’s external
services. Academic data such as curricula, course sections, and grades are consumed through
this adapter and combined with the system’s own data.
Additional implemented patterns include Repository + Unit of Work for transactional consistency, Decorator for extending HTTP clients with security and logging, and Factory for centralized database connection creation.
Azure Service Bus manages asynchronous communication between components. The API publishes events to the bus without knowing which component will process them, keeping modules decoupled and supporting high-concurrency scenarios during academic enrollment periods.
Azure Functions execute asynchronous tasks triggered by Service Bus events, timers, or HTTP requests: data synchronization with UNPHUSIST, processing newly registered users, and sending email notifications through SendGrid when a seat request reaches the required quorum.
Microsoft SQL Server deployed as Azure SQL Database inside a private virtual network (VNet), accessible from the backend only through Virtual Network Integration. Secrets are managed with Azure Key Vault and monitoring with Azure Application Insights. Access control follows an RBAC model with three roles (student, professor, administrator), JWT-based sessions, and encrypted communications through HTTPS.
Shows the Microsoft Azure services that compose the system infrastructure, all deployed in the East US region. External users access the platform exclusively through HTTPS to web applications deployed in App Services — the frontend as the main entry point and the backend as a REST API. Sensitive resources inside the Virtual Network (VNet) include the SQL Server database and Azure Key Vault, both without public IPs and accessible only from within the private network. Developers access these resources through a VPN Gateway. The backend connects to the VNet using Virtual Network Integration and publishes events to Azure Service Bus, which are processed by an Azure Function. Monitoring is instrumented with Azure Application Insights.

Presents the system as the coordination core between three actors: students (academic planning and academic trajectory management), professors (profile and schedule availability), and academic coordinators (analysis and management of academic offerings). The system interacts with three external services: Google OAuth as the identity provider, UNPHUSIST as the institutional academic data source (curricula, grades, sections), and SendGrid for email notifications.

Breaks down the system into its main containers and describes how they communicate with each other. The frontend (Next.js) consumes the backend REST API over HTTPS. The backend (ASP.NET Core) centralizes business logic, connects to the SQL Server database inside the VNet, queries UNPHUSIST through the adapter, and publishes events to Azure Service Bus. The Azure Function subscribes to the bus and executes background tasks. Secrets flow from Azure Key Vault to both the API and the serverless function.

Describes the system’s main entities and their relationships: students, majors, curricula, courses, prerequisites, sections, schedules, planning drafts, seat requests, and grades. The database is divided into two schemas — IdentityDb for authentication and authorization (users, roles, permissions, tokens) and UnphuPlaneador for the system’s operational data.

This section documents some of the most relevant architectural decisions made throughout the project — the reasoning behind them, their tradeoffs, and how they were solved. These are only a few examples; it is normal for a system designed from scratch for such a specific context to accumulate many more decision points along the way.
UNPHU does not provide its own Identity Provider (IdP), and direct access to its user database was not possible. The goal was to maximize adoption — allowing any student to register without friction — while still restricting access to the institutional domain.
The solution was to use Google as the IdP through OpenID Connect, taking advantage
of the fact that all UNPHU students and staff already have an @unphu.edu.do account.
The email format also allows the system to infer the user’s role: student emails follow the
pattern (first name initial)(last name initial)(admission year)-(student id) — for example,
pl21-1894@unphu.edu.do for Pablo Lorenzo, class of 2021, student ID 1894.
The authentication flow for a student works as follows:
user_created event is published to the Message Broker with the student ID.

Retrieving institutional data — majors, curricula, courses, prerequisites, classrooms, seat availability, and professors — was one of the first major challenges. Three approaches were evaluated:
Manual input: discarded. UNPHU has more than 30 majors, each with up to 2 curricula and an average of 70 courses per curriculum. It also did not solve the problem of dynamic data such as sections, classrooms, and professors per academic period.
Web scraping using student credentials: discarded for obvious security and trust reasons.
Unprotected UNPHUSIST endpoints: by inspecting the portal using browser developer tools, endpoints with insufficient authorization protections were identified — with only an access token it was possible to query academic data for students and majors. These endpoints expose incomplete and fragmented information; for example, retrieving a student’s grades requires chaining multiple requests: student ID → internal ID → grades by period → selected courses for that period.
To avoid creating a rigid dependency on these endpoints, data retrieval was abstracted
behind the IUnphuApiAdapter interface. If the endpoints change or become unavailable in
the future, only the implementation must be replaced without affecting the rest of the
system.
public interface IUnphuApiAdapter
{
Task<List<(Career career, Pensum pensum, bool isPrimary)>> GetCareersFromStudent(
Student student, CancellationToken cancellationToken);
Task<(List<Subject> subjects,List<SubjectPrerequisite> prerequisites)> GetStudentSubjectsAsync(
Student student,
bool includeElectives = true,
CancellationToken cancellationToken = default
);
Task<List<PendingGradesResponse>> GetPendingGrades(
Student student,
Predicate<PendingGradesResponse>? predicate,
CancellationToken cancellationToken
);
Task<List<Teacher>> GetStudentTeachers(
Student student,
CancellationToken cancellationToken
);
Task<List<Teacher>> GetStudentCurrentTeachers(
Student student,
CancellationToken cancellationToken
);
Task<List<(Grade Grade,string Subject,string SubjectCode,string Teacher)>> GetSemesterGrades(
Student student,
Func<SemesterGradeResponse, bool>? predicate,
int year,
int period,
CancellationToken cancellationToken
);
Task<(List<Building>, List<Classroom>)> GetBuildingsAndClassrooms(
Student student,
CancellationToken cancellationToken
);
Task<List<(Section Section, List<SectionSchedule> Schedules)>> GetSectionsAsync(
Student student,
int? year,
int? period,
CancellationToken cancellationToken
);
Task<float> GetStudentGpaAsync(
Student student,
CancellationToken cancellationToken
);
Task<CurrentPeriod> GetCurrentPeriodAsync(CancellationToken cancellationToken);
}Since the first version of this project was launched in August 2024, more than 1,000 users have registered on the platform. It is not possible to obtain the exact number because, at the time, the application used a free PostgreSQL database hosted on Render, which had the limitation of deleting data every 30 days.
The estimate comes from Google Analytics data, which has shown traffic peaks of up to 3,000 users, and during some academic periods the platform contained more than 700 registered users simultaneously.
Between December 2025 and January 2026, 577 students from 18 different majors registered on the platform, and more than 319 academic plans were created.
| Major | Students |
|---|---|
| Computer Systems Engineering | 157 |
| Industrial Engineering | 65 |
| Marketing | 61 |
| Law | 48 |
| Chemical Engineering | 28 |
| Accounting and Auditing | 16 |
| Medicine | 12 |
| Architecture | 11 |
| Business Administration | 11 |
| Hotel Administration | 11 |
| Surveying | 10 |
| Clinical Psychology | 9 |
| Dentistry | 5 |
| Veterinary Medicine | 4 |
| International Business | 4 |
| Pharmacy | 3 |
| Geomatics Engineering | 2 |
| Interior Design | 2 |

The administrative module was approached at a conceptual and architectural level, but it was not implemented as an operational software component in this version. The data collected during the pilot served as input to conceptually validate this design, demonstrating the feasibility of centrally analyzing and managing institutional academic demand. Its complete implementation remains future work.
If you visit UNPHU and ask any student about this application, there is a good chance they have already used it.