Skip to content

Commit 3b94968

Browse files
committed
añadiendo vista y logica del sorteo
1 parent a6a32a2 commit 3b94968

File tree

14 files changed

+903
-27
lines changed

14 files changed

+903
-27
lines changed

backend/src/controllers/auth.controllers.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,7 @@ export const login = async (req, res) => {
128128
return res.status(400).send("Error en login: " + error.message);
129129
}
130130
};
131+
132+
133+
134+

backend/src/controllers/rifa.controllers.js

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { createRifa, getRifasUser, getRifa, updateRifa, decrementRifaTickets} from "../models/rifa.model.js";
1+
import { createRifa, getRifasUser, getRifa, updateRifa, decrementRifaTickets, rifaModel} from "../models/rifa.model.js";
2+
import { ticketModel } from "../models/ticket.models.js";
23
import admin from "../firebase.js";
34
import { supabase } from "../db.js";
5+
import { getUserByUid } from "../models/user.model.js";
46

57

68
export const createRifas = async (req, res) => {
@@ -141,4 +143,143 @@ export const decrementRifaTicket = async (req, res) => {
141143
res.status(500).json({ error: "Error interno del servidor" });
142144
}
143145

144-
}
146+
}
147+
148+
// Función para generar un número aleatorio verificable
149+
function generateVerifiableRandom(seed, max) {
150+
// Usamos un algoritmo simple pero verificable
151+
// En producción se puede usar un algoritmo más robusto
152+
let hash = 0;
153+
for (let i = 0; i < seed.length; i++) {
154+
hash = ((hash << 5) - hash) + seed.charCodeAt(i);
155+
hash |= 0; // Convertir a entero de 32 bits
156+
}
157+
158+
// Convertimos a positivo y tomamos el módulo para el rango requerido
159+
const positiveHash = Math.abs(hash);
160+
return positiveHash % max;
161+
}
162+
163+
// Controlador para verificar si una rifa está lista para sorteo
164+
export const checkRifaForDraw = async (req, res) => {
165+
try {
166+
const { rifaId } = req.params;
167+
const { token } = req.body;
168+
169+
if (!token) {
170+
return res.status(401).json({ error: "Autenticación requerida" });
171+
}
172+
173+
// Verificar que el usuario sea el creador de la rifa
174+
const decoded = await admin.auth().verifyIdToken(token);
175+
const { uid } = decoded;
176+
177+
const { data: rifa } = await rifaModel.getRifa(rifaId);
178+
if (!rifa || rifa.userID !== uid) {
179+
return res.status(403).json({ error: "No tienes permiso para realizar el sorteo de esta rifa" });
180+
}
181+
182+
const { isReady, reason } = await rifaModel.isRifaReadyForDraw(rifaId);
183+
184+
return res.status(200).json({
185+
isReady,
186+
reason: !isReady ? reason : null,
187+
message: isReady ? "La rifa está lista para sorteo" : "La rifa no está lista para sorteo"
188+
});
189+
190+
} catch (error) {
191+
console.error("Error al verificar rifa para sorteo:", error);
192+
return res.status(500).json({ error: "Error interno al verificar la rifa" });
193+
}
194+
};
195+
196+
// Controlador para realizar el sorteo
197+
export const realizarSorteo = async (req, res) => {
198+
try {
199+
const { rifaId } = req.params;
200+
const { token } = req.body;
201+
202+
if (!token) {
203+
return res.status(401).json({ error: "Autenticación requerida" });
204+
}
205+
206+
// Verificar que el usuario sea el creador de la rifa
207+
const decoded = await admin.auth().verifyIdToken(token);
208+
const { uid } = decoded;
209+
210+
const { data: rifa } = await rifaModel.getRifa(rifaId);
211+
if (!rifa || rifa.userID !== uid) {
212+
return res.status(403).json({ error: "No tienes permiso para realizar el sorteo de esta rifa" });
213+
}
214+
215+
// Verificar si la rifa está lista para sorteo
216+
const { isReady, reason } = await rifaModel.isRifaReadyForDraw(rifaId);
217+
if (!isReady) {
218+
return res.status(400).json({ error: reason || "Esta rifa no está lista para sorteo" });
219+
}
220+
221+
// Obtener todos los tickets vendidos
222+
const tickets = await ticketModel.getTicketsByRifaId(rifaId);
223+
224+
if (!tickets || tickets.length === 0) {
225+
return res.status(400).json({ error: "No hay tickets vendidos para esta rifa" });
226+
}
227+
228+
// Generar número aleatorio usando un seed verificable
229+
const timestamp = Date.now();
230+
const seed = `${timestamp}-${rifaId}-${uid}`;
231+
232+
// Seleccionar un índice aleatorio entre todos los boletos vendidos
233+
const randomIndex = generateVerifiableRandom(seed, tickets.length);
234+
235+
// Seleccionar ganador
236+
const ganador = tickets[randomIndex];
237+
238+
// Verificamos que el ticket tenga un número de boleto válido
239+
if (!ganador.numero_boleto) {
240+
// Si no tiene número de boleto, generamos uno con formato correcto
241+
ganador.numero_boleto = `#${String(ganador.id).padStart(4, '0')}`;
242+
243+
// En un entorno real, podríamos actualizar el boleto en la base de datos
244+
// pero para esta implementación solo lo usaremos para mostrar
245+
}
246+
247+
// Actualizar estado de la rifa y guardar ganador
248+
await rifaModel.updateRifaStatus(rifaId, 'Cerrada');
249+
await rifaModel.setRifaWinner(rifaId, ganador.id_user, ganador.id);
250+
251+
252+
const user = await getUserByUid(ganador.id_user);
253+
254+
if (!user) {
255+
return res.status(404).json({ error: "Usuario del ganador no encontrado" });
256+
}
257+
258+
const ganadorInfo = {
259+
ticketId: ganador.id,
260+
ticketNumber: ganador.numero_boleto,
261+
userId: user.data.uid,
262+
nombre: user.data.name ? user.data.name : 'Usuario',
263+
email: user.data.email ? user.data.email : '',
264+
};
265+
266+
return res.status(200).json({
267+
success: true,
268+
message: 'Sorteo realizado con éxito',
269+
timestamp,
270+
seed,
271+
randomIndex,
272+
totalTickets: tickets.length,
273+
ganador: ganadorInfo,
274+
rifa: {
275+
id: rifa.id,
276+
title: rifa.title,
277+
prize: rifa.prize
278+
}
279+
});
280+
281+
} catch (error) {
282+
console.error("Error al realizar sorteo:", error);
283+
return res.status(500).json({ error: "Error interno al realizar el sorteo" });
284+
}
285+
};

