var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};

// drizzle/schema.ts
import { int, mysqlEnum, mysqlTable, text, timestamp, varchar, boolean } from "drizzle-orm/mysql-core";
var users, services, nrTrainings, blogPosts, contactMessages, forms, formSignatures, formPhotos, companies, formUsers, formPermissions, apiKeys, esocialLeads, emailQueue, emailTrackingEvents, salesTeamMembers;
var init_schema = __esm({
  "drizzle/schema.ts"() {
    "use strict";
    users = mysqlTable("users", {
      /**
       * Surrogate primary key. Auto-incremented numeric value managed by the database.
       * Use this for relations between tables.
       */
      id: int("id").autoincrement().primaryKey(),
      /** Manus OAuth identifier (openId) returned from the OAuth callback. Unique per user. */
      openId: varchar("openId", { length: 64 }).notNull().unique(),
      name: text("name"),
      email: varchar("email", { length: 320 }),
      loginMethod: varchar("loginMethod", { length: 64 }),
      role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(),
      createdAt: timestamp("createdAt").defaultNow().notNull(),
      updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
      lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull()
    });
    services = mysqlTable("services", {
      id: int("id").autoincrement().primaryKey(),
      slug: varchar("slug", { length: 100 }).notNull().unique(),
      title: varchar("title", { length: 255 }).notNull(),
      shortDescription: text("shortDescription"),
      fullDescription: text("fullDescription"),
      iconUrl: varchar("iconUrl", { length: 500 }),
      imageUrl: varchar("imageUrl", { length: 500 }),
      features: text("features"),
      isActive: boolean("isActive").default(true).notNull(),
      sortOrder: int("sortOrder").default(0).notNull(),
      createdAt: timestamp("createdAt").defaultNow().notNull(),
      updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull()
    });
    nrTrainings = mysqlTable("nr_trainings", {
      id: int("id").autoincrement().primaryKey(),
      nrNumber: varchar("nrNumber", { length: 20 }).notNull(),
      title: varchar("title", { length: 255 }).notNull(),
      description: text("description"),
      fullContent: text("fullContent"),
      iconUrl: varchar("iconUrl", { length: 500 }),
      isActive: boolean("isActive").default(true).notNull(),
      sortOrder: int("sortOrder").default(0).notNull(),
      createdAt: timestamp("createdAt").defaultNow().notNull(),
      updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull()
    });
    blogPosts = mysqlTable("blog_posts", {
      id: int("id").autoincrement().primaryKey(),
      slug: varchar("slug", { length: 255 }).notNull().unique(),
      title: varchar("title", { length: 255 }).notNull(),
      excerpt: text("excerpt"),
      content: text("content"),
      coverImageUrl: varchar("coverImageUrl", { length: 500 }),
      authorId: int("authorId"),
      authorName: varchar("authorName", { length: 255 }),
      category: varchar("category", { length: 100 }),
      tags: text("tags"),
      isPublished: boolean("isPublished").default(false).notNull(),
      publishedAt: timestamp("publishedAt"),
      viewCount: int("viewCount").default(0).notNull(),
      createdAt: timestamp("createdAt").defaultNow().notNull(),
      updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull()
    });
    contactMessages = mysqlTable("contact_messages", {
      id: int("id").autoincrement().primaryKey(),
      name: varchar("name", { length: 255 }).notNull(),
      email: varchar("email", { length: 320 }).notNull(),
      phone: varchar("phone", { length: 50 }),
      company: varchar("company", { length: 255 }),
      subject: varchar("subject", { length: 255 }),
      message: text("message").notNull(),
      serviceInterest: varchar("serviceInterest", { length: 255 }),
      isRead: boolean("isRead").default(false).notNull(),
      isReplied: boolean("isReplied").default(false).notNull(),
      repliedAt: timestamp("repliedAt"),
      createdAt: timestamp("createdAt").defaultNow().notNull()
    });
    forms = mysqlTable("forms", {
      id: int("id").autoincrement().primaryKey(),
      companyId: int("companyId"),
      // Reference to company that owns this form
      formUserId: int("formUserId"),
      // Reference to form user who created this
      formType: varchar("formType", { length: 100 }).notNull(),
      // 'accident-investigation', 'internal-communication', etc.
      formCode: varchar("formCode", { length: 50 }).notNull(),
      // 'RI-010', 'CI-001', etc.
      formData: text("formData").notNull(),
      // JSON string with all form fields
      status: mysqlEnum("status", ["draft", "submitted", "in_review", "approved", "rejected"]).default("submitted").notNull(),
      submittedBy: varchar("submittedBy", { length: 255 }).notNull(),
      submittedByEmail: varchar("submittedByEmail", { length: 320 }),
      submittedByCpf: varchar("submittedByCpf", { length: 14 }),
      reviewedBy: varchar("reviewedBy", { length: 255 }),
      reviewedAt: timestamp("reviewedAt"),
      reviewNotes: text("reviewNotes"),
      createdAt: timestamp("createdAt").defaultNow().notNull(),
      updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull()
    });
    formSignatures = mysqlTable("form_signatures", {
      id: int("id").autoincrement().primaryKey(),
      formId: int("formId").notNull(),
      signerName: varchar("signerName", { length: 255 }).notNull(),
      signerRole: varchar("signerRole", { length: 100 }).notNull(),
      // 'responsible', 'approver', 'witness'
      signatureData: text("signatureData").notNull(),
      // Base64 encoded signature image
      signedAt: timestamp("signedAt").defaultNow().notNull(),
      ipAddress: varchar("ipAddress", { length: 45 })
    });
    formPhotos = mysqlTable("form_photos", {
      id: int("id").autoincrement().primaryKey(),
      formId: int("formId").notNull(),
      photoUrl: varchar("photoUrl", { length: 500 }).notNull(),
      photoData: text("photoData"),
      // Base64 encoded image data (optional, for backup)
      caption: varchar("caption", { length: 255 }),
      uploadedAt: timestamp("uploadedAt").defaultNow().notNull()
    });
    companies = mysqlTable("companies", {
      id: int("id").autoincrement().primaryKey(),
      name: varchar("name", { length: 255 }).notNull(),
      cnpj: varchar("cnpj", { length: 18 }).unique(),
      email: varchar("email", { length: 320 }),
      phone: varchar("phone", { length: 50 }),
      address: text("address"),
      isActive: boolean("isActive").default(true).notNull(),
      createdAt: timestamp("createdAt").defaultNow().notNull(),
      updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull()
    });
    formUsers = mysqlTable("form_users", {
      id: int("id").autoincrement().primaryKey(),
      companyId: int("companyId").notNull(),
      username: varchar("username", { length: 100 }).notNull(),
      passwordHash: varchar("passwordHash", { length: 255 }).notNull(),
      name: varchar("name", { length: 255 }).notNull(),
      email: varchar("email", { length: 320 }),
      userType: mysqlEnum("userType", ["rh", "sesmt", "admin"]).notNull(),
      isActive: boolean("isActive").default(true).notNull(),
      lastLogin: timestamp("lastLogin"),
      createdAt: timestamp("createdAt").defaultNow().notNull(),
      updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull()
    });
    formPermissions = mysqlTable("form_permissions", {
      id: int("id").autoincrement().primaryKey(),
      userType: mysqlEnum("userType", ["rh", "sesmt", "admin"]).notNull(),
      formType: varchar("formType", { length: 100 }).notNull(),
      canView: boolean("canView").default(true).notNull(),
      canCreate: boolean("canCreate").default(true).notNull(),
      canEdit: boolean("canEdit").default(false).notNull(),
      canDelete: boolean("canDelete").default(false).notNull()
    });
    apiKeys = mysqlTable("api_keys", {
      id: int("id").autoincrement().primaryKey(),
      companyId: int("companyId").notNull(),
      keyHash: varchar("keyHash", { length: 255 }).notNull().unique(),
      keyPrefix: varchar("keyPrefix", { length: 10 }).notNull(),
      // First 8 chars for identification
      name: varchar("name", { length: 255 }).notNull(),
      permissions: text("permissions"),
      // JSON array of allowed endpoints
      isActive: boolean("isActive").default(true).notNull(),
      lastUsed: timestamp("lastUsed"),
      expiresAt: timestamp("expiresAt"),
      createdAt: timestamp("createdAt").defaultNow().notNull()
    });
    esocialLeads = mysqlTable("esocial_leads", {
      id: int("id").autoincrement().primaryKey(),
      name: varchar("name", { length: 255 }).notNull(),
      email: varchar("email", { length: 320 }).notNull(),
      company: varchar("company", { length: 255 }),
      phone: varchar("phone", { length: 50 }),
      source: varchar("source", { length: 100 }).default("esocial_alert").notNull(),
      isContacted: boolean("isContacted").default(false).notNull(),
      contactedAt: timestamp("contactedAt"),
      notes: text("notes"),
      // Unsubscribe / LGPD
      isUnsubscribed: boolean("isUnsubscribed").default(false).notNull(),
      unsubscribedAt: timestamp("unsubscribedAt"),
      unsubscribeToken: varchar("unsubscribeToken", { length: 64 }),
      // Lead scoring
      score: int("score").default(0).notNull(),
      emailsOpened: int("emailsOpened").default(0).notNull(),
      emailsClicked: int("emailsClicked").default(0).notNull(),
      lastInteractionAt: timestamp("lastInteractionAt"),
      segment: mysqlEnum("segment", ["cold", "warm", "hot", "converted"]).default("cold").notNull(),
      createdAt: timestamp("createdAt").defaultNow().notNull()
    });
    emailQueue = mysqlTable("email_queue", {
      id: int("id").autoincrement().primaryKey(),
      leadId: int("leadId").notNull(),
      toEmail: varchar("toEmail", { length: 320 }).notNull(),
      toName: varchar("toName", { length: 255 }).notNull(),
      sequenceStep: int("sequenceStep").notNull(),
      // 1, 2, or 3
      scheduledAt: timestamp("scheduledAt").notNull(),
      sentAt: timestamp("sentAt"),
      status: mysqlEnum("status", ["pending", "sent", "failed"]).default("pending").notNull(),
      messageId: varchar("messageId", { length: 255 }),
      errorMessage: text("errorMessage"),
      attempts: int("attempts").default(0).notNull(),
      createdAt: timestamp("createdAt").defaultNow().notNull()
    });
    emailTrackingEvents = mysqlTable("email_tracking_events", {
      id: int("id").autoincrement().primaryKey(),
      leadId: int("leadId").notNull(),
      emailQueueId: int("emailQueueId"),
      eventType: mysqlEnum("eventType", ["open", "click"]).notNull(),
      sequenceStep: int("sequenceStep"),
      metadata: text("metadata"),
      // JSON with extra info (link clicked, user agent, etc.)
      createdAt: timestamp("createdAt").defaultNow().notNull()
    });
    salesTeamMembers = mysqlTable("sales_team_members", {
      id: int("id").autoincrement().primaryKey(),
      name: varchar("name", { length: 255 }).notNull(),
      email: varchar("email", { length: 320 }).notNull(),
      role: varchar("role", { length: 100 }).default("vendedor").notNull(),
      // 'vendedor', 'gerente', 'diretor'
      isActive: boolean("isActive").default(true).notNull(),
      receiveLeadNotifications: boolean("receiveLeadNotifications").default(true).notNull(),
      receiveWeeklyReport: boolean("receiveWeeklyReport").default(false).notNull(),
      createdAt: timestamp("createdAt").defaultNow().notNull(),
      updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull()
    });
  }
});

// server/_core/env.ts
var ENV;
var init_env = __esm({
  "server/_core/env.ts"() {
    "use strict";
    ENV = {
      appId: process.env.VITE_APP_ID ?? "",
      cookieSecret: process.env.JWT_SECRET ?? "",
      databaseUrl: process.env.DATABASE_URL ?? "",
      oAuthServerUrl: process.env.OAUTH_SERVER_URL ?? "",
      ownerOpenId: process.env.OWNER_OPEN_ID ?? "",
      isProduction: process.env.NODE_ENV === "production",
      forgeApiUrl: process.env.BUILT_IN_FORGE_API_URL ?? "",
      forgeApiKey: process.env.BUILT_IN_FORGE_API_KEY ?? ""
    };
  }
});

