Introduction
Portal Ciudadano Manta provides a comprehensive set of composables, stores, and utilities built on Vue 3, Pinia, and Supabase. This section documents all available APIs for building features and extending the platform.
Composables Reusable Vue composition functions for common functionality
Stores Pinia stores for global state management
Utilities Helper functions and configurations
Authentication Supabase authentication setup and helpers
Composables
Composables are reusable composition functions that encapsulate stateful logic. Located in src/composables/.
useAuth
Location: src/composables/useAuth.ts
Simple authentication composable for managing local auth state.
import { useAuth } from '@/composables/useAuth'
const { isAuthenticated , login , logout , getUser } = useAuth ()
// Check authentication status
const isLoggedIn = checkAuth ()
// Login user
login ( 'token-value' , '[email protected] ' )
// Get current user
const user = getUser () // { email: string, token: string }
// Logout
logout ()
API Reference:
isAuthenticated - Computed boolean indicating auth status
checkAuth() - Checks localStorage for existing auth token
login(token: string, email: string) - Store auth credentials
logout() - Clear auth credentials
getUser() - Returns current user data
For production use, prefer useAuthStore which integrates with Supabase authentication.
useImageUpload
Location: src/composables/useImageUpload.ts
Handle image uploads to Supabase Storage with validation.
import { useImageUpload } from '@/composables/useImageUpload'
const { uploading , error , imageUrl , uploadImage , deleteImage } = useImageUpload ({
bucket: 'imagenes' ,
folder: 'reportes' ,
maxSizeMB: 5 ,
allowedTypes: [ 'image/jpeg' , 'image/png' , 'image/webp' ]
})
// Upload an image
const result = await uploadImage ( file )
if ( result . success ) {
console . log ( 'Image URL:' , result . url )
}
// Delete an image
await deleteImage ( imageUrl . value )
Options:
bucket (string) - Supabase storage bucket name (default: ‘imagenes’)
folder (string) - Optional folder path within bucket
maxSizeMB (number) - Maximum file size in MB (default: 5)
allowedTypes (string[]) - Allowed MIME types (default: jpeg, jpg, png, webp)
Returns:
uploading (Ref<boolean>) - Upload in progress
error (Ref<string | null>) - Error message if any
imageUrl (Ref<string>) - URL of uploaded image
uploadImage(file: File) - Upload function
deleteImage(url: string) - Delete function
reset() - Reset state
useLanguage
Location: src/composables/useLanguage.ts
Manage internationalization and language switching.
import { useLanguage } from '@/composables/useLanguage'
const { currentLanguage , languages , changeLanguage , loadSavedLanguage } = useLanguage ()
// Load saved language preference
loadSavedLanguage ()
// Change language
changeLanguage ( 'en' )
// Available languages
console . log ( languages ) // [{ code: 'es', name: 'Español', flag: '🇪🇸' }, ...]
Supported Languages:
Spanish (es) 🇪🇸
English (en) 🇺🇸
Kichwa (qu) 🇪🇨
useReportes
Location: src/composables/useReportes.ts
Utility functions for working with user reports.
import { useReportes } from '@/composables/useReportes'
const { reportes , loading , error , obtenerReportesUsuario , agruparPorEstado , contarPorEstado } = useReportes ()
// Fetch user reports
const userReports = await obtenerReportesUsuario ( 'user-id' )
// Group by status
const grouped = agruparPorEstado ( reportes . value )
console . log ( grouped . pendiente ) // Array of pending reports
// Count by status
const counts = contarPorEstado ( reportes . value )
console . log ( counts ) // { pendiente: 5, en_revision: 2, resuelto: 10, ... }
useSearch
Location: src/composables/useSearch.ts
Basic search functionality composable.
import { useSearch } from '@/composables/useSearch'
const { searchTerm , performSearch , clearSearch } = useSearch ()
// Bind to input
< input v - model = "searchTerm" @ keyup . enter = "performSearch" />
// Perform search
performSearch ()
// Clear search
clearSearch ()
Stores
Pinia stores provide global state management. Located in src/stores/.
useAuthStore
Location: src/stores/auth.store.ts
Comprehensive authentication store with Supabase integration.
import { useAuthStore } from '@/stores/auth.store'
const authStore = useAuthStore ()
// Initialize authentication
await authStore . initAuth ()
// Register new user
const result = await authStore . register (
'[email protected] ' ,
'password123' ,
{
nombres: 'Juan' ,
apellidos: 'Pérez' ,
cedula: '1234567890' ,
parroquia: 'Manta' ,
barrio: 'Centro' ,
tipo: 'ciudadano'
}
)
// Login
await authStore . login ( '[email protected] ' , 'password123' )
// Check authentication
if ( authStore . isAuthenticated ()) {
console . log ( 'User:' , authStore . usuario )
}
// Logout
await authStore . logout ()
State:
user - Supabase User object
usuario - Extended user profile from database
loading - Operation in progress
error - Error message
Getters:
isAuthenticated() - Returns boolean
isCiudadano() - Check if user is citizen
isAdministrador() - Check if user is admin
Actions:
initAuth() - Initialize and setup auth listeners
register(email, password, userData) - Create new account
login(email, password) - Sign in
logout() - Sign out
resetPassword(email) - Send reset email
updateProfile(updates) - Update user profile
The store automatically handles both usuarios and administradores tables, falling back appropriately based on user type.
useReportesStore
Location: src/stores/reportes.store.ts
Manage citizen reports with full CRUD operations.
import { useReportesStore } from '@/stores/reportes.store'
const reportesStore = useReportesStore ()
// Fetch reports with filters
await reportesStore . fetchReportes ({
usuario_id: 'user-id' ,
estado: 'pendiente' ,
categoria: 'infraestructura'
})
// Create new report
const result = await reportesStore . crearReporte ({
titulo: 'Bache en la vía' ,
descripcion: 'Hay un bache grande...' ,
categoria: 'infraestructura' ,
direccion: 'Calle Principal' ,
ubicacion: { lat: - 0.95 , lng: - 80.72 },
estado: 'pendiente'
})
// Update report
await reportesStore . actualizarReporte ( 'report-id' , {
estado: 'en_revision'
})
// Upload image for report
const upload = await reportesStore . subirImagen ( file , 'report-id' )
// Subscribe to realtime updates
const subscription = reportesStore . subscribeToReportes ()
Report States:
pendiente - Pending review
en_revision - Under review
aceptado - Accepted
en_proceso - In progress
resuelto - Resolved
rechazado - Rejected
duplicado - Duplicate
useNoticiasStore
Location: src/stores/noticias.store.ts
Manage news articles with location-based filtering.
import { useNoticiasStore } from '@/stores/noticias.store'
const noticiasStore = useNoticiasStore ()
// Fetch news for specific location
await noticiasStore . fetchNoticiasUsuario ( 'Manta' , 'Centro' )
// Fetch all news (admin only)
await noticiasStore . fetchTodasLasNoticias ()
// Create news article
await noticiasStore . crearNoticia ({
titulo: 'Noticia importante' ,
descripcion: 'Descripción de la noticia' ,
categoria: 'municipal' ,
parroquia_destino: 'Manta' , // null for global news
barrio_destino: null // null for parroquia-wide news
})
// Update news
await noticiasStore . actualizarNoticia ( 'news-id' , {
titulo: 'Título actualizado'
})
Location Filtering Logic:
Global news: parroquia_destino = null
Parroquia news: parroquia_destino = 'X' AND barrio_destino = null
Barrio news: parroquia_destino = 'X' AND barrio_destino = 'Y'
useEncuestasStore
Location: src/stores/encuestas.store.ts
Manage surveys with multiple question types.
import { useEncuestasStore } from '@/stores/encuestas.store'
const encuestasStore = useEncuestasStore ()
// Fetch active surveys
await encuestasStore . fetchEncuestas ( true )
// Create survey
await encuestasStore . crearEncuesta ({
titulo: 'Encuesta de satisfacción' ,
descripcion: 'Ayúdanos a mejorar' ,
tipo: 'opcion_multiple' ,
opciones: [
{
pregunta: '¿Cómo calificas el servicio?' ,
opciones: [ 'Excelente' , 'Bueno' , 'Regular' , 'Malo' ]
}
],
activa: true
})
// Submit response
await encuestasStore . responderEncuesta ( 'survey-id' , {
respuestaLibre: [ 'Excelente' ]
})
// Check if user already responded
const yaRespondio = await encuestasStore . verificarYaRespondio ( 'survey-id' )
// Get statistics
const stats = await encuestasStore . obtenerEstadisticas ( 'survey-id' )
Survey Types:
opcion_multiple - Multiple choice
calificacion - Rating scale
abierta - Open text
Utilities
Helper functions and configurations located in src/lib/.
Supabase Client
Location: src/lib/supabase.ts
Pre-configured Supabase client with type safety.
import { supabase , handleSupabaseError , isAuthenticated , getCurrentUser } from '@/lib/supabase'
// Use the client directly
const { data , error } = await supabase
. from ( 'reportes' )
. select ( '*' )
. eq ( 'estado' , 'pendiente' )
if ( error ) {
const message = handleSupabaseError ( error )
console . error ( message )
}
// Check authentication
const authed = await isAuthenticated ()
// Get current user
const user = await getCurrentUser ()
Helpers:
handleSupabaseError(error) - Format error messages
isAuthenticated() - Check if session exists
getCurrentUser() - Get current authenticated user
Storage
Location: src/lib/storage.ts
Conditional storage that falls back to memory when localStorage is unavailable.
import { conditionalStorage } from '@/lib/storage'
// Use like localStorage
conditionalStorage . setItem ( 'key' , 'value' )
const value = conditionalStorage . getItem ( 'key' )
conditionalStorage . removeItem ( 'key' )
This is used internally by Supabase Auth for session persistence.
Type Safety
All database types are auto-generated from Supabase schema:
import type { Database } from '@/types/database.types'
type Reporte = Database [ 'public' ][ 'Tables' ][ 'reportes' ][ 'Row' ]
type InsertReporte = Database [ 'public' ][ 'Tables' ][ 'reportes' ][ 'Insert' ]
type UpdateReporte = Database [ 'public' ][ 'Tables' ][ 'reportes' ][ 'Update' ]
Next Steps Learn about authentication setup and security best practices