Overview
Portal Ciudadano Manta is built with modern web technologies using Vue 3, TypeScript, and Supabase as the backend. The application follows a modular architecture with clear separation of concerns.
Tech Stack
Frontend Framework Vue 3.5+ with Composition API and TypeScript
Build Tool Vite 7.1+ for fast development and optimized builds
State Management Pinia 3.0+ for centralized state management
Backend Supabase (PostgreSQL, Auth, Storage, Realtime)
Project Structure
src/
├── assets/ # Static assets (images, icons)
│ ├── footer/
│ ├── logo/
│ └── sobreNosotros/
├── components/ # Reusable Vue components
│ ├── accessibility/ # Accessibility features (TextToSpeech)
│ ├── cards/ # Card components (ServiceCard)
│ ├── footer/ # Footer component
│ ├── maps/ # Map components (Leaflet integration)
│ └── nav/ # Navigation components (Navbar)
├── composables/ # Vue composables for shared logic
│ ├── useAuth.ts
│ ├── useImageUpload.ts
│ ├── useKeyboardShortcuts.ts
│ ├── useLanguage.ts
│ └── useReportes.ts
├── i18n/ # Internationalization
│ ├── index.ts # i18n configuration
│ └── locales/ # Translation files (es, en, qu)
├── lib/ # Library configurations
│ ├── supabase.ts # Supabase client setup
│ └── storage.ts # Storage utilities
├── router/ # Vue Router configuration
│ └── index.ts # Route definitions and guards
├── stores/ # Pinia stores
│ ├── auth.store.ts
│ ├── encuestas.store.ts
│ ├── noticias.store.ts
│ └── reportes.store.ts
├── types/ # TypeScript type definitions
│ └── database.types.ts
├── views/ # Page components
│ ├── Home.vue
│ ├── Login.vue
│ ├── Dashboard.vue
│ ├── AdminPanel.vue
│ └── ...
├── App.vue # Root component
└── main.ts # Application entry point
Application Initialization
The application follows a specific initialization sequence to ensure proper setup:
import { createPinia } from "pinia" ;
import { createApp } from "vue" ;
import App from "./App.vue" ;
import i18n from "./i18n" ;
import router from "./router" ;
import { useAuthStore } from "./stores/auth.store" ;
const app = createApp ( App );
const pinia = createPinia ();
// Configure plugins
app . use ( pinia );
app . use ( i18n );
app . use ( router );
// Initialize authentication before mounting
const authStore = useAuthStore ();
authStore . initAuth (). then (() => {
app . mount ( "#app" );
});
The app waits for authentication initialization before mounting to prevent race conditions and ensure consistent user state.
Build Configuration
Vite is configured with optimized code splitting and bundle management:
import tailwindcss from "@tailwindcss/vite" ;
import vue from "@vitejs/plugin-vue" ;
import { resolve } from "path" ;
import { defineConfig } from "vite" ;
export default defineConfig ({
plugins: [ vue (), tailwindcss ()] ,
resolve: {
alias: {
"@" : resolve ( __dirname , "src" ),
},
} ,
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor' : [ 'vue' , 'vue-router' , 'pinia' ],
'i18n' : [ 'vue-i18n' ],
'supabase' : [ '@supabase/supabase-js' ],
'leaflet' : [ 'leaflet' , 'vue3-leaflet' ],
},
},
},
chunkSizeWarningLimit: 1000 ,
minify: 'esbuild' ,
target: 'es2015' ,
} ,
}) ;
Manual Code Splitting
The build is optimized with manual chunk definitions:
vue-vendor : Core Vue libraries (Vue, Router, Pinia)
i18n : Internationalization library
supabase : Backend client
leaflet : Map components
This ensures optimal caching and faster load times.
Routing Architecture
The application uses Vue Router with authentication guards:
Public Routes
Protected Routes
Admin Routes
Routes accessible without authentication:
/ - Home page
/login - Login page
/register - Registration page
/reset-password - Password reset
/sobre-nosotros - About us
Routes requiring authentication:
/dashboard - User dashboard
/reportar-problema - Report issues
/mi-perfil - User profile
/encuestas - Surveys
/noticias - News
Routes requiring admin role:
/admin - Admin panel
/admin/noticias - Manage news
/admin/encuestas - Manage surveys
/admin/reportes - Manage reports
Navigation Guard Example
router . beforeEach ( async ( to , from , next ) => {
const authStore = useAuthStore ();
// Wait for auth initialization
if ( authStore . loading ) {
await new Promise < void >(( resolve ) => {
const timeout = setTimeout (() => resolve (), 5000 );
const stop = watch (
() => authStore . loading ,
( loading ) => {
if ( ! loading ) {
clearTimeout ( timeout );
stop ();
resolve ();
}
}
);
});
}
const requiresAuth = to . meta . requiresAuth ;
const isAuthenticated = authStore . isAuthenticated ();
const isAdmin = authStore . isAdministrador ();
const isAdminRoute = to . path . startsWith ( "/admin" );
// Allow direct access to reset-password
if ( to . name === "ResetPassword" ) {
return next ();
}
// Require authentication
if ( requiresAuth && ! isAuthenticated ) {
return next ({
name: "Login" ,
query: { redirect: to . fullPath },
});
}
// Require admin role
if ( isAdminRoute && isAuthenticated && ! isAdmin ) {
return next ({ name: "Dashboard" });
}
next ();
});
Supabase Integration
The Supabase client is configured with type safety and optimal settings:
import { createClient } from "@supabase/supabase-js" ;
import type { Database } from "../types/database.types" ;
export const supabase = createClient < Database >(
import . meta . env . VITE_SUPABASE_URL ,
import . meta . env . VITE_SUPABASE_ANON_KEY ,
{
auth: {
persistSession: true ,
autoRefreshToken: true ,
detectSessionInUrl: true ,
flowType: "pkce" , // More secure for SPAs
},
db: {
schema: "public" ,
},
realtime: {
timeout: 10000 ,
},
}
);
Always use PKCE flow (flowType: "pkce") for single-page applications to ensure secure authentication.
Design Patterns
Composition API Pattern
All components use Vue 3’s Composition API for better code organization and reusability:
< script setup lang = "ts" >
import { ref , computed , onMounted } from 'vue' ;
import { useAuthStore } from '@/stores/auth.store' ;
const authStore = useAuthStore ();
const isLoggedIn = computed (() => authStore . isAuthenticated ());
onMounted (() => {
// Component initialization
});
</ script >
Store Pattern
Pinia stores follow a consistent pattern:
State : Reactive data
Getters : Computed properties (functions returning computed values)
Actions : Async operations and mutations
Error Handling Pattern
All async operations follow a consistent error handling pattern:
try {
loading . value = true ;
const { data , error } = await supabase . from ( 'table' ). select ();
if ( error ) throw error ;
return { success: true , data };
} catch ( err : any ) {
error . value = err . message ;
console . error ( '❌ Error:' , err );
return { success: false , error: err . message };
} finally {
loading . value = false ;
}
Routes are lazy-loaded using dynamic imports: {
path : '/admin' ,
component : () => import ( '@/views/AdminPanel.vue' )
}
Vite pre-bundles frequently used dependencies: optimizeDeps : {
include : [ 'vue' , 'vue-router' , 'pinia' , 'vue-i18n' , '@supabase/supabase-js' ],
}
Static assets are processed through Vite’s asset pipeline with automatic optimization and fingerprinting.
Next Steps
Database Schema Learn about the database structure and relationships
State Management Explore Pinia stores and state patterns
Internationalization Understand the i18n implementation
API Reference View the complete API documentation