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:
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user