---
name: "FakeBuilder Generator"
description: "Gera FakeBuilders para agregados DDD usando Chance.js seguindo padrão do projeto com PropOrFactory, type augmentation e dados realistas para testes."
---

# FakeBuilder Generator Skill

## Objetivo

Esta Skill orienta Claude Code a gerar FakeBuilders completos para agregados DDD seguindo o padrão estabelecido no projeto: Chance.js para dados fake realistas, fluent API, type augmentation e suporte a factories.

## Quando usar

Ative esta Skill quando:

- Criar um novo agregado que precisa de fake builder para testes
- Gerar dados de teste realistas para testes unitários ou integração
- Implementar test fixtures com Chance.js
- Criar builders com padrão fluent API
- Precisar de dados fake válidos (CPF, CNPJ, emails, etc)
- Configurar test data factories

## Entradas esperadas

- `agregado`: Classe do agregado/entidade para o qual gerar o builder
- `propriedades`: Lista de propriedades e seus tipos
- `validacoes_especiais`: Regras específicas (CPF/CNPJ válido, códigos hierárquicos, etc)
- `caminho_arquivo`: Onde salvar o builder (geralmente `[modulo]/domain/[nome].fake-builder.ts`)

## Saídas esperadas

- Arquivo `.fake-builder.ts` completo e funcional
- Type augmentation para adicionar método `.fake()` no agregado
- Comentários explicativos quando necessário
- Exemplos de uso no topo do arquivo
- Suporte a PropOrFactory<T> para valores fixos ou funções

## Estrutura do FakeBuilder

### Template Base

```typescript
import { Chance } from 'chance';
import { [Agregado], Create[Agregado]Props } from './[agregado].aggregate';

type PropOrFactory<T> = T | ((index: number) => T);

export class [Agregado]FakeBuilder<TBuild = any> {
  private chance: Chance.Chance;
  private countObjs: number;
  private baseIndex: number;
  private static globalIndex = 0;

  // Propriedades com valores padrão usando factories
  private _empresaId: PropOrFactory<string> = () => this.chance.guid();
  private _propriedade1: PropOrFactory<tipo> = (index: number) => {
    // Lógica de geração
  };

  private constructor(countObjs: number = 1) {
    this.countObjs = countObjs;
    this.chance = new Chance();
    this.baseIndex = [Agregado]FakeBuilder.globalIndex * 100;
    [Agregado]FakeBuilder.globalIndex += 1;
  }

  static anEntity() {
    return new [Agregado]FakeBuilder<[Agregado]>(1);
  }

  static theEntities(countObjs: number) {
    return new [Agregado]FakeBuilder<[Agregado][]>(countObjs);
  }

  with[Propriedade](valueOrFactory: PropOrFactory<tipo>) {
    this._propriedade = valueOrFactory;
    return this;
  }

  build(): TBuild {
    const entities = new Array(this.countObjs)
      .fill(undefined)
      .map((_, index) => {
        const props: Create[Agregado]Props = {
          empresaId: this.callFactory(this._empresaId, index),
          propriedade1: this.callFactory(this._propriedade1, index),
          // ... outras propriedades
        };

        const entity = [Agregado].create(props);
        return entity;
      });

    return this.countObjs === 1 ? (entities[0] as any) : (entities as any);
  }

  private callFactory(factoryOrValue: PropOrFactory<any>, index: number) {
    return typeof factoryOrValue === 'function'
      ? factoryOrValue(index)
      : factoryOrValue;
  }
}

// Adicionar método estático ao agregado
[Agregado].fake = function () {
  return [Agregado]FakeBuilder;
};

// Type augmentation
declare module './[agregado].aggregate' {
  export interface [Agregado] {
    fake?: typeof [Agregado]FakeBuilder;
  }
  namespace [Agregado] {
    export let fake: () => typeof [Agregado]FakeBuilder;
  }
}
```

## Padrões Específicos por Tipo de Dado

### 1. Códigos Simples (3 dígitos)

Para bancos:

```typescript
private _codigo: PropOrFactory<string> = (index: number) => {
  const value = (this.baseIndex + index) % 900; // 0-899
  return (value + 100).toString().padStart(3, '0'); // 100-999
};
```

### 2. Códigos Sequenciais

Para centro de custo:

```typescript
private _codigo: PropOrFactory<string> = (index: number) =>
  `CC${(this.baseIndex + index + 1).toString().padStart(3, '0')}`;
```

### 3. Códigos Hierárquicos

Para plano de contas:

```typescript
private _codigo: PropOrFactory<string> = (index: number) => {
  const level1 = ((this.baseIndex + index) % 9) + 1;
  const level2 = ((this.baseIndex + index) % 99) + 1;
  const level3 = (index % 999) + 1;
  return `${level1}.${level2.toString().padStart(2, '0')}.${level3.toString().padStart(3, '0')}`;
};
```

