898 lines
27 KiB
JavaScript
898 lines
27 KiB
JavaScript
import { supabase } from "./supabaseClient";
|
|
|
|
// Generador de código de seguimiento único alfanumérico (6 caracteres)
|
|
const generateTrackingCode = () => {
|
|
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
return "RT-" + Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join("");
|
|
};
|
|
|
|
// Helper para obtener el usuario de localStorage
|
|
const getLocalUser = () => {
|
|
return JSON.parse(localStorage.getItem("repairit_user") || "{}");
|
|
};
|
|
|
|
const mapClient = (client) => {
|
|
if (!client) return null;
|
|
if (typeof client === "string") return client;
|
|
const c = Array.isArray(client) ? client[0] : client;
|
|
if (!c) return null;
|
|
return {
|
|
...c,
|
|
_id: c.id,
|
|
};
|
|
};
|
|
|
|
const mapOrder = (order) => {
|
|
if (!order) return null;
|
|
return {
|
|
...order,
|
|
_id: order.id,
|
|
venueId: order.venue_id,
|
|
clientId: mapClient(order.clientId || order.client_id),
|
|
trackingCode: order.tracking_code,
|
|
deviceType: order.device_type,
|
|
deviceModel: order.device_model,
|
|
diagnosis: order.diagnosis || "",
|
|
budget: order.budget || null,
|
|
};
|
|
};
|
|
|
|
export const api = {
|
|
// Autenticación & Perfil
|
|
auth: {
|
|
login: async (email, password) => {
|
|
const { data, error } = await supabase.auth.signInWithPassword({
|
|
email,
|
|
password,
|
|
});
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
|
|
// Obtener perfil público asociado al usuario
|
|
const { data: profile, error: profileError } = await supabase
|
|
.from("profiles")
|
|
.select("*, organization:organizations(*), venue:venues(*)")
|
|
.eq("id", data.user.id)
|
|
.single();
|
|
|
|
if (profileError) {
|
|
throw new Error("No se pudo obtener el perfil del usuario.");
|
|
}
|
|
|
|
const userData = {
|
|
_id: profile.id,
|
|
name: profile.name,
|
|
email: data.user.email,
|
|
role: profile.role,
|
|
organizationId: profile.organization_id,
|
|
venueId: profile.venue_id,
|
|
token: data.session.access_token,
|
|
};
|
|
|
|
localStorage.setItem("repairit_token", data.session.access_token);
|
|
localStorage.setItem("repairit_user", JSON.stringify(userData));
|
|
|
|
return userData;
|
|
},
|
|
|
|
register: async (registerData) => {
|
|
// Registrar cuenta en Supabase Auth
|
|
const { data, error } = await supabase.auth.signUp({
|
|
email: registerData.email,
|
|
password: registerData.password,
|
|
options: {
|
|
data: {
|
|
name: registerData.name,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
|
|
// Crear Organización
|
|
const { data: org, error: orgError } = await supabase
|
|
.from("organizations")
|
|
.insert({ name: registerData.organizationName || "Organización RepairIT" })
|
|
.select()
|
|
.single();
|
|
|
|
if (orgError) throw new Error(orgError.message);
|
|
|
|
// Crear Sucursal por defecto
|
|
const { data: venue, error: venueError } = await supabase
|
|
.from("venues")
|
|
.insert({
|
|
organization_id: org.id,
|
|
name: registerData.venueName || "Sucursal Central",
|
|
email: registerData.venueEmail || registerData.email,
|
|
phone: registerData.venuePhone || "+54 381 4223344",
|
|
address: registerData.venueAddress || "Av. Sarmiento 1234, San Miguel de Tucumán",
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (venueError) throw new Error(venueError.message);
|
|
|
|
// Crear Perfil Público
|
|
const { error: profileError } = await supabase
|
|
.from("profiles")
|
|
.insert({
|
|
id: data.user.id,
|
|
organization_id: org.id,
|
|
venue_id: venue.id,
|
|
name: registerData.name,
|
|
role: "admin",
|
|
});
|
|
|
|
if (profileError) throw new Error(profileError.message);
|
|
|
|
return { message: "Registro completado con éxito. Por favor inicia sesión." };
|
|
},
|
|
|
|
logout: async () => {
|
|
await supabase.auth.signOut();
|
|
localStorage.removeItem("repairit_token");
|
|
localStorage.removeItem("repairit_user");
|
|
},
|
|
|
|
getProfile: async () => {
|
|
let user = getLocalUser();
|
|
|
|
if (!user.venueId || user.venueId === "undefined" || !user.organizationId) {
|
|
try {
|
|
const { data: authData } = await supabase.auth.getUser();
|
|
if (authData?.user) {
|
|
const { data: profile } = await supabase
|
|
.from("profiles")
|
|
.select("*, organization:organizations(*), venue:venues(*)")
|
|
.eq("id", authData.user.id)
|
|
.maybeSingle();
|
|
|
|
if (profile) {
|
|
user = {
|
|
_id: profile.id,
|
|
name: profile.name,
|
|
email: authData.user.email,
|
|
role: profile.role,
|
|
subscriptionPlan: profile.organization?.subscription_plan || "Multi-Taller Pro",
|
|
subscriptionStatus: profile.organization?.subscription_status || "activo",
|
|
organizationId: profile.organization_id,
|
|
venueId: profile.venue_id,
|
|
};
|
|
localStorage.setItem("repairit_user", JSON.stringify(user));
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error("Error al autorecuperar perfil:", e);
|
|
}
|
|
}
|
|
|
|
if (!user.venueId || user.venueId === "undefined") {
|
|
return { name: "Sucursal Central", address: "Av. Sarmiento 1234, San Miguel de Tucumán", phone: "+54 381 4223344", email: "central@repairit.cloud" };
|
|
}
|
|
|
|
const { data, error } = await supabase
|
|
.from("venues")
|
|
.select("*")
|
|
.eq("id", user.venueId)
|
|
.maybeSingle();
|
|
|
|
if (error || !data) {
|
|
return { name: "Sucursal Central", address: "Av. Sarmiento 1234, San Miguel de Tucumán", phone: "+54 381 4223344", email: "central@repairit.cloud" };
|
|
}
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
updateProfile: async (profileData) => {
|
|
const user = getLocalUser();
|
|
if (!user.venueId || user.venueId === "undefined") {
|
|
throw new Error("No hay sucursal activa en sesión.");
|
|
}
|
|
const { data, error } = await supabase
|
|
.from("venues")
|
|
.update(profileData)
|
|
.eq("id", user.venueId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
getVenues: async () => {
|
|
let user = getLocalUser();
|
|
if (!user.organizationId || user.organizationId === "undefined") {
|
|
try {
|
|
const { data: authData } = await supabase.auth.getUser();
|
|
if (authData?.user) {
|
|
const { data: profile } = await supabase
|
|
.from("profiles")
|
|
.select("organization_id")
|
|
.eq("id", authData.user.id)
|
|
.maybeSingle();
|
|
if (profile) {
|
|
user.organizationId = profile.organization_id;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error("Error al autorecuperar sucursales:", e);
|
|
}
|
|
}
|
|
|
|
if (!user.organizationId) return [];
|
|
|
|
const { data, error } = await supabase
|
|
.from("venues")
|
|
.select("*")
|
|
.eq("organization_id", user.organizationId);
|
|
|
|
if (error || !data) return [];
|
|
return data.map((v) => ({ ...v, _id: v.id }));
|
|
},
|
|
|
|
createVenue: async (venueData) => {
|
|
const user = getLocalUser();
|
|
const { data, error } = await supabase
|
|
.from("venues")
|
|
.insert({
|
|
organization_id: user.organizationId,
|
|
...venueData,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
updateVenue: async (id, venueData) => {
|
|
const user = getLocalUser();
|
|
const { data, error } = await supabase
|
|
.from("venues")
|
|
.update(venueData)
|
|
.eq("id", id)
|
|
.eq("organization_id", user.organizationId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
switchVenue: async (venueId) => {
|
|
const user = getLocalUser();
|
|
const { error } = await supabase
|
|
.from("profiles")
|
|
.update({ venue_id: venueId })
|
|
.eq("id", user._id || user.id);
|
|
|
|
if (error) throw new Error(error.message);
|
|
|
|
return { venueId };
|
|
},
|
|
},
|
|
|
|
// Clientes
|
|
clients: {
|
|
getAll: async () => {
|
|
const user = getLocalUser();
|
|
if (!user.organizationId || !user.venueId || user.organizationId === "undefined" || user.venueId === "undefined") {
|
|
return [];
|
|
}
|
|
const { data, error } = await supabase
|
|
.from("clients")
|
|
.select("*")
|
|
.eq("organization_id", user.organizationId)
|
|
.eq("venue_id", user.venueId);
|
|
|
|
if (error) throw new Error(error.message);
|
|
return data.map((c) => ({ ...c, _id: c.id }));
|
|
},
|
|
|
|
create: async (clientData) => {
|
|
const user = getLocalUser();
|
|
|
|
// Buscar si ya existe por DNI dentro de la sucursal
|
|
const { data: existingClient } = await supabase
|
|
.from("clients")
|
|
.select("*")
|
|
.eq("organization_id", user.organizationId)
|
|
.eq("venue_id", user.venueId)
|
|
.eq("dni", clientData.dni)
|
|
.maybeSingle();
|
|
|
|
if (existingClient) {
|
|
// Actualizar datos
|
|
const { data, error } = await supabase
|
|
.from("clients")
|
|
.update(clientData)
|
|
.eq("id", existingClient.id)
|
|
.select()
|
|
.single();
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
}
|
|
|
|
// Crear nuevo
|
|
const { data, error } = await supabase
|
|
.from("clients")
|
|
.insert({
|
|
organization_id: user.organizationId,
|
|
venue_id: user.venueId,
|
|
...clientData,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
update: async (id, clientData) => {
|
|
const user = getLocalUser();
|
|
const { data, error } = await supabase
|
|
.from("clients")
|
|
.update(clientData)
|
|
.eq("id", id)
|
|
.eq("organization_id", user.organizationId)
|
|
.eq("venue_id", user.venueId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
delete: async (id) => {
|
|
const user = getLocalUser();
|
|
const { error } = await supabase
|
|
.from("clients")
|
|
.delete()
|
|
.eq("id", id)
|
|
.eq("organization_id", user.organizationId)
|
|
.eq("venue_id", user.venueId);
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { message: "Cliente eliminado correctamente." };
|
|
},
|
|
},
|
|
|
|
// Órdenes
|
|
orders: {
|
|
getAll: async () => {
|
|
const user = getLocalUser();
|
|
if (!user.venueId || user.venueId === "undefined") return [];
|
|
const { data, error } = await supabase
|
|
.from("orders")
|
|
.select("*, clientId:clients(*)")
|
|
.eq("venue_id", user.venueId);
|
|
|
|
if (error) throw new Error(error.message);
|
|
|
|
return data.map(mapOrder);
|
|
},
|
|
|
|
create: async (orderData) => {
|
|
const user = getLocalUser();
|
|
const trackingCode = generateTrackingCode();
|
|
|
|
const { data, error } = await supabase
|
|
.from("orders")
|
|
.insert({
|
|
venue_id: user.venueId,
|
|
client_id: orderData.clientId,
|
|
tracking_code: trackingCode,
|
|
device_type: orderData.deviceType,
|
|
device_model: orderData.deviceModel,
|
|
accessories: orderData.accessories,
|
|
cosmetic: orderData.cosmetic,
|
|
issue: orderData.issue,
|
|
date: orderData.date,
|
|
time: orderData.time,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return mapOrder(data);
|
|
},
|
|
|
|
update: async (id, orderData) => {
|
|
const { data, error } = await supabase
|
|
.from("orders")
|
|
.update(orderData)
|
|
.eq("id", id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return mapOrder(data);
|
|
},
|
|
|
|
updateStatus: async (id, status, text) => {
|
|
// Obtener orden actual para leer su historial
|
|
const { data: order } = await supabase
|
|
.from("orders")
|
|
.select("history")
|
|
.eq("id", id)
|
|
.single();
|
|
|
|
const history = Array.isArray(order?.history) ? order.history : [];
|
|
const dateStr = new Date().toLocaleDateString("es-AR") + " " + new Date().toLocaleTimeString("es-AR", { hour: "2-digit", minute: "2-digit" });
|
|
|
|
history.push({ date: dateStr, text });
|
|
|
|
const { data, error } = await supabase
|
|
.from("orders")
|
|
.update({ status, history })
|
|
.eq("id", id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return mapOrder(data);
|
|
},
|
|
|
|
updateDiagnosisAndBudget: async (id, { diagnosis, budget, status }) => {
|
|
const { data: order } = await supabase
|
|
.from("orders")
|
|
.select("history")
|
|
.eq("id", id)
|
|
.single();
|
|
|
|
const history = Array.isArray(order?.history) ? order.history : [];
|
|
const dateStr = new Date().toLocaleDateString("es-AR") + " " + new Date().toLocaleTimeString("es-AR", { hour: "2-digit", minute: "2-digit" });
|
|
|
|
const statusText = status ? status.toUpperCase() : "ACTUALIZADO";
|
|
history.push({ date: dateStr, text: `Diagnóstico / Presupuesto cargado (${statusText})` });
|
|
|
|
const updateData = { history };
|
|
if (diagnosis !== undefined) updateData.diagnosis = diagnosis;
|
|
if (budget !== undefined) updateData.budget = budget;
|
|
if (status) updateData.status = status;
|
|
|
|
const { data, error } = await supabase
|
|
.from("orders")
|
|
.update(updateData)
|
|
.eq("id", id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return mapOrder(data);
|
|
},
|
|
|
|
track: async (code) => {
|
|
const cleanCode = (code || "").trim().toLowerCase();
|
|
if (cleanCode === "demo-id" || cleanCode === "demo") {
|
|
return {
|
|
id: "demo-id-12345",
|
|
_id: "demo-id-12345",
|
|
trackingCode: "demo-id",
|
|
deviceType: "Notebook",
|
|
deviceModel: "Lenovo ThinkPad E14 Gen 2",
|
|
issue: "El equipo no enciende tras descarga eléctrica. Parpadea LED de carga.",
|
|
diagnosis: "Falla en circuito primario de alimentación 19V. Cortocircuito en mosfet de entrada y chip IC PMIC dañados.",
|
|
status: "presupuestado",
|
|
clientId: {
|
|
name: "Juan Pérez (Cliente Demo)",
|
|
dni: "38.450.123",
|
|
phone: "+54 381 555-0192",
|
|
email: "juan.demo@email.com"
|
|
},
|
|
venueId: {
|
|
name: "Sucursal Central (Demo)",
|
|
address: "Av. Sarmiento 1234, San Miguel de Tucumán"
|
|
},
|
|
budget: {
|
|
items: [
|
|
{ desc: "Reemplazo de Mosfet e IC Regulador PMIC", price: 18500 },
|
|
{ desc: "Mantenimiento térmico y limpieza de placa madre", price: 6500 }
|
|
],
|
|
approved: false,
|
|
dateApproved: null
|
|
},
|
|
history: [
|
|
{ date: "24/07/2026 09:30", text: "Equipo ingresado en recepción." },
|
|
{ date: "24/07/2026 11:15", text: "Diagnóstico finalizado por el laboratorio técnico." },
|
|
{ date: "24/07/2026 11:30", text: "Presupuesto detallado generado y disponible online." }
|
|
]
|
|
};
|
|
}
|
|
|
|
const { data, error } = await supabase
|
|
.from("orders")
|
|
.select("*, venueId:venues(*), clientId:clients(*)")
|
|
.eq("tracking_code", code)
|
|
.maybeSingle();
|
|
|
|
if (error) throw new Error(error.message);
|
|
if (!data) throw new Error("Orden de servicio no encontrada.");
|
|
|
|
const mapped = mapOrder(data);
|
|
if (mapped && data.venueId) {
|
|
const v = Array.isArray(data.venueId) ? data.venueId[0] : data.venueId;
|
|
mapped.venueId = v ? { ...v, _id: v.id } : null;
|
|
}
|
|
return mapped;
|
|
},
|
|
|
|
approveBudget: async (code) => {
|
|
const cleanCode = (code || "").trim().toLowerCase();
|
|
if (cleanCode === "demo-id" || cleanCode === "demo") {
|
|
return {
|
|
id: "demo-id-12345",
|
|
_id: "demo-id-12345",
|
|
trackingCode: "demo-id",
|
|
deviceType: "Notebook",
|
|
deviceModel: "Lenovo ThinkPad E14 Gen 2",
|
|
issue: "El equipo no enciende tras descarga eléctrica. Parpadea LED de carga.",
|
|
diagnosis: "Falla en circuito primario de alimentación 19V. Cortocircuito en mosfet de entrada y chip IC PMIC dañados.",
|
|
status: "en_reparacion",
|
|
clientId: {
|
|
name: "Juan Pérez (Cliente Demo)",
|
|
dni: "38.450.123",
|
|
phone: "+54 381 555-0192",
|
|
email: "juan.demo@email.com"
|
|
},
|
|
venueId: {
|
|
name: "Sucursal Central (Demo)",
|
|
address: "Av. Sarmiento 1234, San Miguel de Tucumán"
|
|
},
|
|
budget: {
|
|
items: [
|
|
{ desc: "Reemplazo de Mosfet e IC Regulador PMIC", price: 18500 },
|
|
{ desc: "Mantenimiento térmico y limpieza de placa madre", price: 6500 }
|
|
],
|
|
approved: true,
|
|
dateApproved: new Date().toLocaleDateString("es-AR")
|
|
},
|
|
history: [
|
|
{ date: "24/07/2026 09:30", text: "Equipo ingresado en recepción." },
|
|
{ date: "24/07/2026 11:15", text: "Diagnóstico finalizado por el laboratorio técnico." },
|
|
{ date: "24/07/2026 11:30", text: "Presupuesto detallado generado y disponible online." },
|
|
{ date: new Date().toLocaleDateString("es-AR") + " " + new Date().toLocaleTimeString("es-AR", { hour: "2-digit", minute: "2-digit" }), text: "Presupuesto aprobado online por el cliente (Demo)." }
|
|
]
|
|
};
|
|
}
|
|
|
|
const { data: order } = await supabase
|
|
.from("orders")
|
|
.select("budget, history")
|
|
.eq("tracking_code", code)
|
|
.single();
|
|
|
|
const budget = order.budget || {};
|
|
budget.approved = true;
|
|
budget.dateApproved = new Date().toLocaleDateString("es-AR");
|
|
|
|
const history = Array.isArray(order?.history) ? order.history : [];
|
|
const dateStr = new Date().toLocaleDateString("es-AR") + " " + new Date().toLocaleTimeString("es-AR", { hour: "2-digit", minute: "2-digit" });
|
|
|
|
history.push({ date: dateStr, text: "Presupuesto aprobado online por el cliente." });
|
|
|
|
const { data, error } = await supabase
|
|
.from("orders")
|
|
.update({
|
|
status: "en_reparacion",
|
|
budget,
|
|
history,
|
|
})
|
|
.eq("tracking_code", code)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return mapOrder(data);
|
|
},
|
|
},
|
|
|
|
// Inventario
|
|
inventory: {
|
|
getAll: async () => {
|
|
const user = getLocalUser();
|
|
if (!user.venueId || user.venueId === "undefined") return [];
|
|
const { data, error } = await supabase
|
|
.from("inventory")
|
|
.select("*")
|
|
.eq("venue_id", user.venueId);
|
|
|
|
if (error) throw new Error(error.message);
|
|
return data.map(item => ({ ...item, _id: item.id }));
|
|
},
|
|
|
|
create: async (itemData) => {
|
|
const user = getLocalUser();
|
|
const { data, error } = await supabase
|
|
.from("inventory")
|
|
.insert({
|
|
organization_id: user.organizationId,
|
|
venue_id: user.venueId,
|
|
code: itemData.code,
|
|
name: itemData.name,
|
|
description: itemData.description,
|
|
category: itemData.category,
|
|
quantity: itemData.quantity,
|
|
min_quantity: itemData.minQuantity,
|
|
price: itemData.price,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
update: async (id, itemData) => {
|
|
const user = getLocalUser();
|
|
const { data, error } = await supabase
|
|
.from("inventory")
|
|
.update({
|
|
code: itemData.code,
|
|
name: itemData.name,
|
|
description: itemData.description,
|
|
category: itemData.category,
|
|
quantity: itemData.quantity,
|
|
min_quantity: itemData.minQuantity,
|
|
price: itemData.price,
|
|
})
|
|
.eq("id", id)
|
|
.eq("venue_id", user.venueId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
delete: async (id) => {
|
|
const user = getLocalUser();
|
|
const { error } = await supabase
|
|
.from("inventory")
|
|
.delete()
|
|
.eq("id", id)
|
|
.eq("venue_id", user.venueId);
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { message: "Artículo eliminado." };
|
|
},
|
|
|
|
adjustStock: async (id, amount) => {
|
|
const user = getLocalUser();
|
|
const { data: item } = await supabase
|
|
.from("inventory")
|
|
.select("quantity")
|
|
.eq("id", id)
|
|
.eq("venue_id", user.venueId)
|
|
.single();
|
|
|
|
const newQty = Math.max(0, (item?.quantity || 0) + amount);
|
|
|
|
const { data, error } = await supabase
|
|
.from("inventory")
|
|
.update({ quantity: newQty })
|
|
.eq("id", id)
|
|
.eq("venue_id", user.venueId)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
bulkUpsert: async (itemsArray) => {
|
|
const { data, error } = await supabase
|
|
.from("inventory")
|
|
.upsert(itemsArray, { onConflict: "venue_id, code" });
|
|
|
|
if (error) throw new Error(error.message);
|
|
return data;
|
|
},
|
|
},
|
|
|
|
// Ventas de Caja (POS)
|
|
sales: {
|
|
getAll: async (venueId = null) => {
|
|
const user = getLocalUser();
|
|
let query = supabase
|
|
.from("sales")
|
|
.select("*, venue:venues(name), profile:profiles(name)");
|
|
|
|
if (venueId && venueId !== "todas") {
|
|
query = query.eq("venue_id", venueId);
|
|
} else if (user.role !== "superadmin") {
|
|
if (user.subscriptionPlan === "Multi-Taller Pro") {
|
|
query = query.eq("organization_id", user.organizationId);
|
|
} else {
|
|
query = query.eq("venue_id", user.venueId);
|
|
}
|
|
}
|
|
|
|
const { data, error } = await query.order("created_at", { ascending: false });
|
|
|
|
if (error) throw new Error(error.message);
|
|
return data.map((s) => ({
|
|
...s,
|
|
_id: s.id,
|
|
venueName: s.venue?.name || "Desconocida",
|
|
cajero: s.profile?.name || "Sistema",
|
|
}));
|
|
},
|
|
|
|
create: async (saleData) => {
|
|
const user = getLocalUser();
|
|
|
|
const { data: sale, error: saleErr } = await supabase
|
|
.from("sales")
|
|
.insert({
|
|
organization_id: user.organizationId,
|
|
venue_id: user.venueId,
|
|
total: saleData.total,
|
|
payment_method: saleData.paymentMethod,
|
|
items: saleData.items,
|
|
created_by: user._id || user.id,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (saleErr) throw new Error(saleErr.message);
|
|
|
|
// Descontar stock para cada producto
|
|
for (const item of saleData.items) {
|
|
const { data: currentItem } = await supabase
|
|
.from("inventory")
|
|
.select("quantity")
|
|
.eq("id", item.id)
|
|
.single();
|
|
|
|
const newQty = Math.max(0, (currentItem?.quantity || 0) - item.quantity);
|
|
|
|
await supabase
|
|
.from("inventory")
|
|
.update({ quantity: newQty })
|
|
.eq("id", item.id);
|
|
}
|
|
|
|
return { ...sale, _id: sale.id };
|
|
},
|
|
},
|
|
|
|
// SuperAdmin
|
|
super: {
|
|
getOrganizations: async () => {
|
|
const { data, error } = await supabase
|
|
.from("organizations")
|
|
.select("*, venues(id), profiles(id)");
|
|
|
|
if (error) throw new Error(error.message);
|
|
|
|
return data.map((org) => ({
|
|
...org,
|
|
_id: org.id,
|
|
subscriptionPlan: org.subscription_plan,
|
|
subscriptionStatus: org.subscription_status,
|
|
createdAt: org.created_at,
|
|
venuesCount: Array.isArray(org.venues) ? org.venues.length : 0,
|
|
usersCount: Array.isArray(org.profiles) ? org.profiles.length : 0,
|
|
}));
|
|
},
|
|
|
|
updateSubscription: async (id, subscriptionPlan, subscriptionStatus) => {
|
|
const updateData = {};
|
|
if (subscriptionPlan) updateData.subscription_plan = subscriptionPlan;
|
|
if (subscriptionStatus) updateData.subscription_status = subscriptionStatus;
|
|
|
|
const { data, error } = await supabase
|
|
.from("organizations")
|
|
.update(updateData)
|
|
.eq("id", id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return {
|
|
...data,
|
|
_id: data.id,
|
|
subscriptionPlan: data.subscription_plan,
|
|
subscriptionStatus: data.subscription_status,
|
|
createdAt: data.created_at,
|
|
};
|
|
},
|
|
|
|
getUsers: async () => {
|
|
const { data, error } = await supabase
|
|
.from("profiles")
|
|
.select("*, organizationId:organizations(name), venueId:venues(name)");
|
|
|
|
if (error) throw new Error(error.message);
|
|
|
|
return data.map((profile) => ({
|
|
...profile,
|
|
_id: profile.id,
|
|
createdAt: profile.created_at,
|
|
organizationId: profile.organizationId ? { ...profile.organizationId, _id: profile.organizationId.id } : null,
|
|
venueId: profile.venueId ? { ...profile.venueId, _id: profile.venueId.id } : null,
|
|
}));
|
|
},
|
|
|
|
updateUserRole: async (id, role) => {
|
|
const { data, error } = await supabase
|
|
.from("profiles")
|
|
.update({ role })
|
|
.eq("id", id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { ...data, _id: data.id };
|
|
},
|
|
|
|
manageUser: async (userId, name, email, role, password = null) => {
|
|
const { error } = await supabase.rpc("super_admin_manage_user", {
|
|
p_user_id: userId,
|
|
p_new_name: name,
|
|
p_new_email: email,
|
|
p_new_role: role,
|
|
p_new_password: password,
|
|
});
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { message: "Usuario actualizado correctamente." };
|
|
},
|
|
|
|
deleteUser: async (userId) => {
|
|
const { error } = await supabase.rpc("super_admin_delete_user", {
|
|
p_user_id: userId,
|
|
});
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { message: "Usuario eliminado correctamente." };
|
|
},
|
|
},
|
|
|
|
// Cuentas de Acceso de Venue (Para Administradores)
|
|
venueAccounts: {
|
|
getAll: async () => {
|
|
const user = getLocalUser();
|
|
if (!user.organizationId || user.organizationId === "undefined") return [];
|
|
const { data, error } = await supabase
|
|
.from("profiles")
|
|
.select("*, venue:venues(*)")
|
|
.eq("organization_id", user.organizationId)
|
|
.eq("role", "tecnico");
|
|
|
|
if (error) throw new Error(error.message);
|
|
return data.map((p) => ({
|
|
...p,
|
|
_id: p.id,
|
|
venueId: p.venue ? { ...p.venue, _id: p.venue.id } : null,
|
|
}));
|
|
},
|
|
|
|
create: async (accountData) => {
|
|
const { data, error } = await supabase.rpc("create_venue_account", {
|
|
p_email: accountData.email,
|
|
p_password: accountData.password,
|
|
p_name: accountData.name,
|
|
p_venue_id: accountData.venueId,
|
|
});
|
|
|
|
if (error) throw new Error(error.message);
|
|
return data;
|
|
},
|
|
|
|
delete: async (accountId) => {
|
|
const { error } = await supabase.rpc("delete_venue_account", {
|
|
p_user_id: accountId,
|
|
});
|
|
|
|
if (error) throw new Error(error.message);
|
|
return { message: "Cuenta eliminada correctamente." };
|
|
},
|
|
},
|
|
};
|