// server/db.ts
var db_exports = {};
__export(db_exports, {
  createBlogPost: () => createBlogPost,
  createContactMessage: () => createContactMessage,
  createEsocialLead: () => createEsocialLead,
  createNrTraining: () => createNrTraining,
  createSalesTeamMember: () => createSalesTeamMember,
  createService: () => createService,
  deleteSalesTeamMember: () => deleteSalesTeamMember,
  getActiveSalesTeamMembers: () => getActiveSalesTeamMembers,
  getAllContactMessages: () => getAllContactMessages,
  getAllEsocialLeads: () => getAllEsocialLeads,
  getAllNrTrainings: () => getAllNrTrainings,
  getAllSalesTeamMembers: () => getAllSalesTeamMembers,
  getAllServices: () => getAllServices,
  getBlogPostBySlug: () => getBlogPostBySlug,
  getDb: () => getDb,
  getEmailQueueStats: () => getEmailQueueStats,
  getLeadByEmail: () => getLeadByEmail,
  getLeadById: () => getLeadById,
  getLeadByUnsubscribeToken: () => getLeadByUnsubscribeToken,
  getLeadTrackingEvents: () => getLeadTrackingEvents,
  getNrTrainingByNumber: () => getNrTrainingByNumber,
  getPendingEmails: () => getPendingEmails,
  getPublishedBlogPosts: () => getPublishedBlogPosts,
  getSalesTeamMembersForLeadNotification: () => getSalesTeamMembersForLeadNotification,
  getServiceBySlug: () => getServiceBySlug,
  getUserByOpenId: () => getUserByOpenId,
  incrementBlogPostViewCount: () => incrementBlogPostViewCount,
  incrementLeadClicks: () => incrementLeadClicks,
  incrementLeadOpens: () => incrementLeadOpens,
  markEmailFailed: () => markEmailFailed,
  markEmailSent: () => markEmailSent,
  markLeadAsContacted: () => markLeadAsContacted,
  markMessageAsRead: () => markMessageAsRead,
  recordTrackingEvent: () => recordTrackingEvent,
  scheduleEmail: () => scheduleEmail,
  unsubscribeLead: () => unsubscribeLead,
  updateLeadNotes: () => updateLeadNotes,
  updateLeadSegment: () => updateLeadSegment,
  updateSalesTeamMember: () => updateSalesTeamMember,
  upsertUser: () => upsertUser
});
import { eq } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
import { desc, asc } from "drizzle-orm";
import crypto2 from "crypto";
async function getDb() {
  if (!_db && process.env.DATABASE_URL) {
    try {
      _db = drizzle(process.env.DATABASE_URL);
    } catch (error) {
      console.warn("[Database] Failed to connect:", error);
      _db = null;
    }
  }
  return _db;
}
async function upsertUser(user) {
  if (!user.openId) {
    throw new Error("User openId is required for upsert");
  }
  const db = await getDb();
  if (!db) {
    console.warn("[Database] Cannot upsert user: database not available");
    return;
  }
  try {
    const values = {
      openId: user.openId
    };
    const updateSet = {};
    const textFields = ["name", "email", "loginMethod"];
    const assignNullable = (field) => {
      const value = user[field];
      if (value === void 0) return;
      const normalized = value ?? null;
      values[field] = normalized;
      updateSet[field] = normalized;
    };
    textFields.forEach(assignNullable);
    if (user.lastSignedIn !== void 0) {
      values.lastSignedIn = user.lastSignedIn;
      updateSet.lastSignedIn = user.lastSignedIn;
    }
    if (user.role !== void 0) {
      values.role = user.role;
      updateSet.role = user.role;
    } else if (user.openId === ENV.ownerOpenId) {
      values.role = "admin";
      updateSet.role = "admin";
    }
    if (!values.lastSignedIn) {
      values.lastSignedIn = /* @__PURE__ */ new Date();
    }
    if (Object.keys(updateSet).length === 0) {
      updateSet.lastSignedIn = /* @__PURE__ */ new Date();
    }
    await db.insert(users).values(values).onDuplicateKeyUpdate({
      set: updateSet
    });
  } catch (error) {
    console.error("[Database] Failed to upsert user:", error);
    throw error;
  }
}
async function getUserByOpenId(openId) {
  const db = await getDb();
  if (!db) {
    console.warn("[Database] Cannot get user: database not available");
    return void 0;
  }
  const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1);
  return result.length > 0 ? result[0] : void 0;
}
async function getAllServices() {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(services).where(eq(services.isActive, true)).orderBy(asc(services.sortOrder));
}
async function getServiceBySlug(slug) {
  const db = await getDb();
  if (!db) return void 0;
  const result = await db.select().from(services).where(eq(services.slug, slug)).limit(1);
  return result.length > 0 ? result[0] : void 0;
}
async function createService(service) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.insert(services).values(service);
}
async function getAllNrTrainings() {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(nrTrainings).where(eq(nrTrainings.isActive, true)).orderBy(asc(nrTrainings.sortOrder));
}
async function getNrTrainingByNumber(nrNumber) {
  const db = await getDb();
  if (!db) return void 0;
  const result = await db.select().from(nrTrainings).where(eq(nrTrainings.nrNumber, nrNumber)).limit(1);
  return result.length > 0 ? result[0] : void 0;
}
async function createNrTraining(training) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.insert(nrTrainings).values(training);
}
async function getPublishedBlogPosts(limit) {
  const db = await getDb();
  if (!db) return [];
  let query = db.select().from(blogPosts).where(eq(blogPosts.isPublished, true)).orderBy(desc(blogPosts.publishedAt));
  if (limit) {
    return query.limit(limit);
  }
  return query;
}
async function getBlogPostBySlug(slug) {
  const db = await getDb();
  if (!db) return void 0;
  const result = await db.select().from(blogPosts).where(eq(blogPosts.slug, slug)).limit(1);
  return result.length > 0 ? result[0] : void 0;
}
async function createBlogPost(post) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.insert(blogPosts).values(post);
}
async function incrementBlogPostViewCount(id) {
  const db = await getDb();
  if (!db) return;
  const post = await db.select().from(blogPosts).where(eq(blogPosts.id, id)).limit(1);
  if (post.length > 0) {
    await db.update(blogPosts).set({ viewCount: (post[0].viewCount || 0) + 1 }).where(eq(blogPosts.id, id));
  }
}
async function createContactMessage(message) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.insert(contactMessages).values(message);
}
async function getAllContactMessages() {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(contactMessages).orderBy(desc(contactMessages.createdAt));
}
async function markMessageAsRead(id) {
  const db = await getDb();
  if (!db) return;
  await db.update(contactMessages).set({ isRead: true }).where(eq(contactMessages.id, id));
}
async function createEsocialLead(lead) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const unsubscribeToken = crypto2.randomBytes(32).toString("hex");
  await db.insert(esocialLeads).values({ ...lead, unsubscribeToken });
  return unsubscribeToken;
}
async function getAllEsocialLeads() {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(esocialLeads).orderBy(desc(esocialLeads.createdAt));
}
async function markLeadAsContacted(id) {
  const db = await getDb();
  if (!db) return;
  await db.update(esocialLeads).set({ isContacted: true, contactedAt: /* @__PURE__ */ new Date() }).where(eq(esocialLeads.id, id));
}
async function scheduleEmail(item) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.insert(emailQueue).values(item);
}
async function getPendingEmails() {
  const db = await getDb();
  if (!db) return [];
  const { and: and3, lte: lte2 } = await import("drizzle-orm");
  return db.select().from(emailQueue).where(
    and3(
      eq(emailQueue.status, "pending"),
      lte2(emailQueue.scheduledAt, /* @__PURE__ */ new Date()),
      lte2(emailQueue.attempts, 3)
    )
  ).orderBy(asc(emailQueue.scheduledAt)).limit(50);
}
async function markEmailSent(id, messageId) {
  const db = await getDb();
  if (!db) return;
  await db.update(emailQueue).set({ status: "sent", sentAt: /* @__PURE__ */ new Date(), messageId }).where(eq(emailQueue.id, id));
}
async function markEmailFailed(id, errorMessage, attempts) {
  const db = await getDb();
  if (!db) return;
  await db.update(emailQueue).set({ status: attempts >= 3 ? "failed" : "pending", errorMessage, attempts }).where(eq(emailQueue.id, id));
}
async function getEmailQueueStats() {
  const db = await getDb();
  if (!db) return { pending: 0, sent: 0, failed: 0 };
  const all = await db.select().from(emailQueue);
  return {
    pending: all.filter((e) => e.status === "pending").length,
    sent: all.filter((e) => e.status === "sent").length,
    failed: all.filter((e) => e.status === "failed").length
  };
}
async function getLeadByUnsubscribeToken(token) {
  const db = await getDb();
  if (!db) return null;
  const results = await db.select().from(esocialLeads).where(eq(esocialLeads.unsubscribeToken, token)).limit(1);
  return results[0] || null;
}
async function unsubscribeLead(token) {
  const db = await getDb();
  if (!db) return false;
  const result = await db.update(esocialLeads).set({ isUnsubscribed: true, unsubscribedAt: /* @__PURE__ */ new Date() }).where(eq(esocialLeads.unsubscribeToken, token));
  return true;
}
async function getLeadByEmail(email) {
  const db = await getDb();
  if (!db) return null;
  const results = await db.select().from(esocialLeads).where(eq(esocialLeads.email, email)).limit(1);
  return results[0] || null;
}
async function getLeadById(id) {
  const db = await getDb();
  if (!db) return null;
  const results = await db.select().from(esocialLeads).where(eq(esocialLeads.id, id)).limit(1);
  return results[0] || null;
}
async function recordTrackingEvent(event) {
  const db = await getDb();
  if (!db) return;
  await db.insert(emailTrackingEvents).values(event);
}
async function incrementLeadOpens(leadId) {
  const db = await getDb();
  if (!db) return;
  const lead = await getLeadById(leadId);
  if (!lead) return;
  const newOpens = lead.emailsOpened + 1;
  const newScore = calculateScore(newOpens, lead.emailsClicked);
  const newSegment = getSegmentFromScore(newScore);
  await db.update(esocialLeads).set({
    emailsOpened: newOpens,
    score: newScore,
    segment: newSegment,
    lastInteractionAt: /* @__PURE__ */ new Date()
  }).where(eq(esocialLeads.id, leadId));
}
async function incrementLeadClicks(leadId) {
  const db = await getDb();
  if (!db) return;
  const lead = await getLeadById(leadId);
  if (!lead) return;
  const newClicks = lead.emailsClicked + 1;
  const newScore = calculateScore(lead.emailsOpened, newClicks);
  const newSegment = getSegmentFromScore(newScore);
  await db.update(esocialLeads).set({
    emailsClicked: newClicks,
    score: newScore,
    segment: newSegment,
    lastInteractionAt: /* @__PURE__ */ new Date()
  }).where(eq(esocialLeads.id, leadId));
}
async function updateLeadSegment(id, segment) {
  const db = await getDb();
  if (!db) return;
  await db.update(esocialLeads).set({ segment }).where(eq(esocialLeads.id, id));
}
async function updateLeadNotes(id, notes) {
  const db = await getDb();
  if (!db) return;
  await db.update(esocialLeads).set({ notes }).where(eq(esocialLeads.id, id));
}
async function getLeadTrackingEvents(leadId) {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(emailTrackingEvents).where(eq(emailTrackingEvents.leadId, leadId)).orderBy(desc(emailTrackingEvents.createdAt));
}
async function getActiveSalesTeamMembers() {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(salesTeamMembers).where(eq(salesTeamMembers.isActive, true)).orderBy(asc(salesTeamMembers.name));
}
async function getSalesTeamMembersForLeadNotification() {
  const db = await getDb();
  if (!db) return [];
  const { and: and3 } = await import("drizzle-orm");
  return db.select().from(salesTeamMembers).where(
    and3(
      eq(salesTeamMembers.isActive, true),
      eq(salesTeamMembers.receiveLeadNotifications, true)
    )
  );
}
async function getAllSalesTeamMembers() {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(salesTeamMembers).orderBy(desc(salesTeamMembers.createdAt));
}
async function createSalesTeamMember(member) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.insert(salesTeamMembers).values(member);
}
async function updateSalesTeamMember(id, data) {
  const db = await getDb();
  if (!db) return;
  await db.update(salesTeamMembers).set(data).where(eq(salesTeamMembers.id, id));
}
async function deleteSalesTeamMember(id) {
  const db = await getDb();
  if (!db) return;
  await db.delete(salesTeamMembers).where(eq(salesTeamMembers.id, id));
}
function calculateScore(opens, clicks) {
  return 10 + opens * 5 + clicks * 15;
}
function getSegmentFromScore(score) {
  if (score >= 26) return "hot";
  if (score >= 11) return "warm";
  return "cold";
}
var _db;
var init_db = __esm({
  "server/db.ts"() {
    "use strict";
    init_schema();
    init_env();
    init_schema();
    _db = null;
  }
});

// server/_core/index.ts
import "dotenv/config";
import express2 from "express";
import { createServer } from "http";
import net from "net";
import { createExpressMiddleware } from "@trpc/server/adapters/express";

// shared/const.ts
var COOKIE_NAME = "app_session_id";
var ONE_YEAR_MS = 1e3 * 60 * 60 * 24 * 365;
var AXIOS_TIMEOUT_MS = 3e4;
var UNAUTHED_ERR_MSG = "Please login (10001)";
var NOT_ADMIN_ERR_MSG = "You do not have required permission (10002)";

// server/_core/oauth.ts
init_db();

// server/_core/cookies.ts
function isSecureRequest(req) {
  if (req.protocol === "https") return true;
  const forwardedProto = req.headers["x-forwarded-proto"];
  if (!forwardedProto) return false;
  const protoList = Array.isArray(forwardedProto) ? forwardedProto : forwardedProto.split(",");
  return protoList.some((proto) => proto.trim().toLowerCase() === "https");
}
function getSessionCookieOptions(req) {
  return {
    httpOnly: true,
    path: "/",
    sameSite: "none",
    secure: isSecureRequest(req)
  };
}

// shared/_core/errors.ts
var HttpError = class extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
    this.name = "HttpError";
  }
};
var ForbiddenError = (msg) => new HttpError(403, msg);

