Saltar a contenido

Creación de Modelos

Los modelos hacen referencia a las tablas del diseño de su base de datos. Para este caso asumimos el siguiente diseño.

Modelos del Core del Negocio

Modelos del Esquema de Autorizacion

Detalle de los campos de las tablas

Tablas de Autorización

1. users

  • id (PK, AUTO_INCREMENT)

  • username (STRING, NOT NULL)

  • email (STRING, NOT NULL, UNIQUE)

  • password (STRING, NOT NULL)

  • is_active (ENUM: "ACTIVE"/"INACTIVE", DEFAULT: "ACTIVE")

  • avatar (STRING, NULLABLE)

2. roles

  • id (PK, AUTO_INCREMENT)

  • name (STRING, NOT NULL)

  • is_active (ENUM: "ACTIVE"/"INACTIVE", DEFAULT: "ACTIVE")

3. role_users (Tabla pivote)

  • id (PK, AUTO_INCREMENT)

  • role_id (FK → roles.id)

  • user_id (FK → users.id)

  • is_active (ENUM: "ACTIVE"/"INACTIVE", DEFAULT: "ACTIVE")

4. resources

  • id (PK, AUTO_INCREMENT)

  • path (STRING, NOT NULL)

  • method (STRING, NOT NULL)

  • is_active (ENUM: "ACTIVE"/"INACTIVE", DEFAULT: "ACTIVE")

5. resource_roles (Tabla pivote)

6. refresh_tokens

Tablas de Negocio

7. clients

8. product_types

  • id (PK, AUTO_INCREMENT)

  • name (STRING, NOT NULL)

  • description (STRING, NOT NULL)

  • status (ENUM: "ACTIVE"/"INACTIVE", DEFAULT: "ACTIVE")

9. products

10. sales

11. product_sales (Tabla pivote)

Relaciones

Sistema de Autorización:

  1. usersrole_usersroles (Many-to-Many)

  2. rolesresource_rolesresources (Many-to-Many)

  3. usersrefresh_tokens (One-to-Many)

Sistema de Negocio:

  1. clientssales (One-to-Many)

  2. product_typesproducts (One-to-Many)

  3. salesproduct_salesproducts (Many-to-Many)

Antes de crear los modelos de la aplicación es necesario que configuremos nuestra base de datos.

Especificación de la base de datos

Esto nos indica que procedemos a varios procesos.

Debe contar con la instalacion de los Motores de bases de datos:

  • MySQL Server

  • PosgreSQL

  • MS SQL Server

  • Oracle Server\

Paso 1: Configuración de Base de Datos

1.1 Instalar dependencias de base de datos

npm install sequelize mysql2 pg pg-hstore tedious oracledb
npm install -D @types/sequelize

1.2 Crear archivo .env

PORT=3000

# Variable para seleccionar el motor de base de datos
DB_ENGINE=mysql

# Configuración para MySQL
MYSQL_HOST=localhost
MYSQL_USER=admin
MYSQL_PASSWORD=password
MYSQL_NAME=almacen_2025_iisem_node
MYSQL_PORT=3306

# Configuración para PostgreSQL
POSTGRES_HOST=localhost
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_NAME=almacen_2025_iisem_node
POSTGRES_PORT=5432

# Configuración para SQL Server
MSSQL_HOST=localhost
MSSQL_USER=sa
MSSQL_PASSWORD=password
MSSQL_NAME=almacen_2025_iisem_node
MSSQL_PORT=1433

# Configuración para Oracle
ORACLE_HOST=localhost
ORACLE_USER=ALMACENDB_ADMIN
ORACLE_PASSWORD=password
ORACLE_NAME=xe
ORACLE_PORT=1521

# JWT Secret
JWT_SECRET=your_jwt_secret_key_here

1.3 Crear configuración de base de datos

src/database/db.ts

import { Sequelize } from "sequelize";
import dotenv from "dotenv";

dotenv.config();

interface DatabaseConfig {
  dialect: string;
  host: string;
  username: string;
  password: string;
  database: string;
  port: number;
}

const dbConfigurations: Record<string, DatabaseConfig> = {
  mysql: {
    dialect: "mysql",
    host: process.env.MYSQL_HOST || "localhost",
    username: process.env.MYSQL_USER || "root",
    password: process.env.MYSQL_PASSWORD || "",
    database: process.env.MYSQL_NAME || "test",
    port: parseInt(process.env.MYSQL_PORT || "3306")
  },
  postgres: {
    dialect: "postgres",
    host: process.env.POSTGRES_HOST || "localhost",
    username: process.env.POSTGRES_USER || "postgres",
    password: process.env.POSTGRES_PASSWORD || "",
    database: process.env.POSTGRES_NAME || "test",
    port: parseInt(process.env.POSTGRES_PORT || "5432")
  }
};

