feat(herramientas): agregar soporte de fotografias y evidencias para herramientas, kits y asignaciones de custodia
This commit is contained in:
@@ -184,6 +184,7 @@ async function setup() {
|
||||
estado ENUM('operativo', 'en_mantenimiento', 'danado', 'baja') DEFAULT 'operativo',
|
||||
ubicacion_bodega VARCHAR(150) NULL,
|
||||
notas TEXT NULL,
|
||||
foto_url VARCHAR(255) NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX (company_id),
|
||||
@@ -202,6 +203,7 @@ async function setup() {
|
||||
codigo VARCHAR(50) NULL,
|
||||
descripcion TEXT NULL,
|
||||
categoria VARCHAR(100) DEFAULT 'general',
|
||||
foto_url VARCHAR(255) NULL,
|
||||
is_active TINYINT(1) DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
@@ -238,7 +240,9 @@ async function setup() {
|
||||
fecha_devolucion DATETIME NULL,
|
||||
estado ENUM('asignado', 'devuelto', 'observado') DEFAULT 'asignado',
|
||||
observaciones_entrega TEXT NULL,
|
||||
foto_entrega_url VARCHAR(255) NULL,
|
||||
observaciones_devolucion TEXT NULL,
|
||||
foto_devolucion_url VARCHAR(255) NULL,
|
||||
asignado_por INT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
@@ -254,6 +258,12 @@ async function setup() {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// Migraciones de columnas para fotografías y evidencias
|
||||
await migrateColumn('herramientas', 'foto_url', 'foto_url VARCHAR(255) NULL AFTER notas');
|
||||
await migrateColumn('herramientas_sets', 'foto_url', 'foto_url VARCHAR(255) NULL AFTER descripcion');
|
||||
await migrateColumn('tecnico_herramientas_asignaciones', 'foto_entrega_url', 'foto_entrega_url VARCHAR(255) NULL AFTER observaciones_entrega');
|
||||
await migrateColumn('tecnico_herramientas_asignaciones', 'foto_devolucion_url', 'foto_devolucion_url VARCHAR(255) NULL AFTER observaciones_devolucion');
|
||||
|
||||
// Seed herramientas y sets iniciales para Telcotronics si no existen
|
||||
const [existingTools] = await conn.query('SELECT COUNT(*) as c FROM herramientas WHERE company_id = 1');
|
||||
if (existingTools[0].c === 0) {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const UPLOADS_DIR = path.join(__dirname, '..', 'public', 'uploads', 'herramientas');
|
||||
|
||||
// Asegurar existencia del directorio de uploads
|
||||
if (!fs.existsSync(UPLOADS_DIR)) {
|
||||
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
const ALLOWED_MIME_TYPES = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
'image/gif'
|
||||
];
|
||||
|
||||
const ALLOWED_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.webp', '.gif'];
|
||||
|
||||
/**
|
||||
* Guarda una imagen subida mediante express-fileupload en public/uploads/herramientas/
|
||||
* @param {Object} fileObj - Objeto de archivo de req.files
|
||||
* @param {string} prefix - Prefijo descriptivo del archivo (ej: 'herramienta', 'kit', 'entrega', 'devolucion')
|
||||
* @returns {Promise<string|null>} - Ruta pública (/uploads/herramientas/...) o null si no se proporcionó archivo
|
||||
*/
|
||||
async function guardarFoto(fileObj, prefix = 'herramienta') {
|
||||
if (!fileObj || !fileObj.name || !fileObj.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ext = path.extname(fileObj.name).toLowerCase();
|
||||
if (!ALLOWED_EXTENSIONS.includes(ext) || !ALLOWED_MIME_TYPES.includes(fileObj.mimetype)) {
|
||||
throw new Error('Formato de imagen no soportado. Formatos válidos: JPG, PNG, WEBP, GIF.');
|
||||
}
|
||||
|
||||
// Límite de 8 MB
|
||||
if (fileObj.size > 8 * 1024 * 1024) {
|
||||
throw new Error('La imagen excede el límite máximo de 8MB.');
|
||||
}
|
||||
|
||||
const randomSuffix = crypto.randomBytes(6).toString('hex');
|
||||
const safeFilename = `${prefix}_${Date.now()}_${randomSuffix}${ext}`;
|
||||
const targetPath = path.join(UPLOADS_DIR, safeFilename);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
fileObj.mv(targetPath, (err) => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return `/uploads/herramientas/${safeFilename}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
guardarFoto,
|
||||
UPLOADS_DIR
|
||||
};
|
||||
Reference in New Issue
Block a user