// server/_core/sdk.ts
init_db();
init_env();
import axios from "axios";
import { parse as parseCookieHeader } from "cookie";
import { SignJWT, jwtVerify } from "jose";
var isNonEmptyString = (value) => typeof value === "string" && value.length > 0;
var EXCHANGE_TOKEN_PATH = `/webdev.v1.WebDevAuthPublicService/ExchangeToken`;
var GET_USER_INFO_PATH = `/webdev.v1.WebDevAuthPublicService/GetUserInfo`;
var GET_USER_INFO_WITH_JWT_PATH = `/webdev.v1.WebDevAuthPublicService/GetUserInfoWithJwt`;
var OAuthService = class {
  constructor(client) {
    this.client = client;
    console.log("[OAuth] Initialized with baseURL:", ENV.oAuthServerUrl);
    if (!ENV.oAuthServerUrl) {
      console.error(
        "[OAuth] ERROR: OAUTH_SERVER_URL is not configured! Set OAUTH_SERVER_URL environment variable."
      );
    }
  }
  decodeState(state) {
    const redirectUri = atob(state);
    return redirectUri;
  }
  async getTokenByCode(code, state) {
    const payload = {
      clientId: ENV.appId,
      grantType: "authorization_code",
      code,
      redirectUri: this.decodeState(state)
    };
    const { data } = await this.client.post(
      EXCHANGE_TOKEN_PATH,
      payload
    );
    return data;
  }
  async getUserInfoByToken(token) {
    const { data } = await this.client.post(
      GET_USER_INFO_PATH,
      {
        accessToken: token.accessToken
      }
    );
    return data;
  }
};
var createOAuthHttpClient = () => axios.create({
  baseURL: ENV.oAuthServerUrl,
  timeout: AXIOS_TIMEOUT_MS
});
var SDKServer = class {
  client;
  oauthService;
  constructor(client = createOAuthHttpClient()) {
    this.client = client;
    this.oauthService = new OAuthService(this.client);
  }
  deriveLoginMethod(platforms, fallback) {
    if (fallback && fallback.length > 0) return fallback;
    if (!Array.isArray(platforms) || platforms.length === 0) return null;
    const set = new Set(
      platforms.filter((p) => typeof p === "string")
    );
    if (set.has("REGISTERED_PLATFORM_EMAIL")) return "email";
    if (set.has("REGISTERED_PLATFORM_GOOGLE")) return "google";
    if (set.has("REGISTERED_PLATFORM_APPLE")) return "apple";
    if (set.has("REGISTERED_PLATFORM_MICROSOFT") || set.has("REGISTERED_PLATFORM_AZURE"))
      return "microsoft";
    if (set.has("REGISTERED_PLATFORM_GITHUB")) return "github";
    const first = Array.from(set)[0];
    return first ? first.toLowerCase() : null;
  }
  /**
   * Exchange OAuth authorization code for access token
   * @example
   * const tokenResponse = await sdk.exchangeCodeForToken(code, state);
   */
  async exchangeCodeForToken(code, state) {
    return this.oauthService.getTokenByCode(code, state);
  }
  /**
   * Get user information using access token
   * @example
   * const userInfo = await sdk.getUserInfo(tokenResponse.accessToken);
   */
  async getUserInfo(accessToken) {
    const data = await this.oauthService.getUserInfoByToken({
      accessToken
    });
    const loginMethod = this.deriveLoginMethod(
      data?.platforms,
      data?.platform ?? data.platform ?? null
    );
    return {
      ...data,
      platform: loginMethod,
      loginMethod
    };
  }
  parseCookies(cookieHeader) {
    if (!cookieHeader) {
      return /* @__PURE__ */ new Map();
    }
    const parsed = parseCookieHeader(cookieHeader);
    return new Map(Object.entries(parsed));
  }
  getSessionSecret() {
    const secret = ENV.cookieSecret;
    return new TextEncoder().encode(secret);
  }
  /**
   * Create a session token for a Manus user openId
   * @example
   * const sessionToken = await sdk.createSessionToken(userInfo.openId);
   */
  async createSessionToken(openId, options = {}) {
    return this.signSession(
      {
        openId,
        appId: ENV.appId,
        name: options.name || ""
      },
      options
    );
  }
  async signSession(payload, options = {}) {
    const issuedAt = Date.now();
    const expiresInMs = options.expiresInMs ?? ONE_YEAR_MS;
    const expirationSeconds = Math.floor((issuedAt + expiresInMs) / 1e3);
    const secretKey = this.getSessionSecret();
    return new SignJWT({
      openId: payload.openId,
      appId: payload.appId,
      name: payload.name
    }).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setExpirationTime(expirationSeconds).sign(secretKey);
  }
  async verifySession(cookieValue) {
    if (!cookieValue) {
      console.warn("[Auth] Missing session cookie");
      return null;
    }
    try {
      const secretKey = this.getSessionSecret();
      const { payload } = await jwtVerify(cookieValue, secretKey, {
        algorithms: ["HS256"]
      });
      const { openId, appId, name } = payload;
      if (!isNonEmptyString(openId) || !isNonEmptyString(appId) || !isNonEmptyString(name)) {
        console.warn("[Auth] Session payload missing required fields");
        return null;
      }
      return {
        openId,
        appId,
        name
      };
    } catch (error) {
      console.warn("[Auth] Session verification failed", String(error));
      return null;
    }
  }
  async getUserInfoWithJwt(jwtToken) {
    const payload = {
      jwtToken,
      projectId: ENV.appId
    };
    const { data } = await this.client.post(
      GET_USER_INFO_WITH_JWT_PATH,
      payload
    );
    const loginMethod = this.deriveLoginMethod(
      data?.platforms,
      data?.platform ?? data.platform ?? null
    );
    return {
      ...data,
      platform: loginMethod,
      loginMethod
    };
  }
  async authenticateRequest(req) {
    const cookies = this.parseCookies(req.headers.cookie);
    const sessionCookie = cookies.get(COOKIE_NAME);
    const session = await this.verifySession(sessionCookie);
    if (!session) {
      throw ForbiddenError("Invalid session cookie");
    }
    const sessionUserId = session.openId;
    const signedInAt = /* @__PURE__ */ new Date();
    let user = await getUserByOpenId(sessionUserId);
    if (!user) {
      try {
        const userInfo = await this.getUserInfoWithJwt(sessionCookie ?? "");
        await upsertUser({
          openId: userInfo.openId,
          name: userInfo.name || null,
          email: userInfo.email ?? null,
          loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
          lastSignedIn: signedInAt
        });
        user = await getUserByOpenId(userInfo.openId);
      } catch (error) {
        console.error("[Auth] Failed to sync user from OAuth:", error);
        throw ForbiddenError("Failed to sync user info");
      }
    }
    if (!user) {
      throw ForbiddenError("User not found");
    }
    await upsertUser({
      openId: user.openId,
      lastSignedIn: signedInAt
    });
    return user;
  }
};
var sdk = new SDKServer();

// server/_core/oauth.ts
function getQueryParam(req, key) {
  const value = req.query[key];
  return typeof value === "string" ? value : void 0;
}
function registerOAuthRoutes(app) {
  app.get("/api/oauth/callback", async (req, res) => {
    const code = getQueryParam(req, "code");
    const state = getQueryParam(req, "state");
    if (!code || !state) {
      res.status(400).json({ error: "code and state are required" });
      return;
    }
    try {
      const tokenResponse = await sdk.exchangeCodeForToken(code, state);
      const userInfo = await sdk.getUserInfo(tokenResponse.accessToken);
      if (!userInfo.openId) {
        res.status(400).json({ error: "openId missing from user info" });
        return;
      }
      await upsertUser({
        openId: userInfo.openId,
        name: userInfo.name || null,
        email: userInfo.email ?? null,
        loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
        lastSignedIn: /* @__PURE__ */ new Date()
      });
      const sessionToken = await sdk.createSessionToken(userInfo.openId, {
        name: userInfo.name || "",
        expiresInMs: ONE_YEAR_MS
      });
      const cookieOptions = getSessionCookieOptions(req);
      res.cookie(COOKIE_NAME, sessionToken, { ...cookieOptions, maxAge: ONE_YEAR_MS });
      res.redirect(302, "/");
    } catch (error) {
      console.error("[OAuth] Callback failed", error);
      res.status(500).json({ error: "OAuth callback failed" });
    }
  });
}

// server/_core/systemRouter.ts
import { z } from "zod";

// server/_core/notification.ts
init_env();
import { TRPCError } from "@trpc/server";
var TITLE_MAX_LENGTH = 1200;
var CONTENT_MAX_LENGTH = 2e4;
var trimValue = (value) => value.trim();
var isNonEmptyString2 = (value) => typeof value === "string" && value.trim().length > 0;
var buildEndpointUrl = (baseUrl) => {
  const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
  return new URL(
    "webdevtoken.v1.WebDevService/SendNotification",
    normalizedBase
  ).toString();
};
var validatePayload = (input) => {
  if (!isNonEmptyString2(input.title)) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "Notification title is required."
    });
  }
  if (!isNonEmptyString2(input.content)) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "Notification content is required."
    });
  }
  const title = trimValue(input.title);
  const content = trimValue(input.content);
  if (title.length > TITLE_MAX_LENGTH) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Notification title must be at most ${TITLE_MAX_LENGTH} characters.`
    });
  }
  if (content.length > CONTENT_MAX_LENGTH) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Notification content must be at most ${CONTENT_MAX_LENGTH} characters.`
    });
  }
  return { title, content };
};
async function notifyFormSubmission(formType, formCode, submittedBy, submittedByEmail) {
  const formTypeNames = {
    "investigacao-acidente": "Investiga\xE7\xE3o de Acidente/Incidente",
    "comunicacao-interna": "Comunica\xE7\xE3o Interna",
    "checklist-seguranca": "Checklist de Seguran\xE7a",
    "ordem-servico": "Ordem de Servi\xE7o",
    "solicitacao-epi": "Solicita\xE7\xE3o/Troca EPI & EPC",
    "relatorio-atividades": "Relat\xF3rio de Atividades",
    "nao-conformidade": "Registro de N\xE3o Conformidade",
    "solicitacao-treinamento": "Solicita\xE7\xE3o de Treinamento",
    "permissao-trabalho": "Permiss\xE3o de Trabalho",
    "apr": "An\xE1lise Preliminar de Risco (APR)",
    "inspecao-emergencia": "Inspe\xE7\xE3o de Sistemas de Emerg\xEAncia"
  };
  const formTypeName = formTypeNames[formType] || formType;
  const now = (/* @__PURE__ */ new Date()).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
  const title = `\u{1F4DD} Novo Formul\xE1rio Submetido: ${formTypeName}`;
  const content = `Um novo formul\xE1rio foi submetido no sistema EndoSESMT.

\u{1F4C4} **Tipo:** ${formTypeName}
\u{1F522} **C\xF3digo:** ${formCode}
\u{1F464} **Enviado por:** ${submittedBy}${submittedByEmail ? `
\u{1F4E7} **Email:** ${submittedByEmail}` : ""}
\u{1F4C5} **Data/Hora:** ${now}

Acesse o sistema para revisar e aprovar o formul\xE1rio.`;
  return notifyOwner({ title, content });
}
async function notifyOwner(payload) {
  const { title, content } = validatePayload(payload);
  if (!ENV.forgeApiUrl) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Notification service URL is not configured."
    });
  }
  if (!ENV.forgeApiKey) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Notification service API key is not configured."
    });
  }
  const endpoint = buildEndpointUrl(ENV.forgeApiUrl);
  try {
    const response = await fetch(endpoint, {
      method: "POST",
      headers: {
        accept: "application/json",
        authorization: `Bearer ${ENV.forgeApiKey}`,
        "content-type": "application/json",
        "connect-protocol-version": "1"
      },
      body: JSON.stringify({ title, content })
    });
    if (!response.ok) {
      const detail = await response.text().catch(() => "");
      console.warn(
        `[Notification] Failed to notify owner (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`
      );
      return false;
    }
    return true;
  } catch (error) {
    console.warn("[Notification] Error calling notification service:", error);
    return false;
  }
}

// server/_core/trpc.ts
import { initTRPC, TRPCError as TRPCError2 } from "@trpc/server";
import superjson from "superjson";
var t = initTRPC.context().create({
  transformer: superjson
});
var router = t.router;
var publicProcedure = t.procedure;
var requireUser = t.middleware(async (opts) => {
  const { ctx, next } = opts;
  if (!ctx.user) {
    throw new TRPCError2({ code: "UNAUTHORIZED", message: UNAUTHED_ERR_MSG });
  }
  return next({
    ctx: {
      ...ctx,
      user: ctx.user
    }
  });
});
var protectedProcedure = t.procedure.use(requireUser);
var adminProcedure = t.procedure.use(
  t.middleware(async (opts) => {
    const { ctx, next } = opts;
    if (!ctx.user || ctx.user.role !== "admin") {
      throw new TRPCError2({ code: "FORBIDDEN", message: NOT_ADMIN_ERR_MSG });
    }
    return next({
      ctx: {
        ...ctx,
        user: ctx.user
      }
    });
  })
);

// server/_core/systemRouter.ts
var systemRouter = router({
  health: publicProcedure.input(
    z.object({
      timestamp: z.number().min(0, "timestamp cannot be negative")
    })
  ).query(() => ({
    ok: true
  })),
  notifyOwner: adminProcedure.input(
    z.object({
      title: z.string().min(1, "title is required"),
      content: z.string().min(1, "content is required")
    })
  ).mutation(async ({ input }) => {
    const delivered = await notifyOwner(input);
    return {
      success: delivered
    };
  })
});

// server/routers.ts
init_db();
import { z as z2 } from "zod";