const selectedEngine = process.env.DB_ENGINE || "mysql";
const selectedConfig = dbConfigurations[selectedEngine];

if (!selectedConfig) {
  throw new Error(`Motor de base de datos no soportado: ${selectedEngine}`);
}

console.log(`🔌 Conectando a base de datos: ${selectedEngine.toUpperCase()}`);

export const sequelize = new Sequelize(
  selectedConfig.database,
  selectedConfig.username,
  selectedConfig.password,
  {
    host: selectedConfig.host,
    port: selectedConfig.port,
    dialect: selectedConfig.dialect as any,
    logging: process.env.NODE_ENV === 'development' ? console.log : false,
    pool: {
      max: 5,
      min: 0,
      acquire: 30000,
      idle: 10000
    }
  }
);

export const getDatabaseInfo = () => {
  return {
    engine: selectedEngine,
    config: selectedConfig,
    connectionString: `${selectedConfig.dialect}://${selectedConfig.username}@${selectedConfig.host}:${selectedConfig.port}/${selectedConfig.database}`
  };
};

export const testConnection = async (): Promise<boolean> => {
  try {
    await sequelize.authenticate();
    console.log(`✅ Conexión exitosa a ${selectedEngine.toUpperCase()}`);
    return true;
  } catch (error) {
    console.error(`❌ Error de conexión a ${selectedEngine.toUpperCase()}:`, error);
    return false;
  }
};

Paso 2: Modelos de Base de Datos

2.1 Instalar dependencias de autenticación

npm install bcryptjs jsonwebtoken path-to-regexp
npm install -D @types/bcryptjs @types/jsonwebtoken

2.2 Crear modelos de autorización

2.2.1 Modelo User

src/models/authorization/User.ts

import { Model, DataTypes } from "sequelize";
import { sequelize } from "../../database/db";
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';

export class User extends Model {
  id!: number;
  public username!: string;
  public email!: string;
  public password!: string;
  public is_active!: "ACTIVE" | "INACTIVE";
  public avatar!: string;

  public async checkPassword(password: string): Promise<boolean> {
    return bcrypt.compare(password, this.password);
  }

  public generateToken(): string {
    return jwt.sign({ id: this.id }, process.env.JWT_SECRET || 'secret', {
      expiresIn: '10m',
    });
  }

  public generateRefreshToken(): { token: string, expiresAt: Date } {
    // const expiresIn = '24H';
    const expiresIn = '5m';
    const token = jwt.sign({ id: this.id }, process.env.JWT_SECRET || 'secret', {
      expiresIn,
    });
    const expiresAt = new Date(Date.now() + 5 * 60 * 1000); // 1 minutos
    // const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 horas
    return { token, expiresAt };
  }
}


export interface UserI {
  id?: number;
  username: string;
  email: string;
  password: string;
  is_active: "ACTIVE" | "INACTIVE";
  avatar?: string;
}

User.init(
  {
    username: {
      type: DataTypes.STRING,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    },
    is_active: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
    avatar: {
      type: DataTypes.STRING,
      allowNull: true
    }
  },
  {
    tableName: "users",
    sequelize: sequelize,
    timestamps: false,
    hooks: {
      beforeCreate: async (user: User) => {
        if (user.password) {
          const salt = await bcrypt.genSalt(10);
          user.password = await bcrypt.hash(user.password, salt);
        }
      },
      beforeUpdate: async (user: User) => {
        if (user.password) {
          const salt = await bcrypt.genSalt(10);
          user.password = await bcrypt.hash(user.password, salt);
        }
      }
    }
  }
);
2.2.2 Modelo Role

src/models/authorization/Role.ts

import { Model, DataTypes } from "sequelize";
import { sequelize } from "../../database/db";

export class Role extends Model {
  public id!: number;
  public name!: string;
  public is_active!: "ACTIVE" | "INACTIVE";
}

export interface RoleI {
    id?: number;
    name: string;
    is_active: "ACTIVE" | "INACTIVE";
  }

