feat: nombre y logo de empresa configurables (solo dev, no desplegado a prod)

Nueva marca configurable desde Configuracion -> Empresa (solo superadmin): nombre y logo,
guardados en system_settings (mismo patron que SMTP, sin tabla nueva). Nuevo endpoint publico
GET /api/company (+ /logo) para que login/registro/recuperacion de clave puedan mostrar la marca
antes de iniciar sesion. Sin configurar, se ve "LinkedCorp" y el logo estatico de siempre.

Probado en dev (build + Playwright: panel de Configuracion, sidebar expandido/compacto, login
publico), sin desplegar a produccion por pedido explicito del usuario -- este commit sirve de
punto de control en git antes de decidir el despliegue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-15 14:42:15 -05:00
parent 7ba4a89d49
commit 2a1bdf1ba9
27 changed files with 336 additions and 37 deletions
+31
View File
@@ -0,0 +1,31 @@
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import multer from 'multer';
import { env } from '../config/env.js';
const ALLOWED_MIME = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/svg+xml']);
export const logoDir = path.join(env.storageRoot, 'branding');
fs.mkdirSync(logoDir, { recursive: true });
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, logoDir),
// Nombre generado por el servidor (evita colisiones/path traversal) y sirve de version para
// el cache-busting del logo publico (cambia en cada subida).
filename: (req, file, cb) => {
const ext = path.extname(file.originalname).slice(0, 10);
cb(null, `logo-${Date.now()}-${crypto.randomBytes(6).toString('hex')}${ext}`);
},
});
export const uploadLogo = multer({
storage,
limits: { fileSize: 3 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
if (!ALLOWED_MIME.has(file.mimetype)) {
return cb(new Error('Tipo de archivo no permitido (usa PNG, JPG, WEBP o SVG)'));
}
cb(null, true);
},
});