// server/emailMarketing.ts
import { Resend } from "resend";
var resend = new Resend(process.env.RESEND_API_KEY);
var FROM_EMAIL = "Endosesmt <onboarding@resend.dev>";
var REPLY_TO = "contato@endosesmt.com.br";
function getBaseUrl() {
  return process.env.VITE_APP_URL || process.env.BASE_URL || "https://endosesmt.com.br";
}
function getLgpdFooter(unsubscribeToken, leadId, step) {
  const baseUrl = getBaseUrl();
  const unsubscribeUrl = `${baseUrl}/api/email/unsubscribe?token=${unsubscribeToken}`;
  const trackingPixelUrl = `${baseUrl}/api/email/track/open?lid=${leadId}&step=${step}`;
  return `
          <!-- Tracking pixel -->
          <img src="${trackingPixelUrl}" width="1" height="1" alt="" style="display:none;" />
          <!-- LGPD Footer -->
          <tr>
            <td style="background-color:#f8fafc;padding:25px 40px;border-top:1px solid #e5e7eb;">
              <p style="color:#6b7280;font-size:12px;line-height:1.5;margin:0;text-align:center;">
                EndoSESMT - Sa\xFAde e Seguran\xE7a do Trabalho<br>
                Telefone: (19) 99148-8900 | contato@endosesmt.com.br<br>
                <a href="https://endosesmt.com.br" style="color:#1E58A6;">www.endosesmt.com.br</a>
              </p>
              <p style="color:#9ca3af;font-size:11px;line-height:1.4;margin:15px 0 0;text-align:center;">
                Voc\xEA est\xE1 recebendo este e-mail porque se cadastrou para receber informa\xE7\xF5es sobre conformidade E-Social/SST.<br>
                Em conformidade com a <strong>LGPD (Lei Geral de Prote\xE7\xE3o de Dados)</strong>, voc\xEA pode cancelar o recebimento a qualquer momento.<br>
                <a href="${unsubscribeUrl}" style="color:#dc2626;text-decoration:underline;">Cancelar inscri\xE7\xE3o / Descadastrar-se</a>
              </p>
            </td>
          </tr>`;
}
function trackLink(url, leadId, step) {
  const baseUrl = getBaseUrl();
  return `${baseUrl}/api/email/track/click?lid=${leadId}&step=${step}&url=${encodeURIComponent(url)}`;
}
function getWelcomeEmail(name, unsubscribeToken, leadId) {
  const firstName = name.split(" ")[0];
  const whatsappUrl = trackLink("https://wa.me/5519991488900?text=Ol%C3%A1%2C%20recebi%20o%20e-mail%20sobre%20o%20conflito%20E-Social%20x%20RFB%20e%20gostaria%20de%20saber%20mais.", leadId, 1);
  return {
    subject: "Bem-vindo(a) \xE0 EndoSESMT - Sua empresa pode estar em risco!",
    html: `
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin:0;padding:0;background-color:#f4f6f9;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background-color:#f4f6f9;padding:20px 0;">
    <tr>
      <td align="center">
        <table width="600" cellpadding="0" cellspacing="0" style="background-color:#ffffff;border-radius:8px;overflow:hidden;">
          <!-- Header -->
          <tr>
            <td style="background-color:#051328;padding:30px 40px;text-align:center;">
              <h1 style="color:#ffffff;margin:0;font-size:24px;">ENDOSESMT</h1>
              <p style="color:#00AEEF;margin:5px 0 0;font-size:14px;">Sa\xFAde e Seguran\xE7a do Trabalho</p>
            </td>
          </tr>
          <!-- Body -->
          <tr>
            <td style="padding:40px;">
              <h2 style="color:#051328;margin:0 0 20px;font-size:22px;">Ol\xE1, ${firstName}!</h2>
              <p style="color:#333;font-size:16px;line-height:1.6;margin:0 0 15px;">
                Obrigado por demonstrar interesse em resolver os conflitos entre a <strong>RFB</strong> e o <strong>E-Social (SST)</strong>. Sua preocupa\xE7\xE3o com a conformidade da sua empresa \xE9 o primeiro passo para evitar multas significativas.
              </p>
              <p style="color:#333;font-size:16px;line-height:1.6;margin:0 0 15px;">
                Voc\xEA sabia que o <strong>Par\xE2metro 50.006</strong> da Receita Federal cruza automaticamente os dados do E-Social com as informa\xE7\xF5es tribut\xE1rias? Quando h\xE1 inconsist\xEAncias, as multas podem variar de <strong style="color:#dc2626;">R$ 1.812,87 a R$ 181.284,63</strong> por evento incorreto.
              </p>
              <p style="color:#333;font-size:16px;line-height:1.6;margin:0 0 25px;">
                Nos pr\xF3ximos dias, vamos enviar informa\xE7\xF5es valiosas sobre como identificar e corrigir essas inconsist\xEAncias na sua empresa.
              </p>
              <!-- CTA Button -->
              <table width="100%" cellpadding="0" cellspacing="0">
                <tr>
                  <td align="center">
                    <a href="${whatsappUrl}" style="display:inline-block;background-color:#25D366;color:#ffffff;text-decoration:none;padding:14px 30px;border-radius:6px;font-size:16px;font-weight:bold;">
                      Fale com um Especialista via WhatsApp
                    </a>
                  </td>
                </tr>
              </table>
            </td>
          </tr>
          ${getLgpdFooter(unsubscribeToken, leadId, 1)}
        </table>
      </td>
    </tr>
  </table>
</body>
</html>`
  };
}
function getFollowUpEmail1(name, unsubscribeToken, leadId) {
  const firstName = name.split(" ")[0];
  const auditUrl = trackLink("https://wa.me/5519991488900?text=Ol%C3%A1%2C%20gostaria%20de%20agendar%20uma%20auditoria%20gratuita%20dos%20dados%20E-Social%20da%20minha%20empresa.", leadId, 2);
  return {
    subject: `${firstName}, entenda o Par\xE2metro 50.006 e proteja sua empresa`,
    html: `
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin:0;padding:0;background-color:#f4f6f9;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background-color:#f4f6f9;padding:20px 0;">
    <tr>
      <td align="center">
        <table width="600" cellpadding="0" cellspacing="0" style="background-color:#ffffff;border-radius:8px;overflow:hidden;">
          <!-- Header -->
          <tr>
            <td style="background-color:#051328;padding:30px 40px;text-align:center;">
              <h1 style="color:#ffffff;margin:0;font-size:24px;">ENDOSESMT</h1>
              <p style="color:#00AEEF;margin:5px 0 0;font-size:14px;">Sa\xFAde e Seguran\xE7a do Trabalho</p>
            </td>
          </tr>
          <!-- Body -->
          <tr>
            <td style="padding:40px;">
              <h2 style="color:#051328;margin:0 0 20px;font-size:22px;">${firstName}, sua empresa est\xE1 segura?</h2>
              <p style="color:#333;font-size:16px;line-height:1.6;margin:0 0 15px;">
                No e-mail anterior, falamos sobre os riscos do conflito entre RFB e E-Social. Hoje, queremos aprofundar o tema para que voc\xEA entenda exatamente como o <strong>Par\xE2metro 50.006</strong> funciona.
              </p>
              
              <!-- Info Box -->
              <table width="100%" cellpadding="0" cellspacing="0" style="margin:20px 0;">
                <tr>
                  <td style="background-color:#eff6ff;border-left:4px solid #1E58A6;padding:20px;border-radius:0 6px 6px 0;">
                    <h3 style="color:#1E58A6;margin:0 0 10px;font-size:18px;">Como funciona o cruzamento de dados?</h3>
                    <p style="color:#333;font-size:14px;line-height:1.6;margin:0;">
                      A RFB utiliza o Par\xE2metro 50.006 para cruzar os dados do <strong>Evento S-2240</strong> (condi\xE7\xF5es ambientais de trabalho) com as informa\xE7\xF5es do <strong>LTCAT</strong>, <strong>PGR</strong> e os recolhimentos de <strong>FAP/RAT</strong>. Quando h\xE1 diverg\xEAncias, a fiscaliza\xE7\xE3o \xE9 autom\xE1tica.
                    </p>
                  </td>
                </tr>
              </table>

              <h3 style="color:#051328;margin:20px 0 15px;font-size:18px;">Os 3 erros mais comuns:</h3>
              <table width="100%" cellpadding="0" cellspacing="0">
                <tr>
                  <td style="padding:10px 0;border-bottom:1px solid #e5e7eb;">
                    <strong style="color:#dc2626;">1.</strong> <span style="color:#333;font-size:15px;">Informar agentes nocivos no S-2240 sem correspond\xEAncia no LTCAT</span>
                  </td>
                </tr>
                <tr>
                  <td style="padding:10px 0;border-bottom:1px solid #e5e7eb;">
                    <strong style="color:#dc2626;">2.</strong> <span style="color:#333;font-size:15px;">N\xE3o atualizar o PGR ap\xF3s mudan\xE7as no ambiente de trabalho</span>
                  </td>
                </tr>
                <tr>
                  <td style="padding:10px 0;">
                    <strong style="color:#dc2626;">3.</strong> <span style="color:#333;font-size:15px;">Recolher FAP/RAT com al\xEDquotas incorretas baseadas em dados desatualizados</span>
                  </td>
                </tr>
              </table>

              <p style="color:#333;font-size:16px;line-height:1.6;margin:25px 0 25px;">
                A boa not\xEDcia \xE9 que esses problemas podem ser identificados e corrigidos antes da fiscaliza\xE7\xE3o. A <strong>EndoSESMT</strong> oferece uma auditoria completa e gratuita para verificar a conformidade da sua empresa.
              </p>

              <!-- CTA Button -->
              <table width="100%" cellpadding="0" cellspacing="0">
                <tr>
                  <td align="center">
                    <a href="${auditUrl}" style="display:inline-block;background-color:#1E58A6;color:#ffffff;text-decoration:none;padding:14px 30px;border-radius:6px;font-size:16px;font-weight:bold;">
                      Agendar Auditoria Gratuita
                    </a>
                  </td>
                </tr>
              </table>
            </td>
          </tr>
          ${getLgpdFooter(unsubscribeToken, leadId, 2)}
        </table>
      </td>
    </tr>
  </table>
</body>
</html>`
  };
}
function getFollowUpEmail2(name, unsubscribeToken, leadId) {
  const firstName = name.split(" ")[0];
  const whatsappUrl = trackLink("https://wa.me/5519991488900?text=Ol%C3%A1%2C%20quero%20agendar%20minha%20auditoria%20gratuita%20E-Social%20SST%20agora!", leadId, 3);
  const phoneUrl = trackLink("tel:+5519991488900", leadId, 3);
  return {
    subject: `\xDAltima chance, ${firstName}! Garanta a conformidade da sua empresa`,
    html: `
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin:0;padding:0;background-color:#f4f6f9;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background-color:#f4f6f9;padding:20px 0;">
    <tr>
      <td align="center">
        <table width="600" cellpadding="0" cellspacing="0" style="background-color:#ffffff;border-radius:8px;overflow:hidden;">
          <!-- Header -->
          <tr>
            <td style="background-color:#051328;padding:30px 40px;text-align:center;">
              <h1 style="color:#ffffff;margin:0;font-size:24px;">ENDOSESMT</h1>
              <p style="color:#00AEEF;margin:5px 0 0;font-size:14px;">Sa\xFAde e Seguran\xE7a do Trabalho</p>
            </td>
          </tr>
          <!-- Body -->
          <tr>
            <td style="padding:40px;">
              <h2 style="color:#051328;margin:0 0 20px;font-size:22px;">${firstName}, n\xE3o deixe para depois!</h2>
              <p style="color:#333;font-size:16px;line-height:1.6;margin:0 0 15px;">
                Este \xE9 nosso \xFAltimo e-mail da s\xE9rie sobre o conflito <strong>RFB x E-Social (SST)</strong>. Queremos refor\xE7ar a import\xE2ncia de agir agora, antes que a fiscaliza\xE7\xE3o chegue \xE0 sua empresa.
              </p>

              <!-- Urgency Box -->
              <table width="100%" cellpadding="0" cellspacing="0" style="margin:20px 0;">
                <tr>
                  <td style="background-color:#fef2f2;border-left:4px solid #dc2626;padding:20px;border-radius:0 6px 6px 0;">
                    <h3 style="color:#dc2626;margin:0 0 10px;font-size:18px;">Aten\xE7\xE3o: Fiscaliza\xE7\xE3o Automatizada</h3>
                    <p style="color:#333;font-size:14px;line-height:1.6;margin:0;">
                      A RFB est\xE1 intensificando o cruzamento de dados do E-Social. Empresas com inconsist\xEAncias no Evento S-2240 est\xE3o sendo notificadas automaticamente, com multas que podem chegar a <strong>R$ 181.284,63 por evento</strong>.
                    </p>
                  </td>
                </tr>
              </table>

              <h3 style="color:#051328;margin:20px 0 15px;font-size:18px;">O que a EndoSESMT faz por voc\xEA:</h3>
              <table width="100%" cellpadding="0" cellspacing="0">
                <tr>
                  <td style="padding:8px 0;">
                    <span style="color:#25D366;font-size:18px;">&#10004;</span>
                    <span style="color:#333;font-size:15px;margin-left:8px;">Auditoria completa dos dados de SST no E-Social</span>
                  </td>
                </tr>
                <tr>
                  <td style="padding:8px 0;">
                    <span style="color:#25D366;font-size:18px;">&#10004;</span>
                    <span style="color:#333;font-size:15px;margin-left:8px;">Corre\xE7\xE3o de inconsist\xEAncias no LTCAT, PGR e S-2240</span>
                  </td>
                </tr>
                <tr>
                  <td style="padding:8px 0;">
                    <span style="color:#25D366;font-size:18px;">&#10004;</span>
                    <span style="color:#333;font-size:15px;margin-left:8px;">Revis\xE3o das al\xEDquotas de FAP e RAT</span>
                  </td>
                </tr>
                <tr>
                  <td style="padding:8px 0;">
                    <span style="color:#25D366;font-size:18px;">&#10004;</span>
                    <span style="color:#333;font-size:15px;margin-left:8px;">Garantia de conformidade total com a legisla\xE7\xE3o vigente</span>
                  </td>
                </tr>
              </table>

              <p style="color:#333;font-size:16px;line-height:1.6;margin:25px 0;">
                N\xE3o espere a multa chegar. Entre em contato agora e agende sua <strong>auditoria gratuita</strong>. Nossa equipe de especialistas est\xE1 pronta para ajudar.
              </p>

              <!-- CTA Buttons -->
              <table width="100%" cellpadding="0" cellspacing="0">
                <tr>
                  <td align="center" style="padding:5px 0;">
                    <a href="${whatsappUrl}" style="display:inline-block;background-color:#25D366;color:#ffffff;text-decoration:none;padding:14px 30px;border-radius:6px;font-size:16px;font-weight:bold;">
                      Agendar Auditoria Gratuita pelo WhatsApp
                    </a>
                  </td>
                </tr>
                <tr>
                  <td align="center" style="padding:10px 0;">
                    <a href="${phoneUrl}" style="display:inline-block;background-color:#1E58A6;color:#ffffff;text-decoration:none;padding:14px 30px;border-radius:6px;font-size:16px;font-weight:bold;">
                      Ligar Agora: (19) 99148-8900
                    </a>
                  </td>
                </tr>
              </table>
            </td>
          </tr>
          ${getLgpdFooter(unsubscribeToken, leadId, 3)}
        </table>
      </td>
    </tr>
  </table>
</body>
</html>`
  };
}
function getEmailForStep(step, name, unsubscribeToken, leadId) {
  switch (step) {
    case 1:
      return getWelcomeEmail(name, unsubscribeToken, leadId);
    case 2:
      return getFollowUpEmail1(name, unsubscribeToken, leadId);
    case 3:
      return getFollowUpEmail2(name, unsubscribeToken, leadId);
  }
}
async function sendWelcomeSequenceEmail(to, name, step, unsubscribeToken = "", leadId = 0) {
  try {
    const { subject, html } = getEmailForStep(step, name, unsubscribeToken, leadId);
    const unsubscribeUrl = `${getBaseUrl()}/api/email/unsubscribe?token=${unsubscribeToken}`;
    const { data, error } = await resend.emails.send({
      from: FROM_EMAIL,
      to: [to],
      replyTo: REPLY_TO,
      subject,
      html,
      headers: {
        "List-Unsubscribe": `<${unsubscribeUrl}>`,
        "List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
      }
    });
    if (error) {
      console.error(`[EmailMarketing] Failed to send step ${step} to ${to}:`, error);
      return { success: false, error: error.message };
    }
    console.log(`[EmailMarketing] Step ${step} sent to ${to}, messageId: ${data?.id}`);
    return { success: true, messageId: data?.id };
  } catch (err) {
    const errorMsg = err instanceof Error ? err.message : "Unknown error";
    console.error(`[EmailMarketing] Exception sending step ${step} to ${to}:`, errorMsg);
    return { success: false, error: errorMsg };
  }
}
async function sendWelcomeEmail(to, name, unsubscribeToken = "", leadId = 0) {
  return sendWelcomeSequenceEmail(to, name, 1, unsubscribeToken, leadId);
}
async function sendNewLeadNotification(teamMemberEmail, teamMemberName, lead) {
  try {
    const firstName = teamMemberName.split(" ")[0];
    const leadCompany = lead.company || "N\xE3o informada";
    const leadPhone = lead.phone || "N\xE3o informado";
    const whatsappLink = lead.phone ? `https://wa.me/${lead.phone.replace(/\D/g, "")}?text=Ol%C3%A1%20${encodeURIComponent(lead.name)}%2C%20sou%20da%20EndoSESMT...` : `https://wa.me/5519991488900`;
    const { data, error } = await resend.emails.send({
      from: FROM_EMAIL,
      to: [teamMemberEmail],
      replyTo: REPLY_TO,
      subject: `Novo Lead E-Social: ${lead.name} - ${leadCompany}`,
      html: `
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin:0;padding:0;background-color:#f4f6f9;font-family:Arial,Helvetica,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background-color:#f4f6f9;padding:20px 0;">
    <tr>
      <td align="center">
        <table width="600" cellpadding="0" cellspacing="0" style="background-color:#ffffff;border-radius:8px;overflow:hidden;">
          <!-- Header -->
          <tr>
            <td style="background-color:#051328;padding:25px 40px;text-align:center;">
              <h1 style="color:#ffffff;margin:0;font-size:22px;">ENDOSESMT</h1>
              <p style="color:#00AEEF;margin:5px 0 0;font-size:13px;">Notifica\xE7\xE3o da Equipe de Vendas</p>
            </td>
          </tr>
          <!-- Alert Badge -->
          <tr>
            <td style="background-color:#fef3c7;padding:12px 40px;text-align:center;border-bottom:2px solid #f59e0b;">
              <p style="color:#92400e;font-size:14px;font-weight:bold;margin:0;">&#128276; NOVO LEAD CAPTURADO</p>
            </td>
          </tr>
          <!-- Body -->
          <tr>
            <td style="padding:30px 40px;">
              <p style="color:#333;font-size:16px;line-height:1.6;margin:0 0 20px;">
                Ol\xE1, <strong>${firstName}</strong>! Um novo lead foi capturado pelo formul\xE1rio E-Social/SST.
              </p>
              
              <!-- Lead Info Card -->
              <table width="100%" cellpadding="0" cellspacing="0" style="background-color:#f8fafc;border:1px solid #e5e7eb;border-radius:8px;margin:0 0 25px;">
                <tr>
                  <td style="padding:20px;">
                    <h3 style="color:#051328;margin:0 0 15px;font-size:18px;">Dados do Lead</h3>
                    <table width="100%" cellpadding="0" cellspacing="0">
                      <tr>
                        <td style="padding:6px 0;color:#6b7280;font-size:14px;width:100px;">Nome:</td>
                        <td style="padding:6px 0;color:#111;font-size:14px;font-weight:bold;">${lead.name}</td>
                      </tr>
                      <tr>
                        <td style="padding:6px 0;color:#6b7280;font-size:14px;">E-mail:</td>
                        <td style="padding:6px 0;"><a href="mailto:${lead.email}" style="color:#1E58A6;font-size:14px;">${lead.email}</a></td>
                      </tr>
                      <tr>
                        <td style="padding:6px 0;color:#6b7280;font-size:14px;">Empresa:</td>
                        <td style="padding:6px 0;color:#111;font-size:14px;">${leadCompany}</td>
                      </tr>
                      <tr>
                        <td style="padding:6px 0;color:#6b7280;font-size:14px;">Telefone:</td>
                        <td style="padding:6px 0;color:#111;font-size:14px;">${leadPhone}</td>
                      </tr>
                      <tr>
                        <td style="padding:6px 0;color:#6b7280;font-size:14px;">Origem:</td>
                        <td style="padding:6px 0;color:#111;font-size:14px;">Alerta E-Social/SST</td>
                      </tr>
                      <tr>
                        <td style="padding:6px 0;color:#6b7280;font-size:14px;">Data:</td>
                        <td style="padding:6px 0;color:#111;font-size:14px;">${(/* @__PURE__ */ new Date()).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" })}</td>
                      </tr>
                    </table>
                  </td>
                </tr>
              </table>

              <p style="color:#333;font-size:15px;line-height:1.6;margin:0 0 20px;">
                Este lead demonstrou interesse em resolver conflitos entre a RFB e o E-Social (SST). Recomendamos entrar em contato o mais breve poss\xEDvel para maximizar a taxa de convers\xE3o.
              </p>

              <!-- CTA Buttons -->
              <table width="100%" cellpadding="0" cellspacing="0">
                <tr>
                  <td align="center" style="padding:5px 0;">
                    <a href="${whatsappLink}" style="display:inline-block;background-color:#25D366;color:#ffffff;text-decoration:none;padding:12px 25px;border-radius:6px;font-size:15px;font-weight:bold;">
                      Contatar via WhatsApp
                    </a>
                  </td>
                </tr>
                <tr>
                  <td align="center" style="padding:8px 0;">
                    <a href="mailto:${lead.email}?subject=EndoSESMT%20-%20Conformidade%20E-Social%20SST&body=Ol%C3%A1%20${encodeURIComponent(lead.name)}%2C%0A%0A" style="display:inline-block;background-color:#1E58A6;color:#ffffff;text-decoration:none;padding:12px 25px;border-radius:6px;font-size:15px;font-weight:bold;">
                      Enviar E-mail ao Lead
                    </a>
                  </td>
                </tr>
                <tr>
                  <td align="center" style="padding:8px 0;">
                    <a href="${getBaseUrl()}/admin/leads" style="display:inline-block;background-color:#6b7280;color:#ffffff;text-decoration:none;padding:12px 25px;border-radius:6px;font-size:15px;font-weight:bold;">
                      Ver Painel de Leads
                    </a>
                  </td>
                </tr>
              </table>
            </td>
          </tr>
          <!-- Footer -->
          <tr>
            <td style="background-color:#f8fafc;padding:20px 40px;border-top:1px solid #e5e7eb;">
              <p style="color:#9ca3af;font-size:11px;line-height:1.4;margin:0;text-align:center;">
                Esta \xE9 uma notifica\xE7\xE3o autom\xE1tica do sistema de captura de leads da EndoSESMT.<br>
                Voc\xEA recebe este e-mail porque est\xE1 cadastrado como membro da equipe de vendas.<br>
                Para alterar suas prefer\xEAncias de notifica\xE7\xE3o, acesse o painel administrativo.
              </p>
            </td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
</body>
</html>`
    });
    if (error) {
      console.error(`[SalesNotification] Failed to notify ${teamMemberEmail}:`, error);
      return { success: false, error: error.message };
    }
    console.log(`[SalesNotification] Notified ${teamMemberEmail} about lead ${lead.name}`);
    return { success: true };
  } catch (err) {
    const errorMsg = err instanceof Error ? err.message : "Unknown error";
    console.error(`[SalesNotification] Exception notifying ${teamMemberEmail}:`, errorMsg);
    return { success: false, error: errorMsg };
  }
}
async function notifySalesTeamAboutNewLead(lead) {
  const { getSalesTeamMembersForLeadNotification: getSalesTeamMembersForLeadNotification2 } = await Promise.resolve().then(() => (init_db(), db_exports));
  const members = await getSalesTeamMembersForLeadNotification2();
  if (members.length === 0) {
    console.log("[SalesNotification] No active team members to notify");
    return { notified: 0, failed: 0 };
  }
  let notified = 0;
  let failed = 0;
  for (const member of members) {
    const result = await sendNewLeadNotification(member.email, member.name, lead);
    if (result.success) {
      notified++;
    } else {
      failed++;
    }
  }
  console.log(`[SalesNotification] Notified ${notified}/${members.length} team members about lead ${lead.name}`);
  return { notified, failed };
}