Role.init(
  {
    name: {
      type: DataTypes.STRING,
      allowNull: false
    },
    is_active: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    }
  },
  {
    tableName: "roles",
    sequelize: sequelize,
    timestamps: false
  }
);
2.2.3 Modelo RoleUser (Modelo Pivote Relación muchos a muchos)

src/models/authorization/RoleUser.ts

import { Model, DataTypes } from "sequelize";
import { sequelize } from "../../database/db";


export class RoleUser extends Model {
  public id!: number;
  public role_id!: number;
  public user_id!: number;
  public is_active!: "ACTIVE" | "INACTIVE";
}

export interface RoleUserI {
    id?: number;
    role_id: number;
    user_id: number;
    is_active: "ACTIVE" | "INACTIVE";
  }

RoleUser.init(
  {
    is_active: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    }
  },
  {
    tableName: "role_users",
    sequelize: sequelize,
    timestamps: false
  }
);
2.2.4 Modelo Resource

src/models/authorization/Resource.ts

import { Model, DataTypes } from "sequelize";
import { sequelize } from "../../database/db";

export class Resource extends Model {
  public id!: number;
  public path!: string;
  public method!: string;
  public is_active!: "ACTIVE" | "INACTIVE";
}

export interface ResourceI {
  id?: number;
  path: string;
  method: string;
  is_active: "ACTIVE" | "INACTIVE";
}

Resource.init(
  {
    path: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Path cannot be empty" },
      },
    },
    method: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Method cannot be empty" },
      },
    },
    is_active: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
  },
  {
    tableName: "resources",
    sequelize: sequelize,
    timestamps: false,
  }
);
2.2.5 Modelo ResourceRole(Modelo Pivote Relación muchos a muchos)

src/models/authorization/ResourceRole.ts

import { Model, DataTypes } from "sequelize";
import { sequelize } from "../../database/db";

export class ResourceRole extends Model {
  public id!: number;
  public resource_id!: number;
  public role_id!: number;
  public is_active!: "ACTIVE" | "INACTIVE";
}

export interface ResourceRoleI {
  id?: number;
  resource_id: number;
  role_id: number;
  is_active: "ACTIVE" | "INACTIVE";
}

ResourceRole.init(
  {
    is_active: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
  },
  {
    tableName: "resource_roles",
    sequelize: sequelize,
    timestamps: false,
  }
);
2.2.6 Modelo RefreshToken

src/models/authorization/ResfreshToken.ts

import { Model, DataTypes } from "sequelize";
import {sequelize} from "../../database/db";
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';

export class RefreshToken extends Model {
  id!: number;
  public user_id!: number;
  public token!: string;
  public device_info!: string;
  public is_valid!: "ACTIVE" | "INACTIVE";;
  public expires_at!: Date;
  public created_at!: Date;
  public updated_at!: Date;

}

export interface RefreshTokenI {
  user_id?: number;
  token: string;
  device_info: string;
  is_valid: "ACTIVE" | "INACTIVE";
  expires_at: Date;
  created_at: Date;
  updated_at: Date;

}

RefreshToken.init(
  {
    token: {
      type: DataTypes.STRING,
      allowNull: false
    },
    device_info: {
      type: DataTypes.STRING,
      allowNull: false
    },
    is_valid: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
    expires_at: {
      type: DataTypes.DATE,
      allowNull: true
    },
    created_at: {
      type: DataTypes.DATE,
      allowNull: true
    },
    updated_at: {
      type: DataTypes.DATE,
      allowNull: true
    }
  },
  {
    tableName: "refresh_tokens",
    sequelize: sequelize,
    timestamps: false,
    hooks: {
      beforeCreate: (refreshToken: RefreshToken) => {
        const currentDate = new Date();
        refreshToken.created_at = currentDate;
        refreshToken.updated_at = currentDate;
      },
      beforeUpdate: (refreshToken: RefreshToken) => {
        const currentDate = new Date();
        refreshToken.updated_at = currentDate;
      }
    }
  }
);

2.3 Crear modelos de negocio

2.3.1 Modelo Client

src/models/Client.ts

import { DataTypes, Model } from "sequelize";
import { sequelize } from "../database/db";
import bcrypt from 'bcryptjs';


export interface ClientI {
  id?: number;
  name: string;
  address: string;
  phone: string;
  email: string;
  password: string;
  status: "ACTIVE" | "INACTIVE";
}

export class Client extends Model {
  public id!: number;
  public name!: string;
  public address!: string;
  public phone!: string;
  public email!: string;
  public password!: string;
  public status!: "ACTIVE" | "INACTIVE";
}

