Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ProyectoFerretek/FerreMarket/llms.txt
Use this file to discover all available pages before exploring further.
FerreMarket uses Supabase (PostgreSQL) as its backend database. All data operations — reads, inserts, updates, and deletes — flow through the Supabase JavaScript client initialised in src/lib/supabase/Supabase.tsx. The schema described on this page is inferred from the TypeScript interfaces in src/types/index.ts and the query patterns in src/data/mockData.ts; refer to your live Supabase project for the authoritative column definitions and constraints.
Supabase Client Initialisation
The client is created once and exported as a default singleton from src/lib/supabase/Supabase.tsx. It reads the project URL and anonymous key from Vite environment variables so that no credentials are compiled into source code.
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
const supabase = createClient(supabaseUrl, supabaseKey);
export default supabase;
Both variables must be set in your .env file (see .env.example):
VITE_SUPABASE_URL="https://<project-ref>.supabase.co"
VITE_SUPABASE_ANON_KEY="<your-anon-key>"
Never commit real keys to version control. The anon key is safe to expose to the browser only because Row Level Security (RLS) policies restrict what it can access. The service role key must stay server-side (Edge Functions only).
Tables
The following tables are queried directly by the application. Column names are inferred from the select, insert, and update call arguments in src/data/mockData.ts.
productos
Stores the hardware product catalog.
| Column | Type (inferred) | Description |
|---|
id | integer / bigint | Primary key — used with Number(id) in queries |
sku | text | Stock-keeping unit code; also used as the Cloudflare R2 object key |
nombre | text | Product display name |
descripcion | text | Full product description |
precio | numeric | Unit price (CLP) |
categoria | text | Category identifier (foreign key to categorias.id) |
stock | integer | Units currently in inventory |
imagen | text | CDN URL to the product image |
destacado | boolean | Featured flag |
clientes
Stores both individual and business customer records. A single tipo column ('individual' or 'empresarial') discriminates the subtype.
| Column | Type (inferred) | Description |
|---|
id | integer / bigint | Primary key |
tipo | text | Customer subtype: 'individual' or 'empresarial' |
nombre | text | First name (individual) or commercial name (empresa) |
apellidos | text | Last name(s) — individual only |
email | text | Contact email; used as a lookup key in obtenerClienteIdByMail |
telefono | text | Contact phone number |
direccion | text | Postal address |
run | text | Chilean national ID — individual only |
rut | text | Chilean tax ID — empresarial only |
giro | text | Business activity description — empresarial only |
razonsocial | text | Legal registered name — empresarial only (note: stored lowercase) |
estado | text | 'activo' or 'inactivo' |
notas | text | Internal free-text notes |
ultimacompra | text | ISO 8601 timestamp of last purchase (updated on every new Venta) |
fechaCreacion | text | ISO 8601 creation timestamp |
ultimaModificacion | text | ISO 8601 last-modified timestamp |
ventas
Stores sales transaction headers. Line items are stored separately in ventas_productos.
| Column | Type (inferred) | Description |
|---|
id | integer / bigint | Primary key; returned via .select() after insert |
fecha | text | ISO 8601 timestamp of the sale (America/Santiago timezone) |
cliente | integer | Foreign key → clientes.id |
total | numeric | Grand total of the transaction (CLP) |
metodo_pago | text | Payment method (mapped to metodoPago in the TypeScript layer) |
estado | text | 'completada', 'pendiente', or 'cancelada' |
ventas_productos
Join table that stores the line items for each sale. This is queried alongside ventas to reconstitute the full Venta object with its productos array.
| Column | Type (inferred) | Description |
|---|
id | integer / bigint | Primary key |
venta_id | integer | Foreign key → ventas.id |
producto_id | integer | Foreign key → productos.id |
cantidad | integer | Quantity of the product sold |
precio_unitario | numeric | Unit price at the time of sale (snapshot) |
ventas_productos is also used to compute “featured products” by ordering on cantidad DESC — the products appearing most often across all sales are surfaced as top sellers.
usuarios
Stores application user profiles. Each row is linked to a Supabase Auth identity via the uid column, which holds the Auth UUID.
| Column | Type (inferred) | Description |
|---|
id | integer / bigint | Internal primary key |
uid | uuid / text | Supabase Auth UUID (auth.users.id) |
nombre | text | Display name |
email | text | Login email address |
rol | text | 'admin', 'usuario', or 'cliente' |
estado | text | 'activo' or 'inactivo' |
fecha_creacion | text | ISO 8601 creation timestamp (mapped to fechaCreacion) |
ultima_modificacion | text | ISO 8601 last-modified timestamp (mapped to ultimaModificacion) |
ultimo_acceso | text | ISO 8601 last login timestamp (mapped to ultimoAcceso) |
Stores discount promotion definitions and their running analytics. Column names follow a snake_case convention that is remapped to camelCase in the TypeScript layer by obtenerPromociones.
| Column | Type (inferred) | Description |
|---|
id | integer / bigint | Primary key |
codigo | text | Promo code entered at checkout |
nombre | text | Internal promotion name |
tipo | text | Discount type (e.g. 'porcentaje', 'monto_fijo') |
valor | numeric | Discount magnitude |
valor_maximo | numeric | Maximum discount cap (CLP) for percentage promotions |
monto_minimo | numeric | Minimum cart total (CLP) to activate the promo |
fecha_inicio | text | ISO 8601 start date/time |
fecha_fin | text | ISO 8601 expiry date/time |
limite_total_usos | integer | Global redemption cap |
limite_usos_por_cliente | integer | Per-customer redemption cap |
usos_actuales | integer | Running redemption count |
estado | text | Promotion status |
aplica_a | text | Applicability scope: 'todos', 'productos', or 'categorias' |
productos_incluidos | text[] | Array of product IDs the promo applies to |
productos_excluidos | text[] | Array of product IDs excluded from the promo |
categorias_incluidas | text[] | Array of category IDs the promo applies to |
categorias_excluidas | text[] | Array of category IDs excluded from the promo |
tipo_cliente | text | Customer type restriction |
combinable | boolean | Whether the promo stacks with others |
descripcion | text | Marketing description |
fecha_creacion | text | ISO 8601 creation timestamp |
creado_por | text | Name/ID of the creating user |
ingreso_generado | numeric | Total revenue attributed to this promo (CLP) |
valor_promedio_compra | numeric | Average order value of redemption transactions (CLP) |
tasa_conversion | numeric | Redemption conversion rate |
horarios_uso | text[] | Time-slot usage log |
productos_vendidos | text[] | Product IDs sold under this promotion |
Common Query Patterns
The following examples are extracted directly from src/data/mockData.ts and illustrate the Supabase query patterns used across the application.
Fetch all products
const { data: productos } = await supabase
.from("productos")
.select("*");
Calculate total inventory value
Selects only the two columns needed for the computation to minimise payload size.
const { data: productos, error } = await supabase
.from("productos")
.select("precio, stock");
// Then sum client-side:
for (const producto of productos) {
totalValor += producto.precio * (producto.stock || 0);
}
Count products in a category
const { data: productos, error } = await supabase
.from("productos")
.select("id")
.eq("categoria", categoriaId);
const cantidadProductos = productos ? productos.length : 0;
Fetch top-selling products (by quantity across all sales)
const { data: productos } = await supabase
.from("ventas_productos")
.select("producto_id, cantidad")
.order("cantidad", { ascending: false })
.limit(limit);
Fetch recent sales (ordered, paginated)
const { data: ventas, error } = await supabase
.from("ventas")
.select("*")
.order("fecha", { ascending: false })
.limit(cantidad);
Get a customer’s total spend
const { data: ventas, error } = await supabase
.from("ventas")
.select("total")
.eq("cliente", Number(clienteId));
const totalCompras = ventas.reduce((acc, venta) => acc + (venta.total || 0), 0);
Look up a customer ID by email
const { data, error } = await supabase
.from("clientes")
.select("id")
.eq("email", email)
.single();
Get the internal user ID from a Supabase Auth UUID
// Pattern inferred from agregarUsuario — uid is stored alongside the internal id
const { data: usuarios } = await supabase
.from("usuarios")
.select("*");
// The uid column holds the Supabase Auth UUID
// e.g. usuario.uid === session.user.id
Row Level Security
Supabase Row Level Security (RLS) should be enabled on every table. The anon key bundled with the frontend client has read-only (or no) access to sensitive tables by default; write operations are gated behind authenticated sessions and RLS policies.
Recommended policy approach:
| Table | Anon read | Auth read | Auth write | Notes |
|---|
productos | ✅ public | ✅ | admin only | Product catalog can be public |
clientes | ❌ | ✅ own record | admin / self | PII — restrict tightly |
ventas | ❌ | ✅ own record | admin / POS role | Financial data |
ventas_productos | ❌ | ✅ own record | admin / POS role | Linked to ventas |
usuarios | ❌ | ✅ own record | admin only | Use service role for creation |
promociones | ✅ active only | ✅ | admin only | Active promos needed at checkout |
Admin operations that require the Supabase service role key (bypassing RLS) must never run in the browser. Route them exclusively through Edge Functions as FerreMarket does for user management.
Edge Functions
Three Supabase Edge Functions are invoked from src/context/AuthContext.tsx for user-lifecycle operations that require elevated privileges. These functions run server-side with the service role key and are never exposed to the client directly.
create-user
Creates a new Supabase Auth user (auth.users) and returns the resulting user object. Called during user onboarding from the admin panel.
const { data, error } = await supabase.functions.invoke("create-user", {
body: {
email: email.toLowerCase(),
password: password,
},
});
delete-user
Deletes a Supabase Auth user by their UUID. Called when an admin removes a user from the system.
const { error } = await supabase.functions.invoke("delete-user", {
method: "DELETE",
body: {
userId: uid,
},
});
send-password-recovery-email
Triggers a password recovery email for the specified address. Used on the forgot-password flow.
const { error } = await supabase.functions.invoke("send-password-recovery-email", {
body: {
email: email.toLowerCase(),
},
});
Deploy Edge Functions with supabase functions deploy <function-name>. Each function should validate the caller’s JWT and confirm the requesting user has the admin role before performing destructive operations.
This schema is inferred from the application source code in src/data/mockData.ts and src/types/index.ts. Always consult your live Supabase project’s Table Editor and Database → Schema view for the authoritative column types, constraints, indexes, and RLS policies.