import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { db } from '@/lib/db'; import { z } from 'zod'; interface RouteContext { params: Promise<{ id: string }>; } // Validation schema for updating client const updateClientSchema = z.object({ firstName: z.string().min(1, 'El nombre es requerido').optional(), lastName: z.string().min(1, 'El apellido es requerido').optional(), email: z.string().email('Email invalido').nullable().optional(), phone: z.string().nullable().optional(), avatar: z.string().url('URL invalida').nullable().optional(), dateOfBirth: z.string().nullable().optional(), address: z.string().nullable().optional(), notes: z.string().nullable().optional(), level: z.string().nullable().optional(), tags: z.array(z.string()).optional(), }); // GET /api/clients/[id] - Get a single client with details export async function GET( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'No autorizado' }, { status: 401 } ); } const { id } = await context.params; const client = await db.client.findFirst({ where: { id, organizationId: session.user.organizationId, }, include: { memberships: { where: { status: 'ACTIVE', endDate: { gte: new Date(), }, }, include: { plan: { select: { id: true, name: true, price: true, durationMonths: true, courtHours: true, discountPercent: true, }, }, }, orderBy: { endDate: 'desc', }, take: 1, }, _count: { select: { bookings: true, }, }, }, }); if (!client) { return NextResponse.json( { error: 'Cliente no encontrado' }, { status: 404 } ); } // Calculate total spent from payments const totalSpentResult = await db.payment.aggregate({ where: { clientId: client.id, }, _sum: { amount: true, }, }); // Calculate total from sales const totalSalesResult = await db.sale.aggregate({ where: { clientId: client.id, }, _sum: { total: true, }, }); const totalSpent = Number(totalSpentResult._sum.amount || 0) + Number(totalSalesResult._sum.total || 0); // For now, balance is set to 0 (can be extended with a balance field in the future) const balance = 0; return NextResponse.json({ ...client, stats: { totalBookings: client._count.bookings, totalSpent, balance, }, }); } catch (error) { console.error('Error fetching client:', error); return NextResponse.json( { error: 'Error al obtener el cliente' }, { status: 500 } ); } } // PUT /api/clients/[id] - Update a client export async function PUT( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'No autorizado' }, { status: 401 } ); } const { id } = await context.params; // Verify client exists and belongs to user's organization const existingClient = await db.client.findFirst({ where: { id, organizationId: session.user.organizationId, }, }); if (!existingClient) { return NextResponse.json( { error: 'Cliente no encontrado' }, { status: 404 } ); } const body = await request.json(); // Validate input const validationResult = updateClientSchema.safeParse(body); if (!validationResult.success) { return NextResponse.json( { error: 'Datos de actualizacion invalidos', details: validationResult.error.flatten().fieldErrors, }, { status: 400 } ); } const { firstName, lastName, email, phone, avatar, dateOfBirth, address, notes, level, tags, } = validationResult.data; // Check for email uniqueness if email is being changed if (email && email !== existingClient.email) { const emailExists = await db.client.findFirst({ where: { organizationId: session.user.organizationId, email, id: { not: id, }, }, }); if (emailExists) { return NextResponse.json( { error: 'Ya existe un cliente con este email' }, { status: 409 } ); } } // Build update data const updateData: Record = {}; if (firstName !== undefined) updateData.firstName = firstName; if (lastName !== undefined) updateData.lastName = lastName; if (email !== undefined) updateData.email = email; if (phone !== undefined) updateData.phone = phone; if (avatar !== undefined) updateData.avatar = avatar; if (dateOfBirth !== undefined) { updateData.dateOfBirth = dateOfBirth ? new Date(dateOfBirth) : null; } if (address !== undefined) updateData.address = address; if (notes !== undefined) updateData.notes = notes; if (level !== undefined) updateData.level = level; if (tags !== undefined) updateData.tags = tags; const client = await db.client.update({ where: { id }, data: updateData, include: { memberships: { where: { status: 'ACTIVE', endDate: { gte: new Date(), }, }, include: { plan: { select: { id: true, name: true, price: true, durationMonths: true, courtHours: true, }, }, }, take: 1, }, _count: { select: { bookings: true, }, }, }, }); return NextResponse.json(client); } catch (error) { console.error('Error updating client:', error); // Check for unique constraint violation if (error instanceof Error && error.message.includes('Unique constraint')) { return NextResponse.json( { error: 'Ya existe un cliente con este email o DNI' }, { status: 409 } ); } return NextResponse.json( { error: 'Error al actualizar el cliente' }, { status: 500 } ); } } // DELETE /api/clients/[id] - Soft delete a client (set isActive = false) export async function DELETE( request: NextRequest, context: RouteContext ) { try { const session = await getServerSession(authOptions); if (!session?.user) { return NextResponse.json( { error: 'No autorizado' }, { status: 401 } ); } const { id } = await context.params; // Verify client exists and belongs to user's organization const existingClient = await db.client.findFirst({ where: { id, organizationId: session.user.organizationId, }, include: { memberships: { where: { status: 'ACTIVE', }, }, bookings: { where: { status: { in: ['PENDING', 'CONFIRMED'], }, startTime: { gte: new Date(), }, }, }, }, }); if (!existingClient) { return NextResponse.json( { error: 'Cliente no encontrado' }, { status: 404 } ); } // Check for active memberships if (existingClient.memberships.length > 0) { return NextResponse.json( { error: 'No se puede desactivar un cliente con membresia activa', details: { activeMemberships: existingClient.memberships.length, }, }, { status: 400 } ); } // Check for pending/future bookings if (existingClient.bookings.length > 0) { return NextResponse.json( { error: 'No se puede desactivar un cliente con reservas pendientes', details: { pendingBookings: existingClient.bookings.length, }, }, { status: 400 } ); } // Soft delete by setting isActive to false const client = await db.client.update({ where: { id }, data: { isActive: false, }, select: { id: true, firstName: true, lastName: true, isActive: true, }, }); return NextResponse.json({ message: 'Cliente desactivado exitosamente', client, }); } catch (error) { console.error('Error deleting client:', error); return NextResponse.json( { error: 'Error al desactivar el cliente' }, { status: 500 } ); } }