### 4. CPF/CNPJ Válidos

Para clientes/fornecedores:

```typescript
private _documento: PropOrFactory<string> = (index: number) => {
  return this._tipoPessoa === 'PF'
    ? this.generateValidCPF()
    : this.generateValidCNPJ();
};

private generateValidCPF(): string {
  const digits = Array.from({ length: 9 }, () =>
    this.chance.integer({ min: 0, max: 9 })
  );

  const d1 = this.calculateCPFDigit(digits, 10);
  const d2 = this.calculateCPFDigit([...digits, d1], 11);

  return [...digits, d1, d2].join('');
}

private calculateCPFDigit(digits: number[], weight: number): number {
  const sum = digits.reduce((acc, digit, idx) => {
    return acc + digit * (weight - idx);
  }, 0);

  const remainder = sum % 11;
  return remainder < 2 ? 0 : 11 - remainder;
}

private generateValidCNPJ(): string {
  const digits = Array.from({ length: 12 }, () =>
    this.chance.integer({ min: 0, max: 9 })
  );

  const d1 = this.calculateCNPJDigit(digits, [5,4,3,2,9,8,7,6,5,4,3,2]);
  const d2 = this.calculateCNPJDigit([...digits, d1], [6,5,4,3,2,9,8,7,6,5,4,3,2]);

  return [...digits, d1, d2].join('');
}

private calculateCNPJDigit(digits: number[], weights: number[]): number {
  const sum = digits.reduce((acc, digit, idx) => {
    return acc + digit * weights[idx];
  }, 0);

  const remainder = sum % 11;
  return remainder < 2 ? 0 : 11 - remainder;
}
```

### 5. Nomes Realistas

Usando Chance.js:

```typescript
private _nome: PropOrFactory<string> = (index: number) =>
  this.chance.name();

// Para empresas
private _razaoSocial: PropOrFactory<string> = (index: number) =>
  `${this.chance.company()} ${this.chance.pickone(['Ltda', 'S.A.', 'ME', 'EIRELI'])}`;
```

### 6. Emails

```typescript
private _email: PropOrFactory<string | null> = (index: number) =>
  this.chance.email();

// Ou null por padrão se opcional
private _email: PropOrFactory<string | null> = () => null;
```

### 7. Telefones

```typescript
private _telefone: PropOrFactory<string | null> = (index: number) =>
  `11${this.chance.integer({ min: 900000000, max: 999999999 })}`;
```

### 8. Enums

```typescript
private _tipo: PropOrFactory<TipoPlanoContas> = (index: number) =>
  this.chance.pickone(['RECEITA', 'DESPESA']);

private _status: PropOrFactory<Status> = () => 'ATIVO';
```

### 9. Campos Opcionais

```typescript
private _descricao: PropOrFactory<string | null> = () => null;

// Ou com valor gerado
private _descricao: PropOrFactory<string | null> = (index: number) =>
  this.chance.sentence({ words: 10 });
```

### 10. Relacionamentos (IDs)

```typescript
private _parentId: PropOrFactory<string | null> = () => null;

// Para forçar relacionamento
withParentId(valueOrFactory: PropOrFactory<string>) {
  this._parentId = valueOrFactory;
  return this;
}
```

## Chance.js - Métodos Úteis

```typescript
// Identificadores
this.chance.guid()                    // UUID
this.chance.hash({ length: 10 })      // Hash aleatório

// Textos
this.chance.name()                    // Nome de pessoa
this.chance.company()                 // Nome de empresa
this.chance.email()                   // Email
this.chance.sentence({ words: 10 })   // Frase
this.chance.paragraph()               // Parágrafo

// Números
this.chance.integer({ min: 0, max: 999 })
this.chance.floating({ min: 0, max: 100, fixed: 2 })

// Datas
this.chance.date()
this.chance.timestamp()

// Endereços
this.chance.address()
this.chance.city()
this.chance.state()
this.chance.zip()
this.chance.country()

// Telefones
this.chance.phone()

// Seleção
this.chance.pickone(['A', 'B', 'C'])
this.chance.shuffle(['A', 'B', 'C'])

// Booleanos
this.chance.bool()
this.chance.bool({ likelihood: 70 })  // 70% true
```

## Exemplos Completos

### Exemplo 1: Banco (Simples)

