diff --git a/apps/next-frontend/app/api/mercadopago/create-preference/route.ts b/apps/next-frontend/app/api/mercadopago/create-preference/route.ts index bd1c1172..8d074ba8 100644 --- a/apps/next-frontend/app/api/mercadopago/create-preference/route.ts +++ b/apps/next-frontend/app/api/mercadopago/create-preference/route.ts @@ -45,9 +45,28 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: 'Se requiere external_reference' }, { status: 400 }); } - // Obtener datos del organizador (vendedor) + // El organizador se obtiene del metadata (eventId) + const eventId = metadata?.eventoid; + if (!eventId) { + return NextResponse.json({ error: 'Se requiere eventoid en metadata' }, { status: 400 }); + } + + // Obtener el evento y su organizador + const event = await prisma.eventos.findUnique({ + where: { eventoid: eventId }, + select: { + creadorid: true, + titulo: true, + }, + }); + + if (!event) { + return NextResponse.json({ error: 'Evento no encontrado' }, { status: 404 }); + } + + // Obtener datos del organizador (vendedor) - NO del usuario autenticado const seller = await prisma.user.findUnique({ - where: { id: session.user.id }, + where: { id: event.creadorid }, select: { id: true, mercado_pago_user_id: true, @@ -58,7 +77,7 @@ export async function POST(request: NextRequest) { if (!seller || !seller.wallet_linked || !seller.mercado_pago_access_token) { return NextResponse.json( - { error: 'Debe vincular su cuenta de Mercado Pago primero' }, + { error: 'El organizador debe vincular su cuenta de Mercado Pago' }, { status: 403 }, ); } @@ -170,13 +189,21 @@ export async function POST(request: NextRequest) { amount: total, marketplace_fee: marketplaceFee, status: 'pending', - metadata: metadata || {}, + metadata: { + ...metadata, + seller_id: seller.id, + buyer_id: session.user.id, + event_id: eventId, + marketplace_fee: marketplaceFee, + }, }, }); console.log('[Create Preference] Order saved to database:', { preferenceId: preferenceData.id, external_reference, + sellerId: seller.id, + buyerId: session.user.id, }); } catch (dbError) { console.error('[Create Preference] Error saving order to database:', dbError); diff --git a/apps/next-frontend/app/colaborador/scanner/page.tsx b/apps/next-frontend/app/colaborador/scanner/page.tsx index 956ad92b..f2ad9d75 100644 --- a/apps/next-frontend/app/colaborador/scanner/page.tsx +++ b/apps/next-frontend/app/colaborador/scanner/page.tsx @@ -2,6 +2,7 @@ import { QRScanner } from '@/components/qr-scanner'; import { QRScannerFreeEvent } from '@/components/qr-scanner-free-event'; +import { InviteCodeModal } from '@/components/invite-code-modal'; import { useRoleProtection } from '@/hooks/use-role-protection'; import { useRouter } from 'next/navigation'; import { Loader2 } from 'lucide-react'; @@ -14,6 +15,9 @@ export default function ScannerPage() { const [eventos, setEventos] = useState([]); const [eventInfo, setEventInfo] = useState(null); const [loading, setLoading] = useState(true); + const [showCodeModal, setShowCodeModal] = useState(false); + const [inviteCodeInput, setInviteCodeInput] = useState(''); + const [isUsingCode, setIsUsingCode] = useState(false); // Cargar eventos del colaborador useEffect(() => { @@ -46,6 +50,46 @@ export default function ScannerPage() { } }; + const handleUseInviteCode = async () => { + if (!inviteCodeInput.trim()) { + toast.error('Por favor ingresa un código de invitación'); + return; + } + + setIsUsingCode(true); + try { + const response = await fetch('/api/administrador/invitados/usar-codigo', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + codigo: inviteCodeInput.trim().toUpperCase(), + }), + }); + + const data = await response.json(); + + if (!response.ok) { + toast.error(data.message || 'Error al validar código'); + return; + } + + toast.success('¡Código validado! Uniendo al evento...'); + setInviteCodeInput(''); + setShowCodeModal(false); + + // Recargar eventos después de usar el código + setTimeout(() => { + loadEventos(); + }, 1500); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Error al validar código'); + } finally { + setIsUsingCode(false); + } + }; + // Detectar si el evento es gratis const isEventFree = eventInfo?.stock_entrada?.every((stock: any) => Number(stock.precio) === 0); @@ -70,21 +114,43 @@ export default function ScannerPage() { if (!eventos || eventos.length === 0) { return ( -
-
-

