Skip to main content

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

Build docs developers (and LLMs) love