import express from "express"; import cors from "cors"; import path from "node:path"; import fs from "node:fs"; import { fileURLToPath } from "node:url"; import { db } from "./db.ts"; import { ensurePlatformAdmin, seedBusiness, getTemplate, DEFAULT_TEMPLATE_KEY } from "./scripts/seed.ts"; import { authRouter } from "./routes/auth.ts"; import { businessRouter } from "./routes/business.ts"; import { employeesRouter } from "./routes/employees.ts"; import { servicesRouter } from "./routes/services.ts"; import { clientsRouter } from "./routes/clients.ts"; import { appointmentsRouter } from "./routes/appointments.ts"; import { dashboardRouter } from "./routes/dashboard.ts"; import { adminRouter } from "./routes/admin.ts"; import { settingsRouter } from "./routes/settings.ts"; import { cashRouter } from "./routes/cash.ts"; import { notificationsRouter, scheduleReminders } from "./routes/notifications.ts"; import { bookingRouter } from "./routes/booking.ts"; import { meRouter } from "./routes/me.ts"; import { authRequired } from "./lib/auth.ts"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, ".."); // Auto-seed demo data if the database is empty (first run). Preserves existing data. function ensureSeed() { ensurePlatformAdmin(); const row = db.prepare(`SELECT COUNT(*) c FROM businesses`).get() as { c: number }; if (row.c === 0) { console.log("[seed] Base de datos vacía — creando negocio demo y admin…"); const biz = db .prepare( `INSERT INTO businesses (name, industry, currency, currency_symbol, phone, address, plan, status) VALUES (?, ?, ?, ?, ?, ?, 'trial', 'active') RETURNING id` ) .get("Lumière Estética & Spa", "Estética y Spa", "MXN", "$", "+52 55 1234 5678", "Av. Reforma 245, CDMX") as { id: number }; seedBusiness({ businessId: biz.id, template: getTemplate(DEFAULT_TEMPLATE_KEY)!, ownerEmail: "owner@agendapro.demo", ownerName: "Daniela Reyes", }); } } ensureSeed(); // On startup, refresh pending reminders for all active businesses (idempotent). try { const bids = db.prepare(`SELECT id FROM businesses WHERE status = 'active'`).all() as { id: number }[]; for (const b of bids) scheduleReminders(b.id); } catch (e) { console.warn("[reminders] no se pudieron generar al inicio:", (e as Error).message); } const app = express(); app.use(cors()); app.use(express.json({ limit: "1mb" })); app.use("/api/auth", authRouter); // Public booking (no auth) app.use("/api/public", bookingRouter); // Protected APIs app.use("/api/business", authRequired, businessRouter); app.use("/api/employees", authRequired, employeesRouter); app.use("/api/services", authRequired, servicesRouter); app.use("/api/clients", authRequired, clientsRouter); app.use("/api/appointments", authRequired, appointmentsRouter); app.use("/api/me", authRequired, meRouter); app.use("/api/dashboard", authRequired, dashboardRouter); app.use("/api/admin", authRequired, adminRouter); app.use("/api/settings", authRequired, settingsRouter); app.use("/api/cash", authRequired, cashRouter); app.use("/api/notifications", authRequired, notificationsRouter); app.get("/api/health", (_req, res) => res.json({ ok: true, ts: Date.now() })); // Serve built frontend in production const distDir = path.join(ROOT, "dist"); if (fs.existsSync(distDir)) { app.use(express.static(distDir)); app.get("*", (req, res, next) => { if (req.path.startsWith("/api/")) return next(); res.sendFile(path.join(distDir, "index.html")); }); } app.use((err: any, _req: express.Request, res: express.Response, _next: express.NextFunction) => { console.error("[server error]", err); res.status(err.status || 500).json({ error: err.message || "Error interno" }); }); const port = Number(process.env.PORT) || 3000; const host = process.env.HOST || "0.0.0.0"; app.listen(port, host, () => { console.log(`AgendaMax API en http://${host}:${port}`); });