// La zona del proceso es UTC y la del negocio America/Mexico_City: nunca // coinciden, así que una recaída de zona horaria falla aquí y no en producción. process.env.TZ = "UTC"; import { test, before, after } from "node:test"; import assert from "node:assert/strict"; import { pool } from "../db/pool.ts"; import { createApp } from "../index.ts"; import { resetDb, seedMinimal } from "./helpers.ts"; import type { Server } from "node:http"; let ids: Awaited>; let server: Server; let base: string; before(async () => { await resetDb(); ids = await seedMinimal(); server = createApp().listen(0); base = `http://127.0.0.1:${(server.address() as { port: number }).port}`; }); after(async () => { server.close(); await pool.end(); }); function req(path: string, init: RequestInit = {}, userId = ids.ownerUserId) { return fetch(`${base}${path}`, { ...init, headers: { "content-type": "application/json", authorization: `Bearer ${userId}`, ...(init.headers || {}), }, }); } async function crearCita(startUtc: string): Promise { const { rows } = await pool.query( `INSERT INTO appointments (business_id, client_id, employee_id, service_id, start_at, end_at, price) VALUES ($1,$2,$3,$4,$5::timestamptz,$5::timestamptz + interval '60 minutes',850) RETURNING id`, [ids.businessId, ids.clientId, ids.employeeId, ids.serviceId, startUtc] ); return rows[0].id; } test("una cita de las 19:00 de México cuenta en su día local, no en el UTC", async () => { // 2026-09-07 19:00 en México (UTC-6) = 2026-09-08 01:00 UTC. await crearCita("2026-09-08T01:00:00Z"); const r = await req("/api/day-close?date=2026-09-07"); const body = await r.json(); assert.equal(body.unresolved.length, 1, "debe contarse en el 7, no en el 8"); }); test("no deja cerrar el día con citas sin resolver", async () => { const r = await req("/api/day-close", { method: "POST", body: JSON.stringify({ date: "2026-09-07" }), }); assert.equal(r.status, 409); const body = await r.json(); assert.equal(body.unresolved.length, 1); assert.match(body.error, /sin resolver/i); }); test("cierra el día cuando todas están resueltas y guarda el conteo", async () => { const { rows } = await pool.query(`SELECT id FROM appointments WHERE status = 'scheduled'`); await req(`/api/appointments/${rows[0].id}/attendance`, { method: "POST", body: JSON.stringify({ attended: true, total_charged: 850 }), }); const r = await req("/api/day-close", { method: "POST", body: JSON.stringify({ date: "2026-09-07" }), }); assert.equal(r.status, 200); const { closure } = await r.json(); assert.equal(closure.attended_count, 1); assert.equal(closure.no_show_count, 0); assert.equal(closure.closed_by_user_id, ids.ownerUserId); }); test("cerrar dos veces el mismo día devuelve 409", async () => { const r = await req("/api/day-close", { method: "POST", body: JSON.stringify({ date: "2026-09-07" }), }); assert.equal(r.status, 409); const body = await r.json(); assert.match(body.error, /ya está cerrado/i); }); test("el resumen del día muestra la fecha de cierre", async () => { const r = await req("/api/day-close?date=2026-09-07"); const body = await r.json(); assert.ok(body.closed_at, "un día cerrado reporta cuándo se cerró"); assert.equal(body.attended, 1); });