Modulo TypeScript completo — 3 flujos automatizados para Microsoft 365
// GrowthSaaS — Microsoft Graph API Authentication
// Permisos requeridos: Mail.ReadWrite, Mail.Send, MailboxSettings.ReadWrite
import { ClientSecretCredential } from '@azure/identity';
import { Client } from '@microsoft/microsoft-graph-client';
import { TokenCredentialAuthenticationProvider } from
'@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials';
export const createGraphClient = (): Client => {
const credential = new ClientSecretCredential(
process.env.AZURE_TENANT_ID!, // a1b2c3d4-e5f6-7890-abcd-ef1234567890
process.env.AZURE_CLIENT_ID!, // b2c3d4e5-f6a7-8901-bcde-f12345678901
process.env.AZURE_CLIENT_SECRET!
);
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
scopes: ['https://graph.microsoft.com/.default'],
});
return Client.initWithMiddleware({ authProvider });
};
export const SALES_MAILBOX = 'sales@growthsaas.io';
export const OPS_MAILBOX = 'maria.lopez@growthsaas.io';
import { createGraphClient, OPS_MAILBOX } from './auth';
interface PipelineData {
activeLeads: number;
demoPhase: number;
openRevenue: string;
conversionRate: string;
weekLabel: string;
}
const buildHTMLReport = (data: PipelineData): string => `
<!DOCTYPE html>
<html><body style="font-family:Segoe UI,sans-serif;max-width:600px;margin:0 auto;padding:24px;">
<div style="background:#0078d4;color:#fff;padding:20px 24px;border-radius:8px 8px 0 0;">
<h2 style="margin:0;font-size:20px;">Pipeline Report — ${data.weekLabel}</h2>
<p style="margin:6px 0 0;opacity:0.8;font-size:13px;">GrowthSaaS Revenue Operations</p>
</div>
<table style="width:100%;border-collapse:collapse;border:1px solid #e2e8f0;">
<tr style="background:#f7fafc;">
<th style="padding:12px 16px;text-align:left;font-size:13px;color:#718096;">Metrica</th>
<th style="padding:12px 16px;text-align:right;font-size:13px;color:#718096;">Valor</th>
<th style="padding:12px 16px;text-align:right;font-size:13px;color:#718096;">vs semana anterior</th>
</tr>
<tr><td style="padding:12px 16px;border-top:1px solid #e2e8f0;">Leads activos</td>
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;text-align:right;font-weight:700;">${data.activeLeads}</td>
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;text-align:right;color:#38a169;">+5 ↑</td></tr>
<tr style="background:#f7fafc;">
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;">En fase Demo</td>
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;text-align:right;font-weight:700;">${data.demoPhase}</td>
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;text-align:right;color:#38a169;">+3 ↑</td></tr>
<tr><td style="padding:12px 16px;border-top:1px solid #e2e8f0;">Oportunidades abiertas</td>
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;text-align:right;font-weight:700;">${data.openRevenue}</td>
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;text-align:right;color:#38a169;">+12% ↑</td></tr>
<tr style="background:#f7fafc;">
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;">Tasa de conversion</td>
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;text-align:right;font-weight:700;">${data.conversionRate}</td>
<td style="padding:12px 16px;border-top:1px solid #e2e8f0;text-align:right;color:#e53e3e;">-1.2% ↓</td></tr>
</table>
<p style="font-size:12px;color:#a0aec0;margin-top:16px;">Generado automaticamente por GrowthSaaS RevOps Bot | Microsoft Graph API</p>
</body></html>
`;
export const sendWeeklyPipelineReport = async (): Promise<void> => {
const graph = createGraphClient();
const pipeline: PipelineData = {
activeLeads: 47,
demoPhase: 12,
openRevenue: '$380,000',
conversionRate: '23%',
weekLabel: 'Semana 24 / 2026',
};
await graph.api(`/users/${OPS_MAILBOX}/sendMail`)
.post({
message: {
subject: `Pipeline Report S24 — ${pipeline.activeLeads} leads activos | $${pipeline.openRevenue}`,
body: {
contentType: 'HTML',
content: buildHTMLReport(pipeline),
},
toRecipients: [
{ emailAddress: { address: 'ceo@growthsaas.io', name: 'Carlos Vega (CEO)' }},
{ emailAddress: { address: 'cfo@growthsaas.io', name: 'Ana Ruiz (CFO)' }},
{ emailAddress: { address: 'vpsales@growthsaas.io', name: 'Pedro Saiz (VP Sales)'}},
],
importance: 'normal',
},
saveToSentItems: true,
});
console.log('[WeeklyReport] Reporte S24 enviado a 3 directivos');
};
| Metrica | Valor | vs semana anterior |
|---|---|---|
| Leads activos | 47 | +5 ↑ |
| En fase Demo | 12 | +3 ↑ |
| Oportunidades abiertas | $380,000 | +12% ↑ |
| Tasa de conversion | 23% | -1.2% ↓ |
Generado automaticamente por GrowthSaaS RevOps Bot | Microsoft Graph API
import { createGraphClient, SALES_MAILBOX } from './auth';
export const setupEnterpriseInboxRule = async (): Promise<void> => {
const graph = createGraphClient();
// 1. Crear carpeta "Enterprise Accounts" si no existe
const folders = await graph.api(`/users/${SALES_MAILBOX}/mailFolders`)
.select('id,displayName').get();
let enterpriseFolderId: string;
const existing = folders.value.find((f: any) => f.displayName === 'Enterprise Accounts');
if (existing) {
enterpriseFolderId = existing.id;
console.log('[InboxRules] Carpeta ya existe, id:', enterpriseFolderId);
} else {
const newFolder = await graph.api(`/users/${SALES_MAILBOX}/mailFolders`)
.post({ displayName: 'Enterprise Accounts' });
enterpriseFolderId = newFolder.id;
console.log('[InboxRules] Carpeta creada, id:', enterpriseFolderId);
}
// 2. Crear regla de bandeja: mover emails de @enterprise-*.com
const rule = await graph
.api(`/users/${SALES_MAILBOX}/mailFolders/inbox/messageRules`)
.post({
displayName: 'Auto-organizar clientes Enterprise',
sequence: 1,
isEnabled: true,
conditions: {
senderContains: ['@enterprise-corp.com', '@enterprise-tech.com', '@enterprise-global.com'],
},
actions: {
moveToFolder: enterpriseFolderId,
markAsRead: true,
},
});
console.log(`[InboxRules] Regla '${rule.displayName}' creada (id: ${rule.id})`);
};
import { createGraphClient, SALES_MAILBOX } from './auth';
import { IncomingMessage, ServerResponse } from 'http';
// Webhook URL expuesto por la Azure Function
const WEBHOOK_URL = 'https://growthsaas-functions.azurewebsites.net/api/email-webhook';
const CLIENT_STATE = process.env.WEBHOOK_SECRET!;
// 1. Crear suscripcion al inbox de ventas
export const createDemoRequestSubscription = async (): Promise<string> => {
const graph = createGraphClient();
const expiresAt = new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString();
const sub = await graph.api('/subscriptions').post({
changeType: 'created',
notificationUrl: WEBHOOK_URL,
resource: `/users/${SALES_MAILBOX}/mailFolders/inbox/messages`,
expirationDateTime: expiresAt,
clientState: CLIENT_STATE,
});
console.log(`[Webhook] Suscripcion creada. ID: ${sub.id}. Expira: ${sub.expirationDateTime}`);
return sub.id;
};
// 2. Handler del webhook en Azure Function
export const emailWebhookHandler = async (req: IncomingMessage, res: ServerResponse) => {
// Validacion inicial de suscripcion
const { validationToken } = req.query as any;
if (validationToken) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
return res.end(validationToken);
}
const body = req.body; // JSON parseado por Azure Functions runtime
for (const notification of body.value) {
// Verificar clientState para seguridad
if (notification.clientState !== CLIENT_STATE) continue;
const graph = createGraphClient();
const msgId = notification.resourceData.id;
const msg = await graph
.api(`/users/${SALES_MAILBOX}/messages/${msgId}`)
.select('subject,from,receivedDateTime,bodyPreview').get();
// Filtrar por asunto: Demo Request o Trial
const subject = (msg.subject || '').toLowerCase();
if (subject.includes('demo request') || subject.includes('trial')) {
await notifySlack({
from: msg.from.emailAddress.address,
subject: msg.subject,
preview: msg.bodyPreview,
});
}
}
res.writeHead(202);
res.end();
};
// 3. Notificacion a Slack
const notifySlack = async (lead: { from: string; subject: string; preview: string }) => {
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: 'POST',
body: JSON.stringify({
text: `🚀 *Nuevo lead caliente* en sales@growthsaas.io\n> *De:* ${lead.from}\n> *Asunto:* ${lead.subject}\n> _${lead.preview}_`,
}),
});
console.log(`[Webhook] Lead notificado en Slack: ${lead.from}`);
};