---
id: "GUIA-CONVENCIONES"
title: "Guía de Convenciones de Desarrollo"
type: "Guide"
status: "Active"
project: "platform_marketing_content"
version: "1.0.0"
created_date: "2026-01-04"
updated_date: "2026-01-04"
---
# Guía de Convenciones de Desarrollo

**Versión:** 1.0.0
**Fecha:** 2025-12-08
**Proyecto:** Platform Marketing Content

---

## Estructura del Proyecto

```
platform_marketing_content/
├── apps/
│   ├── backend/                 # API NestJS
│   │   ├── src/
│   │   │   ├── modules/         # Módulos por dominio
│   │   │   │   ├── auth/
│   │   │   │   ├── crm/
│   │   │   │   ├── projects/
│   │   │   │   ├── generation/
│   │   │   │   ├── assets/
│   │   │   │   ├── automation/
│   │   │   │   └── analytics/
│   │   │   ├── common/          # Utilidades compartidas
│   │   │   ├── config/          # Configuración
│   │   │   └── main.ts
│   │   ├── test/
│   │   └── package.json
│   │
│   ├── frontend/                # React + Vite
│   │   ├── src/
│   │   │   ├── components/      # Componentes reutilizables
│   │   │   ├── pages/           # Páginas/vistas
│   │   │   ├── hooks/           # Custom hooks
│   │   │   ├── services/        # API clients
│   │   │   ├── stores/          # Estado global
│   │   │   └── utils/           # Utilidades
│   │   └── package.json
│   │
│   └── comfyui/                 # Workflows ComfyUI
│       └── workflows/
│
├── docs/                        # Documentación
├── orchestration/               # Config SIMCO
└── docker-compose.yml
```

---

## Convenciones de Nomenclatura

### Backend (NestJS/TypeScript)

```typescript
// Archivos: kebab-case
user.controller.ts
create-user.dto.ts
user.service.ts
user.entity.ts

// Clases: PascalCase
export class UserService {}
export class CreateUserDto {}

// Interfaces: PascalCase con prefijo I opcional
export interface IUserRepository {}
export interface User {}

// Variables y funciones: camelCase
const userId = '...';
function getUserById() {}

// Constantes: UPPER_SNAKE_CASE
const MAX_RETRY_ATTEMPTS = 3;
const DEFAULT_PAGE_SIZE = 20;

// Enums: PascalCase
enum UserStatus {
  ACTIVE = 'active',
  SUSPENDED = 'suspended',
}
```

### Frontend (React/TypeScript)

```typescript
// Componentes: PascalCase
UserProfile.tsx
AssetCard.tsx

// Hooks: camelCase con prefijo use
useAuth.ts
useAssets.ts

// Stores: camelCase
authStore.ts
assetsStore.ts

// Utilidades: camelCase
formatDate.ts
validateEmail.ts
```

### Base de Datos

```sql
-- Tablas: snake_case, plural
CREATE TABLE users ...
CREATE TABLE generation_jobs ...

-- Columnas: snake_case
user_id
created_at
is_active

-- Índices: idx_{tabla}_{columnas}
idx_users_email
idx_jobs_tenant_status

-- Foreign keys: fk_{tabla}_{referencia}
fk_users_tenant
```

---

## Estructura de Módulos Backend

```
modules/crm/
├── controllers/
│   ├── clients.controller.ts
│   ├── brands.controller.ts
│   └── products.controller.ts
├── services/
│   ├── clients.service.ts
│   ├── brands.service.ts
│   └── products.service.ts
├── entities/
│   ├── client.entity.ts
│   ├── brand.entity.ts
│   └── product.entity.ts
├── dto/
│   ├── create-client.dto.ts
│   ├── update-client.dto.ts
│   └── client-response.dto.ts
├── repositories/
│   └── clients.repository.ts
├── crm.module.ts
└── index.ts
```

---

## Patrones de Código

### Controllers

```typescript
@Controller('crm/clients')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiTags('CRM - Clients')
export class ClientsController {
  constructor(private readonly clientsService: ClientsService) {}

  @Post()
  @ApiOperation({ summary: 'Create a new client' })
  @ApiResponse({ status: 201, type: ClientResponseDto })
  async create(
    @CurrentTenant() tenantId: string,
    @Body() createClientDto: CreateClientDto,
  ): Promise<ClientResponseDto> {
    return this.clientsService.create(tenantId, createClientDto);
  }

  @Get()
  @ApiOperation({ summary: 'List all clients' })
  async findAll(
    @CurrentTenant() tenantId: string,
    @Query() query: ListClientsQueryDto,
  ): Promise<PaginatedResponse<ClientResponseDto>> {
    return this.clientsService.findAll(tenantId, query);
  }
}
```

### Services