Client.init(
  {
    name: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    address: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    phone: {
      type: DataTypes.STRING,
      allowNull: true,
      validate: {
        notEmpty: { msg: "Phone cannot be empty" },
      },
    },
    email: {
      type: DataTypes.STRING,
      allowNull: true,
      unique: true,
      validate: {
        isEmail: { msg: "Email must be a valid email address" }, // Validate email format
      },
    },
    password: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    status: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
  },
  {
    sequelize,
    modelName: "Client",
    tableName: "clients",
    timestamps: false,
        hooks: {
          beforeCreate: async (client: Client) => {
            if (client.password) {
              const salt = await bcrypt.genSalt(10);
              client.password = await bcrypt.hash(client.password, salt);
            }
          },
          beforeUpdate: async (client: Client) => {
            if (client.password) {
              const salt = await bcrypt.genSalt(10);
              client.password = await bcrypt.hash(client.password, salt);
            }
          }
        }
  }
);
2.3.2 Modelo ProductType

src/models/ProductType.ts

import { DataTypes, Model } from "sequelize";
import { sequelize } from "../database/db";

export interface ProductTypeI {
  id?: number;
  name: string;
  description: string;
  status: "ACTIVE" | "INACTIVE";
}

export class ProductType extends Model {
  public id!: number;
  public name!: string;
  public description!: string;
  public status!: "ACTIVE" | "INACTIVE";
}

ProductType.init(
  {
    name: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Name cannot be empty" },
      },
    },
    description: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Description cannot be empty" },
      },
    },
    status: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
  },
  {
    sequelize,
    modelName: "ProductType",
    tableName: "product_types",
    timestamps: false,
  }
);
2.3.3 Modelo Product

src/models/Product.ts

import { DataTypes, Model } from "sequelize";
import { sequelize } from "../database/db";

export interface ProductI {
  id?: number;
  name: string;
  brand: string;
  price: number;
  min_stock: number;
  quantity: number;
  product_type_id: number;
  status: "ACTIVE" | "INACTIVE";
}

export class Product extends Model {
  public id!: number;
  public name!: string;
  public brand!: string;
  public price!: number;
  public min_stock!: number;
  public quantity!: number;
  public product_type_id!: number;
  public status!: "ACTIVE" | "INACTIVE";
}

Product.init(
  {
    name: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Name cannot be empty" },
      },
    },
    brand: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Brand cannot be empty" },
      },
    },
    price: {
      type: DataTypes.BIGINT,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Price cannot be empty" },
        isFloat: { msg: "Price must be a valid number" },
      },
    },
    min_stock: {
      type: DataTypes.INTEGER,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Minimum stock cannot be empty" },
        isInt: { msg: "Minimum stock must be an integer" },
      },
    },
    quantity: {
      type: DataTypes.INTEGER,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Quantity cannot be empty" },
        isInt: { msg: "Quantity must be an integer" },
      },
    },
    status: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
  },
  {
    sequelize,
    modelName: "Product",
    tableName: "products",
    timestamps: false,
  }
);
2.3.4 Modelo Sale

src/models/Sale.ts

import { DataTypes, Model } from "sequelize";
import { sequelize } from "../database/db";

export interface SaleI {
  id?: number;
  sale_date: Date;
  subtotal: number;
  tax: number;
  discounts: number;
  total: number;
  status: "ACTIVE" | "INACTIVE";
  client_id: number;
}

export class Sale extends Model {
  public id!: number;
  public sale_date!: Date;
  public subtotal!: number;
  public tax!: number;
  public discounts!: number;
  public total!: number;
  public status!: "ACTIVE" | "INACTIVE";
  public client_id!: number;
}

Sale.init(
  {
    sale_date: {
      type: DataTypes.DATE,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Sale date cannot be empty" },
        isDate: { args: true, msg: "Must be a valid date" },
      },
    },
    subtotal: {
      type: DataTypes.BIGINT,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Subtotal cannot be empty" },
        isFloat: { msg: "Subtotal must be a valid number" },
      },
    },
    tax: {
      type: DataTypes.BIGINT,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Tax cannot be empty" },
        isFloat: { msg: "Tax must be a valid number" },
      },
    },
    discounts: {
      type: DataTypes.BIGINT,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Discounts cannot be empty" },
        isFloat: { msg: "Discounts must be a valid number" },
      },
    },
    total: {
      type: DataTypes.BIGINT,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Total cannot be empty" },
        isFloat: { msg: "Total must be a valid number" },
      },
    },
    status: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
  },
  {
    sequelize,
    modelName: "Sale",
    tableName: "sales",
    timestamps: false,
  }
);
2.3.5 Modelo ProductSale

