feat(herramientas): agregar soporte de fotografias y evidencias para herramientas, kits y asignaciones de custodia

This commit is contained in:
2026-11-01 03:11:33 -05:00
parent c07162c375
commit b361e4a9b4
11 changed files with 379 additions and 42 deletions
+10
View File
@@ -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) {
+59
View File
@@ -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
};