# Ejemplos de Uso de TypeORM

Guía rápida para comenzar a usar TypeORM en el proyecto.

---

## 1. Crear una Entity

### Ejemplo: User Entity

**Archivo:** `src/modules/auth/entities/user.entity.ts`

```typescript
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  ManyToMany,
  JoinTable,
} from 'typeorm';
import { Role } from './role.entity';

@Entity('users', { schema: 'auth' })
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true, length: 255 })
  email: string;

  @Column({ length: 255 })
  password: string;

  @Column({ name: 'first_name', length: 100 })
  firstName: string;

  @Column({ name: 'last_name', length: 100 })
  lastName: string;

  @Column({ default: true })
  active: boolean;

  @Column({ name: 'email_verified', default: false })
  emailVerified: boolean;

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;

  @ManyToMany(() => Role, role => role.users)
  @JoinTable({
    name: 'user_roles',
    joinColumn: { name: 'user_id', referencedColumnName: 'id' },
    inverseJoinColumn: { name: 'role_id', referencedColumnName: 'id' },
  })
  roles: Role[];
}
```

### Ejemplo: Role Entity

**Archivo:** `src/modules/auth/entities/role.entity.ts`

```typescript
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  ManyToMany,
} from 'typeorm';
import { User } from './user.entity';

@Entity('roles', { schema: 'auth' })
export class Role {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true, length: 50 })
  name: string;

  @Column({ type: 'text', nullable: true })
  description: string;

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;

  @ManyToMany(() => User, user => user.roles)
  users: User[];
}
```

---

## 2. Actualizar typeorm.ts

Después de crear entities, actualizar el array `entities` en `src/config/typeorm.ts`:

```typescript
export const AppDataSource = new DataSource({
  // ... otras configuraciones ...

  entities: [
    'src/modules/auth/entities/*.entity.ts',
    // Agregar más rutas según sea necesario
  ],

  // ... resto de configuración ...
});
```

---

## 3. Usar Repository en un Service

### Ejemplo: UserService

**Archivo:** `src/modules/auth/services/user.service.ts`

```typescript
import { Repository } from 'typeorm';
import { AppDataSource } from '../../../config/typeorm.js';
import { User } from '../entities/user.entity.js';
import { Role } from '../entities/role.entity.js';

export class UserService {
  private userRepository: Repository<User>;
  private roleRepository: Repository<Role>;

  constructor() {
    this.userRepository = AppDataSource.getRepository(User);
    this.roleRepository = AppDataSource.getRepository(Role);
  }

  // Crear usuario
  async createUser(data: {
    email: string;
    password: string;
    firstName: string;
    lastName: string;
  }): Promise<User> {
    const user = this.userRepository.create(data);
    return await this.userRepository.save(user);
  }

  // Buscar usuario por email (con roles)
  async findByEmail(email: string): Promise<User | null> {
    return await this.userRepository.findOne({
      where: { email },
      relations: ['roles'],
    });
  }

  // Buscar usuario por ID
  async findById(id: string): Promise<User | null> {
    return await this.userRepository.findOne({
      where: { id },
      relations: ['roles'],
    });
  }

  // Listar todos los usuarios (con paginación)
  async findAll(page: number = 1, limit: number = 10): Promise<{
    users: User[];
    total: number;
    page: number;
    totalPages: number;
  }> {
    const [users, total] = await this.userRepository.findAndCount({
      skip: (page - 1) * limit,
      take: limit,
      relations: ['roles'],
      order: { createdAt: 'DESC' },
    });

    return {
      users,
      total,
      page,
      totalPages: Math.ceil(total / limit),
    };
  }

  // Actualizar usuario
  async updateUser(id: string, data: Partial<User>): Promise<User | null> {
    await this.userRepository.update(id, data);
    return await this.findById(id);
  }

  // Asignar rol a usuario
  async assignRole(userId: string, roleId: string): Promise<User | null> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles'],
    });

    if (!user) return null;

    const role = await this.roleRepository.findOne({
      where: { id: roleId },
    });

    if (!role) return null;

    if (!user.roles) user.roles = [];
    user.roles.push(role);

    return await this.userRepository.save(user);
  }

  // Eliminar usuario (soft delete)
  async deleteUser(id: string): Promise<boolean> {
    const result = await this.userRepository.update(id, { active: false });
    return result.affected ? result.affected > 0 : false;
  }
}
```

---

## 4. Query Builder (para queries complejas)

### Ejemplo: Búsqueda avanzada de usuarios

```typescript
async searchUsers(filters: {
  search?: string;
  active?: boolean;
  roleId?: string;
}): Promise<User[]> {
  const query = this.userRepository
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.roles', 'role');

  if (filters.search) {
    query.where(
      'user.email ILIKE :search OR user.firstName ILIKE :search OR user.lastName ILIKE :search',
      { search: `%${filters.search}%` }
    );
  }

  if (filters.active !== undefined) {
    query.andWhere('user.active = :active', { active: filters.active });
  }

  if (filters.roleId) {
    query.andWhere('role.id = :roleId', { roleId: filters.roleId });
  }

  return await query.getMany();
}
```

---

## 5. Transacciones

### Ejemplo: Crear usuario con roles en una transacción

```typescript
async createUserWithRoles(
  userData: {
    email: string;
    password: string;
    firstName: string;
    lastName: string;
  },
  roleIds: string[]
): Promise<User> {
  return await AppDataSource.transaction(async (transactionalEntityManager) => {
    // Crear usuario
    const user = transactionalEntityManager.create(User, userData);
    const savedUser = await transactionalEntityManager.save(user);

    // Buscar roles
    const roles = await transactionalEntityManager.findByIds(Role, roleIds);

    // Asignar roles
    savedUser.roles = roles;
    return await transactionalEntityManager.save(savedUser);
  });
}
```

