This page documents all data structures used in the 200 Mates application, including database schemas, GeoJSON formats, and configuration objects.
Mate Object
The core data structure representing a user submission.
Database Schema
interface Mate {
id : number ; // Auto-increment primary key
public_id ?: string ; // Human-readable public ID (optional)
name : string ; // Submitter's name
country : string ; // Country name (localized)
country_code ?: string ; // ISO 3166-1 alpha-3 code (e.g., "ARG")
brand : string ; // Yerba mate brand name
preparation : string ; // "amargo" | "dulce" | "tereré" | "Mate Cocido"
mate_type ?: string ; // Vessel type (e.g., "Calabaza", "Madera")
photo_path : string ; // Filename in Supabase storage bucket
lat ?: number ; // Latitude (GPS or capital fallback)
lng ?: number ; // Longitude (GPS or capital fallback)
approved : boolean | null ; // true = approved, false = rejected, null = pending
created_at : string ; // ISO 8601 timestamp
}
Example Object
{
"id" : 42 ,
"public_id" : "200M-ARG-042" ,
"name" : "Juan Pérez" ,
"country" : "Argentina" ,
"country_code" : "ARG" ,
"brand" : "La Merced" ,
"preparation" : "amargo" ,
"mate_type" : "Calabaza" ,
"photo_path" : "1709801234567_abc123.jpg" ,
"lat" : -34.6037 ,
"lng" : -58.3816 ,
"approved" : true ,
"created_at" : "2026-03-07T14:32:15.000Z"
}
If GPS coordinates are not available (lat/lng are null), the resolveCoords() function in utils.js falls back to the capital city coordinates for the given country.
Country Data
Country Object
interface Country {
name : string ; // English name
nameEs : string ; // Spanish name
namePt : string ; // Portuguese name
iso3 : string ; // ISO 3166-1 alpha-3 code
flag : string ; // Unicode flag emoji
}
Example
{
name : "Argentina" ,
nameEs : "Argentina" ,
namePt : "Argentina" ,
iso3 : "ARG" ,
flag : "🇦🇷"
}
The COUNTRIES array in data/countries.js contains 202 country objects.
ISO Tables
Two lookup tables enable conversion between country identifiers:
// Numeric ID → ISO3 (for GeoJSON)
const numericToIso3 = {
"32" : "ARG" ,
"76" : "BRA" ,
"152" : "CHL" ,
"600" : "PRY" ,
"858" : "URY" ,
// ...
};
// Country name (lowercase) → ISO3
const nameToIso3 = {
"argentina" : "ARG" ,
"brazil" : "BRA" ,
"brasil" : "BRA" ,
"chile" : "CHL" ,
"paraguay" : "PRY" ,
"uruguay" : "URY" ,
// ...
};
Capital Coordinates
const capitalCoords = {
"ARG" : { lat: - 34.6037 , lng: - 58.3816 }, // Buenos Aires
"BRA" : { lat: - 15.8267 , lng: - 47.9218 }, // Brasília
"CHL" : { lat: - 33.4489 , lng: - 70.6693 }, // Santiago
"PRY" : { lat: - 25.2637 , lng: - 57.5759 }, // Asunción
"URY" : { lat: - 34.9011 , lng: - 56.1645 }, // Montevideo
// ...
};
Capital coordinates are used as fallback locations when GPS data is unavailable. This ensures all mates can be displayed on the globe.
Country Feature
Country boundaries are stored as GeoJSON features:
interface CountryFeature {
type : "Feature" ;
id : string ; // Numeric country ID
properties : object ; // Metadata (unused in 200 Mates)
geometry : {
type : "Polygon" | "MultiPolygon" ;
coordinates : number [][][] | number [][][][];
};
iso3 ?: string ; // Added by app for lookup
}
Example
{
"type" : "Feature" ,
"id" : "32" ,
"properties" : { "name" : "Argentina" },
"geometry" : {
"type" : "MultiPolygon" ,
"coordinates" : [
[[[ -65.5 , -55.2 ], [ -66.4 , -55.2 ], ... ]],
[[[ -68.6 , -52.6 ], ... ]]
]
},
"iso3" : "ARG"
}
Data Source
GeoJSON is generated from World Atlas TopoJSON :
fetch ( "https://unpkg.com/world-atlas@2/countries-110m.json" )
. then ( r => r . ok ? r . json () : Promise . reject ())
. then ( world => {
const raw = topojson . feature ( world , world . objects . countries ). features ;
countriesGeo = raw . map ( f => ({
... f ,
iso3: numericToIso3 [ String ( parseInt ( f . id , 10 ))] || null
}));
renderPolygons ();
});
Translation Structure
i18n Object
interface Translation {
[ language : string ] : {
[ key : string ] : string ;
};
}
const i18n : Translation = {
es: { ... }, // Spanish
en: { ... }, // English
pt: { ... } // Portuguese
};
Example Keys
const i18n = {
es: {
title: "Vuelta al Mundo en unos 200 Mates" ,
statMates: "Mates" ,
statCountries: "Países" ,
formTitle: "Cebate uno" ,
placeholderName: "Nombre*" ,
placeholderCountry: "País*" ,
placeholderYerba: "Marca de yerba*" ,
bitter: "Amargo" ,
sweet: "Dulce" ,
terere: "Tereré" ,
brewedMateTea: "Mate Cocido" ,
submitBtn: "Enviar" ,
// ... 100+ keys
},
en: { ... },
pt: { ... }
};
Translation Categories
Category Keys Description Header title, statMates, statCountriesSite title and stats Globe spinHint, recentMatesGlobe UI text Form formTitle, placeholderName, submitBtnSubmission form Preparation bitter, sweet, terere, brewedMateTeaMate styles Alerts alertRequired, alertSuccess, alertErrorValidation messages Moderation modText1 - modText7Community guidelines FAQ faq1q, faq1a - faq3q, faq3aFrequently asked questions Privacy priv1text - priv8textPrivacy policy Terms terms1title - terms8textTerms of service
State Variables
Global application state in modules/state.js:
let allMates : Mate [] = []; // All approved mates from database
let countriesColored : string [] = []; // ISO3 codes of countries with mates
let countriesGeo : CountryFeature [] = []; // GeoJSON features for globe rendering
let arcInterval : number | null = null ; // setInterval ID for arc animation
let polyTimer : number | null = null ; // setTimeout ID for polygon debounce
let lastMarkerIds : string = "" ; // Cache key for marker rendering
let currentLang : string = "es" ; // Active language code
Supabase Response Types
Query Response
interface SupabaseQueryResponse {
data : Mate [] | null ;
error : {
message : string ;
details : string ;
hint : string ;
code : string ;
} | null ;
}
Storage Upload Response
interface SupabaseStorageResponse {
data : {
path : string ;
} | null ;
error : {
message : string ;
statusCode : string ;
} | null ;
}
Example Query
const { data , error } = await sb
. from ( "mates" )
. select ( "*" )
. not ( "approved" , "eq" , false )
. order ( "created_at" , { ascending: false });
if ( error ) throw error ;
allMates = data || [];
Data captured from the submission form:
interface FormSubmission {
name : string ; // User's name
country : string ; // Country (with flag emoji prefix)
countryCode : string ; // ISO3 code
brand : string ; // Yerba brand
preparation : string ; // Preparation type
mate_type : string ; // Vessel type (optional)
photo : File ; // Photo file
lat : number | null ; // GPS latitude
lng : number | null ; // GPS longitude
}
Validation Rules
Field Required Constraints nameYes Non-empty string countryYes Must match country list brandYes Non-empty string preparationYes One of: “amargo”, “dulce”, “tereré”, “Mate Cocido” mate_typeNo Free text photoYes Image file (JPG, PNG), max 5MB lat/lngNo Valid coordinates or null
Constants
Configuration Values
const SUPABASE_URL = "https://qpwwexlxiksmaaehqsev.supabase.co" ;
const SUPABASE_ANON = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ;
const sb = supabase . createClient ( SUPABASE_URL , SUPABASE_ANON );
const DEFAULT_ALTITUDE = 2.5 ; // Default globe camera altitude
const ZOOM_THRESHOLD = 1.8 ; // Altitude threshold for zoom detection
The SUPABASE_ANON key is intentionally public and has row-level security (RLS) policies enabled on the Supabase backend.
Next Steps
Supabase Integration Learn about the backend setup and queries
Internationalization Understand the translation system