import type { Id } from "./_generated/dataModel" import { mutation } from "./_generated/server" export const seedDemo = mutation({ args: {}, handler: async (ctx) => { const tenantId = "tenant-atlas"; const desiredQueues = [ { name: "Chamados", slug: "chamados" }, { name: "Laboratório", slug: "laboratorio" }, { name: "Visitas", slug: "visitas" }, ]; // Ensure queues const existingQueues = await ctx.db .query("queues") .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)) .collect(); const normalizedQueues = await Promise.all( existingQueues.map(async (queue) => { if (!queue) return queue; if (queue.name === "Suporte N1" || queue.slug === "suporte-n1") { await ctx.db.patch(queue._id, { name: "Chamados", slug: "chamados" }); return (await ctx.db.get(queue._id)) ?? queue; } if (queue.name === "Suporte N2" || queue.slug === "suporte-n2") { await ctx.db.patch(queue._id, { name: "Laboratório", slug: "laboratorio" }); return (await ctx.db.get(queue._id)) ?? queue; } return queue; }) ); const presentQueues = normalizedQueues.filter( (queue): queue is NonNullable<(typeof normalizedQueues)[number]> => Boolean(queue) ); const queuesBySlug = new Map(presentQueues.map((queue) => [queue.slug, queue])); const queuesByName = new Map(presentQueues.map((queue) => [queue.name, queue])); const queues = [] as typeof presentQueues; for (const def of desiredQueues) { let queue = queuesBySlug.get(def.slug) ?? queuesByName.get(def.name); if (!queue) { const newId = await ctx.db.insert("queues", { tenantId, name: def.name, slug: def.slug, teamId: undefined }); queue = (await ctx.db.get(newId))!; queuesBySlug.set(queue.slug, queue); queuesByName.set(queue.name, queue); } queues.push(queue); } const queueChamados = queuesBySlug.get("chamados"); const queueLaboratorio = queuesBySlug.get("laboratorio"); const queueVisitas = queuesBySlug.get("visitas"); if (!queueChamados || !queueLaboratorio || !queueVisitas) { throw new Error("Filas padrão não foram inicializadas"); } // Ensure users function slugify(value: string) { return value .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .replace(/[^\w\s-]/g, "") .trim() .replace(/\s+/g, "-") .replace(/-+/g, "-") .toLowerCase(); } function defaultAvatar(name: string, email: string, role: string) { const normalizedRole = role.toUpperCase(); if (normalizedRole === "CUSTOMER" || normalizedRole === "MANAGER") { return `https://i.pravatar.cc/150?u=${encodeURIComponent(email)}`; } const first = name.split(" ")[0] ?? email; return `https://avatar.vercel.sh/${encodeURIComponent(first)}`; } async function ensureCompany(def: { name: string; slug?: string; cnpj?: string; domain?: string; phone?: string; description?: string; address?: string; }): Promise> { const slug = def.slug ?? slugify(def.name); const existing = await ctx.db .query("companies") .withIndex("by_tenant_slug", (q) => q.eq("tenantId", tenantId).eq("slug", slug)) .first(); const now = Date.now(); const payload = { tenantId, name: def.name, slug, cnpj: def.cnpj ?? undefined, domain: def.domain ?? undefined, phone: def.phone ?? undefined, description: def.description ?? undefined, address: def.address ?? undefined, createdAt: now, updatedAt: now, }; if (existing) { const updates: Record = {}; if (existing.name !== payload.name) updates.name = payload.name; if (existing.cnpj !== payload.cnpj) updates.cnpj = payload.cnpj; if (existing.domain !== payload.domain) updates.domain = payload.domain; if (existing.phone !== payload.phone) updates.phone = payload.phone; if (existing.description !== payload.description) updates.description = payload.description; if (existing.address !== payload.address) updates.address = payload.address; if (Object.keys(updates).length > 0) { updates.updatedAt = now; await ctx.db.patch(existing._id, updates); } return existing._id; } return await ctx.db.insert("companies", payload); } async function ensureUser(params: { name: string; email: string; role?: string; companyId?: Id<"companies">; avatarUrl?: string; }): Promise> { const normalizedEmail = params.email.trim().toLowerCase(); const normalizedRole = (params.role ?? "CUSTOMER").toUpperCase(); const desiredAvatar = params.avatarUrl ?? defaultAvatar(params.name, normalizedEmail, normalizedRole); const existing = await ctx.db .query("users") .withIndex("by_tenant_email", (q) => q.eq("tenantId", tenantId).eq("email", normalizedEmail)) .first(); if (existing) { const updates: Record = {}; if (existing.name !== params.name) updates.name = params.name; if ((existing.role ?? "CUSTOMER") !== normalizedRole) updates.role = normalizedRole; if ((existing.avatarUrl ?? undefined) !== desiredAvatar) updates.avatarUrl = desiredAvatar; if ((existing.companyId ?? undefined) !== (params.companyId ?? undefined)) updates.companyId = params.companyId ?? undefined; if (Object.keys(updates).length > 0) { await ctx.db.patch(existing._id, updates); } return existing._id; } return await ctx.db.insert("users", { tenantId, name: params.name, email: normalizedEmail, role: normalizedRole, avatarUrl: desiredAvatar, companyId: params.companyId ?? undefined, }); } const companiesSeed = [ { name: "Atlas Engenharia Digital", slug: "atlas-engenharia", cnpj: "12.345.678/0001-90", domain: "atlasengenharia.com.br", phone: "+55 11 4002-8922", description: "Transformação digital para empresas de engenharia e construção.", address: "Av. Paulista, 1234 - Bela Vista, São Paulo/SP", }, { name: "Omni Saúde Integrada", slug: "omni-saude", cnpj: "45.678.912/0001-05", domain: "omnisaude.com.br", phone: "+55 31 3555-7788", description: "Rede de clínicas com serviços de telemedicina e prontuário eletrônico.", address: "Rua da Bahia, 845 - Centro, Belo Horizonte/MG", }, ]; const companyIds = new Map>(); for (const company of companiesSeed) { const id = await ensureCompany(company); companyIds.set(company.slug, id); } const adminId = await ensureUser({ name: "Administrador", email: "admin@sistema.dev", role: "ADMIN" }); const staffRoster = [ { name: "Gabriel Oliveira", email: "gabriel.oliveira@rever.com.br" }, { name: "George Araujo", email: "george.araujo@rever.com.br" }, { name: "Hugo Soares", email: "hugo.soares@rever.com.br" }, { name: "Julio Cesar", email: "julio@rever.com.br" }, { name: "Lorena Magalhães", email: "lorena@rever.com.br" }, { name: "Rever", email: "renan.pac@paulicon.com.br" }, { name: "Thiago Medeiros", email: "thiago.medeiros@rever.com.br" }, { name: "Weslei Magalhães", email: "weslei@rever.com.br" }, ]; const staffIds = await Promise.all( staffRoster.map((staff) => ensureUser({ name: staff.name, email: staff.email, role: "AGENT" })), ); const defaultAssigneeId = staffIds[0] ?? adminId; const atlasCompanyId = companyIds.get("atlas-engenharia"); const omniCompanyId = companyIds.get("omni-saude"); if (!atlasCompanyId || !omniCompanyId) { throw new Error("Empresas padrão não foram inicializadas"); } const atlasManagerId = await ensureUser({ name: "Mariana Andrade", email: "mariana.andrade@atlasengenharia.com.br", role: "MANAGER", companyId: atlasCompanyId, }); const joaoAtlasId = await ensureUser({ name: "João Pedro Ramos", email: "joao.ramos@atlasengenharia.com.br", role: "CUSTOMER", companyId: atlasCompanyId, }); await ensureUser({ name: "Aline Rezende", email: "aline.rezende@atlasengenharia.com.br", role: "CUSTOMER", companyId: atlasCompanyId, }); const omniManagerId = await ensureUser({ name: "Fernanda Lima", email: "fernanda.lima@omnisaude.com.br", role: "MANAGER", companyId: omniCompanyId, }); const ricardoOmniId = await ensureUser({ name: "Ricardo Matos", email: "ricardo.matos@omnisaude.com.br", role: "CUSTOMER", companyId: omniCompanyId, }); await ensureUser({ name: "Luciana Prado", email: "luciana.prado@omnisaude.com.br", role: "CUSTOMER", companyId: omniCompanyId, }); const clienteDemoId = await ensureUser({ name: "Cliente Demo", email: "cliente.demo@sistema.dev", role: "CUSTOMER", companyId: omniCompanyId, }); const templateDefinitions = [ { title: "A Rever agradece seu contato", body: "

A Rever agradece seu contato. Recebemos sua solicitação e nossa equipe já está analisando os detalhes. Retornaremos com atualizações em breve.

", }, { title: "Atualização do chamado", body: "

Seu chamado foi atualizado. Caso tenha novas informações ou dúvidas, basta responder a esta mensagem.

", }, { title: "Chamado resolvido", body: "

Concluímos o atendimento deste chamado. A Rever agradece a parceria e permanecemos à disposição para novos suportes.

", }, ]; const existingTemplates = await ctx.db .query("commentTemplates") .withIndex("by_tenant", (q) => q.eq("tenantId", tenantId)) .collect(); for (const definition of templateDefinitions) { const already = existingTemplates.find((template) => template?.title === definition.title); if (already) continue; const timestamp = Date.now(); await ctx.db.insert("commentTemplates", { tenantId, title: definition.title, body: definition.body, createdBy: adminId, updatedBy: adminId, createdAt: timestamp, updatedAt: timestamp, }); } // Seed a couple of tickets const now = Date.now(); const newestRef = await ctx.db .query("tickets") .withIndex("by_tenant_reference", (q) => q.eq("tenantId", tenantId)) .order("desc") .take(1); let ref = newestRef[0]?.reference ?? 41000; const queue1 = queueChamados._id; const queue2 = queueLaboratorio._id; const t1 = await ctx.db.insert("tickets", { tenantId, reference: ++ref, subject: "Erro 500 ao acessar portal do cliente", summary: "Clientes relatam erro intermitente no portal web", status: "AWAITING_ATTENDANCE", priority: "URGENT", channel: "EMAIL", queueId: queue1, requesterId: joaoAtlasId, assigneeId: defaultAssigneeId, companyId: atlasCompanyId, createdAt: now - 1000 * 60 * 60 * 5, updatedAt: now - 1000 * 60 * 10, tags: ["portal", "cliente"], }); await ctx.db.insert("ticketEvents", { ticketId: t1, type: "CREATED", createdAt: now - 1000 * 60 * 60 * 5, payload: {}, }); await ctx.db.insert("ticketEvents", { ticketId: t1, type: "MANAGER_NOTIFIED", createdAt: now - 1000 * 60 * 60 * 4, payload: { managerUserId: atlasManagerId }, }); const t2 = await ctx.db.insert("tickets", { tenantId, reference: ++ref, subject: "Integração ERP parada", summary: "Webhook do ERP retornando timeout", status: "PENDING", priority: "HIGH", channel: "WHATSAPP", queueId: queue2, requesterId: ricardoOmniId, assigneeId: defaultAssigneeId, companyId: omniCompanyId, createdAt: now - 1000 * 60 * 60 * 8, updatedAt: now - 1000 * 60 * 30, tags: ["Integração", "erp"], }); await ctx.db.insert("ticketEvents", { ticketId: t2, type: "CREATED", createdAt: now - 1000 * 60 * 60 * 8, payload: {}, }); await ctx.db.insert("ticketEvents", { ticketId: t2, type: "MANAGER_NOTIFIED", createdAt: now - 1000 * 60 * 60 * 7, payload: { managerUserId: omniManagerId }, }); const t3 = await ctx.db.insert("tickets", { tenantId, reference: ++ref, subject: "Visita técnica para instalação de roteadores", summary: "Equipe Omni solicita agenda para instalação de novos pontos de rede", status: "AWAITING_ATTENDANCE", priority: "MEDIUM", channel: "PHONE", queueId: queueVisitas._id, requesterId: clienteDemoId, assigneeId: defaultAssigneeId, companyId: omniCompanyId, createdAt: now - 1000 * 60 * 60 * 3, updatedAt: now - 1000 * 60 * 20, tags: ["visita", "infraestrutura"], }); await ctx.db.insert("ticketEvents", { ticketId: t3, type: "CREATED", createdAt: now - 1000 * 60 * 60 * 3, payload: {}, }); await ctx.db.insert("ticketEvents", { ticketId: t3, type: "VISIT_SCHEDULED", createdAt: now - 1000 * 60 * 15, payload: { scheduledFor: now + 1000 * 60 * 60 * 24 }, }); }, });