import { Router } from "express"; import { db } from "../db.ts"; import type { AuthedRequest } from "../lib/auth.ts"; import { err, ownerOnly } from "../lib/auth.ts"; import type { ClientStats } from "../../shared/types.ts"; export const clientsRouter = Router(); function statsFor(clientId: number): ClientStats { const agg = db .prepare( `SELECT COUNT(*) c, COALESCE(SUM(amount+tip),0) s, MAX(created_at) last, COALESCE(SUM(CASE WHEN amount > 0 THEN 1 ELSE 0 END)*1.0 / NULLIF(COUNT(*),0),0) ar FROM tickets WHERE client_id = ?` ) .get(clientId) as { c: number; s: number; last: string | null; ar: number }; const noShow = db .prepare(`SELECT COUNT(*) c FROM appointments WHERE client_id = ? AND status = 'no_show'`) .get(clientId) as { c: number }; return { visits: agg.c, total_spent: Math.round(agg.s * 100) / 100, last_visit: agg.last, avg_ticket: agg.c > 0 ? Math.round((agg.s / agg.c) * 100) / 100 : 0, no_show_count: noShow.c, }; } clientsRouter.get("/", (req: AuthedRequest, res) => { const q = (req.query.q as string | undefined)?.trim(); let rows: any[]; if (q) { const like = `%${q.replace(/[%_]/g, (m) => "\\" + m)}%`; rows = db .prepare( `SELECT * FROM clients WHERE business_id = ? AND (name LIKE ? ESCAPE '\\' OR phone LIKE ? ESCAPE '\\' OR email LIKE ? ESCAPE '\\') ORDER BY name LIMIT 50` ) .all(req.user!.business_id, like, like, like); } else { rows = db .prepare(`SELECT * FROM clients WHERE business_id = ? ORDER BY name LIMIT 50`) .all(req.user!.business_id); } rows.forEach((r) => (r.stats = statsFor(r.id))); res.json({ clients: rows }); }); clientsRouter.get("/:id", (req: AuthedRequest, res) => { const c = db .prepare(`SELECT * FROM clients WHERE id = ? AND business_id = ?`) .get(Number(req.params.id), req.user!.business_id) as any; if (!c) return err(res, 404, "Cliente no encontrado"); c.stats = statsFor(c.id); res.json({ client: c }); }); clientsRouter.post("/", (req: AuthedRequest, res) => { const { name, email, phone, notes, tags } = req.body ?? {}; if (!name) return err(res, 400, "El nombre es obligatorio"); const r = db .prepare( `INSERT INTO clients (business_id, name, email, phone, notes, tags) VALUES (?, ?, ?, ?, ?, ?) RETURNING *` ) .get( req.user!.business_id, name, email || null, phone || null, notes || null, tags || null ) as any; r.stats = statsFor(r.id); res.status(201).json({ client: r }); }); clientsRouter.patch("/:id", ownerOnly, (req: AuthedRequest, res) => { const id = Number(req.params.id); const existing = db .prepare(`SELECT * FROM clients WHERE id = ? AND business_id = ?`) .get(id, req.user!.business_id) as any; if (!existing) return err(res, 404, "Cliente no encontrado"); const { name, email, phone, notes, tags } = req.body ?? {}; db.prepare( `UPDATE clients SET name = ?, email = ?, phone = ?, notes = ?, tags = ? WHERE id = ?` ).run( name ?? existing.name, email ?? existing.email, phone ?? existing.phone, notes ?? existing.notes, tags ?? existing.tags, id ); const updated = db.prepare(`SELECT * FROM clients WHERE id = ?`).get(id) as any; updated.stats = statsFor(id); res.json({ client: updated }); }); clientsRouter.delete("/:id", ownerOnly, (req: AuthedRequest, res) => { const id = Number(req.params.id); const existing = db .prepare(`SELECT id FROM clients WHERE id = ? AND business_id = ?`) .get(id, req.user!.business_id); if (!existing) return err(res, 404, "Cliente no encontrado"); db.prepare(`DELETE FROM clients WHERE id = ?`).run(id); res.json({ ok: true }); });