// server/emailQueueProcessor.ts
init_db();
var isProcessing = false;
var intervalId = null;
async function processQueue() {
  if (isProcessing) return;
  isProcessing = true;
  try {
    const pendingEmails = await getPendingEmails();
    if (pendingEmails.length === 0) {
      isProcessing = false;
      return;
    }
    console.log(`[EmailQueue] Processing ${pendingEmails.length} pending email(s)...`);
    for (const email of pendingEmails) {
      try {
        const lead = await getLeadById(email.leadId);
        if (lead?.isUnsubscribed) {
          await markEmailFailed(email.id, "Lead unsubscribed", 99);
          console.log(`[EmailQueue] Skipped email ${email.id} - lead ${email.leadId} unsubscribed`);
          continue;
        }
        const unsubscribeToken = lead?.unsubscribeToken || "";
        const result = await sendWelcomeSequenceEmail(
          email.toEmail,
          email.toName,
          email.sequenceStep,
          unsubscribeToken,
          email.leadId
        );
        if (result.success && result.messageId) {
          await markEmailSent(email.id, result.messageId);
          console.log(
            `[EmailQueue] Sent step ${email.sequenceStep} to ${email.toEmail} (id: ${email.id})`
          );
        } else {
          await markEmailFailed(email.id, result.error || "Unknown error", email.attempts + 1);
          console.warn(
            `[EmailQueue] Failed step ${email.sequenceStep} to ${email.toEmail}: ${result.error}`
          );
        }
        await new Promise((resolve) => setTimeout(resolve, 1e3));
      } catch (err) {
        const errorMsg = err instanceof Error ? err.message : "Unknown error";
        await markEmailFailed(email.id, errorMsg, email.attempts + 1);
        console.error(`[EmailQueue] Exception processing email ${email.id}:`, errorMsg);
      }
    }
  } catch (err) {
    console.error("[EmailQueue] Error fetching pending emails:", err);
  } finally {
    isProcessing = false;
  }
}
async function scheduleWelcomeSequence(leadId, email, name, unsubscribeToken = "") {
  const now = /* @__PURE__ */ new Date();
  const scheduledAt2 = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1e3);
  await scheduleEmail({
    leadId,
    toEmail: email,
    toName: name,
    sequenceStep: 2,
    scheduledAt: scheduledAt2
  });
  const scheduledAt3 = new Date(now.getTime() + 5 * 24 * 60 * 60 * 1e3);
  await scheduleEmail({
    leadId,
    toEmail: email,
    toName: name,
    sequenceStep: 3,
    scheduledAt: scheduledAt3
  });
  console.log(
    `[EmailQueue] Scheduled welcome sequence for ${email}: step 2 at ${scheduledAt2.toISOString()}, step 3 at ${scheduledAt3.toISOString()}`
  );
}
function startEmailQueueProcessor() {
  if (intervalId) {
    console.warn("[EmailQueue] Processor already running");
    return;
  }
  console.log("[EmailQueue] Starting email queue processor (interval: 5 min)");
  processQueue();
  intervalId = setInterval(processQueue, 5 * 60 * 1e3);
}

