Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 41 additions & 29 deletions apps/next-frontend/app/api/invite-codes/use/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Código de invitación requerido' }, { status: 400 });
}

const normalizedCode = codigo.trim().toUpperCase();

// Buscar el código de invitación
const inviteCode = await prisma.invite_codes.findFirst({
where: {
codigo: codigo.trim().toUpperCase(),
codigo: normalizedCode,
estado: 'ACTIVO',
fecha_expiracion: {
gt: new Date(),
Expand All @@ -48,44 +50,54 @@ export async function POST(request: NextRequest) {
}

// Verificar si el usuario ya es colaborador de este evento
const existingColaborador = await prisma.colaborador_eventos.findUnique({
const existingColaborador = await prisma.colaborador_eventos.findFirst({
where: {
eventoid_usuarioid: {
eventoid: inviteCode.eventoid,
usuarioid: session.user.id,
},
eventoid: inviteCode.eventoid,
usuarioid: session.user.id,
},
});

if (existingColaborador) {
return NextResponse.json({ error: 'Ya eres colaborador de este evento' }, { status: 400 });
}

// Crear el registro de colaborador
const newColaborador = await prisma.colaborador_eventos.create({
data: {
eventoid: inviteCode.eventoid,
usuarioid: session.user.id,
invite_code_used: codigo.trim().toUpperCase(),
},
});

// Incrementar el contador de usos del código de invitación
await prisma.invite_codes.update({
where: { codigoid: inviteCode.codigoid },
data: {
usos_totales: {
increment: 1,
const [newColaborador, updatedInviteCode, evento] = await prisma.$transaction([
prisma.colaborador_eventos.create({
data: {
eventoid: inviteCode.eventoid,
usuarioid: session.user.id,
invite_code_used: normalizedCode,
},
},
});
}),
prisma.invite_codes.update({
where: { codigoid: inviteCode.codigoid },
data: {
usos_totales: {
increment: 1,
},
},
}),
prisma.eventos.findUnique({
where: { eventoid: inviteCode.eventoid },
select: {
eventoid: true,
titulo: true,
},
}),
]);

return NextResponse.json({
success: true,
message: '¡Código validado! Ahora eres colaborador del evento',
eventoid: inviteCode.eventoid,
colaborador_evento_id: newColaborador.colaborador_evento_id,
});
return NextResponse.json(
{
success: true,
message: '¡Código validado! Ahora eres colaborador del evento',
eventoid: inviteCode.eventoid,
evento: evento ?? null,
colaborador_evento_id: newColaborador.colaborador_evento_id,
usos_totales: updatedInviteCode.usos_totales,
usos_max: updatedInviteCode.usos_max,
},
{ status: 200 },
);
} catch (error) {
console.error('Error using invite code:', error);
return NextResponse.json({ error: 'Error al validar código de invitación' }, { status: 500 });
Expand Down
20 changes: 14 additions & 6 deletions apps/next-frontend/app/api/invite-codes/validate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Código de invitación requerido' }, { status: 400 });
}

const normalizedCode = codigo.trim().toUpperCase();

// Buscar el código en la base de datos
const inviteCode = await prisma.invite_codes.findFirst({
where: {
codigo: codigo.trim().toUpperCase(),
codigo: normalizedCode,
estado: 'ACTIVO',
fecha_expiracion: {
gt: new Date(),
Expand Down Expand Up @@ -47,11 +49,17 @@ export async function POST(request: NextRequest) {
},
});

return NextResponse.json({
valid: true,
eventoid: inviteCode.eventoid,
titulo: evento?.titulo,
});
return NextResponse.json(
{
valid: true,
eventoid: inviteCode.eventoid,
titulo: evento?.titulo ?? null,
descripcion: evento?.descripcion ?? null,
usos_totales: inviteCode.usos_totales,
usos_max: inviteCode.usos_max,
},
{ status: 200 },
);
} catch (error) {
console.error('Error validating invite code:', error);
return NextResponse.json({ error: 'Error validando código de invitación' }, { status: 500 });
Expand Down
42 changes: 27 additions & 15 deletions apps/next-frontend/app/api/mercadopago/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,6 @@ export async function POST(request: NextRequest) {
const buyerEmail = metadata.buyer_email || payment.payer?.email;
const buyerNombre = metadata.buyer_nombre || payment.payer?.first_name || '';
const sellerId = metadata.seller_id;
const sellerMpUserId = metadata.seller_mp_user_id;

if (!eventId) {
console.error('[MP Webhook] ❌ No eventId in payment metadata');
Expand Down Expand Up @@ -301,7 +300,16 @@ export async function POST(request: NextRequest) {
};

const newStatus = statusMap[payment.status] || 'unknown';
const isPaid = payment.status === 'approved';
const isApproved = payment.status === 'approved';
const isPending = payment.status === 'pending';
const acceptPending = process.env.MP_WEBHOOK_ACCEPT_PENDING === 'true';
const shouldProcessPayment = isApproved || (acceptPending && isPending);

if (acceptPending && isPending) {
console.log('[MP Webhook] ⚠️ Pending payment accepted for ticket creation (testing mode).');
}

const effectiveStatus = shouldProcessPayment ? 'approved' : newStatus;

// Actualizar la orden en la BD si existe
const existingOrder = await prisma.mercadopago_orders.findFirst({
Expand All @@ -314,10 +322,10 @@ export async function POST(request: NextRequest) {
await prisma.mercadopago_orders.update({
where: { id: existingOrder.id },
data: {
status: newStatus,
status: effectiveStatus,
payment_id: payment.id.toString(),
merchant_order_id: payment.order?.id?.toString() || null,
paid_at: isPaid ? new Date() : null,
paid_at: shouldProcessPayment ? new Date() : null,
updated_at: new Date(),
},
});
Expand All @@ -326,22 +334,25 @@ export async function POST(request: NextRequest) {
orderId: existingOrder.id,
preferenceId: existingOrder.preference_id,
oldStatus: existingOrder.status,
newStatus,
isPaid,
newStatus: effectiveStatus,
shouldProcessPayment,
paymentStatus: payment.status,
});
} else {
console.warn('[MP Webhook] ⚠️ Order not found in database for external_reference:', {
external_reference: payment.external_reference,
});
}

// Lógica cuando el pago es aprobado
if (isPaid) {
console.log('[MP Webhook] 🎉 Payment APPROVED - Creating tickets:', {
// Lógica cuando el pago está listo para generar tickets
if (shouldProcessPayment) {
console.log('[MP Webhook] 🎉 Payment ready for ticket generation:', {
eventId,
sellerId,
amount: payment.transaction_amount,
buyerEmail,
paymentStatus: payment.status,
acceptedPending: acceptPending && isPending,
});

try {
Expand All @@ -353,8 +364,8 @@ export async function POST(request: NextRequest) {
{
received: true,
orderId: existingOrder?.id,
status: newStatus,
isPaid,
status: effectiveStatus,
isPaid: shouldProcessPayment,
error: 'No preference_id',
},
{ status: 200 },
Expand Down Expand Up @@ -385,10 +396,10 @@ export async function POST(request: NextRequest) {
: String(createTicketsError),
stack: createTicketsError instanceof Error ? createTicketsError.stack : undefined,
});
// No fallar la request - el pago ya se procesó correctamente
// No fallar la request - el pago ya se procesó correctamente o se aceptó por testing
}
} else {
console.log('[MP Webhook] ⏳ Payment not approved yet:', {
console.log('[MP Webhook] ⏳ Payment not ready for tickets yet:', {
paymentStatus: payment.status,
externalReference: payment.external_reference,
});
Expand All @@ -397,8 +408,9 @@ export async function POST(request: NextRequest) {
return NextResponse.json(
{
received: true,
status: newStatus,
isPaid,
status: effectiveStatus,
isPaid: shouldProcessPayment,
acceptedPending: acceptPending && isPending,
eventId,
},
{ status: 200 },
Expand Down
8 changes: 3 additions & 5 deletions apps/next-frontend/app/colaborador/scanner/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function ScannerPage() {
'Content-Type': 'application/json',
},
body: JSON.stringify({
codigo: inviteCodeInput.trim().toUpperCase(),
codigo: inviteCodeInput.trim(),
}),
});

Expand All @@ -75,14 +75,12 @@ export default function ScannerPage() {
return;
}

toast.success('¡Código validado! Uniendo al evento...');
toast.success(data.message || '¡Código validado! Uniendo al evento...');
setInviteCodeInput('');
setShowCodeModal(false);

// Recargar eventos después de usar el código
setTimeout(() => {
loadEventos();
}, 1500);
await loadEventos();
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Error al validar código');
} finally {
Expand Down
8 changes: 4 additions & 4 deletions apps/next-frontend/components/qr-scanner-free-event.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,18 +212,20 @@ export function QRScannerFreeEvent({ eventoid }: ScannerFreeEventProps) {
};

const handleScan = async (codigo: string) => {
if (!codigo.trim()) return;
const normalizedCode = codigo.trim().toUpperCase();
if (!normalizedCode) return;

setManualCode('');
setScanning(true);
setShowCameraModal(false);

try {
const response = await fetch('/api/validar-qr', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
eventId: eventoid,
codigo: codigo.trim(),
codigo: normalizedCode,
}),
});

Expand All @@ -243,8 +245,6 @@ export function QRScannerFreeEvent({ eventoid }: ScannerFreeEventProps) {
} else {
toast.success(`✓ ${data.data.inscripcion.nombre} - Entrada validada`);
}
// Cerrar modal de cámara después de escaneo exitoso
setShowCameraModal(false);
}
// Recargar la lista (sin mostrar errores si falla)
try {
Expand Down
Loading