```typescript
@Injectable()
export class ClientsService {
  constructor(
    private readonly clientsRepository: ClientsRepository,
    private readonly eventEmitter: EventEmitter2,
  ) {}

  async create(tenantId: string, dto: CreateClientDto): Promise<Client> {
    const client = await this.clientsRepository.create({
      ...dto,
      tenantId,
      status: ClientStatus.PROSPECT,
    });

    this.eventEmitter.emit('client.created', { client });

    return client;
  }

  async findAll(tenantId: string, query: ListClientsQueryDto): Promise<PaginatedResponse<Client>> {
    return this.clientsRepository.findAllPaginated(tenantId, query);
  }
}
```

### DTOs

```typescript
export class CreateClientDto {
  @ApiProperty({ example: 'Acme Corp' })
  @IsString()
  @MinLength(2)
  @MaxLength(255)
  name: string;

  @ApiPropertyOptional()
  @IsString()
  @IsOptional()
  legalName?: string;

  @ApiPropertyOptional({ enum: ClientSize })
  @IsEnum(ClientSize)
  @IsOptional()
  size?: ClientSize;
}
```

### Entities

```typescript
@Entity('clients', { schema: 'crm' })
export class Client extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  tenantId: string;

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

  @Column({ type: 'enum', enum: ClientStatus, default: ClientStatus.PROSPECT })
  status: ClientStatus;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @OneToMany(() => Brand, (brand) => brand.client)
  brands: Brand[];
}
```

---

## API Response Format

### Success Response

```json
{
  "data": { ... },
  "meta": {
    "timestamp": "2025-12-08T10:30:00Z"
  }
}
```

### Paginated Response

```json
{
  "data": [...],
  "meta": {
    "total": 100,
    "page": 1,
    "limit": 20,
    "totalPages": 5
  }
}
```

### Error Response

```json
{
  "statusCode": 400,
  "message": "Validation failed",
  "errors": [
    {
      "field": "email",
      "message": "Invalid email format"
    }
  ],
  "timestamp": "2025-12-08T10:30:00Z"
}
```

---

## Git Workflow

### Branches

```
main                    # Producción
├── develop             # Desarrollo
│   ├── feature/PMC-001-tenants
│   ├── feature/PMC-002-crm
│   ├── fix/login-validation
│   └── refactor/auth-middleware
```

### Commit Messages

```
feat(crm): add client creation endpoint
fix(auth): resolve token refresh issue
docs(api): update swagger documentation
refactor(generation): extract queue service
test(assets): add upload unit tests
chore(deps): update dependencies
```

### Pull Request Template

```markdown
## Description
Brief description of changes

## Type of Change
- [ ] Feature
- [ ] Bug fix
- [ ] Refactor
- [ ] Documentation

## Testing
- [ ] Unit tests added/updated
- [ ] Manual testing completed

## Checklist
- [ ] Code follows conventions
- [ ] Self-review completed
- [ ] Documentation updated
```

---

## Testing

### Unit Tests

```typescript
describe('ClientsService', () => {
  let service: ClientsService;
  let repository: MockType<ClientsRepository>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        ClientsService,
        { provide: ClientsRepository, useFactory: mockRepository },
      ],
    }).compile();

    service = module.get(ClientsService);
    repository = module.get(ClientsRepository);
  });

  describe('create', () => {
    it('should create a client with prospect status', async () => {
      const dto = { name: 'Test Client' };
      repository.create.mockResolvedValue({ id: '1', ...dto, status: 'prospect' });

      const result = await service.create('tenant-1', dto);

      expect(result.status).toBe('prospect');
      expect(repository.create).toHaveBeenCalledWith(expect.objectContaining({
        tenantId: 'tenant-1',
      }));
    });
  });
});
```

### E2E Tests

```typescript
describe('Clients (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    app = await createTestApp();
  });

  it('/crm/clients (POST)', () => {
    return request(app.getHttpServer())
      .post('/crm/clients')
      .set('Authorization', `Bearer ${token}`)
      .send({ name: 'Test Client' })
      .expect(201)
      .expect((res) => {
        expect(res.body.data.name).toBe('Test Client');
      });
  });
});
```

---

## Environment Variables

```bash
# .env.example

# App
NODE_ENV=development
PORT=3000
API_PREFIX=api/v1

# Database
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=pmc
DATABASE_USER=pmc_user
DATABASE_PASSWORD=secret

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379

# JWT
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=1d

# Storage
S3_ENDPOINT=http://localhost:9000
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET=pmc-assets

# ComfyUI
COMFYUI_URL=http://localhost:8188
COMFYUI_WEBHOOK_SECRET=webhook-secret

# OpenAI (for text generation)
OPENAI_API_KEY=sk-...
```

---

## Referencias

- [NestJS Documentation](https://docs.nestjs.com/)
- [TypeORM Documentation](https://typeorm.io/)
- [React Documentation](https://react.dev/)
- [TailwindCSS](https://tailwindcss.com/)

---

**Documento generado por:** Requirements-Analyst
**Fecha:** 2025-12-08
