A multi-role healthcare web platform that connects patients with doctors and clinics, facilitates appointment booking, and integrates a credit-based payment system backed by finance entities and insurance companies.
- High-level Description
- Tech Stack
- Architecture & Design Patterns
- Key Features
- Getting Started
- Available Scripts
- Folder Structure
- Contribution Guidelines
- License
Vitalink is a B2C2B healthcare marketplace built on Nuxt 3. It solves the fragmented experience of booking medical appointments by bringing together four distinct actor types onto a single platform:
| Role | Path prefix | Description |
|---|---|---|
Patient (CUSTOMER) |
/pacientes |
Browse doctors, book appointments, manage credits & payments |
Doctor / Clinic (LEGAL_REPRESENTATIVE) |
/medicos |
Manage availability, services, patient queue, and earnings |
Finance Entity (FINANCE_ENTITY) |
/socio-financiero |
Administer credit lines, suppliers, and financial configuration |
The application is a frontend-only SPA/SSR that communicates exclusively with an external REST API. There is no backend code in this repository.
| Technology | Version | Role |
|---|---|---|
| Nuxt 3 | ^3.8.2 |
Meta-framework (SSR + SPA, routing, server middleware) |
| Vue 3 | ^3.3.12 |
UI component model (Composition API) |
| TypeScript | via Nuxt | Static typing throughout pages, components, and composables |
| Vue Router 4 | ^4.2.5 |
File-system routing (auto-generated by Nuxt) |
| Technology | Version | Role |
|---|---|---|
| Bootstrap 5 | ^5.3.3 |
Responsive grid, utility classes, components |
| Sass | ^1.69.6 |
SCSS preprocessing, variables, mixins, and global tokens |
@popperjs/core |
^2.11.8 |
Tooltip and dropdown positioning for Bootstrap JS |
| Technology | Version | Role |
|---|---|---|
$fetch (Nuxt built-in) |
— | Primary HTTP client for all API calls |
| Axios | ^1.6.5 |
Secondary client for specific request flows |
| Technology | Version | Role |
|---|---|---|
| VueUse | ^10.9.0 |
Composable utilities (debounce, event listeners, storage) |
| nuxt-icon | ^0.6.8 |
Icon rendering from Iconify registry |
| @vuepic/vue-datepicker | ^11.0.2 |
Date & time picker for appointment scheduling |
| v-calendar | ^3.1.2 |
Full calendar display for availability management |
| Chart.js + vue-chartjs | ^4.4.7 / ^5.3.2 |
Dashboard analytics and doughnut charts |
| Technology | Version | Role |
|---|---|---|
| Cybersource | via API | Payment gateway for appointment billing |
| jsPDF + jspdf-autotable | ^3.0.1 / ^5.0.2 |
Client-side PDF generation for receipts and reports |
| jwt-decode | ^4.0.0 |
Decoding JWT access tokens for role extraction |
| Technology | Role |
|---|---|
| Nuxt DevTools | In-browser debugging panel |
Prettier ^3.4.2 |
Code formatting |
| ESLint | Static analysis (see .eslintrc.json) |
The application follows a feature-domain layered architecture on top of Nuxt 3's file-system conventions:
Routing layer → pages/ (Nuxt auto-routes; one file = one route)
View layer → components/ (reusable UI, split by domain and atomic level)
Business logic → composables/ (all state, side effects, and API calls live here)
HTTP layer → composables/api/ (one composable per API resource)
Type system → types/ (shared TypeScript interfaces used everywhere)
Middleware → middleware/ (route guards enforcing role-based access)
components/
├── atoms/ # Stateless, primitive UI pieces (icons, badges)
├── ui/ # Reusable cross-domain widgets (date picker, toast, table base)
├── website/ # Public-facing marketing and search pages
├── pacientes/ # Patient-dashboard feature components
├── medicos/ # Doctor/clinic-dashboard feature components
└── aseguradoras/ # Insurance company workflow components
All shared state is managed through reactive composables using useState (Nuxt's SSR-safe wrapper over ref):
useAuthToken()— JWT lifecycle: read, store, clear.useAuthState()— Logged-in user's decoded profile.useUserInfo()— Cached user data (patient or supplier record).useModalManager()/useMedicalModalManager()— Global modal open/close state.useToast()— Notification queue.
- Mechanism: Custom JWT flow — no third-party auth library.
- Storage: Access token stored as a cookie (
rolekey). Expiry is checked client-side viapayload.exp * 1000 < Date.now(). - Token refresh: Handled by
useRefreshToken()composable, which intercepts 401 responses. - Route guards: Five Nuxt middleware files enforce role access before navigation:
authPacientes→CUSTOMERauthDoctorsHospitals→LEGAL_REPRESENTATIVEauthInsurances→ insurance roleauthHospitals→ hospital sub-roleauthLogin→ redirects authenticated users away from login pages
A single generic wrapper (useApi.ts) wraps $fetch and:
- Injects the
Authorization: Bearer <token>header. - Catches errors and maps codes to Spanish user-facing messages via
translateError(). - Returns a typed
IUsableAPI<T>object.
Each resource has its own composable (e.g. useAppointment, useSupplier, usePayment) that delegates to useApi.
- Multi-role SPA — Four independent dashboards served from a single Nuxt application, each gated by JWT role claims.
- Appointment booking flow — Step-by-step stepper (
reservar-cita-valoracion) covering doctor search, specialty selection, date/time picking, and confirmation. - Credit-based payment system — Patients can request appointment credit; insurance companies approve/reject through a dedicated workflow; finance entities administer credit lines.
- Cybersource payment integration — Secure payment processing with a dedicated SPA receipt page (
/receipt-spa) for post-payment callbacks. - Availability management — Doctors configure weekly availability slots via
useAvailabilityand theavailability-selectorcomponent. - Client-side PDF generation — Appointment reports and receipts generated in the browser using jsPDF + AutoTable, with no server round-trip.
- Dashboard analytics — Chart.js-powered doughnut charts and data summaries via
useDashboardChartsandvue-chartjs. - Doctor public profiles — Dynamic routes (
/perfiles/doctor/[doctor],/perfiles/hospital/[hospital]) with reviews, gallery, specialties, packages, and services. - Centralized Spanish error translation — All API error codes are mapped to human-readable Spanish messages in
utils/errorTranslations.ts. - Responsive Bootstrap 5 UI — Grid, utility classes, and custom SCSS variables/mixins for brand theming.
| Requirement | Version |
|---|---|
| Node.js | >= 18.x (LTS recommended) |
| npm | >= 9.x |
| Git | any recent version |
The project uses ES Modules (
"type": "module"inpackage.json). Ensure your Node version supports native ESM.
git clone https://github.com/VitalinkOrg/VitalinkFrontend.git
cd VitalinkFrontendnpm installThe
postinstallscript runsnuxt prepareautomatically, generating the.nuxt/directory with TypeScript augmentations and auto-imports.
cp .env.example .env # or create .env manually — see section belownpm run devThe app will be available at http://localhost:3000.
Create a .env file at the project root with the following keys:
# Base URL of the backend REST API
API_BASE_URL=
# Full URL Cybersource will redirect to after a payment transaction
NUXT_PUBLIC_CYBERSOURCE_RETURN_URL=
# Public base URL of this frontend application (used for redirects and links)
NUXT_PUBLIC_BASE_URL=
API_BASE_URLis exposed to the client via Nuxt'sruntimeConfig.public. All keys prefixed withNUXT_PUBLIC_are also client-accessible at runtime. Never store secrets in these variables.
| Script | Command | Description |
|---|---|---|
| Development | npm run dev |
Starts the Nuxt dev server with HMR at localhost:3000 |
| Build | npm run build |
Compiles and optimizes the application for production (outputs to .output/) |
| Preview | npm run preview |
Serves the production build locally for pre-deploy verification |
| Static Generate | npm run generate |
Pre-renders all routes to static HTML files |
| Prepare | npm run postinstall |
Runs nuxt prepare — regenerates .nuxt/ type declarations (run after npm install) |
VitalinkFrontend/
│
├── assets/ # Static assets processed by the build pipeline
│ ├── main.scss # Global SCSS entry point
│ ├── scss/ # Shared variables, mixins, and global tokens
│ │ ├── _custom-variables.scss
│ │ ├── _globals.scss # Auto-injected into every component via vite config
│ │ └── _mixin.scss
│ └── styles/
│ └── bootstrap/ # Selective Bootstrap SCSS imports
│
├── components/ # 195 Vue SFCs, organized by domain
│ ├── atoms/ # Primitive, stateless components (icons, badges)
│ ├── ui/ # Cross-domain reusable widgets
│ ├── website/ # Public site components (search, booking, profiles)
│ ├── pacientes/ # Patient dashboard components
│ ├── medicos/ # Doctor/clinic dashboard components
│ └── aseguradoras/ # Insurance workflow components
│
├── composables/ # All business logic and shared state
│ ├── api/ # One composable per API resource (16 files)
│ │ ├── useApi.ts # Generic $fetch wrapper with auth + error handling
│ │ ├── useAuth.ts # Login, register, token refresh, logout
│ │ ├── useAppointment.ts
│ │ ├── usePayment.ts # Cybersource payment flow
│ │ └── ...
│ ├── useAuthToken.ts # JWT read/write/clear from cookie
│ ├── useAuthState.ts # Decoded user profile (reactive)
│ ├── useModalManager.ts
│ ├── useToast.ts
│ └── ... # 34 additional logic composables
│
├── layouts/ # 16 role-specific and shared layout wrappers
│ ├── web.vue # Public website layout
│ ├── pacientes-dashboard.vue
│ ├── medicos-dashboard.vue
│ ├── aseguradoras-dashboard.vue
│ └── ...
│
├── middleware/ # Nuxt route guards (run before page render)
│ ├── authPacientes.ts # CUSTOMER role guard
│ ├── authDoctorsHospitals.ts # LEGAL_REPRESENTATIVE role guard
│ ├── authInsurances.ts # Insurance role guard
│ ├── authHospitals.ts # Hospital sub-role guard
│ └── authLogin.ts # Redirect authenticated users away from login
│
├── pages/ # File-system routes (43+ pages across 5 domains)
│ ├── index.vue # Public homepage
│ ├── buscar.vue # Doctor/clinic search
│ ├── receipt-spa.vue # Cybersource post-payment receipt SPA
│ ├── auth/ # Shared auth pages (login, password reset)
│ ├── pacientes/ # Patient dashboard pages
│ ├── medicos/ # Doctor dashboard pages
│ ├── socio-financiero/ # Finance entity dashboard pages
│ ├── clinicas/ # Clinic onboarding pages
│ ├── perfiles/ # Public doctor & hospital profile pages
│ └── admin/ # Admin login & dashboard
│
├── public/ # Assets served as-is (no processing)
│ ├── bootstrap.bundle.min.js # Bootstrap JS bundle (injected in <body>)
│ └── favicon.ico
│
├── src/ # 130+ image assets (SVG/PNG illustrations)
│
├── types/ # Shared TypeScript interfaces
│ ├── index.d.ts # 760+ lines — all API entity interfaces
│ ├── auth.ts # UserRole enum, token types, role-route map
│ └── payment.ts # Cybersource payment types and error class
│
├── utils/ # Pure utility functions
│ ├── errorTranslations.ts # API error code → Spanish message map
│ └── jwt.ts # Manual JWT base64 decode helper
│
├── app.vue # Root Vue component (NuxtLayout + NuxtPage)
├── nuxt.config.ts # Nuxt configuration (modules, CSS, runtimeConfig)
├── tsconfig.json # Extends .nuxt/tsconfig.json (auto-generated)
├── package.json
└── .env # Local environment variables (not committed)
main ← production-ready code; protected branch
├── feat/<topic> ← new features or significant additions
├── fix/<topic> ← bug fixes targeted at a specific area
├── refactor/<topic> ← code improvements with no behavior change
└── docs/<topic> ← documentation-only changes
- Branch from
mainusing the naming convention above. - Keep PRs focused — one logical change per PR.
- Write a clear description explaining what changed and why.
- Verify the app builds without errors:
npm run build. - Manually test your change across all affected user roles (patient, doctor, insurance, finance).
- Request at least one reviewer before merging.
- Squash or rebase before merge to keep history linear.
| Area | Convention |
|---|---|
| Language | TypeScript everywhere — avoid untyped any |
| Components | Vue 3 Composition API with <script setup lang="ts"> |
| Composables | One responsibility per composable; always prefix with use |
| Naming | PascalCase for components; camelCase for composables, utils, and variables |
| Styling | Bootstrap utility classes first; custom SCSS only for brand-specific overrides |
| API calls | Always go through useApi() — never call $fetch directly from components |
| Error messages | User-facing strings must be in Spanish; use translateError() from utils/errorTranslations.ts |
| Types | Add new entity interfaces to types/index.d.ts; payment types to types/payment.ts |
| Formatting | Run Prettier before committing — config is in package.json |
<type>(<scope>): <short summary>
Types : feat | fix | refactor | docs | style | chore
Scopes: pacientes | medicos | aseguradoras | socio-financiero | website | auth | payments | ui
Examples:
feat(pacientes): add credit request status badge to citas table
fix(payments): handle Cybersource timeout error with Spanish message
refactor(ui): extract reusable pagination component from medicos table
This project is proprietary. All rights reserved by Vitalink. Unauthorized copying, distribution, or modification of this software, via any medium, is strictly prohibited without explicit written permission from the project owners.
Note for new contributors: There is currently no automated test suite — manual QA across all four user roles is required before merging any change that touches shared composables, middleware, or the API layer. Contributions adding Vitest unit tests or Playwright E2E tests are especially welcome.