// server/routers.ts
var appRouter = router({
  system: systemRouter,
  auth: router({
    me: publicProcedure.query((opts) => opts.ctx.user),
    logout: publicProcedure.mutation(({ ctx }) => {
      const cookieOptions = getSessionCookieOptions(ctx.req);
      ctx.res.clearCookie(COOKIE_NAME, { ...cookieOptions, maxAge: -1 });
      return {
        success: true
      };
    })
  }),
  // Services endpoints
  services: router({
    list: publicProcedure.query(async () => {
      return getAllServices();
    }),
    getBySlug: publicProcedure.input(z2.object({ slug: z2.string() })).query(async ({ input }) => {
      return getServiceBySlug(input.slug);
    })
  }),
  // NR Trainings endpoints
  nrTrainings: router({
    list: publicProcedure.query(async () => {
      return getAllNrTrainings();
    }),
    getByNumber: publicProcedure.input(z2.object({ nrNumber: z2.string() })).query(async ({ input }) => {
      return getNrTrainingByNumber(input.nrNumber);
    })
  }),
  // Blog endpoints
  blog: router({
    list: publicProcedure.input(z2.object({ limit: z2.number().optional() }).optional()).query(async ({ input }) => {
      return getPublishedBlogPosts(input?.limit);
    }),
    getBySlug: publicProcedure.input(z2.object({ slug: z2.string() })).query(async ({ input }) => {
      const post = await getBlogPostBySlug(input.slug);
      if (post) {
        await incrementBlogPostViewCount(post.id);
      }
      return post;
    })
  }),
  // E-Social leads endpoint
  esocialLeads: router({
    submit: publicProcedure.input(z2.object({
      name: z2.string().min(2, "Nome deve ter pelo menos 2 caracteres"),
      email: z2.string().email("Email inv\xE1lido"),
      company: z2.string().optional(),
      phone: z2.string().optional()
    })).mutation(async ({ input }) => {
      const unsubscribeToken = await createEsocialLead({
        ...input,
        source: "esocial_alert"
      });
      const lead = await getLeadByEmail(input.email);
      const leadId = lead?.id || 0;
      const emailResult = await sendWelcomeEmail(input.email, input.name, unsubscribeToken, leadId);
      console.log(`[ESocialLeads] Welcome email to ${input.email}: ${emailResult.success ? "sent" : "failed"}`);
      if (leadId > 0) {
        await scheduleWelcomeSequence(leadId, input.email, input.name, unsubscribeToken);
      }
      await notifyOwner({
        title: "Novo Lead E-Social SST - EndoSESMT",
        content: `Um novo lead foi capturado pelo alerta E-Social:

Nome: ${input.name}
Email: ${input.email}
Empresa: ${input.company || "N\xE3o informada"}
Telefone: ${input.phone || "N\xE3o informado"}
E-mail de boas-vindas: ${emailResult.success ? "Enviado com sucesso" : "Falhou - " + emailResult.error}

Este lead demonstrou interesse em resolver conflitos RFB x E-Social (SST). Entre em contato o mais breve poss\xEDvel.`
      });
      const salesNotification = await notifySalesTeamAboutNewLead(input);
      console.log(`[ESocialLeads] Sales team notified: ${salesNotification.notified} success, ${salesNotification.failed} failed`);
      return { success: true, message: "Dados enviados com sucesso! Entraremos em contato em breve." };
    }),
    // Admin only - list all leads
    listAll: protectedProcedure.query(async ({ ctx }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      return getAllEsocialLeads();
    }),
    // Admin only - mark as contacted
    markContacted: protectedProcedure.input(z2.object({ id: z2.number() })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      await markLeadAsContacted(input.id);
      return { success: true };
    }),
    // Admin only - update lead segment
    updateSegment: protectedProcedure.input(z2.object({ id: z2.number(), segment: z2.enum(["cold", "warm", "hot", "converted"]) })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      await updateLeadSegment(input.id, input.segment);
      return { success: true };
    }),
    // Admin only - update lead notes
    updateNotes: protectedProcedure.input(z2.object({ id: z2.number(), notes: z2.string() })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      await updateLeadNotes(input.id, input.notes);
      return { success: true };
    }),
    // Admin only - get lead tracking events
    getTrackingEvents: protectedProcedure.input(z2.object({ leadId: z2.number() })).query(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      return getLeadTrackingEvents(input.leadId);
    }),
    // Admin only - get email queue stats
    getEmailStats: protectedProcedure.query(async ({ ctx }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      return getEmailQueueStats();
    })
  }),
  // Sales team management
  salesTeam: router({
    // Admin only - list all members
    listAll: protectedProcedure.query(async ({ ctx }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      return getAllSalesTeamMembers();
    }),
    // Admin only - add new member
    add: protectedProcedure.input(z2.object({
      name: z2.string().min(2, "Nome deve ter pelo menos 2 caracteres"),
      email: z2.string().email("Email inv\xE1lido"),
      role: z2.string().optional(),
      receiveLeadNotifications: z2.boolean().optional(),
      receiveWeeklyReport: z2.boolean().optional()
    })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      await createSalesTeamMember({
        name: input.name,
        email: input.email,
        role: input.role || "vendedor",
        receiveLeadNotifications: input.receiveLeadNotifications ?? true,
        receiveWeeklyReport: input.receiveWeeklyReport ?? false
      });
      return { success: true };
    }),
    // Admin only - update member
    update: protectedProcedure.input(z2.object({
      id: z2.number(),
      name: z2.string().optional(),
      email: z2.string().email().optional(),
      role: z2.string().optional(),
      isActive: z2.boolean().optional(),
      receiveLeadNotifications: z2.boolean().optional(),
      receiveWeeklyReport: z2.boolean().optional()
    })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      const { id, ...data } = input;
      await updateSalesTeamMember(id, data);
      return { success: true };
    }),
    // Admin only - delete member
    remove: protectedProcedure.input(z2.object({ id: z2.number() })).mutation(async ({ ctx, input }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      await deleteSalesTeamMember(input.id);
      return { success: true };
    })
  }),
  // Contact form endpoint
  contact: router({
    submit: publicProcedure.input(z2.object({
      name: z2.string().min(2, "Nome deve ter pelo menos 2 caracteres"),
      email: z2.string().email("Email inv\xE1lido"),
      phone: z2.string().optional(),
      company: z2.string().optional(),
      subject: z2.string().optional(),
      message: z2.string().min(10, "Mensagem deve ter pelo menos 10 caracteres"),
      serviceInterest: z2.string().optional()
    })).mutation(async ({ input }) => {
      await createContactMessage(input);
      await notifyOwner({
        title: "Nova mensagem de contato - EndoSESMT",
        content: `Nome: ${input.name}
Email: ${input.email}
Telefone: ${input.phone || "N\xE3o informado"}
Empresa: ${input.company || "N\xE3o informada"}
Assunto: ${input.subject || "N\xE3o informado"}

Mensagem:
${input.message}`
      });
      return { success: true, message: "Mensagem enviada com sucesso!" };
    }),
    // Admin only - list all messages
    listAll: protectedProcedure.query(async ({ ctx }) => {
      if (ctx.user.role !== "admin") {
        throw new Error("Acesso n\xE3o autorizado");
      }
      return getAllContactMessages();
    })
  })
});

// server/_core/context.ts
async function createContext(opts) {
  let user = null;
  try {
    user = await sdk.authenticateRequest(opts.req);
  } catch (error) {
    user = null;
  }
  return {
    req: opts.req,
    res: opts.res,
    user
  };
}

// server/_core/vite.ts
import express from "express";
import fs from "fs";
import { nanoid } from "nanoid";
import path2 from "path";
import { createServer as createViteServer } from "vite";

// vite.config.ts
import { jsxLocPlugin } from "@builder.io/vite-plugin-jsx-loc";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import path from "path";
import { defineConfig } from "vite";
import { vitePluginManusRuntime } from "vite-plugin-manus-runtime";
var plugins = [react(), tailwindcss(), jsxLocPlugin(), vitePluginManusRuntime()];
var vite_config_default = defineConfig({
  plugins,
  resolve: {
    alias: {
      "@": path.resolve(import.meta.dirname, "client", "src"),
      "@shared": path.resolve(import.meta.dirname, "shared"),
      "@assets": path.resolve(import.meta.dirname, "attached_assets")
    }
  },
  envDir: path.resolve(import.meta.dirname),
  root: path.resolve(import.meta.dirname, "client"),
  publicDir: path.resolve(import.meta.dirname, "client", "public"),
  build: {
    outDir: path.resolve(import.meta.dirname, "dist/public"),
    emptyOutDir: true
  },
  server: {
    host: true,
    allowedHosts: [
      ".manuspre.computer",
      ".manus.computer",
      ".manus-asia.computer",
      ".manuscomputer.ai",
      ".manusvm.computer",
      "localhost",
      "127.0.0.1"
    ],
    fs: {
      strict: true,
      deny: ["**/.*"]
    }
  }
});

// server/_core/vite.ts
async function setupVite(app, server) {
  const serverOptions = {
    middlewareMode: true,
    hmr: { server },
    allowedHosts: true
  };
  const vite = await createViteServer({
    ...vite_config_default,
    configFile: false,
    server: serverOptions,
    appType: "custom"
  });
  app.use(vite.middlewares);
  app.use("*", async (req, res, next) => {
    const url = req.originalUrl;
    try {
      const clientTemplate = path2.resolve(
        import.meta.dirname,
        "../..",
        "client",
        "index.html"
      );
      let template = await fs.promises.readFile(clientTemplate, "utf-8");
      template = template.replace(
        `src="/src/main.tsx"`,
        `src="/src/main.tsx?v=${nanoid()}"`
      );
      const page = await vite.transformIndexHtml(url, template);
      res.status(200).set({ "Content-Type": "text/html" }).end(page);
    } catch (e) {
      vite.ssrFixStacktrace(e);
      next(e);
    }
  });
}
function serveStatic(app) {
  const distPath = process.env.NODE_ENV === "development" ? path2.resolve(import.meta.dirname, "../..", "dist", "public") : path2.resolve(import.meta.dirname, "public");
  if (!fs.existsSync(distPath)) {
    console.error(
      `Could not find the build directory: ${distPath}, make sure to build the client first`
    );
  }
  app.use(express.static(distPath));
  app.use("*", (_req, res) => {
    res.sendFile(path2.resolve(distPath, "index.html"));
  });
}

// server/routes/forms.ts
init_db();
init_schema();
import { Router } from "express";
import { eq as eq2, desc as desc2, and, gte, lte, like } from "drizzle-orm";
var router2 = Router();
router2.post("/", async (req, res) => {
  try {
    const {
      formType,
      formCode,
      formData,
      status = "submitted",
      submittedBy,
      submittedByEmail,
      submittedByCpf,
      signatures = [],
      photos = []
    } = req.body;
    if (!formType || !formCode || !formData || !submittedBy) {
      return res.status(400).json({
        error: "Missing required fields: formType, formCode, formData, submittedBy"
      });
    }
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const [form] = await db.insert(forms).values({
      formType,
      formCode,
      formData: typeof formData === "string" ? formData : JSON.stringify(formData),
      status,
      submittedBy,
      submittedByEmail,
      submittedByCpf
    });
    const formId = form.insertId;
    if (signatures && signatures.length > 0) {
      await db.insert(formSignatures).values(
        signatures.map((sig) => ({
          formId,
          signerName: sig.signerName,
          signerRole: sig.signerRole,
          signatureData: sig.signatureData,
          ipAddress: req.ip
        }))
      );
    }
    if (photos && photos.length > 0) {
      await db.insert(formPhotos).values(
        photos.map((photo) => ({
          formId,
          photoUrl: photo.photoUrl || "",
          photoData: photo.photoData,
          caption: photo.caption
        }))
      );
    }
    try {
      await notifyFormSubmission(formType, formCode, submittedBy, submittedByEmail);
      console.log(`[Forms] Notification sent for form ${formCode}`);
    } catch (notifyError) {
      console.warn(`[Forms] Failed to send notification for form ${formCode}:`, notifyError);
    }
    res.status(201).json({
      success: true,
      formId,
      message: "Form submitted successfully"
    });
  } catch (error) {
    console.error("Error creating form:", error);
    res.status(500).json({ error: "Failed to submit form" });
  }
});
router2.get("/", async (req, res) => {
  try {
    const {
      formType,
      status,
      startDate,
      endDate,
      submittedBy,
      limit = "50",
      offset = "0"
    } = req.query;
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const conditions = [];
    if (formType) {
      conditions.push(eq2(forms.formType, formType));
    }
    if (status) {
      conditions.push(eq2(forms.status, status));
    }
    if (startDate) {
      conditions.push(gte(forms.createdAt, new Date(startDate)));
    }
    if (endDate) {
      conditions.push(lte(forms.createdAt, new Date(endDate)));
    }
    if (submittedBy) {
      conditions.push(like(forms.submittedBy, `%${submittedBy}%`));
    }
    let query = db.select().from(forms);
    if (conditions.length > 0) {
      query = query.where(and(...conditions));
    }
    const results = await query.orderBy(desc2(forms.createdAt)).limit(parseInt(limit)).offset(parseInt(offset));
    res.json({
      success: true,
      forms: results,
      count: results.length
    });
  } catch (error) {
    console.error("Error fetching forms:", error);
    res.status(500).json({ error: "Failed to fetch forms" });
  }
});
router2.get("/:id", async (req, res) => {
  try {
    const formId = parseInt(req.params.id);
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const [form] = await db.select().from(forms).where(eq2(forms.id, formId));
    if (!form) {
      return res.status(404).json({ error: "Form not found" });
    }
    const signatures = await db.select().from(formSignatures).where(eq2(formSignatures.formId, formId));
    const photos = await db.select().from(formPhotos).where(eq2(formPhotos.formId, formId));
    res.json({
      success: true,
      form: {
        ...form,
        formData: JSON.parse(form.formData),
        signatures,
        photos
      }
    });
  } catch (error) {
    console.error("Error fetching form:", error);
    res.status(500).json({ error: "Failed to fetch form" });
  }
});
router2.put("/:id", async (req, res) => {
  try {
    const formId = parseInt(req.params.id);
    const { formData, status, reviewedBy, reviewNotes } = req.body;
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const updateData = {};
    if (formData) {
      updateData.formData = typeof formData === "string" ? formData : JSON.stringify(formData);
    }
    if (status) {
      updateData.status = status;
    }
    if (reviewedBy) {
      updateData.reviewedBy = reviewedBy;
      updateData.reviewedAt = /* @__PURE__ */ new Date();
    }
    if (reviewNotes) {
      updateData.reviewNotes = reviewNotes;
    }
    await db.update(forms).set(updateData).where(eq2(forms.id, formId));
    res.json({
      success: true,
      message: "Form updated successfully"
    });
  } catch (error) {
    console.error("Error updating form:", error);
    res.status(500).json({ error: "Failed to update form" });
  }
});
router2.delete("/:id", async (req, res) => {
  try {
    const formId = parseInt(req.params.id);
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    await db.delete(formSignatures).where(eq2(formSignatures.formId, formId));
    await db.delete(formPhotos).where(eq2(formPhotos.formId, formId));
    await db.delete(forms).where(eq2(forms.id, formId));
    res.json({
      success: true,
      message: "Form deleted successfully"
    });
  } catch (error) {
    console.error("Error deleting form:", error);
    res.status(500).json({ error: "Failed to delete form" });
  }
});
router2.get("/stats/summary", async (req, res) => {
  try {
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const allForms = await db.select().from(forms);
    const stats = {
      total: allForms.length,
      byType: {},
      byStatus: {},
      recent: allForms.slice(0, 10)
    };
    allForms.forEach((form) => {
      stats.byType[form.formType] = (stats.byType[form.formType] || 0) + 1;
      stats.byStatus[form.status] = (stats.byStatus[form.status] || 0) + 1;
    });
    res.json({
      success: true,
      stats
    });
  } catch (error) {
    console.error("Error fetching stats:", error);
    res.status(500).json({ error: "Failed to fetch statistics" });
  }
});
var forms_default = router2;