src/models/ProductSale.ts

import { DataTypes, Model } from "sequelize";
import { sequelize } from "../database/db";

export interface ProductSaleI {
  id?: number;
  total: number;
  product_id: number;
  sale_id: number;
}

export class ProductSale extends Model {
  public id!: number;
  public total!: number;
  public product_id!: number;
  public sale_id!: number;
}

ProductSale.init(
  {
    total: {
      type: DataTypes.BIGINT,
      allowNull: false,
      validate: {
        notEmpty: { msg: "Name cannot be empty" },
      },
    },
  },
  {
    sequelize,
    modelName: "ProductSale",
    tableName: "product_sales",
    timestamps: false,
  }
);

2.4 Configurar relaciones entre modelos

Agregar al final de cada archivo de modelo las relaciones correspondientes y se tienen en cuenta las relaciones en ambos sentidos

// En User.ts
....
....
....
import { RoleUser } from "./RoleUser";


...
...
...



Al final del archivo 

User.hasMany(RoleUser, {
  foreignKey: 'user_id',
  sourceKey: "id",
});
RoleUser.belongsTo(User, {
  foreignKey: 'user_id',
  targetKey: "id",
});

Quedando finalmente el archivo de la siguiente manera:

src/models/User.ts

import { Model, DataTypes } from "sequelize";
import { sequelize } from "../../database/db";
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { RoleUser } from "./RoleUser";

export class User extends Model {
  id!: number;
  public username!: string;
  public email!: string;
  public password!: string;
  public is_active!: "ACTIVE" | "INACTIVE";
  public avatar!: string;

  public async checkPassword(password: string): Promise<boolean> {
    return bcrypt.compare(password, this.password);
  }

  public generateToken(): string {
    return jwt.sign({ id: this.id }, process.env.JWT_SECRET || 'secret', {
      expiresIn: '10m',
    });
  }

  public generateRefreshToken(): { token: string, expiresAt: Date } {
    // const expiresIn = '24H';
    const expiresIn = '5m';
    const token = jwt.sign({ id: this.id }, process.env.JWT_SECRET || 'secret', {
      expiresIn,
    });
    const expiresAt = new Date(Date.now() + 5 * 60 * 1000); // 1 minutos
    // const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 horas
    return { token, expiresAt };
  }
}


export interface UserI {
  id?: number;
  username: string;
  email: string;
  password: string;
  is_active: "ACTIVE" | "INACTIVE";
  avatar?: string;
}

User.init(
  {
    username: {
      type: DataTypes.STRING,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    },
    is_active: {
      type: DataTypes.ENUM("ACTIVE", "INACTIVE"),
      defaultValue: "ACTIVE",
    },
    avatar: {
      type: DataTypes.STRING,
      allowNull: true
    }
  },
  {
    tableName: "users",
    sequelize: sequelize,
    timestamps: false,
    hooks: {
      beforeCreate: async (user: User) => {
        if (user.password) {
          const salt = await bcrypt.genSalt(10);
          user.password = await bcrypt.hash(user.password, salt);
        }
      },
      beforeUpdate: async (user: User) => {
        if (user.password) {
          const salt = await bcrypt.genSalt(10);
          user.password = await bcrypt.hash(user.password, salt);
        }
      }
    }
  }
);

User.hasMany(RoleUser, {
  foreignKey: 'user_id',
  sourceKey: "id",
});
RoleUser.belongsTo(User, {
  foreignKey: 'user_id',
  targetKey: "id",
});

De la misma manera realizar las distintas relaciones mencionadas a continuacion:

// En Role.ts
....
....
....
import { RoleUser } from "./RoleUser";

...
...
...



Al final del archivo 


Role.hasMany(RoleUser, {
  foreignKey: 'role_id',
  sourceKey: "id",
});
RoleUser.belongsTo(Role, {
  foreignKey: 'role_id',
  targetKey: "id",
});

___________________________________________________________________________________

// En ResourceRole.ts

....
....
....

import { Resource } from "./Resource";
import { Role } from "./Role";

...
...
...



Al final del archivo 

Resource.hasMany(ResourceRole, {
  foreignKey: "resource_id",
  sourceKey: "id",
});
ResourceRole.belongsTo(Resource, {
  foreignKey: "resource_id",
  targetKey: "id",
});

