Overview
Portal Ciudadano Manta uses Supabase (PostgreSQL) as its database. The schema is fully typed in TypeScript for type safety across the application.
Database Structure
The database consists of 6 main tables:
usuarios Citizen user accounts and profiles
administradores Administrator accounts
reportes Citizen reports of municipal issues
encuestas Surveys and polls
respuestas_encuestas Survey responses
noticias Municipal news articles
Type Definitions
All database types are defined in src/types/database.types.ts with full TypeScript support.
Common Types
src/types/database.types.ts
export type UUID = string ;
export type Timestamp = string ;
export type EncuestaTipo = "opcion_multiple" | "abierta" | "calificacion" ;
export type ReporteCategoria =
| "alumbrado"
| "baches"
| "limpieza"
| "agua"
| "alcantarillado"
| "parques"
| "señalizacion"
| "seguridad"
| "ruido"
| "otro" ;
// Simplified states following ISO/IEC 25010 (max 5 states)
export type ReporteEstado =
| "pendiente" // Initial state
| "en_revision" // Admin is evaluating
| "en_proceso" // Accepted and in resolution
| "resuelto" // Successfully completed
| "rechazado" ; // Rejected or duplicate
export type ReportePrioridad = "baja" | "media" | "alta" | "urgente" ;
export type UsuarioTipo = "ciudadano" | "administrador" ;
Tables
usuarios
Stores citizen user information and location data.
Schema
Insert Type
Update Type
export interface UsuariosRow {
id : UUID ; // Supabase Auth user ID
email : string ;
nombres : string ;
apellidos : string ;
cedula : string ; // 10-digit ID number
parroquia : string ; // Parish (neighborhood district)
barrio : string ; // Neighborhood
tipo : UsuarioTipo ; // User type (ciudadano)
activo : boolean | null ;
created_at : Timestamp | null ;
updated_at : Timestamp | null ;
}
export type UsuariosInsert = Omit <
UsuariosRow ,
"created_at" | "updated_at" | "tipo" | "activo"
> & {
tipo ?: UsuarioTipo ;
activo ?: boolean ;
created_at ?: Timestamp | null ;
updated_at ?: Timestamp | null ;
};
export type UsuariosUpdate = Partial < UsuariosRow > & { id : UUID };
The tipo field is typically “ciudadano” for this table. Administrators are stored in a separate table.
administradores
Stores administrator account information.
src/types/database.types.ts
export interface AdministradoresRow {
id : UUID ; // Supabase Auth user ID
email : string ;
nombres : string ;
apellidos : string ;
cedula : string ; // 10-digit ID number
activo : boolean | null ;
created_at : Timestamp | null ;
updated_at : Timestamp | null ;
}
export type AdministradoresInsert = Omit <
AdministradoresRow ,
"created_at" | "updated_at" | "activo"
> & {
activo ?: boolean ;
created_at ?: Timestamp | null ;
updated_at ?: Timestamp | null ;
};
export type AdministradoresUpdate = Partial < AdministradoresRow > & { id : UUID };
reportes
Stores citizen reports of municipal issues with location data.
export interface ReportesRow {
id : UUID ;
usuario_id : UUID ; // Foreign key to usuarios
categoria : ReporteCategoria ;
descripcion : string ;
ubicacion_parroquia : string ;
ubicacion_barrio : string ;
ubicacion_direccion : string ;
ubicacion_lat : number | null ; // GPS latitude
ubicacion_lng : number | null ; // GPS longitude
estado : ReporteEstado ;
prioridad : ReportePrioridad ;
imagen_url : string | null ; // Supabase Storage URL
respuesta_admin : string | null ;
fecha_resolucion : Timestamp | null ;
created_at : Timestamp | null ;
updated_at : Timestamp | null ;
}
Report Categories
Category Description alumbradoStreet lighting issues bachesPotholes and road damage limpiezaCleanliness and sanitation aguaWater supply issues alcantarilladoSewage system issues parquesParks and recreation areas señalizacionTraffic signs and signals seguridadSecurity concerns ruidoNoise complaints otroOther issues
Report States
Follows ISO/IEC 25010 recommendation of maximum 5 states:
"pendiente" → "en_revision" → "en_proceso" → "resuelto"
↓
"rechazado"
encuestas
Stores survey definitions with flexible question types.
src/types/database.types.ts
export interface EncuestasRow {
id : UUID ;
titulo : string ;
descripcion : string ;
tipo : EncuestaTipo ; // Question type
opciones : any | null ; // JSONB - question options
fecha_inicio : Timestamp ;
fecha_fin : Timestamp ;
activa : boolean | null ;
parroquia_destino : string | null ; // Target specific location
barrio_destino : string | null ;
created_at : Timestamp | null ;
updated_at : Timestamp | null ;
}
Survey Types
opcion_multiple
abierta
calificacion
Multiple choice questions. The opciones field stores: [
{
"pregunta" : "¿Cuál es su opinión sobre...?" ,
"opciones" : [ "Muy bueno" , "Bueno" , "Regular" , "Malo" ]
}
]
Open-ended text responses. The opciones field is typically null.
Rating questions (1-5 stars). The opciones field stores: {
"min" : 1 ,
"max" : 5 ,
"labels" : [ "Muy malo" , "Malo" , "Regular" , "Bueno" , "Excelente" ]
}
The opciones field is JSONB, allowing flexible question structures. Always validate the structure when creating surveys.
respuestas_encuestas
Stores user responses to surveys.
src/types/database.types.ts
export interface RespuestasEncuestasRow {
id : UUID ;
encuesta_id : UUID ; // Foreign key to encuestas
usuario_id : UUID ; // Foreign key to usuarios
respuesta : any ; // JSONB - response data
created_at : Timestamp | null ;
}
export type RespuestasEncuestasInsert = Omit <
RespuestasEncuestasRow ,
"id" | "created_at"
> & {
id ?: UUID ;
created_at ?: Timestamp | null ;
};
The respuesta field structure depends on the survey type:
// For opcion_multiple
{
respuestaLibre : [ "Bueno" , "Regular" ] // Array of selected options
}
// For abierta
{
respuestaLibre : "Mi respuesta en texto libre..."
}
// For calificacion
{
calificacion : 4 // Rating value
}
noticias
Stores municipal news articles with optional location targeting.
src/types/database.types.ts
export interface NoticiasRow {
id : UUID ;
titulo : string ;
contenido : string ;
imagen_url : string | null ; // Supabase Storage URL
parroquia_destino : string | null ; // Target specific location
barrio_destino : string | null ;
administrador_id : UUID ; // Foreign key to administradores
created_at : Timestamp | null ;
updated_at : Timestamp | null ;
}
export type NoticiasInsert = Omit <
NoticiasRow ,
"id" | "created_at" | "updated_at"
> & {
id ?: UUID ;
imagen_url ?: string | null ;
parroquia_destino ?: string | null ;
barrio_destino ?: string | null ;
created_at ?: Timestamp | null ;
updated_at ?: Timestamp | null ;
};
Location Targeting
News articles can be targeted to specific locations:
Global : parroquia_destino = null (visible to all users)
Parish-level : parroquia_destino = "Manta" and barrio_destino = null
Neighborhood-level : Both fields specified
Database Type Export
The complete database structure is exported as a single type for Supabase client:
src/types/database.types.ts
export interface Database {
public : {
Tables : {
administradores : {
Row : AdministradoresRow ;
Insert : AdministradoresInsert ;
Update : AdministradoresUpdate ;
};
usuarios : {
Row : UsuariosRow ;
Insert : UsuariosInsert ;
Update : UsuariosUpdate ;
};
encuestas : {
Row : EncuestasRow ;
Insert : EncuestasInsert ;
Update : EncuestasUpdate ;
};
respuestas_encuestas : {
Row : RespuestasEncuestasRow ;
Insert : RespuestasEncuestasInsert ;
Update : RespuestasEncuestasUpdate ;
};
noticias : {
Row : NoticiasRow ;
Insert : NoticiasInsert ;
Update : NoticiasUpdate ;
};
reportes : {
Row : ReportesRow ;
Insert : ReportesInsert ;
Update : ReportesUpdate ;
};
};
Views : Record < string , never >;
Functions : Record < string , never >;
Enums : Record < string , never >;
};
}
Usage in Supabase Client
The database types are used when creating the Supabase client:
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
);
This provides full type safety for all database operations:
// Type-safe query
const { data , error } = await supabase
. from ( 'reportes' ) // ✅ Autocomplete for table names
. select ( '*' )
. eq ( 'estado' , 'pendiente' ); // ✅ Autocomplete for columns and values
// data is typed as ReportesRow[]
Best Practices
Always use typed operations
// ✅ Good: Type-safe
const { data } = await supabase
. from ( 'usuarios' )
. select ( '*' );
// ❌ Bad: Loses type safety
const { data } = await ( supabase . from ( 'usuarios' ) as any )
. select ( '*' );
Use Insert types for creating records
import type { ReportesInsert } from '@/types/database.types' ;
const newReport : Omit < ReportesInsert , 'usuario_id' > = {
categoria: 'baches' ,
descripcion: 'Bache grande en calle principal' ,
// ... other fields
};
Handle nullable fields properly
const report : ReportesRow = await fetchReport ();
// Check for null before using
if ( report . imagen_url ) {
displayImage ( report . imagen_url );
}
Entity Relationships
Next Steps
State Management Learn how stores interact with the database
API Reference Explore the complete API documentation