// server/routes/form-auth.ts
init_db();
init_schema();
import { Router as Router2 } from "express";
import { eq as eq3, and as and2 } from "drizzle-orm";
import jwt from "jsonwebtoken";
var router3 = Router2();
var FORM_JWT_SECRET = process.env.JWT_SECRET || "endosesmt-form-auth-secret-2024";
async function hashPassword(password) {
  const encoder = new TextEncoder();
  const data = encoder.encode(password + FORM_JWT_SECRET);
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}
async function verifyPassword(password, hash) {
  const passwordHash = await hashPassword(password);
  return passwordHash === hash;
}
function generateApiKey() {
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let key = "endo_";
  for (let i = 0; i < 32; i++) {
    key += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return key;
}
var FORM_CATEGORIES = {
  rh: [
    "training-request",
    "epi-request",
    "activity-report"
  ],
  sesmt: [
    "accident-investigation",
    "apr",
    "emergency-inspection",
    "work-permit",
    "fire-inspection",
    "safety-checklist",
    "non-conformity-report"
  ],
  admin: [
    "internal-communication",
    "service-order",
    "training-request",
    "epi-request",
    "activity-report",
    "accident-investigation",
    "apr",
    "emergency-inspection",
    "work-permit",
    "fire-inspection",
    "safety-checklist",
    "non-conformity-report"
  ]
};
async function verifyToken(req, res, next) {
  const token = req.cookies?.form_auth_token || req.headers.authorization?.replace("Bearer ", "");
  if (!token) {
    return res.status(401).json({ error: "N\xE3o autorizado" });
  }
  try {
    const payload = jwt.verify(token, FORM_JWT_SECRET);
    req.formUser = payload;
    next();
  } catch (error) {
    return res.status(401).json({ error: "Token inv\xE1lido ou expirado" });
  }
}
async function verifyAdmin(req, res, next) {
  const user = req.formUser;
  if (user?.userType !== "admin") {
    return res.status(403).json({ error: "Acesso negado. Apenas administradores." });
  }
  next();
}
router3.post("/login", async (req, res) => {
  try {
    const { username, password, companyId } = req.body;
    if (!username || !password) {
      return res.status(400).json({ error: "Usu\xE1rio e senha s\xE3o obrigat\xF3rios" });
    }
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const whereConditions = companyId ? and2(
      eq3(formUsers.username, username),
      eq3(formUsers.companyId, companyId),
      eq3(formUsers.isActive, true)
    ) : and2(
      eq3(formUsers.username, username),
      eq3(formUsers.isActive, true)
    );
    const users2 = await db.select().from(formUsers).where(whereConditions).limit(1);
    if (users2.length === 0) {
      return res.status(401).json({ error: "Usu\xE1rio ou senha inv\xE1lidos" });
    }
    const user = users2[0];
    const isValid = await verifyPassword(password, user.passwordHash);
    if (!isValid) {
      return res.status(401).json({ error: "Usu\xE1rio ou senha inv\xE1lidos" });
    }
    const companyResult = await db.select().from(companies).where(eq3(companies.id, user.companyId)).limit(1);
    const company = companyResult[0];
    await db.update(formUsers).set({ lastLogin: /* @__PURE__ */ new Date() }).where(eq3(formUsers.id, user.id));
    const token = jwt.sign(
      {
        userId: user.id,
        companyId: user.companyId,
        userType: user.userType,
        username: user.username,
        name: user.name
      },
      FORM_JWT_SECRET,
      { expiresIn: "24h" }
    );
    res.cookie("form_auth_token", token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "lax",
      maxAge: 60 * 60 * 24 * 1e3
      // 24 hours
    });
    const allowedForms = FORM_CATEGORIES[user.userType] || [];
    return res.json({
      success: true,
      user: {
        id: user.id,
        name: user.name,
        username: user.username,
        email: user.email,
        userType: user.userType,
        company: company ? {
          id: company.id,
          name: company.name,
          cnpj: company.cnpj
        } : null
      },
      allowedForms,
      token
    });
  } catch (error) {
    console.error("Login error:", error);
    return res.status(500).json({ error: "Erro ao fazer login" });
  }
});
router3.post("/logout", async (req, res) => {
  res.clearCookie("form_auth_token");
  return res.json({ success: true });
});
router3.get("/verify", async (req, res) => {
  try {
    const token = req.cookies?.form_auth_token || req.headers.authorization?.replace("Bearer ", "");
    if (!token) {
      return res.json({ authenticated: false });
    }
    const payload = jwt.verify(token, FORM_JWT_SECRET);
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const users2 = await db.select().from(formUsers).where(eq3(formUsers.id, payload.userId)).limit(1);
    if (users2.length === 0 || !users2[0].isActive) {
      return res.json({ authenticated: false });
    }
    const user = users2[0];
    const companyResult = await db.select().from(companies).where(eq3(companies.id, user.companyId)).limit(1);
    const company = companyResult[0];
    const allowedForms = FORM_CATEGORIES[user.userType] || [];
    return res.json({
      authenticated: true,
      user: {
        id: user.id,
        name: user.name,
        username: user.username,
        email: user.email,
        userType: user.userType,
        company: company ? {
          id: company.id,
          name: company.name,
          cnpj: company.cnpj
        } : null
      },
      allowedForms
    });
  } catch (error) {
    return res.json({ authenticated: false });
  }
});
router3.get("/company-by-email", async (req, res) => {
  try {
    const { email } = req.query;
    if (!email || typeof email !== "string") {
      return res.status(400).json({ error: "Email \xE9 obrigat\xF3rio" });
    }
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const result = await db.select({
      id: companies.id,
      name: companies.name,
      email: companies.email
    }).from(companies).where(
      and2(
        eq3(companies.email, email.toLowerCase().trim()),
        eq3(companies.isActive, true)
      )
    ).limit(1);
    if (result.length === 0) {
      return res.status(404).json({ error: "Empresa n\xE3o encontrada" });
    }
    return res.json(result[0]);
  } catch (error) {
    console.error("Error fetching company by email:", error);
    return res.status(500).json({ error: "Erro ao buscar empresa" });
  }
});
router3.get("/companies", async (req, res) => {
  try {
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const result = await db.select({
      id: companies.id,
      name: companies.name
    }).from(companies).where(eq3(companies.isActive, true));
    return res.json(result);
  } catch (error) {
    console.error("Error fetching companies:", error);
    return res.status(500).json({ error: "Erro ao buscar empresas" });
  }
});
router3.post("/admin/companies", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const { name, cnpj, email, phone, address } = req.body;
    if (!name) {
      return res.status(400).json({ error: "Nome da empresa \xE9 obrigat\xF3rio" });
    }
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const result = await db.insert(companies).values({
      name,
      cnpj,
      email,
      phone,
      address
    });
    return res.json({ success: true, id: result[0].insertId });
  } catch (error) {
    console.error("Error creating company:", error);
    return res.status(500).json({ error: "Erro ao criar empresa" });
  }
});
router3.get("/admin/companies", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const result = await db.select().from(companies);
    return res.json(result);
  } catch (error) {
    console.error("Error listing companies:", error);
    return res.status(500).json({ error: "Erro ao listar empresas" });
  }
});
router3.post("/admin/users", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const { companyId, username, password, name, email, userType } = req.body;
    if (!companyId || !username || !password || !name || !userType) {
      return res.status(400).json({ error: "Campos obrigat\xF3rios: companyId, username, password, name, userType" });
    }
    if (!["rh", "sesmt", "admin"].includes(userType)) {
      return res.status(400).json({ error: "Tipo de usu\xE1rio inv\xE1lido. Use: rh, sesmt ou admin" });
    }
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const existing = await db.select().from(formUsers).where(eq3(formUsers.username, username)).limit(1);
    if (existing.length > 0) {
      return res.status(400).json({ error: "Nome de usu\xE1rio j\xE1 existe" });
    }
    const passwordHash = await hashPassword(password);
    const result = await db.insert(formUsers).values({
      companyId,
      username,
      passwordHash,
      name,
      email,
      userType
    });
    return res.json({ success: true, id: result[0].insertId });
  } catch (error) {
    console.error("Error creating user:", error);
    return res.status(500).json({ error: "Erro ao criar usu\xE1rio" });
  }
});
router3.get("/admin/users", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const result = await db.select({
      id: formUsers.id,
      companyId: formUsers.companyId,
      username: formUsers.username,
      name: formUsers.name,
      email: formUsers.email,
      userType: formUsers.userType,
      isActive: formUsers.isActive,
      lastLogin: formUsers.lastLogin,
      createdAt: formUsers.createdAt
    }).from(formUsers);
    return res.json(result);
  } catch (error) {
    console.error("Error listing users:", error);
    return res.status(500).json({ error: "Erro ao listar usu\xE1rios" });
  }
});
router3.put("/admin/users/:id", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const id = parseInt(req.params.id);
    const { name, email, userType, isActive, password } = req.body;
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const updateData = {};
    if (name) updateData.name = name;
    if (email !== void 0) updateData.email = email;
    if (userType && ["rh", "sesmt", "admin"].includes(userType)) updateData.userType = userType;
    if (isActive !== void 0) updateData.isActive = isActive;
    if (password) updateData.passwordHash = await hashPassword(password);
    await db.update(formUsers).set(updateData).where(eq3(formUsers.id, id));
    return res.json({ success: true });
  } catch (error) {
    console.error("Error updating user:", error);
    return res.status(500).json({ error: "Erro ao atualizar usu\xE1rio" });
  }
});
router3.delete("/admin/users/:id", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const id = parseInt(req.params.id);
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    await db.delete(formUsers).where(eq3(formUsers.id, id));
    return res.json({ success: true });
  } catch (error) {
    console.error("Error deleting user:", error);
    return res.status(500).json({ error: "Erro ao excluir usu\xE1rio" });
  }
});
router3.post("/admin/api-keys", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const { companyId, name, permissions, expiresInDays } = req.body;
    if (!companyId || !name) {
      return res.status(400).json({ error: "companyId e name s\xE3o obrigat\xF3rios" });
    }
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const apiKey = generateApiKey();
    const keyHash = await hashPassword(apiKey);
    const keyPrefix = apiKey.substring(0, 13);
    const expiresAt = expiresInDays ? new Date(Date.now() + expiresInDays * 24 * 60 * 60 * 1e3) : null;
    await db.insert(apiKeys).values({
      companyId,
      keyHash,
      keyPrefix,
      name,
      permissions: permissions ? JSON.stringify(permissions) : null,
      expiresAt
    });
    return res.json({
      success: true,
      apiKey,
      message: "Guarde esta chave com seguran\xE7a. Ela n\xE3o poder\xE1 ser visualizada novamente."
    });
  } catch (error) {
    console.error("Error generating API key:", error);
    return res.status(500).json({ error: "Erro ao gerar chave de API" });
  }
});
router3.get("/admin/api-keys", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const result = await db.select({
      id: apiKeys.id,
      companyId: apiKeys.companyId,
      keyPrefix: apiKeys.keyPrefix,
      name: apiKeys.name,
      permissions: apiKeys.permissions,
      isActive: apiKeys.isActive,
      lastUsed: apiKeys.lastUsed,
      expiresAt: apiKeys.expiresAt,
      createdAt: apiKeys.createdAt
    }).from(apiKeys);
    return res.json(result);
  } catch (error) {
    console.error("Error listing API keys:", error);
    return res.status(500).json({ error: "Erro ao listar chaves de API" });
  }
});
router3.delete("/admin/api-keys/:id", verifyToken, verifyAdmin, async (req, res) => {
  try {
    const id = parseInt(req.params.id);
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    await db.update(apiKeys).set({ isActive: false }).where(eq3(apiKeys.id, id));
    return res.json({ success: true });
  } catch (error) {
    console.error("Error revoking API key:", error);
    return res.status(500).json({ error: "Erro ao revogar chave de API" });
  }
});
router3.post("/seed", async (req, res) => {
  try {
    const db = await getDb();
    if (!db) {
      return res.status(500).json({ error: "Database not available" });
    }
    const existingCompanies = await db.select().from(companies).limit(1);
    if (existingCompanies.length > 0) {
      return res.json({ message: "Dados j\xE1 foram inicializados" });
    }
    const companyResult = await db.insert(companies).values({
      name: "EndoSESMT",
      cnpj: "00.000.000/0001-00",
      email: "contato@endosesmt.com.br",
      phone: "(19) 99148-8900"
    });
    const companyId = companyResult[0].insertId;
    const adminPasswordHash = await hashPassword("admin123");
    await db.insert(formUsers).values({
      companyId,
      username: "admin",
      passwordHash: adminPasswordHash,
      name: "Administrador",
      email: "admin@endosesmt.com.br",
      userType: "admin"
    });
    const rhPasswordHash = await hashPassword("rh123");
    await db.insert(formUsers).values({
      companyId,
      username: "rh",
      passwordHash: rhPasswordHash,
      name: "Usu\xE1rio RH",
      email: "rh@endosesmt.com.br",
      userType: "rh"
    });
    const sesmtPasswordHash = await hashPassword("sesmt123");
    await db.insert(formUsers).values({
      companyId,
      username: "sesmt",
      passwordHash: sesmtPasswordHash,
      name: "Usu\xE1rio SESMT",
      email: "sesmt@endosesmt.com.br",
      userType: "sesmt"
    });
    return res.json({
      success: true,
      message: "Dados iniciais criados com sucesso",
      credentials: {
        admin: { username: "admin", password: "admin123" },
        rh: { username: "rh", password: "rh123" },
        sesmt: { username: "sesmt", password: "sesmt123" }
      }
    });
  } catch (error) {
    console.error("Error seeding data:", error);
    return res.status(500).json({ error: "Erro ao criar dados iniciais" });
  }
});
var form_auth_default = router3;