---

## 6. Raw Queries (cuando sea necesario)

### Ejemplo: Query personalizada con parámetros

```typescript
async getUserStats(): Promise<{ total: number; active: number; inactive: number }> {
  const result = await AppDataSource.query(
    `
    SELECT
      COUNT(*) as total,
      SUM(CASE WHEN active = true THEN 1 ELSE 0 END) as active,
      SUM(CASE WHEN active = false THEN 1 ELSE 0 END) as inactive
    FROM auth.users
    `
  );

  return result[0];
}
```

---

## 7. Migrar código existente gradualmente

### Antes (usando pool pg):

```typescript
// src/modules/auth/services/user.service.ts (viejo)
import { query, queryOne } from '../../../config/database.js';

async findByEmail(email: string): Promise<User | null> {
  return await queryOne(
    'SELECT * FROM auth.users WHERE email = $1',
    [email]
  );
}
```

### Después (usando TypeORM):

```typescript
// src/modules/auth/services/user.service.ts (nuevo)
import { AppDataSource } from '../../../config/typeorm.js';
import { User } from '../entities/user.entity.js';

async findByEmail(email: string): Promise<User | null> {
  const userRepository = AppDataSource.getRepository(User);
  return await userRepository.findOne({ where: { email } });
}
```

**Nota:** Ambos métodos pueden coexistir. Migrar gradualmente cuando sea conveniente.

---

## 8. Uso en Controllers

### Ejemplo: UserController

**Archivo:** `src/modules/auth/controllers/user.controller.ts`

```typescript
import { Request, Response } from 'express';
import { UserService } from '../services/user.service.js';

export class UserController {
  private userService: UserService;

  constructor() {
    this.userService = new UserService();
  }

  // GET /api/v1/users
  async getAll(req: Request, res: Response): Promise<void> {
    try {
      const page = parseInt(req.query.page as string) || 1;
      const limit = parseInt(req.query.limit as string) || 10;

      const result = await this.userService.findAll(page, limit);

      res.json({
        success: true,
        data: result,
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Error fetching users',
      });
    }
  }

  // GET /api/v1/users/:id
  async getById(req: Request, res: Response): Promise<void> {
    try {
      const user = await this.userService.findById(req.params.id);

      if (!user) {
        res.status(404).json({
          success: false,
          error: 'User not found',
        });
        return;
      }

      res.json({
        success: true,
        data: user,
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Error fetching user',
      });
    }
  }

  // POST /api/v1/users
  async create(req: Request, res: Response): Promise<void> {
    try {
      const user = await this.userService.createUser(req.body);

      res.status(201).json({
        success: true,
        data: user,
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Error creating user',
      });
    }
  }
}
```

---

## 9. Validación con Zod (integración)

```typescript
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  firstName: z.string().min(2),
  lastName: z.string().min(2),
});

async create(req: Request, res: Response): Promise<void> {
  try {
    // Validar datos
    const validatedData = createUserSchema.parse(req.body);

    // Crear usuario
    const user = await this.userService.createUser(validatedData);

    res.status(201).json({
      success: true,
      data: user,
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      res.status(400).json({
        success: false,
        error: 'Validation error',
        details: error.errors,
      });
      return;
    }

    res.status(500).json({
      success: false,
      error: 'Error creating user',
    });
  }
}
```

---

## 10. Custom Repository (avanzado)

### Ejemplo: UserRepository personalizado

**Archivo:** `src/modules/auth/repositories/user.repository.ts`

```typescript
import { Repository } from 'typeorm';
import { AppDataSource } from '../../../config/typeorm.js';
import { User } from '../entities/user.entity.js';

export class UserRepository extends Repository<User> {
  constructor() {
    super(User, AppDataSource.createEntityManager());
  }

  // Método personalizado
  async findActiveUsers(): Promise<User[]> {
    return this.createQueryBuilder('user')
      .where('user.active = :active', { active: true })
      .andWhere('user.emailVerified = :verified', { verified: true })
      .leftJoinAndSelect('user.roles', 'role')
      .orderBy('user.createdAt', 'DESC')
      .getMany();
  }

  // Otro método personalizado
  async findByRoleName(roleName: string): Promise<User[]> {
    return this.createQueryBuilder('user')
      .leftJoinAndSelect('user.roles', 'role')
      .where('role.name = :roleName', { roleName })
      .getMany();
  }
}
```

---

## Recursos Adicionales

- [TypeORM Documentation](https://typeorm.io/)
- [TypeORM Entity Documentation](https://typeorm.io/entities)
- [TypeORM Relations](https://typeorm.io/relations)
- [TypeORM Query Builder](https://typeorm.io/select-query-builder)
- [TypeORM Migrations](https://typeorm.io/migrations)

---

## Recomendaciones

1. Comenzar con entities simples y agregar complejidad gradualmente
2. Usar Repository para queries simples
3. Usar QueryBuilder para queries complejas
4. Usar transacciones para operaciones que afectan múltiples tablas
5. Validar datos con Zod antes de guardar en base de datos
6. No usar `synchronize: true` en producción
7. Crear índices manualmente en DDL para mejor performance
8. Usar eager/lazy loading según el caso de uso
9. Documentar entities con comentarios JSDoc
10. Mantener código existente con pool pg hasta estar listo para migrar
