const fs = require("node:fs/promises"); const path = require("node:path"); const ALLOWED_DOC_EXTS = new Set([".pdf", ".tif", ".tiff"]); function matchesPattern(filename, pattern) { if (!filename || !pattern) return false; const fname = filename.toLowerCase(); const pat = pattern.toLowerCase().trim(); const ext = path.extname(fname); if (!ALLOWED_DOC_EXTS.has(ext)) return false; const hasWildcards = pat.includes("*") || pat.includes("?"); if (hasWildcards) { const pExt = path.extname(pat); if (ALLOWED_DOC_EXTS.has(pExt)) { return fnmatch(fname, pat); } const stem = path.basename(fname, ext); return fnmatch(stem, pat); } return fname.includes(pat); } function fnmatch(name, pattern) { const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, "."); return new RegExp(`^${escaped}$`, "i").test(name); } async function listDirSafe(dirPath) { try { return await fs.readdir(dirPath, { withFileTypes: true }); } catch { return []; } } async function statSize(filePath) { try { const stat = await fs.stat(filePath); return stat.size; } catch { return 0; } } async function findFirstMatchingFile(files, keyword) { const sorted = [...files].sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); if (!keyword) { return sorted.find((name) => ALLOWED_DOC_EXTS.has(path.extname(name).toLowerCase())) || null; } return sorted.find((name) => matchesPattern(name, keyword)) || null; } async function indexRadicados(rootPath, docTypes) { const entries = []; const radicadoDirs = (await listDirSafe(rootPath)) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); for (const radicado of radicadoDirs) { const radPath = path.join(rootPath, radicado); const files = (await listDirSafe(radPath)) .filter((entry) => entry.isFile()) .map((entry) => entry.name); for (const docType of docTypes) { const name = String(docType.name || "").trim(); const keyword = String(docType.keyword || "").trim(); if (!name || !keyword) continue; const match = await findFirstMatchingFile(files, keyword); if (!match) continue; const localPath = path.join(radPath, match); entries.push({ radicado, filename: match, docType: name, localPath, size: await statSize(localPath), }); } } return entries; } async function indexGeneral(rootPath, keywords) { const entries = []; const normalizedKeywords = (keywords || []) .map((item) => String(item || "").trim()) .filter(Boolean); const files = (await listDirSafe(rootPath)) .filter((entry) => entry.isFile()) .map((entry) => entry.name) .filter((name) => ALLOWED_DOC_EXTS.has(path.extname(name).toLowerCase())) .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); for (const filename of files) { if (normalizedKeywords.length > 0 && !normalizedKeywords.some((kw) => matchesPattern(filename, kw))) { continue; } const localPath = path.join(rootPath, filename); entries.push({ radicado: path.basename(filename, path.extname(filename)), filename, localPath, size: await statSize(localPath), }); } return entries; } async function indexFolder(rootPath, structureMode, docTypes, keywords) { const mode = structureMode === "general" ? "general" : "radicados"; if (mode === "general") { return indexGeneral(rootPath, keywords); } return indexRadicados(rootPath, docTypes || []); } module.exports = { indexFolder, matchesPattern, ALLOWED_DOC_EXTS, };