// server/routes/pdf-generator.ts
import { Router as Router3 } from "express";
import puppeteer from "puppeteer";
var router4 = Router3();
var printStyles = `
  <style>
    /* Force print colors and backgrounds */
    * {
      -webkit-print-color-adjust: exact !important;
      print-color-adjust: exact !important;
      color-adjust: exact !important;
    }
    
    /* Reset body */
    body {
      margin: 0;
      padding: 20px;
      font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background: white !important;
      color: #1a1a1a !important;
    }
    
    /* Hide elements that shouldn't appear in PDF */
    .no-print,
    nav,
    header:not(.form-header),
    footer:not(.form-footer),
    .sidebar,
    button,
    .action-buttons,
    [data-no-print="true"] {
      display: none !important;
    }
    
    /* Form container */
    .form-container,
    .pdf-content {
      max-width: 100%;
      margin: 0 auto;
      padding: 0;
    }
    
    /* Images */
    img {
      max-width: 100% !important;
      height: auto !important;
      page-break-inside: avoid;
      break-inside: avoid;
    }
    
    /* Image grid for photos */
    .photo-grid,
    .image-grid {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 10px;
      page-break-inside: avoid;
    }
    
    .photo-item,
    .image-item {
      page-break-inside: avoid;
      break-inside: avoid;
    }
    
    .photo-item img,
    .image-item img {
      width: 100%;
      height: auto;
      object-fit: contain;
      border: 1px solid #e5e5e5;
      border-radius: 4px;
    }
    
    /* Tables */
    table {
      width: 100%;
      border-collapse: collapse;
      page-break-inside: auto;
    }
    
    tr {
      page-break-inside: avoid;
      page-break-after: auto;
    }
    
    th, td {
      border: 1px solid #e5e5e5;
      padding: 8px;
      text-align: left;
    }
    
    th {
      background-color: #f5f5f5 !important;
      font-weight: 600;
    }
    
    /* Sections */
    section,
    .form-section {
      page-break-inside: avoid;
      margin-bottom: 20px;
    }
    
    /* Headers */
    h1, h2, h3, h4, h5, h6 {
      page-break-after: avoid;
      color: #1a1a1a !important;
    }
    
    h1 { font-size: 24px; margin-bottom: 10px; }
    h2 { font-size: 20px; margin-bottom: 8px; }
    h3 { font-size: 16px; margin-bottom: 6px; }
    
    /* Form fields display */
    .field-group {
      margin-bottom: 12px;
      page-break-inside: avoid;
    }
    
    .field-label {
      font-weight: 600;
      font-size: 12px;
      color: #666 !important;
      margin-bottom: 4px;
    }
    
    .field-value {
      font-size: 14px;
      color: #1a1a1a !important;
      padding: 8px;
      background: #f9f9f9 !important;
      border: 1px solid #e5e5e5;
      border-radius: 4px;
      min-height: 20px;
    }
    
    /* Checkboxes display */
    .checkbox-item {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-bottom: 4px;
    }
    
    .checkbox-checked {
      color: #16a34a !important;
    }
    
    .checkbox-unchecked {
      color: #dc2626 !important;
    }
    
    /* Signature */
    .signature-area {
      border: 1px solid #e5e5e5;
      border-radius: 4px;
      padding: 10px;
      min-height: 100px;
      page-break-inside: avoid;
    }
    
    .signature-area img {
      max-height: 100px;
      width: auto;
    }
    
    /* Status badges */
    .status-badge {
      display: inline-block;
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 12px;
      font-weight: 500;
    }
    
    .status-conforme {
      background: #dcfce7 !important;
      color: #16a34a !important;
    }
    
    .status-nao-conforme {
      background: #fee2e2 !important;
      color: #dc2626 !important;
    }
    
    .status-na {
      background: #f3f4f6 !important;
      color: #6b7280 !important;
    }
    
    /* Logo and header */
    .pdf-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
      padding-bottom: 15px;
      border-bottom: 2px solid #16a34a;
    }
    
    .pdf-logo {
      height: 50px;
      width: auto;
    }
    
    .pdf-title {
      text-align: center;
      flex: 1;
    }
    
    .pdf-title h1 {
      margin: 0;
      font-size: 22px;
      color: #1a1a1a !important;
    }
    
    .pdf-title p {
      margin: 5px 0 0;
      font-size: 12px;
      color: #666 !important;
    }
    
    /* Footer */
    .pdf-footer {
      margin-top: 30px;
      padding-top: 15px;
      border-top: 1px solid #e5e5e5;
      font-size: 10px;
      color: #666 !important;
      text-align: center;
    }
    
    /* Grid layouts */
    .grid-2 {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 15px;
    }
    
    .grid-3 {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 15px;
    }
    
    /* Page breaks */
    .page-break {
      page-break-before: always;
    }
    
    .avoid-break {
      page-break-inside: avoid;
    }
  </style>
`;
router4.post("/generate", async (req, res) => {
  const { html, filename = "documento", format = "A4", landscape = false } = req.body;
  if (!html) {
    return res.status(400).json({ error: "HTML content is required" });
  }
  let browser = null;
  try {
    browser = await puppeteer.launch({
      headless: true,
      timeout: 6e4,
      // 60 seconds timeout for browser launch
      args: [
        "--no-sandbox",
        "--disable-setuid-sandbox",
        "--disable-dev-shm-usage",
        "--disable-accelerated-2d-canvas",
        "--disable-gpu",
        "--font-render-hinting=none",
        "--single-process"
      ]
    });
    const page = await browser.newPage();
    await page.setViewport({
      width: 1200,
      height: 1600,
      deviceScaleFactor: 2
    });
    const fullHtml = `
      <!DOCTYPE html>
      <html lang="pt-BR">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
        ${printStyles}
      </head>
      <body>
        ${html}
      </body>
      </html>
    `;
    await page.setContent(fullHtml, {
      waitUntil: ["load", "domcontentloaded", "networkidle0"],
      timeout: 3e4
    });
    await page.evaluate(async () => {
      const images = document.querySelectorAll("img");
      await Promise.all(
        Array.from(images).map((img) => {
          if (img.complete) return Promise.resolve();
          return new Promise((resolve, reject) => {
            img.onload = resolve;
            img.onerror = resolve;
          });
        })
      );
    });
    await new Promise((resolve) => setTimeout(resolve, 500));
    const pdfBuffer = await page.pdf({
      format,
      landscape,
      printBackground: true,
      preferCSSPageSize: false,
      margin: {
        top: "15mm",
        right: "15mm",
        bottom: "15mm",
        left: "15mm"
      }
    });
    res.setHeader("Content-Type", "application/pdf");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="${filename}.pdf"`
    );
    res.setHeader("Content-Length", pdfBuffer.length);
    res.send(pdfBuffer);
  } catch (error) {
    console.error("Error generating PDF:", error);
    res.status(500).json({
      error: "Failed to generate PDF",
      details: error instanceof Error ? error.message : "Unknown error"
    });
  } finally {
    if (browser) {
      await browser.close();
    }
  }
});
var pdf_generator_default = router4;

// server/_core/index.ts
import cookieParser from "cookie-parser";

// server/routes/email-tracking.ts
init_db();
import { Router as Router4 } from "express";
var emailTrackingRouter = Router4();
emailTrackingRouter.get("/unsubscribe", async (req, res) => {
  try {
    const token = req.query.token;
    if (!token) {
      return res.status(400).send(getUnsubscribePage("Token inv\xE1lido.", false));
    }
    const lead = await getLeadByUnsubscribeToken(token);
    if (!lead) {
      return res.status(404).send(getUnsubscribePage("Link de descadastramento inv\xE1lido ou expirado.", false));
    }
    if (lead.isUnsubscribed) {
      return res.send(getUnsubscribePage("Voc\xEA j\xE1 foi descadastrado anteriormente. N\xE3o receber\xE1 mais e-mails.", true));
    }
    await unsubscribeLead(token);
    return res.send(getUnsubscribePage(
      `${lead.name}, voc\xEA foi descadastrado com sucesso. N\xE3o receber\xE1 mais e-mails da EndoSESMT. Em conformidade com a LGPD, seus dados ser\xE3o mantidos apenas para fins de registro.`,
      true
    ));
  } catch (err) {
    console.error("[Unsubscribe] Error:", err);
    return res.status(500).send(getUnsubscribePage("Ocorreu um erro. Tente novamente mais tarde.", false));
  }
});
emailTrackingRouter.get("/track/open", async (req, res) => {
  try {
    const leadId = parseInt(req.query.lid);
    const step = parseInt(req.query.step);
    if (leadId && !isNaN(leadId)) {
      await recordTrackingEvent({
        leadId,
        eventType: "open",
        sequenceStep: step || null,
        metadata: JSON.stringify({
          userAgent: req.headers["user-agent"],
          timestamp: (/* @__PURE__ */ new Date()).toISOString()
        })
      });
      await incrementLeadOpens(leadId);
    }
  } catch (err) {
    console.error("[EmailTracking] Open tracking error:", err);
  }
  const pixel = Buffer.from(
    "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
    "base64"
  );
  res.set("Content-Type", "image/gif");
  res.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
  res.set("Pragma", "no-cache");
  res.set("Expires", "0");
  res.send(pixel);
});
emailTrackingRouter.get("/track/click", async (req, res) => {
  try {
    const leadId = parseInt(req.query.lid);
    const step = parseInt(req.query.step);
    const targetUrl = req.query.url;
    if (leadId && !isNaN(leadId)) {
      await recordTrackingEvent({
        leadId,
        eventType: "click",
        sequenceStep: step || null,
        metadata: JSON.stringify({
          targetUrl,
          userAgent: req.headers["user-agent"],
          timestamp: (/* @__PURE__ */ new Date()).toISOString()
        })
      });
      await incrementLeadClicks(leadId);
    }
    if (targetUrl) {
      return res.redirect(302, decodeURIComponent(targetUrl));
    }
    return res.redirect(302, "https://endosesmt.com.br");
  } catch (err) {
    console.error("[EmailTracking] Click tracking error:", err);
    const targetUrl = req.query.url;
    if (targetUrl) {
      return res.redirect(302, decodeURIComponent(targetUrl));
    }
    return res.redirect(302, "https://endosesmt.com.br");
  }
});
function getUnsubscribePage(message, success) {
  const statusColor = success ? "#22c55e" : "#ef4444";
  const statusIcon = success ? "&#10004;" : "&#10008;";
  return `
<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Descadastramento - EndoSESMT</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: Arial, Helvetica, sans-serif;
      background-color: #f4f6f9;
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      padding: 20px;
    }
    .card {
      background: white;
      border-radius: 12px;
      padding: 40px;
      max-width: 500px;
      width: 100%;
      text-align: center;
      box-shadow: 0 4px 20px rgba(0,0,0,0.08);
    }
    .icon {
      font-size: 48px;
      color: ${statusColor};
      margin-bottom: 20px;
    }
    .logo {
      color: #051328;
      font-size: 24px;
      font-weight: bold;
      margin-bottom: 8px;
    }
    .subtitle {
      color: #00AEEF;
      font-size: 13px;
      margin-bottom: 30px;
    }
    .message {
      color: #333;
      font-size: 16px;
      line-height: 1.6;
      margin-bottom: 30px;
    }
    .back-link {
      display: inline-block;
      background-color: #051328;
      color: white;
      text-decoration: none;
      padding: 12px 24px;
      border-radius: 6px;
      font-weight: bold;
      font-size: 14px;
    }
    .back-link:hover { opacity: 0.9; }
    .lgpd-note {
      color: #9ca3af;
      font-size: 11px;
      margin-top: 20px;
      line-height: 1.4;
    }
  </style>
</head>
<body>
  <div class="card">
    <div class="icon">${statusIcon}</div>
    <div class="logo">ENDOSESMT</div>
    <div class="subtitle">Sa\xFAde e Seguran\xE7a do Trabalho</div>
    <p class="message">${message}</p>
    <a href="https://endosesmt.com.br" class="back-link">Voltar ao Site</a>
    <p class="lgpd-note">
      Em conformidade com a Lei Geral de Prote\xE7\xE3o de Dados (LGPD - Lei n\xBA 13.709/2018),
      garantimos o tratamento adequado dos seus dados pessoais.
    </p>
  </div>
</body>
</html>`;
}
var email_tracking_default = emailTrackingRouter;

// server/_core/index.ts
function isPortAvailable(port) {
  return new Promise((resolve) => {
    const server = net.createServer();
    server.listen(port, () => {
      server.close(() => resolve(true));
    });
    server.on("error", () => resolve(false));
  });
}
async function findAvailablePort(startPort = 3e3) {
  for (let port = startPort; port < startPort + 20; port++) {
    if (await isPortAvailable(port)) {
      return port;
    }
  }
  throw new Error(`No available port found starting from ${startPort}`);
}
async function startServer() {
  const app = express2();
  const server = createServer(app);
  app.use(express2.json({ limit: "50mb" }));
  app.use(express2.urlencoded({ limit: "50mb", extended: true }));
  app.use(cookieParser());
  registerOAuthRoutes(app);
  app.use("/api/forms", forms_default);
  app.use("/api/form-auth", form_auth_default);
  app.use("/api/pdf", pdf_generator_default);
  app.use("/api/email", email_tracking_default);
  app.use(
    "/api/trpc",
    createExpressMiddleware({
      router: appRouter,
      createContext
    })
  );
  if (process.env.NODE_ENV === "development") {
    await setupVite(app, server);
  } else {
    serveStatic(app);
  }
  const preferredPort = parseInt(process.env.PORT || "3000");
  const port = await findAvailablePort(preferredPort);
  if (port !== preferredPort) {
    console.log(`Port ${preferredPort} is busy, using port ${port} instead`);
  }
  server.listen(port, () => {
    console.log(`Server running on http://localhost:${port}/`);
    startEmailQueueProcessor();
  });
}
startServer().catch(console.error);
