Skip to main content

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.
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;
}
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

CategoryDescription
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

Multiple choice questions. The opciones field stores:
[
  {
    "pregunta": "¿Cuál es su opinión sobre...?",
    "opciones": ["Muy bueno", "Bueno", "Regular", "Malo"]
  }
]
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:
src/lib/supabase.ts
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

// ✅ Good: Type-safe
const { data } = await supabase
  .from('usuarios')
  .select('*');

// ❌ Bad: Loses type safety
const { data } = await (supabase.from('usuarios') as any)
  .select('*');
import type { ReportesInsert } from '@/types/database.types';

const newReport: Omit<ReportesInsert, 'usuario_id'> = {
  categoria: 'baches',
  descripcion: 'Bache grande en calle principal',
  // ... other fields
};
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

Build docs developers (and LLMs) love