```typescript
import { Chance } from 'chance';
import { Banco, CreateBancoProps } from './banco.aggregate';

type PropOrFactory<T> = T | ((index: number) => T);

export class BancoFakeBuilder<TBuild = any> {
  private chance: Chance.Chance;
  private countObjs: number;
  private baseIndex: number;
  private static globalIndex = 0;

  private _empresaId: PropOrFactory<string> = () => this.chance.guid();

  private _codigo: PropOrFactory<string> = (index: number) => {
    const value = (this.baseIndex + index) % 900;
    return (value + 100).toString().padStart(3, '0');
  };

  private _nome: PropOrFactory<string> = (index: number) =>
    `Banco ${this.baseIndex + index + 1}`;

  private constructor(countObjs: number = 1) {
    this.countObjs = countObjs;
    this.chance = new Chance();
    this.baseIndex = BancoFakeBuilder.globalIndex * 100;
    BancoFakeBuilder.globalIndex += 1;
  }

  static anEntity() {
    return new BancoFakeBuilder<Banco>(1);
  }

  static theEntities(countObjs: number) {
    return new BancoFakeBuilder<Banco[]>(countObjs);
  }

  withEmpresaId(valueOrFactory: PropOrFactory<string>) {
    this._empresaId = valueOrFactory;
    return this;
  }

  withCodigo(valueOrFactory: PropOrFactory<string>) {
    this._codigo = valueOrFactory;
    return this;
  }

  withNome(valueOrFactory: PropOrFactory<string>) {
    this._nome = valueOrFactory;
    return this;
  }

  build(): TBuild {
    const entities = new Array(this.countObjs)
      .fill(undefined)
      .map((_, index) => {
        const props: CreateBancoProps = {
          empresaId: this.callFactory(this._empresaId, index),
          codigo: this.callFactory(this._codigo, index),
          nome: this.callFactory(this._nome, index),
        };

        const banco = Banco.create(props);
        return banco;
      });

    return this.countObjs === 1 ? (entities[0] as any) : (entities as any);
  }

  private callFactory(factoryOrValue: PropOrFactory<any>, index: number) {
    return typeof factoryOrValue === 'function'
      ? factoryOrValue(index)
      : factoryOrValue;
  }
}

Banco.fake = function () {
  return BancoFakeBuilder;
};

declare module './banco.aggregate' {
  export interface Banco {
    fake?: typeof BancoFakeBuilder;
  }
  namespace Banco {
    export let fake: () => typeof BancoFakeBuilder;
  }
}
```

### Exemplo 2: PlanoContas (Hierárquico)

## Uso dos FakeBuilders

### Single Entity

```typescript
const banco = Banco.fake()
  .anEntity()
  .build();
```

### Multiple Entities

```typescript
const bancos = Banco.fake()
  .theEntities(5)
  .build();
```

### Custom Properties

```typescript
const banco = Banco.fake()
  .anEntity()
  .withCodigo('001')
  .withNome('Banco do Brasil')
  .build();
```

### Factory Functions

```typescript
const bancos = Banco.fake()
  .theEntities(10)
  .withCodigo((index) => (index + 1).toString().padStart(3, '0'))
  .withEmpresaId(() => 'same-empresa-id')
  .build();
```

### Test Data Setup

```typescript
describe('BancoService', () => {
  let empresaId: string;
  let bancos: Banco[];

  beforeEach(() => {
    empresaId = 'test-empresa-id';

    bancos = Banco.fake()
      .theEntities(5)
      .withEmpresaId(empresaId)
      .build();
  });

  it('should have all bancos with same empresaId', () => {
    expect(bancos.every(b => b.empresaId === empresaId)).toBe(true);
  });
});
```

## Checklist de Implementação

Ao gerar um FakeBuilder, SEMPRE inclua:

### Estrutura Base

- [ ] Import de Chance
- [ ] Import do agregado e suas props
- [ ] Type `PropOrFactory<T>`
- [ ] Classe com TBuild generic
- [ ] Propriedades privadas com factories
- [ ] Constructor privado
- [ ] Métodos estáticos `anEntity()` e `theEntities()`
- [ ] Métodos `withX()` para cada propriedade
- [ ] Método `build()`
- [ ] Método auxiliar `callFactory()`

### Type Augmentation

- [ ] Adicionar método `.fake()` ao agregado
- [ ] Declare module correto
- [ ] Export do tipo correto

### Dados Realistas

- [ ] Usar Chance.js apropriadamente
- [ ] CPF/CNPJ válidos quando necessário
- [ ] Códigos únicos usando baseIndex
- [ ] Emails válidos
- [ ] Nomes em português quando apropriado

### Índice Global

- [ ] Static globalIndex para evitar colisões
- [ ] baseIndex calculado no constructor
- [ ] Incremento do globalIndex

## Quando NÃO usar esta Skill

- Para criar in-memory repositories
- Para refatorações de código existente
