OneDrive via Microsoft Graph API
Referencia completa para gestionar archivos y carpetas en OneDrive personal y OneDrive for Business mediante la API de Microsoft Graph. Cubre subida, descarga, compartición, búsqueda, sincronización incremental con delta queries y webhooks.
Descarga abierta · sin registro · para Microsoft 365, OneDrive, SharePoint
/**
- FlowMetrics SaaS — Agente de Sincronización OneDrive
- =====================================================
- Gestiona informes mensuales de clientes en OneDrive for Business
- mediante Microsoft Graph API.
- Stack: TypeScript · @microsoft/microsoft-graph-client · Azure AD OAuth2
- Permisos requeridos (Azure AD App):
- Files.ReadWrite.All
- Sites.ReadWrite.All (si se usa SharePoint) */
import { Client } from "@microsoft/microsoft-graph-client"; import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials"; import { ClientSecretCredential } from "@azure/identity"; import * as fs from "fs"; import * as path from "path";
// ─── Configuración ────────────────────────────────────────────────────────────
const CONFIG = { tenantId: process.env.AZURE_TENANT_ID!, clientId: process.env.AZURE_CLIENT_ID!, clientSecret: process.env.AZURE_CLIENT_SECRET!, userId: process.env.ONEDRIVE_USER_ID!, // UPN o GUID del account de servicio deltaLinkFile: "./delta_link.json", // Persiste el estado del sync };
// ─── Cliente Graph ─────────────────────────────────────────────────────────────
function createGraphClient(): Client { const credential = new ClientSecretCredential( CONFIG.tenantId, CONFIG.clientId, CONFIG.clientSecret, ); const authProvider = new TokenCredentialAuthenticationProvider(credential, { scopes: ["https://graph.microsoft.com/.default"], }); return Client.initWithMiddleware({ authProvider }); }
const graphClient = createGraphClient(); const userId = CONFIG.userId;
// ─── 1. Información del drive ──────────────────────────────────────────────────
async function getDriveInfo(): Promise {
const drive = await graphClient
.api(/users/${userId}/drive)
.select("id,driveType,quota")
.get();
const usedGB = (drive.quota.used / 1e9).toFixed(1);
const totalGB = (drive.quota.total / 1e9).toFixed(0);
console.log(✓ Drive [${drive.driveType}] ${usedGB} GB / ${totalGB} GB usados);
}
// ─── 2. Crear estructura de carpetas ──────────────────────────────────────────
async function ensureFolder(folderPath: string): Promise { /**
- Crea la carpeta si no existe; devuelve su itemId.
- Ejemplo: "Clientes/Acme Logistics/2026-06"
*/
try {
const item = await graphClient
.api(
/users/${userId}/drive/root:/${folderPath}) .select("id,name") .get(); return item.id; // Ya existe } catch { // Construir recursivamente (la API no crea rutas completas de una vez) const parts = folderPath.split("/"); let current = ""; let lastId = "";
for (const part of parts) {
current = current ? `${current}/${part}` : part;
try {
const existing = await graphClient
.api(`/users/${userId}/drive/root:/${current}`)
.select("id")
.get();
lastId = existing.id;
} catch {
const parentPath = current.substring(0, current.lastIndexOf("/")) || "root";
const parentApi = parentPath === "root"
? `/users/${userId}/drive/root/children`
: `/users/${userId}/drive/root:/${parentPath}:/children`;
const created = await graphClient.api(parentApi).post({
name: part,
folder: {},
"@microsoft.graph.conflictBehavior": "rename",
});
lastId = created.id;
console.log(` + Carpeta creada: ${current}`);
}
}
return lastId;
} }
// ─── 3. Subir archivos ────────────────────────────────────────────────────────
async function uploadFile( localPath: string, oneDrivePath: string, ): Promise { const stats = fs.statSync(localPath); const fileSize = stats.size; const webUrl: string = fileSize <= 4 * 1024 * 1024 ? await uploadSmall(localPath, oneDrivePath) : await uploadLarge(localPath, oneDrivePath, fileSize); return webUrl; }
async function uploadSmall(localPath: string, oneDrivePath: string): Promise {
const buffer = fs.readFileSync(localPath);
const uploaded = await graphClient
.api(/users/${userId}/drive/root:/${oneDrivePath}:/content)
.put(buffer);
console.log( ↑ Subido (simple): ${path.basename(localPath)});
return uploaded.webUrl;
}
async function uploadLarge(
localPath: string,
oneDrivePath: string,
fileSize: number,
): Promise {
const session = await graphClient
.api(/users/${userId}/drive/root:/${oneDrivePath}:/createUploadSession)
.post({
item: {
"@microsoft.graph.conflictBehavior": "replace",
name: path.basename(oneDrivePath),
},
});
const CHUNK = 10 * 1024 * 1024; // 10 MB (múltiplo de 320 KB) const fd = fs.openSync(localPath, "r"); let offset = 0; let webUrl = "";
while (offset < fileSize) { const length = Math.min(CHUNK, fileSize - offset); const buf = Buffer.alloc(length); fs.readSync(fd, buf, 0, length, offset);
const res = await fetch(session.uploadUrl, {
method: "PUT",
headers: {
"Content-Length": `${length}`,
"Content-Range": `bytes ${offset}-${offset + length - 1}/${fileSize}`,
},
body: buf,
});
if (res.status === 201 || res.status === 200) {
const data = (await res.json()) as { webUrl: string };
webUrl = data.webUrl;
}
offset += length;
console.log(
` ↑ [chunked] ${path.basename(localPath)} — ${Math.round((offset / fileSize) * 100)}%`,
);
} fs.closeSync(fd); return webUrl; }
// ─── 4. Generar sharing link con expiración ───────────────────────────────────
async function createShareLink( itemId: string, expiresInDays = 30, ): Promise { const expiry = new Date(); expiry.setDate(expiry.getDate() + expiresInDays);
const link = await graphClient
.api(/users/${userId}/drive/items/${itemId}/createLink)
.post({
type: "view",
scope: "organization",
expirationDateTime: expiry.toISOString(),
});
console.log( 🔗 Link (expira ${expiresInDays}d): ${link.link.webUrl});
return link.link.webUrl;
}
// ─── 5. Invitar a cliente externo ─────────────────────────────────────────────
async function inviteExternal(
itemId: string,
email: string,
message: string,
): Promise {
await graphClient
.api(/users/${userId}/drive/items/${itemId}/invite)
.post({
requireSignIn: true,
sendInvitation: true,
roles: ["read"],
recipients: [{ email }],
message,
});
console.log( ✉ Invitación enviada a: ${email});
}
// ─── 6. Delta sync incremental ────────────────────────────────────────────────
interface DeltaState { deltaLink: string; lastSync: string; }
async function initialSync(folderPath: string): Promise {
let response = await graphClient
.api(/users/${userId}/drive/root:/${folderPath}:/delta)
.select("id,name,deleted,file,folder,parentReference,lastModifiedDateTime")
.get();
const items: unknown[] = [...response.value];
while (response["@odata.nextLink"]) { response = await graphClient.api(response["@odata.nextLink"]).get(); items.push(...response.value); }
const state: DeltaState = { deltaLink: response["@odata.deltaLink"], lastSync: new Date().toISOString(), }; fs.writeFileSync(CONFIG.deltaLinkFile, JSON.stringify(state, null, 2));
console.log(\n📦 Sync inicial — ${items.length} elementos indexados);
return state;
}
async function incrementalSync(): Promise { if (!fs.existsSync(CONFIG.deltaLinkFile)) { console.warn("No hay deltaLink guardado. Ejecuta initialSync() primero."); return; }
const state: DeltaState = JSON.parse(fs.readFileSync(CONFIG.deltaLinkFile, "utf-8")); const changes = await graphClient.api(state.deltaLink).get();
let added = 0, modified = 0, deleted = 0;
for (const item of changes.value) {
if (item.deleted) {
console.log( 🗑 Eliminado: ${item.id});
deleted++;
} else if (item.file) {
console.log( ✎ Archivo ${item.name} — ${new Date(item.lastModifiedDateTime).toLocaleDateString("es-ES")});
modified++;
} else if (item.folder) {
added++;
}
}
// Actualizar deltaLink para el próximo sync state.deltaLink = changes["@odata.deltaLink"]; state.lastSync = new Date().toISOString(); fs.writeFileSync(CONFIG.deltaLinkFile, JSON.stringify(state, null, 2));
console.log(\n📊 Cambios desde último sync: +${added} carpetas, ~${modified} archivos, -${deleted} eliminados);
}
// ─── 7. Webhook (suscripción a cambios) ───────────────────────────────────────
async function subscribeToChanges(webhookUrl: string): Promise { const expiry = new Date(); expiry.setHours(expiry.getHours() + 4230); // Max para OneDrive: ~43 días
const sub = await graphClient.api("/subscriptions").post({
changeType: "updated,deleted,created",
notificationUrl: webhookUrl,
resource: /users/${userId}/drive/root,
expirationDateTime: expiry.toISOString(),
clientState: "flowmetrics-secret-token",
});
console.log(\n🔔 Suscripción webhook activa hasta ${new Date(sub.expirationDateTime).toLocaleDateString("es-ES")});
console.log( ID: ${sub.id});
}
// ─── 8. Orquestador principal ─────────────────────────────────────────────────
interface ClientReport { clientName: string; clientEmail: string; period: string; // "2026-06" pdfPath: string; excelPath: string; }
async function syncClientReports(reports: ClientReport[]): Promise { console.log("=".repeat(60)); console.log(" FlowMetrics — Agente Sync OneDrive"); console.log("=".repeat(60));
await getDriveInfo();
for (const report of reports) {
const folderPath = Clientes/${report.clientName}/${report.period};
console.log(\n▶ Procesando: ${report.clientName} / ${report.period});
// 2. Asegurar estructura de carpetas
const folderId = await ensureFolder(folderPath);
// 3. Subir archivos
const pdfItem = await graphClient
.api(`/users/${userId}/drive/root:/${folderPath}/${path.basename(report.pdfPath)}`)
.select("id,webUrl")
.get().catch(async () => {
// Archivo no existe aún, lo subimos
return graphClient
.api(`/users/${userId}/drive/root:/${folderPath}/${path.basename(report.pdfPath)}:/content`)
.put(Buffer.alloc(1024)); // simulado
});
// 4. Crear link de compartición (30 días)
const shareLink = await createShareLink(pdfItem.id ?? folderId, 30);
// 5. Invitar al cliente con el link
await inviteExternal(
pdfItem.id ?? folderId,
report.clientEmail,
`Hola, adjuntamos el informe de analítica de procesos correspondiente a ${report.period}. ` +
`Puede acceder desde el siguiente enlace (válido 30 días): ${shareLink}`,
);
}
// 6. Delta sync para detectar cambios posteriores console.log("\n─── Delta Sync ───────────────────────────────────────────"); if (!fs.existsSync(CONFIG.deltaLinkFile)) { await initialSync("Clientes"); } else { await incrementalSync(); }
// 7. Webhook para notificaciones en tiempo real await subscribeToChanges("https://api.flowmetrics.io/webhooks/onedrive");
console.log("\n✅ Sincronización completada."); }
// ─── Ejecución ────────────────────────────────────────────────────────────────
const REPORTS: ClientReport[] = [ { clientName: "Acme Logistics", clientEmail: "ops@acmelogistics.com", period: "2026-06", pdfPath: "./reports/acme-logistics-2026-06.pdf", excelPath: "./reports/acme-logistics-2026-06-data.xlsx", }, { clientName: "TechGroup EU", clientEmail: "analytics@techgroupeu.com", period: "2026-06", pdfPath: "./reports/techgroupeu-2026-06.pdf", excelPath: "./reports/techgroupeu-2026-06-data.xlsx", }, { clientName: "Retail Prime ES", clientEmail: "datateam@retailprime.es", period: "2026-06", pdfPath: "./reports/retailprime-2026-06.pdf", excelPath: "./reports/retailprime-2026-06-data.xlsx", }, ];
syncClientReports(REPORTS).catch(console.error);
// qué_hace
Permite a agentes IA gestionar archivos en OneDrive (subir, descargar, compartir, sincronizar) mediante la API de Microsoft Graph.
// cómo_lo_hace
Proporciona fragmentos TypeScript listos para usar con el cliente Graph: operaciones CRUD de archivos, sesiones de subida resumible para archivos grandes, delta queries para sync incremental y creación de enlaces de compartición con expiración.
// ejemplo_de_uso
Úsala cuando un agente IA necesita guardar o recuperar archivos del OneDrive corporativo como parte de un flujo de trabajo. Ej.: el agente sube el informe PDF generado automáticamente a una carpeta compartida y crea un enlace con expiración de 7 días.
// plataformas
// opiniones_de_la_comunidad
Opiniones
Cargando opiniones…
// pase_cultiva_ia
Llévate todo el arsenal con el Pase
Todas las skills, prompts y automatizaciones del catálogo en un único archivo, listas para usar: un pago, acceso de por vida y las novedades que añadamos. Sin suscripción.
Pago único · IVA incluido · pago seguro con Stripe.
Acceso inmediato · si no es lo que esperabas, te devolvemos los 10 €.