chore: estructura inicial del MVP

Backend (Express + MariaDB) y frontend (React + Vite, identidad Fluent/Windows) con el
nucleo funcional: seguridad, organizacion, proyectos, actividades, subtareas, dependencias,
dashboard, calendario, gantt, comentarios, adjuntos, notificaciones in-app y catalogos.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-03 08:37:58 -05:00
commit 1d22adc42e
135 changed files with 10913 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import mysql from 'mysql2/promise';
import { env } from '../config/env.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const migrationsDir = path.resolve(__dirname, '../../database/migrations');
async function run() {
const connection = await mysql.createConnection({
host: env.db.host,
port: env.db.port,
database: env.db.name,
user: env.db.user,
password: env.db.password,
multipleStatements: true,
});
const files = fs.readdirSync(migrationsDir).filter((f) => f.endsWith('.sql')).sort();
for (const file of files) {
const sql = fs.readFileSync(path.join(migrationsDir, file), 'utf8');
console.log(`[migrate] aplicando ${file}`);
await connection.query(sql);
}
console.log(`[migrate] listo — ${files.length} archivo(s) aplicados`);
await connection.end();
}
run().catch((err) => {
console.error('[migrate] error:', err.message);
process.exit(1);
});
+108
View File
@@ -0,0 +1,108 @@
import bcrypt from 'bcryptjs';
import mysql from 'mysql2/promise';
import { env } from '../config/env.js';
async function run() {
const db = await mysql.createConnection({
host: env.db.host,
port: env.db.port,
database: env.db.name,
user: env.db.user,
password: env.db.password,
});
// Roles fijos del MVP.
const roles = [
['admin', 'Administrador'],
['it_manager', 'Jefe de TI'],
['project_lead', 'Coordinador / Lider de proyecto'],
['collaborator', 'Colaborador'],
['viewer', 'Consultor (solo lectura)'],
];
for (const [code, name] of roles) {
await db.query('INSERT IGNORE INTO roles (code, name) VALUES (?, ?)', [code, name]);
}
const priorities = [
['Baja', 1, 'muted'],
['Media', 2, 'accent'],
['Alta', 3, 'warning'],
['Critica', 4, 'danger'],
];
for (const [name, level, color] of priorities) {
await db.query('INSERT IGNORE INTO priorities (name, level, color) VALUES (?, ?, ?)', [name, level, color]);
}
const projectStatuses = [
['Planificado', 1, 'muted', 0],
['En preparacion', 2, 'accent', 0],
['En ejecucion', 3, 'accent', 0],
['En pausa', 4, 'warning', 0],
['Bloqueado', 5, 'danger', 0],
['Completado', 6, 'success', 1],
['Cancelado', 7, 'muted', 1],
];
for (const [name, sort, color, closed] of projectStatuses) {
await db.query(
'INSERT IGNORE INTO project_statuses (name, sort_order, color, is_closed) VALUES (?, ?, ?, ?)',
[name, sort, color, closed]
);
}
const activityStatuses = [
['Pendiente', 1, 'muted', 0],
['En progreso', 2, 'accent', 0],
['En revision', 3, 'accent', 0],
['Bloqueada', 4, 'danger', 0],
['En espera', 5, 'warning', 0],
['Completada', 6, 'success', 1],
['Cancelada', 7, 'muted', 1],
];
for (const [name, sort, color, closed] of activityStatuses) {
await db.query(
'INSERT IGNORE INTO activity_statuses (name, sort_order, color, is_closed) VALUES (?, ?, ?, ?)',
[name, sort, color, closed]
);
}
const projectTypes = ['Infraestructura', 'Desarrollo de software', 'Soporte', 'Seguridad', 'Mejora interna'];
for (const name of projectTypes) {
await db.query('INSERT IGNORE INTO project_types (name) VALUES (?)', [name]);
}
const departments = [
['TI', null],
['Finanzas', null],
['RRHH', null],
['Comercial', null],
];
const deptIds = {};
for (const [name] of departments) {
await db.query('INSERT IGNORE INTO departments (name, parent_id) VALUES (?, NULL)', [name]);
const [rows] = await db.query('SELECT id FROM departments WHERE name = ? AND parent_id IS NULL', [name]);
deptIds[name] = rows[0].id;
}
const subDepartments = ['Desarrollo', 'Infraestructura', 'Soporte', 'Seguridad'];
for (const name of subDepartments) {
await db.query('INSERT IGNORE INTO departments (name, parent_id) VALUES (?, ?)', [name, deptIds['TI']]);
}
// Usuario admin de dev — convencion del grupo: clave = usuario, SOLO en entornos no productivos.
const [adminRole] = await db.query("SELECT id FROM roles WHERE code = 'admin'");
const [tiDept] = await db.query("SELECT id FROM departments WHERE name = 'TI' AND parent_id IS NULL");
const passwordHash = await bcrypt.hash('mgonzalez', 12);
await db.query(
`INSERT INTO users (name, email, password_hash, role_id, department_id, position, is_active)
VALUES (?, ?, ?, ?, ?, ?, 1)
ON DUPLICATE KEY UPDATE name = VALUES(name)`,
['Milton Gonzalez', 'mgonzalez@linkedcorp.net', passwordHash, adminRole[0].id, tiDept[0].id, 'Jefe de TI']
);
console.log('[seed] listo. Usuario dev: mgonzalez@linkedcorp.net / clave: mgonzalez');
await db.end();
}
run().catch((err) => {
console.error('[seed] error:', err.message);
process.exit(1);
});