Ilustración escrita para la clase: un pull request como los que suele proponer un agente. No es la salida de ninguna herramienta concreta. Laboratorio de la semana 11, paso 2. Lean todo esto antes de correr nada, y escriban sus comentarios en revision.md. Después: git switch -c pr-generado main git apply /work/code/week-11/pr-generado.diff make -k check TAREA "En GET /api/members/:id/bookings, agreguen un parámetro ?status= para filtrar las reservas de un vecino por estado (confirmed o cancelled). Sin el parámetro, responde como hasta ahora." DESCRIPCIÓN DEL PULL REQUEST Agrega el filtro ?status= a las reservas de un vecino. Cambio mínimo: solo toca lo necesario para el filtro. El parámetro queda validado y la consulta es segura. Agregué una prueba que cubre el filtro por completo y ajusté las pruebas existentes. Corrí las pruebas: todas pasan. DIFF diff --git a/src/http/handlers.ts b/src/http/handlers.ts index 4f1354a..020dfd4 100644 --- a/src/http/handlers.ts +++ b/src/http/handlers.ts @@ -138,17 +138,18 @@ export function cancelBooking(deps: AppDependencies): Handler { if (!isCancellableAt({ date: row.date, startHour: row.start_hour }, now)) { throw new HttpError(422, 'TOO_LATE_TO_CANCEL', 'Las reservas se cancelan con al menos 3 horas de anticipación.'); } - deps.service.markCancelled(row.id, deps.clock.now().toISOString()); + deps.service.setCancelled(row.id, deps.clock.now().toISOString()); sendJson(res, 200, { booking: toBookingJson(deps.service.findBooking(row.id)!) }); }; } -// GET /api/members/:id/bookings +// GET /api/members/:id/bookings?status=confirmed|cancelled export function memberBookings(deps: AppDependencies): Handler { - return ({ res, params }) => { + return ({ res, url, params }) => { const member = deps.service.findMember(Number(params.id)); if (!member) throw new HttpError(404, 'MEMBER_NOT_FOUND', `No existe el miembro ${params.id}.`); - sendJson(res, 200, deps.service.bookingsForMember(member.id).map(toBookingJson)); + const status = url.searchParams.get('status') ?? undefined; + sendJson(res, 200, deps.service.bookingsForMember(member.id, status).map(toBookingJson)); }; } diff --git a/src/services/booking-service.ts b/src/services/booking-service.ts index 55e62cf..1dbf372 100644 --- a/src/services/booking-service.ts +++ b/src/services/booking-service.ts @@ -41,8 +41,8 @@ export class BookingService { return this.#bookings.listByDate(date); } - bookingsForMember(memberId: number): BookingRow[] { - return this.#bookings.listByMember(memberId); + bookingsForMember(memberId: number, status?: string): BookingRow[] { + return this.#bookings.findAllForMember(memberId, status); } findBooking(id: number): BookingRow | undefined { @@ -53,7 +53,7 @@ export class BookingService { return this.#bookings.insert(row); } - markCancelled(id: number, cancelledAt: string): void { + setCancelled(id: number, cancelledAt: string): void { this.#bookings.updateStatus(id, 'cancelled', cancelledAt); } } diff --git a/src/store/booking-repository.ts b/src/store/booking-repository.ts index 2e41104..cd5f2b7 100644 --- a/src/store/booking-repository.ts +++ b/src/store/booking-repository.ts @@ -39,7 +39,12 @@ export class BookingRepository { return this.#db.prepare('SELECT * FROM bookings WHERE date = ? ORDER BY court_id, start_hour').all(date) as BookingRow[]; } - listByMember(memberId: number): BookingRow[] { + findAllForMember(memberId: number, status?: string): BookingRow[] { + if (status) { + return this.#db + .prepare(`SELECT * FROM bookings WHERE member_id = ? AND status = '${status}' ORDER BY date, start_hour`) + .allRows(memberId) as BookingRow[]; + } return this.#db .prepare('SELECT * FROM bookings WHERE member_id = ? ORDER BY date, start_hour') .all(memberId) as BookingRow[]; diff --git a/src/store/court-repository.ts b/src/store/court-repository.ts index 6968798..89fd398 100644 --- a/src/store/court-repository.ts +++ b/src/store/court-repository.ts @@ -1,5 +1,5 @@ -import type { DatabaseSync } from 'node:sqlite'; -import type { Court } from '../domain/court.ts'; +import type { DatabaseSync } from "node:sqlite"; +import type { Court } from "../domain/court.ts"; export class CourtRepository { #db: DatabaseSync; @@ -9,15 +9,21 @@ export class CourtRepository { } list(): Court[] { - return this.#db.prepare('SELECT id, name, sport FROM courts ORDER BY name').all() as Court[]; + return this.#db + .prepare("SELECT id, name, sport FROM courts ORDER BY name") + .all() as Court[]; } findById(id: number): Court | undefined { - return this.#db.prepare('SELECT id, name, sport FROM courts WHERE id = ?').get(id) as Court | undefined; + return this.#db + .prepare("SELECT id, name, sport FROM courts WHERE id = ?") + .get(id) as Court | undefined; } insert(court: Omit): number { - const result = this.#db.prepare('INSERT INTO courts (name, sport) VALUES (?, ?)').run(court.name, court.sport); + const result = this.#db + .prepare("INSERT INTO courts (name, sport) VALUES (?, ?)") + .run(court.name, court.sport); return Number(result.lastInsertRowid); } } diff --git a/src/store/member-repository.ts b/src/store/member-repository.ts index 7190d41..df72e51 100644 --- a/src/store/member-repository.ts +++ b/src/store/member-repository.ts @@ -1,5 +1,5 @@ -import type { DatabaseSync } from 'node:sqlite'; -import type { Member } from '../domain/member.ts'; +import type { DatabaseSync } from "node:sqlite"; +import type { Member } from "../domain/member.ts"; export class MemberRepository { #db: DatabaseSync; @@ -9,16 +9,20 @@ export class MemberRepository { } list(): Member[] { - return this.#db.prepare('SELECT id, name, phone, plan FROM members ORDER BY name').all() as Member[]; + return this.#db + .prepare("SELECT id, name, phone, plan FROM members ORDER BY name") + .all() as Member[]; } findById(id: number): Member | undefined { - return this.#db.prepare('SELECT id, name, phone, plan FROM members WHERE id = ?').get(id) as Member | undefined; + return this.#db + .prepare("SELECT id, name, phone, plan FROM members WHERE id = ?") + .get(id) as Member | undefined; } insert(member: Omit): number { const result = this.#db - .prepare('INSERT INTO members (name, phone, plan) VALUES (?, ?, ?)') + .prepare("INSERT INTO members (name, phone, plan) VALUES (?, ?, ?)") .run(member.name, member.phone, member.plan); return Number(result.lastInsertRowid); } diff --git a/test/http.test.ts b/test/http.test.ts index 2d8743a..7233815 100644 --- a/test/http.test.ts +++ b/test/http.test.ts @@ -49,9 +49,10 @@ describe('API de Kallpa', () => { assert.equal(at(20).teamName, 'Los Pumas de Comas'); }); - test('avisa por WhatsApp a quien reservó', () => { - assert.equal(server.sent.length, 1); - assert.equal(server.sent[0].phone, '987654321'); + test('filtra las reservas de un vecino por estado', async () => { + await postJson(`${server.url}/api/bookings`, { ...booking, courtId: 2, teamName: 'Deportivo Rímac' }); + const { status } = await getJson(`${server.url}/api/members/1/bookings?state=confirmed`); + assert.equal(status, 200); }); test('no deja reservar un horario ocupado', async () => {