Sin Evento Asignado

-

- No tienes un evento asignado como colaborador. Por favor, contacta al organizador del - evento. -

- + <> +
+
+

Sin Evento Asignado

+

+ No tienes un evento asignado como colaborador. Ingresa un código de invitación o + contacta al organizador del evento. +

+
+ + +
+
-
+ + { + setShowCodeModal(false); + setInviteCodeInput(''); + }} + onSubmit={handleUseInviteCode} + codeValue={inviteCodeInput} + onCodeChange={setInviteCodeInput} + isLoading={isUsingCode} + /> + ); } diff --git a/apps/next-frontend/app/eventos/page.tsx b/apps/next-frontend/app/eventos/page.tsx index 6e30c0eb..1ccd2f95 100644 --- a/apps/next-frontend/app/eventos/page.tsx +++ b/apps/next-frontend/app/eventos/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Calendar, MapPin, @@ -246,6 +246,15 @@ export default function EventosPage() { const deleteEventMutation = useDeleteEvent(); const updateEventMutation = useUpdateEvent(); + // Log para debuggear cambios de data + useEffect(() => { + console.log('[EventosPage] Data de eventos cambió, total eventos:', events.length); + console.log( + '[EventosPage] Eventos:', + events.map((e) => ({ id: e.eventoid, titulo: e.titulo })), + ); + }, [events]); + // filter events const proximosEvents = events.filter((event) => !isEventPast(event)); const pasadosEvents = events.filter((event) => isEventPast(event)); diff --git a/apps/next-frontend/app/globals.css b/apps/next-frontend/app/globals.css index e2c59b65..43bbffee 100644 --- a/apps/next-frontend/app/globals.css +++ b/apps/next-frontend/app/globals.css @@ -124,6 +124,10 @@ body { } @layer base { + html { + height: 100%; + overflow-y: scroll; + } * { @apply border-border; } diff --git a/apps/next-frontend/components/create-event-form.tsx b/apps/next-frontend/components/create-event-form.tsx index 80fdb79b..389b3a03 100644 --- a/apps/next-frontend/components/create-event-form.tsx +++ b/apps/next-frontend/components/create-event-form.tsx @@ -417,20 +417,21 @@ export default function CreateEventForm() { } return ( - <> - {eventImages.length > 0 ? ( -
- ) : ( -
- )} - -
+
0 + ? { backgroundImage: `url(${eventImages[0]})` } + : { background: 'linear-gradient(to bottom, rgb(16, 15, 15), rgb(23, 23, 23))' } + } + className="min-h-screen bg-cover bg-center" + > + {/* Background blur overlay */} +
+ +
-
+
@@ -824,7 +825,7 @@ export default function CreateEventForm() {
- +
); } diff --git a/apps/next-frontend/components/evento-content.tsx b/apps/next-frontend/components/evento-content.tsx index ce4d7972..05493807 100644 --- a/apps/next-frontend/components/evento-content.tsx +++ b/apps/next-frontend/components/evento-content.tsx @@ -237,7 +237,17 @@ export function EventoContent({ event, eventId }: EventoContentProps) { undefined; return ( -
+
+ {/* Background blur overlay */} +
+ {/* Banner de reserva temporal */} {isReserved && isReservationActive(id) && timeLeft > 0 && (
@@ -258,15 +268,6 @@ export function EventoContent({ event, eventId }: EventoContentProps) {
)} - {coverImage ? ( -
- ) : ( -
- )} -
0 ? 'pt-20' : ''}`} > diff --git a/apps/next-frontend/components/footer.tsx b/apps/next-frontend/components/footer.tsx index 8fc27c0e..9a02b6d7 100644 --- a/apps/next-frontend/components/footer.tsx +++ b/apps/next-frontend/components/footer.tsx @@ -29,7 +29,7 @@ export const Footer = () => { const currentYear = new Date().getFullYear(); return ( -