Role.hasMany(ResourceRole, {
  foreignKey: "role_id",
  sourceKey: "id",
});
ResourceRole.belongsTo(Role, {
  foreignKey: "role_id",
  targetKey: "id",
});

__________________________________________________________________________________

// En RefreshToken.ts

....
....
....

import { User } from "./User";

...
...
...



Al final del archivo 

User.hasMany(RefreshToken, {
  foreignKey: 'user_id',
  sourceKey: "id",
});
RefreshToken.belongsTo(User, {
  foreignKey: 'user_id',
  targetKey: "id",
});



_________________________________________________________________________________

// En Client.ts
....
....
....

import { Sale } from "./Sale";

...
...
...



Al final del archivo 

Client.hasMany(Sale, {
  foreignKey: "client_id",
  sourceKey: "id",
});
Sale.belongsTo(Client, {
  foreignKey: "client_id",
  targetKey: "id",
});


_________________________________________________________________________________

// En ProductType.ts
....
....
....

import { Product } from "./Product";

...
...
...



Al final del archivo 


ProductType.hasMany(Product, {
  foreignKey: "product_type_id",
  sourceKey: "id",
});
Product.belongsTo(ProductType, {
  foreignKey: "product_type_id",
  targetKey: "id",
});


___________________________________________________________________________________

// En Product.ts
....
....
....

import { ProductSale } from "./ProductSale";

...
...
...



Al final del archivo 

Product.hasMany(ProductSale, {
  foreignKey: "product_id",
  sourceKey: "id",
});
ProductSale.belongsTo(Product, {
  foreignKey: "product_id",
  targetKey: "id",
});




_________________________________________________________________________________
// En Sale.ts

....
....
....

import { ProductSale } from "./ProductSale";

...
...
...


Al final del archivo 

Sale.hasMany(ProductSale, {
  foreignKey: "sale_id",
  sourceKey: "id",
});
ProductSale.belongsTo(Sale, {
  foreignKey: "sale_id",
  targetKey: "id",
});

Paso 3: Creacion, conexion y sincronizacion de la base de datos

Es importante saber que en el archivo .env se encuentran los datos de conexion y nombre de la base de datos, por ende se be crear las bases de datos con dicho nombre en los motores de base de datos indicados:

Por ejemplo en MySQL

# Configuración para MySQL 

MYSQL_HOST=localhost 
MYSQL_USER=admin 
MYSQL_PASSWORD=MiNiCo57** 
MYSQL_NAME=almacen_2025_iisem_node 
MYSQL_PORT=3306

Para mas informacion revisar archivo .env

Esto quiere decir que los motores deben existir al igual que las bases de datos en blanco

Luego se debe sincronizar en la configuracion asi:

src/config/index.ts

Agregar el metodo dbConnection

import dotenv from "dotenv";
import express, { Application } from "express";
import morgan from "morgan";
import { sequelize, testConnection, getDatabaseInfo } from "../database/db";
var cors = require("cors");

dotenv.config();

export class App {
  public app: Application;

  constructor(private port?: number | string) {
    this.app = express();
    this.settings();
    this.middlewares();
    this.routes();
    this.dbConnection();
  }

  private settings(): void {
    this.app.set('port', this.port || process.env.PORT || 4000);
  }

  private middlewares(): void {
    this.app.use(morgan('dev'));
    this.app.use(cors());
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: false }));
  }

  private routes(): void {
    // Las rutas se configurarán más adelante
  }

  private async dbConnection(): Promise<void> {
    try {
      // Mostrar información de la base de datos seleccionada
      const dbInfo = getDatabaseInfo();
      console.log(`🔗 Intentando conectar a: ${dbInfo.engine.toUpperCase()}`);

      // Probar la conexión
      const isConnected = await testConnection();

      if (!isConnected) {
        throw new Error(`No se pudo conectar a la base de datos ${dbInfo.engine.toUpperCase()}`);
      }

      // Sincronizar la base de datos
      await sequelize.sync({ force: false });
      console.log(`📦 Base de datos sincronizada exitosamente`);

    } catch (error) {
      console.error("❌ Error al conectar con la base de datos:", error);
      process.exit(1); // Terminar la aplicación si no se puede conectar
    }
  }

  async listen() {
    await this.app.listen(this.app.get('port'));
    console.log(`🚀 Servidor ejecutándose en puerto ${this.app.get('port')}`);
  }
}

Una vez realizado estos cambios, ejecutamos con

npm run dev