474 lines
9.4 KiB
Markdown
474 lines
9.4 KiB
Markdown
# 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
|