backend/src/controllers/ticket.controllers.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createTicket, getAllTicketsModel } from "../models/ticket.models.js";
1+
import { createTicket, getAllTicketsModel, getTicketsByRifaId } from "../models/ticket.models.js";
22
import admin from "../firebase.js";
33

44

@@ -45,8 +45,26 @@ export const getAllTickets = async (req, res) => {
4545

4646
res.status(200).json({ tickets: data });
4747
} catch (error) {
48-
console.error("Error en la peticion de tdoas las rifas", error)
48+
console.error("Error en la peticion de todas las rifas", error)
4949
res.status(400).json({error : "Error interno del servidor"})
5050
}
51-
51+
}
52+
53+
// Nuevo controlador para obtener tickets por ID de rifa
54+
export const getTicketsByRifaIdController = async (req, res) => {
55+
try {
56+
const { rifaId } = req.params;
57+
58+
if (!rifaId) {
59+
return res.status(400).json({ error: "ID de rifa no proporcionado" });
60+
}
61+
62+
// Utiliza el modelo para obtener los tickets
63+
const tickets = await getTicketsByRifaId(rifaId);
64+
65+
res.status(200).json(tickets);
66+
} catch (error) {
67+
console.error("Error al obtener tickets por ID de rifa:", error);
68+
res.status(500).json({ error: "Error interno del servidor" });
69+
}
5270
}

backend/src/models/rifa.model.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,68 @@ export async function decrementRifaTickets(rifaId, amount){
9494

9595
if (error) throw error;
9696
return { data, error };
97-
}
97+
}
98+
99+
// Nuevas funciones para el sorteo de rifas
100+
101+
export async function updateRifaStatus(rifaId, status) {
102+
const { data, error } = await supabase
103+
.from('rifas')
104+
.update({ state: status })
105+
.eq('id', rifaId);
106+
107+
if (error) throw error;
108+
return { data, error };
109+
}
110+
111+
export async function setRifaWinner(rifaId, userId, ticketId) {
112+
const { data, error } = await supabase
113+
.from('rifas')
114+
.update({
115+
winner_user_id: userId,
116+
winner_ticket_id: ticketId,
117+
draw_date: new Date().toISOString()
118+
})
119+
.eq('id', rifaId);
120+
121+
if (error) throw error;
122+
return { data, error };
123+
}
124+
125+
// Verifica si una rifa está lista para sorteo
126+
export async function isRifaReadyForDraw(rifaId) {
127+
const { data: rifa, error } = await supabase
128+
.from('rifas')
129+
.select('*')
130+
.eq('id', rifaId)
131+
.single();
132+
133+
if (error) throw error;
134+
135+
// Una rifa está lista si:
136+
137+
// 2. No tiene ganador todavía
138+
// 3. Tiene al menos un ticket vendido
139+
const hasNoWinner = !rifa.winner_user_id;
140+
const hasTicketsSold = rifa.total_tickets_sold > 0;
141+
142+
return {
143+
isReady: hasNoWinner && hasTicketsSold,
144+
rifa,
145+
reason: !hasTicketsSold ? 'No hay tickets vendidos' :
146+
!hasNoWinner ? 'Esta rifa ya tiene un ganador' : null
147+
};
148+
}
149+
150+
// Exportamos también un objeto con todas las funciones para facilitar las importaciones
151+
export const rifaModel = {
152+
createRifa,
153+
getRifasUser,
154+
getRifa,
155+
updateRifa,
156+
deleteRifa,
157+
decrementRifaTickets,
158+
updateRifaStatus,
159+
setRifaWinner,
160+
isRifaReadyForDraw
161+
};

