/** * Siembra la base de desarrollo con el spa, su personal, un catálogo de partida * y unas citas de hoy sin resolver, para poder recorrer el cierre de día. * * ATENCIÓN SOBRE EL CATÁLOGO: los servicios de abajo salen del vocabulario * medido en la muestra anotada de hilos del CRM (`extensiones`, `facial`, * `pedicura`, `pestañas`, `uñas`, `depilación`, `masaje`) — no de la lista de * precios de la dueña. **Las duraciones y los precios son marcadores de * posición**, puestos para que la rejilla tenga algo que dibujar. Hay que * sustituirlos por los reales en una sesión con ella antes de enseñar esto como * catálogo del negocio. */ import { pool } from "../db/pool.ts"; import { runMigrations } from "../db/migrate.ts"; import { normalizePhone } from "../lib/phone.ts"; const WORKING_HOURS = JSON.stringify({ 1: { start: "09:00", end: "20:00" }, 2: { start: "09:00", end: "20:00" }, 3: { start: "09:00", end: "20:00" }, 4: { start: "09:00", end: "20:00" }, 5: { start: "09:00", end: "20:00" }, 6: { start: "10:00", end: "18:00" }, 7: null, }); // nombre, categoría, duración (min), precio — duración y precio SIN VERIFICAR. const SERVICIOS: [string, string, number, number][] = [ ["Extensiones de pestañas", "pestañas", 120, 850], ["Retoque de pestañas", "pestañas", 75, 550], ["Limpieza facial profunda", "facial", 60, 700], ["Manicura", "uñas", 45, 300], ["Pedicura", "uñas", 60, 400], ["Uñas acrílicas", "uñas", 90, 600], ["Depilación con cera", "depilación", 30, 250], ["Masaje relajante", "masaje", 60, 750], ]; const PERSONAL: [string, string][] = [ ["Karla Ruiz", "karla@yolafranco.mx"], ["Brenda Salas", "brenda@yolafranco.mx"], ["Paola Núñez", "paola@yolafranco.mx"], ]; const CLIENTAS: [string, string | null][] = [ ["Mariana López", "55 8888 7777"], ["Alejandra Torres", "5544443333"], ["Gabriela Méndez", "+52 55 2222 1111"], ["Rocío Herrera", null], // sin teléfono: no contactable, y es un caso real y frecuente ["Diana Castillo", "01 55 6666 5555"], ]; async function main() { await runMigrations(); const ya = await pool.query(`SELECT id FROM businesses WHERE slug = 'yola-franco'`); if (ya.rows[0]) { console.log("[seed] el negocio ya existe — no se toca nada"); await pool.end(); return; } const biz = await pool.query( `INSERT INTO businesses (name, industry, slug, timezone, working_hours) VALUES ('Yola Franco Spa','Estética y Spa','yola-franco','America/Mexico_City',$1::jsonb) RETURNING id`, [WORKING_HOURS] ); const bid = biz.rows[0].id as number; const serviceIds: number[] = []; for (const [name, category, duration, price] of SERVICIOS) { const r = await pool.query( `INSERT INTO services (business_id, name, category, duration_min, price) VALUES ($1,$2,$3,$4,$5) RETURNING id`, [bid, name, category, duration, price] ); serviceIds.push(r.rows[0].id); } const employeeIds: number[] = []; for (const [name, email] of PERSONAL) { const r = await pool.query( `INSERT INTO employees (business_id, name, email) VALUES ($1,$2,$3) RETURNING id`, [bid, name, email] ); employeeIds.push(r.rows[0].id); // Todo el personal puede dar todos los servicios hasta que la dueña acote // quién hace qué. Es una suposición, y conviene que se note. for (const sid of serviceIds) { await pool.query( `INSERT INTO employee_services (employee_id, service_id) VALUES ($1,$2)`, [r.rows[0].id, sid] ); } } await pool.query( `INSERT INTO users (business_id, email, password, name, role) VALUES ($1,'yola@yolafranco.mx','demo1234','Yola Franco','owner')`, [bid] ); for (let i = 0; i < PERSONAL.length; i++) { await pool.query( `INSERT INTO users (business_id, email, password, name, role, employee_id) VALUES ($1,$2,'demo1234',$3,'employee',$4)`, [bid, PERSONAL[i][1], PERSONAL[i][0], employeeIds[i]] ); } const clientIds: number[] = []; for (const [name, phone] of CLIENTAS) { const r = await pool.query( `INSERT INTO clients (business_id, name, phone, phone_e164) VALUES ($1,$2,$3,$4) RETURNING id`, [bid, name, phone, normalizePhone(phone)] ); clientIds.push(r.rows[0].id); } // Citas de HOY, sin resolver, para que el cierre de día tenga trabajo. Las // horas se construyen sobre el día local del proceso, que en desarrollo es el // del spa; el servidor las acota con la zona del negocio de todas formas. const hoy = new Date(); const p = (n: number) => String(n).padStart(2, "0"); const dia = `${hoy.getFullYear()}-${p(hoy.getMonth() + 1)}-${p(hoy.getDate())}`; // Hora local de México → UTC: +6 h. Se escribe explícito para no depender del // reloj del proceso. const citas: [number, number, number, string][] = [ [clientIds[0], employeeIds[0], serviceIds[0], `${dia}T16:00:00Z`], // 10:00 local [clientIds[1], employeeIds[1], serviceIds[3], `${dia}T17:00:00Z`], // 11:00 [clientIds[2], employeeIds[2], serviceIds[2], `${dia}T18:30:00Z`], // 12:30 [clientIds[3], employeeIds[0], serviceIds[6], `${dia}T20:00:00Z`], // 14:00 [clientIds[4], employeeIds[1], serviceIds[7], `${dia}T22:00:00Z`], // 16:00 ]; for (const [cid, eid, sid, start] of citas) { await pool.query( `INSERT INTO appointments (business_id, client_id, employee_id, service_id, start_at, end_at, price) SELECT $1,$2,$3,$4,$5::timestamptz, $5::timestamptz + make_interval(mins => duration_min), price FROM services WHERE id = $4`, [bid, cid, eid, sid, start] ); } console.log( `[seed] Yola Franco Spa creado: ${SERVICIOS.length} servicios, ` + `${PERSONAL.length} especialistas, ${CLIENTAS.length} clientas, ${citas.length} citas de hoy.` ); console.log("[seed] Entra con yola@yolafranco.mx / demo1234"); await pool.end(); } main().catch((e) => { console.error(e); process.exit(1); });