backend/src/models/ticket.models.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { supabase } from "../db.js";
22

33

44
export async function createTicket(ticket){
5+
56
const { data, error } = await supabase
67
.from('tickets')
78
.insert([
@@ -24,20 +25,37 @@ export async function createTicket(ticket){
2425

2526

2627
export async function getTicketsByRifaId(rifaId) {
28+
2729
const { data, error } = await supabase
2830
.from('tickets')
29-
.select('*')
30-
.eq('rifaId', rifaId);
31+
.select("*")
32+
.eq('id_rifa', rifaId);
33+
3134
if (error) throw error;
32-
return { data, error };
35+
36+
const processedData = data.map(ticket => {
37+
if (!ticket.numero_boleto) {
38+
ticket.numero_boleto = `#${String(ticket.id).padStart(4, '0')}`;
39+
}
40+
return ticket;
41+
});
42+
43+
return processedData; // Retornamos los datos procesados
3344
}
3445

3546
export async function getAllTicketsModel(userId) {
3647
const { data, error } = await supabase
3748
.from('tickets')
3849
.select('*')
39-
.eq('id_user', userId)
50+
.eq('id_user', userId);
4051

4152
if (error) throw error;
4253
return { data, error };
43-
}
54+
}
55+
56+
// Exportamos las funciones en un objeto
57+
export const ticketModel = {
58+
createTicket,
59+
getTicketsByRifaId,
60+
getAllTicketsModel
61+
};

backend/src/routes/auth.routes.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
import { Router } from "express";
22
import { register, login } from "../controllers/auth.controllers.js";
3-
import { getRifasUsers, createRifas, getAllRifas, getRifaById, updatePartialRifa, decrementRifaTicket } from "../controllers/rifa.controllers.js";
3+
import {
4+
getRifasUsers,
5+
createRifas,
6+
getAllRifas,
7+
getRifaById,
8+
updatePartialRifa,
9+
decrementRifaTicket,
10+
checkRifaForDraw,
11+
realizarSorteo
12+
} from "../controllers/rifa.controllers.js";
413
import { requestPaymentIntent } from "../controllers/striper.controllers.js";
5-
import { createTicketController,getAllTickets } from "../controllers/ticket.controllers.js";
6-
14+
import {
15+
createTicketController,
16+
getAllTickets,
17+
getTicketsByRifaIdController
18+
} from "../controllers/ticket.controllers.js";
719

820

921
const router = Router();
@@ -18,6 +30,9 @@ router.get("/get/rifa", getRifaById);
1830
router.patch("/update/rifa", updatePartialRifa);
1931
router.patch("/decrement/ticket", decrementRifaTicket);
2032
router.post("/create/ticket", createTicketController);
21-
router.post("/get/all/tickets", getAllTickets)
33+
router.post("/get/all/tickets", getAllTickets);
34+
router.post("/rifas/:rifaId/check-draw", checkRifaForDraw);
35+
router.post("/rifas/:rifaId/sorteo", realizarSorteo);
36+
router.get("/tickets/rifa/:rifaId", getTicketsByRifaIdController);
2237

2338
export default router;

client/src/api/rifa.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ export const getAllRifas = () => axios.get(import.meta.env.VITE_API_BACKEND_GET_
66
export const getRifaById = (rifaId) => axios.get(import.meta.env.VITE_API_GET_RIFA, {params : {rifaId}});
77
export const updatePartialRifa = (id, fieldsToUpdate) => axios.patch(import.meta.env.VITE_UPDATE_RIFA, fieldsToUpdate, {params : {id}});
88
export const decrementRifaTicket = (rifaId, amount) => axios.patch(import.meta.env.VITE_DECREMENT_TICKET, {rifaId, amount});
9+
export const checkRifaForDraw = (rifaId, token) =>
10+
axios.post(`${import.meta.env.VITE_API_BACKEND_URL}/rifas/${rifaId}/check-draw`, { token });
11+
export const realizarSorteo = (rifaId, token) =>
12+
axios.post(`${import.meta.env.VITE_API_BACKEND_URL}/rifas/${rifaId}/sorteo`, { token });

client/src/api/ticket.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import axios from "axios";
22

33
export const createTicket = (ticket) => axios.post(import.meta.env.VITE_CREATE_TICKET, ticket);
4-
export const getAllTicketsUser = (userId) => axios.post(import.meta.env.VITE_ALL_TICKETS, {userId});
4+
export const getAllTicketsUser = (userId) => axios.post(import.meta.env.VITE_ALL_TICKETS, {userId});
5+
export const getTicketsByRifaId = (rifaId) => axios.get(`${import.meta.env.VITE_API_BACKEND_URL}/tickets/rifa/${rifaId}`);

0 commit comments

Comments
 (0)