feat: Add SaaS products architecture and alignment analysis
Analysis and Documentation: - Add ANALISIS-ALINEACION-WORKSPACE-2025-12-08.md with comprehensive gap analysis - Document SIMCO v3.2 system with 20+ directives - Identify alignment gaps between orchestration and projects New SaaS Products Structure: - Create apps/products/pos-micro/ - Ultra basic POS (~100 MXN/month) - Target: Mexican informal market (street vendors, small stores) - Features: Offline-first PWA, WhatsApp bot, minimal DB (~10 tables) - Create apps/products/erp-basico/ - Austere ERP (~300-500 MXN/month) - Target: SMBs needing full ERP without complexity - Features: Inherits from erp-core, modular pricing SaaS Layer: - Create apps/saas/ structure (billing, portal, admin, onboarding) - Add README.md and CONTEXTO-SAAS.md documentation Vertical Alignment: - Verify HERENCIA-ERP-CORE.md exists in all verticals - Add HERENCIA-SPECS-CORE.md to verticals - Update orchestration inventories Updates: - Update WORKSPACE-STATUS.md with new products and analysis - Update suite inventories with new structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d30fe4d644
commit
2781837d9e
@ -74,6 +74,29 @@ core/
|
||||
│ ├── CONTEXTO-NIVEL-SUITE-CORE.md # 🆕 Template para core de suite
|
||||
│ └── CONTEXTO-NIVEL-VERTICAL.md # 🆕 Template para verticales
|
||||
│
|
||||
├── patrones/ # PATRONES DE CÓDIGO
|
||||
│ ├── MAPEO-TIPOS-DDL-TYPESCRIPT.md # Mapeo PostgreSQL ↔ TypeScript
|
||||
│ ├── PATRON-VALIDACION.md # Validación con class-validator/Zod
|
||||
│ ├── PATRON-EXCEPTION-HANDLING.md # Manejo de errores y excepciones
|
||||
│ ├── PATRON-TESTING.md # Patrones de testing
|
||||
│ ├── PATRON-LOGGING.md # Logging estructurado
|
||||
│ ├── PATRON-CONFIGURACION.md # Variables de entorno y config
|
||||
│ ├── PATRON-SEGURIDAD.md # Seguridad y OWASP
|
||||
│ ├── PATRON-PERFORMANCE.md # Optimización y caching
|
||||
│ ├── PATRON-TRANSACCIONES.md # Transacciones de BD
|
||||
│ ├── ANTIPATRONES.md # Lo que NUNCA hacer
|
||||
│ └── NOMENCLATURA-UNIFICADA.md # Convenciones de nombres
|
||||
│
|
||||
├── impactos/ # IMPACTO DE CAMBIOS
|
||||
│ ├── IMPACTO-CAMBIOS-DDL.md # Cascada de cambios en BD
|
||||
│ ├── IMPACTO-CAMBIOS-BACKEND.md # Sincronización Backend↔Frontend
|
||||
│ ├── IMPACTO-CAMBIOS-ENTITY.md # Cambios en Entities TypeORM
|
||||
│ ├── IMPACTO-CAMBIOS-API.md # Cambios en endpoints REST
|
||||
│ └── MATRIZ-DEPENDENCIAS.md # Matriz completa de dependencias
|
||||
│
|
||||
├── procesos/ # PROCESOS DE TRABAJO
|
||||
│ └── ORDEN-IMPLEMENTACION.md # DDL-First, orden de capas
|
||||
│
|
||||
├── _historico/
|
||||
│ └── MAPA-CONTEXTO-AGENTE.md # Trazabilidad (histórico)
|
||||
│
|
||||
|
||||
669
core/orchestration/impactos/IMPACTO-CAMBIOS-API.md
Normal file
669
core/orchestration/impactos/IMPACTO-CAMBIOS-API.md
Normal file
@ -0,0 +1,669 @@
|
||||
# IMPACTO DE CAMBIOS EN API
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - Consultar antes de modificar endpoints
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Documentar el impacto de modificar endpoints REST (Controllers), rutas, metodos HTTP, y contratos de API en el sistema.
|
||||
|
||||
---
|
||||
|
||||
## 1. CLASIFICACION DE CAMBIOS API
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ TIPOS DE CAMBIOS EN API ║
|
||||
╠══════════════════════════════════════════════════════════════════════╣
|
||||
║ ║
|
||||
║ BREAKING CHANGES (⚠️ ROMPEN CLIENTES): ║
|
||||
║ • Eliminar endpoint ║
|
||||
║ • Cambiar ruta de endpoint ║
|
||||
║ • Cambiar metodo HTTP ║
|
||||
║ • Eliminar campo de response ║
|
||||
║ • Cambiar tipo de campo en response ║
|
||||
║ • Agregar campo requerido a request ║
|
||||
║ • Cambiar formato de error ║
|
||||
║ ║
|
||||
║ NON-BREAKING CHANGES (✅ COMPATIBLES): ║
|
||||
║ • Agregar endpoint nuevo ║
|
||||
║ • Agregar campo opcional a request ║
|
||||
║ • Agregar campo a response ║
|
||||
║ • Agregar nuevo codigo de error ║
|
||||
║ • Mejorar mensaje de error ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. CAMBIOS EN RUTAS (BREAKING)
|
||||
|
||||
### 2.1 Cambiar Ruta de Endpoint
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
@Get('users/by-email/:email')
|
||||
|
||||
// DESPUES
|
||||
@Get('users/search/email/:email')
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Componente | Impacto | Accion Requerida |
|
||||
|------------|---------|------------------|
|
||||
| Frontend Service | ROMPE | Actualizar URL |
|
||||
| Frontend Hooks | ROMPE | Actualizar llamadas |
|
||||
| Documentacion | Desactualizada | Actualizar |
|
||||
| Clientes externos | ROMPE | Notificar + migrar |
|
||||
| Tests e2e | ROMPE | Actualizar URLs |
|
||||
|
||||
**Proceso de Migracion:**
|
||||
|
||||
```
|
||||
OPCION A: Migracion Inmediata (proyectos internos)
|
||||
═══════════════════════════════════════════════════════════════
|
||||
1. Actualizar Frontend primero (cambiar URL)
|
||||
2. Actualizar Backend (cambiar ruta)
|
||||
3. Deploy coordinado
|
||||
|
||||
OPCION B: Deprecacion Gradual (APIs publicas)
|
||||
═══════════════════════════════════════════════════════════════
|
||||
1. Agregar nueva ruta (mantener vieja)
|
||||
2. Marcar vieja como @Deprecated
|
||||
3. Notificar clientes
|
||||
4. Periodo de gracia (30-90 dias)
|
||||
5. Eliminar ruta vieja
|
||||
```
|
||||
|
||||
**Implementacion Deprecacion:**
|
||||
|
||||
```typescript
|
||||
// Mantener ambas rutas temporalmente
|
||||
@Get('users/by-email/:email')
|
||||
@ApiOperation({
|
||||
summary: 'Buscar por email (DEPRECATED)',
|
||||
deprecated: true,
|
||||
description: 'Use GET /users/search/email/:email instead'
|
||||
})
|
||||
async findByEmailDeprecated(@Param('email') email: string) {
|
||||
return this.findByEmail(email);
|
||||
}
|
||||
|
||||
@Get('users/search/email/:email')
|
||||
@ApiOperation({ summary: 'Buscar usuario por email' })
|
||||
async findByEmail(@Param('email') email: string) {
|
||||
return this.userService.findByEmail(email);
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Cambiar Prefijo de Controller
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
@Controller('products')
|
||||
|
||||
// DESPUES
|
||||
@Controller('catalog/products')
|
||||
```
|
||||
|
||||
**Impacto:** TODOS los endpoints del controller cambian de ruta.
|
||||
|
||||
```
|
||||
ANTES:
|
||||
GET /api/products
|
||||
POST /api/products
|
||||
GET /api/products/:id
|
||||
|
||||
DESPUES:
|
||||
GET /api/catalog/products
|
||||
POST /api/catalog/products
|
||||
GET /api/catalog/products/:id
|
||||
```
|
||||
|
||||
**Checklist:**
|
||||
|
||||
```
|
||||
[ ] 1. Buscar en frontend: grep -rn "/products" apps/
|
||||
[ ] 2. Actualizar TODOS los services que usan estos endpoints
|
||||
[ ] 3. Actualizar tests e2e
|
||||
[ ] 4. Actualizar documentacion
|
||||
[ ] 5. Verificar Swagger refleja cambios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. CAMBIOS EN METODO HTTP (BREAKING)
|
||||
|
||||
### Cambiar Metodo
|
||||
|
||||
```typescript
|
||||
// ANTES: Actualizar con POST
|
||||
@Post(':id/update')
|
||||
async update() {}
|
||||
|
||||
// DESPUES: Actualizar con PUT (RESTful correcto)
|
||||
@Put(':id')
|
||||
async update() {}
|
||||
```
|
||||
|
||||
**Impacto Frontend:**
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
const response = await api.post(`/users/${id}/update`, data);
|
||||
|
||||
// DESPUES
|
||||
const response = await api.put(`/users/${id}`, data);
|
||||
```
|
||||
|
||||
**Checklist:**
|
||||
|
||||
```
|
||||
[ ] 1. Identificar todos los lugares que llaman al endpoint
|
||||
[ ] 2. Actualizar metodo HTTP en frontend service
|
||||
[ ] 3. Actualizar tests
|
||||
[ ] 4. Si API publica: periodo de deprecacion
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. CAMBIOS EN REQUEST DTO
|
||||
|
||||
### 4.1 Agregar Campo Requerido (BREAKING)
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
export class CreateOrderDto {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
// DESPUES - Nuevo campo requerido
|
||||
export class CreateOrderDto {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
@IsNotEmpty()
|
||||
deliveryAddress: string; // ← BREAKING: requerido
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Componente | Impacto | Accion |
|
||||
|------------|---------|--------|
|
||||
| Frontend Form | ROMPE | Agregar campo |
|
||||
| Frontend Zod | ROMPE | Agregar validacion |
|
||||
| Clientes existentes | ROMPE | Falla validacion |
|
||||
| Tests | ROMPE | Actualizar fixtures |
|
||||
|
||||
**Mitigacion:**
|
||||
|
||||
```typescript
|
||||
// OPCION 1: Hacer opcional con default
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
deliveryAddress?: string = 'Por definir';
|
||||
|
||||
// OPCION 2: Migrar en fases
|
||||
// Fase 1: Agregar como opcional
|
||||
// Fase 2: Poblar datos existentes
|
||||
// Fase 3: Hacer requerido
|
||||
```
|
||||
|
||||
### 4.2 Agregar Campo Opcional (NON-BREAKING)
|
||||
|
||||
```typescript
|
||||
// Agregar campo opcional - NO rompe
|
||||
export class CreateOrderDto {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string; // ← NON-BREAKING: opcional
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Componente | Impacto | Accion |
|
||||
|------------|---------|--------|
|
||||
| Frontend | Ninguno inmediato | Agregar si se quiere usar |
|
||||
| Clientes existentes | Ninguno | Siguen funcionando |
|
||||
| Tests | Ninguno | Siguen pasando |
|
||||
|
||||
### 4.3 Eliminar Campo de Request (BREAKING si requerido)
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
export class CreateUserDto {
|
||||
email: string;
|
||||
password: string;
|
||||
legacyCode: string; // ← Se va a eliminar
|
||||
}
|
||||
|
||||
// DESPUES
|
||||
export class CreateUserDto {
|
||||
email: string;
|
||||
password: string;
|
||||
// legacyCode eliminado
|
||||
}
|
||||
```
|
||||
|
||||
**Proceso:**
|
||||
|
||||
```
|
||||
1. Hacer campo opcional primero (si era requerido)
|
||||
2. Marcar como @Deprecated en Swagger
|
||||
3. Ignorar en backend (no procesar)
|
||||
4. Notificar clientes
|
||||
5. Eliminar despues de periodo de gracia
|
||||
```
|
||||
|
||||
### 4.4 Cambiar Validacion (Puede ser BREAKING)
|
||||
|
||||
```typescript
|
||||
// ANTES: email cualquier formato
|
||||
@IsString()
|
||||
email: string;
|
||||
|
||||
// DESPUES: email debe ser valido
|
||||
@IsEmail()
|
||||
email: string;
|
||||
```
|
||||
|
||||
**Impacto:** Requests que antes pasaban ahora fallan.
|
||||
|
||||
**Mitigacion:**
|
||||
|
||||
```typescript
|
||||
// Agregar transformacion para casos edge
|
||||
@IsEmail()
|
||||
@Transform(({ value }) => value?.toLowerCase().trim())
|
||||
email: string;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. CAMBIOS EN RESPONSE DTO
|
||||
|
||||
### 5.1 Eliminar Campo de Response (BREAKING)
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
export class UserResponseDto {
|
||||
id: string;
|
||||
email: string;
|
||||
legacyCode: string; // ← Se elimina
|
||||
}
|
||||
|
||||
// DESPUES
|
||||
export class UserResponseDto {
|
||||
id: string;
|
||||
email: string;
|
||||
// legacyCode eliminado
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto Frontend:**
|
||||
|
||||
```typescript
|
||||
// Si frontend usa el campo, ROMPE
|
||||
const UserCard = ({ user }) => {
|
||||
return <div>{user.legacyCode}</div>; // ← TypeError: undefined
|
||||
};
|
||||
```
|
||||
|
||||
**Proceso Seguro:**
|
||||
|
||||
```
|
||||
1. Buscar uso en frontend: grep -rn "legacyCode" apps/
|
||||
2. Eliminar uso en frontend primero
|
||||
3. Luego eliminar de ResponseDto
|
||||
4. Deploy coordinado
|
||||
```
|
||||
|
||||
### 5.2 Agregar Campo a Response (NON-BREAKING)
|
||||
|
||||
```typescript
|
||||
// Agregar campo - NO rompe
|
||||
export class UserResponseDto {
|
||||
id: string;
|
||||
email: string;
|
||||
createdAt: Date; // ← Nuevo campo
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
- Frontend puede ignorar campos nuevos
|
||||
- TypeScript mostrara warning si interface no coincide
|
||||
- Actualizar interface en frontend eventualmente
|
||||
|
||||
### 5.3 Cambiar Tipo de Campo (BREAKING)
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
export class ProductResponseDto {
|
||||
price: number; // 99
|
||||
}
|
||||
|
||||
// DESPUES
|
||||
export class ProductResponseDto {
|
||||
price: string; // "99.00"
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto Frontend:**
|
||||
|
||||
```typescript
|
||||
// ANTES funcionaba
|
||||
const total = product.price * quantity;
|
||||
|
||||
// DESPUES rompe (string * number = NaN)
|
||||
const total = product.price * quantity; // NaN!
|
||||
|
||||
// Debe cambiar a
|
||||
const total = parseFloat(product.price) * quantity;
|
||||
```
|
||||
|
||||
### 5.4 Cambiar Estructura de Response (BREAKING)
|
||||
|
||||
```typescript
|
||||
// ANTES: Array directo
|
||||
@Get()
|
||||
async findAll(): Promise<UserResponseDto[]> {
|
||||
return this.users;
|
||||
}
|
||||
// Response: [{ id: 1 }, { id: 2 }]
|
||||
|
||||
// DESPUES: Objeto paginado
|
||||
@Get()
|
||||
async findAll(): Promise<PaginatedResponse<UserResponseDto>> {
|
||||
return { data: this.users, total: 100, page: 1 };
|
||||
}
|
||||
// Response: { data: [...], total: 100, page: 1 }
|
||||
```
|
||||
|
||||
**Impacto Frontend:**
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
const users = await api.get('/users');
|
||||
users.map(u => ...);
|
||||
|
||||
// DESPUES
|
||||
const response = await api.get('/users');
|
||||
response.data.map(u => ...); // Acceder a .data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. CAMBIOS EN CODIGOS DE ERROR
|
||||
|
||||
### 6.1 Agregar Nuevo Codigo (NON-BREAKING)
|
||||
|
||||
```typescript
|
||||
// Agregar nueva excepcion - generalmente no rompe
|
||||
throw new ConflictException('Email already registered');
|
||||
// HTTP 409 - nuevo codigo
|
||||
```
|
||||
|
||||
**Impacto:** Frontend deberia manejar, pero no rompe si no lo hace.
|
||||
|
||||
### 6.2 Cambiar Codigo Existente (BREAKING)
|
||||
|
||||
```typescript
|
||||
// ANTES: 400 para duplicado
|
||||
throw new BadRequestException('Email exists');
|
||||
|
||||
// DESPUES: 409 para duplicado (correcto)
|
||||
throw new ConflictException('Email exists');
|
||||
```
|
||||
|
||||
**Impacto:** Frontend que verifica status code especifico rompe.
|
||||
|
||||
```typescript
|
||||
// Frontend que rompe
|
||||
if (error.status === 400 && error.message.includes('Email')) {
|
||||
// Ya no entra aqui
|
||||
}
|
||||
|
||||
// Frontend robusto
|
||||
if (error.status === 409 || error.message.includes('Email')) {
|
||||
// Maneja ambos casos
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Cambiar Formato de Error (BREAKING)
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
{
|
||||
"statusCode": 400,
|
||||
"message": "Validation failed"
|
||||
}
|
||||
|
||||
// DESPUES
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Validation failed",
|
||||
"details": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto:** TODO el manejo de errores en frontend debe cambiar.
|
||||
|
||||
---
|
||||
|
||||
## 7. VERSIONADO DE API
|
||||
|
||||
### Cuando Usar Versionado
|
||||
|
||||
```
|
||||
USA VERSIONADO CUANDO:
|
||||
• API es consumida por clientes externos
|
||||
• Cambios breaking frecuentes
|
||||
• Necesitas mantener compatibilidad largo plazo
|
||||
|
||||
NO NECESITAS VERSIONADO CUANDO:
|
||||
• Solo frontend interno consume la API
|
||||
• Puedes coordinar deploys
|
||||
• Equipo pequeno con comunicacion directa
|
||||
```
|
||||
|
||||
### Implementacion de Versiones
|
||||
|
||||
```typescript
|
||||
// Opcion 1: En URL
|
||||
@Controller('v1/users')
|
||||
export class UsersV1Controller {}
|
||||
|
||||
@Controller('v2/users')
|
||||
export class UsersV2Controller {}
|
||||
|
||||
// Opcion 2: En Header
|
||||
@Controller('users')
|
||||
@ApiHeader({ name: 'Api-Version', enum: ['1', '2'] })
|
||||
export class UsersController {
|
||||
@Get()
|
||||
findAll(@Headers('Api-Version') version: string) {
|
||||
if (version === '2') {
|
||||
return this.findAllV2();
|
||||
}
|
||||
return this.findAllV1();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. CHECKLIST POR TIPO DE CAMBIO
|
||||
|
||||
### Agregar Endpoint Nuevo
|
||||
|
||||
```
|
||||
[ ] 1. Crear metodo en Controller
|
||||
[ ] 2. Agregar decoradores Swagger (@ApiOperation, @ApiResponse)
|
||||
[ ] 3. Crear/actualizar DTOs si necesario
|
||||
[ ] 4. Implementar en Service
|
||||
[ ] 5. Agregar tests
|
||||
[ ] 6. Actualizar Frontend service
|
||||
[ ] 7. Crear Frontend hook si necesario
|
||||
[ ] 8. Verificar Swagger
|
||||
```
|
||||
|
||||
### Modificar Endpoint Existente (NON-BREAKING)
|
||||
|
||||
```
|
||||
[ ] 1. Verificar que cambio es non-breaking
|
||||
[ ] 2. Actualizar Controller
|
||||
[ ] 3. Actualizar DTOs si necesario
|
||||
[ ] 4. Actualizar Swagger decoradores
|
||||
[ ] 5. Actualizar tests
|
||||
[ ] 6. Actualizar Frontend si aprovecha nuevas features
|
||||
```
|
||||
|
||||
### Modificar Endpoint Existente (BREAKING)
|
||||
|
||||
```
|
||||
[ ] 1. Evaluar: ¿se puede hacer non-breaking?
|
||||
[ ] 2. Buscar todos los consumidores: grep -rn "endpoint" apps/
|
||||
[ ] 3. Planear estrategia de migracion
|
||||
[ ] 4. Si API publica: crear version nueva, deprecar vieja
|
||||
[ ] 5. Si API interna: coordinar con frontend
|
||||
[ ] 6. Actualizar Frontend PRIMERO (apuntar a nuevo)
|
||||
[ ] 7. Actualizar Backend
|
||||
[ ] 8. Actualizar tests
|
||||
[ ] 9. Deploy coordinado
|
||||
[ ] 10. Eliminar codigo deprecated despues de periodo
|
||||
```
|
||||
|
||||
### Eliminar Endpoint
|
||||
|
||||
```
|
||||
[ ] 1. Buscar todos los usos: grep -rn "endpoint" apps/ tests/
|
||||
[ ] 2. Eliminar uso en Frontend
|
||||
[ ] 3. Eliminar tests del endpoint
|
||||
[ ] 4. Marcar como @Deprecated (si API publica)
|
||||
[ ] 5. Periodo de gracia
|
||||
[ ] 6. Eliminar de Controller
|
||||
[ ] 7. Limpiar Service si metodos quedan sin usar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. FRONTEND: ADAPTARSE A CAMBIOS API
|
||||
|
||||
### Service Layer Pattern
|
||||
|
||||
```typescript
|
||||
// Encapsular llamadas API en service
|
||||
// Facilita cambiar cuando API cambia
|
||||
|
||||
// user.service.ts
|
||||
export const userService = {
|
||||
// Si cambia la ruta, solo cambiar aqui
|
||||
getAll: () => api.get<User[]>('/users'),
|
||||
|
||||
// Si cambia estructura response
|
||||
getAllPaginated: async (page: number) => {
|
||||
const response = await api.get<PaginatedResponse<User>>('/users', { params: { page } });
|
||||
return response.data; // Extraer data aqui
|
||||
},
|
||||
|
||||
// Si cambia metodo HTTP
|
||||
update: (id: string, data: UpdateUserDto) =>
|
||||
api.put<User>(`/users/${id}`, data), // Cambiar post→put aqui
|
||||
};
|
||||
```
|
||||
|
||||
### Type Guards para Cambios de Estructura
|
||||
|
||||
```typescript
|
||||
// Manejar diferentes versiones de response
|
||||
interface UserV1 {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UserV2 {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
function isUserV2(user: UserV1 | UserV2): user is UserV2 {
|
||||
return 'firstName' in user;
|
||||
}
|
||||
|
||||
function getDisplayName(user: UserV1 | UserV2): string {
|
||||
if (isUserV2(user)) {
|
||||
return `${user.firstName} ${user.lastName}`;
|
||||
}
|
||||
return user.name;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. MATRIZ DE DECISION
|
||||
|
||||
```
|
||||
¿Es BREAKING CHANGE?
|
||||
│
|
||||
▼
|
||||
┌────────────┐
|
||||
│ ¿Cambio de │───NO───▶ Implementar directamente
|
||||
│ ruta? │ Actualizar Swagger
|
||||
└────────────┘ Actualizar Frontend (opcional)
|
||||
│
|
||||
YES
|
||||
│
|
||||
▼
|
||||
┌────────────┐
|
||||
│ ¿API │───NO───▶ Actualizar Frontend PRIMERO
|
||||
│ publica? │ Luego actualizar Backend
|
||||
└────────────┘ Deploy coordinado
|
||||
│
|
||||
YES
|
||||
│
|
||||
▼
|
||||
┌────────────────────────────────────────┐
|
||||
│ 1. Crear endpoint nuevo │
|
||||
│ 2. Deprecar endpoint viejo │
|
||||
│ 3. Notificar clientes │
|
||||
│ 4. Periodo de gracia (30-90 dias) │
|
||||
│ 5. Eliminar endpoint viejo │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. COMANDOS UTILES
|
||||
|
||||
```bash
|
||||
# Ver todos los endpoints actuales
|
||||
curl http://localhost:3000/api-json | jq '.paths | keys'
|
||||
|
||||
# Ver detalle de un endpoint
|
||||
curl http://localhost:3000/api-json | jq '.paths["/users/{id}"]'
|
||||
|
||||
# Buscar uso de endpoint en frontend
|
||||
grep -rn "users" apps/*/services/
|
||||
grep -rn "/api/users" apps/
|
||||
|
||||
# Comparar Swagger entre versiones
|
||||
diff <(curl -s http://localhost:3000/api-json | jq -S) \
|
||||
<(curl -s http://production/api-json | jq -S)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guia de Impacto
|
||||
498
core/orchestration/impactos/IMPACTO-CAMBIOS-BACKEND.md
Normal file
498
core/orchestration/impactos/IMPACTO-CAMBIOS-BACKEND.md
Normal file
@ -0,0 +1,498 @@
|
||||
# IMPACTO DE CAMBIOS EN BACKEND
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - Consultar antes de modificar Backend
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Documentar la cascada de cambios que ocurre cuando se modifica cualquier componente del Backend (Entity, DTO, Service, Controller) y su impacto en Frontend.
|
||||
|
||||
---
|
||||
|
||||
## 1. MATRIZ DE IMPACTO POR COMPONENTE
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ CAMBIO EN BACKEND → IMPACTO EN CAPAS │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ENTITY DTO SERVICE CONTROLLER FRONTEND │
|
||||
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
|
||||
│ │ │──────▶│ │───────▶│ │───────▶│ │───────▶│ │ │
|
||||
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
|
||||
│ │ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ Refleja Valida Logica Endpoints Types │
|
||||
│ DDL Input Negocio REST Zod │
|
||||
│ Hooks │
|
||||
│ Components │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. CAMBIOS EN ENTITY
|
||||
|
||||
### 2.1 Agregar Campo a Entity
|
||||
|
||||
```typescript
|
||||
// CAMBIO: Agregar campo 'phone' a UserEntity
|
||||
@Column({ type: 'varchar', length: 20, nullable: true })
|
||||
phone: string | null;
|
||||
```
|
||||
|
||||
**Cascada de Cambios:**
|
||||
|
||||
| Capa | Archivo | Accion | Obligatorio |
|
||||
|------|---------|--------|-------------|
|
||||
| DDL | `XX-users.sql` | Agregar columna (DDL-First) | SI - PRIMERO |
|
||||
| Entity | `user.entity.ts` | Ya agregado | SI |
|
||||
| CreateDto | `create-user.dto.ts` | Agregar campo + validacion | SI |
|
||||
| UpdateDto | `update-user.dto.ts` | Hereda de CreateDto | AUTO |
|
||||
| ResponseDto | `user-response.dto.ts` | Agregar campo | SI |
|
||||
| Service | `user.service.ts` | Sin cambios (TypeORM maneja) | NO |
|
||||
| Controller | `user.controller.ts` | Sin cambios | NO |
|
||||
| **Frontend** | `user.types.ts` | Agregar campo a interface | SI |
|
||||
| **Frontend** | `user.schema.ts` | Agregar a Zod schema | SI |
|
||||
| **Frontend** | `UserForm.tsx` | Agregar input si editable | CONDICIONAL |
|
||||
|
||||
**Checklist:**
|
||||
|
||||
```
|
||||
[ ] 1. DDL tiene la columna (DDL-First)
|
||||
[ ] 2. Entity refleja DDL exactamente
|
||||
[ ] 3. CreateDto tiene validacion apropiada
|
||||
[ ] 4. ResponseDto incluye el campo
|
||||
[ ] 5. Swagger muestra campo correctamente
|
||||
[ ] 6. Frontend types actualizados
|
||||
[ ] 7. Frontend Zod schema actualizado
|
||||
[ ] 8. Componentes de formulario actualizados (si aplica)
|
||||
[ ] 9. npm run build (backend)
|
||||
[ ] 10. npm run build (frontend)
|
||||
[ ] 11. npm run typecheck (frontend)
|
||||
```
|
||||
|
||||
### 2.2 Eliminar Campo de Entity
|
||||
|
||||
```typescript
|
||||
// CAMBIO: Eliminar campo 'legacyCode' de ProductEntity
|
||||
// @Column() legacyCode: string; // ELIMINADO
|
||||
```
|
||||
|
||||
**Cascada de Cambios:**
|
||||
|
||||
| Capa | Archivo | Accion | Obligatorio |
|
||||
|------|---------|--------|-------------|
|
||||
| DDL | `XX-products.sql` | DROP COLUMN (con migracion) | SI - PRIMERO |
|
||||
| Entity | `product.entity.ts` | Eliminar @Column | SI |
|
||||
| CreateDto | `create-product.dto.ts` | Eliminar campo | SI |
|
||||
| UpdateDto | `update-product.dto.ts` | Hereda cambio | AUTO |
|
||||
| ResponseDto | `product-response.dto.ts` | Eliminar campo | SI |
|
||||
| Service | `product.service.ts` | Eliminar referencias | VERIFICAR |
|
||||
| Controller | `product.controller.ts` | Eliminar de queries | VERIFICAR |
|
||||
| **Frontend** | `product.types.ts` | Eliminar de interface | SI |
|
||||
| **Frontend** | `product.schema.ts` | Eliminar de Zod | SI |
|
||||
| **Frontend** | Componentes | Eliminar referencias | SI |
|
||||
|
||||
**ADVERTENCIA:**
|
||||
|
||||
```
|
||||
⚠️ ANTES DE ELIMINAR UN CAMPO:
|
||||
1. Buscar TODAS las referencias en backend: grep -r "legacyCode" src/
|
||||
2. Buscar TODAS las referencias en frontend: grep -r "legacyCode" apps/
|
||||
3. Verificar que no hay datos importantes en la columna
|
||||
4. Considerar soft-delete o campo deprecated primero
|
||||
```
|
||||
|
||||
### 2.3 Cambiar Tipo de Campo
|
||||
|
||||
```typescript
|
||||
// CAMBIO: price de number a Decimal
|
||||
// ANTES
|
||||
@Column({ type: 'integer' })
|
||||
price: number;
|
||||
|
||||
// DESPUES
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
price: string; // String para precision decimal
|
||||
```
|
||||
|
||||
**Cascada de Cambios:**
|
||||
|
||||
| Capa | Archivo | Accion | Obligatorio |
|
||||
|------|---------|--------|-------------|
|
||||
| DDL | `XX-products.sql` | ALTER COLUMN type | SI - PRIMERO |
|
||||
| Entity | `product.entity.ts` | Cambiar tipo TS | SI |
|
||||
| CreateDto | `create-product.dto.ts` | Cambiar validador | SI |
|
||||
| ResponseDto | `product-response.dto.ts` | Cambiar tipo | SI |
|
||||
| Service | `product.service.ts` | Ajustar transformaciones | VERIFICAR |
|
||||
| **Frontend** | `product.types.ts` | Cambiar tipo | SI |
|
||||
| **Frontend** | `product.schema.ts` | Cambiar validador Zod | SI |
|
||||
| **Frontend** | Componentes | Ajustar formateo/parsing | SI |
|
||||
|
||||
---
|
||||
|
||||
## 3. CAMBIOS EN DTO
|
||||
|
||||
### 3.1 Agregar Validacion a DTO
|
||||
|
||||
```typescript
|
||||
// CAMBIO: Agregar validacion de formato a email
|
||||
@IsEmail({}, { message: 'Email invalido' })
|
||||
@MaxLength(255)
|
||||
@Transform(({ value }) => value?.toLowerCase().trim())
|
||||
email: string;
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Capa | Impacto | Accion Requerida |
|
||||
|------|---------|------------------|
|
||||
| Entity | Ninguno | - |
|
||||
| Service | Ninguno | - |
|
||||
| Controller | Ninguno (validacion automatica) | - |
|
||||
| Swagger | Actualiza automaticamente | Verificar |
|
||||
| **Frontend** | Debe sincronizar validacion | Actualizar Zod |
|
||||
|
||||
**Frontend debe reflejar:**
|
||||
|
||||
```typescript
|
||||
// Zod schema debe coincidir con backend
|
||||
const userSchema = z.object({
|
||||
email: z.string()
|
||||
.email('Email invalido')
|
||||
.max(255)
|
||||
.transform(v => v.toLowerCase().trim()),
|
||||
});
|
||||
```
|
||||
|
||||
### 3.2 Agregar Campo a ResponseDto
|
||||
|
||||
```typescript
|
||||
// CAMBIO: Agregar campo calculado 'fullName'
|
||||
@ApiProperty({ example: 'Juan Perez' })
|
||||
fullName: string;
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Capa | Impacto | Accion Requerida |
|
||||
|------|---------|------------------|
|
||||
| Entity | Ninguno (campo calculado) | - |
|
||||
| Service | Debe calcular el campo | Agregar logica |
|
||||
| Controller | Ninguno | - |
|
||||
| **Frontend** | Debe tipar el campo | Actualizar interface |
|
||||
|
||||
**Service debe mapear:**
|
||||
|
||||
```typescript
|
||||
// En service
|
||||
toResponseDto(entity: UserEntity): UserResponseDto {
|
||||
return {
|
||||
...entity,
|
||||
fullName: `${entity.firstName} ${entity.lastName}`,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. CAMBIOS EN SERVICE
|
||||
|
||||
### 4.1 Agregar Metodo al Service
|
||||
|
||||
```typescript
|
||||
// CAMBIO: Agregar metodo de busqueda avanzada
|
||||
async searchByFilters(filters: SearchFiltersDto): Promise<UserEntity[]> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Capa | Impacto | Accion Requerida |
|
||||
|------|---------|------------------|
|
||||
| Entity | Ninguno | - |
|
||||
| DTO | Crear SearchFiltersDto | SI |
|
||||
| Controller | Exponer endpoint | SI |
|
||||
| **Frontend** | Crear servicio + hook | SI |
|
||||
|
||||
### 4.2 Modificar Logica de Negocio
|
||||
|
||||
```typescript
|
||||
// CAMBIO: Agregar validacion de negocio en create
|
||||
async create(dto: CreateOrderDto): Promise<OrderEntity> {
|
||||
// NUEVA: Validar stock antes de crear
|
||||
await this.validateStock(dto.items);
|
||||
// ... resto
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Capa | Impacto | Accion Requerida |
|
||||
|------|---------|------------------|
|
||||
| Entity | Ninguno | - |
|
||||
| DTO | Posible nuevo error response | Documentar |
|
||||
| Controller | Ninguno | - |
|
||||
| Swagger | Documentar nuevo error | SI |
|
||||
| **Frontend** | Manejar nuevo error | SI |
|
||||
|
||||
**Frontend debe manejar:**
|
||||
|
||||
```typescript
|
||||
// Hook debe manejar error especifico
|
||||
const { mutate } = useCreateOrder({
|
||||
onError: (error) => {
|
||||
if (error.message.includes('stock')) {
|
||||
toast.error('Stock insuficiente');
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. CAMBIOS EN CONTROLLER
|
||||
|
||||
### 5.1 Agregar Endpoint
|
||||
|
||||
```typescript
|
||||
// CAMBIO: Agregar endpoint de exportacion
|
||||
@Get('export')
|
||||
@ApiOperation({ summary: 'Exportar usuarios a CSV' })
|
||||
async exportToCsv(): Promise<StreamableFile> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Impacto Frontend:**
|
||||
|
||||
| Componente | Accion Requerida |
|
||||
|------------|------------------|
|
||||
| `user.service.ts` | Agregar metodo `exportToCsv()` |
|
||||
| `useExportUsers.ts` | Crear hook (si necesario) |
|
||||
| Componente UI | Agregar boton de exportar |
|
||||
|
||||
### 5.2 Modificar Ruta de Endpoint
|
||||
|
||||
```typescript
|
||||
// CAMBIO: Renombrar endpoint
|
||||
// ANTES: @Get('by-email/:email')
|
||||
// DESPUES: @Get('search/email/:email')
|
||||
```
|
||||
|
||||
**ADVERTENCIA - BREAKING CHANGE:**
|
||||
|
||||
```
|
||||
⚠️ CAMBIO DE RUTA ES BREAKING CHANGE
|
||||
|
||||
PROCESO OBLIGATORIO:
|
||||
1. Verificar que frontend no usa la ruta antigua
|
||||
2. Si usa: Actualizar frontend PRIMERO
|
||||
3. Considerar: Mantener ruta antigua con @Deprecated
|
||||
4. Documentar en CHANGELOG
|
||||
|
||||
BUSCAR EN FRONTEND:
|
||||
grep -r "by-email" apps/
|
||||
grep -r "users/by-email" apps/
|
||||
```
|
||||
|
||||
### 5.3 Cambiar Metodo HTTP
|
||||
|
||||
```typescript
|
||||
// CAMBIO: De POST a PUT para update
|
||||
// ANTES: @Post(':id/update')
|
||||
// DESPUES: @Put(':id')
|
||||
```
|
||||
|
||||
**Impacto Frontend:**
|
||||
|
||||
```typescript
|
||||
// ANTES
|
||||
await api.post(`/users/${id}/update`, data);
|
||||
|
||||
// DESPUES
|
||||
await api.put(`/users/${id}`, data);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. SINCRONIZACION BACKEND-FRONTEND
|
||||
|
||||
### 6.1 Mapeo de Tipos
|
||||
|
||||
| Backend (NestJS) | Frontend (TypeScript) | Notas |
|
||||
|------------------|----------------------|-------|
|
||||
| `string` | `string` | Directo |
|
||||
| `number` | `number` | Directo |
|
||||
| `boolean` | `boolean` | Directo |
|
||||
| `Date` | `string` | ISO string en JSON |
|
||||
| `Decimal` (string) | `string` o `number` | Parsear si necesario |
|
||||
| `UUID` | `string` | Directo |
|
||||
| `enum Status` | `type Status = ...` | Replicar valores |
|
||||
| `T[]` | `T[]` | Directo |
|
||||
| `T \| null` | `T \| null` | Directo |
|
||||
|
||||
### 6.2 Patron de Sincronizacion
|
||||
|
||||
```typescript
|
||||
// BACKEND: ResponseDto define contrato
|
||||
export class UserResponseDto {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ enum: UserStatus })
|
||||
status: UserStatus;
|
||||
|
||||
@ApiProperty()
|
||||
createdAt: Date; // Se serializa como ISO string
|
||||
}
|
||||
|
||||
// FRONTEND: Interface refleja ResponseDto
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
status: UserStatus;
|
||||
createdAt: string; // String porque viene de JSON
|
||||
}
|
||||
|
||||
// FRONTEND: Enum replicado
|
||||
export enum UserStatus {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
}
|
||||
|
||||
// FRONTEND: Zod refleja CreateDto
|
||||
export const createUserSchema = z.object({
|
||||
email: z.string().email(),
|
||||
// ... campos de CreateUserDto
|
||||
});
|
||||
```
|
||||
|
||||
### 6.3 Checklist de Sincronizacion
|
||||
|
||||
```
|
||||
Cuando cambias Backend, verificar en Frontend:
|
||||
|
||||
[ ] 1. Interfaces actualizadas (types/*.ts)
|
||||
[ ] 2. Enums sincronizados
|
||||
[ ] 3. Zod schemas actualizados
|
||||
[ ] 4. Services API actualizados
|
||||
[ ] 5. Hooks actualizados (si cambia endpoint)
|
||||
[ ] 6. Componentes manejan nuevos campos
|
||||
[ ] 7. Componentes manejan campos eliminados
|
||||
[ ] 8. Manejo de errores actualizado
|
||||
[ ] 9. npm run typecheck pasa
|
||||
[ ] 10. npm run build pasa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. COMANDOS DE VERIFICACION
|
||||
|
||||
```bash
|
||||
# Buscar referencias a campo en backend
|
||||
grep -rn "fieldName" src/
|
||||
|
||||
# Buscar referencias a campo en frontend
|
||||
grep -rn "fieldName" apps/ shared/
|
||||
|
||||
# Verificar tipos TypeScript
|
||||
npm run typecheck
|
||||
|
||||
# Verificar que Swagger refleja cambios
|
||||
curl http://localhost:3000/api-json | jq '.paths["/users"]'
|
||||
|
||||
# Comparar DTO con Interface
|
||||
diff <(grep -A 50 "class UserResponseDto" src/modules/user/dto/user-response.dto.ts) \
|
||||
<(grep -A 50 "interface User" apps/web/types/user.types.ts)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. ANTI-PATRONES
|
||||
|
||||
### Anti-Patron 1: Cambiar Backend sin Frontend
|
||||
|
||||
```
|
||||
❌ INCORRECTO:
|
||||
1. Backend-Agent agrega campo a ResponseDto
|
||||
2. Se hace deploy
|
||||
3. Frontend falla porque no espera el campo (usualmente OK)
|
||||
4. O peor: Frontend espera campo que ya no existe (ROMPE)
|
||||
|
||||
✅ CORRECTO:
|
||||
1. Backend-Agent agrega campo
|
||||
2. Frontend-Agent actualiza types ANTES de deploy
|
||||
3. Deploy coordinado
|
||||
```
|
||||
|
||||
### Anti-Patron 2: Tipos Diferentes
|
||||
|
||||
```
|
||||
❌ INCORRECTO:
|
||||
// Backend
|
||||
@Column({ type: 'decimal' })
|
||||
price: string; // String para precision
|
||||
|
||||
// Frontend
|
||||
interface Product {
|
||||
price: number; // ← INCORRECTO: tipo diferente
|
||||
}
|
||||
|
||||
✅ CORRECTO:
|
||||
// Frontend
|
||||
interface Product {
|
||||
price: string; // String como backend
|
||||
}
|
||||
|
||||
// O usar transformacion explicita
|
||||
const displayPrice = parseFloat(product.price).toFixed(2);
|
||||
```
|
||||
|
||||
### Anti-Patron 3: Validaciones Desincronizadas
|
||||
|
||||
```
|
||||
❌ INCORRECTO:
|
||||
// Backend: max 100 caracteres
|
||||
@MaxLength(100)
|
||||
name: string;
|
||||
|
||||
// Frontend: max 50 caracteres
|
||||
const schema = z.object({
|
||||
name: z.string().max(50) // ← INCORRECTO: limite diferente
|
||||
});
|
||||
|
||||
✅ CORRECTO:
|
||||
// Mismos limites en ambos lados
|
||||
// Backend: @MaxLength(100)
|
||||
// Frontend: z.string().max(100)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. MATRIZ RESUMEN
|
||||
|
||||
| Si Cambias... | Actualiza en Backend | Actualiza en Frontend |
|
||||
|---------------|---------------------|----------------------|
|
||||
| Entity campo | DTO, posible Service | Types, Schema, Components |
|
||||
| Entity relacion | DTO, Service | Types, Hooks |
|
||||
| CreateDto campo | - | Schema (Zod) |
|
||||
| CreateDto validacion | - | Schema (Zod) |
|
||||
| ResponseDto campo | Service (si calculado) | Types, Components |
|
||||
| Service metodo | Controller (si expuesto) | Service, Hook |
|
||||
| Service logica | - | Manejo errores |
|
||||
| Controller endpoint | Swagger | Service, Hook |
|
||||
| Controller ruta | Swagger | Service (URL) |
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guia de Impacto
|
||||
428
core/orchestration/impactos/IMPACTO-CAMBIOS-DDL.md
Normal file
428
core/orchestration/impactos/IMPACTO-CAMBIOS-DDL.md
Normal file
@ -0,0 +1,428 @@
|
||||
# IMPACTO: CAMBIOS EN DDL (Base de Datos)
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** CRÍTICA - Leer antes de modificar DDL
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPÓSITO
|
||||
|
||||
Documentar el impacto cascada de cambios en DDL y las acciones requeridas en cada capa.
|
||||
|
||||
---
|
||||
|
||||
## PRINCIPIO FUNDAMENTAL
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ CAMBIO EN DDL = CAMBIO EN CASCADA ║
|
||||
║ ║
|
||||
║ DDL → Entity → DTO → Service → Controller → Frontend ║
|
||||
║ ║
|
||||
║ ⚠️ NUNCA cambiar DDL sin actualizar las capas dependientes ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. AGREGAR COLUMNA
|
||||
|
||||
### Impacto por Capa
|
||||
|
||||
| Capa | Archivo(s) Afectado(s) | Acción Requerida |
|
||||
|------|------------------------|------------------|
|
||||
| **DDL** | `schemas/{schema}/tables/{tabla}.sql` | Agregar columna |
|
||||
| **Entity** | `{nombre}.entity.ts` | Agregar @Column |
|
||||
| **DTO Create** | `create-{nombre}.dto.ts` | Agregar campo (si input) |
|
||||
| **DTO Update** | `update-{nombre}.dto.ts` | Heredado de Create |
|
||||
| **DTO Response** | `{nombre}-response.dto.ts` | Agregar campo (si output) |
|
||||
| **Service** | `{nombre}.service.ts` | Ajustar lógica si necesario |
|
||||
| **Controller** | `{nombre}.controller.ts` | Swagger actualizado automático |
|
||||
| **Frontend Types** | `{nombre}.types.ts` | Agregar campo |
|
||||
| **Frontend Forms** | `{Nombre}Form.tsx` | Agregar input (si editable) |
|
||||
| **Frontend Display** | `{Nombre}Card.tsx`, etc. | Mostrar campo (si visible) |
|
||||
| **Tests** | `*.spec.ts`, `*.test.tsx` | Actualizar fixtures |
|
||||
|
||||
### Checklist: Agregar Columna
|
||||
|
||||
```
|
||||
DDL (Database-Agent):
|
||||
[ ] ALTER TABLE o modificar CREATE TABLE
|
||||
[ ] Definir tipo correcto (ver MAPEO-TIPOS.md)
|
||||
[ ] Definir NULL/NOT NULL
|
||||
[ ] Definir DEFAULT si aplica
|
||||
[ ] Crear índice si es buscable
|
||||
[ ] Ejecutar carga limpia exitosa
|
||||
[ ] Actualizar DATABASE_INVENTORY.yml
|
||||
|
||||
Backend (Backend-Agent):
|
||||
[ ] Agregar @Column en Entity
|
||||
- type: coincide con DDL
|
||||
- nullable: coincide con DDL
|
||||
- default: coincide con DDL
|
||||
[ ] Agregar campo en CreateDto (si es input)
|
||||
- Validaciones apropiadas
|
||||
- @ApiProperty con ejemplo
|
||||
[ ] Agregar campo en ResponseDto (si es output)
|
||||
[ ] Ajustar Service si hay lógica nueva
|
||||
[ ] npm run build → pasa
|
||||
[ ] npm run lint → pasa
|
||||
[ ] Actualizar BACKEND_INVENTORY.yml
|
||||
|
||||
Frontend (Frontend-Agent):
|
||||
[ ] Agregar campo en interface/type
|
||||
[ ] Actualizar Zod schema si hay form
|
||||
[ ] Agregar input en formulario (si editable)
|
||||
[ ] Mostrar en UI (si visible)
|
||||
[ ] npm run build → pasa
|
||||
[ ] npm run lint → pasa
|
||||
[ ] Actualizar FRONTEND_INVENTORY.yml
|
||||
|
||||
Integración:
|
||||
[ ] Test e2e funciona
|
||||
[ ] Swagger muestra campo nuevo
|
||||
[ ] Frontend consume correctamente
|
||||
```
|
||||
|
||||
### Ejemplo Completo: Agregar Campo `phone`
|
||||
|
||||
**1. DDL:**
|
||||
```sql
|
||||
-- schemas/auth/tables/01-users.sql
|
||||
ALTER TABLE auth.users
|
||||
ADD COLUMN phone VARCHAR(20);
|
||||
```
|
||||
|
||||
**2. Entity:**
|
||||
```typescript
|
||||
// entities/user.entity.ts
|
||||
@Column({ type: 'varchar', length: 20, nullable: true })
|
||||
phone: string;
|
||||
```
|
||||
|
||||
**3. DTO Create:**
|
||||
```typescript
|
||||
// dto/create-user.dto.ts
|
||||
@ApiPropertyOptional({ description: 'Teléfono', example: '+521234567890' })
|
||||
@IsOptional()
|
||||
@IsPhoneNumber('MX')
|
||||
phone?: string;
|
||||
```
|
||||
|
||||
**4. DTO Response:**
|
||||
```typescript
|
||||
// dto/user-response.dto.ts
|
||||
@ApiPropertyOptional()
|
||||
phone?: string;
|
||||
```
|
||||
|
||||
**5. Frontend Type:**
|
||||
```typescript
|
||||
// types/user.types.ts
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
phone?: string; // ← NUEVO
|
||||
}
|
||||
```
|
||||
|
||||
**6. Frontend Form:**
|
||||
```typescript
|
||||
// components/UserForm.tsx
|
||||
<FormField
|
||||
name="phone"
|
||||
label="Teléfono"
|
||||
placeholder="+52 123 456 7890"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. ELIMINAR COLUMNA
|
||||
|
||||
### Impacto por Capa
|
||||
|
||||
| Capa | Acción | Riesgo |
|
||||
|------|--------|--------|
|
||||
| **DDL** | `ALTER TABLE DROP COLUMN` | ALTO - Datos perdidos |
|
||||
| **Entity** | Eliminar @Column | MEDIO |
|
||||
| **DTO** | Eliminar campo | MEDIO |
|
||||
| **Service** | Eliminar referencias | ALTO - Lógica rota |
|
||||
| **Frontend** | Eliminar campo y usos | ALTO - UI rota |
|
||||
| **Tests** | Actualizar fixtures | MEDIO |
|
||||
|
||||
### Checklist: Eliminar Columna
|
||||
|
||||
```
|
||||
⚠️ ANTES DE ELIMINAR:
|
||||
[ ] Confirmar que datos no son necesarios
|
||||
[ ] Verificar que no hay dependencias en código
|
||||
[ ] Grep en todo el proyecto: grep -rn "{columna}" apps/
|
||||
[ ] Planificar migración de datos si necesario
|
||||
|
||||
DDL:
|
||||
[ ] ALTER TABLE DROP COLUMN
|
||||
[ ] Verificar que no rompe constraints/FK
|
||||
[ ] Carga limpia exitosa
|
||||
|
||||
Backend:
|
||||
[ ] Eliminar @Column de Entity
|
||||
[ ] Eliminar de DTOs
|
||||
[ ] Buscar y eliminar referencias en Services
|
||||
[ ] npm run build → sin errores de tipo
|
||||
[ ] npm run test → pasa
|
||||
|
||||
Frontend:
|
||||
[ ] Eliminar de types/interfaces
|
||||
[ ] Eliminar de formularios
|
||||
[ ] Eliminar de displays
|
||||
[ ] npm run build → sin errores de tipo
|
||||
|
||||
Post-eliminación:
|
||||
[ ] Verificar que aplicación funciona
|
||||
[ ] Verificar que no hay errores en consola
|
||||
[ ] Actualizar documentación
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. MODIFICAR TIPO DE COLUMNA
|
||||
|
||||
### Impacto por Capa
|
||||
|
||||
| Cambio | Backend | Frontend | Riesgo |
|
||||
|--------|---------|----------|--------|
|
||||
| `VARCHAR(50)` → `VARCHAR(100)` | Cambiar length | - | BAJO |
|
||||
| `VARCHAR` → `TEXT` | Cambiar type | - | BAJO |
|
||||
| `INTEGER` → `BIGINT` | `number` → `string` | Cambiar tipo | MEDIO |
|
||||
| `VARCHAR` → `ENUM` | Agregar enum | Agregar enum | MEDIO |
|
||||
| `TEXT` → `JSON` | Cambiar tipo, parsear | Cambiar tipo | ALTO |
|
||||
|
||||
### Checklist: Modificar Tipo
|
||||
|
||||
```
|
||||
DDL:
|
||||
[ ] ALTER TABLE ALTER COLUMN TYPE
|
||||
[ ] Verificar que datos existentes son compatibles
|
||||
[ ] Carga limpia exitosa
|
||||
|
||||
Backend:
|
||||
[ ] Actualizar @Column type
|
||||
[ ] Actualizar tipo TypeScript si cambió
|
||||
[ ] Actualizar validaciones en DTO
|
||||
[ ] npm run build → pasa
|
||||
|
||||
Frontend:
|
||||
[ ] Actualizar tipo en interface
|
||||
[ ] Actualizar validación Zod
|
||||
[ ] Ajustar componentes si tipo cambió
|
||||
[ ] npm run build → pasa
|
||||
|
||||
Datos:
|
||||
[ ] Migrar datos existentes si necesario
|
||||
[ ] Validar integridad post-migración
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. AGREGAR/MODIFICAR FOREIGN KEY
|
||||
|
||||
### Impacto por Capa
|
||||
|
||||
| Capa | Acción |
|
||||
|------|--------|
|
||||
| **DDL** | Agregar FK + índice |
|
||||
| **Entity** | Agregar @ManyToOne + @JoinColumn |
|
||||
| **Service** | Validar existencia antes de insertar |
|
||||
| **DTO** | Agregar campo ID de relación |
|
||||
| **Frontend** | Agregar selector/dropdown |
|
||||
|
||||
### Checklist: Agregar FK
|
||||
|
||||
```
|
||||
DDL:
|
||||
[ ] Agregar columna FK (UUID)
|
||||
[ ] Agregar FOREIGN KEY constraint
|
||||
[ ] Definir ON DELETE (CASCADE/SET NULL/RESTRICT)
|
||||
[ ] Crear índice en FK
|
||||
[ ] Verificar que tabla referenciada existe
|
||||
|
||||
Backend:
|
||||
[ ] Agregar columna en Entity: {relation}Id: string
|
||||
[ ] Agregar relación: @ManyToOne(() => RelatedEntity)
|
||||
[ ] Agregar @JoinColumn({ name: '{relation}_id' })
|
||||
[ ] Validar existencia en Service antes de crear
|
||||
[ ] Agregar en DTO con @IsUUID
|
||||
|
||||
Frontend:
|
||||
[ ] Agregar campo en type
|
||||
[ ] Crear selector si es editable
|
||||
[ ] Mostrar relación en UI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. AGREGAR/MODIFICAR ÍNDICE
|
||||
|
||||
### Impacto
|
||||
|
||||
- **DDL**: Agregar `CREATE INDEX`
|
||||
- **Otras capas**: Sin impacto directo
|
||||
- **Performance**: Mejora en queries
|
||||
|
||||
### Checklist: Agregar Índice
|
||||
|
||||
```
|
||||
[ ] Identificar columnas frecuentemente buscadas
|
||||
[ ] Agregar CREATE INDEX en DDL
|
||||
[ ] Para búsquedas compuestas: índice compuesto
|
||||
[ ] Ejecutar carga limpia
|
||||
[ ] Verificar plan de query (EXPLAIN)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. AGREGAR TABLA NUEVA
|
||||
|
||||
### Impacto Completo
|
||||
|
||||
```
|
||||
NUEVA TABLA = Feature completa
|
||||
|
||||
DDL:
|
||||
├── CREATE TABLE con todas las columnas
|
||||
├── PRIMARY KEY
|
||||
├── FOREIGN KEYS
|
||||
├── INDEXES
|
||||
├── COMMENTS
|
||||
└── Actualizar DATABASE_INVENTORY.yml
|
||||
|
||||
Backend:
|
||||
├── {nombre}.entity.ts
|
||||
├── create-{nombre}.dto.ts
|
||||
├── update-{nombre}.dto.ts
|
||||
├── {nombre}-response.dto.ts
|
||||
├── {nombre}.service.ts
|
||||
├── {nombre}.controller.ts
|
||||
├── {nombre}.module.ts
|
||||
├── Tests unitarios
|
||||
└── Actualizar BACKEND_INVENTORY.yml
|
||||
|
||||
Frontend:
|
||||
├── {nombre}.types.ts
|
||||
├── {nombre}.service.ts (API calls)
|
||||
├── use{Nombre}.ts (hook)
|
||||
├── {Nombre}Form.tsx
|
||||
├── {Nombre}List.tsx
|
||||
├── {Nombre}Page.tsx
|
||||
├── Tests de componentes
|
||||
└── Actualizar FRONTEND_INVENTORY.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. ELIMINAR TABLA
|
||||
|
||||
### Impacto CRÍTICO
|
||||
|
||||
```
|
||||
⚠️ ELIMINAR TABLA = DESTRUIR FEATURE
|
||||
|
||||
ANTES de eliminar:
|
||||
[ ] Confirmar que feature ya no se usa
|
||||
[ ] Backup de datos si necesario
|
||||
[ ] Verificar que no hay FK apuntando a esta tabla
|
||||
[ ] Grep completo: grep -rn "{tabla}" apps/
|
||||
|
||||
Orden de eliminación (inverso a creación):
|
||||
1. Frontend: Eliminar páginas, componentes, types
|
||||
2. Backend: Eliminar controller, service, entity, DTOs, module
|
||||
3. DDL: DROP TABLE
|
||||
4. Actualizar todos los inventarios
|
||||
|
||||
Post-eliminación:
|
||||
[ ] Build pasa en todas las capas
|
||||
[ ] Aplicación funciona sin errores
|
||||
[ ] Documentación actualizada
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. MATRIZ DE PROPAGACIÓN RÁPIDA
|
||||
|
||||
```
|
||||
┌─────────────────┬─────────┬────────┬─────────┬──────────┬──────────┐
|
||||
│ Cambio DDL │ Entity │ DTO │ Service │ Frontend │ Tests │
|
||||
├─────────────────┼─────────┼────────┼─────────┼──────────┼──────────┤
|
||||
│ + Columna │ +Column │ +Field │ ?Lógica │ +Type │ +Fixture │
|
||||
│ - Columna │ -Column │ -Field │ -Refs │ -Type │ -Fixture │
|
||||
│ ~ Tipo │ ~Type │ ~Valid │ ?Parse │ ~Type │ ~Fixture │
|
||||
│ + FK │ +Rel │ +UUID │ +Valid │ +Select │ +Test │
|
||||
│ + Índice │ - │ - │ - │ - │ - │
|
||||
│ + Tabla │ +Todo │ +Todo │ +Todo │ +Todo │ +Todo │
|
||||
│ - Tabla │ -Todo │ -Todo │ -Todo │ -Todo │ -Todo │
|
||||
└─────────────────┴─────────┴────────┴─────────┴──────────┴──────────┘
|
||||
|
||||
Leyenda:
|
||||
+ = Agregar
|
||||
- = Eliminar
|
||||
~ = Modificar
|
||||
? = Posiblemente
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. COMANDOS DE VERIFICACIÓN
|
||||
|
||||
```bash
|
||||
# Verificar que cambio DDL no rompe
|
||||
cd {DB_SCRIPTS_PATH}
|
||||
./recreate-database.sh # Carga limpia completa
|
||||
|
||||
# Verificar build backend
|
||||
cd {BACKEND_ROOT}
|
||||
npm run build && npm run lint
|
||||
|
||||
# Verificar build frontend
|
||||
cd {FRONTEND_ROOT}
|
||||
npm run build && npm run lint && npm run typecheck
|
||||
|
||||
# Buscar referencias a columna/tabla
|
||||
grep -rn "{nombre}" apps/
|
||||
|
||||
# Verificar alineación Entity-DDL
|
||||
# Comparar @Column en Entity vs columnas en DDL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. ANTI-PATRONES
|
||||
|
||||
```
|
||||
❌ NUNCA: Cambiar DDL sin actualizar Entity
|
||||
→ Resultado: Runtime error al acceder columna
|
||||
|
||||
❌ NUNCA: Agregar columna NOT NULL sin DEFAULT
|
||||
→ Resultado: INSERT falla en registros existentes
|
||||
|
||||
❌ NUNCA: Eliminar columna sin verificar uso
|
||||
→ Resultado: Código roto en múltiples lugares
|
||||
|
||||
❌ NUNCA: Cambiar tipo sin migrar datos
|
||||
→ Resultado: Datos corruptos o perdidos
|
||||
|
||||
❌ NUNCA: Modificar FK sin ajustar relaciones en Entity
|
||||
→ Resultado: ORM genera queries incorrectas
|
||||
|
||||
✅ SIEMPRE: DDL primero → Entity segundo → DTO tercero → Frontend cuarto
|
||||
✅ SIEMPRE: Verificar build en TODAS las capas después de cambio
|
||||
✅ SIEMPRE: Actualizar inventarios y trazas
|
||||
✅ SIEMPRE: Grep antes de eliminar para encontrar usos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guía de Impacto
|
||||
439
core/orchestration/impactos/IMPACTO-CAMBIOS-ENTITY.md
Normal file
439
core/orchestration/impactos/IMPACTO-CAMBIOS-ENTITY.md
Normal file
@ -0,0 +1,439 @@
|
||||
# IMPACTO DE CAMBIOS EN ENTITY
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - Consultar antes de modificar Entities
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Documentar especificamente los impactos de modificar Entities de TypeORM, que son el puente entre la base de datos y la logica de negocio.
|
||||
|
||||
---
|
||||
|
||||
## 1. PRINCIPIO FUNDAMENTAL
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ ENTITY ES REFLEJO DE DDL, NO AL REVES ║
|
||||
║ ║
|
||||
║ DDL (fuente) ──────▶ Entity (reflejo) ──────▶ DTO (contrato API) ║
|
||||
║ ║
|
||||
║ ⚠️ NUNCA crear Entity sin DDL existente ║
|
||||
║ ⚠️ NUNCA modificar Entity sin verificar DDL primero ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. TIPOS DE CAMBIOS EN ENTITY
|
||||
|
||||
### 2.1 Cambios de Columna
|
||||
|
||||
| Tipo de Cambio | Impacto DDL | Impacto DTO | Impacto Service | Impacto Frontend |
|
||||
|----------------|-------------|-------------|-----------------|------------------|
|
||||
| Agregar campo | SI | SI | Posible | SI |
|
||||
| Eliminar campo | SI | SI | Verificar | SI |
|
||||
| Renombrar campo | SI | SI | SI | SI |
|
||||
| Cambiar tipo | SI | SI | Posible | SI |
|
||||
| Cambiar nullable | SI | SI | Posible | SI |
|
||||
| Agregar default | SI | Posible | NO | NO |
|
||||
|
||||
### 2.2 Cambios de Relacion
|
||||
|
||||
| Tipo de Cambio | Impacto DDL | Impacto DTO | Impacto Service | Impacto Frontend |
|
||||
|----------------|-------------|-------------|-----------------|------------------|
|
||||
| Agregar ManyToOne | SI (FK) | SI | SI | SI |
|
||||
| Agregar OneToMany | NO | SI | SI | SI |
|
||||
| Agregar ManyToMany | SI (junction) | SI | SI | SI |
|
||||
| Eliminar relacion | SI | SI | SI | SI |
|
||||
| Cambiar cascade | NO | NO | Verificar | NO |
|
||||
|
||||
### 2.3 Cambios de Indice/Constraint
|
||||
|
||||
| Tipo de Cambio | Impacto DDL | Impacto DTO | Impacto Service | Impacto Frontend |
|
||||
|----------------|-------------|-------------|-----------------|------------------|
|
||||
| Agregar indice | SI | NO | NO | NO |
|
||||
| Agregar unique | SI | NO | Manejar error | Manejar error |
|
||||
| Agregar check | SI | NO | Manejar error | Manejar error |
|
||||
|
||||
---
|
||||
|
||||
## 3. AGREGAR CAMPO A ENTITY
|
||||
|
||||
### Flujo Completo
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PASO 1: DDL (Database-Agent) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ ALTER TABLE auth.users ADD COLUMN phone VARCHAR(20); │
|
||||
│ │
|
||||
│ Validar: ./recreate-database.sh && psql -c "\d auth.users" │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PASO 2: Entity (Backend-Agent) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ @Column({ type: 'varchar', length: 20, nullable: true }) │
|
||||
│ phone: string | null; │
|
||||
│ │
|
||||
│ Validar: npm run build │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PASO 3: DTOs (Backend-Agent) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ CreateUserDto: │
|
||||
│ @IsOptional() │
|
||||
│ @IsString() │
|
||||
│ @MaxLength(20) │
|
||||
│ phone?: string; │
|
||||
│ │
|
||||
│ UserResponseDto: │
|
||||
│ @ApiProperty({ required: false }) │
|
||||
│ phone: string | null; │
|
||||
│ │
|
||||
│ Validar: npm run build && npm run lint │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ PASO 4: Frontend (Frontend-Agent) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ user.types.ts: │
|
||||
│ interface User { │
|
||||
│ phone: string | null; │
|
||||
│ } │
|
||||
│ │
|
||||
│ user.schema.ts: │
|
||||
│ phone: z.string().max(20).optional() │
|
||||
│ │
|
||||
│ Validar: npm run build && npm run typecheck │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Checklist Agregar Campo
|
||||
|
||||
```
|
||||
[ ] 1. DDL tiene la columna con tipo correcto
|
||||
[ ] 2. Entity @Column refleja DDL (tipo, length, nullable)
|
||||
[ ] 3. CreateDto tiene validaciones apropiadas
|
||||
[ ] 4. UpdateDto hereda de CreateDto (automatico con PartialType)
|
||||
[ ] 5. ResponseDto tiene el campo con ApiProperty
|
||||
[ ] 6. Frontend interface tiene el campo
|
||||
[ ] 7. Frontend Zod schema tiene validacion equivalente
|
||||
[ ] 8. Swagger documenta correctamente
|
||||
[ ] 9. Build pasa en backend
|
||||
[ ] 10. Build y typecheck pasan en frontend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. ELIMINAR CAMPO DE ENTITY
|
||||
|
||||
### ADVERTENCIA
|
||||
|
||||
```
|
||||
⚠️ ELIMINAR CAMPO ES OPERACION DESTRUCTIVA
|
||||
|
||||
ANTES DE ELIMINAR:
|
||||
1. Verificar que no hay datos importantes
|
||||
2. Buscar TODAS las referencias en codigo
|
||||
3. Considerar deprecar primero (nullable + ignorar)
|
||||
4. Planear migracion de datos si necesario
|
||||
```
|
||||
|
||||
### Flujo de Eliminacion
|
||||
|
||||
```bash
|
||||
# PASO 0: Buscar todas las referencias
|
||||
grep -rn "fieldName" src/
|
||||
grep -rn "fieldName" apps/
|
||||
grep -rn "field_name" db/ # snake_case en DDL
|
||||
|
||||
# PASO 1: Eliminar uso en Frontend primero (evita errores de tipo)
|
||||
# PASO 2: Eliminar uso en Service/Controller
|
||||
# PASO 3: Eliminar de DTOs
|
||||
# PASO 4: Eliminar de Entity
|
||||
# PASO 5: Eliminar de DDL (ultimo porque es irreversible)
|
||||
```
|
||||
|
||||
### Checklist Eliminar Campo
|
||||
|
||||
```
|
||||
[ ] 1. Buscar referencias en backend: grep -rn "field" src/
|
||||
[ ] 2. Buscar referencias en frontend: grep -rn "field" apps/
|
||||
[ ] 3. Eliminar de componentes Frontend
|
||||
[ ] 4. Eliminar de types/interfaces Frontend
|
||||
[ ] 5. Eliminar de Zod schemas Frontend
|
||||
[ ] 6. Eliminar de Service (si hay logica)
|
||||
[ ] 7. Eliminar de ResponseDto
|
||||
[ ] 8. Eliminar de CreateDto
|
||||
[ ] 9. Eliminar de Entity
|
||||
[ ] 10. Eliminar de DDL (con migracion)
|
||||
[ ] 11. Build pasa en todos los proyectos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. AGREGAR RELACION A ENTITY
|
||||
|
||||
### 5.1 ManyToOne (FK hacia otra tabla)
|
||||
|
||||
```typescript
|
||||
// Entity: Order tiene un User
|
||||
@ManyToOne(() => UserEntity, { nullable: false })
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user: UserEntity;
|
||||
|
||||
// Columna FK explicita (opcional pero recomendado)
|
||||
@Column({ type: 'uuid' })
|
||||
userId: string;
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Capa | Archivo | Cambio Requerido |
|
||||
|------|---------|------------------|
|
||||
| DDL | `XX-orders.sql` | `user_id UUID REFERENCES auth.users(id)` |
|
||||
| Entity | `order.entity.ts` | `@ManyToOne` + `@JoinColumn` |
|
||||
| CreateDto | `create-order.dto.ts` | `@IsUUID() userId: string;` |
|
||||
| ResponseDto | `order-response.dto.ts` | `userId: string;` o `user: UserResponseDto;` |
|
||||
| Service | `order.service.ts` | Decidir: cargar relacion o no |
|
||||
| Frontend | `order.types.ts` | `userId: string;` o `user: User;` |
|
||||
|
||||
### 5.2 OneToMany (Coleccion inversa)
|
||||
|
||||
```typescript
|
||||
// Entity: User tiene muchos Orders
|
||||
@OneToMany(() => OrderEntity, order => order.user)
|
||||
orders: OrderEntity[];
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Capa | Archivo | Cambio Requerido |
|
||||
|------|---------|------------------|
|
||||
| DDL | N/A | Sin cambio (FK esta en orders) |
|
||||
| Entity | `user.entity.ts` | `@OneToMany` |
|
||||
| ResponseDto | `user-response.dto.ts` | Decidir: incluir o no |
|
||||
| Service | `user.service.ts` | Decidir: cargar relacion o no |
|
||||
| Frontend | `user.types.ts` | `orders?: Order[];` |
|
||||
|
||||
### 5.3 ManyToMany (Tabla junction)
|
||||
|
||||
```typescript
|
||||
// Entity: User tiene muchos Roles
|
||||
@ManyToMany(() => RoleEntity)
|
||||
@JoinTable({
|
||||
name: 'user_roles',
|
||||
joinColumn: { name: 'user_id' },
|
||||
inverseJoinColumn: { name: 'role_id' },
|
||||
})
|
||||
roles: RoleEntity[];
|
||||
```
|
||||
|
||||
**Impacto:**
|
||||
|
||||
| Capa | Archivo | Cambio Requerido |
|
||||
|------|---------|------------------|
|
||||
| DDL | `XX-user-roles.sql` | Crear tabla junction |
|
||||
| Entity | `user.entity.ts` | `@ManyToMany` + `@JoinTable` |
|
||||
| ResponseDto | `user-response.dto.ts` | `roles: RoleResponseDto[];` |
|
||||
| Service | `user.service.ts` | Logica para asignar/remover roles |
|
||||
| Controller | `user.controller.ts` | Endpoints para manejar roles |
|
||||
| Frontend | `user.types.ts` | `roles: Role[];` |
|
||||
| Frontend | `useUserRoles.ts` | Hook para manejar roles |
|
||||
|
||||
### Checklist Agregar Relacion
|
||||
|
||||
```
|
||||
[ ] 1. DDL tiene FK o tabla junction
|
||||
[ ] 2. Entity tiene decorador de relacion correcto
|
||||
[ ] 3. Entity relacionada tiene relacion inversa (si necesario)
|
||||
[ ] 4. CreateDto acepta ID de relacion
|
||||
[ ] 5. ResponseDto decide: incluir objeto o solo ID
|
||||
[ ] 6. Service decide: eager loading o lazy
|
||||
[ ] 7. Frontend types reflejan estructura
|
||||
[ ] 8. Frontend tiene logica para manejar relacion
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. CAMBIAR TIPO DE CAMPO
|
||||
|
||||
### Mapeo de Cambios Comunes
|
||||
|
||||
| Cambio | DDL | Entity | DTO | Frontend |
|
||||
|--------|-----|--------|-----|----------|
|
||||
| `INT` → `BIGINT` | ALTER TYPE | `number` (sin cambio) | Sin cambio | Sin cambio |
|
||||
| `VARCHAR` → `TEXT` | ALTER TYPE | Sin cambio | Ajustar MaxLength | Ajustar max |
|
||||
| `INTEGER` → `DECIMAL` | ALTER TYPE | `number` → `string` | Ajustar validador | Ajustar tipo |
|
||||
| `BOOLEAN` → `ENUM` | ALTER TYPE | `boolean` → `enum` | Cambiar validador | Cambiar tipo |
|
||||
| `VARCHAR` → `UUID` | ALTER TYPE + datos | `string` (sin cambio) | Agregar @IsUUID | Agregar validacion |
|
||||
|
||||
### Ejemplo: INTEGER a DECIMAL
|
||||
|
||||
```sql
|
||||
-- DDL
|
||||
ALTER TABLE products ALTER COLUMN price TYPE DECIMAL(10,2);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity ANTES
|
||||
@Column({ type: 'integer' })
|
||||
price: number;
|
||||
|
||||
// Entity DESPUES
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
price: string; // String para precision
|
||||
```
|
||||
|
||||
```typescript
|
||||
// CreateDto ANTES
|
||||
@IsNumber()
|
||||
price: number;
|
||||
|
||||
// CreateDto DESPUES
|
||||
@IsNumberString()
|
||||
@Matches(/^\d+(\.\d{1,2})?$/)
|
||||
price: string;
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Frontend ANTES
|
||||
interface Product {
|
||||
price: number;
|
||||
}
|
||||
|
||||
// Frontend DESPUES
|
||||
interface Product {
|
||||
price: string;
|
||||
}
|
||||
|
||||
// Uso en componente
|
||||
const displayPrice = parseFloat(product.price).toFixed(2);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. PATRON DE MIGRACION SEGURA
|
||||
|
||||
### Para cambios que pueden romper
|
||||
|
||||
```
|
||||
FASE 1: Agregar nuevo (sin eliminar viejo)
|
||||
═══════════════════════════════════════════════════════════════
|
||||
1. Agregar nuevo campo/columna junto al existente
|
||||
2. Actualizar codigo para escribir en ambos
|
||||
3. Deploy backend
|
||||
|
||||
FASE 2: Migrar datos
|
||||
═══════════════════════════════════════════════════════════════
|
||||
1. Script para copiar datos de viejo a nuevo
|
||||
2. Verificar integridad
|
||||
|
||||
FASE 3: Cambiar lecturas
|
||||
═══════════════════════════════════════════════════════════════
|
||||
1. Actualizar codigo para leer de nuevo
|
||||
2. Deploy backend + frontend
|
||||
|
||||
FASE 4: Eliminar viejo
|
||||
═══════════════════════════════════════════════════════════════
|
||||
1. Eliminar campo viejo de codigo
|
||||
2. Eliminar columna de DDL
|
||||
3. Deploy final
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. DECORADORES ENTITY Y SU IMPACTO
|
||||
|
||||
### @Column Options
|
||||
|
||||
| Option | Impacto si cambia | Requiere DDL |
|
||||
|--------|-------------------|--------------|
|
||||
| `type` | Tipo TS, validacion DTO | SI |
|
||||
| `length` | Validacion DTO | SI |
|
||||
| `nullable` | Tipo TS (null), validacion | SI |
|
||||
| `default` | Posible logica Service | SI |
|
||||
| `unique` | Manejo error Service | SI |
|
||||
| `name` | Ninguno (mapeo interno) | SI |
|
||||
| `precision/scale` | Tipo TS, formateo FE | SI |
|
||||
|
||||
### @Index
|
||||
|
||||
| Cambio | Impacto |
|
||||
|--------|---------|
|
||||
| Agregar | Solo performance, DDL |
|
||||
| Eliminar | Solo performance, DDL |
|
||||
| Cambiar columnas | Solo performance, DDL |
|
||||
|
||||
### Relation Options
|
||||
|
||||
| Option | Impacto si cambia |
|
||||
|--------|-------------------|
|
||||
| `eager: true` | Queries cargan automatico |
|
||||
| `cascade: true` | Operaciones se propagan |
|
||||
| `onDelete` | Comportamiento al eliminar padre |
|
||||
| `nullable` | Validacion, tipo TS |
|
||||
|
||||
---
|
||||
|
||||
## 9. COMANDOS DE VERIFICACION
|
||||
|
||||
```bash
|
||||
# Ver diferencias entre DDL y Entity
|
||||
# (manual: comparar columnas)
|
||||
psql -d mydb -c "\d schema.table"
|
||||
cat src/modules/x/entities/x.entity.ts
|
||||
|
||||
# Verificar que Entity compila
|
||||
npm run build
|
||||
|
||||
# Verificar que no hay referencias rotas
|
||||
npm run lint
|
||||
|
||||
# Verificar sincronizacion con frontend
|
||||
npm run typecheck --prefix apps/web
|
||||
|
||||
# Buscar uso de campo especifico
|
||||
grep -rn "fieldName" src/ apps/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. MATRIZ DE DECISION
|
||||
|
||||
```
|
||||
¿Que tipo de cambio en Entity?
|
||||
|
||||
┌─────────────────────┐
|
||||
│ Agregar campo │──▶ DDL primero → Entity → DTO → Frontend
|
||||
└─────────────────────┘
|
||||
|
||||
┌─────────────────────┐
|
||||
│ Eliminar campo │──▶ Frontend primero → Service → DTO → Entity → DDL
|
||||
└─────────────────────┘
|
||||
|
||||
┌─────────────────────┐
|
||||
│ Cambiar tipo │──▶ DDL primero → Entity → DTO → Frontend
|
||||
└─────────────────────┘
|
||||
|
||||
┌─────────────────────┐
|
||||
│ Agregar relacion │──▶ DDL (FK) → Entity → DTO → Service → Frontend
|
||||
└─────────────────────┘
|
||||
|
||||
┌─────────────────────┐
|
||||
│ Renombrar campo │──▶ Migracion segura (agregar nuevo → migrar → eliminar viejo)
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guia de Impacto
|
||||
445
core/orchestration/impactos/MATRIZ-DEPENDENCIAS.md
Normal file
445
core/orchestration/impactos/MATRIZ-DEPENDENCIAS.md
Normal file
@ -0,0 +1,445 @@
|
||||
# MATRIZ DE DEPENDENCIAS ENTRE CAPAS
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** REFERENCIA - Consultar para entender impactos
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Proporcionar una matriz completa de dependencias entre todos los componentes del sistema para que los agentes puedan identificar rapidamente que se debe actualizar cuando se modifica algo.
|
||||
|
||||
---
|
||||
|
||||
## 1. VISION GENERAL DE DEPENDENCIAS
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ FLUJO DE DEPENDENCIAS │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ DDL │────▶│ Entity │────▶│ DTO │────▶│ Service │ │
|
||||
│ │ (SQL) │ │(TypeORM)│ │ (class) │ │ (logic) │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ ▼ │
|
||||
│ │ │ │ ┌─────────┐ │
|
||||
│ │ │ │ │Controller│ │
|
||||
│ │ │ │ │ (REST) │ │
|
||||
│ │ │ │ └─────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ │ ▼ │
|
||||
│ │ │ │ ┌─────────────────┐ │
|
||||
│ │ │ │ │ SWAGGER │ │
|
||||
│ │ │ │ │ (API Contract) │ │
|
||||
│ │ │ │ └─────────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ▼ ▼ │
|
||||
│ │ │ ┌─────────────────────────┐ │
|
||||
│ │ │ │ FRONTEND │ │
|
||||
│ │ │ │ Types│Schema│Hooks│UI │ │
|
||||
│ │ │ └─────────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ INVENTARIOS │ │
|
||||
│ │ DB│BE│FE│MASTER │ │
|
||||
│ └─────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. MATRIZ DE DEPENDENCIA: DDL
|
||||
|
||||
### Cuando se modifica DDL (tablas, columnas)
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **Entity** | SI | 1 - Inmediato | Debe reflejar estructura DDL |
|
||||
| **CreateDto** | SI | 2 | Campos nuevos/eliminados |
|
||||
| **UpdateDto** | AUTO | 2 | Hereda de CreateDto |
|
||||
| **ResponseDto** | SI | 2 | Campos en response |
|
||||
| **Service** | POSIBLE | 3 | Si hay logica de campo |
|
||||
| **Controller** | NO | - | Usa DTOs |
|
||||
| **Frontend Types** | SI | 4 | Reflejar ResponseDto |
|
||||
| **Frontend Schema** | SI | 4 | Reflejar CreateDto |
|
||||
| **Frontend Components** | POSIBLE | 5 | Si muestran campo |
|
||||
| **Swagger** | AUTO | - | Generado de DTOs |
|
||||
| **DATABASE_INVENTORY** | SI | 6 | Registrar cambio |
|
||||
| **Tests** | POSIBLE | 5 | Si prueban campo |
|
||||
|
||||
### Diagrama de Propagacion DDL
|
||||
|
||||
```
|
||||
DDL Change
|
||||
│
|
||||
├──▶ Entity (obligatorio)
|
||||
│ │
|
||||
│ ├──▶ CreateDto (obligatorio)
|
||||
│ │ │
|
||||
│ │ └──▶ Frontend Schema (obligatorio)
|
||||
│ │
|
||||
│ ├──▶ ResponseDto (obligatorio)
|
||||
│ │ │
|
||||
│ │ └──▶ Frontend Types (obligatorio)
|
||||
│ │ │
|
||||
│ │ └──▶ Frontend Components (si usa campo)
|
||||
│ │
|
||||
│ └──▶ Service (si logica de campo)
|
||||
│
|
||||
└──▶ DATABASE_INVENTORY (obligatorio)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. MATRIZ DE DEPENDENCIA: ENTITY
|
||||
|
||||
### Cuando se modifica Entity
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **DDL** | VERIFICAR | 0 | DDL debe existir primero |
|
||||
| **CreateDto** | SI | 1 | Reflejar campos |
|
||||
| **UpdateDto** | AUTO | 1 | Hereda de CreateDto |
|
||||
| **ResponseDto** | SI | 1 | Reflejar campos |
|
||||
| **Service** | POSIBLE | 2 | Si usa campo directamente |
|
||||
| **Controller** | NO | - | Usa DTOs |
|
||||
| **Frontend Types** | SI | 3 | Reflejar ResponseDto |
|
||||
| **Frontend Schema** | SI | 3 | Reflejar CreateDto |
|
||||
| **Frontend Components** | POSIBLE | 4 | Si muestran campo |
|
||||
| **BACKEND_INVENTORY** | SI | 5 | Registrar cambio |
|
||||
| **Unit Tests** | SI | 4 | Actualizar mocks |
|
||||
|
||||
### Por Tipo de Cambio en Entity
|
||||
|
||||
| Cambio | DTOs | Service | Frontend | Tests |
|
||||
|--------|------|---------|----------|-------|
|
||||
| Agregar @Column | SI | NO | SI | SI |
|
||||
| Eliminar @Column | SI | Verificar | SI | SI |
|
||||
| Cambiar tipo @Column | SI | Posible | SI | SI |
|
||||
| Agregar @ManyToOne | SI | SI | SI | SI |
|
||||
| Agregar @OneToMany | SI | Posible | SI | Posible |
|
||||
| Cambiar @Index | NO | NO | NO | NO |
|
||||
| Cambiar nullable | SI | NO | SI | Posible |
|
||||
|
||||
---
|
||||
|
||||
## 4. MATRIZ DE DEPENDENCIA: DTO
|
||||
|
||||
### Cuando se modifica CreateDto
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **UpdateDto** | AUTO | 1 | Si usa PartialType |
|
||||
| **Service** | NO | - | Recibe DTO |
|
||||
| **Controller** | NO | - | Usa DTO |
|
||||
| **Swagger** | AUTO | - | Generado |
|
||||
| **Frontend Schema (Zod)** | SI | 2 | Mismas validaciones |
|
||||
| **Frontend Forms** | POSIBLE | 3 | Si campos cambian |
|
||||
| **Integration Tests** | SI | 3 | Fixtures |
|
||||
|
||||
### Cuando se modifica ResponseDto
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **Service** | SI | 1 | Si mapea a ResponseDto |
|
||||
| **Controller** | NO | - | Retorna Service result |
|
||||
| **Swagger** | AUTO | - | Generado |
|
||||
| **Frontend Types** | SI | 2 | Interface debe coincidir |
|
||||
| **Frontend Components** | POSIBLE | 3 | Si muestran campo |
|
||||
| **Frontend Hooks** | NO | - | Usan Types |
|
||||
|
||||
---
|
||||
|
||||
## 5. MATRIZ DE DEPENDENCIA: SERVICE
|
||||
|
||||
### Cuando se modifica Service
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **Controller** | POSIBLE | 1 | Si cambia firma metodo |
|
||||
| **Swagger** | POSIBLE | 2 | Si cambia response/errors |
|
||||
| **Frontend** | POSIBLE | 2 | Si cambia contrato API |
|
||||
| **Unit Tests** | SI | 1 | Tests del service |
|
||||
| **Integration Tests** | POSIBLE | 2 | Si cambia comportamiento |
|
||||
|
||||
### Por Tipo de Cambio en Service
|
||||
|
||||
| Cambio | Controller | Frontend | Tests |
|
||||
|--------|------------|----------|-------|
|
||||
| Agregar metodo | SI (nuevo endpoint) | SI | SI |
|
||||
| Eliminar metodo | SI | SI | SI |
|
||||
| Cambiar firma | SI | SI | SI |
|
||||
| Cambiar logica interna | NO | NO | SI |
|
||||
| Agregar validacion | NO | Manejar error | SI |
|
||||
| Cambiar error thrown | NO | Manejar error | SI |
|
||||
|
||||
---
|
||||
|
||||
## 6. MATRIZ DE DEPENDENCIA: CONTROLLER
|
||||
|
||||
### Cuando se modifica Controller
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **Service** | NO | - | Controller consume Service |
|
||||
| **Swagger** | AUTO | - | Generado de decoradores |
|
||||
| **Frontend Service** | SI | 1 | Si cambia ruta/metodo |
|
||||
| **Frontend Hooks** | POSIBLE | 2 | Si cambia endpoint |
|
||||
| **E2E Tests** | SI | 2 | Tests de endpoint |
|
||||
|
||||
### Por Tipo de Cambio en Controller
|
||||
|
||||
| Cambio | Frontend Service | Frontend Hooks | Tests |
|
||||
|--------|------------------|----------------|-------|
|
||||
| Agregar endpoint | SI (nuevo metodo) | SI (nuevo hook) | SI |
|
||||
| Eliminar endpoint | SI | SI | SI |
|
||||
| Cambiar ruta | SI | NO | SI |
|
||||
| Cambiar metodo HTTP | SI | NO | SI |
|
||||
| Cambiar decoradores Swagger | NO | NO | NO |
|
||||
| Agregar guard | NO | Manejar 401/403 | SI |
|
||||
|
||||
---
|
||||
|
||||
## 7. MATRIZ DE DEPENDENCIA: FRONTEND
|
||||
|
||||
### Cuando se modifica Frontend Types
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **Zod Schema** | VERIFICAR | 1 | Debe ser consistente |
|
||||
| **Hooks** | NO | - | Usan Types |
|
||||
| **Components** | VERIFICAR | 2 | Si usan tipo |
|
||||
| **Services** | NO | - | Usan Types |
|
||||
|
||||
### Cuando se modifica Frontend Schema (Zod)
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **Forms** | VERIFICAR | 1 | Si usan schema |
|
||||
| **Types** | VERIFICAR | 1 | Deben coincidir |
|
||||
|
||||
### Cuando se modifica Frontend Hook
|
||||
|
||||
| Componente Dependiente | Requiere Actualizacion | Prioridad | Detalle |
|
||||
|------------------------|------------------------|-----------|---------|
|
||||
| **Components** | VERIFICAR | 1 | Si usan hook |
|
||||
| **Pages** | VERIFICAR | 1 | Si usan hook |
|
||||
|
||||
---
|
||||
|
||||
## 8. MATRIZ RAPIDA DE REFERENCIA
|
||||
|
||||
### "Si cambio X, que debo actualizar?"
|
||||
|
||||
```
|
||||
┌─────────────────┬─────┬────────┬─────────┬─────────┬────────┬─────────────┐
|
||||
│ SI CAMBIO... │ DDL │ Entity │DTO-Crea │DTO-Resp │Service │ Controller │
|
||||
├─────────────────┼─────┼────────┼─────────┼─────────┼────────┼─────────────┤
|
||||
│ DDL columna │ ── │ SI │ SI │ SI │ Posib │ NO │
|
||||
│ DDL FK │ ── │ SI │ SI │ SI │ SI │ Posible │
|
||||
│ DDL indice │ ── │ NO │ NO │ NO │ NO │ NO │
|
||||
├─────────────────┼─────┼────────┼─────────┼─────────┼────────┼─────────────┤
|
||||
│ Entity columna │ Ver │ ── │ SI │ SI │ Posib │ NO │
|
||||
│ Entity relacion │ Ver │ ── │ SI │ SI │ SI │ Posible │
|
||||
├─────────────────┼─────┼────────┼─────────┼─────────┼────────┼─────────────┤
|
||||
│ CreateDto campo │ NO │ NO │ ── │ NO │ NO │ NO │
|
||||
│ CreateDto valid │ NO │ NO │ ── │ NO │ NO │ NO │
|
||||
│ ResponseDto │ NO │ NO │ NO │ ── │ Ver │ NO │
|
||||
├─────────────────┼─────┼────────┼─────────┼─────────┼────────┼─────────────┤
|
||||
│ Service metodo │ NO │ NO │ NO │ Posib │ ── │ SI │
|
||||
│ Service logica │ NO │ NO │ NO │ NO │ ── │ NO │
|
||||
├─────────────────┼─────┼────────┼─────────┼─────────┼────────┼─────────────┤
|
||||
│ Controller ruta │ NO │ NO │ NO │ NO │ NO │ ── │
|
||||
│ Controller meth │ NO │ NO │ NO │ NO │ NO │ ── │
|
||||
└─────────────────┴─────┴────────┴─────────┴─────────┴────────┴─────────────┘
|
||||
|
||||
Continuacion → Frontend
|
||||
|
||||
┌─────────────────┬───────┬────────┬───────┬───────────┬───────────┐
|
||||
│ SI CAMBIO... │ Types │ Schema │ Hooks │Components │ Inventory │
|
||||
├─────────────────┼───────┼────────┼───────┼───────────┼───────────┤
|
||||
│ DDL columna │ SI │ SI │ NO │ Posible │ DB+MAST │
|
||||
│ DDL FK │ SI │ SI │ SI │ Posible │ DB+MAST │
|
||||
│ DDL indice │ NO │ NO │ NO │ NO │ DB │
|
||||
├─────────────────┼───────┼────────┼───────┼───────────┼───────────┤
|
||||
│ Entity columna │ SI │ SI │ NO │ Posible │ BE+MAST │
|
||||
│ Entity relacion │ SI │ SI │ SI │ Posible │ BE+MAST │
|
||||
├─────────────────┼───────┼────────┼───────┼───────────┼───────────┤
|
||||
│ CreateDto campo │ NO │ SI │ NO │ Posible │ BE │
|
||||
│ CreateDto valid │ NO │ SI │ NO │ NO │ NO │
|
||||
│ ResponseDto │ SI │ NO │ NO │ Posible │ BE │
|
||||
├─────────────────┼───────┼────────┼───────┼───────────┼───────────┤
|
||||
│ Service metodo │ Posib │ Posib │ SI │ Posible │ BE │
|
||||
│ Service logica │ NO │ NO │ NO │ NO │ NO │
|
||||
├─────────────────┼───────┼────────┼───────┼───────────┼───────────┤
|
||||
│ Controller ruta │ NO │ NO │ SI │ NO │ BE │
|
||||
│ Controller meth │ NO │ NO │ SI │ NO │ NO │
|
||||
├─────────────────┼───────┼────────┼───────┼───────────┼───────────┤
|
||||
│ Frontend Types │ ── │ Ver │ NO │ Ver │ FE │
|
||||
│ Frontend Schema │ Ver │ ── │ NO │ Ver │ FE │
|
||||
│ Frontend Hook │ NO │ NO │ ── │ Ver │ FE │
|
||||
│ Frontend Comp │ NO │ NO │ NO │ ── │ FE │
|
||||
└─────────────────┴───────┴────────┴───────┴───────────┴───────────┘
|
||||
|
||||
Leyenda:
|
||||
SI = Siempre actualizar
|
||||
NO = No requiere actualizacion
|
||||
Ver = Verificar DDL primero
|
||||
Posib = Posiblemente, depende del caso
|
||||
── = No aplica (mismo componente)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. DEPENDENCIAS POR MODULO
|
||||
|
||||
### Modulo Tipico NestJS
|
||||
|
||||
```
|
||||
modules/user/
|
||||
├── entities/
|
||||
│ └── user.entity.ts ← Depende de: DDL
|
||||
│ Impacta: DTOs, Service
|
||||
├── dto/
|
||||
│ ├── create-user.dto.ts ← Depende de: Entity
|
||||
│ │ Impacta: Frontend Schema
|
||||
│ ├── update-user.dto.ts ← Depende de: CreateDto
|
||||
│ │ Impacta: Frontend Schema
|
||||
│ └── user-response.dto.ts ← Depende de: Entity
|
||||
│ Impacta: Frontend Types
|
||||
├── services/
|
||||
│ └── user.service.ts ← Depende de: Entity, DTOs
|
||||
│ Impacta: Controller
|
||||
├── controllers/
|
||||
│ └── user.controller.ts ← Depende de: Service, DTOs
|
||||
│ Impacta: Frontend Hooks
|
||||
└── user.module.ts ← Depende de: Todo lo anterior
|
||||
```
|
||||
|
||||
### Modulo Tipico Frontend
|
||||
|
||||
```
|
||||
shared/
|
||||
├── types/
|
||||
│ └── user.types.ts ← Depende de: ResponseDto
|
||||
│ Impacta: Hooks, Components
|
||||
├── schemas/
|
||||
│ └── user.schema.ts ← Depende de: CreateDto
|
||||
│ Impacta: Forms
|
||||
└── services/
|
||||
└── user.service.ts ← Depende de: Controller routes
|
||||
Impacta: Hooks
|
||||
|
||||
apps/web/
|
||||
├── hooks/
|
||||
│ └── useUsers.ts ← Depende de: Service, Types
|
||||
│ Impacta: Pages, Components
|
||||
├── components/
|
||||
│ └── UserCard.tsx ← Depende de: Types, Hooks
|
||||
│ Impacta: Pages
|
||||
└── pages/
|
||||
└── UsersPage.tsx ← Depende de: Hooks, Components
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. COMANDOS DE VERIFICACION DE DEPENDENCIAS
|
||||
|
||||
```bash
|
||||
# Ver todas las importaciones de un archivo
|
||||
grep -n "import" src/modules/user/user.service.ts
|
||||
|
||||
# Ver quien importa un archivo
|
||||
grep -rn "from.*user.entity" src/
|
||||
|
||||
# Ver dependencias de modulo
|
||||
grep -rn "UserModule" src/
|
||||
|
||||
# Ver uso de tipo en frontend
|
||||
grep -rn "User" apps/web/
|
||||
|
||||
# Arbol de dependencias (si usa herramientas)
|
||||
npx madge --image graph.svg src/modules/user/
|
||||
|
||||
# TypeScript: ver errores de tipo despues de cambio
|
||||
npm run typecheck 2>&1 | head -50
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. ESCENARIOS COMUNES
|
||||
|
||||
### Escenario 1: Agregar campo a tabla existente
|
||||
|
||||
```
|
||||
1. DDL: ALTER TABLE ADD COLUMN
|
||||
2. Entity: Agregar @Column
|
||||
3. CreateDto: Agregar campo + validacion
|
||||
4. ResponseDto: Agregar campo
|
||||
5. Service: Solo si hay logica especial
|
||||
6. Frontend Types: Agregar a interface
|
||||
7. Frontend Schema: Agregar validacion Zod
|
||||
8. Frontend Components: Agregar UI si necesario
|
||||
9. Inventarios: DB, BE, FE, MASTER
|
||||
```
|
||||
|
||||
### Escenario 2: Crear nuevo modulo/feature
|
||||
|
||||
```
|
||||
1. DDL: CREATE TABLE
|
||||
2. Entity: Crear archivo
|
||||
3. DTOs: Crear Create, Update, Response
|
||||
4. Service: Crear con CRUD basico
|
||||
5. Controller: Crear con endpoints REST
|
||||
6. Module: Crear y registrar en AppModule
|
||||
7. Frontend Types: Crear interface
|
||||
8. Frontend Schema: Crear Zod schema
|
||||
9. Frontend Service: Crear API service
|
||||
10. Frontend Hook: Crear useQuery/useMutation
|
||||
11. Frontend Components: Crear UI
|
||||
12. Inventarios: TODOS
|
||||
```
|
||||
|
||||
### Escenario 3: Refactorizar nombre de campo
|
||||
|
||||
```
|
||||
1. EVALUAR: ¿Es breaking change?
|
||||
2. OPCION A (breaking): Cambiar en cadena DDL→Entity→DTO→FE
|
||||
3. OPCION B (seguro):
|
||||
a. Agregar nuevo campo
|
||||
b. Migrar datos
|
||||
c. Actualizar codigo para usar nuevo
|
||||
d. Eliminar campo viejo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. ANTI-PATRON: DEPENDENCIAS CIRCULARES
|
||||
|
||||
```
|
||||
❌ INCORRECTO: Dependencia circular entre Services
|
||||
UserService ──▶ OrderService
|
||||
▲ │
|
||||
└───────────────┘
|
||||
|
||||
✅ CORRECTO: Extraer a servicio compartido
|
||||
UserService ──▶ SharedService ◀── OrderService
|
||||
```
|
||||
|
||||
```
|
||||
❌ INCORRECTO: Frontend importa de Backend
|
||||
apps/web/types/user.ts
|
||||
import { UserEntity } from '@backend/modules/user';
|
||||
|
||||
✅ CORRECTO: Frontend tiene sus propios tipos
|
||||
apps/web/types/user.ts
|
||||
export interface User { ... } // Definido independiente
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Matriz de Referencia
|
||||
714
core/orchestration/patrones/ANTIPATRONES.md
Normal file
714
core/orchestration/patrones/ANTIPATRONES.md
Normal file
@ -0,0 +1,714 @@
|
||||
# ANTIPATRONES: Lo que NUNCA Hacer
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - Consultar antes de implementar
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPÓSITO
|
||||
|
||||
Documentar antipatrones comunes para que agentes y subagentes los eviten. Cada antipatrón incluye el problema, por qué es malo, y la solución correcta.
|
||||
|
||||
---
|
||||
|
||||
## 1. ANTIPATRONES DE DATABASE
|
||||
|
||||
### DB-001: Crear tabla sin schema
|
||||
|
||||
```sql
|
||||
-- ❌ INCORRECTO
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY
|
||||
);
|
||||
|
||||
-- ✅ CORRECTO
|
||||
CREATE TABLE auth.users (
|
||||
id UUID PRIMARY KEY
|
||||
);
|
||||
```
|
||||
|
||||
**Por qué es malo:** Sin schema, la tabla va a `public`, mezclando dominios y dificultando permisos.
|
||||
|
||||
---
|
||||
|
||||
### DB-002: Columna NOT NULL sin DEFAULT en tabla existente
|
||||
|
||||
```sql
|
||||
-- ❌ INCORRECTO (en tabla con datos)
|
||||
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL;
|
||||
|
||||
-- ✅ CORRECTO
|
||||
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';
|
||||
```
|
||||
|
||||
**Por qué es malo:** Falla en INSERT para registros existentes que no tienen valor.
|
||||
|
||||
---
|
||||
|
||||
### DB-003: Foreign Key sin ON DELETE
|
||||
|
||||
```sql
|
||||
-- ❌ INCORRECTO
|
||||
CREATE TABLE orders (
|
||||
user_id UUID REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- ✅ CORRECTO
|
||||
CREATE TABLE orders (
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE
|
||||
-- o ON DELETE SET NULL, ON DELETE RESTRICT según el caso
|
||||
);
|
||||
```
|
||||
|
||||
**Por qué es malo:** Al eliminar usuario, los orders quedan huérfanos o el DELETE falla sin explicación clara.
|
||||
|
||||
---
|
||||
|
||||
### DB-004: Usar TEXT cuando debería ser ENUM
|
||||
|
||||
```sql
|
||||
-- ❌ INCORRECTO
|
||||
CREATE TABLE users (
|
||||
status TEXT -- Permite cualquier valor
|
||||
);
|
||||
|
||||
-- ✅ CORRECTO
|
||||
CREATE TYPE user_status AS ENUM ('active', 'inactive', 'suspended');
|
||||
CREATE TABLE users (
|
||||
status user_status DEFAULT 'active'
|
||||
);
|
||||
|
||||
-- O con CHECK constraint
|
||||
CREATE TABLE users (
|
||||
status VARCHAR(20) CHECK (status IN ('active', 'inactive', 'suspended'))
|
||||
);
|
||||
```
|
||||
|
||||
**Por qué es malo:** TEXT permite valores inválidos, causando bugs silenciosos.
|
||||
|
||||
---
|
||||
|
||||
### DB-005: Índice faltante en columna de búsqueda frecuente
|
||||
|
||||
```sql
|
||||
-- ❌ INCORRECTO
|
||||
CREATE TABLE products (
|
||||
sku VARCHAR(50) UNIQUE -- Sin índice adicional para búsquedas
|
||||
);
|
||||
|
||||
-- ✅ CORRECTO
|
||||
CREATE TABLE products (
|
||||
sku VARCHAR(50) UNIQUE
|
||||
);
|
||||
CREATE INDEX idx_products_sku ON products(sku);
|
||||
-- UNIQUE ya crea índice, pero para búsquedas parciales:
|
||||
CREATE INDEX idx_products_sku_pattern ON products(sku varchar_pattern_ops);
|
||||
```
|
||||
|
||||
**Por qué es malo:** Queries lentas en tablas grandes (full table scan).
|
||||
|
||||
---
|
||||
|
||||
### DB-006: Guardar contraseñas en texto plano
|
||||
|
||||
```sql
|
||||
-- ❌ INCORRECTO
|
||||
CREATE TABLE users (
|
||||
password VARCHAR(100) -- Almacena "mipassword123"
|
||||
);
|
||||
|
||||
-- ✅ CORRECTO
|
||||
CREATE TABLE users (
|
||||
password_hash VARCHAR(255) -- Almacena hash bcrypt
|
||||
);
|
||||
-- Hashing se hace en backend, no en SQL
|
||||
```
|
||||
|
||||
**Por qué es malo:** Vulnerabilidad de seguridad crítica.
|
||||
|
||||
---
|
||||
|
||||
## 2. ANTIPATRONES DE BACKEND
|
||||
|
||||
### BE-001: Lógica de negocio en Controller
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
@Controller('orders')
|
||||
export class OrderController {
|
||||
@Post()
|
||||
async create(@Body() dto: CreateOrderDto) {
|
||||
// Lógica de negocio en controller
|
||||
const total = dto.items.reduce((sum, item) => sum + item.price * item.qty, 0);
|
||||
const tax = total * 0.16;
|
||||
const discount = dto.couponCode ? await this.calculateDiscount() : 0;
|
||||
// ... más lógica
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECTO
|
||||
@Controller('orders')
|
||||
export class OrderController {
|
||||
@Post()
|
||||
async create(@Body() dto: CreateOrderDto) {
|
||||
return this.orderService.create(dto); // Delega a service
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class OrderService {
|
||||
async create(dto: CreateOrderDto) {
|
||||
const total = this.calculateTotal(dto.items);
|
||||
const tax = this.calculateTax(total);
|
||||
// Lógica de negocio en service
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Por qué es malo:** Controller difícil de testear, lógica no reutilizable, violación de SRP.
|
||||
|
||||
---
|
||||
|
||||
### BE-002: Query directo en Service sin Repository
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
async findActive() {
|
||||
// Query directo con QueryBuilder
|
||||
return this.connection.query(`
|
||||
SELECT * FROM users WHERE status = 'active'
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECTO
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
@InjectRepository(UserEntity)
|
||||
private readonly repository: Repository<UserEntity>,
|
||||
) {}
|
||||
|
||||
async findActive() {
|
||||
return this.repository.find({
|
||||
where: { status: UserStatus.ACTIVE },
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Por qué es malo:** No tipado, vulnerable a SQL injection, difícil de mantener.
|
||||
|
||||
---
|
||||
|
||||
### BE-003: Validación en Service en lugar de DTO
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
async create(dto: any) {
|
||||
if (!dto.email) throw new BadRequestException('Email required');
|
||||
if (!dto.email.includes('@')) throw new BadRequestException('Invalid email');
|
||||
if (dto.name.length < 2) throw new BadRequestException('Name too short');
|
||||
// ... más validaciones manuales
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECTO
|
||||
export class CreateUserDto {
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
name: string;
|
||||
}
|
||||
|
||||
// Service recibe DTO ya validado
|
||||
```
|
||||
|
||||
**Por qué es malo:** Validación duplicada, inconsistente, no documentada en Swagger.
|
||||
|
||||
---
|
||||
|
||||
### BE-004: Catch vacío o silencioso
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
async processPayment() {
|
||||
try {
|
||||
await this.paymentGateway.charge();
|
||||
} catch (e) {
|
||||
// Silencioso - error perdido
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ TAMBIÉN INCORRECTO
|
||||
async processPayment() {
|
||||
try {
|
||||
await this.paymentGateway.charge();
|
||||
} catch (e) {
|
||||
console.log(e); // Solo log, sin manejar
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECTO
|
||||
async processPayment() {
|
||||
try {
|
||||
await this.paymentGateway.charge();
|
||||
} catch (error) {
|
||||
this.logger.error('Payment failed', { error, context: 'payment' });
|
||||
throw new InternalServerErrorException('Error procesando pago');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Por qué es malo:** Errores silenciosos causan bugs imposibles de debuggear.
|
||||
|
||||
---
|
||||
|
||||
### BE-005: Entity no alineada con DDL
|
||||
|
||||
```typescript
|
||||
// DDL tiene:
|
||||
// status VARCHAR(20) CHECK (status IN ('active', 'inactive'))
|
||||
|
||||
// ❌ INCORRECTO
|
||||
@Column()
|
||||
status: string; // No valida valores
|
||||
|
||||
// ✅ CORRECTO
|
||||
enum UserStatus {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
}
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
status: UserStatus;
|
||||
```
|
||||
|
||||
**Por qué es malo:** Entity permite valores que BD rechaza, errores en runtime.
|
||||
|
||||
---
|
||||
|
||||
### BE-006: Hardcodear configuración
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
const API_URL = 'https://api.stripe.com/v1';
|
||||
const DB_HOST = '192.168.1.100';
|
||||
|
||||
// ✅ CORRECTO
|
||||
const API_URL = this.configService.get('STRIPE_API_URL');
|
||||
const DB_HOST = this.configService.get('DB_HOST');
|
||||
|
||||
// O usar decorador
|
||||
@Injectable()
|
||||
export class PaymentService {
|
||||
constructor(
|
||||
@Inject('STRIPE_CONFIG')
|
||||
private readonly config: StripeConfig,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
**Por qué es malo:** Difícil cambiar entre ambientes, secretos expuestos en código.
|
||||
|
||||
---
|
||||
|
||||
### BE-007: N+1 Query Problem
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
async getOrdersWithItems() {
|
||||
const orders = await this.orderRepository.find();
|
||||
for (const order of orders) {
|
||||
order.items = await this.itemRepository.find({
|
||||
where: { orderId: order.id }
|
||||
});
|
||||
}
|
||||
return orders;
|
||||
}
|
||||
// Resultado: 1 query para orders + N queries para items
|
||||
|
||||
// ✅ CORRECTO
|
||||
async getOrdersWithItems() {
|
||||
return this.orderRepository.find({
|
||||
relations: ['items'], // JOIN en una query
|
||||
});
|
||||
}
|
||||
// Resultado: 1 query con JOIN
|
||||
```
|
||||
|
||||
**Por qué es malo:** Performance terrible en listas grandes (100 orders = 101 queries).
|
||||
|
||||
---
|
||||
|
||||
## 3. ANTIPATRONES DE FRONTEND
|
||||
|
||||
### FE-001: Fetch directo en componente
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
const UserProfile = () => {
|
||||
const [user, setUser] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/users/me')
|
||||
.then(res => res.json())
|
||||
.then(setUser);
|
||||
}, []);
|
||||
|
||||
return <div>{user?.name}</div>;
|
||||
};
|
||||
|
||||
// ✅ CORRECTO
|
||||
// hooks/useUser.ts
|
||||
const useUser = () => {
|
||||
return useQuery({
|
||||
queryKey: ['user', 'me'],
|
||||
queryFn: () => userService.getMe(),
|
||||
});
|
||||
};
|
||||
|
||||
// components/UserProfile.tsx
|
||||
const UserProfile = () => {
|
||||
const { data: user, isLoading, error } = useUser();
|
||||
|
||||
if (isLoading) return <Loading />;
|
||||
if (error) return <Error error={error} />;
|
||||
|
||||
return <div>{user.name}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
**Por qué es malo:** No cachea, no maneja loading/error, lógica no reutilizable.
|
||||
|
||||
---
|
||||
|
||||
### FE-002: Hardcodear URLs de API
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
const response = await fetch('http://localhost:3000/api/users');
|
||||
|
||||
// ✅ CORRECTO
|
||||
// config.ts
|
||||
export const API_URL = import.meta.env.VITE_API_URL;
|
||||
|
||||
// services/api.ts
|
||||
const api = axios.create({
|
||||
baseURL: API_URL,
|
||||
});
|
||||
```
|
||||
|
||||
**Por qué es malo:** Rompe en producción, difícil cambiar backend.
|
||||
|
||||
---
|
||||
|
||||
### FE-003: Estado global para todo
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO - Redux/Zustand para estado de formulario
|
||||
const formStore = create((set) => ({
|
||||
name: '',
|
||||
email: '',
|
||||
setName: (name) => set({ name }),
|
||||
setEmail: (email) => set({ email }),
|
||||
}));
|
||||
|
||||
// ✅ CORRECTO - Estado local para formularios
|
||||
const UserForm = () => {
|
||||
const [name, setName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
// O usar react-hook-form
|
||||
};
|
||||
|
||||
// Store global SOLO para:
|
||||
// - Auth state (usuario logueado)
|
||||
// - UI state (tema, sidebar abierto)
|
||||
// - Cache de server state (mejor usar React Query)
|
||||
```
|
||||
|
||||
**Por qué es malo:** Complejidad innecesaria, renders innecesarios, difícil de seguir.
|
||||
|
||||
---
|
||||
|
||||
### FE-004: Props drilling excesivo
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
<App user={user} theme={theme}>
|
||||
<Layout user={user} theme={theme}>
|
||||
<Sidebar user={user} theme={theme}>
|
||||
<UserMenu user={user} theme={theme}>
|
||||
<Avatar user={user} /> // Finalmente se usa
|
||||
</UserMenu>
|
||||
</Sidebar>
|
||||
</Layout>
|
||||
</App>
|
||||
|
||||
// ✅ CORRECTO - Context para datos compartidos
|
||||
const UserContext = createContext<User | null>(null);
|
||||
const useUser = () => useContext(UserContext);
|
||||
|
||||
<UserProvider value={user}>
|
||||
<App>
|
||||
<Layout>
|
||||
<Sidebar>
|
||||
<UserMenu>
|
||||
<Avatar /> // Usa useUser() internamente
|
||||
</UserMenu>
|
||||
</Sidebar>
|
||||
</Layout>
|
||||
</App>
|
||||
</UserProvider>
|
||||
```
|
||||
|
||||
**Por qué es malo:** Componentes intermedios reciben props que no usan, refactoring doloroso.
|
||||
|
||||
---
|
||||
|
||||
### FE-005: useEffect para todo
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
const ProductList = ({ categoryId }) => {
|
||||
const [products, setProducts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
fetch(`/api/products?category=${categoryId}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
setProducts(data);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [categoryId]);
|
||||
// Race conditions, no cleanup, no error handling
|
||||
};
|
||||
|
||||
// ✅ CORRECTO - React Query
|
||||
const ProductList = ({ categoryId }) => {
|
||||
const { data: products, isLoading } = useQuery({
|
||||
queryKey: ['products', categoryId],
|
||||
queryFn: () => productService.getByCategory(categoryId),
|
||||
});
|
||||
// Maneja cache, loading, error, race conditions automáticamente
|
||||
};
|
||||
```
|
||||
|
||||
**Por qué es malo:** Race conditions, memory leaks, re-inventar la rueda.
|
||||
|
||||
---
|
||||
|
||||
### FE-006: Tipos no sincronizados con backend
|
||||
|
||||
```typescript
|
||||
// Backend DTO (actualizado)
|
||||
class UserDto {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
phone: string; // ← NUEVO CAMPO
|
||||
}
|
||||
|
||||
// ❌ INCORRECTO - Frontend desactualizado
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
// Falta phone → undefined en runtime
|
||||
}
|
||||
|
||||
// ✅ CORRECTO - Mantener sincronizado
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
phone?: string; // Sincronizado con backend
|
||||
}
|
||||
```
|
||||
|
||||
**Por qué es malo:** Bugs silenciosos en runtime cuando backend cambia.
|
||||
|
||||
---
|
||||
|
||||
## 4. ANTIPATRONES DE ARQUITECTURA
|
||||
|
||||
### ARCH-001: Importar de capa incorrecta
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO - Controller importa de otro módulo directamente
|
||||
import { ProductEntity } from '../products/entities/product.entity';
|
||||
|
||||
// ✅ CORRECTO - Usar exports del módulo
|
||||
import { ProductService } from '../products/product.module';
|
||||
|
||||
// O mejor: dependency injection
|
||||
@Module({
|
||||
imports: [ProductModule],
|
||||
})
|
||||
export class OrderModule {}
|
||||
```
|
||||
|
||||
**Por qué es malo:** Acopla módulos, rompe encapsulamiento, dependencias circulares.
|
||||
|
||||
---
|
||||
|
||||
### ARCH-002: Duplicar código entre módulos
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO - Misma función en dos módulos
|
||||
// users/utils.ts
|
||||
function formatDate(date: Date) { ... }
|
||||
|
||||
// orders/utils.ts
|
||||
function formatDate(date: Date) { ... } // Duplicado
|
||||
|
||||
// ✅ CORRECTO - Shared utils
|
||||
// shared/utils/date.utils.ts
|
||||
export function formatDate(date: Date) { ... }
|
||||
```
|
||||
|
||||
**Por qué es malo:** Cambios deben hacerse en múltiples lugares, inconsistencias.
|
||||
|
||||
---
|
||||
|
||||
### ARCH-003: Circular dependencies
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
// user.service.ts
|
||||
import { OrderService } from '../orders/order.service';
|
||||
|
||||
// order.service.ts
|
||||
import { UserService } from '../users/user.service';
|
||||
|
||||
// ✅ CORRECTO - Usar forwardRef o reestructurar
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => OrderService))
|
||||
private orderService: OrderService,
|
||||
) {}
|
||||
}
|
||||
|
||||
// O mejor: Extraer lógica compartida a un tercer servicio
|
||||
```
|
||||
|
||||
**Por qué es malo:** Errores en runtime, código difícil de entender.
|
||||
|
||||
---
|
||||
|
||||
## 5. ANTIPATRONES DE TESTING
|
||||
|
||||
### TEST-001: Tests que dependen de orden
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO
|
||||
describe('UserService', () => {
|
||||
it('should create user', () => {
|
||||
const user = service.create({ email: 'test@test.com' });
|
||||
// Asume que este test corre primero
|
||||
});
|
||||
|
||||
it('should find user', () => {
|
||||
const user = service.findByEmail('test@test.com');
|
||||
// Depende del test anterior
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ CORRECTO - Tests independientes
|
||||
describe('UserService', () => {
|
||||
beforeEach(async () => {
|
||||
// Setup limpio para cada test
|
||||
await repository.clear();
|
||||
});
|
||||
|
||||
it('should create user', () => { ... });
|
||||
|
||||
it('should find user', async () => {
|
||||
// Crear datos necesarios en el test
|
||||
await repository.save({ email: 'test@test.com' });
|
||||
const user = await service.findByEmail('test@test.com');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### TEST-002: Mock incorrecto
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO - Mock parcial/inconsistente
|
||||
jest.mock('./user.service', () => ({
|
||||
findOne: jest.fn().mockResolvedValue({ id: '1' }),
|
||||
// Otros métodos no mockeados → undefined
|
||||
}));
|
||||
|
||||
// ✅ CORRECTO - Mock completo
|
||||
const mockUserService = {
|
||||
findOne: jest.fn(),
|
||||
findAll: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. CHECKLIST DE REVISIÓN
|
||||
|
||||
Antes de hacer commit, verificar que NO estás haciendo:
|
||||
|
||||
```
|
||||
Database:
|
||||
[ ] Tabla sin schema
|
||||
[ ] NOT NULL sin DEFAULT en tabla existente
|
||||
[ ] FK sin ON DELETE
|
||||
[ ] TEXT donde debería ser ENUM
|
||||
[ ] Índices faltantes
|
||||
|
||||
Backend:
|
||||
[ ] Lógica en Controller
|
||||
[ ] Queries directas sin Repository
|
||||
[ ] Validación en Service
|
||||
[ ] Catch vacío
|
||||
[ ] Entity desalineada con DDL
|
||||
[ ] Config hardcodeada
|
||||
[ ] N+1 queries
|
||||
|
||||
Frontend:
|
||||
[ ] Fetch en componente
|
||||
[ ] URLs hardcodeadas
|
||||
[ ] Store global para estado local
|
||||
[ ] Props drilling excesivo
|
||||
[ ] useEffect innecesario
|
||||
[ ] Tipos desactualizados
|
||||
|
||||
Arquitectura:
|
||||
[ ] Import de capa incorrecta
|
||||
[ ] Código duplicado
|
||||
[ ] Dependencias circulares
|
||||
|
||||
Testing:
|
||||
[ ] Tests dependientes
|
||||
[ ] Mocks incompletos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guía de Antipatrones
|
||||
499
core/orchestration/patrones/MAPEO-TIPOS-DDL-TYPESCRIPT.md
Normal file
499
core/orchestration/patrones/MAPEO-TIPOS-DDL-TYPESCRIPT.md
Normal file
@ -0,0 +1,499 @@
|
||||
# MAPEO DE TIPOS: PostgreSQL DDL → TypeScript/TypeORM
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - Consultar antes de crear Entity
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPÓSITO
|
||||
|
||||
Este documento define el mapeo EXACTO entre tipos de PostgreSQL y TypeScript/TypeORM para garantizar alineación 100% entre DDL y Entities.
|
||||
|
||||
---
|
||||
|
||||
## REGLA FUNDAMENTAL
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ DDL ES LA FUENTE DE VERDAD (DDL-First) ║
|
||||
║ ║
|
||||
║ 1. DDL define el tipo → Entity lo REFLEJA ║
|
||||
║ 2. NUNCA crear Entity sin DDL existente ║
|
||||
║ 3. Si DDL cambia → Entity DEBE cambiar ║
|
||||
║ 4. Si Entity no coincide → ERROR (corregir Entity, no DDL) ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TABLA DE MAPEO COMPLETA
|
||||
|
||||
### Tipos Numéricos
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `SMALLINT` | `number` | `@Column({ type: 'smallint' })` | -32,768 a 32,767 |
|
||||
| `INTEGER` | `number` | `@Column({ type: 'integer' })` | -2B a 2B |
|
||||
| `BIGINT` | `string` | `@Column({ type: 'bigint' })` | Usar string para precisión |
|
||||
| `DECIMAL(p,s)` | `string` | `@Column({ type: 'decimal', precision: p, scale: s })` | Usar string para dinero |
|
||||
| `NUMERIC(p,s)` | `string` | `@Column({ type: 'numeric', precision: p, scale: s })` | Igual que DECIMAL |
|
||||
| `REAL` | `number` | `@Column({ type: 'real' })` | Punto flotante 4 bytes |
|
||||
| `DOUBLE PRECISION` | `number` | `@Column({ type: 'double precision' })` | Punto flotante 8 bytes |
|
||||
| `SERIAL` | `number` | `@PrimaryGeneratedColumn()` | Auto-increment |
|
||||
| `BIGSERIAL` | `string` | `@PrimaryGeneratedColumn('increment')` | Auto-increment grande |
|
||||
|
||||
**Ejemplo Numéricos:**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE products (
|
||||
id SERIAL PRIMARY KEY,
|
||||
price DECIMAL(10,2) NOT NULL,
|
||||
quantity INTEGER DEFAULT 0,
|
||||
rating REAL
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Entity({ schema: 'inventory', name: 'products' })
|
||||
export class ProductEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
price: string; // String para precisión decimal
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
quantity: number;
|
||||
|
||||
@Column({ type: 'real', nullable: true })
|
||||
rating: number;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tipos de Texto
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `CHAR(n)` | `string` | `@Column({ type: 'char', length: n })` | Longitud fija |
|
||||
| `VARCHAR(n)` | `string` | `@Column({ type: 'varchar', length: n })` | Longitud variable |
|
||||
| `TEXT` | `string` | `@Column({ type: 'text' })` | Sin límite |
|
||||
|
||||
**Ejemplo Texto:**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE users (
|
||||
code CHAR(10) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
bio TEXT
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Column({ type: 'char', length: 10 })
|
||||
code: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
email: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
bio: string;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tipos de Fecha y Hora
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `DATE` | `Date` | `@Column({ type: 'date' })` | Solo fecha |
|
||||
| `TIME` | `string` | `@Column({ type: 'time' })` | Solo hora |
|
||||
| `TIMESTAMP` | `Date` | `@Column({ type: 'timestamp' })` | Fecha + hora sin TZ |
|
||||
| `TIMESTAMPTZ` | `Date` | `@Column({ type: 'timestamptz' })` | Fecha + hora con TZ |
|
||||
| `INTERVAL` | `string` | `@Column({ type: 'interval' })` | Intervalo de tiempo |
|
||||
|
||||
**Ejemplo Fechas:**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE events (
|
||||
event_date DATE NOT NULL,
|
||||
start_time TIME,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
duration INTERVAL
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Column({ type: 'date' })
|
||||
eventDate: Date;
|
||||
|
||||
@Column({ type: 'time', nullable: true })
|
||||
startTime: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamp' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({ type: 'interval', nullable: true })
|
||||
duration: string;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tipos Booleanos
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `BOOLEAN` | `boolean` | `@Column({ type: 'boolean' })` | true/false |
|
||||
|
||||
**Ejemplo Boolean:**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE users (
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
is_verified BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Column({ type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isVerified: boolean;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tipos UUID
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `UUID` | `string` | `@Column({ type: 'uuid' })` | Usar con gen_random_uuid() |
|
||||
|
||||
**Ejemplo UUID:**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenants(id)
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@ManyToOne(() => TenantEntity)
|
||||
@JoinColumn({ name: 'tenant_id' })
|
||||
tenant: TenantEntity;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tipos JSON
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `JSON` | `object \| any` | `@Column({ type: 'json' })` | Sin indexación |
|
||||
| `JSONB` | `object \| any` | `@Column({ type: 'jsonb' })` | Con indexación |
|
||||
|
||||
**Ejemplo JSON:**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE users (
|
||||
preferences JSONB DEFAULT '{}',
|
||||
metadata JSON
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity - Opción 1: any
|
||||
@Column({ type: 'jsonb', default: {} })
|
||||
preferences: any;
|
||||
|
||||
// Entity - Opción 2: Interface tipada (RECOMENDADO)
|
||||
interface UserPreferences {
|
||||
theme: 'light' | 'dark';
|
||||
notifications: boolean;
|
||||
language: string;
|
||||
}
|
||||
|
||||
@Column({ type: 'jsonb', default: {} })
|
||||
preferences: UserPreferences;
|
||||
|
||||
@Column({ type: 'json', nullable: true })
|
||||
metadata: Record<string, any>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tipos ENUM
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `CREATE TYPE ... AS ENUM` | `enum` | `@Column({ type: 'enum', enum: X })` | Definir enum TS |
|
||||
| `VARCHAR CHECK (IN ...)` | `enum \| string` | `@Column()` + validator | Alternativa sin tipo |
|
||||
|
||||
**Ejemplo ENUM (Recomendado):**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TYPE user_status AS ENUM ('active', 'inactive', 'suspended', 'deleted');
|
||||
|
||||
CREATE TABLE users (
|
||||
status user_status DEFAULT 'active'
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
export enum UserStatus {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
SUSPENDED = 'suspended',
|
||||
DELETED = 'deleted',
|
||||
}
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: UserStatus,
|
||||
default: UserStatus.ACTIVE,
|
||||
})
|
||||
status: UserStatus;
|
||||
```
|
||||
|
||||
**Ejemplo VARCHAR con CHECK (Alternativa):**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE orders (
|
||||
status VARCHAR(20) CHECK (status IN ('pending', 'processing', 'completed', 'cancelled'))
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity - Usar tipo union + validación en DTO
|
||||
type OrderStatus = 'pending' | 'processing' | 'completed' | 'cancelled';
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
status: OrderStatus;
|
||||
|
||||
// DTO - Validación obligatoria
|
||||
@IsIn(['pending', 'processing', 'completed', 'cancelled'])
|
||||
status: string;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tipos Array
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `INTEGER[]` | `number[]` | `@Column({ type: 'integer', array: true })` | Array de enteros |
|
||||
| `VARCHAR[]` | `string[]` | `@Column({ type: 'varchar', array: true })` | Array de strings |
|
||||
| `UUID[]` | `string[]` | `@Column({ type: 'uuid', array: true })` | Array de UUIDs |
|
||||
|
||||
**Ejemplo Arrays:**
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE posts (
|
||||
tags VARCHAR(50)[],
|
||||
category_ids UUID[]
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Column({ type: 'varchar', array: true, nullable: true })
|
||||
tags: string[];
|
||||
|
||||
@Column({ type: 'uuid', array: true, nullable: true })
|
||||
categoryIds: string[];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tipos Especiales
|
||||
|
||||
| PostgreSQL | TypeScript | TypeORM Column | Notas |
|
||||
|------------|------------|----------------|-------|
|
||||
| `BYTEA` | `Buffer` | `@Column({ type: 'bytea' })` | Datos binarios |
|
||||
| `INET` | `string` | `@Column({ type: 'inet' })` | Dirección IP |
|
||||
| `CIDR` | `string` | `@Column({ type: 'cidr' })` | Red IP |
|
||||
| `MACADDR` | `string` | `@Column({ type: 'macaddr' })` | Dirección MAC |
|
||||
|
||||
---
|
||||
|
||||
## MAPEO DE CONSTRAINTS
|
||||
|
||||
### NOT NULL
|
||||
|
||||
```sql
|
||||
-- DDL
|
||||
email VARCHAR(255) NOT NULL
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Column({ type: 'varchar', length: 255, nullable: false })
|
||||
email: string; // Sin ? = no nullable
|
||||
```
|
||||
|
||||
### DEFAULT
|
||||
|
||||
```sql
|
||||
-- DDL
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
status VARCHAR(20) DEFAULT 'active'
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@CreateDateColumn({ type: 'timestamp' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'active' })
|
||||
status: string;
|
||||
```
|
||||
|
||||
### UNIQUE
|
||||
|
||||
```sql
|
||||
-- DDL
|
||||
email VARCHAR(255) UNIQUE
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
email: string;
|
||||
```
|
||||
|
||||
### CHECK (No soportado directamente en TypeORM)
|
||||
|
||||
```sql
|
||||
-- DDL
|
||||
age INTEGER CHECK (age >= 0 AND age <= 150)
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity - Sin validación en Entity
|
||||
@Column({ type: 'integer' })
|
||||
age: number;
|
||||
|
||||
// DTO - OBLIGATORIO validar aquí
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(150)
|
||||
age: number;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MAPEO DE RELACIONES
|
||||
|
||||
### ONE-TO-MANY / MANY-TO-ONE
|
||||
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE orders (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity Order
|
||||
@ManyToOne(() => UserEntity, user => user.orders, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user: UserEntity;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
userId: string;
|
||||
|
||||
// Entity User
|
||||
@OneToMany(() => OrderEntity, order => order.user)
|
||||
orders: OrderEntity[];
|
||||
```
|
||||
|
||||
### MANY-TO-MANY
|
||||
|
||||
```sql
|
||||
-- DDL
|
||||
CREATE TABLE user_roles (
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (user_id, role_id)
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Entity User
|
||||
@ManyToMany(() => RoleEntity, role => role.users)
|
||||
@JoinTable({
|
||||
name: 'user_roles',
|
||||
joinColumn: { name: 'user_id', referencedColumnName: 'id' },
|
||||
inverseJoinColumn: { name: 'role_id', referencedColumnName: 'id' },
|
||||
})
|
||||
roles: RoleEntity[];
|
||||
|
||||
// Entity Role
|
||||
@ManyToMany(() => UserEntity, user => user.roles)
|
||||
users: UserEntity[];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CHECKLIST DE ALINEACIÓN
|
||||
|
||||
Antes de completar Entity, verificar:
|
||||
|
||||
```
|
||||
[ ] Cada columna DDL tiene @Column en Entity
|
||||
[ ] Tipos PostgreSQL mapeados correctamente (ver tabla)
|
||||
[ ] nullable: false para NOT NULL
|
||||
[ ] default: X para DEFAULT X
|
||||
[ ] unique: true para UNIQUE
|
||||
[ ] Relaciones (@ManyToOne, etc.) coinciden con FOREIGN KEY
|
||||
[ ] onDelete coincide con ON DELETE
|
||||
[ ] Nombre de tabla correcto en @Entity({ name: 'x' })
|
||||
[ ] Schema correcto en @Entity({ schema: 'x' })
|
||||
[ ] PK coincide (@PrimaryGeneratedColumn vs @PrimaryColumn)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ERRORES COMUNES
|
||||
|
||||
| Error | Causa | Solución |
|
||||
|-------|-------|----------|
|
||||
| `bigint` retorna string inesperado | BIGINT se mapea a string | Usar `string` en TypeScript |
|
||||
| Decimales pierden precisión | DECIMAL mapeado a number | Usar `string` para dinero |
|
||||
| Enum no reconocido | Tipo no creado en BD | Crear TYPE primero en DDL |
|
||||
| Array vacío falla | Array sin default | Agregar `default: []` |
|
||||
| Fecha inválida | TIMESTAMP vs TIMESTAMPTZ | Usar TIMESTAMPTZ para TZ |
|
||||
|
||||
---
|
||||
|
||||
## REFERENCIAS
|
||||
|
||||
- SIMCO-DDL.md - Crear tablas
|
||||
- SIMCO-BACKEND.md - Crear entities
|
||||
- PRINCIPIO-VALIDACION-OBLIGATORIA.md - Validar alineación
|
||||
|
||||
---
|
||||
|
||||
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patrón de Mapeo
|
||||
539
core/orchestration/patrones/NOMENCLATURA-UNIFICADA.md
Normal file
539
core/orchestration/patrones/NOMENCLATURA-UNIFICADA.md
Normal file
@ -0,0 +1,539 @@
|
||||
# NOMENCLATURA UNIFICADA
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - Seguir en todo el código
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPÓSITO
|
||||
|
||||
Definir convenciones de nomenclatura consistentes para todas las capas del sistema.
|
||||
|
||||
---
|
||||
|
||||
## 1. DIRECTORIOS
|
||||
|
||||
### Regla General
|
||||
|
||||
```
|
||||
lowercase-kebab-case
|
||||
```
|
||||
|
||||
### Ejemplos
|
||||
|
||||
```
|
||||
✅ CORRECTO ❌ INCORRECTO
|
||||
───────────────────────────────────────
|
||||
user-management/ UserManagement/
|
||||
auth-module/ AuthModule/
|
||||
shared-utils/ sharedUtils/
|
||||
api-v1/ API_V1/
|
||||
```
|
||||
|
||||
### Estructura por Capa
|
||||
|
||||
```
|
||||
DATABASE:
|
||||
db/
|
||||
├── schemas/
|
||||
│ ├── auth/
|
||||
│ │ └── tables/
|
||||
│ ├── core/
|
||||
│ └── {domain}/
|
||||
├── seeds/
|
||||
│ ├── dev/
|
||||
│ └── prod/
|
||||
└── scripts/
|
||||
|
||||
BACKEND (NestJS):
|
||||
src/
|
||||
├── modules/
|
||||
│ └── {module-name}/
|
||||
│ ├── entities/
|
||||
│ ├── dto/
|
||||
│ ├── services/
|
||||
│ ├── controllers/
|
||||
│ └── tests/
|
||||
├── shared/
|
||||
│ ├── config/
|
||||
│ ├── guards/
|
||||
│ ├── decorators/
|
||||
│ ├── filters/
|
||||
│ ├── interceptors/
|
||||
│ └── utils/
|
||||
└── main.ts
|
||||
|
||||
FRONTEND (React):
|
||||
src/
|
||||
├── apps/
|
||||
│ └── {app-name}/
|
||||
│ ├── components/
|
||||
│ ├── pages/
|
||||
│ └── hooks/
|
||||
├── shared/
|
||||
│ ├── components/
|
||||
│ │ ├── ui/ # Componentes base
|
||||
│ │ └── common/ # Componentes compartidos
|
||||
│ ├── hooks/
|
||||
│ ├── stores/
|
||||
│ ├── services/
|
||||
│ ├── types/
|
||||
│ └── utils/
|
||||
└── main.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. ARCHIVOS
|
||||
|
||||
### Por Capa
|
||||
|
||||
| Capa | Patrón | Ejemplo |
|
||||
|------|--------|---------|
|
||||
| **DDL** | `{NN}-{nombre}.sql` | `01-users.sql`, `02-products.sql` |
|
||||
| **Seed** | `{NN}-{nombre}.sql` | `01-admin-user.sql` |
|
||||
| **Entity** | `{nombre}.entity.ts` | `user.entity.ts` |
|
||||
| **DTO Create** | `create-{nombre}.dto.ts` | `create-user.dto.ts` |
|
||||
| **DTO Update** | `update-{nombre}.dto.ts` | `update-user.dto.ts` |
|
||||
| **DTO Response** | `{nombre}-response.dto.ts` | `user-response.dto.ts` |
|
||||
| **DTO Query** | `{nombre}-query.dto.ts` | `user-query.dto.ts` |
|
||||
| **Service** | `{nombre}.service.ts` | `user.service.ts` |
|
||||
| **Controller** | `{nombre}.controller.ts` | `user.controller.ts` |
|
||||
| **Module** | `{nombre}.module.ts` | `user.module.ts` |
|
||||
| **Guard** | `{nombre}.guard.ts` | `jwt-auth.guard.ts` |
|
||||
| **Strategy** | `{nombre}.strategy.ts` | `jwt.strategy.ts` |
|
||||
| **Filter** | `{nombre}.filter.ts` | `http-exception.filter.ts` |
|
||||
| **Interceptor** | `{nombre}.interceptor.ts` | `logging.interceptor.ts` |
|
||||
| **Decorator** | `{nombre}.decorator.ts` | `current-user.decorator.ts` |
|
||||
| **Component** | `{Nombre}.tsx` | `UserCard.tsx` |
|
||||
| **Page** | `{Nombre}Page.tsx` | `UserProfilePage.tsx` |
|
||||
| **Hook** | `use{Nombre}.ts` | `useUser.ts`, `useAuth.ts` |
|
||||
| **Store** | `{nombre}.store.ts` | `auth.store.ts` |
|
||||
| **Service (FE)** | `{nombre}.service.ts` | `user.service.ts` |
|
||||
| **Types** | `{nombre}.types.ts` | `user.types.ts` |
|
||||
| **Test Unit** | `{nombre}.spec.ts` | `user.service.spec.ts` |
|
||||
| **Test E2E** | `{nombre}.e2e-spec.ts` | `user.e2e-spec.ts` |
|
||||
| **Test Component** | `{Nombre}.test.tsx` | `UserCard.test.tsx` |
|
||||
|
||||
### DDL - Orden de Archivos
|
||||
|
||||
```sql
|
||||
-- Usar prefijo numérico para orden de ejecución
|
||||
01-users.sql -- Tablas base primero
|
||||
02-roles.sql -- Tablas relacionadas
|
||||
03-user-roles.sql -- Junction tables después
|
||||
04-permissions.sql
|
||||
10-products.sql -- Otro dominio, nuevo rango
|
||||
11-categories.sql
|
||||
12-product-categories.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. CLASES Y TIPOS
|
||||
|
||||
### TypeScript/NestJS
|
||||
|
||||
| Tipo | Patrón | Ejemplo |
|
||||
|------|--------|---------|
|
||||
| **Entity** | `{Nombre}Entity` | `UserEntity` |
|
||||
| **DTO** | `{Accion}{Nombre}Dto` | `CreateUserDto` |
|
||||
| **Service** | `{Nombre}Service` | `UserService` |
|
||||
| **Controller** | `{Nombre}Controller` | `UserController` |
|
||||
| **Module** | `{Nombre}Module` | `UserModule` |
|
||||
| **Guard** | `{Nombre}Guard` | `JwtAuthGuard` |
|
||||
| **Strategy** | `{Nombre}Strategy` | `JwtStrategy` |
|
||||
| **Filter** | `{Nombre}Filter` | `HttpExceptionFilter` |
|
||||
| **Interceptor** | `{Nombre}Interceptor` | `LoggingInterceptor` |
|
||||
| **Enum** | `{Nombre}` | `UserStatus`, `OrderState` |
|
||||
| **Interface** | `{Nombre}` o `I{Nombre}` | `User`, `IUserService` |
|
||||
| **Type** | `{Nombre}` | `UserWithRoles`, `OrderSummary` |
|
||||
|
||||
### Ejemplos Completos
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Entity({ schema: 'auth', name: 'users' })
|
||||
export class UserEntity { ... }
|
||||
|
||||
// DTOs
|
||||
export class CreateUserDto { ... }
|
||||
export class UpdateUserDto extends PartialType(CreateUserDto) { ... }
|
||||
export class UserResponseDto { ... }
|
||||
export class UserQueryDto { ... }
|
||||
|
||||
// Service
|
||||
@Injectable()
|
||||
export class UserService { ... }
|
||||
|
||||
// Controller
|
||||
@Controller('users')
|
||||
export class UserController { ... }
|
||||
|
||||
// Module
|
||||
@Module({})
|
||||
export class UserModule { ... }
|
||||
|
||||
// Enum
|
||||
export enum UserStatus {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
}
|
||||
|
||||
// Interface
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
// Type
|
||||
export type UserWithRoles = User & { roles: Role[] };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. FUNCIONES Y MÉTODOS
|
||||
|
||||
### Verbos Estándar
|
||||
|
||||
| Operación | Verbo | Ejemplo |
|
||||
|-----------|-------|---------|
|
||||
| Obtener uno | `find`, `get` | `findById()`, `getUser()` |
|
||||
| Obtener muchos | `findAll`, `list` | `findAll()`, `listUsers()` |
|
||||
| Crear | `create` | `create()`, `createUser()` |
|
||||
| Actualizar | `update` | `update()`, `updateUser()` |
|
||||
| Eliminar | `remove`, `delete` | `remove()`, `deleteUser()` |
|
||||
| Buscar | `search` | `search()`, `searchUsers()` |
|
||||
| Verificar | `is`, `has`, `can` | `isActive()`, `hasPermission()` |
|
||||
| Contar | `count` | `count()`, `countActive()` |
|
||||
| Validar | `validate` | `validateEmail()` |
|
||||
| Formatear | `format` | `formatDate()`, `formatPrice()` |
|
||||
| Parsear | `parse` | `parseJson()`, `parseDate()` |
|
||||
| Convertir | `to`, `from` | `toDto()`, `fromEntity()` |
|
||||
|
||||
### Ejemplos por Capa
|
||||
|
||||
```typescript
|
||||
// Service - CRUD estándar
|
||||
class UserService {
|
||||
async create(dto: CreateUserDto): Promise<UserEntity> { ... }
|
||||
async findAll(query: UserQueryDto): Promise<UserEntity[]> { ... }
|
||||
async findById(id: string): Promise<UserEntity> { ... }
|
||||
async findByEmail(email: string): Promise<UserEntity | null> { ... }
|
||||
async update(id: string, dto: UpdateUserDto): Promise<UserEntity> { ... }
|
||||
async remove(id: string): Promise<void> { ... }
|
||||
|
||||
// Métodos adicionales
|
||||
async activate(id: string): Promise<void> { ... }
|
||||
async deactivate(id: string): Promise<void> { ... }
|
||||
async assignRole(userId: string, roleId: string): Promise<void> { ... }
|
||||
async hasPermission(userId: string, permission: string): Promise<boolean> { ... }
|
||||
}
|
||||
|
||||
// Controller - Endpoints REST
|
||||
class UserController {
|
||||
@Get()
|
||||
async findAll(@Query() query: UserQueryDto) { ... }
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) { ... }
|
||||
|
||||
@Post()
|
||||
async create(@Body() dto: CreateUserDto) { ... }
|
||||
|
||||
@Put(':id')
|
||||
async update(@Param('id') id: string, @Body() dto: UpdateUserDto) { ... }
|
||||
|
||||
@Delete(':id')
|
||||
async remove(@Param('id') id: string) { ... }
|
||||
}
|
||||
|
||||
// Hook (Frontend)
|
||||
function useUsers() {
|
||||
return useQuery({ ... });
|
||||
}
|
||||
|
||||
function useUser(id: string) {
|
||||
return useQuery({ ... });
|
||||
}
|
||||
|
||||
function useCreateUser() {
|
||||
return useMutation({ ... });
|
||||
}
|
||||
|
||||
function useUpdateUser() {
|
||||
return useMutation({ ... });
|
||||
}
|
||||
|
||||
function useDeleteUser() {
|
||||
return useMutation({ ... });
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. VARIABLES Y CONSTANTES
|
||||
|
||||
### Variables
|
||||
|
||||
```typescript
|
||||
// camelCase
|
||||
const userId = '123';
|
||||
const isActive = true;
|
||||
const userCount = 10;
|
||||
const createdAt = new Date();
|
||||
|
||||
// Arrays en plural
|
||||
const users = [];
|
||||
const activeUsers = [];
|
||||
const userIds = ['1', '2', '3'];
|
||||
|
||||
// Maps/Records con sufijo
|
||||
const userMap = new Map<string, User>();
|
||||
const roleById = {}; // Record<string, Role>
|
||||
```
|
||||
|
||||
### Constantes
|
||||
|
||||
```typescript
|
||||
// UPPER_SNAKE_CASE para constantes
|
||||
const MAX_LOGIN_ATTEMPTS = 5;
|
||||
const DEFAULT_PAGE_SIZE = 20;
|
||||
const API_VERSION = 'v1';
|
||||
const JWT_EXPIRATION = '1d';
|
||||
|
||||
// Constantes de configuración
|
||||
const CONFIG = {
|
||||
MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB
|
||||
ALLOWED_EXTENSIONS: ['.jpg', '.png', '.pdf'],
|
||||
RATE_LIMIT: 100,
|
||||
};
|
||||
|
||||
// Rutas/Paths
|
||||
const ROUTES = {
|
||||
HOME: '/',
|
||||
LOGIN: '/auth/login',
|
||||
DASHBOARD: '/dashboard',
|
||||
USER_PROFILE: '/users/:id',
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. ENUMS
|
||||
|
||||
### Definición
|
||||
|
||||
```typescript
|
||||
// PascalCase para nombre
|
||||
// Valores en minúsculas (para BD) o UPPER_CASE (para constantes)
|
||||
|
||||
// Opción 1: Valores string (recomendado para BD)
|
||||
export enum UserStatus {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
SUSPENDED = 'suspended',
|
||||
DELETED = 'deleted',
|
||||
}
|
||||
|
||||
// Opción 2: Valores UPPER_CASE (para constantes internas)
|
||||
export enum HttpMethod {
|
||||
GET = 'GET',
|
||||
POST = 'POST',
|
||||
PUT = 'PUT',
|
||||
DELETE = 'DELETE',
|
||||
}
|
||||
|
||||
// Opción 3: Numéricos (solo si necesario)
|
||||
export enum Priority {
|
||||
LOW = 1,
|
||||
MEDIUM = 2,
|
||||
HIGH = 3,
|
||||
CRITICAL = 4,
|
||||
}
|
||||
```
|
||||
|
||||
### Uso
|
||||
|
||||
```typescript
|
||||
// Entity
|
||||
@Column({ type: 'enum', enum: UserStatus, default: UserStatus.ACTIVE })
|
||||
status: UserStatus;
|
||||
|
||||
// Validación DTO
|
||||
@IsEnum(UserStatus)
|
||||
status: UserStatus;
|
||||
|
||||
// Frontend
|
||||
const statusLabel = {
|
||||
[UserStatus.ACTIVE]: 'Activo',
|
||||
[UserStatus.INACTIVE]: 'Inactivo',
|
||||
[UserStatus.SUSPENDED]: 'Suspendido',
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. DATABASE (SQL)
|
||||
|
||||
### Tablas y Columnas
|
||||
|
||||
```sql
|
||||
-- Tablas: snake_case, plural
|
||||
CREATE TABLE users ( ... );
|
||||
CREATE TABLE product_categories ( ... );
|
||||
CREATE TABLE user_login_attempts ( ... );
|
||||
|
||||
-- Columnas: snake_case
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY,
|
||||
first_name VARCHAR(100),
|
||||
last_name VARCHAR(100),
|
||||
email VARCHAR(255),
|
||||
is_active BOOLEAN,
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Foreign Keys: {tabla_singular}_id
|
||||
CREATE TABLE orders (
|
||||
user_id UUID REFERENCES users(id),
|
||||
product_id UUID REFERENCES products(id)
|
||||
);
|
||||
|
||||
-- Junction Tables: {tabla1}_{tabla2} (orden alfabético)
|
||||
CREATE TABLE role_users ( ... ); -- ❌
|
||||
CREATE TABLE user_roles ( ... ); -- ✅ (u antes de r)
|
||||
```
|
||||
|
||||
### Índices y Constraints
|
||||
|
||||
```sql
|
||||
-- Índices: idx_{tabla}_{columnas}
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at);
|
||||
|
||||
-- Unique: uq_{tabla}_{columnas}
|
||||
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);
|
||||
|
||||
-- Foreign Key: fk_{tabla}_{referencia}
|
||||
ALTER TABLE orders ADD CONSTRAINT fk_orders_user
|
||||
FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
|
||||
-- Check: chk_{tabla}_{descripcion}
|
||||
ALTER TABLE users ADD CONSTRAINT chk_users_status
|
||||
CHECK (status IN ('active', 'inactive'));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. API ENDPOINTS
|
||||
|
||||
### Convenciones REST
|
||||
|
||||
```
|
||||
GET /api/v1/users # Listar
|
||||
GET /api/v1/users/:id # Obtener uno
|
||||
POST /api/v1/users # Crear
|
||||
PUT /api/v1/users/:id # Actualizar completo
|
||||
PATCH /api/v1/users/:id # Actualizar parcial
|
||||
DELETE /api/v1/users/:id # Eliminar
|
||||
|
||||
# Recursos anidados
|
||||
GET /api/v1/users/:id/orders # Órdenes del usuario
|
||||
POST /api/v1/users/:id/orders # Crear orden para usuario
|
||||
|
||||
# Acciones especiales
|
||||
POST /api/v1/users/:id/activate # Acción
|
||||
POST /api/v1/users/:id/deactivate # Acción
|
||||
POST /api/v1/auth/login # Auth
|
||||
POST /api/v1/auth/logout # Auth
|
||||
POST /api/v1/auth/refresh # Auth
|
||||
```
|
||||
|
||||
### URL Patterns
|
||||
|
||||
```
|
||||
✅ CORRECTO ❌ INCORRECTO
|
||||
──────────────────────────────────────────
|
||||
/users /user # Plural
|
||||
/users/:id /users/get/:id # Verbo innecesario
|
||||
/users/:id/orders /getUserOrders # Snake case, verbo
|
||||
/auth/login /doLogin # Verbo innecesario
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. COMPONENTES REACT
|
||||
|
||||
### Nombres
|
||||
|
||||
```typescript
|
||||
// PascalCase para componentes
|
||||
const UserCard = () => { ... };
|
||||
const ProductList = () => { ... };
|
||||
const LoginForm = () => { ... };
|
||||
|
||||
// Sufijos descriptivos
|
||||
const UserProfilePage = () => { ... }; // Página completa
|
||||
const UserEditModal = () => { ... }; // Modal
|
||||
const UserDeleteDialog = () => { ... }; // Dialog de confirmación
|
||||
const UserAvatarSkeleton = () => { ... }; // Loading skeleton
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
```typescript
|
||||
// Interface con Props suffix
|
||||
interface UserCardProps {
|
||||
user: User;
|
||||
onEdit?: (user: User) => void;
|
||||
onDelete?: (id: string) => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
const UserCard: React.FC<UserCardProps> = ({
|
||||
user,
|
||||
onEdit,
|
||||
onDelete,
|
||||
isLoading = false,
|
||||
}) => { ... };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. CHECKLIST DE NOMENCLATURA
|
||||
|
||||
```
|
||||
Antes de crear archivo:
|
||||
[ ] Nombre en formato correcto para su tipo
|
||||
[ ] Directorio correcto según capa
|
||||
[ ] Sin duplicados en nombre
|
||||
|
||||
Antes de crear clase/tipo:
|
||||
[ ] PascalCase
|
||||
[ ] Sufijo correcto (Entity, Dto, Service, etc.)
|
||||
[ ] Nombre descriptivo
|
||||
|
||||
Antes de crear función:
|
||||
[ ] camelCase
|
||||
[ ] Verbo al inicio
|
||||
[ ] Nombre describe qué hace
|
||||
|
||||
Antes de crear variable:
|
||||
[ ] camelCase
|
||||
[ ] Nombre descriptivo
|
||||
[ ] Plural para arrays
|
||||
|
||||
Antes de crear endpoint:
|
||||
[ ] Recurso en plural
|
||||
[ ] Sin verbos en URL
|
||||
[ ] Estructura RESTful
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guía de Nomenclatura
|
||||
745
core/orchestration/patrones/PATRON-CONFIGURACION.md
Normal file
745
core/orchestration/patrones/PATRON-CONFIGURACION.md
Normal file
@ -0,0 +1,745 @@
|
||||
# PATRON DE CONFIGURACION
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** RECOMENDADA - Seguir para consistencia
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Definir patrones estandarizados para manejo de configuracion en todas las capas, incluyendo variables de entorno, archivos de configuracion, y validacion de settings.
|
||||
|
||||
---
|
||||
|
||||
## 1. PRINCIPIOS FUNDAMENTALES
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ PRINCIPIOS DE CONFIGURACION ║
|
||||
╠══════════════════════════════════════════════════════════════════════╣
|
||||
║ ║
|
||||
║ 1. NUNCA hardcodear valores sensibles ║
|
||||
║ 2. SIEMPRE validar configuracion al iniciar ║
|
||||
║ 3. FALLAR RAPIDO si configuracion es invalida ║
|
||||
║ 4. SEPARAR configuracion por ambiente ║
|
||||
║ 5. CENTRALIZAR acceso a configuracion ║
|
||||
║ 6. DOCUMENTAR cada variable requerida ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. ESTRUCTURA DE ARCHIVOS
|
||||
|
||||
### Estructura Recomendada
|
||||
|
||||
```
|
||||
project/
|
||||
├── .env.example # Template con TODAS las variables (sin valores reales)
|
||||
├── .env # Valores locales (NUNCA en git)
|
||||
├── .env.development # Override para desarrollo
|
||||
├── .env.staging # Override para staging
|
||||
├── .env.production # Override para produccion (solo en servidor)
|
||||
│
|
||||
├── apps/backend/
|
||||
│ └── src/
|
||||
│ └── shared/
|
||||
│ └── config/
|
||||
│ ├── configuration.ts # Configuracion principal
|
||||
│ ├── config.validation.ts # Schema de validacion
|
||||
│ ├── database.config.ts # Config de BD
|
||||
│ ├── jwt.config.ts # Config de JWT
|
||||
│ └── index.ts # Export centralizado
|
||||
│
|
||||
└── apps/frontend/
|
||||
└── src/
|
||||
└── shared/
|
||||
└── config/
|
||||
├── env.ts # Variables de entorno
|
||||
└── constants.ts # Constantes de app
|
||||
```
|
||||
|
||||
### .env.example (Template)
|
||||
|
||||
```bash
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# CONFIGURACION DE BASE DE DATOS
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=myapp_development
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD= # REQUERIDO: Password de BD
|
||||
DB_SSL=false
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# CONFIGURACION DE JWT/AUTH
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
JWT_SECRET= # REQUERIDO: Minimo 32 caracteres
|
||||
JWT_EXPIRATION=1d
|
||||
JWT_REFRESH_EXPIRATION=7d
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# CONFIGURACION DE SERVIDOR
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
API_PREFIX=api
|
||||
CORS_ORIGINS=http://localhost:5173
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# CONFIGURACION DE SERVICIOS EXTERNOS
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
REDIS_URL=redis://localhost:6379
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# CONFIGURACION DE LOGGING
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
LOG_LEVEL=debug # trace|debug|info|warn|error
|
||||
LOG_FORMAT=pretty # pretty|json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. BACKEND (NestJS)
|
||||
|
||||
### Schema de Validacion con Joi
|
||||
|
||||
```typescript
|
||||
// src/shared/config/config.validation.ts
|
||||
import * as Joi from 'joi';
|
||||
|
||||
export const configValidationSchema = Joi.object({
|
||||
// Database
|
||||
DB_HOST: Joi.string().required(),
|
||||
DB_PORT: Joi.number().default(5432),
|
||||
DB_NAME: Joi.string().required(),
|
||||
DB_USER: Joi.string().required(),
|
||||
DB_PASSWORD: Joi.string().required(),
|
||||
DB_SSL: Joi.boolean().default(false),
|
||||
|
||||
// JWT
|
||||
JWT_SECRET: Joi.string().min(32).required(),
|
||||
JWT_EXPIRATION: Joi.string().default('1d'),
|
||||
JWT_REFRESH_EXPIRATION: Joi.string().default('7d'),
|
||||
|
||||
// Server
|
||||
NODE_ENV: Joi.string()
|
||||
.valid('development', 'staging', 'production', 'test')
|
||||
.default('development'),
|
||||
PORT: Joi.number().default(3000),
|
||||
API_PREFIX: Joi.string().default('api'),
|
||||
CORS_ORIGINS: Joi.string().default('*'),
|
||||
|
||||
// Redis (opcional)
|
||||
REDIS_URL: Joi.string().uri().optional(),
|
||||
|
||||
// SMTP (opcional)
|
||||
SMTP_HOST: Joi.string().optional(),
|
||||
SMTP_PORT: Joi.number().optional(),
|
||||
SMTP_USER: Joi.string().optional(),
|
||||
SMTP_PASSWORD: Joi.string().optional(),
|
||||
|
||||
// Logging
|
||||
LOG_LEVEL: Joi.string()
|
||||
.valid('trace', 'debug', 'info', 'warn', 'error')
|
||||
.default('info'),
|
||||
LOG_FORMAT: Joi.string().valid('pretty', 'json').default('json'),
|
||||
});
|
||||
```
|
||||
|
||||
### Configuracion Tipada
|
||||
|
||||
```typescript
|
||||
// src/shared/config/configuration.ts
|
||||
export interface DatabaseConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
name: string;
|
||||
user: string;
|
||||
password: string;
|
||||
ssl: boolean;
|
||||
}
|
||||
|
||||
export interface JwtConfig {
|
||||
secret: string;
|
||||
expiration: string;
|
||||
refreshExpiration: string;
|
||||
}
|
||||
|
||||
export interface ServerConfig {
|
||||
nodeEnv: string;
|
||||
port: number;
|
||||
apiPrefix: string;
|
||||
corsOrigins: string[];
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
database: DatabaseConfig;
|
||||
jwt: JwtConfig;
|
||||
server: ServerConfig;
|
||||
redis?: {
|
||||
url: string;
|
||||
};
|
||||
smtp?: {
|
||||
host: string;
|
||||
port: number;
|
||||
user: string;
|
||||
password: string;
|
||||
};
|
||||
logging: {
|
||||
level: string;
|
||||
format: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default (): AppConfig => ({
|
||||
database: {
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT, 10),
|
||||
name: process.env.DB_NAME,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
ssl: process.env.DB_SSL === 'true',
|
||||
},
|
||||
jwt: {
|
||||
secret: process.env.JWT_SECRET,
|
||||
expiration: process.env.JWT_EXPIRATION,
|
||||
refreshExpiration: process.env.JWT_REFRESH_EXPIRATION,
|
||||
},
|
||||
server: {
|
||||
nodeEnv: process.env.NODE_ENV,
|
||||
port: parseInt(process.env.PORT, 10),
|
||||
apiPrefix: process.env.API_PREFIX,
|
||||
corsOrigins: process.env.CORS_ORIGINS?.split(',') || ['*'],
|
||||
},
|
||||
redis: process.env.REDIS_URL
|
||||
? { url: process.env.REDIS_URL }
|
||||
: undefined,
|
||||
smtp: process.env.SMTP_HOST
|
||||
? {
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT, 10),
|
||||
user: process.env.SMTP_USER,
|
||||
password: process.env.SMTP_PASSWORD,
|
||||
}
|
||||
: undefined,
|
||||
logging: {
|
||||
level: process.env.LOG_LEVEL,
|
||||
format: process.env.LOG_FORMAT,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Registro en AppModule
|
||||
|
||||
```typescript
|
||||
// src/app.module.ts
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import configuration from './shared/config/configuration';
|
||||
import { configValidationSchema } from './shared/config/config.validation';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
load: [configuration],
|
||||
validationSchema: configValidationSchema,
|
||||
validationOptions: {
|
||||
abortEarly: true, // Fallar en primer error
|
||||
},
|
||||
expandVariables: true, // Permitir ${VAR} en valores
|
||||
}),
|
||||
// ... otros modulos
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
### Uso en Services
|
||||
|
||||
```typescript
|
||||
// src/modules/auth/services/auth.service.ts
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtConfig } from '@/shared/config/configuration';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly jwtConfig: JwtConfig;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
// Acceso tipado a configuracion
|
||||
this.jwtConfig = this.configService.get<JwtConfig>('jwt');
|
||||
}
|
||||
|
||||
async generateToken(userId: string): Promise<string> {
|
||||
return this.jwtService.sign(
|
||||
{ sub: userId },
|
||||
{
|
||||
secret: this.jwtConfig.secret,
|
||||
expiresIn: this.jwtConfig.expiration,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuraciones Especificas
|
||||
|
||||
```typescript
|
||||
// src/shared/config/database.config.ts
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
|
||||
export default registerAs('database', (): TypeOrmModuleOptions => ({
|
||||
type: 'postgres',
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT, 10),
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
ssl: process.env.DB_SSL === 'true'
|
||||
? { rejectUnauthorized: false }
|
||||
: false,
|
||||
autoLoadEntities: true,
|
||||
synchronize: false, // NUNCA true en produccion
|
||||
logging: process.env.NODE_ENV === 'development',
|
||||
}));
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/shared/config/jwt.config.ts
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import { JwtModuleOptions } from '@nestjs/jwt';
|
||||
|
||||
export default registerAs('jwt', (): JwtModuleOptions => ({
|
||||
secret: process.env.JWT_SECRET,
|
||||
signOptions: {
|
||||
expiresIn: process.env.JWT_EXPIRATION || '1d',
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. FRONTEND (React/Vite)
|
||||
|
||||
### Variables de Entorno
|
||||
|
||||
```typescript
|
||||
// src/shared/config/env.ts
|
||||
|
||||
// Vite expone variables con prefijo VITE_
|
||||
interface EnvConfig {
|
||||
apiUrl: string;
|
||||
apiTimeout: number;
|
||||
environment: 'development' | 'staging' | 'production';
|
||||
enableMockApi: boolean;
|
||||
sentryDsn?: string;
|
||||
gaTrackingId?: string;
|
||||
}
|
||||
|
||||
function validateEnv(): EnvConfig {
|
||||
const apiUrl = import.meta.env.VITE_API_URL;
|
||||
const environment = import.meta.env.VITE_ENVIRONMENT || 'development';
|
||||
|
||||
// Validar variables requeridas
|
||||
if (!apiUrl) {
|
||||
throw new Error('VITE_API_URL is required');
|
||||
}
|
||||
|
||||
return {
|
||||
apiUrl,
|
||||
apiTimeout: parseInt(import.meta.env.VITE_API_TIMEOUT || '30000', 10),
|
||||
environment: environment as EnvConfig['environment'],
|
||||
enableMockApi: import.meta.env.VITE_ENABLE_MOCK_API === 'true',
|
||||
sentryDsn: import.meta.env.VITE_SENTRY_DSN,
|
||||
gaTrackingId: import.meta.env.VITE_GA_TRACKING_ID,
|
||||
};
|
||||
}
|
||||
|
||||
export const env = validateEnv();
|
||||
|
||||
// Helpers
|
||||
export const isDev = env.environment === 'development';
|
||||
export const isProd = env.environment === 'production';
|
||||
export const isStaging = env.environment === 'staging';
|
||||
```
|
||||
|
||||
### Constantes de Aplicacion
|
||||
|
||||
```typescript
|
||||
// src/shared/config/constants.ts
|
||||
import { env } from './env';
|
||||
|
||||
export const APP_CONFIG = {
|
||||
// API
|
||||
API_URL: env.apiUrl,
|
||||
API_TIMEOUT: env.apiTimeout,
|
||||
|
||||
// Paginacion
|
||||
DEFAULT_PAGE_SIZE: 20,
|
||||
MAX_PAGE_SIZE: 100,
|
||||
|
||||
// UI
|
||||
TOAST_DURATION: 5000,
|
||||
DEBOUNCE_DELAY: 300,
|
||||
|
||||
// Storage keys
|
||||
STORAGE_KEYS: {
|
||||
AUTH_TOKEN: 'auth_token',
|
||||
REFRESH_TOKEN: 'refresh_token',
|
||||
USER_PREFERENCES: 'user_preferences',
|
||||
THEME: 'theme',
|
||||
},
|
||||
|
||||
// Feature flags (pueden venir de API)
|
||||
FEATURES: {
|
||||
DARK_MODE: true,
|
||||
NOTIFICATIONS: true,
|
||||
BETA_FEATURES: env.environment !== 'production',
|
||||
},
|
||||
} as const;
|
||||
|
||||
// Rutas
|
||||
export const ROUTES = {
|
||||
HOME: '/',
|
||||
LOGIN: '/auth/login',
|
||||
REGISTER: '/auth/register',
|
||||
DASHBOARD: '/dashboard',
|
||||
PROFILE: '/profile',
|
||||
SETTINGS: '/settings',
|
||||
USERS: '/users',
|
||||
USER_DETAIL: '/users/:id',
|
||||
} as const;
|
||||
|
||||
// API Endpoints
|
||||
export const API_ENDPOINTS = {
|
||||
AUTH: {
|
||||
LOGIN: '/auth/login',
|
||||
REGISTER: '/auth/register',
|
||||
REFRESH: '/auth/refresh',
|
||||
LOGOUT: '/auth/logout',
|
||||
},
|
||||
USERS: {
|
||||
BASE: '/users',
|
||||
BY_ID: (id: string) => `/users/${id}`,
|
||||
ME: '/users/me',
|
||||
},
|
||||
// ... otros endpoints
|
||||
} as const;
|
||||
```
|
||||
|
||||
### Uso en Componentes
|
||||
|
||||
```typescript
|
||||
// src/apps/web/hooks/useAuth.ts
|
||||
import { APP_CONFIG } from '@/shared/config/constants';
|
||||
|
||||
export const useAuth = () => {
|
||||
const login = async (credentials: LoginCredentials) => {
|
||||
const response = await api.post(API_ENDPOINTS.AUTH.LOGIN, credentials);
|
||||
|
||||
// Guardar token usando key centralizada
|
||||
localStorage.setItem(
|
||||
APP_CONFIG.STORAGE_KEYS.AUTH_TOKEN,
|
||||
response.data.accessToken,
|
||||
);
|
||||
};
|
||||
|
||||
return { login, /* ... */ };
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. DATABASE
|
||||
|
||||
### Configuracion de Conexion
|
||||
|
||||
```typescript
|
||||
// src/shared/config/typeorm.config.ts
|
||||
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export const dataSourceOptions: DataSourceOptions = {
|
||||
type: 'postgres',
|
||||
host: process.env.DB_HOST,
|
||||
port: parseInt(process.env.DB_PORT, 10),
|
||||
username: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
ssl: process.env.DB_SSL === 'true'
|
||||
? { rejectUnauthorized: false }
|
||||
: false,
|
||||
|
||||
// Entities
|
||||
entities: ['dist/**/*.entity.js'],
|
||||
|
||||
// Migrations
|
||||
migrations: ['dist/migrations/*.js'],
|
||||
migrationsTableName: 'migrations',
|
||||
|
||||
// Logging
|
||||
logging: process.env.DB_LOGGING === 'true',
|
||||
maxQueryExecutionTime: 1000, // Log queries > 1s
|
||||
|
||||
// Pool
|
||||
extra: {
|
||||
max: parseInt(process.env.DB_POOL_SIZE || '10', 10),
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 10000,
|
||||
},
|
||||
};
|
||||
|
||||
// Para CLI de TypeORM
|
||||
export default new DataSource(dataSourceOptions);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. PATRON POR AMBIENTE
|
||||
|
||||
### Configuracion Condicional
|
||||
|
||||
```typescript
|
||||
// src/shared/config/by-environment.ts
|
||||
type Environment = 'development' | 'staging' | 'production' | 'test';
|
||||
|
||||
interface EnvironmentConfig {
|
||||
api: {
|
||||
rateLimit: number;
|
||||
timeout: number;
|
||||
};
|
||||
cache: {
|
||||
ttl: number;
|
||||
enabled: boolean;
|
||||
};
|
||||
features: {
|
||||
debugMode: boolean;
|
||||
mockExternalApis: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const configs: Record<Environment, EnvironmentConfig> = {
|
||||
development: {
|
||||
api: {
|
||||
rateLimit: 1000, // Muy alto para desarrollo
|
||||
timeout: 60000,
|
||||
},
|
||||
cache: {
|
||||
ttl: 60,
|
||||
enabled: false, // Deshabilitado para desarrollo
|
||||
},
|
||||
features: {
|
||||
debugMode: true,
|
||||
mockExternalApis: true,
|
||||
},
|
||||
},
|
||||
staging: {
|
||||
api: {
|
||||
rateLimit: 100,
|
||||
timeout: 30000,
|
||||
},
|
||||
cache: {
|
||||
ttl: 300,
|
||||
enabled: true,
|
||||
},
|
||||
features: {
|
||||
debugMode: true,
|
||||
mockExternalApis: false,
|
||||
},
|
||||
},
|
||||
production: {
|
||||
api: {
|
||||
rateLimit: 60,
|
||||
timeout: 15000,
|
||||
},
|
||||
cache: {
|
||||
ttl: 3600,
|
||||
enabled: true,
|
||||
},
|
||||
features: {
|
||||
debugMode: false,
|
||||
mockExternalApis: false,
|
||||
},
|
||||
},
|
||||
test: {
|
||||
api: {
|
||||
rateLimit: 10000,
|
||||
timeout: 5000,
|
||||
},
|
||||
cache: {
|
||||
ttl: 0,
|
||||
enabled: false,
|
||||
},
|
||||
features: {
|
||||
debugMode: true,
|
||||
mockExternalApis: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function getEnvironmentConfig(): EnvironmentConfig {
|
||||
const env = (process.env.NODE_ENV || 'development') as Environment;
|
||||
return configs[env];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. SECRETOS Y SEGURIDAD
|
||||
|
||||
### Nunca en Codigo
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: Secretos hardcodeados
|
||||
const JWT_SECRET = 'my-super-secret-key-12345';
|
||||
const DB_PASSWORD = 'password123';
|
||||
|
||||
// ✅ CORRECTO: Desde variables de entorno
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD;
|
||||
```
|
||||
|
||||
### Validar Secretos Requeridos
|
||||
|
||||
```typescript
|
||||
// src/shared/config/secrets.validation.ts
|
||||
const REQUIRED_SECRETS = [
|
||||
'JWT_SECRET',
|
||||
'DB_PASSWORD',
|
||||
];
|
||||
|
||||
const OPTIONAL_SECRETS = [
|
||||
'SMTP_PASSWORD',
|
||||
'STRIPE_SECRET_KEY',
|
||||
];
|
||||
|
||||
export function validateSecrets(): void {
|
||||
const missing: string[] = [];
|
||||
|
||||
for (const secret of REQUIRED_SECRETS) {
|
||||
if (!process.env[secret]) {
|
||||
missing.push(secret);
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(
|
||||
`Missing required secrets: ${missing.join(', ')}\n` +
|
||||
'Please check your .env file or environment variables.',
|
||||
);
|
||||
}
|
||||
|
||||
// Validar formato de secretos
|
||||
if (process.env.JWT_SECRET && process.env.JWT_SECRET.length < 32) {
|
||||
throw new Error('JWT_SECRET must be at least 32 characters');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Rotacion de Secretos
|
||||
|
||||
```typescript
|
||||
// Soportar multiples secretos para rotacion
|
||||
const JWT_SECRETS = (process.env.JWT_SECRETS || process.env.JWT_SECRET).split(',');
|
||||
|
||||
// Verificar token con cualquier secreto valido
|
||||
async function verifyToken(token: string): Promise<TokenPayload> {
|
||||
for (const secret of JWT_SECRETS) {
|
||||
try {
|
||||
return jwt.verify(token, secret.trim()) as TokenPayload;
|
||||
} catch {
|
||||
continue; // Probar siguiente secreto
|
||||
}
|
||||
}
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
|
||||
// Firmar siempre con el primer secreto (mas reciente)
|
||||
function signToken(payload: TokenPayload): string {
|
||||
return jwt.sign(payload, JWT_SECRETS[0].trim());
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. CHECKLIST DE CONFIGURACION
|
||||
|
||||
```
|
||||
Setup inicial:
|
||||
[ ] .env.example creado con TODAS las variables
|
||||
[ ] .env en .gitignore
|
||||
[ ] Schema de validacion implementado
|
||||
[ ] App falla si configuracion invalida
|
||||
[ ] Tipos definidos para configuracion
|
||||
|
||||
Seguridad:
|
||||
[ ] Ningun secreto en codigo fuente
|
||||
[ ] Ningun secreto en logs
|
||||
[ ] Secretos rotables (multiples valores soportados)
|
||||
[ ] Variables sensibles marcadas en documentacion
|
||||
|
||||
Por ambiente:
|
||||
[ ] Configuracion diferenciada por ambiente
|
||||
[ ] Defaults seguros para produccion
|
||||
[ ] Modo debug deshabilitado en produccion
|
||||
[ ] Rate limiting apropiado por ambiente
|
||||
|
||||
Documentacion:
|
||||
[ ] Cada variable documentada en .env.example
|
||||
[ ] README explica como configurar
|
||||
[ ] Variables opcionales vs requeridas claras
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. ANTI-PATRONES
|
||||
|
||||
```typescript
|
||||
// ❌ ANTI-PATRON 1: Configuracion dispersa
|
||||
// archivo1.ts
|
||||
const API_URL = 'http://api.com';
|
||||
// archivo2.ts
|
||||
const apiUrl = process.env.API_URL || 'http://api.com';
|
||||
|
||||
// ✅ CORRECTO: Centralizado
|
||||
// config/constants.ts
|
||||
export const API_URL = process.env.API_URL;
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ❌ ANTI-PATRON 2: No validar
|
||||
const port = process.env.PORT; // Puede ser undefined o string invalido
|
||||
server.listen(port); // Error en runtime
|
||||
|
||||
// ✅ CORRECTO: Validar y convertir
|
||||
const port = parseInt(process.env.PORT, 10);
|
||||
if (isNaN(port)) {
|
||||
throw new Error('PORT must be a valid number');
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ❌ ANTI-PATRON 3: Defaults inseguros
|
||||
const DEBUG = process.env.DEBUG || true; // Debug activo por default
|
||||
|
||||
// ✅ CORRECTO: Defaults seguros
|
||||
const DEBUG = process.env.DEBUG === 'true'; // False por default
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patron de Codigo
|
||||
534
core/orchestration/patrones/PATRON-EXCEPTION-HANDLING.md
Normal file
534
core/orchestration/patrones/PATRON-EXCEPTION-HANDLING.md
Normal file
@ -0,0 +1,534 @@
|
||||
# PATRÓN: MANEJO DE EXCEPCIONES
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Aplica a:** Backend (NestJS/Express)
|
||||
**Prioridad:** OBLIGATORIA
|
||||
|
||||
---
|
||||
|
||||
## PROPÓSITO
|
||||
|
||||
Definir patrones estándar de manejo de errores para garantizar respuestas consistentes y debugging efectivo.
|
||||
|
||||
---
|
||||
|
||||
## PRINCIPIO FUNDAMENTAL
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ EXCEPCIONES CLARAS Y CONSISTENTES ║
|
||||
║ ║
|
||||
║ 1. Usar HttpException estándar de NestJS ║
|
||||
║ 2. Mensajes claros para el usuario ║
|
||||
║ 3. Detalles técnicos en logs (no en response) ║
|
||||
║ 4. Códigos HTTP semánticos ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. EXCEPCIONES HTTP ESTÁNDAR
|
||||
|
||||
### Matriz de Decisión
|
||||
|
||||
| Situación | Exception | HTTP Code | Cuándo Usar |
|
||||
|-----------|-----------|-----------|-------------|
|
||||
| Recurso no existe | `NotFoundException` | 404 | `findOne` retorna null |
|
||||
| Ya existe (duplicado) | `ConflictException` | 409 | Violación de unique |
|
||||
| Datos inválidos | `BadRequestException` | 400 | Validación de negocio falla |
|
||||
| Sin autenticación | `UnauthorizedException` | 401 | Token falta o inválido |
|
||||
| Sin permiso | `ForbiddenException` | 403 | Autenticado pero sin permiso |
|
||||
| Método no permitido | `MethodNotAllowedException` | 405 | HTTP method incorrecto |
|
||||
| Payload muy grande | `PayloadTooLargeException` | 413 | Archivo/body excede límite |
|
||||
| Rate limit | `TooManyRequestsException` | 429 | Muchas peticiones |
|
||||
| Error interno | `InternalServerErrorException` | 500 | Error inesperado |
|
||||
| Servicio no disponible | `ServiceUnavailableException` | 503 | DB/API externa caída |
|
||||
|
||||
---
|
||||
|
||||
## 2. PATRONES POR CASO
|
||||
|
||||
### Not Found (404)
|
||||
|
||||
```typescript
|
||||
// PATRÓN: Recurso no encontrado
|
||||
async findOne(id: string): Promise<UserEntity> {
|
||||
const user = await this.repository.findOne({ where: { id } });
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException(`Usuario con ID ${id} no encontrado`);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
// PATRÓN: Recurso relacionado no encontrado
|
||||
async assignRole(userId: string, roleId: string): Promise<void> {
|
||||
const user = await this.userRepository.findOne({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new NotFoundException(`Usuario ${userId} no encontrado`);
|
||||
}
|
||||
|
||||
const role = await this.roleRepository.findOne({ where: { id: roleId } });
|
||||
if (!role) {
|
||||
throw new NotFoundException(`Rol ${roleId} no encontrado`);
|
||||
}
|
||||
|
||||
// Proceder...
|
||||
}
|
||||
```
|
||||
|
||||
### Conflict (409)
|
||||
|
||||
```typescript
|
||||
// PATRÓN: Duplicado por campo único
|
||||
async create(dto: CreateUserDto): Promise<UserEntity> {
|
||||
const existing = await this.repository.findOne({
|
||||
where: { email: dto.email },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictException('El email ya está registrado');
|
||||
}
|
||||
|
||||
return this.repository.save(this.repository.create(dto));
|
||||
}
|
||||
|
||||
// PATRÓN: Duplicado con múltiples campos
|
||||
async createProduct(dto: CreateProductDto): Promise<ProductEntity> {
|
||||
const existing = await this.repository.findOne({
|
||||
where: {
|
||||
sku: dto.sku,
|
||||
tenantId: dto.tenantId,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictException(
|
||||
`Ya existe un producto con SKU ${dto.sku} en este tenant`
|
||||
);
|
||||
}
|
||||
|
||||
return this.repository.save(this.repository.create(dto));
|
||||
}
|
||||
```
|
||||
|
||||
### Bad Request (400)
|
||||
|
||||
```typescript
|
||||
// PATRÓN: Validación de negocio
|
||||
async transfer(dto: TransferDto): Promise<void> {
|
||||
if (dto.fromAccountId === dto.toAccountId) {
|
||||
throw new BadRequestException(
|
||||
'La cuenta origen y destino no pueden ser iguales'
|
||||
);
|
||||
}
|
||||
|
||||
const fromAccount = await this.findAccount(dto.fromAccountId);
|
||||
|
||||
if (fromAccount.balance < dto.amount) {
|
||||
throw new BadRequestException('Saldo insuficiente para la transferencia');
|
||||
}
|
||||
|
||||
// Proceder...
|
||||
}
|
||||
|
||||
// PATRÓN: Estado inválido para operación
|
||||
async cancelOrder(orderId: string): Promise<void> {
|
||||
const order = await this.findOne(orderId);
|
||||
|
||||
if (order.status === 'delivered') {
|
||||
throw new BadRequestException(
|
||||
'No se puede cancelar un pedido ya entregado'
|
||||
);
|
||||
}
|
||||
|
||||
if (order.status === 'cancelled') {
|
||||
throw new BadRequestException('El pedido ya está cancelado');
|
||||
}
|
||||
|
||||
// Proceder...
|
||||
}
|
||||
```
|
||||
|
||||
### Forbidden (403)
|
||||
|
||||
```typescript
|
||||
// PATRÓN: Sin permiso sobre recurso
|
||||
async update(userId: string, dto: UpdateUserDto, currentUser: User): Promise<UserEntity> {
|
||||
const user = await this.findOne(userId);
|
||||
|
||||
// Solo el usuario mismo o admin puede editar
|
||||
if (user.id !== currentUser.id && !currentUser.roles.includes('admin')) {
|
||||
throw new ForbiddenException('No tienes permiso para editar este usuario');
|
||||
}
|
||||
|
||||
return this.repository.save({ ...user, ...dto });
|
||||
}
|
||||
|
||||
// PATRÓN: Límite de plan/tenant
|
||||
async createProject(dto: CreateProjectDto, tenant: Tenant): Promise<Project> {
|
||||
const projectCount = await this.repository.count({
|
||||
where: { tenantId: tenant.id },
|
||||
});
|
||||
|
||||
if (projectCount >= tenant.plan.maxProjects) {
|
||||
throw new ForbiddenException(
|
||||
`Tu plan permite máximo ${tenant.plan.maxProjects} proyectos. ` +
|
||||
'Actualiza tu plan para crear más.'
|
||||
);
|
||||
}
|
||||
|
||||
// Proceder...
|
||||
}
|
||||
```
|
||||
|
||||
### Unauthorized (401)
|
||||
|
||||
```typescript
|
||||
// PATRÓN: Token inválido (en Guard)
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
handleRequest(err: any, user: any, info: any) {
|
||||
if (err || !user) {
|
||||
if (info?.name === 'TokenExpiredError') {
|
||||
throw new UnauthorizedException('Tu sesión ha expirado');
|
||||
}
|
||||
if (info?.name === 'JsonWebTokenError') {
|
||||
throw new UnauthorizedException('Token inválido');
|
||||
}
|
||||
throw new UnauthorizedException('No autenticado');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
// PATRÓN: Credenciales incorrectas
|
||||
async login(dto: LoginDto): Promise<TokenResponse> {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: { email: dto.email },
|
||||
});
|
||||
|
||||
if (!user || !(await bcrypt.compare(dto.password, user.password))) {
|
||||
throw new UnauthorizedException('Credenciales incorrectas');
|
||||
}
|
||||
|
||||
// Generar token...
|
||||
}
|
||||
```
|
||||
|
||||
### Internal Server Error (500)
|
||||
|
||||
```typescript
|
||||
// PATRÓN: Error inesperado con logging
|
||||
async processPayment(dto: PaymentDto): Promise<PaymentResult> {
|
||||
try {
|
||||
const result = await this.paymentGateway.charge(dto);
|
||||
return result;
|
||||
} catch (error) {
|
||||
// Log detallado para debugging
|
||||
this.logger.error('Error procesando pago', {
|
||||
dto,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
gatewayResponse: error.response?.data,
|
||||
});
|
||||
|
||||
// Respuesta genérica al usuario
|
||||
throw new InternalServerErrorException(
|
||||
'Error procesando el pago. Por favor intenta de nuevo.'
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. ESTRUCTURA DE RESPUESTA DE ERROR
|
||||
|
||||
### Formato Estándar
|
||||
|
||||
```typescript
|
||||
// Respuesta de error estándar
|
||||
interface ErrorResponse {
|
||||
statusCode: number;
|
||||
message: string | string[];
|
||||
error: string;
|
||||
timestamp: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
// Ejemplo de respuesta
|
||||
{
|
||||
"statusCode": 404,
|
||||
"message": "Usuario con ID abc-123 no encontrado",
|
||||
"error": "Not Found",
|
||||
"timestamp": "2024-01-15T10:30:00.000Z",
|
||||
"path": "/api/v1/users/abc-123"
|
||||
}
|
||||
```
|
||||
|
||||
### Exception Filter Global
|
||||
|
||||
```typescript
|
||||
// filters/http-exception.filter.ts
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
@Catch()
|
||||
export class GlobalExceptionFilter implements ExceptionFilter {
|
||||
private readonly logger = new Logger(GlobalExceptionFilter.name);
|
||||
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const request = ctx.getRequest<Request>();
|
||||
|
||||
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
let message: string | string[] = 'Error interno del servidor';
|
||||
let error = 'Internal Server Error';
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
status = exception.getStatus();
|
||||
const exceptionResponse = exception.getResponse();
|
||||
|
||||
if (typeof exceptionResponse === 'object') {
|
||||
message = (exceptionResponse as any).message || exception.message;
|
||||
error = (exceptionResponse as any).error || exception.name;
|
||||
} else {
|
||||
message = exceptionResponse;
|
||||
}
|
||||
} else if (exception instanceof Error) {
|
||||
// Log error interno completo
|
||||
this.logger.error('Unhandled exception', {
|
||||
message: exception.message,
|
||||
stack: exception.stack,
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
user: (request as any).user?.id,
|
||||
});
|
||||
}
|
||||
|
||||
response.status(status).json({
|
||||
statusCode: status,
|
||||
message,
|
||||
error,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. EXCEPCIONES PERSONALIZADAS
|
||||
|
||||
### Cuándo Crear Excepción Custom
|
||||
|
||||
```typescript
|
||||
// CREAR excepción custom cuando:
|
||||
// 1. Necesitas información adicional estructurada
|
||||
// 2. El error es específico del dominio
|
||||
// 3. Quieres diferenciar en handling
|
||||
|
||||
// NO crear custom para errores HTTP estándar
|
||||
// ❌ class UserNotFoundException extends HttpException {} // Usar NotFoundException
|
||||
```
|
||||
|
||||
### Ejemplo Excepción Custom
|
||||
|
||||
```typescript
|
||||
// exceptions/business.exception.ts
|
||||
export class InsufficientBalanceException extends BadRequestException {
|
||||
constructor(
|
||||
public readonly currentBalance: number,
|
||||
public readonly requiredAmount: number,
|
||||
) {
|
||||
super({
|
||||
message: `Saldo insuficiente. Tienes $${currentBalance}, necesitas $${requiredAmount}`,
|
||||
error: 'Insufficient Balance',
|
||||
currentBalance,
|
||||
requiredAmount,
|
||||
deficit: requiredAmount - currentBalance,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Uso
|
||||
if (account.balance < amount) {
|
||||
throw new InsufficientBalanceException(account.balance, amount);
|
||||
}
|
||||
```
|
||||
|
||||
### Excepción para Errores de Integración
|
||||
|
||||
```typescript
|
||||
// exceptions/integration.exception.ts
|
||||
export class PaymentGatewayException extends ServiceUnavailableException {
|
||||
constructor(
|
||||
public readonly gateway: string,
|
||||
public readonly originalError: string,
|
||||
) {
|
||||
super({
|
||||
message: 'Error de conexión con el servicio de pagos',
|
||||
error: 'Payment Gateway Error',
|
||||
gateway,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Uso
|
||||
try {
|
||||
await stripe.charges.create(params);
|
||||
} catch (error) {
|
||||
throw new PaymentGatewayException('Stripe', error.message);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. LOGGING DE ERRORES
|
||||
|
||||
### Niveles de Log
|
||||
|
||||
```typescript
|
||||
// logger.service.ts
|
||||
@Injectable()
|
||||
export class AppLogger {
|
||||
private readonly logger = new Logger();
|
||||
|
||||
// ERROR: Errores que requieren atención
|
||||
error(message: string, context: object) {
|
||||
this.logger.error(message, { ...context, timestamp: new Date() });
|
||||
}
|
||||
|
||||
// WARN: Situaciones anómalas pero manejadas
|
||||
warn(message: string, context: object) {
|
||||
this.logger.warn(message, { ...context, timestamp: new Date() });
|
||||
}
|
||||
|
||||
// INFO: Eventos importantes del negocio
|
||||
info(message: string, context: object) {
|
||||
this.logger.log(message, { ...context, timestamp: new Date() });
|
||||
}
|
||||
|
||||
// DEBUG: Información para desarrollo
|
||||
debug(message: string, context: object) {
|
||||
this.logger.debug(message, { ...context, timestamp: new Date() });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Qué Loggear
|
||||
|
||||
```typescript
|
||||
// SIEMPRE loggear en ERROR:
|
||||
{
|
||||
message: 'Descripción del error',
|
||||
stack: error.stack, // Stack trace
|
||||
userId: currentUser?.id, // Quién causó el error
|
||||
tenantId: currentUser?.tenant, // Contexto de tenant
|
||||
requestId: request.id, // Para tracing
|
||||
path: request.url, // Endpoint
|
||||
method: request.method, // HTTP method
|
||||
body: sanitize(request.body), // Body (sin passwords)
|
||||
query: request.query, // Query params
|
||||
timestamp: new Date(), // Cuándo
|
||||
}
|
||||
|
||||
// NUNCA loggear:
|
||||
// - Passwords
|
||||
// - Tokens
|
||||
// - Tarjetas de crédito
|
||||
// - Datos sensibles (CURP, RFC, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. DOCUMENTACIÓN SWAGGER
|
||||
|
||||
```typescript
|
||||
// Documentar posibles errores en controller
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Crear usuario' })
|
||||
@ApiResponse({ status: 201, description: 'Usuario creado', type: UserEntity })
|
||||
@ApiResponse({ status: 400, description: 'Datos inválidos' })
|
||||
@ApiResponse({ status: 409, description: 'Email ya registrado' })
|
||||
@ApiResponse({ status: 401, description: 'No autenticado' })
|
||||
@ApiResponse({ status: 403, description: 'Sin permisos' })
|
||||
async create(@Body() dto: CreateUserDto): Promise<UserEntity> {
|
||||
return this.service.create(dto);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. CHECKLIST DE MANEJO DE ERRORES
|
||||
|
||||
```
|
||||
Service:
|
||||
[ ] Cada método que busca por ID usa NotFoundException si no existe
|
||||
[ ] Operaciones de creación verifican duplicados → ConflictException
|
||||
[ ] Validaciones de negocio usan BadRequestException
|
||||
[ ] Verificaciones de permisos usan ForbiddenException
|
||||
[ ] Errores de integraciones externas tienen try/catch
|
||||
[ ] Errores inesperados se loggean con contexto completo
|
||||
[ ] Mensajes de error son claros para el usuario
|
||||
|
||||
Controller:
|
||||
[ ] Swagger documenta posibles errores
|
||||
[ ] @ApiResponse para cada código de error posible
|
||||
|
||||
Global:
|
||||
[ ] GlobalExceptionFilter configurado
|
||||
[ ] Logger configurado para errores
|
||||
[ ] Errores no exponen detalles técnicos en producción
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ANTI-PATRONES
|
||||
|
||||
```typescript
|
||||
// ❌ NUNCA: Mensaje genérico sin contexto
|
||||
throw new BadRequestException('Error');
|
||||
|
||||
// ✅ SIEMPRE: Mensaje descriptivo
|
||||
throw new BadRequestException('El email ya está registrado');
|
||||
|
||||
// ❌ NUNCA: Exponer stack trace en response
|
||||
throw new Error(error.stack);
|
||||
|
||||
// ✅ SIEMPRE: Log interno, mensaje limpio al usuario
|
||||
this.logger.error('Error detallado', { stack: error.stack });
|
||||
throw new InternalServerErrorException('Error procesando solicitud');
|
||||
|
||||
// ❌ NUNCA: Catch vacío
|
||||
try { ... } catch (e) { }
|
||||
|
||||
// ✅ SIEMPRE: Manejar o re-lanzar
|
||||
try { ... } catch (e) {
|
||||
this.logger.error('Context', { error: e });
|
||||
throw new InternalServerErrorException('Mensaje usuario');
|
||||
}
|
||||
|
||||
// ❌ NUNCA: 500 para errores de validación
|
||||
throw new InternalServerErrorException('Email inválido');
|
||||
|
||||
// ✅ SIEMPRE: Código HTTP semántico
|
||||
throw new BadRequestException('Email inválido');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patrón de Excepciones
|
||||
663
core/orchestration/patrones/PATRON-LOGGING.md
Normal file
663
core/orchestration/patrones/PATRON-LOGGING.md
Normal file
@ -0,0 +1,663 @@
|
||||
# PATRON DE LOGGING
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** RECOMENDADA - Seguir para consistencia
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Definir patrones estandarizados de logging para todas las capas del sistema, asegurando trazabilidad, debugging efectivo y monitoreo en produccion.
|
||||
|
||||
---
|
||||
|
||||
## 1. NIVELES DE LOG
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ NIVELES DE LOG (de menor a mayor severidad) ║
|
||||
╠══════════════════════════════════════════════════════════════════════╣
|
||||
║ ║
|
||||
║ TRACE → Detalle extremo (solo desarrollo) ║
|
||||
║ DEBUG → Informacion de debugging ║
|
||||
║ INFO → Eventos normales del sistema ║
|
||||
║ WARN → Situaciones anormales pero manejables ║
|
||||
║ ERROR → Errores que afectan funcionalidad ║
|
||||
║ FATAL → Errores criticos que detienen el sistema ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
### Cuando Usar Cada Nivel
|
||||
|
||||
| Nivel | Uso | Ejemplo |
|
||||
|-------|-----|---------|
|
||||
| **TRACE** | Flujo detallado de ejecucion | `Entering method findById with id=123` |
|
||||
| **DEBUG** | Variables, estados internos | `User cache hit: userId=123` |
|
||||
| **INFO** | Eventos de negocio normales | `Order created: orderId=456` |
|
||||
| **WARN** | Situaciones inesperadas no criticas | `Retry attempt 2/3 for external API` |
|
||||
| **ERROR** | Errores que necesitan atencion | `Failed to process payment: timeout` |
|
||||
| **FATAL** | Sistema no puede continuar | `Database connection lost` |
|
||||
|
||||
---
|
||||
|
||||
## 2. ESTRUCTURA DE LOG
|
||||
|
||||
### Formato Estandar
|
||||
|
||||
```typescript
|
||||
{
|
||||
timestamp: "2025-12-08T10:30:45.123Z", // ISO 8601
|
||||
level: "INFO", // Nivel
|
||||
context: "UserService", // Clase/modulo origen
|
||||
message: "User created successfully", // Mensaje descriptivo
|
||||
correlationId: "req-abc123", // ID de request/transaccion
|
||||
userId: "user-456", // Usuario (si aplica)
|
||||
data: { // Datos adicionales
|
||||
email: "user@example.com",
|
||||
action: "create"
|
||||
},
|
||||
duration: 45 // Duracion en ms (si aplica)
|
||||
}
|
||||
```
|
||||
|
||||
### Campos Obligatorios
|
||||
|
||||
| Campo | Descripcion | Siempre |
|
||||
|-------|-------------|---------|
|
||||
| `timestamp` | Fecha/hora ISO 8601 | SI |
|
||||
| `level` | Nivel del log | SI |
|
||||
| `context` | Origen del log | SI |
|
||||
| `message` | Descripcion del evento | SI |
|
||||
| `correlationId` | ID para trazar request | EN PRODUCCION |
|
||||
|
||||
### Campos Opcionales Recomendados
|
||||
|
||||
| Campo | Cuando Usar |
|
||||
|-------|-------------|
|
||||
| `userId` | Cuando hay usuario autenticado |
|
||||
| `data` | Datos relevantes al evento |
|
||||
| `duration` | Para operaciones medibles |
|
||||
| `error` | Cuando es log de error |
|
||||
| `stack` | Stack trace en errores |
|
||||
|
||||
---
|
||||
|
||||
## 3. BACKEND (NestJS)
|
||||
|
||||
### Configuracion del Logger
|
||||
|
||||
```typescript
|
||||
// src/shared/logger/logger.service.ts
|
||||
import { Injectable, LoggerService, Scope } from '@nestjs/common';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
@Injectable({ scope: Scope.TRANSIENT })
|
||||
export class AppLogger implements LoggerService {
|
||||
private context: string;
|
||||
private correlationId: string;
|
||||
|
||||
constructor(private readonly logger: Logger) {}
|
||||
|
||||
setContext(context: string) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
setCorrelationId(correlationId: string) {
|
||||
this.correlationId = correlationId;
|
||||
}
|
||||
|
||||
log(message: string, data?: Record<string, any>) {
|
||||
this.logger.info(message, {
|
||||
context: this.context,
|
||||
correlationId: this.correlationId,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
error(message: string, trace?: string, data?: Record<string, any>) {
|
||||
this.logger.error(message, {
|
||||
context: this.context,
|
||||
correlationId: this.correlationId,
|
||||
stack: trace,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
warn(message: string, data?: Record<string, any>) {
|
||||
this.logger.warn(message, {
|
||||
context: this.context,
|
||||
correlationId: this.correlationId,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
debug(message: string, data?: Record<string, any>) {
|
||||
this.logger.debug(message, {
|
||||
context: this.context,
|
||||
correlationId: this.correlationId,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuracion Winston
|
||||
|
||||
```typescript
|
||||
// src/shared/logger/winston.config.ts
|
||||
import * as winston from 'winston';
|
||||
|
||||
const { combine, timestamp, json, printf, colorize } = winston.format;
|
||||
|
||||
// Formato para desarrollo
|
||||
const devFormat = combine(
|
||||
colorize(),
|
||||
timestamp(),
|
||||
printf(({ timestamp, level, message, context, ...meta }) => {
|
||||
return `${timestamp} [${context}] ${level}: ${message} ${
|
||||
Object.keys(meta).length ? JSON.stringify(meta) : ''
|
||||
}`;
|
||||
}),
|
||||
);
|
||||
|
||||
// Formato para produccion (JSON estructurado)
|
||||
const prodFormat = combine(
|
||||
timestamp(),
|
||||
json(),
|
||||
);
|
||||
|
||||
export const winstonConfig: winston.LoggerOptions = {
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: process.env.NODE_ENV === 'production' ? prodFormat : devFormat,
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
// En produccion: agregar transports adicionales
|
||||
// new winston.transports.File({ filename: 'error.log', level: 'error' }),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Uso en Service
|
||||
|
||||
```typescript
|
||||
// src/modules/user/services/user.service.ts
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
constructor(
|
||||
private readonly logger: AppLogger,
|
||||
private readonly repository: Repository<UserEntity>,
|
||||
) {
|
||||
this.logger.setContext(UserService.name);
|
||||
}
|
||||
|
||||
async create(dto: CreateUserDto): Promise<UserEntity> {
|
||||
this.logger.log('Creating new user', { email: dto.email });
|
||||
|
||||
try {
|
||||
const user = await this.repository.save(dto);
|
||||
|
||||
this.logger.log('User created successfully', {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to create user', error.stack, {
|
||||
email: dto.email,
|
||||
errorCode: error.code,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<UserEntity> {
|
||||
this.logger.debug('Finding user by ID', { userId: id });
|
||||
|
||||
const startTime = Date.now();
|
||||
const user = await this.repository.findOne({ where: { id } });
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
if (!user) {
|
||||
this.logger.warn('User not found', { userId: id, duration });
|
||||
throw new NotFoundException(`User ${id} not found`);
|
||||
}
|
||||
|
||||
this.logger.debug('User found', { userId: id, duration });
|
||||
return user;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Interceptor para Correlation ID
|
||||
|
||||
```typescript
|
||||
// src/shared/interceptors/correlation.interceptor.ts
|
||||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@Injectable()
|
||||
export class CorrelationInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
// Usar header existente o generar nuevo
|
||||
const correlationId = request.headers['x-correlation-id'] || uuidv4();
|
||||
|
||||
// Guardar en request para uso posterior
|
||||
request.correlationId = correlationId;
|
||||
|
||||
// Agregar a response headers
|
||||
const response = context.switchToHttp().getResponse();
|
||||
response.setHeader('x-correlation-id', correlationId);
|
||||
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Interceptor de Logging de Requests
|
||||
|
||||
```typescript
|
||||
// src/shared/interceptors/logging.interceptor.ts
|
||||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class LoggingInterceptor implements NestInterceptor {
|
||||
constructor(private readonly logger: AppLogger) {
|
||||
this.logger.setContext('HTTP');
|
||||
}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const { method, url, correlationId, user } = request;
|
||||
const startTime = Date.now();
|
||||
|
||||
this.logger.log('Incoming request', {
|
||||
method,
|
||||
url,
|
||||
correlationId,
|
||||
userId: user?.id,
|
||||
});
|
||||
|
||||
return next.handle().pipe(
|
||||
tap({
|
||||
next: () => {
|
||||
const duration = Date.now() - startTime;
|
||||
this.logger.log('Request completed', {
|
||||
method,
|
||||
url,
|
||||
correlationId,
|
||||
duration,
|
||||
statusCode: context.switchToHttp().getResponse().statusCode,
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
const duration = Date.now() - startTime;
|
||||
this.logger.error('Request failed', error.stack, {
|
||||
method,
|
||||
url,
|
||||
correlationId,
|
||||
duration,
|
||||
errorMessage: error.message,
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. FRONTEND (React)
|
||||
|
||||
### Logger Service
|
||||
|
||||
```typescript
|
||||
// src/shared/services/logger.service.ts
|
||||
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
||||
|
||||
interface LogEntry {
|
||||
timestamp: string;
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
context?: string;
|
||||
data?: Record<string, any>;
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
class LoggerService {
|
||||
private isDev = process.env.NODE_ENV === 'development';
|
||||
private userId: string | null = null;
|
||||
|
||||
setUserId(userId: string | null) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
private log(level: LogLevel, message: string, context?: string, data?: Record<string, any>) {
|
||||
const entry: LogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level,
|
||||
message,
|
||||
context,
|
||||
data,
|
||||
userId: this.userId || undefined,
|
||||
};
|
||||
|
||||
// En desarrollo: console
|
||||
if (this.isDev) {
|
||||
const consoleMethod = level === 'error' ? console.error :
|
||||
level === 'warn' ? console.warn :
|
||||
level === 'debug' ? console.debug : console.log;
|
||||
consoleMethod(`[${entry.context}] ${message}`, data || '');
|
||||
}
|
||||
|
||||
// En produccion: enviar a servicio de logging
|
||||
if (!this.isDev && (level === 'error' || level === 'warn')) {
|
||||
this.sendToServer(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendToServer(entry: LogEntry) {
|
||||
try {
|
||||
await fetch('/api/logs', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(entry),
|
||||
});
|
||||
} catch {
|
||||
// Silently fail - don't cause more errors
|
||||
}
|
||||
}
|
||||
|
||||
debug(message: string, context?: string, data?: Record<string, any>) {
|
||||
this.log('debug', message, context, data);
|
||||
}
|
||||
|
||||
info(message: string, context?: string, data?: Record<string, any>) {
|
||||
this.log('info', message, context, data);
|
||||
}
|
||||
|
||||
warn(message: string, context?: string, data?: Record<string, any>) {
|
||||
this.log('warn', message, context, data);
|
||||
}
|
||||
|
||||
error(message: string, context?: string, data?: Record<string, any>) {
|
||||
this.log('error', message, context, data);
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new LoggerService();
|
||||
```
|
||||
|
||||
### Uso en Componentes
|
||||
|
||||
```typescript
|
||||
// src/apps/web/pages/UsersPage.tsx
|
||||
import { logger } from '@/shared/services/logger.service';
|
||||
|
||||
export const UsersPage = () => {
|
||||
const { data, error, isLoading } = useUsers();
|
||||
|
||||
useEffect(() => {
|
||||
logger.info('Users page mounted', 'UsersPage');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
logger.error('Failed to load users', 'UsersPage', {
|
||||
errorMessage: error.message,
|
||||
});
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const handleDelete = async (userId: string) => {
|
||||
logger.info('Deleting user', 'UsersPage', { userId });
|
||||
|
||||
try {
|
||||
await deleteUser(userId);
|
||||
logger.info('User deleted successfully', 'UsersPage', { userId });
|
||||
} catch (err) {
|
||||
logger.error('Failed to delete user', 'UsersPage', {
|
||||
userId,
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (/* ... */);
|
||||
};
|
||||
```
|
||||
|
||||
### Error Boundary con Logging
|
||||
|
||||
```typescript
|
||||
// src/shared/components/ErrorBoundary.tsx
|
||||
import { Component, ErrorInfo, ReactNode } from 'react';
|
||||
import { logger } from '@/shared/services/logger.service';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
state = { hasError: false };
|
||||
|
||||
static getDerivedStateFromError(): State {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
logger.error('React error boundary caught error', 'ErrorBoundary', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
componentStack: errorInfo.componentStack,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback || <div>Something went wrong</div>;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. DATABASE
|
||||
|
||||
### Logging de Queries (TypeORM)
|
||||
|
||||
```typescript
|
||||
// src/shared/config/typeorm.config.ts
|
||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
|
||||
export const typeOrmConfig: TypeOrmModuleOptions = {
|
||||
// ... otras opciones
|
||||
logging: process.env.NODE_ENV === 'development'
|
||||
? ['query', 'error', 'warn']
|
||||
: ['error'],
|
||||
logger: 'advanced-console', // o custom logger
|
||||
maxQueryExecutionTime: 1000, // Log queries > 1s
|
||||
};
|
||||
```
|
||||
|
||||
### Custom Query Logger
|
||||
|
||||
```typescript
|
||||
// src/shared/logger/typeorm.logger.ts
|
||||
import { Logger as TypeOrmLogger } from 'typeorm';
|
||||
import { AppLogger } from './logger.service';
|
||||
|
||||
export class CustomTypeOrmLogger implements TypeOrmLogger {
|
||||
constructor(private readonly logger: AppLogger) {
|
||||
this.logger.setContext('TypeORM');
|
||||
}
|
||||
|
||||
logQuery(query: string, parameters?: any[]) {
|
||||
this.logger.debug('Query executed', {
|
||||
query: query.substring(0, 500), // Truncar queries largas
|
||||
parameters,
|
||||
});
|
||||
}
|
||||
|
||||
logQueryError(error: string, query: string, parameters?: any[]) {
|
||||
this.logger.error('Query failed', undefined, {
|
||||
error,
|
||||
query: query.substring(0, 500),
|
||||
parameters,
|
||||
});
|
||||
}
|
||||
|
||||
logQuerySlow(time: number, query: string, parameters?: any[]) {
|
||||
this.logger.warn('Slow query detected', {
|
||||
duration: time,
|
||||
query: query.substring(0, 500),
|
||||
parameters,
|
||||
});
|
||||
}
|
||||
|
||||
logSchemaBuild(message: string) {
|
||||
this.logger.info(message);
|
||||
}
|
||||
|
||||
logMigration(message: string) {
|
||||
this.logger.info(message);
|
||||
}
|
||||
|
||||
log(level: 'log' | 'info' | 'warn', message: any) {
|
||||
if (level === 'warn') {
|
||||
this.logger.warn(message);
|
||||
} else {
|
||||
this.logger.log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. QUE LOGUEAR Y QUE NO
|
||||
|
||||
### SI Loguear
|
||||
|
||||
```
|
||||
✅ Inicio/fin de operaciones importantes
|
||||
✅ Errores y excepciones (con contexto)
|
||||
✅ Eventos de autenticacion (login, logout, failed attempts)
|
||||
✅ Operaciones de negocio criticas (pagos, cambios de estado)
|
||||
✅ Llamadas a APIs externas (request/response resumido)
|
||||
✅ Queries lentas (>1s)
|
||||
✅ Warnings de recursos (memoria, conexiones)
|
||||
✅ Cambios de configuracion en runtime
|
||||
```
|
||||
|
||||
### NO Loguear
|
||||
|
||||
```
|
||||
❌ Datos sensibles (passwords, tokens, tarjetas)
|
||||
❌ PII sin necesidad (emails completos, nombres)
|
||||
❌ Cada iteracion de loops
|
||||
❌ Contenido completo de requests/responses grandes
|
||||
❌ Logs de debug en produccion
|
||||
❌ Informacion redundante
|
||||
❌ Stack traces en logs INFO/DEBUG
|
||||
```
|
||||
|
||||
### Sanitizacion de Datos Sensibles
|
||||
|
||||
```typescript
|
||||
// src/shared/utils/log-sanitizer.ts
|
||||
const SENSITIVE_FIELDS = ['password', 'token', 'secret', 'authorization', 'credit_card'];
|
||||
|
||||
export function sanitizeForLogging(data: Record<string, any>): Record<string, any> {
|
||||
const sanitized = { ...data };
|
||||
|
||||
for (const key of Object.keys(sanitized)) {
|
||||
if (SENSITIVE_FIELDS.some(field => key.toLowerCase().includes(field))) {
|
||||
sanitized[key] = '[REDACTED]';
|
||||
} else if (typeof sanitized[key] === 'object' && sanitized[key] !== null) {
|
||||
sanitized[key] = sanitizeForLogging(sanitized[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
// Uso
|
||||
this.logger.log('User login attempt', sanitizeForLogging({
|
||||
email: dto.email,
|
||||
password: dto.password, // Se convierte en [REDACTED]
|
||||
}));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. CONFIGURACION POR AMBIENTE
|
||||
|
||||
```typescript
|
||||
// src/shared/config/logger.config.ts
|
||||
export const loggerConfig = {
|
||||
development: {
|
||||
level: 'debug',
|
||||
format: 'pretty',
|
||||
includeTimestamp: true,
|
||||
colorize: true,
|
||||
},
|
||||
staging: {
|
||||
level: 'info',
|
||||
format: 'json',
|
||||
includeTimestamp: true,
|
||||
colorize: false,
|
||||
},
|
||||
production: {
|
||||
level: 'warn',
|
||||
format: 'json',
|
||||
includeTimestamp: true,
|
||||
colorize: false,
|
||||
// Enviar a servicio externo
|
||||
externalService: {
|
||||
enabled: true,
|
||||
endpoint: process.env.LOG_ENDPOINT,
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. CHECKLIST DE LOGGING
|
||||
|
||||
```
|
||||
Antes de hacer deploy:
|
||||
[ ] Logs no contienen datos sensibles
|
||||
[ ] Nivel de log apropiado para ambiente
|
||||
[ ] Errores tienen contexto suficiente para debug
|
||||
[ ] Correlation ID implementado
|
||||
[ ] Queries lentas se detectan
|
||||
[ ] Error boundary implementado en frontend
|
||||
|
||||
En cada Service nuevo:
|
||||
[ ] Logger inyectado y contexto configurado
|
||||
[ ] Operaciones principales logueadas
|
||||
[ ] Errores logueados con stack trace
|
||||
[ ] Tiempos de operaciones criticas medidos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patron de Codigo
|
||||
657
core/orchestration/patrones/PATRON-PERFORMANCE.md
Normal file
657
core/orchestration/patrones/PATRON-PERFORMANCE.md
Normal file
@ -0,0 +1,657 @@
|
||||
# PATRON DE PERFORMANCE
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** RECOMENDADA - Seguir para optimizacion
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Definir patrones de optimizacion de rendimiento para todas las capas del sistema, asegurando tiempos de respuesta aceptables y uso eficiente de recursos.
|
||||
|
||||
---
|
||||
|
||||
## 1. METRICAS OBJETIVO
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ OBJETIVOS DE PERFORMANCE ║
|
||||
╠══════════════════════════════════════════════════════════════════════╣
|
||||
║ ║
|
||||
║ API Response Time: ║
|
||||
║ • P50: < 100ms ║
|
||||
║ • P95: < 500ms ║
|
||||
║ • P99: < 1000ms ║
|
||||
║ ║
|
||||
║ Database Queries: ║
|
||||
║ • Simple query: < 10ms ║
|
||||
║ • Complex query: < 100ms ║
|
||||
║ • Report query: < 1000ms ║
|
||||
║ ║
|
||||
║ Frontend: ║
|
||||
║ • First Contentful Paint: < 1.5s ║
|
||||
║ • Time to Interactive: < 3s ║
|
||||
║ • Largest Contentful Paint: < 2.5s ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. DATABASE PERFORMANCE
|
||||
|
||||
### 2.1 Indices Efectivos
|
||||
|
||||
```sql
|
||||
-- Indices para columnas frecuentemente filtradas
|
||||
CREATE INDEX idx_users_email ON auth.users(email);
|
||||
CREATE INDEX idx_users_status ON auth.users(status);
|
||||
|
||||
-- Indice compuesto para queries frecuentes
|
||||
CREATE INDEX idx_orders_user_created
|
||||
ON core.orders(user_id, created_at DESC);
|
||||
|
||||
-- Indice parcial para datos activos
|
||||
CREATE INDEX idx_users_active
|
||||
ON auth.users(email)
|
||||
WHERE status = 'active';
|
||||
|
||||
-- Indice para busqueda de texto
|
||||
CREATE INDEX idx_products_name_gin
|
||||
ON core.products USING gin(to_tsvector('spanish', name));
|
||||
```
|
||||
|
||||
### 2.2 Analisis de Queries
|
||||
|
||||
```sql
|
||||
-- Ver plan de ejecucion
|
||||
EXPLAIN ANALYZE
|
||||
SELECT * FROM orders
|
||||
WHERE user_id = 'uuid-123'
|
||||
AND created_at > NOW() - INTERVAL '30 days';
|
||||
|
||||
-- Identificar queries lentas
|
||||
SELECT query, calls, mean_time, total_time
|
||||
FROM pg_stat_statements
|
||||
ORDER BY mean_time DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
### 2.3 Evitar N+1 Queries
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: N+1 queries
|
||||
async findAllWithOrders(): Promise<User[]> {
|
||||
const users = await this.userRepository.find();
|
||||
// N queries adicionales para cargar orders
|
||||
for (const user of users) {
|
||||
user.orders = await this.orderRepository.find({
|
||||
where: { userId: user.id }
|
||||
});
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: Join en una query
|
||||
async findAllWithOrders(): Promise<User[]> {
|
||||
return this.userRepository.find({
|
||||
relations: ['orders'], // TypeORM hace JOIN
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: QueryBuilder con control
|
||||
async findAllWithOrders(): Promise<User[]> {
|
||||
return this.userRepository
|
||||
.createQueryBuilder('user')
|
||||
.leftJoinAndSelect('user.orders', 'order')
|
||||
.where('user.status = :status', { status: 'active' })
|
||||
.orderBy('user.createdAt', 'DESC')
|
||||
.getMany();
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 Paginacion Eficiente
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: OFFSET para paginas grandes
|
||||
async findPaginated(page: number, limit: number) {
|
||||
return this.repository.find({
|
||||
skip: (page - 1) * limit, // Lento en paginas grandes
|
||||
take: limit,
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: Cursor-based pagination
|
||||
async findPaginatedByCursor(cursor?: string, limit: number = 20) {
|
||||
const qb = this.repository
|
||||
.createQueryBuilder('item')
|
||||
.orderBy('item.createdAt', 'DESC')
|
||||
.take(limit + 1); // +1 para saber si hay mas
|
||||
|
||||
if (cursor) {
|
||||
const decodedCursor = this.decodeCursor(cursor);
|
||||
qb.where('item.createdAt < :cursor', { cursor: decodedCursor });
|
||||
}
|
||||
|
||||
const items = await qb.getMany();
|
||||
const hasMore = items.length > limit;
|
||||
|
||||
if (hasMore) {
|
||||
items.pop(); // Remover el extra
|
||||
}
|
||||
|
||||
return {
|
||||
data: items,
|
||||
nextCursor: hasMore ? this.encodeCursor(items[items.length - 1]) : null,
|
||||
hasMore,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 Select Solo Campos Necesarios
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: Traer toda la entidad
|
||||
const users = await this.userRepository.find();
|
||||
|
||||
// ✅ CORRECTO: Solo campos necesarios
|
||||
const users = await this.userRepository.find({
|
||||
select: ['id', 'email', 'firstName'],
|
||||
});
|
||||
|
||||
// ✅ CORRECTO: Con QueryBuilder
|
||||
const users = await this.userRepository
|
||||
.createQueryBuilder('user')
|
||||
.select(['user.id', 'user.email', 'user.firstName'])
|
||||
.getMany();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. BACKEND PERFORMANCE
|
||||
|
||||
### 3.1 Caching con Redis
|
||||
|
||||
```typescript
|
||||
// src/shared/cache/cache.service.ts
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { Cache } from 'cache-manager';
|
||||
|
||||
@Injectable()
|
||||
export class CacheService {
|
||||
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
|
||||
|
||||
async get<T>(key: string): Promise<T | null> {
|
||||
return this.cacheManager.get<T>(key);
|
||||
}
|
||||
|
||||
async set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {
|
||||
await this.cacheManager.set(key, value, ttlSeconds * 1000);
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
await this.cacheManager.del(key);
|
||||
}
|
||||
|
||||
async delByPattern(pattern: string): Promise<void> {
|
||||
const keys = await this.cacheManager.store.keys(pattern);
|
||||
await Promise.all(keys.map(key => this.cacheManager.del(key)));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Cache Decorator
|
||||
|
||||
```typescript
|
||||
// src/shared/decorators/cached.decorator.ts
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const CACHE_KEY = 'cache_key';
|
||||
export const CACHE_TTL = 'cache_ttl';
|
||||
|
||||
export const Cached = (key: string, ttlSeconds: number = 300) => {
|
||||
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||
SetMetadata(CACHE_KEY, key)(target, propertyKey, descriptor);
|
||||
SetMetadata(CACHE_TTL, ttlSeconds)(target, propertyKey, descriptor);
|
||||
};
|
||||
};
|
||||
|
||||
// Interceptor que implementa el caching
|
||||
@Injectable()
|
||||
export class CacheInterceptor implements NestInterceptor {
|
||||
constructor(
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly reflector: Reflector,
|
||||
) {}
|
||||
|
||||
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
|
||||
const cacheKey = this.reflector.get<string>(CACHE_KEY, context.getHandler());
|
||||
if (!cacheKey) {
|
||||
return next.handle();
|
||||
}
|
||||
|
||||
const cacheTtl = this.reflector.get<number>(CACHE_TTL, context.getHandler()) || 300;
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const fullKey = `${cacheKey}:${JSON.stringify(request.query)}`;
|
||||
|
||||
const cached = await this.cacheService.get(fullKey);
|
||||
if (cached) {
|
||||
return of(cached);
|
||||
}
|
||||
|
||||
return next.handle().pipe(
|
||||
tap(async (data) => {
|
||||
await this.cacheService.set(fullKey, data, cacheTtl);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Uso de Cache en Service
|
||||
|
||||
```typescript
|
||||
// src/modules/product/services/product.service.ts
|
||||
@Injectable()
|
||||
export class ProductService {
|
||||
constructor(
|
||||
private readonly repository: Repository<ProductEntity>,
|
||||
private readonly cacheService: CacheService,
|
||||
) {}
|
||||
|
||||
async findAll(query: ProductQueryDto): Promise<Product[]> {
|
||||
const cacheKey = `products:list:${JSON.stringify(query)}`;
|
||||
|
||||
// Intentar obtener de cache
|
||||
const cached = await this.cacheService.get<Product[]>(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Query a BD
|
||||
const products = await this.repository.find({
|
||||
where: this.buildWhereClause(query),
|
||||
take: query.limit,
|
||||
});
|
||||
|
||||
// Guardar en cache (5 minutos)
|
||||
await this.cacheService.set(cacheKey, products, 300);
|
||||
|
||||
return products;
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateProductDto): Promise<Product> {
|
||||
const product = await this.repository.save({ id, ...dto });
|
||||
|
||||
// Invalidar cache relacionado
|
||||
await this.cacheService.delByPattern('products:*');
|
||||
|
||||
return product;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Compresion de Responses
|
||||
|
||||
```typescript
|
||||
// src/main.ts
|
||||
import * as compression from 'compression';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Comprimir responses > 1kb
|
||||
app.use(compression({
|
||||
threshold: 1024,
|
||||
level: 6, // Balance entre compresion y CPU
|
||||
}));
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 Lazy Loading de Modulos
|
||||
|
||||
```typescript
|
||||
// Cargar modulo pesado solo cuando se necesita
|
||||
@Module({
|
||||
imports: [
|
||||
// Modulo de reportes cargado lazy
|
||||
RouterModule.register([
|
||||
{
|
||||
path: 'reports',
|
||||
module: ReportsModule,
|
||||
},
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. FRONTEND PERFORMANCE
|
||||
|
||||
### 4.1 Code Splitting
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: Importar todo
|
||||
import { HeavyComponent } from './HeavyComponent';
|
||||
|
||||
// ✅ CORRECTO: Lazy loading
|
||||
const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
||||
|
||||
// Uso con Suspense
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<HeavyComponent />
|
||||
</Suspense>
|
||||
```
|
||||
|
||||
### 4.2 Memoizacion
|
||||
|
||||
```typescript
|
||||
// React.memo para componentes puros
|
||||
const UserCard = memo(({ user }: { user: User }) => {
|
||||
return (
|
||||
<div>
|
||||
<h3>{user.name}</h3>
|
||||
<p>{user.email}</p>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// useMemo para calculos costosos
|
||||
const ExpensiveList = ({ items, filter }: Props) => {
|
||||
const filteredItems = useMemo(
|
||||
() => items.filter(item => complexFilter(item, filter)),
|
||||
[items, filter], // Solo recalcular si cambian
|
||||
);
|
||||
|
||||
return <ul>{filteredItems.map(/* ... */)}</ul>;
|
||||
};
|
||||
|
||||
// useCallback para funciones estables
|
||||
const ParentComponent = () => {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
console.log('clicked');
|
||||
}, []); // Funcion estable
|
||||
|
||||
return <ChildComponent onClick={handleClick} />;
|
||||
};
|
||||
```
|
||||
|
||||
### 4.3 Virtualizacion de Listas
|
||||
|
||||
```typescript
|
||||
// Para listas largas, usar virtualizacion
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
|
||||
const VirtualList = ({ items }: { items: Item[] }) => {
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: items.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 50, // Altura estimada de cada item
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
|
||||
<div style={{ height: virtualizer.getTotalSize() }}>
|
||||
{virtualizer.getVirtualItems().map(virtualItem => (
|
||||
<div
|
||||
key={virtualItem.key}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: virtualItem.start,
|
||||
height: virtualItem.size,
|
||||
}}
|
||||
>
|
||||
<ItemComponent item={items[virtualItem.index]} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 4.4 Optimizacion de Imagenes
|
||||
|
||||
```typescript
|
||||
// Componente de imagen optimizada
|
||||
const OptimizedImage = ({
|
||||
src,
|
||||
alt,
|
||||
width,
|
||||
height,
|
||||
}: ImageProps) => {
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
width={width}
|
||||
height={height}
|
||||
loading="lazy" // Lazy loading nativo
|
||||
decoding="async" // Decodificacion asincrona
|
||||
style={{ aspectRatio: `${width}/${height}` }} // Prevenir layout shift
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// Con srcset para responsive
|
||||
const ResponsiveImage = ({ src, alt }: Props) => {
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
srcSet={`
|
||||
${src}?w=400 400w,
|
||||
${src}?w=800 800w,
|
||||
${src}?w=1200 1200w
|
||||
`}
|
||||
sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
|
||||
loading="lazy"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 4.5 Debounce y Throttle
|
||||
|
||||
```typescript
|
||||
// src/shared/hooks/useDebounce.ts
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
// Uso en busqueda
|
||||
const SearchInput = () => {
|
||||
const [search, setSearch] = useState('');
|
||||
const debouncedSearch = useDebounce(search, 300);
|
||||
|
||||
// Query solo se ejecuta cuando debouncedSearch cambia
|
||||
const { data } = useQuery({
|
||||
queryKey: ['search', debouncedSearch],
|
||||
queryFn: () => api.search(debouncedSearch),
|
||||
enabled: debouncedSearch.length > 2,
|
||||
});
|
||||
|
||||
return <input value={search} onChange={e => setSearch(e.target.value)} />;
|
||||
};
|
||||
```
|
||||
|
||||
### 4.6 React Query - Cache y Stale Time
|
||||
|
||||
```typescript
|
||||
// src/shared/hooks/useProducts.ts
|
||||
export const useProducts = (filters: ProductFilters) => {
|
||||
return useQuery({
|
||||
queryKey: ['products', filters],
|
||||
queryFn: () => productService.getAll(filters),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutos antes de refetch
|
||||
gcTime: 30 * 60 * 1000, // 30 minutos en cache
|
||||
placeholderData: keepPreviousData, // Mostrar datos anteriores mientras carga
|
||||
});
|
||||
};
|
||||
|
||||
// Prefetch para navegacion anticipada
|
||||
const ProductList = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const handleMouseEnter = (productId: string) => {
|
||||
// Prefetch detalle del producto
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['product', productId],
|
||||
queryFn: () => productService.getById(productId),
|
||||
});
|
||||
};
|
||||
|
||||
return (/* ... */);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API DESIGN PARA PERFORMANCE
|
||||
|
||||
### 5.1 Campos Seleccionables
|
||||
|
||||
```typescript
|
||||
// Permitir al cliente elegir campos
|
||||
@Get()
|
||||
async findAll(
|
||||
@Query('fields') fields?: string, // ?fields=id,name,price
|
||||
): Promise<Partial<Product>[]> {
|
||||
const select = fields?.split(',') || undefined;
|
||||
return this.productService.findAll({ select });
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Expansion de Relaciones
|
||||
|
||||
```typescript
|
||||
// Permitir expansion opcional de relaciones
|
||||
@Get(':id')
|
||||
async findOne(
|
||||
@Param('id') id: string,
|
||||
@Query('expand') expand?: string, // ?expand=category,reviews
|
||||
): Promise<Product> {
|
||||
const relations = expand?.split(',') || [];
|
||||
return this.productService.findOne(id, { relations });
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Batch Endpoints
|
||||
|
||||
```typescript
|
||||
// Endpoint para multiples operaciones
|
||||
@Post('batch')
|
||||
async batchCreate(@Body() dtos: CreateProductDto[]): Promise<Product[]> {
|
||||
// Una transaccion en lugar de N requests
|
||||
return this.productService.createMany(dtos);
|
||||
}
|
||||
|
||||
// Endpoint para multiples IDs
|
||||
@Get('batch')
|
||||
async batchGet(@Query('ids') ids: string): Promise<Product[]> {
|
||||
const idArray = ids.split(',');
|
||||
return this.productService.findByIds(idArray);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. MONITORING Y PROFILING
|
||||
|
||||
### 6.1 Metricas de API
|
||||
|
||||
```typescript
|
||||
// src/shared/interceptors/metrics.interceptor.ts
|
||||
@Injectable()
|
||||
export class MetricsInterceptor implements NestInterceptor {
|
||||
constructor(private readonly metricsService: MetricsService) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const { method, url } = request;
|
||||
const startTime = Date.now();
|
||||
|
||||
return next.handle().pipe(
|
||||
tap({
|
||||
next: () => {
|
||||
const duration = Date.now() - startTime;
|
||||
this.metricsService.recordRequest(method, url, 200, duration);
|
||||
},
|
||||
error: (error) => {
|
||||
const duration = Date.now() - startTime;
|
||||
this.metricsService.recordRequest(method, url, error.status || 500, duration);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Query Logging Condicional
|
||||
|
||||
```typescript
|
||||
// Solo loguear queries lentas en produccion
|
||||
const typeOrmConfig: TypeOrmModuleOptions = {
|
||||
logging: process.env.NODE_ENV === 'production' ? ['error', 'warn'] : true,
|
||||
maxQueryExecutionTime: 1000, // Loguear queries > 1s
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. CHECKLIST DE PERFORMANCE
|
||||
|
||||
```
|
||||
Database:
|
||||
[ ] Indices en columnas de WHERE frecuentes
|
||||
[ ] Indices compuestos para queries comunes
|
||||
[ ] No N+1 queries (usar JOIN/relations)
|
||||
[ ] Paginacion cursor-based para datasets grandes
|
||||
[ ] SELECT solo campos necesarios
|
||||
[ ] EXPLAIN ANALYZE en queries criticas
|
||||
|
||||
Backend:
|
||||
[ ] Cache implementado para datos frecuentes
|
||||
[ ] Invalidacion de cache correcta
|
||||
[ ] Compresion habilitada
|
||||
[ ] Connection pooling configurado
|
||||
[ ] Timeouts apropiados
|
||||
|
||||
Frontend:
|
||||
[ ] Code splitting / lazy loading
|
||||
[ ] Memoizacion donde corresponde
|
||||
[ ] Virtualizacion para listas largas
|
||||
[ ] Imagenes optimizadas y lazy loaded
|
||||
[ ] Debounce en inputs de busqueda
|
||||
[ ] React Query con staleTime apropiado
|
||||
|
||||
API:
|
||||
[ ] Paginacion en endpoints de listas
|
||||
[ ] Campos seleccionables (opcional)
|
||||
[ ] Batch endpoints para operaciones multiples
|
||||
[ ] Rate limiting para proteger recursos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patron de Performance
|
||||
778
core/orchestration/patrones/PATRON-SEGURIDAD.md
Normal file
778
core/orchestration/patrones/PATRON-SEGURIDAD.md
Normal file
@ -0,0 +1,778 @@
|
||||
# PATRON DE SEGURIDAD
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - Seguir en todo el codigo
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Definir patrones de seguridad obligatorios para prevenir vulnerabilidades comunes (OWASP Top 10) y proteger datos sensibles.
|
||||
|
||||
---
|
||||
|
||||
## 1. OWASP TOP 10 - RESUMEN
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ OWASP TOP 10 - 2021 ║
|
||||
╠══════════════════════════════════════════════════════════════════════╣
|
||||
║ ║
|
||||
║ A01 - Broken Access Control ║
|
||||
║ A02 - Cryptographic Failures ║
|
||||
║ A03 - Injection ║
|
||||
║ A04 - Insecure Design ║
|
||||
║ A05 - Security Misconfiguration ║
|
||||
║ A06 - Vulnerable Components ║
|
||||
║ A07 - Authentication Failures ║
|
||||
║ A08 - Software Integrity Failures ║
|
||||
║ A09 - Logging & Monitoring Failures ║
|
||||
║ A10 - Server-Side Request Forgery (SSRF) ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. SANITIZACION DE INPUT
|
||||
|
||||
### Backend - Validacion con class-validator
|
||||
|
||||
```typescript
|
||||
// src/modules/user/dto/create-user.dto.ts
|
||||
import {
|
||||
IsEmail,
|
||||
IsString,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
Matches,
|
||||
IsNotEmpty,
|
||||
} from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CreateUserDto {
|
||||
@ApiProperty({ example: 'user@example.com' })
|
||||
@IsEmail({}, { message: 'Email invalido' })
|
||||
@MaxLength(255)
|
||||
@Transform(({ value }) => value?.toLowerCase().trim()) // Sanitizar
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ example: 'John' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(2)
|
||||
@MaxLength(100)
|
||||
@Matches(/^[a-zA-ZÀ-ÿ\s'-]+$/, {
|
||||
message: 'Nombre solo puede contener letras',
|
||||
})
|
||||
@Transform(({ value }) => value?.trim()) // Sanitizar espacios
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
@MaxLength(128)
|
||||
@Matches(
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/,
|
||||
{ message: 'Password debe tener mayuscula, minuscula, numero y simbolo' },
|
||||
)
|
||||
password: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Sanitizacion de HTML (Prevenir XSS)
|
||||
|
||||
```typescript
|
||||
// src/shared/utils/sanitizer.ts
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
|
||||
export function sanitizeHtml(dirty: string): string {
|
||||
return DOMPurify.sanitize(dirty, {
|
||||
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
|
||||
ALLOWED_ATTR: [],
|
||||
});
|
||||
}
|
||||
|
||||
export function stripHtml(dirty: string): string {
|
||||
return DOMPurify.sanitize(dirty, {
|
||||
ALLOWED_TAGS: [],
|
||||
ALLOWED_ATTR: [],
|
||||
});
|
||||
}
|
||||
|
||||
// Uso en DTO
|
||||
@Transform(({ value }) => stripHtml(value))
|
||||
@IsString()
|
||||
comment: string;
|
||||
```
|
||||
|
||||
### Prevenir SQL Injection
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: SQL Injection vulnerable
|
||||
async findByName(name: string) {
|
||||
return this.repository.query(
|
||||
`SELECT * FROM users WHERE name = '${name}'` // VULNERABLE
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: Usar parametros
|
||||
async findByName(name: string) {
|
||||
return this.repository.query(
|
||||
'SELECT * FROM users WHERE name = $1',
|
||||
[name], // Parametrizado
|
||||
);
|
||||
}
|
||||
|
||||
// ✅ MEJOR: Usar QueryBuilder de TypeORM
|
||||
async findByName(name: string) {
|
||||
return this.repository
|
||||
.createQueryBuilder('user')
|
||||
.where('user.name = :name', { name }) // Automaticamente seguro
|
||||
.getMany();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. AUTENTICACION
|
||||
|
||||
### Password Hashing
|
||||
|
||||
```typescript
|
||||
// src/shared/utils/password.util.ts
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
const SALT_ROUNDS = 12; // Minimo 10 para produccion
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
export async function verifyPassword(
|
||||
password: string,
|
||||
hash: string,
|
||||
): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
```
|
||||
|
||||
### JWT con Refresh Tokens
|
||||
|
||||
```typescript
|
||||
// src/modules/auth/services/auth.service.ts
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly userService: UserService,
|
||||
private readonly tokenService: RefreshTokenService,
|
||||
) {}
|
||||
|
||||
async login(dto: LoginDto): Promise<AuthTokens> {
|
||||
const user = await this.validateUser(dto.email, dto.password);
|
||||
if (!user) {
|
||||
// Mensaje generico para no revelar si email existe
|
||||
throw new UnauthorizedException('Credenciales invalidas');
|
||||
}
|
||||
|
||||
const tokens = await this.generateTokens(user);
|
||||
|
||||
// Guardar refresh token hasheado en BD
|
||||
await this.tokenService.saveRefreshToken(
|
||||
user.id,
|
||||
await hashPassword(tokens.refreshToken),
|
||||
);
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private async generateTokens(user: UserEntity): Promise<AuthTokens> {
|
||||
const payload: JwtPayload = {
|
||||
sub: user.id,
|
||||
email: user.email,
|
||||
roles: user.roles.map(r => r.name),
|
||||
};
|
||||
|
||||
const [accessToken, refreshToken] = await Promise.all([
|
||||
this.jwtService.signAsync(payload, {
|
||||
secret: this.configService.get('JWT_SECRET'),
|
||||
expiresIn: '15m', // Corta duracion
|
||||
}),
|
||||
this.jwtService.signAsync(
|
||||
{ sub: user.id, type: 'refresh' },
|
||||
{
|
||||
secret: this.configService.get('JWT_REFRESH_SECRET'),
|
||||
expiresIn: '7d',
|
||||
},
|
||||
),
|
||||
]);
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
}
|
||||
|
||||
async refresh(refreshToken: string): Promise<AuthTokens> {
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(refreshToken, {
|
||||
secret: this.configService.get('JWT_REFRESH_SECRET'),
|
||||
});
|
||||
|
||||
// Verificar que token existe en BD y no fue revocado
|
||||
const storedToken = await this.tokenService.findByUserId(payload.sub);
|
||||
if (!storedToken || !await verifyPassword(refreshToken, storedToken.hash)) {
|
||||
throw new UnauthorizedException('Token invalido');
|
||||
}
|
||||
|
||||
const user = await this.userService.findById(payload.sub);
|
||||
return this.generateTokens(user);
|
||||
} catch {
|
||||
throw new UnauthorizedException('Token invalido o expirado');
|
||||
}
|
||||
}
|
||||
|
||||
async logout(userId: string): Promise<void> {
|
||||
// Revocar todos los refresh tokens del usuario
|
||||
await this.tokenService.revokeAllUserTokens(userId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Guard de Autenticacion
|
||||
|
||||
```typescript
|
||||
// src/shared/guards/jwt-auth.guard.ts
|
||||
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
constructor(private reflector: Reflector) {
|
||||
super();
|
||||
}
|
||||
|
||||
canActivate(context: ExecutionContext) {
|
||||
// Verificar si es ruta publica
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (isPublic) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.canActivate(context);
|
||||
}
|
||||
|
||||
handleRequest(err: any, user: any, info: any) {
|
||||
if (err || !user) {
|
||||
throw err || new UnauthorizedException('No autorizado');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. AUTORIZACION (RBAC)
|
||||
|
||||
### Roles y Permisos
|
||||
|
||||
```typescript
|
||||
// src/shared/enums/roles.enum.ts
|
||||
export enum Role {
|
||||
SUPER_ADMIN = 'super_admin',
|
||||
ADMIN = 'admin',
|
||||
MANAGER = 'manager',
|
||||
USER = 'user',
|
||||
GUEST = 'guest',
|
||||
}
|
||||
|
||||
export enum Permission {
|
||||
// Users
|
||||
USER_CREATE = 'user:create',
|
||||
USER_READ = 'user:read',
|
||||
USER_UPDATE = 'user:update',
|
||||
USER_DELETE = 'user:delete',
|
||||
|
||||
// Products
|
||||
PRODUCT_CREATE = 'product:create',
|
||||
PRODUCT_READ = 'product:read',
|
||||
PRODUCT_UPDATE = 'product:update',
|
||||
PRODUCT_DELETE = 'product:delete',
|
||||
}
|
||||
|
||||
// Mapeo de roles a permisos
|
||||
export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
|
||||
[Role.SUPER_ADMIN]: Object.values(Permission),
|
||||
[Role.ADMIN]: [
|
||||
Permission.USER_CREATE,
|
||||
Permission.USER_READ,
|
||||
Permission.USER_UPDATE,
|
||||
Permission.PRODUCT_CREATE,
|
||||
Permission.PRODUCT_READ,
|
||||
Permission.PRODUCT_UPDATE,
|
||||
Permission.PRODUCT_DELETE,
|
||||
],
|
||||
[Role.MANAGER]: [
|
||||
Permission.USER_READ,
|
||||
Permission.PRODUCT_CREATE,
|
||||
Permission.PRODUCT_READ,
|
||||
Permission.PRODUCT_UPDATE,
|
||||
],
|
||||
[Role.USER]: [
|
||||
Permission.PRODUCT_READ,
|
||||
],
|
||||
[Role.GUEST]: [],
|
||||
};
|
||||
```
|
||||
|
||||
### Guard de Roles
|
||||
|
||||
```typescript
|
||||
// src/shared/guards/roles.guard.ts
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Role, Permission, ROLE_PERMISSIONS } from '../enums/roles.enum';
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(
|
||||
'permissions',
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
|
||||
if (!requiredRoles && !requiredPermissions) {
|
||||
return true; // Sin restricciones
|
||||
}
|
||||
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
|
||||
if (!user) {
|
||||
throw new ForbiddenException('Usuario no autenticado');
|
||||
}
|
||||
|
||||
// Verificar roles
|
||||
if (requiredRoles?.length > 0) {
|
||||
const hasRole = requiredRoles.some(role => user.roles?.includes(role));
|
||||
if (!hasRole) {
|
||||
throw new ForbiddenException('Rol insuficiente');
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar permisos
|
||||
if (requiredPermissions?.length > 0) {
|
||||
const userPermissions = this.getUserPermissions(user.roles);
|
||||
const hasPermission = requiredPermissions.every(
|
||||
permission => userPermissions.includes(permission),
|
||||
);
|
||||
if (!hasPermission) {
|
||||
throw new ForbiddenException('Permiso insuficiente');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private getUserPermissions(roles: Role[]): Permission[] {
|
||||
const permissions = new Set<Permission>();
|
||||
for (const role of roles) {
|
||||
for (const permission of ROLE_PERMISSIONS[role] || []) {
|
||||
permissions.add(permission);
|
||||
}
|
||||
}
|
||||
return Array.from(permissions);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Decoradores
|
||||
|
||||
```typescript
|
||||
// src/shared/decorators/roles.decorator.ts
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { Role, Permission } from '../enums/roles.enum';
|
||||
|
||||
export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
|
||||
export const Permissions = (...permissions: Permission[]) =>
|
||||
SetMetadata('permissions', permissions);
|
||||
```
|
||||
|
||||
### Uso en Controller
|
||||
|
||||
```typescript
|
||||
// src/modules/user/controllers/user.controller.ts
|
||||
@Controller('users')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class UserController {
|
||||
@Get()
|
||||
@Roles(Role.ADMIN, Role.MANAGER)
|
||||
findAll() {
|
||||
return this.userService.findAll();
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Permissions(Permission.USER_CREATE)
|
||||
create(@Body() dto: CreateUserDto) {
|
||||
return this.userService.create(dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Roles(Role.SUPER_ADMIN) // Solo super admin puede eliminar
|
||||
remove(@Param('id') id: string) {
|
||||
return this.userService.remove(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. PROTECCION DE DATOS
|
||||
|
||||
### Encriptacion de Datos Sensibles
|
||||
|
||||
```typescript
|
||||
// src/shared/utils/encryption.util.ts
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
const ALGORITHM = 'aes-256-gcm';
|
||||
const IV_LENGTH = 16;
|
||||
const AUTH_TAG_LENGTH = 16;
|
||||
|
||||
export function encrypt(text: string, key: string): string {
|
||||
const iv = crypto.randomBytes(IV_LENGTH);
|
||||
const cipher = crypto.createCipheriv(
|
||||
ALGORITHM,
|
||||
Buffer.from(key, 'hex'),
|
||||
iv,
|
||||
);
|
||||
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
// IV + AuthTag + Encrypted
|
||||
return iv.toString('hex') + authTag.toString('hex') + encrypted;
|
||||
}
|
||||
|
||||
export function decrypt(encryptedText: string, key: string): string {
|
||||
const iv = Buffer.from(encryptedText.slice(0, IV_LENGTH * 2), 'hex');
|
||||
const authTag = Buffer.from(
|
||||
encryptedText.slice(IV_LENGTH * 2, (IV_LENGTH + AUTH_TAG_LENGTH) * 2),
|
||||
'hex',
|
||||
);
|
||||
const encrypted = encryptedText.slice((IV_LENGTH + AUTH_TAG_LENGTH) * 2);
|
||||
|
||||
const decipher = crypto.createDecipheriv(
|
||||
ALGORITHM,
|
||||
Buffer.from(key, 'hex'),
|
||||
iv,
|
||||
);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
```
|
||||
|
||||
### Columnas Encriptadas en Entity
|
||||
|
||||
```typescript
|
||||
// src/shared/transformers/encrypted.transformer.ts
|
||||
import { ValueTransformer } from 'typeorm';
|
||||
import { encrypt, decrypt } from '../utils/encryption.util';
|
||||
|
||||
export class EncryptedTransformer implements ValueTransformer {
|
||||
constructor(private readonly key: string) {}
|
||||
|
||||
to(value: string | null): string | null {
|
||||
if (!value) return null;
|
||||
return encrypt(value, this.key);
|
||||
}
|
||||
|
||||
from(value: string | null): string | null {
|
||||
if (!value) return null;
|
||||
return decrypt(value, this.key);
|
||||
}
|
||||
}
|
||||
|
||||
// Uso en Entity
|
||||
@Column({
|
||||
type: 'text',
|
||||
transformer: new EncryptedTransformer(process.env.ENCRYPTION_KEY),
|
||||
})
|
||||
ssn: string; // Se guarda encriptado en BD
|
||||
```
|
||||
|
||||
### Nunca Exponer Datos Sensibles
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: Exponer password en response
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
return this.userRepository.findOne({ where: { id } });
|
||||
// Retorna { id, email, password, ... } - PASSWORD EXPUESTO!
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: Usar ResponseDto que excluye campos sensibles
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string): Promise<UserResponseDto> {
|
||||
const user = await this.userService.findOne(id);
|
||||
return plainToClass(UserResponseDto, user, {
|
||||
excludeExtraneousValues: true,
|
||||
});
|
||||
}
|
||||
|
||||
// ResponseDto solo expone campos seguros
|
||||
export class UserResponseDto {
|
||||
@Expose() id: string;
|
||||
@Expose() email: string;
|
||||
@Expose() firstName: string;
|
||||
// password NO esta expuesto
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. RATE LIMITING
|
||||
|
||||
### Implementacion con Throttler
|
||||
|
||||
```typescript
|
||||
// src/app.module.ts
|
||||
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ThrottlerModule.forRoot([
|
||||
{
|
||||
name: 'short',
|
||||
ttl: 1000, // 1 segundo
|
||||
limit: 3, // 3 requests por segundo
|
||||
},
|
||||
{
|
||||
name: 'medium',
|
||||
ttl: 10000, // 10 segundos
|
||||
limit: 20, // 20 requests por 10 segundos
|
||||
},
|
||||
{
|
||||
name: 'long',
|
||||
ttl: 60000, // 1 minuto
|
||||
limit: 100, // 100 requests por minuto
|
||||
},
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: ThrottlerGuard,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
### Rate Limiting por Endpoint
|
||||
|
||||
```typescript
|
||||
// Rate limit especifico para login (prevenir brute force)
|
||||
@Post('login')
|
||||
@Throttle({ default: { limit: 5, ttl: 60000 } }) // 5 intentos por minuto
|
||||
async login(@Body() dto: LoginDto) {
|
||||
return this.authService.login(dto);
|
||||
}
|
||||
|
||||
// Endpoint sin rate limit
|
||||
@Get('health')
|
||||
@SkipThrottle()
|
||||
healthCheck() {
|
||||
return { status: 'ok' };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. HEADERS DE SEGURIDAD
|
||||
|
||||
### Helmet Middleware
|
||||
|
||||
```typescript
|
||||
// src/main.ts
|
||||
import helmet from 'helmet';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
// Headers de seguridad
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", 'data:', 'https:'],
|
||||
},
|
||||
},
|
||||
hsts: {
|
||||
maxAge: 31536000, // 1 año
|
||||
includeSubDomains: true,
|
||||
},
|
||||
}));
|
||||
|
||||
// CORS configurado
|
||||
app.enableCors({
|
||||
origin: process.env.CORS_ORIGINS?.split(',') || false,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. FRONTEND - SEGURIDAD
|
||||
|
||||
### Almacenamiento de Tokens
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: Token en localStorage (vulnerable a XSS)
|
||||
localStorage.setItem('token', accessToken);
|
||||
|
||||
// ✅ MEJOR: HttpOnly cookies (configurado desde backend)
|
||||
// El token se maneja automaticamente por el navegador
|
||||
|
||||
// ✅ ALTERNATIVA: Si debe estar en JS, usar memoria
|
||||
class TokenStore {
|
||||
private accessToken: string | null = null;
|
||||
|
||||
setToken(token: string) {
|
||||
this.accessToken = token;
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
clearToken() {
|
||||
this.accessToken = null;
|
||||
}
|
||||
}
|
||||
|
||||
export const tokenStore = new TokenStore();
|
||||
```
|
||||
|
||||
### Prevenir XSS en React
|
||||
|
||||
```typescript
|
||||
// ❌ INCORRECTO: dangerouslySetInnerHTML sin sanitizar
|
||||
<div dangerouslySetInnerHTML={{ __html: userInput }} />
|
||||
|
||||
// ✅ CORRECTO: Sanitizar primero
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
<div dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(userInput)
|
||||
}} />
|
||||
|
||||
// ✅ MEJOR: Evitar dangerouslySetInnerHTML cuando sea posible
|
||||
<div>{userInput}</div> // React escapa automaticamente
|
||||
```
|
||||
|
||||
### Validacion en Frontend (Defense in Depth)
|
||||
|
||||
```typescript
|
||||
// src/shared/schemas/user.schema.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
export const createUserSchema = z.object({
|
||||
email: z.string()
|
||||
.email('Email invalido')
|
||||
.max(255)
|
||||
.transform(v => v.toLowerCase().trim()),
|
||||
|
||||
firstName: z.string()
|
||||
.min(2, 'Minimo 2 caracteres')
|
||||
.max(100)
|
||||
.regex(/^[a-zA-ZÀ-ÿ\s'-]+$/, 'Solo letras permitidas')
|
||||
.transform(v => v.trim()),
|
||||
|
||||
password: z.string()
|
||||
.min(8, 'Minimo 8 caracteres')
|
||||
.max(128)
|
||||
.regex(
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
|
||||
'Debe incluir mayuscula, minuscula, numero y simbolo',
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. CHECKLIST DE SEGURIDAD
|
||||
|
||||
```
|
||||
Input/Output:
|
||||
[ ] Todos los inputs validados con class-validator
|
||||
[ ] HTML sanitizado antes de renderizar
|
||||
[ ] SQL usa queries parametrizadas
|
||||
[ ] Datos sensibles nunca en logs
|
||||
[ ] ResponseDto excluye campos sensibles
|
||||
|
||||
Autenticacion:
|
||||
[ ] Passwords hasheados con bcrypt (rounds >= 10)
|
||||
[ ] JWT con expiracion corta (< 15min)
|
||||
[ ] Refresh tokens almacenados hasheados
|
||||
[ ] Logout revoca tokens
|
||||
[ ] Mensajes de error genericos (no revelar info)
|
||||
|
||||
Autorizacion:
|
||||
[ ] Guards en todos los endpoints protegidos
|
||||
[ ] Verificacion de ownership en recursos
|
||||
[ ] Roles y permisos implementados
|
||||
[ ] Principio de minimo privilegio
|
||||
|
||||
Infraestructura:
|
||||
[ ] HTTPS obligatorio
|
||||
[ ] Headers de seguridad (Helmet)
|
||||
[ ] CORS configurado correctamente
|
||||
[ ] Rate limiting implementado
|
||||
[ ] Secrets en variables de entorno
|
||||
|
||||
Frontend:
|
||||
[ ] No localStorage para tokens sensibles
|
||||
[ ] CSP configurado
|
||||
[ ] Validacion client-side (defense in depth)
|
||||
[ ] No exponer errores detallados a usuarios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. RECURSOS ADICIONALES
|
||||
|
||||
- OWASP Cheat Sheets: https://cheatsheetseries.owasp.org/
|
||||
- NestJS Security: https://docs.nestjs.com/security/helmet
|
||||
- React Security: https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patron de Seguridad
|
||||
727
core/orchestration/patrones/PATRON-TESTING.md
Normal file
727
core/orchestration/patrones/PATRON-TESTING.md
Normal file
@ -0,0 +1,727 @@
|
||||
# PATRÓN: TESTING
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Aplica a:** Backend (NestJS), Frontend (React)
|
||||
**Prioridad:** RECOMENDADA
|
||||
|
||||
---
|
||||
|
||||
## PROPÓSITO
|
||||
|
||||
Definir patrones estándar de testing para garantizar código de calidad.
|
||||
|
||||
---
|
||||
|
||||
## 1. TIPOS DE TESTS
|
||||
|
||||
| Tipo | Qué Testea | Herramienta | Cobertura Objetivo |
|
||||
|------|------------|-------------|-------------------|
|
||||
| **Unit** | Funciones/Clases aisladas | Jest | 70%+ |
|
||||
| **Integration** | Módulos integrados | Jest + Supertest | 50%+ |
|
||||
| **E2E** | Flujos completos | Jest + Supertest | Críticos |
|
||||
| **Component** | Componentes React | React Testing Library | 60%+ |
|
||||
|
||||
---
|
||||
|
||||
## 2. BACKEND: TEST DE SERVICE
|
||||
|
||||
### Template
|
||||
|
||||
```typescript
|
||||
// user.service.spec.ts
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { UserService } from './user.service';
|
||||
import { UserEntity } from '../entities/user.entity';
|
||||
import { CreateUserDto } from '../dto/create-user.dto';
|
||||
import { NotFoundException, ConflictException } from '@nestjs/common';
|
||||
|
||||
describe('UserService', () => {
|
||||
let service: UserService;
|
||||
let repository: jest.Mocked<Repository<UserEntity>>;
|
||||
|
||||
// Mock del repositorio
|
||||
const mockRepository = {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
count: jest.fn(),
|
||||
};
|
||||
|
||||
// Fixtures
|
||||
const mockUser: UserEntity = {
|
||||
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
status: 'active',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const createUserDto: CreateUserDto = {
|
||||
email: 'new@example.com',
|
||||
name: 'New User',
|
||||
password: 'SecurePass123!',
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
UserService,
|
||||
{
|
||||
provide: getRepositoryToken(UserEntity),
|
||||
useValue: mockRepository,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UserService>(UserService);
|
||||
repository = module.get(getRepositoryToken(UserEntity));
|
||||
|
||||
// Reset mocks
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('findOne', () => {
|
||||
it('should return user when found', async () => {
|
||||
// Arrange
|
||||
mockRepository.findOne.mockResolvedValue(mockUser);
|
||||
|
||||
// Act
|
||||
const result = await service.findOne(mockUser.id);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(mockRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: mockUser.id },
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw NotFoundException when user not found', async () => {
|
||||
// Arrange
|
||||
mockRepository.findOne.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.findOne('non-existent-id'))
|
||||
.rejects
|
||||
.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create user successfully', async () => {
|
||||
// Arrange
|
||||
mockRepository.findOne.mockResolvedValue(null); // No existe
|
||||
mockRepository.create.mockReturnValue(mockUser);
|
||||
mockRepository.save.mockResolvedValue(mockUser);
|
||||
|
||||
// Act
|
||||
const result = await service.create(createUserDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(mockRepository.findOne).toHaveBeenCalled();
|
||||
expect(mockRepository.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
email: createUserDto.email,
|
||||
name: createUserDto.name,
|
||||
})
|
||||
);
|
||||
expect(mockRepository.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw ConflictException when email exists', async () => {
|
||||
// Arrange
|
||||
mockRepository.findOne.mockResolvedValue(mockUser); // Ya existe
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.create(createUserDto))
|
||||
.rejects
|
||||
.toThrow(ConflictException);
|
||||
|
||||
expect(mockRepository.save).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return array of users', async () => {
|
||||
// Arrange
|
||||
const users = [mockUser, { ...mockUser, id: '2' }];
|
||||
mockRepository.find.mockResolvedValue(users);
|
||||
|
||||
// Act
|
||||
const result = await service.findAll();
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(2);
|
||||
expect(mockRepository.find).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return empty array when no users', async () => {
|
||||
// Arrange
|
||||
mockRepository.find.mockResolvedValue([]);
|
||||
|
||||
// Act
|
||||
const result = await service.findAll();
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update user successfully', async () => {
|
||||
// Arrange
|
||||
const updateDto = { name: 'Updated Name' };
|
||||
const updatedUser = { ...mockUser, ...updateDto };
|
||||
|
||||
mockRepository.findOne.mockResolvedValue(mockUser);
|
||||
mockRepository.save.mockResolvedValue(updatedUser);
|
||||
|
||||
// Act
|
||||
const result = await service.update(mockUser.id, updateDto);
|
||||
|
||||
// Assert
|
||||
expect(result.name).toBe('Updated Name');
|
||||
expect(mockRepository.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw NotFoundException when user not found', async () => {
|
||||
// Arrange
|
||||
mockRepository.findOne.mockResolvedValue(null);
|
||||
|
||||
// Act & Assert
|
||||
await expect(service.update('non-existent', { name: 'Test' }))
|
||||
.rejects
|
||||
.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it('should remove user successfully', async () => {
|
||||
// Arrange
|
||||
mockRepository.findOne.mockResolvedValue(mockUser);
|
||||
mockRepository.remove.mockResolvedValue(mockUser);
|
||||
|
||||
// Act
|
||||
await service.remove(mockUser.id);
|
||||
|
||||
// Assert
|
||||
expect(mockRepository.remove).toHaveBeenCalledWith(mockUser);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. BACKEND: TEST DE CONTROLLER
|
||||
|
||||
### Template
|
||||
|
||||
```typescript
|
||||
// user.controller.spec.ts
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserController } from './user.controller';
|
||||
import { UserService } from '../services/user.service';
|
||||
import { CreateUserDto } from '../dto/create-user.dto';
|
||||
import { UserEntity } from '../entities/user.entity';
|
||||
import { NotFoundException } from '@nestjs/common';
|
||||
|
||||
describe('UserController', () => {
|
||||
let controller: UserController;
|
||||
let service: jest.Mocked<UserService>;
|
||||
|
||||
const mockService = {
|
||||
findAll: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
};
|
||||
|
||||
const mockUser: UserEntity = {
|
||||
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
status: 'active',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [UserController],
|
||||
providers: [
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: mockService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<UserController>(UserController);
|
||||
service = module.get(UserService);
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return array of users', async () => {
|
||||
// Arrange
|
||||
mockService.findAll.mockResolvedValue([mockUser]);
|
||||
|
||||
// Act
|
||||
const result = await controller.findAll();
|
||||
|
||||
// Assert
|
||||
expect(result).toHaveLength(1);
|
||||
expect(service.findAll).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOne', () => {
|
||||
it('should return user by id', async () => {
|
||||
// Arrange
|
||||
mockService.findOne.mockResolvedValue(mockUser);
|
||||
|
||||
// Act
|
||||
const result = await controller.findOne(mockUser.id);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(service.findOne).toHaveBeenCalledWith(mockUser.id);
|
||||
});
|
||||
|
||||
it('should propagate NotFoundException', async () => {
|
||||
// Arrange
|
||||
mockService.findOne.mockRejectedValue(new NotFoundException());
|
||||
|
||||
// Act & Assert
|
||||
await expect(controller.findOne('non-existent'))
|
||||
.rejects
|
||||
.toThrow(NotFoundException);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create and return user', async () => {
|
||||
// Arrange
|
||||
const createDto: CreateUserDto = {
|
||||
email: 'new@example.com',
|
||||
name: 'New User',
|
||||
password: 'Pass123!',
|
||||
};
|
||||
mockService.create.mockResolvedValue(mockUser);
|
||||
|
||||
// Act
|
||||
const result = await controller.create(createDto);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(service.create).toHaveBeenCalledWith(createDto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should update and return user', async () => {
|
||||
// Arrange
|
||||
const updateDto = { name: 'Updated' };
|
||||
const updated = { ...mockUser, ...updateDto };
|
||||
mockService.update.mockResolvedValue(updated);
|
||||
|
||||
// Act
|
||||
const result = await controller.update(mockUser.id, updateDto);
|
||||
|
||||
// Assert
|
||||
expect(result.name).toBe('Updated');
|
||||
expect(service.update).toHaveBeenCalledWith(mockUser.id, updateDto);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it('should remove user', async () => {
|
||||
// Arrange
|
||||
mockService.remove.mockResolvedValue(undefined);
|
||||
|
||||
// Act
|
||||
await controller.remove(mockUser.id);
|
||||
|
||||
// Assert
|
||||
expect(service.remove).toHaveBeenCalledWith(mockUser.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. BACKEND: TEST E2E
|
||||
|
||||
### Template
|
||||
|
||||
```typescript
|
||||
// user.e2e-spec.ts
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from '../src/app.module';
|
||||
|
||||
describe('UserController (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
let createdUserId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe('/users (POST)', () => {
|
||||
it('should create user', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/users')
|
||||
.send({
|
||||
email: 'e2e@test.com',
|
||||
name: 'E2E User',
|
||||
password: 'SecurePass123!',
|
||||
})
|
||||
.expect(201)
|
||||
.expect((res) => {
|
||||
expect(res.body.id).toBeDefined();
|
||||
expect(res.body.email).toBe('e2e@test.com');
|
||||
createdUserId = res.body.id;
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject invalid email', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/users')
|
||||
.send({
|
||||
email: 'invalid-email',
|
||||
name: 'Test',
|
||||
password: 'Pass123!',
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should reject duplicate email', () => {
|
||||
return request(app.getHttpServer())
|
||||
.post('/users')
|
||||
.send({
|
||||
email: 'e2e@test.com', // Ya existe
|
||||
name: 'Duplicate',
|
||||
password: 'Pass123!',
|
||||
})
|
||||
.expect(409);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/users (GET)', () => {
|
||||
it('should return users list', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/users')
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(Array.isArray(res.body)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/users/:id (GET)', () => {
|
||||
it('should return user by id', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/users/${createdUserId}`)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.id).toBe(createdUserId);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent user', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/users/550e8400-e29b-41d4-a716-446655440000')
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/users/:id (PUT)', () => {
|
||||
it('should update user', () => {
|
||||
return request(app.getHttpServer())
|
||||
.put(`/users/${createdUserId}`)
|
||||
.send({ name: 'Updated Name' })
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.name).toBe('Updated Name');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/users/:id (DELETE)', () => {
|
||||
it('should delete user', () => {
|
||||
return request(app.getHttpServer())
|
||||
.delete(`/users/${createdUserId}`)
|
||||
.expect(204);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. FRONTEND: TEST DE COMPONENTE
|
||||
|
||||
### Template con React Testing Library
|
||||
|
||||
```typescript
|
||||
// UserCard.test.tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { UserCard } from './UserCard';
|
||||
import { User } from '@/types/user.types';
|
||||
|
||||
describe('UserCard', () => {
|
||||
const mockUser: User = {
|
||||
id: '1',
|
||||
email: 'test@example.com',
|
||||
name: 'Test User',
|
||||
status: 'active',
|
||||
};
|
||||
|
||||
const mockOnEdit = jest.fn();
|
||||
const mockOnDelete = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render user information', () => {
|
||||
render(<UserCard user={mockUser} />);
|
||||
|
||||
expect(screen.getByText('Test User')).toBeInTheDocument();
|
||||
expect(screen.getByText('test@example.com')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onEdit when edit button clicked', () => {
|
||||
render(
|
||||
<UserCard
|
||||
user={mockUser}
|
||||
onEdit={mockOnEdit}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
|
||||
|
||||
expect(mockOnEdit).toHaveBeenCalledWith(mockUser);
|
||||
});
|
||||
|
||||
it('should call onDelete when delete button clicked', () => {
|
||||
render(
|
||||
<UserCard
|
||||
user={mockUser}
|
||||
onDelete={mockOnDelete}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /delete/i }));
|
||||
|
||||
expect(mockOnDelete).toHaveBeenCalledWith(mockUser.id);
|
||||
});
|
||||
|
||||
it('should show loading state', () => {
|
||||
render(<UserCard user={mockUser} isLoading />);
|
||||
|
||||
expect(screen.getByTestId('loading-skeleton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide actions when not provided', () => {
|
||||
render(<UserCard user={mockUser} />);
|
||||
|
||||
expect(screen.queryByRole('button', { name: /edit/i })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. FRONTEND: TEST DE HOOK
|
||||
|
||||
### Template
|
||||
|
||||
```typescript
|
||||
// useUsers.test.tsx
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { useUsers, useCreateUser } from './useUsers';
|
||||
import { userService } from '@/services/user.service';
|
||||
|
||||
// Mock del servicio
|
||||
jest.mock('@/services/user.service');
|
||||
const mockUserService = userService as jest.Mocked<typeof userService>;
|
||||
|
||||
describe('useUsers', () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = ({ children }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('useUsers', () => {
|
||||
it('should fetch users', async () => {
|
||||
// Arrange
|
||||
const mockUsers = [
|
||||
{ id: '1', name: 'User 1', email: 'u1@test.com' },
|
||||
{ id: '2', name: 'User 2', email: 'u2@test.com' },
|
||||
];
|
||||
mockUserService.getAll.mockResolvedValue(mockUsers);
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(() => useUsers(), { wrapper });
|
||||
|
||||
// Assert - Initially loading
|
||||
expect(result.current.isLoading).toBe(true);
|
||||
|
||||
// Wait for data
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(mockUsers);
|
||||
});
|
||||
|
||||
it('should handle error', async () => {
|
||||
// Arrange
|
||||
mockUserService.getAll.mockRejectedValue(new Error('Network error'));
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(() => useUsers(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCreateUser', () => {
|
||||
it('should create user and invalidate cache', async () => {
|
||||
// Arrange
|
||||
const newUser = { id: '3', name: 'New', email: 'new@test.com' };
|
||||
mockUserService.create.mockResolvedValue(newUser);
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(() => useCreateUser(), { wrapper });
|
||||
|
||||
await result.current.mutateAsync({
|
||||
name: 'New',
|
||||
email: 'new@test.com',
|
||||
password: 'Pass123!',
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(mockUserService.create).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. CONVENCIONES DE NOMBRES
|
||||
|
||||
```typescript
|
||||
// Archivos de test
|
||||
user.service.spec.ts // Unit test backend
|
||||
user.controller.spec.ts // Unit test controller
|
||||
user.e2e-spec.ts // E2E test
|
||||
UserCard.test.tsx // Component test
|
||||
useUsers.test.tsx // Hook test
|
||||
|
||||
// Describe blocks
|
||||
describe('UserService', () => { ... });
|
||||
describe('findOne', () => { ... });
|
||||
|
||||
// Test cases
|
||||
it('should return user when found', () => { ... });
|
||||
it('should throw NotFoundException when user not found', () => { ... });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. CHECKLIST DE TESTING
|
||||
|
||||
```
|
||||
Por Service:
|
||||
[ ] Test de cada método público
|
||||
[ ] Test de casos de éxito
|
||||
[ ] Test de casos de error (NotFoundException, etc.)
|
||||
[ ] Test de validaciones de negocio
|
||||
|
||||
Por Controller:
|
||||
[ ] Test de cada endpoint
|
||||
[ ] Test de status codes correctos
|
||||
[ ] Test de propagación de errores
|
||||
|
||||
Por Componente:
|
||||
[ ] Test de render correcto
|
||||
[ ] Test de eventos (click, change)
|
||||
[ ] Test de estados (loading, error)
|
||||
[ ] Test de props opcionales
|
||||
|
||||
Por Hook:
|
||||
[ ] Test de fetch exitoso
|
||||
[ ] Test de manejo de error
|
||||
[ ] Test de mutaciones
|
||||
|
||||
Cobertura:
|
||||
[ ] npm run test:cov muestra 70%+ en services
|
||||
[ ] npm run test:cov muestra 60%+ en components
|
||||
[ ] Tests críticos e2e pasan
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. COMANDOS
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
npm run test # Unit tests
|
||||
npm run test:watch # Watch mode
|
||||
npm run test:cov # Con cobertura
|
||||
npm run test:e2e # E2E tests
|
||||
|
||||
# Frontend
|
||||
npm run test # All tests
|
||||
npm run test -- --watch # Watch mode
|
||||
npm run test -- --coverage # Con cobertura
|
||||
npm run test -- UserCard # Test específico
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patrón de Testing
|
||||
678
core/orchestration/patrones/PATRON-TRANSACCIONES.md
Normal file
678
core/orchestration/patrones/PATRON-TRANSACCIONES.md
Normal file
@ -0,0 +1,678 @@
|
||||
# PATRON DE TRANSACCIONES
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - Seguir para integridad de datos
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPOSITO
|
||||
|
||||
Definir patrones de manejo de transacciones de base de datos para garantizar integridad de datos, consistencia y correcta recuperacion de errores.
|
||||
|
||||
---
|
||||
|
||||
## 1. PRINCIPIOS ACID
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ PROPIEDADES ACID ║
|
||||
╠══════════════════════════════════════════════════════════════════════╣
|
||||
║ ║
|
||||
║ A - Atomicity (Atomicidad) ║
|
||||
║ Todo o nada. Si una parte falla, se revierte todo. ║
|
||||
║ ║
|
||||
║ C - Consistency (Consistencia) ║
|
||||
║ La BD pasa de un estado valido a otro estado valido. ║
|
||||
║ ║
|
||||
║ I - Isolation (Aislamiento) ║
|
||||
║ Transacciones concurrentes no interfieren entre si. ║
|
||||
║ ║
|
||||
║ D - Durability (Durabilidad) ║
|
||||
║ Una vez confirmada, la transaccion persiste. ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. CUANDO USAR TRANSACCIONES
|
||||
|
||||
### SI Usar Transaccion
|
||||
|
||||
```
|
||||
✅ Multiples operaciones que deben ser atomicas
|
||||
✅ Operaciones que afectan multiples tablas relacionadas
|
||||
✅ Operaciones financieras o criticas
|
||||
✅ Creacion de entidades con relaciones obligatorias
|
||||
✅ Actualizaciones que requieren consistencia
|
||||
```
|
||||
|
||||
### NO Necesitas Transaccion
|
||||
|
||||
```
|
||||
❌ Una sola query simple (SELECT, INSERT, UPDATE)
|
||||
❌ Operaciones de solo lectura
|
||||
❌ Operaciones independientes sin relacion
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. TYPEORM - METODOS DE TRANSACCION
|
||||
|
||||
### 3.1 QueryRunner (Recomendado para control total)
|
||||
|
||||
```typescript
|
||||
// src/modules/order/services/order.service.ts
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class OrderService {
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
async createOrder(dto: CreateOrderDto): Promise<Order> {
|
||||
// Crear QueryRunner
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
// Conectar y comenzar transaccion
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
// 1. Crear la orden
|
||||
const order = queryRunner.manager.create(OrderEntity, {
|
||||
userId: dto.userId,
|
||||
status: OrderStatus.PENDING,
|
||||
total: 0,
|
||||
});
|
||||
await queryRunner.manager.save(order);
|
||||
|
||||
// 2. Crear items y calcular total
|
||||
let total = 0;
|
||||
for (const item of dto.items) {
|
||||
// Verificar stock
|
||||
const product = await queryRunner.manager.findOne(ProductEntity, {
|
||||
where: { id: item.productId },
|
||||
lock: { mode: 'pessimistic_write' }, // Lock para evitar race condition
|
||||
});
|
||||
|
||||
if (!product || product.stock < item.quantity) {
|
||||
throw new BadRequestException(
|
||||
`Stock insuficiente para producto ${item.productId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Crear item
|
||||
const orderItem = queryRunner.manager.create(OrderItemEntity, {
|
||||
orderId: order.id,
|
||||
productId: item.productId,
|
||||
quantity: item.quantity,
|
||||
price: product.price,
|
||||
});
|
||||
await queryRunner.manager.save(orderItem);
|
||||
|
||||
// Actualizar stock
|
||||
product.stock -= item.quantity;
|
||||
await queryRunner.manager.save(product);
|
||||
|
||||
total += product.price * item.quantity;
|
||||
}
|
||||
|
||||
// 3. Actualizar total de la orden
|
||||
order.total = total;
|
||||
await queryRunner.manager.save(order);
|
||||
|
||||
// 4. Confirmar transaccion
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
return order;
|
||||
|
||||
} catch (error) {
|
||||
// Revertir en caso de error
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
|
||||
} finally {
|
||||
// Liberar QueryRunner
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 DataSource.transaction() (Mas simple)
|
||||
|
||||
```typescript
|
||||
// Para casos mas simples
|
||||
async createUserWithProfile(dto: CreateUserWithProfileDto): Promise<User> {
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
// Crear usuario
|
||||
const user = manager.create(UserEntity, {
|
||||
email: dto.email,
|
||||
password: await hashPassword(dto.password),
|
||||
});
|
||||
await manager.save(user);
|
||||
|
||||
// Crear perfil
|
||||
const profile = manager.create(ProfileEntity, {
|
||||
userId: user.id,
|
||||
firstName: dto.firstName,
|
||||
lastName: dto.lastName,
|
||||
});
|
||||
await manager.save(profile);
|
||||
|
||||
return user;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 @Transaction Decorator (NestJS)
|
||||
|
||||
```typescript
|
||||
// src/shared/decorators/transactional.decorator.ts
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
export function Transactional() {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = async function (...args: any[]) {
|
||||
const dataSource: DataSource = this.dataSource;
|
||||
|
||||
return dataSource.transaction(async (manager) => {
|
||||
// Inyectar manager temporal
|
||||
const originalManager = this.manager;
|
||||
this.manager = manager;
|
||||
|
||||
try {
|
||||
return await originalMethod.apply(this, args);
|
||||
} finally {
|
||||
this.manager = originalManager;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
}
|
||||
|
||||
// Uso
|
||||
@Injectable()
|
||||
export class OrderService {
|
||||
constructor(
|
||||
private readonly dataSource: DataSource,
|
||||
@InjectRepository(OrderEntity)
|
||||
private readonly orderRepository: Repository<OrderEntity>,
|
||||
) {}
|
||||
|
||||
private manager: EntityManager;
|
||||
|
||||
@Transactional()
|
||||
async createOrder(dto: CreateOrderDto): Promise<Order> {
|
||||
// this.manager es el manager transaccional
|
||||
const order = this.manager.create(OrderEntity, dto);
|
||||
return this.manager.save(order);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. NIVELES DE AISLAMIENTO
|
||||
|
||||
### Niveles Disponibles
|
||||
|
||||
| Nivel | Dirty Reads | Non-Repeatable Reads | Phantom Reads |
|
||||
|-------|-------------|---------------------|---------------|
|
||||
| READ UNCOMMITTED | Posible | Posible | Posible |
|
||||
| READ COMMITTED | No | Posible | Posible |
|
||||
| REPEATABLE READ | No | No | Posible |
|
||||
| SERIALIZABLE | No | No | No |
|
||||
|
||||
### Configurar Nivel de Aislamiento
|
||||
|
||||
```typescript
|
||||
// Por defecto PostgreSQL usa READ COMMITTED
|
||||
// Para operaciones criticas, usar SERIALIZABLE
|
||||
|
||||
async processPayment(orderId: string): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
|
||||
// Configurar nivel de aislamiento
|
||||
await queryRunner.startTransaction('SERIALIZABLE');
|
||||
|
||||
try {
|
||||
// Operaciones criticas aqui
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
|
||||
// SERIALIZABLE puede fallar por conflictos
|
||||
if (error.code === '40001') { // Serialization failure
|
||||
// Reintentar la transaccion
|
||||
return this.processPayment(orderId);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. LOCKS (BLOQUEOS)
|
||||
|
||||
### 5.1 Pessimistic Locking
|
||||
|
||||
```typescript
|
||||
// Bloquear fila para escritura (otros esperan)
|
||||
async updateStock(productId: string, quantity: number): Promise<void> {
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
// Obtener producto con lock exclusivo
|
||||
const product = await manager.findOne(ProductEntity, {
|
||||
where: { id: productId },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
if (product.stock < quantity) {
|
||||
throw new BadRequestException('Stock insuficiente');
|
||||
}
|
||||
|
||||
product.stock -= quantity;
|
||||
await manager.save(product);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Optimistic Locking (Version)
|
||||
|
||||
```typescript
|
||||
// Entity con columna de version
|
||||
@Entity()
|
||||
export class ProductEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
stock: number;
|
||||
|
||||
@VersionColumn() // Auto-incrementa en cada update
|
||||
version: number;
|
||||
}
|
||||
|
||||
// El update falla si version cambio (alguien mas modifico)
|
||||
async updateStock(productId: string, quantity: number): Promise<void> {
|
||||
const product = await this.productRepository.findOne({
|
||||
where: { id: productId },
|
||||
});
|
||||
|
||||
product.stock -= quantity;
|
||||
|
||||
try {
|
||||
await this.productRepository.save(product);
|
||||
} catch (error) {
|
||||
if (error instanceof OptimisticLockVersionMismatchError) {
|
||||
// Reintentar o notificar conflicto
|
||||
throw new ConflictException('El producto fue modificado. Reintente.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cuando Usar Cada Tipo
|
||||
|
||||
| Scenario | Lock Recomendado |
|
||||
|----------|------------------|
|
||||
| Alta concurrencia, conflictos raros | Optimistic |
|
||||
| Operaciones financieras criticas | Pessimistic |
|
||||
| Updates frecuentes al mismo registro | Pessimistic |
|
||||
| Lecturas frecuentes, updates raros | Optimistic |
|
||||
|
||||
---
|
||||
|
||||
## 6. PATRONES COMUNES
|
||||
|
||||
### 6.1 Unit of Work Pattern
|
||||
|
||||
```typescript
|
||||
// src/shared/database/unit-of-work.ts
|
||||
@Injectable()
|
||||
export class UnitOfWork {
|
||||
private queryRunner: QueryRunner;
|
||||
|
||||
constructor(private readonly dataSource: DataSource) {}
|
||||
|
||||
async begin(): Promise<void> {
|
||||
this.queryRunner = this.dataSource.createQueryRunner();
|
||||
await this.queryRunner.connect();
|
||||
await this.queryRunner.startTransaction();
|
||||
}
|
||||
|
||||
getRepository<T>(entity: EntityTarget<T>): Repository<T> {
|
||||
return this.queryRunner.manager.getRepository(entity);
|
||||
}
|
||||
|
||||
async commit(): Promise<void> {
|
||||
await this.queryRunner.commitTransaction();
|
||||
await this.queryRunner.release();
|
||||
}
|
||||
|
||||
async rollback(): Promise<void> {
|
||||
await this.queryRunner.rollbackTransaction();
|
||||
await this.queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Uso
|
||||
@Injectable()
|
||||
export class OrderService {
|
||||
constructor(private readonly uow: UnitOfWork) {}
|
||||
|
||||
async createOrder(dto: CreateOrderDto): Promise<Order> {
|
||||
await this.uow.begin();
|
||||
|
||||
try {
|
||||
const orderRepo = this.uow.getRepository(OrderEntity);
|
||||
const productRepo = this.uow.getRepository(ProductEntity);
|
||||
|
||||
// Operaciones...
|
||||
|
||||
await this.uow.commit();
|
||||
return order;
|
||||
} catch (error) {
|
||||
await this.uow.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Saga Pattern (Para transacciones distribuidas)
|
||||
|
||||
```typescript
|
||||
// Cuando no puedes usar transaccion de BD (microservicios)
|
||||
interface SagaStep {
|
||||
execute(): Promise<void>;
|
||||
compensate(): Promise<void>; // Revertir si falla
|
||||
}
|
||||
|
||||
class CreateOrderSaga {
|
||||
private executedSteps: SagaStep[] = [];
|
||||
|
||||
async execute(steps: SagaStep[]): Promise<void> {
|
||||
try {
|
||||
for (const step of steps) {
|
||||
await step.execute();
|
||||
this.executedSteps.push(step);
|
||||
}
|
||||
} catch (error) {
|
||||
// Compensar en orden inverso
|
||||
for (const step of this.executedSteps.reverse()) {
|
||||
await step.compensate();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Uso
|
||||
const saga = new CreateOrderSaga();
|
||||
await saga.execute([
|
||||
{
|
||||
execute: () => this.orderService.create(orderDto),
|
||||
compensate: () => this.orderService.delete(orderId),
|
||||
},
|
||||
{
|
||||
execute: () => this.inventoryService.reserve(items),
|
||||
compensate: () => this.inventoryService.release(items),
|
||||
},
|
||||
{
|
||||
execute: () => this.paymentService.charge(payment),
|
||||
compensate: () => this.paymentService.refund(payment),
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
### 6.3 Retry con Backoff
|
||||
|
||||
```typescript
|
||||
// Para manejar deadlocks y conflictos
|
||||
async withRetry<T>(
|
||||
operation: () => Promise<T>,
|
||||
maxRetries: number = 3,
|
||||
baseDelay: number = 100,
|
||||
): Promise<T> {
|
||||
let lastError: Error;
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
|
||||
// Solo reintentar errores transitorios
|
||||
const isRetryable =
|
||||
error.code === '40001' || // Serialization failure
|
||||
error.code === '40P01' || // Deadlock
|
||||
error.code === '55P03'; // Lock not available
|
||||
|
||||
if (!isRetryable || attempt === maxRetries) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Exponential backoff con jitter
|
||||
const delay = baseDelay * Math.pow(2, attempt - 1) * (0.5 + Math.random());
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
// Uso
|
||||
async createOrder(dto: CreateOrderDto): Promise<Order> {
|
||||
return this.withRetry(() => this.createOrderTransaction(dto));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. ERRORES COMUNES Y SOLUCIONES
|
||||
|
||||
### 7.1 Deadlock
|
||||
|
||||
```typescript
|
||||
// ❌ PROBLEMA: Deadlock por orden inconsistente de locks
|
||||
// Transaction 1: Lock A, then B
|
||||
// Transaction 2: Lock B, then A
|
||||
|
||||
// ✅ SOLUCION: Siempre lockear en el mismo orden
|
||||
async transferFunds(fromId: string, toId: string, amount: number): Promise<void> {
|
||||
// Ordenar IDs para lockear siempre en el mismo orden
|
||||
const [firstId, secondId] = [fromId, toId].sort();
|
||||
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
const firstAccount = await manager.findOne(AccountEntity, {
|
||||
where: { id: firstId },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
const secondAccount = await manager.findOne(AccountEntity, {
|
||||
where: { id: secondId },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
// Ahora operar
|
||||
const from = firstId === fromId ? firstAccount : secondAccount;
|
||||
const to = firstId === toId ? firstAccount : secondAccount;
|
||||
|
||||
from.balance -= amount;
|
||||
to.balance += amount;
|
||||
|
||||
await manager.save([from, to]);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Connection Leak
|
||||
|
||||
```typescript
|
||||
// ❌ PROBLEMA: QueryRunner no liberado
|
||||
async badMethod(): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
const data = await queryRunner.manager.find(Entity);
|
||||
// Si hay error aqui, queryRunner nunca se libera!
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
await queryRunner.release();
|
||||
}
|
||||
|
||||
// ✅ SOLUCION: Siempre usar try/finally
|
||||
async goodMethod(): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const data = await queryRunner.manager.find(Entity);
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release(); // SIEMPRE se ejecuta
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 Long-Running Transactions
|
||||
|
||||
```typescript
|
||||
// ❌ PROBLEMA: Transaccion muy larga
|
||||
async processAllOrders(): Promise<void> {
|
||||
return this.dataSource.transaction(async (manager) => {
|
||||
const orders = await manager.find(OrderEntity); // Puede ser miles
|
||||
for (const order of orders) {
|
||||
await this.processOrder(order); // Minutos de bloqueo
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ SOLUCION: Procesar en batches con transacciones cortas
|
||||
async processAllOrders(): Promise<void> {
|
||||
const BATCH_SIZE = 100;
|
||||
let processed = 0;
|
||||
|
||||
while (true) {
|
||||
const orders = await this.orderRepository.find({
|
||||
where: { status: OrderStatus.PENDING },
|
||||
take: BATCH_SIZE,
|
||||
});
|
||||
|
||||
if (orders.length === 0) break;
|
||||
|
||||
// Transaccion corta por batch
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
for (const order of orders) {
|
||||
await this.processOrderInTransaction(manager, order);
|
||||
}
|
||||
});
|
||||
|
||||
processed += orders.length;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. TESTING DE TRANSACCIONES
|
||||
|
||||
```typescript
|
||||
// src/modules/order/order.service.spec.ts
|
||||
describe('OrderService', () => {
|
||||
let service: OrderService;
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
imports: [TypeOrmModule.forRoot(testConfig)],
|
||||
providers: [OrderService],
|
||||
}).compile();
|
||||
|
||||
service = module.get(OrderService);
|
||||
dataSource = module.get(DataSource);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Limpiar despues de cada test
|
||||
await dataSource.synchronize(true);
|
||||
});
|
||||
|
||||
describe('createOrder', () => {
|
||||
it('should rollback if stock is insufficient', async () => {
|
||||
// Arrange
|
||||
const product = await productRepo.save({
|
||||
name: 'Test',
|
||||
stock: 5,
|
||||
price: 100,
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
await expect(
|
||||
service.createOrder({
|
||||
items: [{ productId: product.id, quantity: 10 }],
|
||||
}),
|
||||
).rejects.toThrow('Stock insuficiente');
|
||||
|
||||
// Verificar que stock no cambio (rollback funciono)
|
||||
const updatedProduct = await productRepo.findOne({
|
||||
where: { id: product.id },
|
||||
});
|
||||
expect(updatedProduct.stock).toBe(5);
|
||||
});
|
||||
|
||||
it('should commit successfully with valid data', async () => {
|
||||
// ...
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. CHECKLIST DE TRANSACCIONES
|
||||
|
||||
```
|
||||
Antes de implementar:
|
||||
[ ] ¿Necesito atomicidad? (multiples operaciones)
|
||||
[ ] ¿Que nivel de aislamiento necesito?
|
||||
[ ] ¿Necesito locks? ¿Pesimista u optimista?
|
||||
|
||||
Durante implementacion:
|
||||
[ ] QueryRunner siempre liberado (finally)
|
||||
[ ] Rollback en catch
|
||||
[ ] Manejo de errores transitorios (retry)
|
||||
[ ] Transacciones lo mas cortas posible
|
||||
[ ] Orden consistente de locks (evitar deadlocks)
|
||||
|
||||
Testing:
|
||||
[ ] Test de rollback en error
|
||||
[ ] Test de commit exitoso
|
||||
[ ] Test de concurrencia (si aplica)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patron de Codigo
|
||||
619
core/orchestration/patrones/PATRON-VALIDACION.md
Normal file
619
core/orchestration/patrones/PATRON-VALIDACION.md
Normal file
@ -0,0 +1,619 @@
|
||||
# PATRÓN: VALIDACIÓN DE DATOS
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Aplica a:** Backend (NestJS/Express), Frontend (React)
|
||||
**Prioridad:** OBLIGATORIA
|
||||
|
||||
---
|
||||
|
||||
## PROPÓSITO
|
||||
|
||||
Definir patrones estándar de validación de datos para garantizar consistencia en toda la aplicación.
|
||||
|
||||
---
|
||||
|
||||
## PRINCIPIO FUNDAMENTAL
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ VALIDACIÓN EN CAPAS ║
|
||||
║ ║
|
||||
║ 1. Frontend: UX inmediata (opcional pero recomendada) ║
|
||||
║ 2. DTO: Validación de entrada (OBLIGATORIA) ║
|
||||
║ 3. Service: Validación de negocio (cuando aplica) ║
|
||||
║ 4. Database: Constraints (última línea de defensa) ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. VALIDACIÓN EN DTO (NestJS - class-validator)
|
||||
|
||||
### Decoradores Básicos
|
||||
|
||||
```typescript
|
||||
import {
|
||||
IsString,
|
||||
IsEmail,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
IsInt,
|
||||
IsNumber,
|
||||
IsBoolean,
|
||||
IsDate,
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsUrl,
|
||||
IsPhoneNumber,
|
||||
} from 'class-validator';
|
||||
```
|
||||
|
||||
### Decoradores de Longitud
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Length,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
Min,
|
||||
Max,
|
||||
ArrayMinSize,
|
||||
ArrayMaxSize,
|
||||
} from 'class-validator';
|
||||
```
|
||||
|
||||
### Decoradores de Formato
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Matches,
|
||||
IsAlpha,
|
||||
IsAlphanumeric,
|
||||
IsAscii,
|
||||
Contains,
|
||||
IsISO8601,
|
||||
IsCreditCard,
|
||||
IsHexColor,
|
||||
IsJSON,
|
||||
} from 'class-validator';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. PATRONES POR TIPO DE DATO
|
||||
|
||||
### Email
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR
|
||||
@ApiProperty({
|
||||
description: 'Correo electrónico del usuario',
|
||||
example: 'usuario@empresa.com',
|
||||
})
|
||||
@IsEmail({}, { message: 'El correo electrónico no es válido' })
|
||||
@IsNotEmpty({ message: 'El correo electrónico es requerido' })
|
||||
@MaxLength(255, { message: 'El correo no puede exceder 255 caracteres' })
|
||||
@Transform(({ value }) => value?.toLowerCase().trim())
|
||||
email: string;
|
||||
```
|
||||
|
||||
### Password
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR - Contraseña segura
|
||||
@ApiProperty({
|
||||
description: 'Contraseña (mín 8 caracteres, 1 mayúscula, 1 número, 1 especial)',
|
||||
example: 'MiPassword123!',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: 'La contraseña es requerida' })
|
||||
@MinLength(8, { message: 'La contraseña debe tener al menos 8 caracteres' })
|
||||
@MaxLength(100, { message: 'La contraseña no puede exceder 100 caracteres' })
|
||||
@Matches(
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/,
|
||||
{ message: 'La contraseña debe contener mayúscula, minúscula, número y carácter especial' }
|
||||
)
|
||||
password: string;
|
||||
```
|
||||
|
||||
### UUID
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR
|
||||
@ApiProperty({
|
||||
description: 'ID único del recurso',
|
||||
example: '550e8400-e29b-41d4-a716-446655440000',
|
||||
})
|
||||
@IsUUID('4', { message: 'El ID debe ser un UUID válido' })
|
||||
@IsNotEmpty({ message: 'El ID es requerido' })
|
||||
id: string;
|
||||
|
||||
// UUID Opcional (para referencias)
|
||||
@ApiPropertyOptional({
|
||||
description: 'ID del usuario relacionado',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID('4', { message: 'El ID de usuario debe ser un UUID válido' })
|
||||
userId?: string;
|
||||
```
|
||||
|
||||
### Nombre/Texto Simple
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR
|
||||
@ApiProperty({
|
||||
description: 'Nombre del usuario',
|
||||
example: 'Juan Pérez',
|
||||
minLength: 2,
|
||||
maxLength: 100,
|
||||
})
|
||||
@IsString({ message: 'El nombre debe ser texto' })
|
||||
@IsNotEmpty({ message: 'El nombre es requerido' })
|
||||
@MinLength(2, { message: 'El nombre debe tener al menos 2 caracteres' })
|
||||
@MaxLength(100, { message: 'El nombre no puede exceder 100 caracteres' })
|
||||
@Transform(({ value }) => value?.trim())
|
||||
name: string;
|
||||
```
|
||||
|
||||
### Número Entero
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR - Entero positivo
|
||||
@ApiProperty({
|
||||
description: 'Cantidad de items',
|
||||
example: 10,
|
||||
minimum: 1,
|
||||
})
|
||||
@IsInt({ message: 'La cantidad debe ser un número entero' })
|
||||
@Min(1, { message: 'La cantidad mínima es 1' })
|
||||
@Max(10000, { message: 'La cantidad máxima es 10,000' })
|
||||
quantity: number;
|
||||
|
||||
// Entero con valor por defecto
|
||||
@ApiPropertyOptional({
|
||||
description: 'Página actual',
|
||||
default: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Transform(({ value }) => parseInt(value) || 1)
|
||||
page?: number = 1;
|
||||
```
|
||||
|
||||
### Número Decimal (Dinero)
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR - Precio/Dinero
|
||||
@ApiProperty({
|
||||
description: 'Precio del producto',
|
||||
example: 99.99,
|
||||
minimum: 0,
|
||||
})
|
||||
@IsNumber(
|
||||
{ maxDecimalPlaces: 2 },
|
||||
{ message: 'El precio debe tener máximo 2 decimales' }
|
||||
)
|
||||
@Min(0, { message: 'El precio no puede ser negativo' })
|
||||
@Max(999999.99, { message: 'El precio máximo es 999,999.99' })
|
||||
price: number;
|
||||
```
|
||||
|
||||
### Boolean
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR
|
||||
@ApiProperty({
|
||||
description: 'Estado activo del usuario',
|
||||
example: true,
|
||||
})
|
||||
@IsBoolean({ message: 'El valor debe ser verdadero o falso' })
|
||||
@IsNotEmpty()
|
||||
isActive: boolean;
|
||||
|
||||
// Boolean opcional con default
|
||||
@ApiPropertyOptional({
|
||||
description: 'Enviar notificación',
|
||||
default: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
sendNotification?: boolean = false;
|
||||
```
|
||||
|
||||
### Enum
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR
|
||||
export enum UserStatus {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
SUSPENDED = 'suspended',
|
||||
}
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Estado del usuario',
|
||||
enum: UserStatus,
|
||||
example: UserStatus.ACTIVE,
|
||||
})
|
||||
@IsEnum(UserStatus, { message: 'El estado debe ser: active, inactive o suspended' })
|
||||
@IsNotEmpty()
|
||||
status: UserStatus;
|
||||
```
|
||||
|
||||
### Fecha
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR - Fecha ISO
|
||||
@ApiProperty({
|
||||
description: 'Fecha de nacimiento',
|
||||
example: '1990-05-15',
|
||||
})
|
||||
@IsISO8601({}, { message: 'La fecha debe estar en formato ISO 8601' })
|
||||
@IsNotEmpty()
|
||||
birthDate: string;
|
||||
|
||||
// Fecha como Date object
|
||||
@ApiProperty({
|
||||
description: 'Fecha de inicio',
|
||||
example: '2024-01-15T10:30:00Z',
|
||||
})
|
||||
@IsDate({ message: 'Debe ser una fecha válida' })
|
||||
@Type(() => Date)
|
||||
@IsNotEmpty()
|
||||
startDate: Date;
|
||||
```
|
||||
|
||||
### URL
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR
|
||||
@ApiPropertyOptional({
|
||||
description: 'URL del avatar',
|
||||
example: 'https://example.com/avatar.jpg',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUrl({}, { message: 'La URL no es válida' })
|
||||
@MaxLength(500)
|
||||
avatarUrl?: string;
|
||||
```
|
||||
|
||||
### Teléfono
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR - México
|
||||
@ApiPropertyOptional({
|
||||
description: 'Número de teléfono',
|
||||
example: '+521234567890',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsPhoneNumber('MX', { message: 'El número de teléfono no es válido' })
|
||||
phone?: string;
|
||||
|
||||
// Alternativa con Regex
|
||||
@IsOptional()
|
||||
@Matches(/^\+?[1-9]\d{1,14}$/, { message: 'Formato de teléfono inválido' })
|
||||
phone?: string;
|
||||
```
|
||||
|
||||
### Array
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR - Array de strings
|
||||
@ApiProperty({
|
||||
description: 'Etiquetas del producto',
|
||||
example: ['electrónica', 'ofertas'],
|
||||
type: [String],
|
||||
})
|
||||
@IsArray({ message: 'Las etiquetas deben ser un arreglo' })
|
||||
@IsString({ each: true, message: 'Cada etiqueta debe ser texto' })
|
||||
@ArrayMinSize(1, { message: 'Debe haber al menos una etiqueta' })
|
||||
@ArrayMaxSize(10, { message: 'Máximo 10 etiquetas permitidas' })
|
||||
tags: string[];
|
||||
|
||||
// Array de UUIDs
|
||||
@ApiProperty({
|
||||
description: 'IDs de categorías',
|
||||
type: [String],
|
||||
})
|
||||
@IsArray()
|
||||
@IsUUID('4', { each: true, message: 'Cada ID debe ser un UUID válido' })
|
||||
@ArrayMinSize(1)
|
||||
categoryIds: string[];
|
||||
```
|
||||
|
||||
### JSON/Object
|
||||
|
||||
```typescript
|
||||
// PATRÓN ESTÁNDAR - Objeto flexible
|
||||
@ApiPropertyOptional({
|
||||
description: 'Metadatos adicionales',
|
||||
example: { key: 'value' },
|
||||
})
|
||||
@IsOptional()
|
||||
@IsObject({ message: 'Los metadatos deben ser un objeto' })
|
||||
metadata?: Record<string, any>;
|
||||
|
||||
// Objeto con estructura definida (usar class anidada)
|
||||
class AddressDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
street: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
city: string;
|
||||
|
||||
@IsString()
|
||||
@Length(5, 5)
|
||||
zipCode: string;
|
||||
}
|
||||
|
||||
@ApiProperty({ type: AddressDto })
|
||||
@ValidateNested()
|
||||
@Type(() => AddressDto)
|
||||
address: AddressDto;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. DTOs ESTÁNDAR
|
||||
|
||||
### CreateDto Pattern
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* DTO para crear usuario
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
* "email": "user@example.com",
|
||||
* "password": "SecurePass123!",
|
||||
* "name": "Juan Pérez"
|
||||
* }
|
||||
*/
|
||||
export class CreateUserDto {
|
||||
@ApiProperty({ description: 'Email del usuario', example: 'user@example.com' })
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(255)
|
||||
@Transform(({ value }) => value?.toLowerCase().trim())
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ description: 'Contraseña', example: 'SecurePass123!' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(8)
|
||||
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/)
|
||||
password: string;
|
||||
|
||||
@ApiProperty({ description: 'Nombre completo', example: 'Juan Pérez' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(2)
|
||||
@MaxLength(100)
|
||||
@Transform(({ value }) => value?.trim())
|
||||
name: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Teléfono', example: '+521234567890' })
|
||||
@IsOptional()
|
||||
@IsPhoneNumber('MX')
|
||||
phone?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### UpdateDto Pattern
|
||||
|
||||
```typescript
|
||||
import { PartialType, OmitType } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* DTO para actualizar usuario
|
||||
*
|
||||
* Todos los campos son opcionales.
|
||||
* Email y password NO se pueden actualizar aquí.
|
||||
*/
|
||||
export class UpdateUserDto extends PartialType(
|
||||
OmitType(CreateUserDto, ['email', 'password'] as const)
|
||||
) {}
|
||||
```
|
||||
|
||||
### QueryDto Pattern (Filtros y Paginación)
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* DTO para búsqueda y paginación de usuarios
|
||||
*/
|
||||
export class QueryUsersDto {
|
||||
@ApiPropertyOptional({ description: 'Página', default: 1 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Items por página', default: 20 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
limit?: number = 20;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Buscar por nombre o email' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
search?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Filtrar por estado', enum: UserStatus })
|
||||
@IsOptional()
|
||||
@IsEnum(UserStatus)
|
||||
status?: UserStatus;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Ordenar por campo' })
|
||||
@IsOptional()
|
||||
@IsIn(['name', 'email', 'createdAt'])
|
||||
sortBy?: string = 'createdAt';
|
||||
|
||||
@ApiPropertyOptional({ description: 'Dirección de orden', enum: ['asc', 'desc'] })
|
||||
@IsOptional()
|
||||
@IsIn(['asc', 'desc'])
|
||||
sortOrder?: 'asc' | 'desc' = 'desc';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. VALIDACIÓN EN SERVICE (Lógica de Negocio)
|
||||
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
async create(dto: CreateUserDto): Promise<UserEntity> {
|
||||
// Validación de negocio (después de DTO)
|
||||
|
||||
// 1. Verificar unicidad
|
||||
const existing = await this.repository.findOne({
|
||||
where: { email: dto.email },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('El email ya está registrado');
|
||||
}
|
||||
|
||||
// 2. Validar reglas de negocio
|
||||
if (await this.isEmailDomainBlocked(dto.email)) {
|
||||
throw new BadRequestException('Dominio de email no permitido');
|
||||
}
|
||||
|
||||
// 3. Validar límites
|
||||
const userCount = await this.repository.count({
|
||||
where: { tenantId: dto.tenantId },
|
||||
});
|
||||
if (userCount >= this.MAX_USERS_PER_TENANT) {
|
||||
throw new ForbiddenException('Límite de usuarios alcanzado');
|
||||
}
|
||||
|
||||
// Proceder con creación
|
||||
const user = this.repository.create(dto);
|
||||
return this.repository.save(user);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. VALIDACIÓN EN FRONTEND (React)
|
||||
|
||||
### Con React Hook Form + Zod
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
// Schema de validación (espejo del DTO backend)
|
||||
const createUserSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, 'El email es requerido')
|
||||
.email('Email inválido')
|
||||
.max(255),
|
||||
password: z
|
||||
.string()
|
||||
.min(8, 'Mínimo 8 caracteres')
|
||||
.regex(
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
|
||||
'Debe contener mayúscula, minúscula, número y carácter especial'
|
||||
),
|
||||
name: z
|
||||
.string()
|
||||
.min(2, 'Mínimo 2 caracteres')
|
||||
.max(100, 'Máximo 100 caracteres'),
|
||||
phone: z
|
||||
.string()
|
||||
.regex(/^\+?[1-9]\d{1,14}$/, 'Teléfono inválido')
|
||||
.optional(),
|
||||
});
|
||||
|
||||
type CreateUserForm = z.infer<typeof createUserSchema>;
|
||||
|
||||
// Uso en componente
|
||||
const UserForm = () => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<CreateUserForm>({
|
||||
resolver: zodResolver(createUserSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: CreateUserForm) => {
|
||||
// data ya está validado
|
||||
await userService.create(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input {...register('email')} />
|
||||
{errors.email && <span>{errors.email.message}</span>}
|
||||
{/* ... */}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. MENSAJES DE ERROR ESTÁNDAR
|
||||
|
||||
```typescript
|
||||
// Mensajes consistentes en español
|
||||
const VALIDATION_MESSAGES = {
|
||||
required: (field: string) => `${field} es requerido`,
|
||||
minLength: (field: string, min: number) => `${field} debe tener al menos ${min} caracteres`,
|
||||
maxLength: (field: string, max: number) => `${field} no puede exceder ${max} caracteres`,
|
||||
email: 'El correo electrónico no es válido',
|
||||
uuid: 'El ID no es válido',
|
||||
enum: (values: string[]) => `El valor debe ser uno de: ${values.join(', ')}`,
|
||||
min: (field: string, min: number) => `${field} debe ser mayor o igual a ${min}`,
|
||||
max: (field: string, max: number) => `${field} debe ser menor o igual a ${max}`,
|
||||
pattern: (field: string) => `${field} tiene un formato inválido`,
|
||||
unique: (field: string) => `${field} ya existe`,
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CHECKLIST DE VALIDACIÓN
|
||||
|
||||
```
|
||||
DTO:
|
||||
[ ] Cada campo tiene @ApiProperty o @ApiPropertyOptional
|
||||
[ ] Campos requeridos tienen @IsNotEmpty
|
||||
[ ] Campos opcionales tienen @IsOptional
|
||||
[ ] Tipos validados (@IsString, @IsNumber, etc.)
|
||||
[ ] Longitudes validadas (@MinLength, @MaxLength)
|
||||
[ ] Formatos validados (@IsEmail, @IsUUID, etc.)
|
||||
[ ] Transformaciones aplicadas (@Transform)
|
||||
[ ] Mensajes de error en español
|
||||
|
||||
Service:
|
||||
[ ] Validación de unicidad
|
||||
[ ] Validación de existencia (referencias)
|
||||
[ ] Validación de reglas de negocio
|
||||
[ ] Validación de permisos
|
||||
|
||||
Frontend:
|
||||
[ ] Schema Zod espeja DTO backend
|
||||
[ ] Mensajes de error visibles
|
||||
[ ] Validación en submit
|
||||
[ ] Validación en blur (opcional)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Patrón de Validación
|
||||
413
core/orchestration/procesos/ORDEN-IMPLEMENTACION.md
Normal file
413
core/orchestration/procesos/ORDEN-IMPLEMENTACION.md
Normal file
@ -0,0 +1,413 @@
|
||||
# ORDEN DE IMPLEMENTACIÓN
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Prioridad:** OBLIGATORIA - DDL-First
|
||||
**Sistema:** SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## PROPÓSITO
|
||||
|
||||
Definir el orden correcto de implementación cuando una tarea toca múltiples capas.
|
||||
|
||||
---
|
||||
|
||||
## PRINCIPIO FUNDAMENTAL
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════════════╗
|
||||
║ DDL-FIRST: La Base de Datos es la Fuente de Verdad ║
|
||||
║ ║
|
||||
║ ORDEN OBLIGATORIO: ║
|
||||
║ ║
|
||||
║ 1. DATABASE → Tablas existen ║
|
||||
║ 2. BACKEND → Entity refleja DDL ║
|
||||
║ 3. FRONTEND → Types reflejan DTOs ║
|
||||
║ ║
|
||||
║ ⚠️ NUNCA invertir este orden ║
|
||||
╚══════════════════════════════════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. FLUJO COMPLETO DE IMPLEMENTACIÓN
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ FASE 1: BASE DE DATOS │
|
||||
│ (Database-Agent) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ [ ] 1.1 Crear/modificar archivo DDL │
|
||||
│ [ ] 1.2 Definir columnas con tipos correctos │
|
||||
│ [ ] 1.3 Definir PK, FK, constraints │
|
||||
│ [ ] 1.4 Crear índices necesarios │
|
||||
│ [ ] 1.5 Ejecutar carga limpia (recreate-database.sh) │
|
||||
│ [ ] 1.6 Verificar con \dt, \d {tabla} │
|
||||
│ [ ] 1.7 Actualizar DATABASE_INVENTORY.yml │
|
||||
│ [ ] 1.8 Registrar en TRAZA-TAREAS-DATABASE.md │
|
||||
│ │
|
||||
│ GATE: Carga limpia exitosa antes de continuar │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ FASE 2: BACKEND │
|
||||
│ (Backend-Agent) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ [ ] 2.1 Crear Entity alineada con DDL (ver MAPEO-TIPOS.md) │
|
||||
│ [ ] 2.2 Crear CreateDto con validaciones │
|
||||
│ [ ] 2.3 Crear UpdateDto (extends PartialType) │
|
||||
│ [ ] 2.4 Crear ResponseDto │
|
||||
│ [ ] 2.5 Crear Service con lógica de negocio │
|
||||
│ [ ] 2.6 Crear Controller con Swagger decorators │
|
||||
│ [ ] 2.7 Registrar en Module │
|
||||
│ [ ] 2.8 Ejecutar: npm run build │
|
||||
│ [ ] 2.9 Ejecutar: npm run lint │
|
||||
│ [ ] 2.10 Ejecutar: npm run test (si hay tests) │
|
||||
│ [ ] 2.11 Actualizar BACKEND_INVENTORY.yml │
|
||||
│ [ ] 2.12 Registrar en TRAZA-TAREAS-BACKEND.md │
|
||||
│ │
|
||||
│ GATE: Build y lint pasan antes de continuar │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ FASE 3: FRONTEND │
|
||||
│ (Frontend-Agent) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ [ ] 3.1 Crear types/interfaces (alineados con ResponseDto) │
|
||||
│ [ ] 3.2 Crear Zod schema (alineado con CreateDto) │
|
||||
│ [ ] 3.3 Crear API service │
|
||||
│ [ ] 3.4 Crear custom hook (useQuery/useMutation) │
|
||||
│ [ ] 3.5 Crear componentes necesarios │
|
||||
│ [ ] 3.6 Crear página/formulario │
|
||||
│ [ ] 3.7 Ejecutar: npm run build │
|
||||
│ [ ] 3.8 Ejecutar: npm run lint │
|
||||
│ [ ] 3.9 Ejecutar: npm run typecheck │
|
||||
│ [ ] 3.10 Actualizar FRONTEND_INVENTORY.yml │
|
||||
│ [ ] 3.11 Registrar en TRAZA-TAREAS-FRONTEND.md │
|
||||
│ │
|
||||
│ GATE: Build, lint y typecheck pasan │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ FASE 4: INTEGRACIÓN │
|
||||
│ (Orquestador) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ [ ] 4.1 Verificar flujo completo funciona │
|
||||
│ [ ] 4.2 Verificar Swagger documenta correctamente │
|
||||
│ [ ] 4.3 Tests e2e (si existen) │
|
||||
│ [ ] 4.4 Actualizar MASTER_INVENTORY.yml │
|
||||
│ [ ] 4.5 Actualizar PROXIMA-ACCION.md │
|
||||
│ [ ] 4.6 Propagar a niveles superiores (SIMCO-PROPAGACION.md) │
|
||||
│ │
|
||||
│ GATE: Todo funciona end-to-end │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. POR QUÉ DDL-FIRST
|
||||
|
||||
### Problema: Backend-First
|
||||
|
||||
```typescript
|
||||
// Alguien crea Entity primero sin DDL...
|
||||
@Entity()
|
||||
export class ProductEntity {
|
||||
@Column()
|
||||
price: number; // ← Asume que existe en BD
|
||||
}
|
||||
|
||||
// Resultado:
|
||||
// - TypeORM syncronize: Crea tabla con tipos incorrectos
|
||||
// - Sin syncronize: Error en runtime "column not found"
|
||||
// - Nadie sabe qué tipo debería ser price (DECIMAL? NUMERIC? INTEGER?)
|
||||
```
|
||||
|
||||
### Solución: DDL-First
|
||||
|
||||
```sql
|
||||
-- 1. DDL define la verdad
|
||||
CREATE TABLE products (
|
||||
price DECIMAL(10,2) NOT NULL -- Explícito: 10 dígitos, 2 decimales
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// 2. Entity REFLEJA la verdad
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
price: string; // String para precisión decimal
|
||||
|
||||
// 3. DTO documenta para Swagger
|
||||
@ApiProperty({ example: 99.99 })
|
||||
@IsNumber({ maxDecimalPlaces: 2 })
|
||||
price: number;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. DEPENDENCIAS ENTRE CAPAS
|
||||
|
||||
### Diagrama de Dependencias
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ DATABASE │
|
||||
│ (DDL) │
|
||||
└──────┬───────┘
|
||||
│
|
||||
│ Entity refleja DDL
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ BACKEND │
|
||||
│ (Entity,DTO) │
|
||||
└──────┬───────┘
|
||||
│
|
||||
│ Types reflejan DTOs
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ FRONTEND │
|
||||
│(Types,Forms) │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
### Reglas de Dependencia
|
||||
|
||||
| Si cambias... | Debes actualizar... | En ese orden |
|
||||
|---------------|---------------------|--------------|
|
||||
| DDL columna | Entity → DTO → Types | DDL → BE → FE |
|
||||
| Entity campo | DTO → Types | BE → FE |
|
||||
| DTO campo | Types | BE → FE |
|
||||
| Types | - | FE solo |
|
||||
|
||||
---
|
||||
|
||||
## 4. ANTI-PATRONES DE ORDEN
|
||||
|
||||
### Anti-Patrón 1: Frontend First
|
||||
|
||||
```
|
||||
❌ INCORRECTO:
|
||||
1. Frontend-Agent crea formulario
|
||||
2. Backend-Agent crea endpoint
|
||||
3. Database-Agent... ¿qué campos necesita?
|
||||
|
||||
RESULTADO:
|
||||
- Frontend asume campos que no existen
|
||||
- Backend inventa estructura
|
||||
- Database no sabe qué crear
|
||||
- Retrabajos múltiples
|
||||
```
|
||||
|
||||
### Anti-Patrón 2: Parallel sin Coordinación
|
||||
|
||||
```
|
||||
❌ INCORRECTO:
|
||||
1. Database-Agent crea tabla (en paralelo)
|
||||
2. Backend-Agent crea entity (en paralelo)
|
||||
3. Frontend-Agent crea types (en paralelo)
|
||||
|
||||
RESULTADO:
|
||||
- Cada uno asume diferente
|
||||
- Tipos no coinciden
|
||||
- Errores en integración
|
||||
```
|
||||
|
||||
### Anti-Patrón 3: Saltar Validación
|
||||
|
||||
```
|
||||
❌ INCORRECTO:
|
||||
1. Database-Agent crea tabla (OK)
|
||||
2. Backend-Agent crea entity (sin build)
|
||||
3. Frontend-Agent crea types (sin typecheck)
|
||||
|
||||
RESULTADO:
|
||||
- Errores no detectados hasta runtime
|
||||
- Bugs en producción
|
||||
- Debug difícil
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. TEMPLATES POR CASO
|
||||
|
||||
### Caso A: Feature Nueva Completa
|
||||
|
||||
```
|
||||
TAREA: Crear sistema de comentarios
|
||||
|
||||
FASE 1: DATABASE (Database-Agent)
|
||||
═══════════════════════════════════════════════════════════════
|
||||
Crear: schemas/core/tables/05-comments.sql
|
||||
|
||||
CREATE TABLE core.comments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
content TEXT NOT NULL,
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id),
|
||||
post_id UUID NOT NULL REFERENCES core.posts(id),
|
||||
parent_id UUID REFERENCES core.comments(id),
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_comments_post ON core.comments(post_id);
|
||||
CREATE INDEX idx_comments_user ON core.comments(user_id);
|
||||
|
||||
Validar: ./recreate-database.sh && psql -c "\d core.comments"
|
||||
|
||||
FASE 2: BACKEND (Backend-Agent)
|
||||
═══════════════════════════════════════════════════════════════
|
||||
Crear:
|
||||
- modules/comment/entities/comment.entity.ts
|
||||
- modules/comment/dto/create-comment.dto.ts
|
||||
- modules/comment/dto/update-comment.dto.ts
|
||||
- modules/comment/dto/comment-response.dto.ts
|
||||
- modules/comment/services/comment.service.ts
|
||||
- modules/comment/controllers/comment.controller.ts
|
||||
- modules/comment/comment.module.ts
|
||||
|
||||
Validar: npm run build && npm run lint
|
||||
|
||||
FASE 3: FRONTEND (Frontend-Agent)
|
||||
═══════════════════════════════════════════════════════════════
|
||||
Crear:
|
||||
- shared/types/comment.types.ts
|
||||
- shared/services/comment.service.ts
|
||||
- apps/web/hooks/useComments.ts
|
||||
- apps/web/components/comments/CommentCard.tsx
|
||||
- apps/web/components/comments/CommentForm.tsx
|
||||
- apps/web/components/comments/CommentList.tsx
|
||||
|
||||
Validar: npm run build && npm run lint && npm run typecheck
|
||||
|
||||
FASE 4: INTEGRACIÓN
|
||||
═══════════════════════════════════════════════════════════════
|
||||
- Test flujo completo
|
||||
- Verificar Swagger
|
||||
- Actualizar inventarios
|
||||
```
|
||||
|
||||
### Caso B: Agregar Campo a Feature Existente
|
||||
|
||||
```
|
||||
TAREA: Agregar campo "rating" a comments
|
||||
|
||||
FASE 1: DATABASE
|
||||
═══════════════════════════════════════════════════════════════
|
||||
ALTER TABLE core.comments ADD COLUMN rating INTEGER CHECK (rating BETWEEN 1 AND 5);
|
||||
|
||||
Validar: Carga limpia
|
||||
|
||||
FASE 2: BACKEND
|
||||
═══════════════════════════════════════════════════════════════
|
||||
- Agregar @Column en CommentEntity
|
||||
- Agregar campo en CreateCommentDto con @IsInt @Min(1) @Max(5)
|
||||
- Agregar en ResponseDto
|
||||
|
||||
Validar: npm run build
|
||||
|
||||
FASE 3: FRONTEND
|
||||
═══════════════════════════════════════════════════════════════
|
||||
- Agregar rating en Comment interface
|
||||
- Agregar input en CommentForm
|
||||
- Mostrar en CommentCard
|
||||
|
||||
Validar: npm run build && npm run typecheck
|
||||
```
|
||||
|
||||
### Caso C: Cambio Solo en Backend (Nueva Lógica)
|
||||
|
||||
```
|
||||
TAREA: Agregar validación de spam en comentarios
|
||||
|
||||
FASE 1: DATABASE
|
||||
═══════════════════════════════════════════════════════════════
|
||||
- SIN CAMBIOS (lógica no afecta schema)
|
||||
|
||||
FASE 2: BACKEND
|
||||
═══════════════════════════════════════════════════════════════
|
||||
- Agregar SpamService
|
||||
- Modificar CommentService.create() para validar
|
||||
|
||||
Validar: npm run build && npm run test
|
||||
|
||||
FASE 3: FRONTEND
|
||||
═══════════════════════════════════════════════════════════════
|
||||
- SIN CAMBIOS (endpoint sigue igual)
|
||||
- Posible: Mostrar error si spam detectado
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. CHECKLIST RÁPIDO
|
||||
|
||||
```
|
||||
Antes de empezar implementación:
|
||||
[ ] ¿La tabla existe en DDL? Si no → DATABASE primero
|
||||
[ ] ¿Entity refleja DDL actual? Si no → Actualizar Entity
|
||||
[ ] ¿DTOs coinciden con Entity? Si no → Actualizar DTOs
|
||||
[ ] ¿Types coinciden con DTOs? Si no → Actualizar Types
|
||||
|
||||
Durante implementación:
|
||||
[ ] No crear Entity sin DDL
|
||||
[ ] No crear Types sin DTOs
|
||||
[ ] No modificar DDL sin actualizar Entity
|
||||
[ ] No modificar DTO sin actualizar Types
|
||||
|
||||
Después de cada capa:
|
||||
[ ] Build pasa
|
||||
[ ] Lint pasa
|
||||
[ ] Tests pasan (si existen)
|
||||
[ ] Inventario actualizado
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. COMANDOS DE VALIDACIÓN
|
||||
|
||||
```bash
|
||||
# FASE 1: Validar Database
|
||||
cd {DB_SCRIPTS_PATH}
|
||||
./recreate-database.sh
|
||||
psql -d {DB_NAME} -c "\dt {schema}.*"
|
||||
psql -d {DB_NAME} -c "\d {schema}.{tabla}"
|
||||
|
||||
# FASE 2: Validar Backend
|
||||
cd {BACKEND_ROOT}
|
||||
npm run build
|
||||
npm run lint
|
||||
npm run test
|
||||
|
||||
# FASE 3: Validar Frontend
|
||||
cd {FRONTEND_ROOT}
|
||||
npm run build
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
|
||||
# FASE 4: Validar Integración
|
||||
curl http://localhost:3000/api/{endpoint}
|
||||
# Verificar respuesta correcta
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. MATRIZ DE DELEGACIÓN
|
||||
|
||||
| Tarea | Database-Agent | Backend-Agent | Frontend-Agent | Orquestador |
|
||||
|-------|----------------|---------------|----------------|-------------|
|
||||
| Crear tabla | ✅ | ❌ | ❌ | Coordina |
|
||||
| Modificar DDL | ✅ | ❌ | ❌ | Coordina |
|
||||
| Crear Entity | ❌ | ✅ | ❌ | Verifica |
|
||||
| Crear DTO | ❌ | ✅ | ❌ | Verifica |
|
||||
| Crear Service | ❌ | ✅ | ❌ | Verifica |
|
||||
| Crear Controller | ❌ | ✅ | ❌ | Verifica |
|
||||
| Crear Types | ❌ | ❌ | ✅ | Verifica |
|
||||
| Crear Component | ❌ | ❌ | ✅ | Verifica |
|
||||
| Integración | ❌ | ❌ | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
**Versión:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guía de Proceso
|
||||
@ -79,6 +79,38 @@ global:
|
||||
"@CHK_API": "core/orchestration/checklists/CHECKLIST-CODE-REVIEW-API.md"
|
||||
"@CHK_REFACTOR": "core/orchestration/checklists/CHECKLIST-REFACTORIZACION.md"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# PATRONES DE CÓDIGO (CONSULTAR ANTES DE IMPLEMENTAR)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
"@PATRONES": "core/orchestration/patrones/"
|
||||
"@MAPEO_TIPOS": "core/orchestration/patrones/MAPEO-TIPOS-DDL-TYPESCRIPT.md"
|
||||
"@PATRON_VALIDACION": "core/orchestration/patrones/PATRON-VALIDACION.md"
|
||||
"@PATRON_EXCEPTIONS": "core/orchestration/patrones/PATRON-EXCEPTION-HANDLING.md"
|
||||
"@PATRON_TESTING": "core/orchestration/patrones/PATRON-TESTING.md"
|
||||
"@PATRON_LOGGING": "core/orchestration/patrones/PATRON-LOGGING.md"
|
||||
"@PATRON_CONFIG": "core/orchestration/patrones/PATRON-CONFIGURACION.md"
|
||||
"@PATRON_SEGURIDAD": "core/orchestration/patrones/PATRON-SEGURIDAD.md"
|
||||
"@PATRON_PERFORMANCE": "core/orchestration/patrones/PATRON-PERFORMANCE.md"
|
||||
"@PATRON_TRANSACCIONES": "core/orchestration/patrones/PATRON-TRANSACCIONES.md"
|
||||
"@ANTIPATRONES": "core/orchestration/patrones/ANTIPATRONES.md"
|
||||
"@NOMENCLATURA": "core/orchestration/patrones/NOMENCLATURA-UNIFICADA.md"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# IMPACTOS DE CAMBIOS (CONSULTAR ANTES DE MODIFICAR)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
"@IMPACTOS": "core/orchestration/impactos/"
|
||||
"@IMPACTO_DDL": "core/orchestration/impactos/IMPACTO-CAMBIOS-DDL.md"
|
||||
"@IMPACTO_BACKEND": "core/orchestration/impactos/IMPACTO-CAMBIOS-BACKEND.md"
|
||||
"@IMPACTO_ENTITY": "core/orchestration/impactos/IMPACTO-CAMBIOS-ENTITY.md"
|
||||
"@IMPACTO_API": "core/orchestration/impactos/IMPACTO-CAMBIOS-API.md"
|
||||
"@MATRIZ_DEPENDENCIAS": "core/orchestration/impactos/MATRIZ-DEPENDENCIAS.md"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# PROCESOS DE TRABAJO
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
"@PROCESOS": "core/orchestration/procesos/"
|
||||
"@ORDEN_IMPLEMENTACION": "core/orchestration/procesos/ORDEN-IMPLEMENTACION.md"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────────
|
||||
# ALIAS DE OPERACIONES (atajos directos a directivas SIMCO)
|
||||
# ─────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
434
orchestration/ANALISIS-ALINEACION-WORKSPACE-2025-12-08.md
Normal file
434
orchestration/ANALISIS-ALINEACION-WORKSPACE-2025-12-08.md
Normal file
@ -0,0 +1,434 @@
|
||||
# Análisis de Alineación del Workspace
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión Sistema:** NEXUS v3.2 + SIMCO + CAPVED
|
||||
|
||||
---
|
||||
|
||||
## RESUMEN EJECUTIVO
|
||||
|
||||
Este documento presenta el análisis exhaustivo de alineación entre el sistema de orquestación central (`core/orchestration/`) y su implementación en todos los proyectos del workspace.
|
||||
|
||||
### Hallazgos Clave
|
||||
|
||||
| Área | Estado | Acción Requerida |
|
||||
|------|--------|------------------|
|
||||
| Sistema SIMCO/CAPVED | Completo (20+ directivas) | Base sólida |
|
||||
| ERP-Core | 100% docs, 0% código | Implementar |
|
||||
| Verticales | Parcialmente alineadas | Propagar estándares |
|
||||
| SaaS Layer | 0% (vacío) | Crear desde cero |
|
||||
| Proyectos Standalone | Variable | Homologar |
|
||||
|
||||
---
|
||||
|
||||
## 1. SISTEMA DE ORQUESTACIÓN (CORE)
|
||||
|
||||
### 1.1 Componentes Implementados
|
||||
|
||||
| Componente | Ubicación | Archivos | Estado |
|
||||
|------------|-----------|----------|--------|
|
||||
| Directivas SIMCO | `core/orchestration/directivas/simco/` | 17 archivos | Completo |
|
||||
| Principios | `core/orchestration/directivas/principios/` | 5 archivos | Completo |
|
||||
| Perfiles Agentes | `core/orchestration/agents/perfiles/` | 12 archivos | Completo |
|
||||
| Templates | `core/orchestration/templates/` | 14 archivos | Completo |
|
||||
| Checklists | `core/orchestration/checklists/` | 3 archivos | Completo |
|
||||
| Catálogo | `core/catalog/` | 8 funcionalidades | Documentado |
|
||||
|
||||
### 1.2 Directivas SIMCO Disponibles
|
||||
|
||||
```
|
||||
SIMCO-TAREA.md # Ciclo CAPVED (obligatorio)
|
||||
SIMCO-CREAR.md # Crear archivos nuevos
|
||||
SIMCO-MODIFICAR.md # Modificar existentes
|
||||
SIMCO-VALIDAR.md # Validación obligatoria
|
||||
SIMCO-DOCUMENTAR.md # Documentación
|
||||
SIMCO-DELEGACION.md # Delegar a subagentes
|
||||
SIMCO-BUSCAR.md # Exploración
|
||||
SIMCO-REUTILIZAR.md # Uso del catálogo
|
||||
SIMCO-DDL.md # Base de datos
|
||||
SIMCO-BACKEND.md # NestJS/TypeORM
|
||||
SIMCO-FRONTEND.md # React/TypeScript
|
||||
SIMCO-NIVELES.md # Jerarquía de proyectos
|
||||
SIMCO-PROPAGACION.md # Propagación de cambios
|
||||
SIMCO-ML.md # Machine Learning
|
||||
SIMCO-MOBILE.md # Aplicaciones móviles
|
||||
SIMCO-ALINEACION.md # Alineación entre capas
|
||||
SIMCO-DECISION-MATRIZ.md # Matriz de decisiones
|
||||
```
|
||||
|
||||
### 1.3 Principios Fundamentales (5)
|
||||
|
||||
1. **PRINCIPIO-CAPVED** - Ciclo obligatorio: Contexto→Análisis→Planeación→Validación→Ejecución→Documentación
|
||||
2. **PRINCIPIO-DOC-PRIMERO** - Documentar antes de implementar
|
||||
3. **PRINCIPIO-ANTI-DUPLICACION** - Verificar catálogo/inventarios antes de crear
|
||||
4. **PRINCIPIO-VALIDACION-OBLIGATORIA** - Build+Lint+Tests deben pasar
|
||||
5. **PRINCIPIO-ECONOMIA-TOKENS** - Desglosar tareas grandes
|
||||
|
||||
### 1.4 Catálogo de Funcionalidades Reutilizables
|
||||
|
||||
| Funcionalidad | Ubicación | Estado |
|
||||
|---------------|-----------|--------|
|
||||
| auth | `core/catalog/auth/` | Documentado |
|
||||
| session-management | `core/catalog/session-management/` | Documentado |
|
||||
| rate-limiting | `core/catalog/rate-limiting/` | Documentado |
|
||||
| notifications | `core/catalog/notifications/` | Documentado |
|
||||
| multi-tenancy | `core/catalog/multi-tenancy/` | Documentado |
|
||||
| feature-flags | `core/catalog/feature-flags/` | Documentado |
|
||||
| websocket | `core/catalog/websocket/` | Documentado |
|
||||
| payments | `core/catalog/payments/` | Documentado |
|
||||
|
||||
---
|
||||
|
||||
## 2. ANÁLISIS POR PROYECTO
|
||||
|
||||
### 2.1 ERP-SUITE
|
||||
|
||||
#### Estado General
|
||||
| Componente | Documentación | Código | BD | Orquestación |
|
||||
|------------|---------------|--------|-----|--------------|
|
||||
| ERP-Core | 100% (827 MD) | 0% | 0% | 100% |
|
||||
| Construcción | 100% (449 MD) | 25% | 0% | 100% |
|
||||
| Mecánicas Diesel | 95% (75 MD) | 0% | 0% | 100% |
|
||||
| Vidrio Templado | 0% | 0% | 0% | 50% |
|
||||
| Retail | 0% | 0% | 0% | 50% |
|
||||
| Clínicas | 0% | 0% | 0% | 50% |
|
||||
| SaaS Layer | 10% | 0% | 0% | 0% |
|
||||
|
||||
#### Gaps Identificados en ERP-Suite
|
||||
|
||||
| ID | Gap | Severidad | Ubicación |
|
||||
|----|-----|-----------|-----------|
|
||||
| GAP-ERP-001 | SaaS layer vacío | CRÍTICO | `apps/saas/` |
|
||||
| GAP-ERP-002 | shared-libs vacío | ALTO | `apps/shared-libs/` |
|
||||
| GAP-ERP-003 | Verticales sin HERENCIA-ERP-CORE.md | MEDIO | 3 de 5 verticales |
|
||||
| GAP-ERP-004 | Inventarios desactualizados | MEDIO | Varios |
|
||||
| GAP-ERP-005 | No existe POS básico minimalista | CRÍTICO | No existe |
|
||||
|
||||
### 2.2 Otros Proyectos
|
||||
|
||||
| Proyecto | Estado | Docs | Código | Alineación |
|
||||
|----------|--------|------|--------|------------|
|
||||
| Gamilit | Producción | 470 MD | Completo | Alta |
|
||||
| Trading-Platform | Desarrollo | 259 MD | 70% | Alta |
|
||||
| Betting-Analytics | Planificación | 1 MD | 0% | Parcial |
|
||||
| Inmobiliaria-Analytics | Planificación | 1 MD | 0% | Parcial |
|
||||
|
||||
---
|
||||
|
||||
## 3. GAPS DE ALINEACIÓN CRÍTICOS
|
||||
|
||||
### 3.1 Gaps Estructurales
|
||||
|
||||
| # | Descripción | Proyectos Afectados | Impacto |
|
||||
|---|-------------|---------------------|---------|
|
||||
| 1 | Falta estructura SaaS multi-tier | erp-suite | Bloqueante para comercialización |
|
||||
| 2 | Falta POS minimalista (100 MXN) | erp-suite | Mercado desatendido |
|
||||
| 3 | Inventarios no propagados | Todos | Trazabilidad incompleta |
|
||||
| 4 | HERENCIA-DIRECTIVAS inconsistente | Verticales | Duplicación de esfuerzo |
|
||||
|
||||
### 3.2 Gaps de Documentación
|
||||
|
||||
| # | Descripción | Ubicación |
|
||||
|---|-------------|-----------|
|
||||
| 1 | ERP-Core sin README actualizado con arquitectura SaaS | `erp-suite/apps/erp-core/` |
|
||||
| 2 | Verticales sin docs de herencia | 3 verticales |
|
||||
| 3 | Catálogo sin código de referencia | `core/catalog/*/` |
|
||||
| 4 | betting/inmobiliaria sin documentación | 2 proyectos |
|
||||
|
||||
### 3.3 Gaps de Implementación
|
||||
|
||||
| # | Descripción | Prioridad |
|
||||
|---|-------------|-----------|
|
||||
| 1 | ERP-Core 0% código con 100% docs | P0 |
|
||||
| 2 | SaaS Layer inexistente | P0 |
|
||||
| 3 | POS Básico no existe | P0 |
|
||||
| 4 | Mecánicas Diesel 0% código | P1 |
|
||||
|
||||
---
|
||||
|
||||
## 4. PROPUESTA: ARQUITECTURA SAAS MULTI-TIER
|
||||
|
||||
### 4.1 Estructura Propuesta
|
||||
|
||||
```
|
||||
erp-suite/
|
||||
├── apps/
|
||||
│ ├── erp-core/ # Base compartida (EXISTENTE)
|
||||
│ │ ├── backend/ # API Core
|
||||
│ │ ├── frontend/ # UI Core (componentes base)
|
||||
│ │ └── database/ # Schemas compartidos
|
||||
│ │
|
||||
│ ├── saas/ # Capa SaaS (CREAR)
|
||||
│ │ ├── billing/ # Facturación y suscripciones
|
||||
│ │ ├── portal/ # Portal de clientes
|
||||
│ │ ├── admin/ # Admin multi-tenant
|
||||
│ │ └── onboarding/ # Autoregistro
|
||||
│ │
|
||||
│ ├── products/ # Productos SaaS (CREAR)
|
||||
│ │ │
|
||||
│ │ ├── erp-basico/ # ERP SaaS Mediano (~300-500 MXN/mes)
|
||||
│ │ │ ├── backend/ # Hereda erp-core + módulos seleccionados
|
||||
│ │ │ ├── frontend/ # UI simplificada
|
||||
│ │ │ └── config/ # Feature flags para módulos
|
||||
│ │ │
|
||||
│ │ └── pos-micro/ # POS Ultra Básico (~100 MXN/mes)
|
||||
│ │ ├── backend/ # Mínimo: ventas, inventario básico, reportes
|
||||
│ │ ├── frontend/ # UI ultra simple (móvil-first)
|
||||
│ │ ├── pwa/ # Progressive Web App
|
||||
│ │ └── whatsapp/ # Integración WhatsApp Business
|
||||
│ │
|
||||
│ └── verticales/ # Extensiones por industria (EXISTENTE)
|
||||
│ ├── construccion/
|
||||
│ ├── mecanicas-diesel/
|
||||
│ ├── vidrio-templado/
|
||||
│ ├── retail/
|
||||
│ └── clinicas/
|
||||
```
|
||||
|
||||
### 4.2 Producto: ERP SaaS Mediano
|
||||
|
||||
**Target:** PyMEs que necesitan ERP integral pero económico
|
||||
**Precio:** ~300-500 MXN/mes
|
||||
**Características:**
|
||||
|
||||
| Módulo | Incluido | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| Auth | Obligatorio | Login, roles básicos |
|
||||
| Usuarios | Obligatorio | Gestión de usuarios |
|
||||
| Multi-tenant | Obligatorio | Aislamiento por empresa |
|
||||
| Inventario | Incluido | Control de stock básico |
|
||||
| Ventas | Incluido | Cotizaciones, pedidos, facturas |
|
||||
| Compras | Incluido | Órdenes de compra básicas |
|
||||
| Clientes/Proveedores | Incluido | CRM básico |
|
||||
| Reportes | Incluido | Reportes esenciales |
|
||||
| Contabilidad | Opcional | +100 MXN/mes |
|
||||
| RRHH | Opcional | +100 MXN/mes |
|
||||
| WhatsApp Bot | Opcional | Por consumo de tokens |
|
||||
|
||||
### 4.3 Producto: POS Micro (Ultra Básico)
|
||||
|
||||
**Target:** Mercado informal mexicano
|
||||
- Puestos de calle
|
||||
- Tiendas de abarrotes/misceláneas
|
||||
- Puestos de comida
|
||||
- Pequeños locales
|
||||
|
||||
**Precio:** ~100 MXN/mes
|
||||
**Modelo:** SaaS + consumo de IA
|
||||
|
||||
**Características MÍNIMAS:**
|
||||
|
||||
| Característica | Descripción |
|
||||
|----------------|-------------|
|
||||
| Punto de Venta | Vender productos, calcular cambio |
|
||||
| Inventario Básico | Agregar productos, ver stock |
|
||||
| Catálogo Simple | Lista de productos con precio |
|
||||
| Corte de Caja | Resumen diario de ventas |
|
||||
| Reportes Básicos | Ventas del día/semana/mes |
|
||||
| WhatsApp Bot | Consultas de precio, stock, ventas |
|
||||
| PWA Offline | Funciona sin internet (sincroniza después) |
|
||||
|
||||
**Arquitectura Minimalista:**
|
||||
|
||||
```
|
||||
pos-micro/
|
||||
├── backend/
|
||||
│ ├── src/
|
||||
│ │ ├── modules/
|
||||
│ │ │ ├── auth/ # Login simple (email/WhatsApp)
|
||||
│ │ │ ├── products/ # CRUD productos
|
||||
│ │ │ ├── sales/ # Registrar ventas
|
||||
│ │ │ ├── inventory/ # Stock básico
|
||||
│ │ │ └── reports/ # Reportes simples
|
||||
│ │ └── shared/
|
||||
│ │ ├── tenant/ # Multi-tenant básico
|
||||
│ │ └── whatsapp/ # Integración WA Business
|
||||
├── frontend/
|
||||
│ ├── pwa/ # Progressive Web App
|
||||
│ │ ├── pages/
|
||||
│ │ │ ├── pos/ # Pantalla de venta
|
||||
│ │ │ ├── products/ # Gestión productos
|
||||
│ │ │ ├── reports/ # Ver reportes
|
||||
│ │ │ └── settings/ # Configuración
|
||||
│ │ └── offline/ # Service Worker
|
||||
└── database/
|
||||
└── schemas/
|
||||
└── pos_micro/ # ~10 tablas máximo
|
||||
```
|
||||
|
||||
**Base de Datos Minimalista (~10 tablas):**
|
||||
|
||||
```sql
|
||||
-- Schema: pos_micro
|
||||
CREATE TABLE tenants (id, name, whatsapp_number, plan, created_at);
|
||||
CREATE TABLE users (id, tenant_id, email, password_hash, role);
|
||||
CREATE TABLE products (id, tenant_id, name, price, stock, barcode);
|
||||
CREATE TABLE sales (id, tenant_id, user_id, total, payment_method, created_at);
|
||||
CREATE TABLE sale_items (id, sale_id, product_id, quantity, price);
|
||||
CREATE TABLE inventory_movements (id, tenant_id, product_id, quantity, type, created_at);
|
||||
CREATE TABLE daily_closures (id, tenant_id, date, total_sales, total_cash);
|
||||
CREATE TABLE whatsapp_sessions (id, tenant_id, phone, token, expires_at);
|
||||
CREATE TABLE ai_usage (id, tenant_id, tokens_used, model, created_at);
|
||||
CREATE TABLE subscriptions (id, tenant_id, plan, amount, status, next_billing);
|
||||
```
|
||||
|
||||
### 4.4 Integración WhatsApp Business
|
||||
|
||||
```
|
||||
Flujo Usuario POS Micro:
|
||||
1. Usuario envía "hola" a número de WhatsApp
|
||||
2. Bot responde: "Hola! Soy tu asistente. Puedo ayudarte con:
|
||||
- Ver ventas del día
|
||||
- Consultar stock de producto
|
||||
- Agregar producto
|
||||
- Ver reporte semanal"
|
||||
3. Usuario: "ventas del día"
|
||||
4. Bot: "Ventas hoy: $2,450 MXN (23 tickets)"
|
||||
5. Usuario: "stock de coca cola"
|
||||
6. Bot: "Coca Cola 600ml: 45 unidades en stock"
|
||||
|
||||
Costo: Tokens consumidos por consulta (~0.01-0.05 USD por consulta)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. PLAN DE IMPLEMENTACIÓN
|
||||
|
||||
### Fase 1: Preparación (Inmediato)
|
||||
|
||||
| Tarea | Descripción | Prioridad |
|
||||
|-------|-------------|-----------|
|
||||
| 1.1 | Actualizar HERENCIA-ERP-CORE.md en todas las verticales | P0 |
|
||||
| 1.2 | Crear estructura `apps/products/` | P0 |
|
||||
| 1.3 | Crear documentación base para pos-micro | P0 |
|
||||
| 1.4 | Crear documentación base para erp-basico | P0 |
|
||||
| 1.5 | Actualizar SaaS layer con billing básico | P0 |
|
||||
|
||||
### Fase 2: POS Micro (Prioridad Alta)
|
||||
|
||||
| Tarea | Descripción | Estimación |
|
||||
|-------|-------------|------------|
|
||||
| 2.1 | Diseñar schema BD (~10 tablas) | 2h |
|
||||
| 2.2 | Implementar backend mínimo | 8h |
|
||||
| 2.3 | Implementar PWA frontend | 8h |
|
||||
| 2.4 | Integrar WhatsApp Business API | 4h |
|
||||
| 2.5 | Implementar offline-first | 4h |
|
||||
| 2.6 | Testing y validación | 4h |
|
||||
|
||||
### Fase 3: ERP Básico SaaS
|
||||
|
||||
| Tarea | Descripción | Estimación |
|
||||
|-------|-------------|------------|
|
||||
| 3.1 | Definir módulos incluidos vs opcionales | 2h |
|
||||
| 3.2 | Configurar feature flags | 4h |
|
||||
| 3.3 | Implementar billing/suscripciones | 8h |
|
||||
| 3.4 | Portal de onboarding | 8h |
|
||||
| 3.5 | Testing multi-tenant | 4h |
|
||||
|
||||
### Fase 4: Propagación
|
||||
|
||||
| Tarea | Descripción |
|
||||
|-------|-------------|
|
||||
| 4.1 | Actualizar inventarios en todos los proyectos |
|
||||
| 4.2 | Verificar HERENCIA-DIRECTIVAS en cada vertical |
|
||||
| 4.3 | Ejecutar CHECKLIST-PROPAGACION en todos los niveles |
|
||||
| 4.4 | Documentar dependencias entre productos |
|
||||
|
||||
---
|
||||
|
||||
## 6. MÉTRICAS DE ALINEACIÓN
|
||||
|
||||
### 6.1 Checklist de Alineación por Proyecto
|
||||
|
||||
```yaml
|
||||
Proyecto Alineado:
|
||||
Estructura:
|
||||
[ ] apps/backend/ existe
|
||||
[ ] apps/frontend/ existe
|
||||
[ ] apps/database/ existe
|
||||
[ ] docs/ con estructura estándar
|
||||
[ ] orchestration/ con estructura NEXUS
|
||||
|
||||
Orchestration:
|
||||
[ ] 00-guidelines/CONTEXTO-PROYECTO.md
|
||||
[ ] PROXIMA-ACCION.md
|
||||
[ ] inventarios/MASTER_INVENTORY.yml
|
||||
[ ] trazas/ con archivos por capa
|
||||
[ ] HERENCIA-DIRECTIVAS.md
|
||||
|
||||
Documentación:
|
||||
[ ] README.md actualizado
|
||||
[ ] Épicas documentadas
|
||||
[ ] Historias de usuario
|
||||
[ ] Especificaciones técnicas
|
||||
[ ] Schemas de BD
|
||||
|
||||
Código:
|
||||
[ ] Sigue estándares de nomenclatura
|
||||
[ ] Entities alineadas con DDL
|
||||
[ ] DTOs con validaciones
|
||||
[ ] Tests implementados
|
||||
[ ] Build + Lint pasan
|
||||
```
|
||||
|
||||
### 6.2 Estado Actual de Alineación
|
||||
|
||||
| Proyecto | Estructura | Orchestration | Docs | Código | TOTAL |
|
||||
|----------|------------|---------------|------|--------|-------|
|
||||
| Gamilit | 100% | 100% | 100% | 90% | **97%** |
|
||||
| Trading | 100% | 90% | 95% | 70% | **89%** |
|
||||
| ERP-Core | 100% | 100% | 100% | 0% | **75%** |
|
||||
| Construcción | 100% | 100% | 100% | 25% | **81%** |
|
||||
| Mecánicas Diesel | 100% | 100% | 95% | 0% | **74%** |
|
||||
| Vidrio Templado | 80% | 50% | 0% | 0% | **33%** |
|
||||
| Retail | 80% | 50% | 0% | 0% | **33%** |
|
||||
| Clínicas | 80% | 50% | 0% | 0% | **33%** |
|
||||
| Betting | 80% | 40% | 5% | 0% | **31%** |
|
||||
| Inmobiliaria | 80% | 40% | 5% | 0% | **31%** |
|
||||
|
||||
---
|
||||
|
||||
## 7. ACCIONES INMEDIATAS
|
||||
|
||||
### 7.1 Crear Productos SaaS
|
||||
|
||||
```bash
|
||||
# Crear estructura de productos
|
||||
mkdir -p projects/erp-suite/apps/products/erp-basico/{backend,frontend,database,docs,orchestration}
|
||||
mkdir -p projects/erp-suite/apps/products/pos-micro/{backend,frontend,pwa,database,docs,orchestration}
|
||||
```
|
||||
|
||||
### 7.2 Propagar HERENCIA-ERP-CORE.md
|
||||
|
||||
Verticales que necesitan el archivo:
|
||||
- [ ] vidrio-templado
|
||||
- [ ] retail
|
||||
- [ ] clinicas
|
||||
|
||||
### 7.3 Actualizar SaaS Layer
|
||||
|
||||
```bash
|
||||
mkdir -p projects/erp-suite/apps/saas/{billing,portal,admin,onboarding}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. CONCLUSIONES
|
||||
|
||||
1. **El sistema de orquestación está completo** - SIMCO, CAPVED, perfiles y catálogo bien definidos
|
||||
|
||||
2. **La documentación es excelente** - ERP-Core tiene 827 MD, gap analysis completo vs Odoo
|
||||
|
||||
3. **Falta implementación de código** - 0% en ERP-Core a pesar de 100% documentación
|
||||
|
||||
4. **Falta capa SaaS** - Crítico para comercialización
|
||||
|
||||
5. **Falta producto POS minimalista** - Oportunidad de mercado desatendida (100 MXN/mes)
|
||||
|
||||
6. **Verticales parcialmente alineadas** - 3 de 5 sin HERENCIA-ERP-CORE.md
|
||||
|
||||
7. **Proyectos standalone bien alineados** - Gamilit y Trading-Platform son ejemplos a seguir
|
||||
|
||||
---
|
||||
|
||||
*Documento generado: 2025-12-08*
|
||||
*Sistema: NEXUS v3.2 + SIMCO + CAPVED*
|
||||
@ -1,8 +1,8 @@
|
||||
# WORKSPACE STATUS
|
||||
|
||||
**Nivel:** 0 - Workspace Root
|
||||
**Actualizado:** 2025-12-08 (Post-Limpieza)
|
||||
**Sistema:** SIMCO v2.2.0 + CAPVED
|
||||
**Actualizado:** 2025-12-08 (Análisis Alineación + Productos SaaS)
|
||||
**Sistema:** SIMCO v3.2 + CAPVED + NEXUS
|
||||
|
||||
---
|
||||
|
||||
@ -10,10 +10,11 @@
|
||||
|
||||
```yaml
|
||||
estado: "OPERATIVO"
|
||||
version_simco: "2.2.0"
|
||||
version_simco: "3.2"
|
||||
ultima_actualizacion: "2025-12-08"
|
||||
proyectos_activos: 5
|
||||
verticales_activos: 5
|
||||
productos_saas: 2 # NUEVO: pos-micro, erp-basico
|
||||
catalogo_funcionalidades: 8
|
||||
```
|
||||
|
||||
@ -23,7 +24,25 @@ catalogo_funcionalidades: 8
|
||||
|
||||
### 2025-12-08
|
||||
|
||||
#### Corrección de Gaps de Documentación (NUEVO)
|
||||
#### Análisis de Alineación y Productos SaaS (NUEVO)
|
||||
- **Acción:** Análisis exhaustivo del sistema de orquestación y alineación de proyectos
|
||||
- **Documento generado:** `ANALISIS-ALINEACION-WORKSPACE-2025-12-08.md`
|
||||
- **Estructuras creadas:**
|
||||
- `apps/products/pos-micro/` - POS ultra básico (100 MXN/mes)
|
||||
- `apps/products/erp-basico/` - ERP austero (300-500 MXN/mes)
|
||||
- `apps/saas/` - Capa de billing, portal, admin, onboarding
|
||||
- **Documentación:**
|
||||
- README.md y CONTEXTO-PROYECTO.md para cada producto
|
||||
- README.md para SaaS layer
|
||||
- CONTEXTO-SAAS.md para orquestación SaaS
|
||||
- **Hallazgos:**
|
||||
- Sistema SIMCO/CAPVED completo (20+ directivas)
|
||||
- ERP-Core: 100% docs, 0% código
|
||||
- Verticales: Todas con HERENCIA-ERP-CORE.md
|
||||
- Productos SaaS: Nueva línea para mercado mexicano
|
||||
- **Agente:** Claude Code
|
||||
|
||||
#### Corrección de Gaps de Documentación
|
||||
- **Acción:** Corrección de 7 gaps identificados en análisis de documentación
|
||||
- **Archivos creados:**
|
||||
- `PERFIL-REQUIREMENTS-ANALYST.md` (v1.4.0)
|
||||
|
||||
@ -0,0 +1,211 @@
|
||||
# Mapeo de Especificaciones Transversales a Verticales
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Autor:** Sistema SIMCO
|
||||
**Ubicación SPECS:** `docs/04-modelado/especificaciones-tecnicas/transversal/`
|
||||
|
||||
---
|
||||
|
||||
## Propósito
|
||||
|
||||
Este documento define qué especificaciones transversales del ERP-Core aplican a cada vertical del ERP-Suite. Sirve como referencia para la propagación de funcionalidades y el desarrollo de cada proyecto vertical.
|
||||
|
||||
---
|
||||
|
||||
## Leyenda
|
||||
|
||||
| Símbolo | Significado |
|
||||
|---------|-------------|
|
||||
| ✓ | Aplica - Debe implementarse |
|
||||
| ○ | Opcional - Puede implementarse según necesidad |
|
||||
| ✗ | No aplica - No es relevante para esta vertical |
|
||||
|
||||
---
|
||||
|
||||
## Matriz de Aplicabilidad
|
||||
|
||||
### SPECS P0 - Funcionales (Críticos)
|
||||
|
||||
| SPEC | Descripción | Construcción | Mecánicas | Vidrio | Retail | Clínicas |
|
||||
|------|-------------|:------------:|:---------:|:------:|:------:|:--------:|
|
||||
| SPEC-SISTEMA-SECUENCIAS | Secuencias automáticas de documentos | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-VALORACION-INVENTARIO | FIFO/AVCO valorización | ✓ | ✓ | ✓ | ✓ | ○ |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | API Keys + ACL + RLS | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-REPORTES-FINANCIEROS | Balance/P&L SAT | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-PORTAL-PROVEEDORES | Portal RFQ | ✓ | ✓ | ✓ | ○ | ✗ |
|
||||
| SPEC-NOMINA-BASICA | hr_payroll | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-GASTOS-EMPLEADOS | hr_expense | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-TAREAS-RECURRENTES | project.task.recurrence | ✓ | ✓ | ✓ | ○ | ○ |
|
||||
| SPEC-SCHEDULER-REPORTES | ir.cron + mail | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-INTEGRACION-CALENDAR | calendar integration | ○ | ✗ | ✗ | ✗ | ✓ |
|
||||
|
||||
### SPECS P1 - Complementarios
|
||||
|
||||
| SPEC | Descripción | Construcción | Mecánicas | Vidrio | Retail | Clínicas |
|
||||
|------|-------------|:------------:|:---------:|:------:|:------:|:--------:|
|
||||
| SPEC-CONTABILIDAD-ANALITICA-MULTIDIMENSIONAL | Centros de costo | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-CONCILIACION-BANCARIA | Conciliación automática | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | Firma electrónica | ✓ | ○ | ○ | ○ | ✓ |
|
||||
| SPEC-TWO-FACTOR-AUTHENTICATION | 2FA, TOTP, SMS | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-TRAZABILIDAD-LOTES-SERIES | Lotes y números de serie | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-PRICING-RULES | Reglas de precios | ○ | ✓ | ✓ | ✓ | ○ |
|
||||
| SPEC-BLANKET-ORDERS | Órdenes marco | ✓ | ✓ | ✓ | ✓ | ✗ |
|
||||
| SPEC-OAUTH2-SOCIAL-LOGIN | OAuth2, Google, Microsoft | ○ | ○ | ○ | ○ | ✓ |
|
||||
| SPEC-INVENTARIOS-CICLICOS | Conteo cíclico | ○ | ✓ | ○ | ✓ | ○ |
|
||||
| SPEC-IMPUESTOS-AVANZADOS | IVA, ISR configurables | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-PLANTILLAS-CUENTAS | Plan de cuentas por país | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-CONSOLIDACION-FINANCIERA | Multi-empresa | ○ | ○ | ○ | ○ | ○ |
|
||||
| SPEC-TASAS-CAMBIO-AUTOMATICAS | Tipos de cambio | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-ALERTAS-PRESUPUESTO | Alertas de exceso | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-PRESUPUESTOS-REVISIONES | Aprobación de presupuestos | ✓ | ✓ | ✓ | ○ | ○ |
|
||||
| SPEC-RRHH-EVALUACIONES-SKILLS | Evaluaciones, skills | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | Dependencias, burndown | ✓ | ✗ | ✓ | ✗ | ✗ |
|
||||
| SPEC-LOCALIZACION-PAISES | Configuración por país | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
### Patrones Técnicos P0
|
||||
|
||||
| SPEC | Descripción | Construcción | Mecánicas | Vidrio | Retail | Clínicas |
|
||||
|------|-------------|:------------:|:---------:|:------:|:------:|:--------:|
|
||||
| SPEC-MAIL-THREAD-TRACKING | mail.thread mixin | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | TransientModel | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## Resumen por Vertical
|
||||
|
||||
### Construcción (MAI/MAE)
|
||||
- **SPECS Aplicables:** 26/30
|
||||
- **SPECS Obligatorias:** 22
|
||||
- **SPECS Opcionales:** 4
|
||||
- **SPECS No Aplican:** 4
|
||||
- **Enfoque:** Proyectos, control de obra, estimaciones, RRHH construcción
|
||||
|
||||
### Mecánicas-Diesel (MMD)
|
||||
- **SPECS Aplicables:** 25/30
|
||||
- **SPECS Obligatorias:** 23
|
||||
- **SPECS Opcionales:** 2
|
||||
- **SPECS No Aplican:** 5
|
||||
- **Enfoque:** Órdenes de trabajo, inventario refacciones, diagnósticos
|
||||
|
||||
### Vidrio-Templado (VT)
|
||||
- **SPECS Aplicables:** 25/30
|
||||
- **SPECS Obligatorias:** 22
|
||||
- **SPECS Opcionales:** 3
|
||||
- **SPECS No Aplican:** 5
|
||||
- **Enfoque:** Producción, control de calidad, hornos de templado
|
||||
|
||||
### Retail (RT)
|
||||
- **SPECS Aplicables:** 24/30
|
||||
- **SPECS Obligatorias:** 21
|
||||
- **SPECS Opcionales:** 3
|
||||
- **SPECS No Aplican:** 6
|
||||
- **Enfoque:** POS, inventario multi-sucursal, promociones, caja
|
||||
|
||||
### Clínicas (CL)
|
||||
- **SPECS Aplicables:** 24/30
|
||||
- **SPECS Obligatorias:** 20
|
||||
- **SPECS Opcionales:** 4
|
||||
- **SPECS No Aplican:** 6
|
||||
- **Enfoque:** Expediente clínico, citas, calendario, cumplimiento normativo
|
||||
|
||||
---
|
||||
|
||||
## Detalle por Vertical
|
||||
|
||||
### Construcción
|
||||
|
||||
**SPECS Críticas para el Dominio:**
|
||||
1. `SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN` - Control de obra y avances
|
||||
2. `SPEC-VALORACION-INVENTARIO` - Costeo de materiales de construcción
|
||||
3. `SPEC-TRAZABILIDAD-LOTES-SERIES` - Trazabilidad de materiales
|
||||
4. `SPEC-PRESUPUESTOS-REVISIONES` - Control presupuestal de obras
|
||||
|
||||
**Adaptaciones Requeridas:**
|
||||
- Proyectos = Obras/Fraccionamientos
|
||||
- Tareas = Etapas de construcción
|
||||
- Productos = Materiales de construcción
|
||||
|
||||
### Mecánicas-Diesel
|
||||
|
||||
**SPECS Críticas para el Dominio:**
|
||||
1. `SPEC-VALORACION-INVENTARIO` - Costeo de refacciones
|
||||
2. `SPEC-TRAZABILIDAD-LOTES-SERIES` - Tracking de partes OEM
|
||||
3. `SPEC-INVENTARIOS-CICLICOS` - Control de stock
|
||||
4. `SPEC-PRICING-RULES` - Reglas de precio por tipo de servicio
|
||||
|
||||
**Adaptaciones Requeridas:**
|
||||
- Productos = Refacciones, partes
|
||||
- Órdenes de venta = Órdenes de servicio
|
||||
- Partners = Clientes con vehículos
|
||||
|
||||
### Vidrio-Templado
|
||||
|
||||
**SPECS Críticas para el Dominio:**
|
||||
1. `SPEC-VALORACION-INVENTARIO` - Costeo de materia prima y producto terminado
|
||||
2. `SPEC-TRAZABILIDAD-LOTES-SERIES` - Lotes de producción de vidrio
|
||||
3. `SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN` - Órdenes de producción
|
||||
4. `SPEC-PRICING-RULES` - Precios por dimensiones y tipos de vidrio
|
||||
|
||||
**Adaptaciones Requeridas:**
|
||||
- Productos = Tipos de vidrio (templado, laminado, etc.)
|
||||
- Producción = Control de hornos y parámetros
|
||||
- Calidad = Inspecciones de fragmentación
|
||||
|
||||
### Retail
|
||||
|
||||
**SPECS Críticas para el Dominio:**
|
||||
1. `SPEC-PRICING-RULES` - Promociones y descuentos
|
||||
2. `SPEC-INVENTARIOS-CICLICOS` - Conteos en sucursales
|
||||
3. `SPEC-TRAZABILIDAD-LOTES-SERIES` - Productos con lote/serie
|
||||
4. `SPEC-VALORACION-INVENTARIO` - Costeo de mercancía
|
||||
|
||||
**Adaptaciones Requeridas:**
|
||||
- Almacenes = Sucursales
|
||||
- Ventas = Transacciones POS
|
||||
- Clientes = Programa de lealtad
|
||||
|
||||
### Clínicas
|
||||
|
||||
**SPECS Críticas para el Dominio:**
|
||||
1. `SPEC-INTEGRACION-CALENDAR` - Agenda de citas médicas
|
||||
2. `SPEC-MAIL-THREAD-TRACKING` - Historial de comunicación con pacientes
|
||||
3. `SPEC-RRHH-EVALUACIONES-SKILLS` - Credenciales médicas
|
||||
4. `SPEC-FIRMA-ELECTRONICA-NOM151` - Firma de expedientes
|
||||
|
||||
**Adaptaciones Requeridas:**
|
||||
- Partners = Pacientes
|
||||
- Productos = Servicios médicos, medicamentos
|
||||
- Calendario = Agenda de consultas
|
||||
- Cumplimiento = NOM-024-SSA3-2012
|
||||
|
||||
---
|
||||
|
||||
## Workflows Aplicables
|
||||
|
||||
| Workflow | Construcción | Mecánicas | Vidrio | Retail | Clínicas |
|
||||
|----------|:------------:|:---------:|:------:|:------:|:--------:|
|
||||
| WORKFLOW-CIERRE-PERIODO-CONTABLE | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| WORKFLOW-3-WAY-MATCH | ✓ | ✓ | ✓ | ○ | ○ |
|
||||
| WORKFLOW-PAGOS-ANTICIPADOS | ✓ | ✓ | ✓ | ✗ | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## Próximos Pasos
|
||||
|
||||
1. Crear `HERENCIA-SPECS-CORE.md` en cada vertical con detalle de implementación
|
||||
2. Actualizar `HERENCIA-ERP-CORE.md` con referencia a SPECS aplicables
|
||||
3. Documentar adaptaciones específicas por vertical en carpeta `transversal-core/`
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- SPECS del Core: `erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/`
|
||||
- Análisis de Gaps: `erp-core/orchestration/01-analisis/ANALISIS-GAPS-CONSOLIDADO.md`
|
||||
- Directiva de Extensión: `erp-core/orchestration/directivas/DIRECTIVA-EXTENSION-VERTICALES.md`
|
||||
|
||||
---
|
||||
|
||||
**Documento de referencia canónico para propagación de SPECS**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -231,24 +231,74 @@ modulos:
|
||||
|
||||
verticales_dependientes:
|
||||
- nombre: construccion
|
||||
estado: 35%
|
||||
codigo: MAI/MAE
|
||||
estado: 40%
|
||||
fase: EN_DESARROLLO
|
||||
path: ../verticales/construccion/
|
||||
|
||||
- nombre: vidrio-templado
|
||||
estado: 0%
|
||||
path: ../verticales/vidrio-templado/
|
||||
modulos: 18
|
||||
specs_aplicables: 26
|
||||
specs_implementadas: 0
|
||||
tablas_heredadas: 124
|
||||
tablas_especificas: 33
|
||||
|
||||
- nombre: mecanicas-diesel
|
||||
estado: 0%
|
||||
codigo: MMD
|
||||
estado: 20%
|
||||
fase: DDL_IMPLEMENTADO
|
||||
path: ../verticales/mecanicas-diesel/
|
||||
modulos: 6
|
||||
specs_aplicables: 25
|
||||
specs_implementadas: 0
|
||||
tablas_heredadas: 97
|
||||
tablas_especificas: 30
|
||||
|
||||
- nombre: vidrio-templado
|
||||
codigo: VT
|
||||
estado: 15%
|
||||
fase: PLANIFICACION_COMPLETA
|
||||
path: ../verticales/vidrio-templado/
|
||||
modulos: 8
|
||||
specs_aplicables: 25
|
||||
specs_implementadas: 0
|
||||
tablas_heredadas: 97
|
||||
tablas_especificas: 25
|
||||
|
||||
- nombre: retail
|
||||
estado: 0%
|
||||
codigo: RT
|
||||
estado: 15%
|
||||
fase: PLANIFICACION_COMPLETA
|
||||
path: ../verticales/retail/
|
||||
modulos: 10
|
||||
specs_aplicables: 24
|
||||
specs_implementadas: 0
|
||||
tablas_heredadas: 102
|
||||
tablas_especificas: 30
|
||||
|
||||
- nombre: clinicas
|
||||
estado: 0%
|
||||
codigo: CL
|
||||
estado: 15%
|
||||
fase: PLANIFICACION_COMPLETA
|
||||
path: ../verticales/clinicas/
|
||||
modulos: 12
|
||||
specs_aplicables: 24
|
||||
specs_implementadas: 0
|
||||
tablas_heredadas: 100
|
||||
tablas_especificas: 35
|
||||
|
||||
# ============================================================================
|
||||
# MAPEO DE SPECS A VERTICALES (Referencia)
|
||||
# ============================================================================
|
||||
mapeo_specs_verticales:
|
||||
documento_completo: docs/04-modelado/MAPEO-SPECS-VERTICALES.md
|
||||
fecha_actualizacion: 2025-12-08
|
||||
resumen:
|
||||
total_specs: 30
|
||||
por_vertical:
|
||||
construccion: {aplicables: 26, obligatorias: 22, opcionales: 4}
|
||||
mecanicas_diesel: {aplicables: 25, obligatorias: 23, opcionales: 2}
|
||||
vidrio_templado: {aplicables: 25, obligatorias: 22, opcionales: 3}
|
||||
retail: {aplicables: 24, obligatorias: 21, opcionales: 3}
|
||||
clinicas: {aplicables: 24, obligatorias: 20, opcionales: 4}
|
||||
|
||||
documentacion:
|
||||
total_archivos: 630
|
||||
|
||||
191
projects/erp-suite/apps/products/erp-basico/README.md
Normal file
191
projects/erp-suite/apps/products/erp-basico/README.md
Normal file
@ -0,0 +1,191 @@
|
||||
# ERP Básico SaaS - Solución Integral Austera
|
||||
|
||||
## Descripción
|
||||
|
||||
Sistema ERP completo pero austero, diseñado para PyMEs que necesitan funcionalidad integral sin la complejidad ni el costo de soluciones enterprise.
|
||||
|
||||
## Target de Mercado
|
||||
|
||||
- PyMEs con 5-50 empleados
|
||||
- Negocios que necesitan más que un POS
|
||||
- Empresas que buscan digitalización económica
|
||||
- Comercios con operaciones de compra-venta
|
||||
- Pequeñas manufacturas
|
||||
|
||||
## Precio
|
||||
|
||||
**~300-500 MXN/mes** (según módulos activos)
|
||||
|
||||
## Plan Base (300 MXN/mes)
|
||||
|
||||
| Módulo | Incluido | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| Autenticación | Obligatorio | Login, 2FA, roles básicos |
|
||||
| Usuarios | Obligatorio | Hasta 5 usuarios |
|
||||
| Multi-tenant | Obligatorio | Aislamiento por empresa |
|
||||
| Catálogos | Incluido | Productos, categorías, unidades |
|
||||
| Inventario | Incluido | Stock, movimientos, alertas |
|
||||
| Ventas | Incluido | Cotizaciones, pedidos, facturas |
|
||||
| Compras | Incluido | Órdenes de compra, proveedores |
|
||||
| Clientes | Incluido | CRM básico, contactos |
|
||||
| Reportes | Incluido | Dashboard, reportes esenciales |
|
||||
|
||||
## Módulos Opcionales
|
||||
|
||||
| Módulo | Precio | Descripción |
|
||||
|--------|--------|-------------|
|
||||
| Contabilidad | +150 MXN/mes | Pólizas, balances, estados financieros |
|
||||
| RRHH | +100 MXN/mes | Empleados, nómina básica, asistencia |
|
||||
| Facturación CFDI | +100 MXN/mes | Timbrado SAT México |
|
||||
| Usuarios extra | +50 MXN/usuario | Más de 5 usuarios |
|
||||
| WhatsApp Bot | Por consumo | Consultas y notificaciones |
|
||||
| Soporte Premium | +200 MXN/mes | Atención prioritaria |
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
- **Backend:** Node.js + Express/NestJS + TypeScript
|
||||
- **Frontend:** React 18 + Vite + Tailwind CSS
|
||||
- **Database:** PostgreSQL 15+ con RLS
|
||||
- **Cache:** Redis (compartido)
|
||||
- **Auth:** JWT + bcrypt
|
||||
|
||||
## Arquitectura
|
||||
|
||||
```
|
||||
erp-basico/
|
||||
├── backend/
|
||||
│ ├── src/
|
||||
│ │ ├── modules/
|
||||
│ │ │ ├── auth/ # Autenticación
|
||||
│ │ │ ├── users/ # Gestión usuarios
|
||||
│ │ │ ├── companies/ # Multi-tenant
|
||||
│ │ │ ├── catalogs/ # Catálogos maestros
|
||||
│ │ │ ├── inventory/ # Inventario
|
||||
│ │ │ ├── sales/ # Ventas
|
||||
│ │ │ ├── purchases/ # Compras
|
||||
│ │ │ ├── partners/ # Clientes/Proveedores
|
||||
│ │ │ └── reports/ # Reportes
|
||||
│ │ └── shared/
|
||||
│ │ ├── guards/
|
||||
│ │ ├── decorators/
|
||||
│ │ └── utils/
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ │ ├── features/ # Por módulo
|
||||
│ │ ├── shared/ # Componentes base
|
||||
│ │ └── app/ # Layout, routing
|
||||
├── database/
|
||||
│ └── ddl/
|
||||
│ ├── 00-extensions.sql
|
||||
│ ├── 01-schemas.sql
|
||||
│ ├── 02-core-tables.sql
|
||||
│ └── 03-business-tables.sql
|
||||
└── orchestration/
|
||||
```
|
||||
|
||||
## Base de Datos (~40 tablas)
|
||||
|
||||
### Schema: `auth`
|
||||
- users, roles, permissions, sessions, tokens
|
||||
|
||||
### Schema: `core`
|
||||
- companies, settings, sequences, audit_logs
|
||||
|
||||
### Schema: `catalog`
|
||||
- products, categories, units, taxes, payment_methods
|
||||
|
||||
### Schema: `inventory`
|
||||
- warehouses, stock_moves, stock_quants, adjustments
|
||||
|
||||
### Schema: `sales`
|
||||
- quotations, sale_orders, invoices, payments
|
||||
|
||||
### Schema: `purchases`
|
||||
- purchase_orders, supplier_invoices, receipts
|
||||
|
||||
### Schema: `partners`
|
||||
- partners, contacts, addresses
|
||||
|
||||
### Schema: `reports`
|
||||
- report_configs, saved_reports
|
||||
|
||||
## Diferenciación vs POS Micro
|
||||
|
||||
| Aspecto | POS Micro | ERP Básico |
|
||||
|---------|-----------|------------|
|
||||
| Precio | 100 MXN | 300-500 MXN |
|
||||
| Tablas BD | ~10 | ~40 |
|
||||
| Módulos | 4 | 10+ |
|
||||
| Usuarios | 1 | 5+ |
|
||||
| Compras | No | Sí |
|
||||
| Inventario | Básico | Completo |
|
||||
| Reportes | Mínimos | Dashboard |
|
||||
| Facturación | No | Opcional |
|
||||
| Contabilidad | No | Opcional |
|
||||
|
||||
## Herencia del Core
|
||||
|
||||
Este producto hereda **directamente** de `erp-core`:
|
||||
|
||||
| Componente | % Herencia | Adaptación |
|
||||
|------------|------------|------------|
|
||||
| Auth | 100% | Ninguna |
|
||||
| Users | 100% | Ninguna |
|
||||
| Multi-tenant | 100% | Ninguna |
|
||||
| Catálogos | 80% | Simplificado |
|
||||
| Inventario | 70% | Sin lotes/series |
|
||||
| Ventas | 70% | Sin workflows complejos |
|
||||
| Compras | 70% | Sin aprobaciones |
|
||||
| Partners | 90% | Ninguna |
|
||||
| Reportes | 50% | Subset de reportes |
|
||||
|
||||
## Feature Flags
|
||||
|
||||
```yaml
|
||||
# Configuración por tenant
|
||||
features:
|
||||
accounting: false # +150 MXN
|
||||
hr: false # +100 MXN
|
||||
cfdi: false # +100 MXN
|
||||
whatsapp_bot: false # Por consumo
|
||||
advanced_reports: false
|
||||
multi_warehouse: false
|
||||
serial_numbers: false
|
||||
lot_tracking: false
|
||||
```
|
||||
|
||||
## Limitaciones (Por diseño)
|
||||
|
||||
- Máximo 10,000 productos
|
||||
- Máximo 5 usuarios en plan base
|
||||
- Sin multi-sucursal en plan base
|
||||
- Sin contabilidad avanzada (solo opcional)
|
||||
- Sin manufactura
|
||||
- Sin proyectos
|
||||
- Sin e-commerce integrado
|
||||
|
||||
## Roadmap
|
||||
|
||||
### MVP (v1.0)
|
||||
- [x] Auth completo (heredado de core)
|
||||
- [ ] Catálogos básicos
|
||||
- [ ] Inventario simple
|
||||
- [ ] Ventas (cotización → pedido → factura)
|
||||
- [ ] Compras básicas
|
||||
- [ ] Dashboard inicial
|
||||
|
||||
### v1.1
|
||||
- [ ] Módulo contabilidad (opcional)
|
||||
- [ ] CFDI México (opcional)
|
||||
- [ ] Reportes adicionales
|
||||
|
||||
### v1.2
|
||||
- [ ] RRHH básico (opcional)
|
||||
- [ ] Multi-almacén
|
||||
- [ ] Integraciones bancarias
|
||||
|
||||
---
|
||||
|
||||
*Producto: ERP Básico SaaS v1.0*
|
||||
*Precio Target: 300-500 MXN/mes*
|
||||
*Mercado: PyMEs México*
|
||||
@ -0,0 +1,238 @@
|
||||
# Contexto del Proyecto: ERP Básico SaaS
|
||||
|
||||
## Identificación
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nombre** | ERP Básico SaaS |
|
||||
| **Tipo** | Producto SaaS |
|
||||
| **Nivel** | 2B.2 (Producto dentro de Suite) |
|
||||
| **Suite Padre** | erp-suite |
|
||||
| **Ruta Base** | `projects/erp-suite/apps/products/erp-basico/` |
|
||||
| **Estado** | En Planificación |
|
||||
|
||||
## Descripción
|
||||
|
||||
ERP completo pero austero para PyMEs. Hereda directamente de erp-core con configuración simplificada y precios accesibles.
|
||||
|
||||
## Target de Mercado
|
||||
|
||||
- PyMEs con 5-50 empleados
|
||||
- Comercios con operaciones compra-venta
|
||||
- Pequeñas manufacturas
|
||||
- Distribuidores
|
||||
- Empresas en proceso de digitalización
|
||||
|
||||
## Propuesta de Valor
|
||||
|
||||
1. **ERP completo** - No solo POS, gestión integral
|
||||
2. **Precio accesible** - 300-500 MXN/mes vs 2,000+ de SAP/Odoo
|
||||
3. **Sin complejidad** - Configuración mínima
|
||||
4. **Modular** - Paga solo lo que usas
|
||||
5. **Mexicanizado** - CFDI, bancos mexicanos
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
runtime: Node.js 20+
|
||||
framework: NestJS (heredado de core)
|
||||
language: TypeScript 5.3+
|
||||
orm: TypeORM
|
||||
validation: class-validator
|
||||
|
||||
frontend:
|
||||
framework: React 18
|
||||
bundler: Vite
|
||||
styling: Tailwind CSS
|
||||
state: Zustand
|
||||
forms: React Hook Form
|
||||
|
||||
database:
|
||||
engine: PostgreSQL 15+
|
||||
multi_tenant: true (RLS)
|
||||
schemas: 8
|
||||
tables: ~40
|
||||
|
||||
cache:
|
||||
engine: Redis
|
||||
usage: Sessions, rate-limiting
|
||||
```
|
||||
|
||||
## Variables del Proyecto
|
||||
|
||||
```yaml
|
||||
# Identificadores
|
||||
PROJECT_NAME: erp-basico
|
||||
PROJECT_CODE: ERPB
|
||||
SUITE: erp-suite
|
||||
|
||||
# Database
|
||||
DB_NAME: erp_suite_db # Compartida
|
||||
SCHEMAS:
|
||||
- auth
|
||||
- core
|
||||
- catalog
|
||||
- inventory
|
||||
- sales
|
||||
- purchases
|
||||
- partners
|
||||
- reports
|
||||
|
||||
# Paths
|
||||
BACKEND_ROOT: apps/products/erp-basico/backend
|
||||
FRONTEND_ROOT: apps/products/erp-basico/frontend
|
||||
DATABASE_ROOT: apps/products/erp-basico/database
|
||||
|
||||
# Business
|
||||
BASE_PRICE_MXN: 300
|
||||
MAX_USERS_BASE: 5
|
||||
MAX_PRODUCTS: 10000
|
||||
```
|
||||
|
||||
## Herencia del Core
|
||||
|
||||
### Módulos Heredados (100%)
|
||||
|
||||
| Módulo Core | Uso en ERP Básico |
|
||||
|-------------|-------------------|
|
||||
| MGN-001 Auth | Completo |
|
||||
| MGN-002 Users | Completo |
|
||||
| MGN-003 Roles | Simplificado (3 roles) |
|
||||
| MGN-004 Tenants | Completo |
|
||||
| MGN-005 Catalogs | 80% (sin variantes) |
|
||||
| MGN-008 Notifications | Simplificado |
|
||||
|
||||
### Módulos Adaptados
|
||||
|
||||
| Módulo Core | Adaptación |
|
||||
|-------------|------------|
|
||||
| MGN-007 Audit | Solo logs críticos |
|
||||
| MGN-009 Reports | Subset de reportes |
|
||||
| Inventory | Sin lotes/series |
|
||||
| Sales | Sin workflows aprobación |
|
||||
| Purchases | Sin aprobaciones multi-nivel |
|
||||
|
||||
### Módulos NO Incluidos
|
||||
|
||||
| Módulo Core | Razón |
|
||||
|-------------|-------|
|
||||
| MGN-010 Financial | Opcional (+150 MXN) |
|
||||
| Projects | Complejidad innecesaria |
|
||||
| Manufacturing | Fuera de scope |
|
||||
| Advanced HR | Opcional (+100 MXN) |
|
||||
|
||||
## Módulos del Producto
|
||||
|
||||
### Obligatorios (Plan Base)
|
||||
|
||||
| Módulo | Tablas | Endpoints | Componentes |
|
||||
|--------|--------|-----------|-------------|
|
||||
| auth | 5 | 8 | 4 |
|
||||
| users | 2 | 6 | 3 |
|
||||
| companies | 3 | 5 | 2 |
|
||||
| catalogs | 5 | 12 | 6 |
|
||||
| inventory | 4 | 10 | 5 |
|
||||
| sales | 4 | 12 | 6 |
|
||||
| purchases | 3 | 8 | 4 |
|
||||
| partners | 3 | 8 | 4 |
|
||||
| reports | 2 | 6 | 3 |
|
||||
|
||||
### Opcionales (Feature Flags)
|
||||
|
||||
| Módulo | Precio | Tablas Extra |
|
||||
|--------|--------|--------------|
|
||||
| accounting | +150 MXN | 8 |
|
||||
| hr | +100 MXN | 6 |
|
||||
| cfdi | +100 MXN | 3 |
|
||||
|
||||
## Feature Flags
|
||||
|
||||
```typescript
|
||||
interface TenantFeatures {
|
||||
// Plan base
|
||||
base_erp: true;
|
||||
max_users: 5;
|
||||
max_products: 10000;
|
||||
|
||||
// Opcionales
|
||||
accounting: boolean; // +150 MXN
|
||||
hr: boolean; // +100 MXN
|
||||
cfdi: boolean; // +100 MXN
|
||||
extra_users: number; // +50 MXN c/u
|
||||
multi_warehouse: boolean; // +100 MXN
|
||||
whatsapp_bot: boolean; // Por consumo
|
||||
advanced_reports: boolean;// +50 MXN
|
||||
}
|
||||
```
|
||||
|
||||
## Diferenciación
|
||||
|
||||
### vs POS Micro
|
||||
|
||||
| Aspecto | POS Micro | ERP Básico |
|
||||
|---------|-----------|------------|
|
||||
| Complejidad | Mínima | Media |
|
||||
| Módulos | 4 | 10+ |
|
||||
| Usuarios | 1 | 5+ |
|
||||
| Compras | No | Sí |
|
||||
| Multi-almacén | No | Opcional |
|
||||
| Contabilidad | No | Opcional |
|
||||
| Precio | 100 MXN | 300+ MXN |
|
||||
|
||||
### vs ERP Enterprise (Verticales)
|
||||
|
||||
| Aspecto | ERP Básico | Verticales |
|
||||
|---------|------------|------------|
|
||||
| Industria | General | Especializado |
|
||||
| Complejidad | Media | Alta |
|
||||
| Customización | Baja | Alta |
|
||||
| Workflows | Simples | Complejos |
|
||||
| Precio | 300-500 MXN | 1,000+ MXN |
|
||||
|
||||
## Métricas de Éxito
|
||||
|
||||
| Métrica | Target |
|
||||
|---------|--------|
|
||||
| Tiempo de onboarding | < 30 minutos |
|
||||
| Usuarios activos diarios | > 60% |
|
||||
| NPS | > 40 |
|
||||
| Churn mensual | < 3% |
|
||||
| Tickets soporte/usuario | < 0.5/mes |
|
||||
|
||||
## Roadmap
|
||||
|
||||
### MVP (v1.0)
|
||||
- [ ] Herencia completa de auth/users/tenants
|
||||
- [ ] Catálogos (productos, categorías, unidades)
|
||||
- [ ] Inventario básico (stock, movimientos)
|
||||
- [ ] Ventas (cotización → pedido → factura)
|
||||
- [ ] Compras básicas
|
||||
- [ ] Dashboard inicial
|
||||
- [ ] Billing/suscripciones
|
||||
|
||||
### v1.1
|
||||
- [ ] Módulo contabilidad (opcional)
|
||||
- [ ] CFDI México (opcional)
|
||||
- [ ] Reportes financieros
|
||||
|
||||
### v1.2
|
||||
- [ ] RRHH básico (opcional)
|
||||
- [ ] Multi-almacén (opcional)
|
||||
- [ ] Integraciones bancarias México
|
||||
|
||||
### v2.0
|
||||
- [ ] App móvil
|
||||
- [ ] Integraciones marketplace
|
||||
- [ ] IA para predicciones
|
||||
|
||||
## Documentos Relacionados
|
||||
|
||||
- `../README.md` - Descripción general
|
||||
- `../../erp-core/` - Core heredado
|
||||
- `../../erp-core/docs/` - Documentación detallada de módulos
|
||||
- `../../../orchestration/` - Orquestación suite level
|
||||
|
||||
---
|
||||
|
||||
*Última actualización: 2025-12-08*
|
||||
139
projects/erp-suite/apps/products/pos-micro/README.md
Normal file
139
projects/erp-suite/apps/products/pos-micro/README.md
Normal file
@ -0,0 +1,139 @@
|
||||
# POS Micro - Punto de Venta Ultra Básico
|
||||
|
||||
## Descripción
|
||||
|
||||
Sistema de punto de venta minimalista diseñado para el mercado informal mexicano. Enfocado en simplicidad extrema, bajo costo y funcionalidad offline.
|
||||
|
||||
## Target de Mercado
|
||||
|
||||
- Puestos de calle y ambulantes
|
||||
- Tiendas de abarrotes y misceláneas
|
||||
- Puestos de comida (tacos, tortas, etc.)
|
||||
- Pequeños locales comerciales
|
||||
- Vendedores independientes
|
||||
|
||||
## Precio
|
||||
|
||||
**~100 MXN/mes** + consumo de IA (opcional)
|
||||
|
||||
## Características
|
||||
|
||||
### Incluidas en Plan Base (100 MXN/mes)
|
||||
|
||||
| Característica | Descripción |
|
||||
|----------------|-------------|
|
||||
| Punto de Venta | Registrar ventas, calcular cambio |
|
||||
| Catálogo | Lista de productos con precios |
|
||||
| Inventario Básico | Control de stock simple |
|
||||
| Corte de Caja | Resumen diario |
|
||||
| Reportes | Ventas día/semana/mes |
|
||||
| PWA Offline | Funciona sin internet |
|
||||
| 1 Usuario | Operador principal |
|
||||
|
||||
### Opcionales (Por Consumo)
|
||||
|
||||
| Característica | Costo |
|
||||
|----------------|-------|
|
||||
| WhatsApp Bot | ~0.02 USD por consulta |
|
||||
| Usuario adicional | +30 MXN/mes |
|
||||
| Soporte prioritario | +50 MXN/mes |
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
- **Backend:** Node.js + Express + TypeScript
|
||||
- **Frontend:** React + PWA + Tailwind CSS
|
||||
- **Database:** PostgreSQL (compartida multi-tenant)
|
||||
- **WhatsApp:** WhatsApp Business API
|
||||
- **IA:** Claude API (para bot)
|
||||
|
||||
## Arquitectura
|
||||
|
||||
```
|
||||
pos-micro/
|
||||
├── backend/ # API mínima
|
||||
├── frontend/ # SPA React
|
||||
├── pwa/ # Service Worker + Offline
|
||||
├── database/ # ~10 tablas
|
||||
├── whatsapp/ # Integración WA Business
|
||||
├── docs/ # Documentación
|
||||
└── orchestration/ # Sistema NEXUS
|
||||
```
|
||||
|
||||
## Base de Datos (~10 tablas)
|
||||
|
||||
1. `tenants` - Empresas/negocios
|
||||
2. `users` - Usuarios del sistema
|
||||
3. `products` - Catálogo de productos
|
||||
4. `sales` - Ventas registradas
|
||||
5. `sale_items` - Detalle de ventas
|
||||
6. `inventory_movements` - Movimientos de inventario
|
||||
7. `daily_closures` - Cortes de caja
|
||||
8. `whatsapp_sessions` - Sesiones WA
|
||||
9. `ai_usage` - Consumo de tokens IA
|
||||
10. `subscriptions` - Suscripciones y pagos
|
||||
|
||||
## Flujo de Usuario
|
||||
|
||||
### Registro
|
||||
1. Usuario accede a landing page
|
||||
2. Ingresa número de WhatsApp
|
||||
3. Recibe código de verificación
|
||||
4. Configura nombre del negocio
|
||||
5. Agrega primeros productos
|
||||
6. Listo para vender
|
||||
|
||||
### Venta Típica
|
||||
1. Abrir PWA (funciona offline)
|
||||
2. Seleccionar productos
|
||||
3. Ver total automático
|
||||
4. Registrar pago (efectivo/tarjeta)
|
||||
5. Calcular cambio
|
||||
6. Venta registrada
|
||||
|
||||
### Consulta por WhatsApp
|
||||
```
|
||||
Usuario: "ventas de hoy"
|
||||
Bot: "Ventas hoy: $1,250 MXN (15 tickets)
|
||||
Producto más vendido: Coca Cola 600ml (23 unidades)"
|
||||
|
||||
Usuario: "stock de sabritas"
|
||||
Bot: "Sabritas Original: 12 unidades
|
||||
Sabritas Adobadas: 8 unidades
|
||||
Sabritas Limón: 15 unidades"
|
||||
```
|
||||
|
||||
## Principios de Diseño
|
||||
|
||||
1. **Simplicidad extrema** - Máximo 3 clicks para cualquier acción
|
||||
2. **Mobile-first** - Diseñado para celulares
|
||||
3. **Offline-first** - Funciona sin internet
|
||||
4. **Bajo costo** - Infraestructura mínima
|
||||
5. **Sin fricción** - Onboarding en 5 minutos
|
||||
|
||||
## Limitaciones (Por diseño)
|
||||
|
||||
- Máximo 500 productos
|
||||
- Máximo 1,000 ventas/mes en plan base
|
||||
- Sin facturación electrónica (CFDI)
|
||||
- Sin contabilidad
|
||||
- Sin multi-sucursal
|
||||
- Sin CRM avanzado
|
||||
|
||||
## Herencia del Core
|
||||
|
||||
Este producto hereda de `erp-core`:
|
||||
- Sistema de autenticación básico
|
||||
- Multi-tenancy (RLS)
|
||||
- Estructura de proyectos
|
||||
|
||||
NO hereda (por simplicidad):
|
||||
- Módulos financieros
|
||||
- RRHH
|
||||
- CRM completo
|
||||
- Reportes avanzados
|
||||
|
||||
---
|
||||
|
||||
*Producto: POS Micro v1.0*
|
||||
*Precio Target: 100 MXN/mes*
|
||||
*Mercado: Informal mexicano*
|
||||
@ -0,0 +1,164 @@
|
||||
# Contexto del Proyecto: POS Micro
|
||||
|
||||
## Identificación
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nombre** | POS Micro |
|
||||
| **Tipo** | Producto SaaS |
|
||||
| **Nivel** | 2B.2 (Producto dentro de Suite) |
|
||||
| **Suite Padre** | erp-suite |
|
||||
| **Ruta Base** | `projects/erp-suite/apps/products/pos-micro/` |
|
||||
| **Estado** | En Planificación |
|
||||
|
||||
## Descripción
|
||||
|
||||
Sistema de punto de venta ultra-minimalista diseñado para el mercado informal mexicano. Precio target: **100 MXN/mes**.
|
||||
|
||||
## Target de Mercado
|
||||
|
||||
- Puestos ambulantes
|
||||
- Tiendas de abarrotes
|
||||
- Misceláneas
|
||||
- Puestos de comida
|
||||
- Pequeños comercios
|
||||
|
||||
## Propuesta de Valor
|
||||
|
||||
1. **Precio accesible** - 100 MXN/mes (vs 500+ de competidores)
|
||||
2. **Simplicidad** - Solo lo esencial
|
||||
3. **Offline** - Funciona sin internet
|
||||
4. **WhatsApp** - Consultas por chat
|
||||
5. **Sin fricción** - Registro en 5 minutos
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
runtime: Node.js 20+
|
||||
framework: Express
|
||||
language: TypeScript
|
||||
orm: TypeORM (simplificado)
|
||||
|
||||
frontend:
|
||||
framework: React 18
|
||||
bundler: Vite
|
||||
styling: Tailwind CSS
|
||||
pwa: Workbox
|
||||
|
||||
database:
|
||||
engine: PostgreSQL 15+
|
||||
multi_tenant: true (RLS)
|
||||
max_tables: 10
|
||||
|
||||
integrations:
|
||||
whatsapp: WhatsApp Business API
|
||||
ai: Claude API (opcional)
|
||||
payments: Stripe/Conekta
|
||||
```
|
||||
|
||||
## Variables del Proyecto
|
||||
|
||||
```yaml
|
||||
# Identificadores
|
||||
PROJECT_NAME: pos-micro
|
||||
PROJECT_CODE: POS
|
||||
SUITE: erp-suite
|
||||
|
||||
# Database
|
||||
DB_SCHEMA: pos_micro
|
||||
DB_NAME: erp_suite_db # Compartida
|
||||
MAX_TABLES: 10
|
||||
|
||||
# Paths
|
||||
BACKEND_ROOT: apps/products/pos-micro/backend
|
||||
FRONTEND_ROOT: apps/products/pos-micro/frontend
|
||||
PWA_ROOT: apps/products/pos-micro/pwa
|
||||
DATABASE_ROOT: apps/products/pos-micro/database
|
||||
|
||||
# Business
|
||||
PRICE_MXN: 100
|
||||
PRICE_USD: 6
|
||||
MAX_PRODUCTS: 500
|
||||
MAX_SALES_MONTH: 1000
|
||||
```
|
||||
|
||||
## Herencia del Core
|
||||
|
||||
### SÍ Hereda
|
||||
|
||||
| Componente | Origen | Adaptación |
|
||||
|------------|--------|------------|
|
||||
| Auth básico | erp-core/auth | Simplificado (solo email/WA) |
|
||||
| Multi-tenant | erp-core/tenant | RLS básico |
|
||||
| API patterns | erp-core/shared | Endpoints mínimos |
|
||||
|
||||
### NO Hereda (Por Diseño)
|
||||
|
||||
| Componente | Razón |
|
||||
|------------|-------|
|
||||
| Contabilidad | Demasiado complejo |
|
||||
| RRHH | No aplica |
|
||||
| CRM | Simplificar |
|
||||
| Compras | No necesario |
|
||||
| Reportes avanzados | Overkill |
|
||||
|
||||
## Módulos del Producto
|
||||
|
||||
| Módulo | Prioridad | Tablas | Endpoints |
|
||||
|--------|-----------|--------|-----------|
|
||||
| auth | P0 | 2 | 4 |
|
||||
| products | P0 | 1 | 5 |
|
||||
| sales | P0 | 2 | 4 |
|
||||
| inventory | P0 | 1 | 3 |
|
||||
| reports | P1 | 1 | 3 |
|
||||
| whatsapp | P1 | 2 | 2 |
|
||||
| billing | P1 | 1 | 2 |
|
||||
|
||||
## Restricciones de Diseño
|
||||
|
||||
1. **Máximo 10 tablas** - Simplicidad de BD
|
||||
2. **Máximo 20 endpoints** - API mínima
|
||||
3. **Máximo 10 pantallas** - UI simple
|
||||
4. **Offline-first** - Service Worker obligatorio
|
||||
5. **Mobile-first** - Diseño responsivo primero móvil
|
||||
6. **3-click rule** - Cualquier acción en máximo 3 clicks
|
||||
|
||||
## Métricas de Éxito
|
||||
|
||||
| Métrica | Target |
|
||||
|---------|--------|
|
||||
| Tiempo de onboarding | < 5 minutos |
|
||||
| Tiempo carga PWA | < 2 segundos |
|
||||
| Funcionalidad offline | 100% ventas |
|
||||
| Costo infraestructura/usuario | < $1 USD/mes |
|
||||
| Churn mensual | < 5% |
|
||||
|
||||
## Roadmap
|
||||
|
||||
### MVP (v1.0)
|
||||
- [ ] Auth por WhatsApp
|
||||
- [ ] CRUD productos
|
||||
- [ ] Registro de ventas
|
||||
- [ ] Corte de caja
|
||||
- [ ] PWA offline
|
||||
|
||||
### v1.1
|
||||
- [ ] WhatsApp Bot básico
|
||||
- [ ] Reportes por WhatsApp
|
||||
- [ ] Notificaciones stock bajo
|
||||
|
||||
### v1.2
|
||||
- [ ] Dashboard web simple
|
||||
- [ ] Exportar datos CSV
|
||||
- [ ] Backup automático
|
||||
|
||||
## Documentos Relacionados
|
||||
|
||||
- `../README.md` - Descripción general
|
||||
- `../../erp-core/orchestration/` - Core heredado
|
||||
- `../../../orchestration/` - Suite level
|
||||
|
||||
---
|
||||
|
||||
*Última actualización: 2025-12-08*
|
||||
198
projects/erp-suite/apps/saas/README.md
Normal file
198
projects/erp-suite/apps/saas/README.md
Normal file
@ -0,0 +1,198 @@
|
||||
# SaaS Layer - ERP Suite
|
||||
|
||||
## Descripción
|
||||
|
||||
Capa de servicios SaaS que gestiona multi-tenancy, billing, suscripciones y portal de clientes para todos los productos del ERP Suite.
|
||||
|
||||
## Componentes
|
||||
|
||||
```
|
||||
saas/
|
||||
├── billing/ # Facturación y cobros
|
||||
├── portal/ # Portal de clientes
|
||||
├── admin/ # Administración multi-tenant
|
||||
├── onboarding/ # Registro y configuración inicial
|
||||
├── docs/ # Documentación
|
||||
└── orchestration/ # Sistema NEXUS
|
||||
```
|
||||
|
||||
## Billing
|
||||
|
||||
Gestión de suscripciones y cobros.
|
||||
|
||||
### Funcionalidades
|
||||
|
||||
- Planes de suscripción (POS Micro, ERP Básico, Verticales)
|
||||
- Cobro recurrente (mensual/anual)
|
||||
- Integración con Stripe/Conekta
|
||||
- Facturación automática (CFDI México)
|
||||
- Gestión de módulos opcionales
|
||||
|
||||
### Planes
|
||||
|
||||
| Plan | Precio Base | Productos |
|
||||
|------|-------------|-----------|
|
||||
| POS Micro | 100 MXN/mes | pos-micro |
|
||||
| ERP Básico | 300 MXN/mes | erp-basico |
|
||||
| ERP Pro | 500 MXN/mes | erp-basico + módulos |
|
||||
| Vertical | 1,000+ MXN/mes | erp-core + vertical |
|
||||
|
||||
### Módulos Opcionales
|
||||
|
||||
| Módulo | Precio | Disponible en |
|
||||
|--------|--------|---------------|
|
||||
| Contabilidad | +150 MXN/mes | ERP Básico, Verticales |
|
||||
| RRHH | +100 MXN/mes | ERP Básico, Verticales |
|
||||
| CFDI | +100 MXN/mes | Todos |
|
||||
| WhatsApp Bot | Por consumo | Todos |
|
||||
| Usuario extra | +50 MXN/mes | Todos |
|
||||
|
||||
## Portal
|
||||
|
||||
Portal self-service para clientes.
|
||||
|
||||
### Funcionalidades
|
||||
|
||||
- Dashboard de cuenta
|
||||
- Gestión de suscripción
|
||||
- Historial de facturas
|
||||
- Cambio de plan
|
||||
- Soporte/tickets
|
||||
- Configuración de módulos
|
||||
|
||||
## Admin
|
||||
|
||||
Panel de administración para operadores.
|
||||
|
||||
### Funcionalidades
|
||||
|
||||
- Gestión de tenants
|
||||
- Métricas de uso
|
||||
- Facturación manual
|
||||
- Soporte nivel 1
|
||||
- Configuración global
|
||||
- Feature flags por tenant
|
||||
|
||||
## Onboarding
|
||||
|
||||
Flujo de registro y configuración inicial.
|
||||
|
||||
### Flujo
|
||||
|
||||
1. **Registro** - Email o WhatsApp
|
||||
2. **Selección de plan** - POS Micro, ERP Básico, etc.
|
||||
3. **Datos de empresa** - RFC, dirección, giro
|
||||
4. **Configuración inicial** - Productos, usuarios
|
||||
5. **Pago** - Tarjeta o transferencia
|
||||
6. **Activación** - Acceso inmediato
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
runtime: Node.js 20+
|
||||
framework: NestJS
|
||||
language: TypeScript
|
||||
payments: Stripe + Conekta
|
||||
invoicing: PAC CFDI
|
||||
|
||||
frontend:
|
||||
framework: React 18
|
||||
bundler: Vite
|
||||
styling: Tailwind CSS
|
||||
|
||||
database:
|
||||
engine: PostgreSQL 15+
|
||||
schema: saas
|
||||
tables: ~15
|
||||
```
|
||||
|
||||
## Base de Datos
|
||||
|
||||
### Schema: `saas`
|
||||
|
||||
```sql
|
||||
-- Gestión de tenants y suscripciones
|
||||
|
||||
saas.tenants -- Empresas/clientes
|
||||
saas.subscriptions -- Suscripciones activas
|
||||
saas.plans -- Catálogo de planes
|
||||
saas.plan_features -- Features por plan
|
||||
saas.invoices -- Facturas emitidas
|
||||
saas.payments -- Pagos recibidos
|
||||
saas.payment_methods -- Métodos de pago guardados
|
||||
saas.usage_tracking -- Tracking de consumo
|
||||
saas.support_tickets -- Tickets de soporte
|
||||
saas.onboarding_sessions -- Sesiones de registro
|
||||
```
|
||||
|
||||
## Integración con Productos
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ SAAS LAYER │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ billing │ │ portal │ │ admin │ │onboarding│ │
|
||||
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ └───────────┴───────────┴───────────┘ │
|
||||
│ │ │
|
||||
│ API Gateway │
|
||||
│ │ │
|
||||
└─────────────────────────┼───────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ POS Micro│ │ERP Básico│ │Verticales│
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## Variables de Entorno
|
||||
|
||||
```env
|
||||
# Payments
|
||||
STRIPE_SECRET_KEY=sk_xxx
|
||||
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
||||
CONEKTA_API_KEY=key_xxx
|
||||
|
||||
# CFDI
|
||||
PAC_RFC=XXX
|
||||
PAC_API_KEY=xxx
|
||||
PAC_ENVIRONMENT=sandbox|production
|
||||
|
||||
# Database
|
||||
DATABASE_URL=postgresql://...
|
||||
SAAS_SCHEMA=saas
|
||||
|
||||
# General
|
||||
ONBOARDING_URL=https://registro.erp-suite.com
|
||||
PORTAL_URL=https://portal.erp-suite.com
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
### MVP (v1.0)
|
||||
- [ ] Modelo de datos billing
|
||||
- [ ] Integración Stripe básica
|
||||
- [ ] Portal mínimo (ver facturas)
|
||||
- [ ] Onboarding POS Micro
|
||||
- [ ] Admin básico
|
||||
|
||||
### v1.1
|
||||
- [ ] Integración Conekta
|
||||
- [ ] CFDI automático
|
||||
- [ ] Onboarding ERP Básico
|
||||
- [ ] Métricas de uso
|
||||
|
||||
### v1.2
|
||||
- [ ] Portal completo
|
||||
- [ ] Cambio de plan self-service
|
||||
- [ ] Soporte integrado
|
||||
- [ ] Referidos
|
||||
|
||||
---
|
||||
|
||||
*SaaS Layer v1.0*
|
||||
*ERP Suite*
|
||||
122
projects/erp-suite/apps/saas/orchestration/CONTEXTO-SAAS.md
Normal file
122
projects/erp-suite/apps/saas/orchestration/CONTEXTO-SAAS.md
Normal file
@ -0,0 +1,122 @@
|
||||
# Contexto del Proyecto: SaaS Layer
|
||||
|
||||
## Identificación
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **Nombre** | SaaS Layer |
|
||||
| **Tipo** | Infraestructura |
|
||||
| **Nivel** | 2B.1 (Core de Suite) |
|
||||
| **Suite** | erp-suite |
|
||||
| **Ruta Base** | `projects/erp-suite/apps/saas/` |
|
||||
| **Estado** | En Planificación |
|
||||
|
||||
## Descripción
|
||||
|
||||
Capa de servicios compartidos para gestión de multi-tenancy, billing, suscripciones y portal de clientes.
|
||||
|
||||
## Responsabilidades
|
||||
|
||||
1. **Billing** - Cobros, suscripciones, facturación
|
||||
2. **Portal** - Self-service para clientes
|
||||
3. **Admin** - Gestión de tenants
|
||||
4. **Onboarding** - Registro de nuevos clientes
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
runtime: Node.js 20+
|
||||
framework: NestJS
|
||||
language: TypeScript 5.3+
|
||||
|
||||
frontend:
|
||||
framework: React 18
|
||||
bundler: Vite
|
||||
styling: Tailwind CSS
|
||||
|
||||
database:
|
||||
engine: PostgreSQL 15+
|
||||
schema: saas
|
||||
|
||||
integrations:
|
||||
payments: Stripe, Conekta
|
||||
invoicing: PAC CFDI (México)
|
||||
notifications: Email, WhatsApp
|
||||
```
|
||||
|
||||
## Variables del Proyecto
|
||||
|
||||
```yaml
|
||||
PROJECT_NAME: saas-layer
|
||||
PROJECT_CODE: SAAS
|
||||
SUITE: erp-suite
|
||||
|
||||
# Paths
|
||||
BILLING_ROOT: apps/saas/billing
|
||||
PORTAL_ROOT: apps/saas/portal
|
||||
ADMIN_ROOT: apps/saas/admin
|
||||
ONBOARDING_ROOT: apps/saas/onboarding
|
||||
|
||||
# Database
|
||||
DB_SCHEMA: saas
|
||||
MAX_TABLES: 15
|
||||
```
|
||||
|
||||
## Módulos
|
||||
|
||||
| Módulo | Descripción | Prioridad |
|
||||
|--------|-------------|-----------|
|
||||
| billing | Suscripciones y cobros | P0 |
|
||||
| portal | Portal de clientes | P1 |
|
||||
| admin | Panel de administración | P1 |
|
||||
| onboarding | Registro de clientes | P0 |
|
||||
|
||||
## Planes de Suscripción
|
||||
|
||||
| ID | Plan | Precio | Target |
|
||||
|----|------|--------|--------|
|
||||
| pos-micro | POS Micro | 100 MXN/mes | Mercado informal |
|
||||
| erp-basic | ERP Básico | 300 MXN/mes | PyMEs |
|
||||
| erp-pro | ERP Pro | 500 MXN/mes | PyMEs+ |
|
||||
| vertical-x | Vertical | 1,000+ MXN/mes | Industrias específicas |
|
||||
|
||||
## Dependencias
|
||||
|
||||
### Productos que dependen de SaaS Layer
|
||||
|
||||
- `products/pos-micro` - Billing, onboarding
|
||||
- `products/erp-basico` - Billing, portal, onboarding
|
||||
- `verticales/*` - Billing, portal, admin
|
||||
|
||||
### Servicios externos
|
||||
|
||||
- Stripe - Pagos internacionales
|
||||
- Conekta - Pagos México
|
||||
- PAC CFDI - Facturación electrónica
|
||||
- SendGrid - Email transaccional
|
||||
- WhatsApp Business API - Notificaciones
|
||||
|
||||
## Roadmap
|
||||
|
||||
### Sprint 1: Billing MVP
|
||||
- [ ] Modelo de datos
|
||||
- [ ] Integración Stripe básica
|
||||
- [ ] Webhook de pagos
|
||||
- [ ] API de suscripciones
|
||||
|
||||
### Sprint 2: Onboarding
|
||||
- [ ] Flujo de registro
|
||||
- [ ] Selección de plan
|
||||
- [ ] Configuración inicial
|
||||
- [ ] Activación automática
|
||||
|
||||
### Sprint 3: Portal
|
||||
- [ ] Dashboard cliente
|
||||
- [ ] Ver facturas
|
||||
- [ ] Cambiar plan
|
||||
- [ ] Soporte básico
|
||||
|
||||
---
|
||||
|
||||
*Última actualización: 2025-12-08*
|
||||
@ -0,0 +1,213 @@
|
||||
# Herencia de Base de Datos - ERP Core -> Clínicas
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Clínicas
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## RESUMEN
|
||||
|
||||
La vertical de Clínicas hereda los schemas base del ERP Core y extiende con schemas específicos del dominio de gestión médica y expediente clínico.
|
||||
|
||||
**Ubicación DDL Core:** `apps/erp-core/database/ddl/`
|
||||
|
||||
---
|
||||
|
||||
## ARQUITECTURA DE HERENCIA
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ERP CORE (Base) │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ auth │ │ core │ │financial│ │inventory│ │ hr │ │
|
||||
│ │ 26 tbl │ │ 12 tbl │ │ 15 tbl │ │ 15 tbl │ │ 6 tbl │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ sales │ │analytics│ │ system │ │ crm │ │
|
||||
│ │ 6 tbl │ │ 5 tbl │ │ 10 tbl │ │ 5 tbl │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ TOTAL: ~100 tablas heredadas │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HEREDA
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ CLÍNICAS (Extensiones) │
|
||||
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
||||
│ │ medical │ │ appointments │ │ patients │ │
|
||||
│ │ (expediente) │ │ (citas) │ │ (pacientes) │ │
|
||||
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
||||
│ EXTENSIONES: ~35 tablas (planificadas) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SCHEMAS HEREDADOS DEL CORE
|
||||
|
||||
| Schema | Tablas | Uso en Clínicas |
|
||||
|--------|--------|-----------------|
|
||||
| `auth` | 26 | Autenticación, usuarios médicos |
|
||||
| `core` | 12 | Partners (pacientes), catálogos |
|
||||
| `financial` | 15 | Facturas de servicios médicos |
|
||||
| `inventory` | 15 | Medicamentos, insumos |
|
||||
| `hr` | 6 | Personal médico |
|
||||
| `sales` | 6 | Servicios médicos |
|
||||
| `crm` | 5 | Seguimiento de pacientes |
|
||||
| `analytics` | 5 | Estadísticas médicas |
|
||||
| `system` | 10 | Recordatorios, notificaciones |
|
||||
|
||||
**Total heredado:** ~100 tablas
|
||||
|
||||
---
|
||||
|
||||
## SCHEMAS ESPECÍFICOS DE CLÍNICAS (Planificados)
|
||||
|
||||
### 1. Schema `patients` (estimado 10+ tablas)
|
||||
|
||||
**Propósito:** Gestión de pacientes
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
patients.patients -- Pacientes (extiende core.partners)
|
||||
patients.patient_contacts -- Contactos de emergencia
|
||||
patients.insurance_policies -- Pólizas de seguro
|
||||
patients.medical_history -- Antecedentes médicos
|
||||
patients.allergies -- Alergias
|
||||
patients.family_history -- Antecedentes familiares
|
||||
```
|
||||
|
||||
### 2. Schema `medical` (estimado 15+ tablas)
|
||||
|
||||
**Propósito:** Expediente clínico electrónico
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
medical.consultations -- Consultas médicas
|
||||
medical.diagnoses -- Diagnósticos (CIE-10)
|
||||
medical.prescriptions -- Recetas médicas
|
||||
medical.prescription_lines -- Medicamentos recetados
|
||||
medical.vital_signs -- Signos vitales
|
||||
medical.lab_results -- Resultados de laboratorio
|
||||
medical.imaging_studies -- Estudios de imagen
|
||||
medical.clinical_notes -- Notas clínicas
|
||||
medical.treatments -- Tratamientos
|
||||
```
|
||||
|
||||
### 3. Schema `appointments` (estimado 10+ tablas)
|
||||
|
||||
**Propósito:** Gestión de citas
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
appointments.doctors -- Médicos
|
||||
appointments.specialties -- Especialidades
|
||||
appointments.doctor_schedules -- Horarios de médicos
|
||||
appointments.consulting_rooms -- Consultorios
|
||||
appointments.appointments -- Citas
|
||||
appointments.appointment_types -- Tipos de cita
|
||||
appointments.reminders -- Recordatorios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SPECS DEL CORE APLICABLES
|
||||
|
||||
**Documento detallado:** `orchestration/00-guidelines/HERENCIA-SPECS-CORE.md`
|
||||
|
||||
### SPECS Obligatorias
|
||||
|
||||
| Spec Core | Aplicación en Clínicas | SP | Estado |
|
||||
|-----------|----------------------|----:|--------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | Foliado de expedientes y citas | 8 | PENDIENTE |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | Control de acceso a expedientes | 31 | PENDIENTE |
|
||||
| SPEC-INTEGRACION-CALENDAR | Agenda de citas médicas | 8 | PENDIENTE |
|
||||
| SPEC-RRHH-EVALUACIONES-SKILLS | Credenciales médicas | 26 | PENDIENTE |
|
||||
| SPEC-MAIL-THREAD-TRACKING | Historial de comunicación | 13 | PENDIENTE |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | Wizards de receta y referencia | 8 | PENDIENTE |
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | Firma de expedientes clínicos | 13 | PENDIENTE |
|
||||
| SPEC-TWO-FACTOR-AUTHENTICATION | Seguridad de acceso | 13 | PENDIENTE |
|
||||
| SPEC-OAUTH2-SOCIAL-LOGIN | Portal de pacientes | 8 | PENDIENTE |
|
||||
|
||||
### SPECS Opcionales
|
||||
|
||||
| Spec Core | Decisión | Razón |
|
||||
|-----------|----------|-------|
|
||||
| SPEC-VALORACION-INVENTARIO | EVALUAR | Solo si hay farmacia interna |
|
||||
| SPEC-PRICING-RULES | EVALUAR | Para paquetes de servicios |
|
||||
| SPEC-TAREAS-RECURRENTES | EVALUAR | Para citas periódicas |
|
||||
|
||||
### SPECS No Aplican
|
||||
|
||||
| Spec Core | Razón |
|
||||
|-----------|-------|
|
||||
| SPEC-PORTAL-PROVEEDORES | No hay compras complejas |
|
||||
| SPEC-BLANKET-ORDERS | No aplica en servicios médicos |
|
||||
| SPEC-INVENTARIOS-CICLICOS | Solo si hay farmacia grande |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | No hay proyectos de este tipo |
|
||||
|
||||
### Cumplimiento Normativo
|
||||
|
||||
| Norma | Descripción | SPECS Relacionadas |
|
||||
|-------|-------------|-------------------|
|
||||
| NOM-024-SSA3-2012 | Expediente clínico electrónico | SPEC-SEGURIDAD, SPEC-MAIL-THREAD |
|
||||
| LFPDPPP | Protección de datos personales | SPEC-SEGURIDAD, SPEC-2FA |
|
||||
| NOM-004-SSA3-2012 | Expediente clínico | SPEC-FIRMA-ELECTRONICA |
|
||||
|
||||
---
|
||||
|
||||
## CUMPLIMIENTO NORMATIVO
|
||||
|
||||
Este sistema debe cumplir con:
|
||||
|
||||
| Norma | Descripción | Impacto |
|
||||
|-------|-------------|---------|
|
||||
| NOM-024-SSA3-2012 | Expediente clínico electrónico | Estructura de datos |
|
||||
| LFPDPPP | Protección de datos personales | Seguridad y acceso |
|
||||
| NOM-004-SSA3-2012 | Expediente clínico | Contenido mínimo |
|
||||
|
||||
---
|
||||
|
||||
## ORDEN DE EJECUCIÓN DDL (Futuro)
|
||||
|
||||
```bash
|
||||
# PASO 1: Cargar ERP Core (base)
|
||||
cd apps/erp-core/database
|
||||
./scripts/reset-database.sh --force
|
||||
|
||||
# PASO 2: Cargar extensiones de Clínicas
|
||||
cd apps/verticales/clinicas/database
|
||||
psql $DATABASE_URL -f init/00-extensions.sql
|
||||
psql $DATABASE_URL -f init/01-create-schemas.sql
|
||||
psql $DATABASE_URL -f init/02-patients-tables.sql
|
||||
psql $DATABASE_URL -f init/03-medical-tables.sql
|
||||
psql $DATABASE_URL -f init/04-appointments-tables.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MAPEO DE NOMENCLATURA
|
||||
|
||||
| Core | Clínicas |
|
||||
|------|----------|
|
||||
| `core.partners` | Pacientes base |
|
||||
| `hr.employees` | Personal médico |
|
||||
| `inventory.products` | Medicamentos, insumos |
|
||||
| `sales.sale_orders` | Servicios médicos |
|
||||
| `financial.invoices` | Facturas de consultas |
|
||||
|
||||
---
|
||||
|
||||
## REFERENCIAS
|
||||
|
||||
- ERP Core DDL: `apps/erp-core/database/ddl/`
|
||||
- ERP Core README: `apps/erp-core/database/README.md`
|
||||
- Directivas: `orchestration/directivas/`
|
||||
- Inventarios: `orchestration/inventarios/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,199 @@
|
||||
# Herencia de SPECS del Core - Clínicas
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Clínicas (CL)
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| SPECS Aplicables | 24/30 |
|
||||
| SPECS Obligatorias | 20 |
|
||||
| SPECS Opcionales | 4 |
|
||||
| SPECS No Aplican | 6 |
|
||||
| Estado Implementación | 0% |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Obligatorias (Deben Implementarse)
|
||||
|
||||
### P0 - Críticas
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | ir.sequence | 8 | PENDIENTE | CL-001, CL-002, CL-005 |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | API Keys + ACL | 31 | PENDIENTE | CL-001, CL-011 |
|
||||
| SPEC-REPORTES-FINANCIEROS | Balance/P&L SAT | 13 | PENDIENTE | CL-008, CL-009 |
|
||||
| SPEC-NOMINA-BASICA | hr_payroll | 21 | PENDIENTE | CL-001 |
|
||||
| SPEC-GASTOS-EMPLEADOS | hr_expense | 13 | PENDIENTE | CL-001 |
|
||||
| SPEC-SCHEDULER-REPORTES | ir.cron + mail | 8 | PENDIENTE | CL-009 |
|
||||
| SPEC-INTEGRACION-CALENDAR | calendar integration | 8 | PENDIENTE | CL-003 |
|
||||
|
||||
### P1 - Complementarias
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-CONTABILIDAD-ANALITICA | Centros de costo | 21 | PENDIENTE | CL-008, CL-009 |
|
||||
| SPEC-CONCILIACION-BANCARIA | Conciliación | 21 | PENDIENTE | CL-008 |
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | e.firma | 13 | PENDIENTE | CL-011 |
|
||||
| SPEC-TWO-FACTOR-AUTHENTICATION | 2FA | 13 | PENDIENTE | CL-001 |
|
||||
| SPEC-TRAZABILIDAD-LOTES-SERIES | Lotes/Series | 13 | PENDIENTE | CL-007 |
|
||||
| SPEC-OAUTH2-SOCIAL-LOGIN | OAuth2 | 8 | PENDIENTE | CL-002, CL-010 |
|
||||
| SPEC-IMPUESTOS-AVANZADOS | IVA, ISR | 8 | PENDIENTE | CL-008 |
|
||||
| SPEC-PLANTILLAS-CUENTAS | Plan contable | 8 | PENDIENTE | CL-008 |
|
||||
| SPEC-TASAS-CAMBIO-AUTOMATICAS | Tipos cambio | 5 | PENDIENTE | CL-008 |
|
||||
| SPEC-ALERTAS-PRESUPUESTO | Alertas | 8 | PENDIENTE | CL-008 |
|
||||
| SPEC-RRHH-EVALUACIONES-SKILLS | Evaluaciones | 26 | PENDIENTE | CL-001 |
|
||||
| SPEC-LOCALIZACION-PAISES | Localización | 13 | PENDIENTE | CL-001, CL-008 |
|
||||
|
||||
### Patrones Técnicos
|
||||
|
||||
| SPEC | Patrón | SP | Estado | Aplicación |
|
||||
|------|--------|----:|--------|------------|
|
||||
| SPEC-MAIL-THREAD-TRACKING | mail.thread | 13 | PENDIENTE | Expedientes, Citas, Comunicación |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | TransientModel | 8 | PENDIENTE | Wizards de receta, referencia |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Opcionales
|
||||
|
||||
| SPEC | Descripción | SP | Decisión | Razón |
|
||||
|------|-------------|----:|----------|-------|
|
||||
| SPEC-VALORACION-INVENTARIO | FIFO/AVCO | 21 | EVALUAR | Solo para farmacia interna |
|
||||
| SPEC-PRICING-RULES | Reglas precio | 8 | EVALUAR | Para paquetes de servicios |
|
||||
| SPEC-TAREAS-RECURRENTES | Recurrencia | 13 | EVALUAR | Para citas periódicas |
|
||||
| SPEC-PRESUPUESTOS-REVISIONES | Aprobación | 8 | EVALUAR | Para tratamientos largos |
|
||||
|
||||
---
|
||||
|
||||
## SPECS No Aplicables
|
||||
|
||||
| SPEC | Razón |
|
||||
|------|-------|
|
||||
| SPEC-PORTAL-PROVEEDORES | No hay compras complejas |
|
||||
| SPEC-BLANKET-ORDERS | No aplica en servicios médicos |
|
||||
| SPEC-INVENTARIOS-CICLICOS | Solo si hay farmacia grande |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | No hay proyectos de este tipo |
|
||||
| SPEC-CONSOLIDACION-FINANCIERA | Generalmente una clínica |
|
||||
|
||||
---
|
||||
|
||||
## Adaptaciones Requeridas
|
||||
|
||||
### Mapeo de Conceptos Core → Clínicas
|
||||
|
||||
| Concepto Core | Concepto Clínicas |
|
||||
|---------------|-------------------|
|
||||
| `core.partners` | Pacientes |
|
||||
| `sales.sale_orders` | Consultas/Servicios |
|
||||
| `inventory.products` | Medicamentos, servicios médicos |
|
||||
| `hr.employees` | Personal médico |
|
||||
| `calendar.events` | Citas médicas |
|
||||
| `financial.invoices` | Facturas de consulta |
|
||||
|
||||
### Extensiones de Entidad
|
||||
|
||||
```sql
|
||||
-- Pacientes (extiende partners)
|
||||
patients.patients (
|
||||
partner_id → core.partners,
|
||||
numero_expediente VARCHAR UNIQUE,
|
||||
fecha_nacimiento DATE,
|
||||
sexo ENUM('M', 'F'),
|
||||
tipo_sangre VARCHAR(5),
|
||||
alergias TEXT[],
|
||||
antecedentes JSONB,
|
||||
seguro_medico_id → insurance_policies
|
||||
)
|
||||
|
||||
-- Expediente clínico
|
||||
medical.clinical_records (
|
||||
id UUID,
|
||||
patient_id → patients,
|
||||
fecha TIMESTAMPTZ,
|
||||
tipo ENUM('consulta', 'urgencia', 'hospitalizacion'),
|
||||
motivo_consulta TEXT,
|
||||
diagnostico TEXT,
|
||||
tratamiento TEXT,
|
||||
medico_id → hr.employees,
|
||||
signos_vitales JSONB
|
||||
)
|
||||
|
||||
-- Citas médicas
|
||||
appointments.appointments (
|
||||
id UUID,
|
||||
patient_id → patients,
|
||||
doctor_id → hr.employees,
|
||||
specialty_id → specialties,
|
||||
fecha_hora TIMESTAMPTZ,
|
||||
duracion_minutos INTEGER,
|
||||
estado ENUM('programada', 'confirmada', 'en_progreso', 'completada', 'cancelada'),
|
||||
notas TEXT
|
||||
)
|
||||
|
||||
-- Recetas médicas
|
||||
medical.prescriptions (
|
||||
id UUID,
|
||||
clinical_record_id → clinical_records,
|
||||
fecha TIMESTAMPTZ,
|
||||
vigencia_dias INTEGER,
|
||||
firma_electronica BYTEA,
|
||||
productos JSONB
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cumplimiento Normativo
|
||||
|
||||
Esta vertical debe cumplir con normas específicas:
|
||||
|
||||
| Norma | Descripción | SPECS Relacionadas |
|
||||
|-------|-------------|-------------------|
|
||||
| NOM-024-SSA3-2012 | Expediente clínico electrónico | SPEC-SEGURIDAD, SPEC-MAIL-THREAD |
|
||||
| LFPDPPP | Protección de datos personales | SPEC-SEGURIDAD, SPEC-2FA |
|
||||
| NOM-004-SSA3-2012 | Expediente clínico | SPEC-FIRMA-ELECTRONICA |
|
||||
|
||||
---
|
||||
|
||||
## Plan de Implementación
|
||||
|
||||
### Fase 1: Fundamentos (SP: 60)
|
||||
1. SPEC-SISTEMA-SECUENCIAS
|
||||
2. SPEC-SEGURIDAD-API-KEYS-PERMISOS
|
||||
3. SPEC-TWO-FACTOR-AUTHENTICATION
|
||||
4. SPEC-OAUTH2-SOCIAL-LOGIN
|
||||
|
||||
### Fase 2: Agenda y Comunicación (SP: 34)
|
||||
5. SPEC-INTEGRACION-CALENDAR
|
||||
6. SPEC-MAIL-THREAD-TRACKING
|
||||
7. SPEC-WIZARD-TRANSIENT-MODEL
|
||||
|
||||
### Fase 3: Expediente y Cumplimiento (SP: 39)
|
||||
8. SPEC-FIRMA-ELECTRONICA-NOM151
|
||||
9. SPEC-RRHH-EVALUACIONES-SKILLS
|
||||
|
||||
### Fase 4: Financiero (SP: 65)
|
||||
10. SPEC-REPORTES-FINANCIEROS
|
||||
11. SPEC-CONTABILIDAD-ANALITICA
|
||||
12. SPEC-CONCILIACION-BANCARIA
|
||||
13. SPEC-IMPUESTOS-AVANZADOS
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- Documento Core: `erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md`
|
||||
- SPECS del Core: `erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/`
|
||||
- Herencia DB: `database/HERENCIA-ERP-CORE.md`
|
||||
- Directivas: `orchestration/directivas/`
|
||||
- Normatividad: NOM-024-SSA3-2012, LFPDPPP, NOM-004-SSA3-2012
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia de SPECS oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,103 @@
|
||||
# Inventarios - ERP Clínicas
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Nivel SIMCO:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Descripción
|
||||
|
||||
Este directorio contiene los inventarios YAML que sirven como **Single Source of Truth (SSOT)** para el proyecto ERP Clínicas. Estos archivos son la referencia canónica para métricas, trazabilidad y componentes del sistema.
|
||||
|
||||
---
|
||||
|
||||
## Archivos de Inventario
|
||||
|
||||
| Archivo | Descripción | Estado |
|
||||
|---------|-------------|--------|
|
||||
| [MASTER_INVENTORY.yml](./MASTER_INVENTORY.yml) | Inventario maestro con métricas globales | Completo |
|
||||
| [DATABASE_INVENTORY.yml](./DATABASE_INVENTORY.yml) | Inventario de objetos de base de datos | Planificado |
|
||||
| [BACKEND_INVENTORY.yml](./BACKEND_INVENTORY.yml) | Inventario de componentes backend | Planificado |
|
||||
| [FRONTEND_INVENTORY.yml](./FRONTEND_INVENTORY.yml) | Inventario de componentes frontend | Planificado |
|
||||
| [TRACEABILITY_MATRIX.yml](./TRACEABILITY_MATRIX.yml) | Matriz de trazabilidad RF->ET->US | Completo |
|
||||
| [DEPENDENCY_GRAPH.yml](./DEPENDENCY_GRAPH.yml) | Grafo de dependencias entre módulos | Completo |
|
||||
|
||||
---
|
||||
|
||||
## Herencia del Core
|
||||
|
||||
Este proyecto hereda del **ERP Core** (nivel 2B.1):
|
||||
|
||||
| Aspecto | Heredado | Específico |
|
||||
|---------|----------|------------|
|
||||
| **Tablas DB** | ~100 | Planificado |
|
||||
| **Schemas** | 8+ | Planificado |
|
||||
| **Specs** | 3 | - |
|
||||
|
||||
### Specs Heredadas
|
||||
|
||||
1. SPEC-RRHH-EVALUACIONES-SKILLS.md
|
||||
2. SPEC-INTEGRACION-CALENDAR.md
|
||||
3. SPEC-MAIL-THREAD-TRACKING.md
|
||||
|
||||
---
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
### Métricas del Proyecto
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| **Módulos** | 5 (CL-001 a CL-005) |
|
||||
| **Estado** | PLANIFICACION_COMPLETA |
|
||||
| **Completitud** | 15% |
|
||||
|
||||
### Dominio del Negocio
|
||||
|
||||
- Expediente clínico electrónico
|
||||
- Gestión de citas médicas
|
||||
- Control de consultorios
|
||||
- Facturación de servicios médicos
|
||||
|
||||
---
|
||||
|
||||
## Directivas Específicas
|
||||
|
||||
1. [DIRECTIVA-EXPEDIENTE-CLINICO.md](../directivas/DIRECTIVA-EXPEDIENTE-CLINICO.md)
|
||||
2. [DIRECTIVA-GESTION-CITAS.md](../directivas/DIRECTIVA-GESTION-CITAS.md)
|
||||
|
||||
---
|
||||
|
||||
## Configuración de Puertos (Planificado)
|
||||
|
||||
| Servicio | Puerto |
|
||||
|----------|--------|
|
||||
| Backend API | 3500 |
|
||||
| Frontend Web | 5179 |
|
||||
| Patient Portal | 5180 |
|
||||
|
||||
---
|
||||
|
||||
## Cumplimiento Normativo
|
||||
|
||||
Este proyecto debe cumplir con:
|
||||
- NOM-024-SSA3-2012 (Expediente clínico electrónico)
|
||||
- Ley de Protección de Datos Personales en Posesión de Particulares
|
||||
|
||||
---
|
||||
|
||||
## Alineación con ERP Core
|
||||
|
||||
Estos inventarios siguen la misma estructura que:
|
||||
- `/erp-core/orchestration/inventarios/` (proyecto padre)
|
||||
|
||||
### Referencias
|
||||
|
||||
- Suite Master: `orchestration/inventarios/SUITE_MASTER_INVENTORY.yml`
|
||||
- Core: `apps/erp-core/orchestration/inventarios/`
|
||||
- Status Global: `orchestration/inventarios/STATUS.yml`
|
||||
|
||||
---
|
||||
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Fraccionamiento Controller
|
||||
* API endpoints para gestión de fraccionamientos/obras
|
||||
*
|
||||
* @module Construction
|
||||
* @prefix /api/v1/fraccionamientos
|
||||
*/
|
||||
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
FraccionamientoService,
|
||||
CreateFraccionamientoDto,
|
||||
UpdateFraccionamientoDto
|
||||
} from '../services/fraccionamiento.service';
|
||||
|
||||
const router = Router();
|
||||
const fraccionamientoService = new FraccionamientoService();
|
||||
|
||||
/**
|
||||
* GET /api/v1/fraccionamientos
|
||||
* Lista todos los fraccionamientos del tenant
|
||||
*/
|
||||
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const { proyectoId, estado } = req.query;
|
||||
|
||||
const fraccionamientos = await fraccionamientoService.findAll({
|
||||
tenantId,
|
||||
proyectoId: proyectoId as string,
|
||||
estado: estado as any,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: fraccionamientos,
|
||||
count: fraccionamientos.length,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/v1/fraccionamientos/:id
|
||||
* Obtiene un fraccionamiento por ID
|
||||
*/
|
||||
router.get('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const fraccionamiento = await fraccionamientoService.findById(req.params.id, tenantId);
|
||||
if (!fraccionamiento) {
|
||||
return res.status(404).json({ error: 'Fraccionamiento no encontrado' });
|
||||
}
|
||||
|
||||
return res.json({ success: true, data: fraccionamiento });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/v1/fraccionamientos
|
||||
* Crea un nuevo fraccionamiento
|
||||
*/
|
||||
router.post('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const data: CreateFraccionamientoDto = {
|
||||
...req.body,
|
||||
tenantId,
|
||||
createdById: (req as any).user?.id,
|
||||
};
|
||||
|
||||
// Validate required fields
|
||||
if (!data.codigo || !data.nombre || !data.proyectoId) {
|
||||
return res.status(400).json({
|
||||
error: 'codigo, nombre y proyectoId son requeridos'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if codigo already exists
|
||||
const existing = await fraccionamientoService.findByCodigo(data.codigo, tenantId);
|
||||
if (existing) {
|
||||
return res.status(409).json({ error: 'Ya existe un fraccionamiento con ese código' });
|
||||
}
|
||||
|
||||
const fraccionamiento = await fraccionamientoService.create(data);
|
||||
return res.status(201).json({ success: true, data: fraccionamiento });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PATCH /api/v1/fraccionamientos/:id
|
||||
* Actualiza un fraccionamiento
|
||||
*/
|
||||
router.patch('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const data: UpdateFraccionamientoDto = req.body;
|
||||
const fraccionamiento = await fraccionamientoService.update(
|
||||
req.params.id,
|
||||
tenantId,
|
||||
data
|
||||
);
|
||||
|
||||
if (!fraccionamiento) {
|
||||
return res.status(404).json({ error: 'Fraccionamiento no encontrado' });
|
||||
}
|
||||
|
||||
return res.json({ success: true, data: fraccionamiento });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/v1/fraccionamientos/:id
|
||||
* Elimina un fraccionamiento
|
||||
*/
|
||||
router.delete('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const deleted = await fraccionamientoService.delete(req.params.id, tenantId);
|
||||
if (!deleted) {
|
||||
return res.status(404).json({ error: 'Fraccionamiento no encontrado' });
|
||||
}
|
||||
|
||||
return res.json({ success: true, message: 'Fraccionamiento eliminado' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Construction Controllers Index
|
||||
* @module Construction
|
||||
*/
|
||||
|
||||
export { default as proyectoController } from './proyecto.controller';
|
||||
export { default as fraccionamientoController } from './fraccionamiento.controller';
|
||||
@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Proyecto Controller
|
||||
* API endpoints para gestión de proyectos
|
||||
*
|
||||
* @module Construction
|
||||
* @prefix /api/v1/proyectos
|
||||
*/
|
||||
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { ProyectoService, CreateProyectoDto, UpdateProyectoDto } from '../services/proyecto.service';
|
||||
|
||||
const router = Router();
|
||||
const proyectoService = new ProyectoService();
|
||||
|
||||
/**
|
||||
* GET /api/v1/proyectos
|
||||
* Lista todos los proyectos del tenant
|
||||
*/
|
||||
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const { estadoProyecto, ciudad } = req.query;
|
||||
|
||||
const proyectos = await proyectoService.findAll({
|
||||
tenantId,
|
||||
estadoProyecto: estadoProyecto as any,
|
||||
ciudad: ciudad as string,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: proyectos,
|
||||
count: proyectos.length,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/v1/proyectos/statistics
|
||||
* Estadísticas de proyectos
|
||||
*/
|
||||
router.get('/statistics', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const stats = await proyectoService.getStatistics(tenantId);
|
||||
return res.json({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/v1/proyectos/:id
|
||||
* Obtiene un proyecto por ID
|
||||
*/
|
||||
router.get('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const proyecto = await proyectoService.findById(req.params.id, tenantId);
|
||||
if (!proyecto) {
|
||||
return res.status(404).json({ error: 'Proyecto no encontrado' });
|
||||
}
|
||||
|
||||
return res.json({ success: true, data: proyecto });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/v1/proyectos
|
||||
* Crea un nuevo proyecto
|
||||
*/
|
||||
router.post('/', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const data: CreateProyectoDto = {
|
||||
...req.body,
|
||||
tenantId,
|
||||
createdById: (req as any).user?.id,
|
||||
};
|
||||
|
||||
// Validate required fields
|
||||
if (!data.codigo || !data.nombre) {
|
||||
return res.status(400).json({ error: 'codigo y nombre son requeridos' });
|
||||
}
|
||||
|
||||
// Check if codigo already exists
|
||||
const existing = await proyectoService.findByCodigo(data.codigo, tenantId);
|
||||
if (existing) {
|
||||
return res.status(409).json({ error: 'Ya existe un proyecto con ese código' });
|
||||
}
|
||||
|
||||
const proyecto = await proyectoService.create(data);
|
||||
return res.status(201).json({ success: true, data: proyecto });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PATCH /api/v1/proyectos/:id
|
||||
* Actualiza un proyecto
|
||||
*/
|
||||
router.patch('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const data: UpdateProyectoDto = req.body;
|
||||
const proyecto = await proyectoService.update(req.params.id, tenantId, data);
|
||||
|
||||
if (!proyecto) {
|
||||
return res.status(404).json({ error: 'Proyecto no encontrado' });
|
||||
}
|
||||
|
||||
return res.json({ success: true, data: proyecto });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/v1/proyectos/:id
|
||||
* Elimina un proyecto
|
||||
*/
|
||||
router.delete('/:id', async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tenantId = req.headers['x-tenant-id'] as string;
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({ error: 'X-Tenant-Id header required' });
|
||||
}
|
||||
|
||||
const deleted = await proyectoService.delete(req.params.id, tenantId);
|
||||
if (!deleted) {
|
||||
return res.status(404).json({ error: 'Proyecto no encontrado' });
|
||||
}
|
||||
|
||||
return res.json({ success: true, message: 'Proyecto eliminado' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Fraccionamiento Service
|
||||
* Servicio para gestión de fraccionamientos/obras
|
||||
*
|
||||
* @module Construction
|
||||
*/
|
||||
|
||||
import { Repository, FindOptionsWhere } from 'typeorm';
|
||||
import { AppDataSource } from '../../../shared/database/typeorm.config';
|
||||
import { Fraccionamiento, EstadoFraccionamiento } from '../entities/fraccionamiento.entity';
|
||||
|
||||
export interface CreateFraccionamientoDto {
|
||||
tenantId: string;
|
||||
proyectoId: string;
|
||||
codigo: string;
|
||||
nombre: string;
|
||||
descripcion?: string;
|
||||
direccion?: string;
|
||||
ubicacionGeo?: string;
|
||||
fechaInicio?: Date;
|
||||
fechaFinEstimada?: Date;
|
||||
createdById?: string;
|
||||
}
|
||||
|
||||
export interface UpdateFraccionamientoDto {
|
||||
nombre?: string;
|
||||
descripcion?: string;
|
||||
direccion?: string;
|
||||
ubicacionGeo?: string;
|
||||
fechaInicio?: Date;
|
||||
fechaFinEstimada?: Date;
|
||||
estado?: EstadoFraccionamiento;
|
||||
}
|
||||
|
||||
export interface FraccionamientoFilters {
|
||||
tenantId: string;
|
||||
proyectoId?: string;
|
||||
estado?: EstadoFraccionamiento;
|
||||
}
|
||||
|
||||
export class FraccionamientoService {
|
||||
private repository: Repository<Fraccionamiento>;
|
||||
|
||||
constructor() {
|
||||
this.repository = AppDataSource.getRepository(Fraccionamiento);
|
||||
}
|
||||
|
||||
async findAll(filters: FraccionamientoFilters): Promise<Fraccionamiento[]> {
|
||||
const where: FindOptionsWhere<Fraccionamiento> = {
|
||||
tenantId: filters.tenantId,
|
||||
};
|
||||
|
||||
if (filters.proyectoId) {
|
||||
where.proyectoId = filters.proyectoId;
|
||||
}
|
||||
|
||||
if (filters.estado) {
|
||||
where.estado = filters.estado;
|
||||
}
|
||||
|
||||
return this.repository.find({
|
||||
where,
|
||||
relations: ['proyecto'],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id: string, tenantId: string): Promise<Fraccionamiento | null> {
|
||||
return this.repository.findOne({
|
||||
where: { id, tenantId },
|
||||
relations: ['proyecto', 'createdBy'],
|
||||
});
|
||||
}
|
||||
|
||||
async findByCodigo(codigo: string, tenantId: string): Promise<Fraccionamiento | null> {
|
||||
return this.repository.findOne({
|
||||
where: { codigo, tenantId },
|
||||
});
|
||||
}
|
||||
|
||||
async findByProyecto(proyectoId: string, tenantId: string): Promise<Fraccionamiento[]> {
|
||||
return this.repository.find({
|
||||
where: { proyectoId, tenantId },
|
||||
order: { codigo: 'ASC' },
|
||||
});
|
||||
}
|
||||
|
||||
async create(data: CreateFraccionamientoDto): Promise<Fraccionamiento> {
|
||||
const fraccionamiento = this.repository.create(data);
|
||||
return this.repository.save(fraccionamiento);
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
tenantId: string,
|
||||
data: UpdateFraccionamientoDto
|
||||
): Promise<Fraccionamiento | null> {
|
||||
const fraccionamiento = await this.findById(id, tenantId);
|
||||
if (!fraccionamiento) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object.assign(fraccionamiento, data);
|
||||
return this.repository.save(fraccionamiento);
|
||||
}
|
||||
|
||||
async delete(id: string, tenantId: string): Promise<boolean> {
|
||||
const result = await this.repository.delete({ id, tenantId });
|
||||
return result.affected ? result.affected > 0 : false;
|
||||
}
|
||||
|
||||
async countByProyecto(proyectoId: string, tenantId: string): Promise<number> {
|
||||
return this.repository.count({
|
||||
where: { proyectoId, tenantId },
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Construction Services Index
|
||||
* @module Construction
|
||||
*/
|
||||
|
||||
export * from './proyecto.service';
|
||||
export * from './fraccionamiento.service';
|
||||
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Proyecto Service
|
||||
* Servicio para gestión de proyectos de construcción
|
||||
*
|
||||
* @module Construction
|
||||
*/
|
||||
|
||||
import { Repository, FindOptionsWhere } from 'typeorm';
|
||||
import { AppDataSource } from '../../../shared/database/typeorm.config';
|
||||
import { Proyecto, EstadoProyecto } from '../entities/proyecto.entity';
|
||||
|
||||
export interface CreateProyectoDto {
|
||||
tenantId: string;
|
||||
codigo: string;
|
||||
nombre: string;
|
||||
descripcion?: string;
|
||||
direccion?: string;
|
||||
ciudad?: string;
|
||||
estado?: string;
|
||||
fechaInicio?: Date;
|
||||
fechaFinEstimada?: Date;
|
||||
createdById?: string;
|
||||
}
|
||||
|
||||
export interface UpdateProyectoDto {
|
||||
nombre?: string;
|
||||
descripcion?: string;
|
||||
direccion?: string;
|
||||
ciudad?: string;
|
||||
estado?: string;
|
||||
fechaInicio?: Date;
|
||||
fechaFinEstimada?: Date;
|
||||
estadoProyecto?: EstadoProyecto;
|
||||
}
|
||||
|
||||
export interface ProyectoFilters {
|
||||
tenantId: string;
|
||||
estadoProyecto?: EstadoProyecto;
|
||||
ciudad?: string;
|
||||
}
|
||||
|
||||
export class ProyectoService {
|
||||
private repository: Repository<Proyecto>;
|
||||
|
||||
constructor() {
|
||||
this.repository = AppDataSource.getRepository(Proyecto);
|
||||
}
|
||||
|
||||
async findAll(filters: ProyectoFilters): Promise<Proyecto[]> {
|
||||
const where: FindOptionsWhere<Proyecto> = {
|
||||
tenantId: filters.tenantId,
|
||||
};
|
||||
|
||||
if (filters.estadoProyecto) {
|
||||
where.estadoProyecto = filters.estadoProyecto;
|
||||
}
|
||||
|
||||
if (filters.ciudad) {
|
||||
where.ciudad = filters.ciudad;
|
||||
}
|
||||
|
||||
return this.repository.find({
|
||||
where,
|
||||
relations: ['fraccionamientos'],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id: string, tenantId: string): Promise<Proyecto | null> {
|
||||
return this.repository.findOne({
|
||||
where: { id, tenantId },
|
||||
relations: ['fraccionamientos', 'createdBy'],
|
||||
});
|
||||
}
|
||||
|
||||
async findByCodigo(codigo: string, tenantId: string): Promise<Proyecto | null> {
|
||||
return this.repository.findOne({
|
||||
where: { codigo, tenantId },
|
||||
});
|
||||
}
|
||||
|
||||
async create(data: CreateProyectoDto): Promise<Proyecto> {
|
||||
const proyecto = this.repository.create(data);
|
||||
return this.repository.save(proyecto);
|
||||
}
|
||||
|
||||
async update(id: string, tenantId: string, data: UpdateProyectoDto): Promise<Proyecto | null> {
|
||||
const proyecto = await this.findById(id, tenantId);
|
||||
if (!proyecto) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object.assign(proyecto, data);
|
||||
return this.repository.save(proyecto);
|
||||
}
|
||||
|
||||
async delete(id: string, tenantId: string): Promise<boolean> {
|
||||
const result = await this.repository.delete({ id, tenantId });
|
||||
return result.affected ? result.affected > 0 : false;
|
||||
}
|
||||
|
||||
async getStatistics(tenantId: string): Promise<{
|
||||
total: number;
|
||||
activos: number;
|
||||
completados: number;
|
||||
pausados: number;
|
||||
}> {
|
||||
const proyectos = await this.repository.find({ where: { tenantId } });
|
||||
|
||||
return {
|
||||
total: proyectos.length,
|
||||
activos: proyectos.filter(p => p.estadoProyecto === 'activo').length,
|
||||
completados: proyectos.filter(p => p.estadoProyecto === 'completado').length,
|
||||
pausados: proyectos.filter(p => p.estadoProyecto === 'pausado').length,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Capacitacion Entity
|
||||
* Catálogo de capacitaciones HSE
|
||||
*
|
||||
* @module HSE
|
||||
* @table hse.capacitaciones
|
||||
* @ddl schemas/03-hse-schema-ddl.sql
|
||||
* @rf RF-MAA017-002
|
||||
*/
|
||||
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
import { Tenant } from '../../core/entities/tenant.entity';
|
||||
|
||||
export type TipoCapacitacion = 'induccion' | 'especifica' | 'certificacion' | 'reentrenamiento';
|
||||
|
||||
@Entity({ schema: 'hse', name: 'capacitaciones' })
|
||||
@Index(['tenantId', 'codigo'], { unique: true })
|
||||
export class Capacitacion {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
codigo: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
nombre: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
descripcion: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['induccion', 'especifica', 'certificacion', 'reentrenamiento']
|
||||
})
|
||||
tipo: TipoCapacitacion;
|
||||
|
||||
@Column({ name: 'duracion_horas', type: 'integer', default: 1 })
|
||||
duracionHoras: number;
|
||||
|
||||
@Column({ name: 'vigencia_meses', type: 'integer', nullable: true })
|
||||
vigenciaMeses: number;
|
||||
|
||||
@Column({ name: 'requiere_evaluacion', type: 'boolean', default: false })
|
||||
requiereEvaluacion: boolean;
|
||||
|
||||
@Column({ name: 'calificacion_minima', type: 'integer', nullable: true })
|
||||
calificacionMinima: number;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
activo: boolean;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Tenant)
|
||||
@JoinColumn({ name: 'tenant_id' })
|
||||
tenant: Tenant;
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* IncidenteAccion Entity
|
||||
* Acciones correctivas de incidentes
|
||||
*
|
||||
* @module HSE
|
||||
* @table hse.incidente_acciones
|
||||
* @ddl schemas/03-hse-schema-ddl.sql
|
||||
* @rf RF-MAA017-001
|
||||
*/
|
||||
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Incidente } from './incidente.entity';
|
||||
import { Employee } from '../../hr/entities/employee.entity';
|
||||
|
||||
export type EstadoAccion = 'pendiente' | 'en_progreso' | 'completada' | 'verificada';
|
||||
|
||||
@Entity({ schema: 'hse', name: 'incidente_acciones' })
|
||||
export class IncidenteAccion {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'incidente_id', type: 'uuid' })
|
||||
incidenteId: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
descripcion: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
tipo: string;
|
||||
|
||||
@Column({ name: 'responsable_id', type: 'uuid', nullable: true })
|
||||
responsableId: string;
|
||||
|
||||
@Column({ name: 'fecha_compromiso', type: 'date' })
|
||||
fechaCompromiso: Date;
|
||||
|
||||
@Column({ name: 'fecha_cierre', type: 'date', nullable: true })
|
||||
fechaCierre: Date;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'pendiente' })
|
||||
estado: EstadoAccion;
|
||||
|
||||
@Column({ name: 'evidencia_url', type: 'varchar', length: 500, nullable: true })
|
||||
evidenciaUrl: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
observaciones: string;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Incidente, (i) => i.acciones, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'incidente_id' })
|
||||
incidente: Incidente;
|
||||
|
||||
@ManyToOne(() => Employee)
|
||||
@JoinColumn({ name: 'responsable_id' })
|
||||
responsable: Employee;
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* IncidenteInvolucrado Entity
|
||||
* Personas involucradas en incidentes
|
||||
*
|
||||
* @module HSE
|
||||
* @table hse.incidente_involucrados
|
||||
* @ddl schemas/03-hse-schema-ddl.sql
|
||||
* @rf RF-MAA017-001
|
||||
*/
|
||||
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Incidente } from './incidente.entity';
|
||||
import { Employee } from '../../hr/entities/employee.entity';
|
||||
|
||||
export type RolInvolucrado = 'lesionado' | 'testigo' | 'responsable';
|
||||
|
||||
@Entity({ schema: 'hse', name: 'incidente_involucrados' })
|
||||
export class IncidenteInvolucrado {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'incidente_id', type: 'uuid' })
|
||||
incidenteId: string;
|
||||
|
||||
@Column({ name: 'employee_id', type: 'uuid' })
|
||||
employeeId: string;
|
||||
|
||||
@Column({ type: 'enum', enum: ['lesionado', 'testigo', 'responsable'] })
|
||||
rol: RolInvolucrado;
|
||||
|
||||
@Column({ name: 'descripcion_lesion', type: 'text', nullable: true })
|
||||
descripcionLesion: string;
|
||||
|
||||
@Column({ name: 'parte_cuerpo', type: 'varchar', length: 100, nullable: true })
|
||||
parteCuerpo: string;
|
||||
|
||||
@Column({ name: 'dias_incapacidad', type: 'integer', default: 0 })
|
||||
diasIncapacidad: number;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Incidente, (i) => i.involucrados, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'incidente_id' })
|
||||
incidente: Incidente;
|
||||
|
||||
@ManyToOne(() => Employee)
|
||||
@JoinColumn({ name: 'employee_id' })
|
||||
employee: Employee;
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Incidente Entity
|
||||
* Gestión de incidentes de seguridad
|
||||
*
|
||||
* @module HSE
|
||||
* @table hse.incidentes
|
||||
* @ddl schemas/03-hse-schema-ddl.sql
|
||||
* @rf RF-MAA017-001
|
||||
*/
|
||||
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
import { Tenant } from '../../core/entities/tenant.entity';
|
||||
import { User } from '../../core/entities/user.entity';
|
||||
import { Fraccionamiento } from '../../construction/entities/fraccionamiento.entity';
|
||||
import { IncidenteInvolucrado } from './incidente-involucrado.entity';
|
||||
import { IncidenteAccion } from './incidente-accion.entity';
|
||||
|
||||
export type TipoIncidente = 'accidente' | 'incidente' | 'casi_accidente';
|
||||
export type GravedadIncidente = 'leve' | 'moderado' | 'grave' | 'fatal';
|
||||
export type EstadoIncidente = 'abierto' | 'en_investigacion' | 'cerrado';
|
||||
|
||||
@Entity({ schema: 'hse', name: 'incidentes' })
|
||||
@Index(['tenantId', 'folio'], { unique: true })
|
||||
export class Incidente {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
folio: string;
|
||||
|
||||
@Column({ name: 'fecha_hora', type: 'timestamptz' })
|
||||
fechaHora: Date;
|
||||
|
||||
@Column({ name: 'fraccionamiento_id', type: 'uuid' })
|
||||
fraccionamientoId: string;
|
||||
|
||||
@Column({ name: 'ubicacion_descripcion', type: 'text', nullable: true })
|
||||
ubicacionDescripcion: string;
|
||||
|
||||
@Column({
|
||||
name: 'ubicacion_geo',
|
||||
type: 'geometry',
|
||||
spatialFeatureType: 'Point',
|
||||
srid: 4326,
|
||||
nullable: true
|
||||
})
|
||||
ubicacionGeo: string;
|
||||
|
||||
@Column({ type: 'enum', enum: ['accidente', 'incidente', 'casi_accidente'] })
|
||||
tipo: TipoIncidente;
|
||||
|
||||
@Column({ type: 'enum', enum: ['leve', 'moderado', 'grave', 'fatal'] })
|
||||
gravedad: GravedadIncidente;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
descripcion: string;
|
||||
|
||||
@Column({ name: 'causa_inmediata', type: 'text', nullable: true })
|
||||
causaInmediata: string;
|
||||
|
||||
@Column({ name: 'causa_basica', type: 'text', nullable: true })
|
||||
causaBasica: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['abierto', 'en_investigacion', 'cerrado'],
|
||||
default: 'abierto'
|
||||
})
|
||||
estado: EstadoIncidente;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({ name: 'created_by', type: 'uuid', nullable: true })
|
||||
createdById: string;
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Tenant)
|
||||
@JoinColumn({ name: 'tenant_id' })
|
||||
tenant: Tenant;
|
||||
|
||||
@ManyToOne(() => Fraccionamiento)
|
||||
@JoinColumn({ name: 'fraccionamiento_id' })
|
||||
fraccionamiento: Fraccionamiento;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'created_by' })
|
||||
createdBy: User;
|
||||
|
||||
@OneToMany(() => IncidenteInvolucrado, (ii) => ii.incidente)
|
||||
involucrados: IncidenteInvolucrado[];
|
||||
|
||||
@OneToMany(() => IncidenteAccion, (ia) => ia.incidente)
|
||||
acciones: IncidenteAccion[];
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* HSE Entities Index
|
||||
* @module HSE
|
||||
*
|
||||
* Entities for Health, Safety & Environment module
|
||||
* Based on RF-MAA017-001 to RF-MAA017-008
|
||||
*/
|
||||
|
||||
// RF-MAA017-001: Gestión de Incidentes
|
||||
export * from './incidente.entity';
|
||||
export * from './incidente-involucrado.entity';
|
||||
export * from './incidente-accion.entity';
|
||||
|
||||
// RF-MAA017-002: Control de Capacitaciones
|
||||
export * from './capacitacion.entity';
|
||||
|
||||
// TODO: Implementar entities adicionales según se necesiten:
|
||||
// - RF-MAA017-003: Inspecciones de Seguridad
|
||||
// - RF-MAA017-004: Control de EPP
|
||||
// - RF-MAA017-005: Cumplimiento STPS
|
||||
// - RF-MAA017-006: Gestión Ambiental
|
||||
// - RF-MAA017-007: Permisos de Trabajo
|
||||
// - RF-MAA017-008: Indicadores HSE
|
||||
@ -47,8 +47,10 @@ app.get('/health', (req, res) => {
|
||||
|
||||
/**
|
||||
* API Routes
|
||||
* TODO: Agregar rutas de módulos aquí
|
||||
*/
|
||||
import { proyectoController, fraccionamientoController } from './modules/construction/controllers';
|
||||
|
||||
// Root API info
|
||||
app.get(`/api/${API_VERSION}`, (req, res) => {
|
||||
res.status(200).json({
|
||||
message: 'API MVP Sistema Administración de Obra',
|
||||
@ -57,12 +59,16 @@ app.get(`/api/${API_VERSION}`, (req, res) => {
|
||||
health: '/health',
|
||||
docs: `/api/${API_VERSION}/docs`,
|
||||
auth: `/api/${API_VERSION}/auth`,
|
||||
projects: `/api/${API_VERSION}/projects`,
|
||||
budgets: `/api/${API_VERSION}/budgets`,
|
||||
proyectos: `/api/${API_VERSION}/proyectos`,
|
||||
fraccionamientos: `/api/${API_VERSION}/fraccionamientos`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Construction Module Routes
|
||||
app.use(`/api/${API_VERSION}/proyectos`, proyectoController);
|
||||
app.use(`/api/${API_VERSION}/fraccionamientos`, fraccionamientoController);
|
||||
|
||||
/**
|
||||
* 404 Handler
|
||||
*/
|
||||
|
||||
@ -0,0 +1,159 @@
|
||||
# Herencia de SPECS del Core - Construcción
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Construcción (MAI/MAE)
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| SPECS Aplicables | 26/30 |
|
||||
| SPECS Obligatorias | 22 |
|
||||
| SPECS Opcionales | 4 |
|
||||
| SPECS No Aplican | 4 |
|
||||
| Estado Implementación | 0% |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Obligatorias (Deben Implementarse)
|
||||
|
||||
### P0 - Críticas
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | ir.sequence | 8 | PENDIENTE | MAI-001, MAE-001 |
|
||||
| SPEC-VALORACION-INVENTARIO | FIFO/AVCO | 21 | PENDIENTE | MAI-004, MAI-012 |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | API Keys + ACL | 31 | PENDIENTE | MAI-001 |
|
||||
| SPEC-REPORTES-FINANCIEROS | Balance/P&L SAT | 13 | PENDIENTE | MAE-003 |
|
||||
| SPEC-PORTAL-PROVEEDORES | Portal RFQ | 13 | PENDIENTE | MAI-006 |
|
||||
| SPEC-NOMINA-BASICA | hr_payroll | 21 | PENDIENTE | MAI-008 |
|
||||
| SPEC-GASTOS-EMPLEADOS | hr_expense | 13 | PENDIENTE | MAI-008 |
|
||||
| SPEC-TAREAS-RECURRENTES | project.task.recurrence | 13 | PENDIENTE | MAI-002, MAI-005 |
|
||||
| SPEC-SCHEDULER-REPORTES | ir.cron + mail | 8 | PENDIENTE | MAE-003 |
|
||||
|
||||
### P1 - Complementarias
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-CONTABILIDAD-ANALITICA | Centros de costo | 21 | PENDIENTE | MAE-003 |
|
||||
| SPEC-CONCILIACION-BANCARIA | Conciliación | 21 | PENDIENTE | MAE-003 |
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | e.firma | 13 | PENDIENTE | MAE-001, MAI-007 |
|
||||
| SPEC-TWO-FACTOR-AUTHENTICATION | 2FA | 13 | PENDIENTE | MAI-001 |
|
||||
| SPEC-TRAZABILIDAD-LOTES-SERIES | Lotes/Series | 13 | PENDIENTE | MAI-004, MAI-012 |
|
||||
| SPEC-BLANKET-ORDERS | Órdenes marco | 13 | PENDIENTE | MAI-006 |
|
||||
| SPEC-IMPUESTOS-AVANZADOS | IVA, ISR | 8 | PENDIENTE | MAE-003 |
|
||||
| SPEC-PLANTILLAS-CUENTAS | Plan contable | 8 | PENDIENTE | MAE-003 |
|
||||
| SPEC-TASAS-CAMBIO-AUTOMATICAS | Tipos cambio | 5 | PENDIENTE | MAE-003 |
|
||||
| SPEC-ALERTAS-PRESUPUESTO | Alertas | 8 | PENDIENTE | MAI-012 |
|
||||
| SPEC-PRESUPUESTOS-REVISIONES | Aprobación | 8 | PENDIENTE | MAI-005, MAI-012 |
|
||||
| SPEC-RRHH-EVALUACIONES-SKILLS | Evaluaciones | 26 | PENDIENTE | MAI-008 |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | Burndown | 13 | PENDIENTE | MAI-002, MAI-005 |
|
||||
| SPEC-LOCALIZACION-PAISES | Localización | 13 | PENDIENTE | MAE-001 |
|
||||
|
||||
### Patrones Técnicos
|
||||
|
||||
| SPEC | Patrón | SP | Estado | Aplicación |
|
||||
|------|--------|----:|--------|------------|
|
||||
| SPEC-MAIL-THREAD-TRACKING | mail.thread | 13 | PENDIENTE | Proyectos, Estimaciones, Obras |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | TransientModel | 8 | PENDIENTE | Wizards de cierre, aprobación |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Opcionales
|
||||
|
||||
| SPEC | Descripción | SP | Decisión | Razón |
|
||||
|------|-------------|----:|----------|-------|
|
||||
| SPEC-INTEGRACION-CALENDAR | Calendario | 8 | EVALUAR | Útil para programación de obra |
|
||||
| SPEC-PRICING-RULES | Reglas precio | 8 | EVALUAR | Para cotizaciones complejas |
|
||||
| SPEC-OAUTH2-SOCIAL-LOGIN | OAuth2 | 8 | DIFERIR | No prioritario |
|
||||
| SPEC-CONSOLIDACION-FINANCIERA | Multi-empresa | 13 | DIFERIR | Futuro para constructoras grandes |
|
||||
|
||||
---
|
||||
|
||||
## SPECS No Aplicables
|
||||
|
||||
| SPEC | Razón |
|
||||
|------|-------|
|
||||
| SPEC-INVENTARIOS-CICLICOS | No hay inventario tradicional de productos |
|
||||
| SPEC-INTEGRACION-CALENDAR | El módulo de proyectos maneja calendario propio |
|
||||
|
||||
---
|
||||
|
||||
## Adaptaciones Requeridas
|
||||
|
||||
### Mapeo de Conceptos Core → Construcción
|
||||
|
||||
| Concepto Core | Concepto Construcción |
|
||||
|---------------|----------------------|
|
||||
| `projects.projects` | Obras, Fraccionamientos |
|
||||
| `projects.tasks` | Etapas de construcción |
|
||||
| `inventory.products` | Materiales de construcción |
|
||||
| `inventory.lots` | Lotes de materiales |
|
||||
| `hr.employees` | Trabajadores de obra |
|
||||
| `sales.sale_orders` | Contratos de obra |
|
||||
| `purchase.purchase_orders` | Órdenes de compra de materiales |
|
||||
|
||||
### Extensiones de Entidad
|
||||
|
||||
```sql
|
||||
-- Extensión de projects para construcción
|
||||
construction.project_extensions (
|
||||
project_id → projects.projects,
|
||||
tipo_obra ENUM,
|
||||
numero_licencia VARCHAR,
|
||||
fecha_inicio_obra DATE,
|
||||
fecha_fin_estimada DATE,
|
||||
m2_construccion DECIMAL,
|
||||
presupuesto_aprobado DECIMAL
|
||||
)
|
||||
|
||||
-- Extensión de employees para construcción
|
||||
construction.employee_extensions (
|
||||
employee_id → hr.employees,
|
||||
numero_imss VARCHAR,
|
||||
categoria_obra ENUM,
|
||||
especialidad VARCHAR,
|
||||
certificaciones JSONB
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plan de Implementación
|
||||
|
||||
### Fase 1: Fundamentos (SP: 60)
|
||||
1. SPEC-SISTEMA-SECUENCIAS
|
||||
2. SPEC-SEGURIDAD-API-KEYS-PERMISOS
|
||||
3. SPEC-TWO-FACTOR-AUTHENTICATION
|
||||
|
||||
### Fase 2: Core de Negocio (SP: 80)
|
||||
4. SPEC-VALORACION-INVENTARIO
|
||||
5. SPEC-TRAZABILIDAD-LOTES-SERIES
|
||||
6. SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN
|
||||
|
||||
### Fase 3: Financiero (SP: 65)
|
||||
7. SPEC-REPORTES-FINANCIEROS
|
||||
8. SPEC-CONTABILIDAD-ANALITICA
|
||||
9. SPEC-IMPUESTOS-AVANZADOS
|
||||
|
||||
### Fase 4: RRHH (SP: 60)
|
||||
10. SPEC-NOMINA-BASICA
|
||||
11. SPEC-GASTOS-EMPLEADOS
|
||||
12. SPEC-RRHH-EVALUACIONES-SKILLS
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- Documento Core: `erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md`
|
||||
- SPECS del Core: `erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/`
|
||||
- Herencia DB: `database/HERENCIA-ERP-CORE.md`
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia de SPECS oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -53,20 +53,37 @@ resumen:
|
||||
|
||||
# ESTADO REAL DE IMPLEMENTACIÓN (2025-12-08)
|
||||
estado_implementacion:
|
||||
porcentaje: "5%"
|
||||
archivos_ts: 7
|
||||
entities_implementadas: 2
|
||||
services_implementados: 0
|
||||
controllers_implementados: 0
|
||||
porcentaje: "15%"
|
||||
archivos_ts: 25
|
||||
entities_implementadas: 12
|
||||
services_implementados: 2
|
||||
controllers_implementados: 2
|
||||
|
||||
archivos_existentes:
|
||||
- src/types/
|
||||
- src/entities/ # Parcialmente definidas
|
||||
modulos_implementados:
|
||||
construction:
|
||||
entities: [Proyecto, Fraccionamiento]
|
||||
services: [ProyectoService, FraccionamientoService]
|
||||
controllers: [ProyectoController, FraccionamientoController]
|
||||
estado: "FUNCIONAL"
|
||||
hr:
|
||||
entities: [Employee, Puesto, EmployeeFraccionamiento]
|
||||
services: []
|
||||
controllers: []
|
||||
estado: "ENTITIES_COMPLETAS"
|
||||
hse:
|
||||
entities: [Incidente, IncidenteInvolucrado, IncidenteAccion, Capacitacion]
|
||||
services: []
|
||||
controllers: []
|
||||
estado: "ENTITIES_PARCIALES"
|
||||
core:
|
||||
entities: [User, Tenant]
|
||||
estado: "BASE"
|
||||
|
||||
gap_documentacion_vs_codigo:
|
||||
documentacion_md: 449
|
||||
archivos_codigo: 7
|
||||
ratio: "1.5%"
|
||||
archivos_codigo: 25
|
||||
ratio: "5.6%"
|
||||
nota: "Gap reducido - entities y services base implementados"
|
||||
|
||||
herencia_core:
|
||||
version_core: "1.1.0"
|
||||
|
||||
@ -0,0 +1,169 @@
|
||||
# Herencia de SPECS del Core - Mecánicas Diesel
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Mecánicas Diesel (MMD)
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| SPECS Aplicables | 25/30 |
|
||||
| SPECS Obligatorias | 23 |
|
||||
| SPECS Opcionales | 2 |
|
||||
| SPECS No Aplican | 5 |
|
||||
| Estado Implementación | 0% |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Obligatorias (Deben Implementarse)
|
||||
|
||||
### P0 - Críticas
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | ir.sequence | 8 | PENDIENTE | MMD-001, MMD-002 |
|
||||
| SPEC-VALORACION-INVENTARIO | FIFO/AVCO | 21 | PENDIENTE | MMD-004 |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | API Keys + ACL | 31 | PENDIENTE | MMD-001 |
|
||||
| SPEC-REPORTES-FINANCIEROS | Balance/P&L SAT | 13 | PENDIENTE | MMD-006 |
|
||||
| SPEC-PORTAL-PROVEEDORES | Portal RFQ | 13 | PENDIENTE | MMD-004 |
|
||||
| SPEC-NOMINA-BASICA | hr_payroll | 21 | PENDIENTE | MMD-001 |
|
||||
| SPEC-GASTOS-EMPLEADOS | hr_expense | 13 | PENDIENTE | MMD-001 |
|
||||
| SPEC-TAREAS-RECURRENTES | project.task.recurrence | 13 | PENDIENTE | MMD-002 |
|
||||
| SPEC-SCHEDULER-REPORTES | ir.cron + mail | 8 | PENDIENTE | MMD-006 |
|
||||
|
||||
### P1 - Complementarias
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-CONTABILIDAD-ANALITICA | Centros de costo | 21 | PENDIENTE | MMD-006 |
|
||||
| SPEC-CONCILIACION-BANCARIA | Conciliación | 21 | PENDIENTE | MMD-006 |
|
||||
| SPEC-TWO-FACTOR-AUTHENTICATION | 2FA | 13 | PENDIENTE | MMD-001 |
|
||||
| SPEC-TRAZABILIDAD-LOTES-SERIES | Lotes/Series | 13 | PENDIENTE | MMD-004 |
|
||||
| SPEC-PRICING-RULES | Reglas precio | 8 | PENDIENTE | MMD-002, MMD-006 |
|
||||
| SPEC-BLANKET-ORDERS | Órdenes marco | 13 | PENDIENTE | MMD-004 |
|
||||
| SPEC-INVENTARIOS-CICLICOS | Conteo cíclico | 13 | PENDIENTE | MMD-004 |
|
||||
| SPEC-IMPUESTOS-AVANZADOS | IVA, ISR | 8 | PENDIENTE | MMD-006 |
|
||||
| SPEC-PLANTILLAS-CUENTAS | Plan contable | 8 | PENDIENTE | MMD-006 |
|
||||
| SPEC-TASAS-CAMBIO-AUTOMATICAS | Tipos cambio | 5 | PENDIENTE | MMD-006 |
|
||||
| SPEC-ALERTAS-PRESUPUESTO | Alertas | 8 | PENDIENTE | MMD-002, MMD-006 |
|
||||
| SPEC-PRESUPUESTOS-REVISIONES | Aprobación | 8 | PENDIENTE | MMD-002 |
|
||||
| SPEC-RRHH-EVALUACIONES-SKILLS | Evaluaciones | 26 | PENDIENTE | MMD-001 |
|
||||
| SPEC-LOCALIZACION-PAISES | Localización | 13 | PENDIENTE | MMD-001 |
|
||||
|
||||
### Patrones Técnicos
|
||||
|
||||
| SPEC | Patrón | SP | Estado | Aplicación |
|
||||
|------|--------|----:|--------|------------|
|
||||
| SPEC-MAIL-THREAD-TRACKING | mail.thread | 13 | PENDIENTE | Órdenes de trabajo, Diagnósticos |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | TransientModel | 8 | PENDIENTE | Wizards de cotización, cierre |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Opcionales
|
||||
|
||||
| SPEC | Descripción | SP | Decisión | Razón |
|
||||
|------|-------------|----:|----------|-------|
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | e.firma | 13 | EVALUAR | Para contratos de servicio |
|
||||
| SPEC-OAUTH2-SOCIAL-LOGIN | OAuth2 | 8 | DIFERIR | No prioritario |
|
||||
|
||||
---
|
||||
|
||||
## SPECS No Aplicables
|
||||
|
||||
| SPEC | Razón |
|
||||
|------|-------|
|
||||
| SPEC-INTEGRACION-CALENDAR | No hay agenda de citas compleja |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | No hay gestión de proyectos larga |
|
||||
| SPEC-CONSOLIDACION-FINANCIERA | Negocio de una sola ubicación |
|
||||
|
||||
---
|
||||
|
||||
## Adaptaciones Requeridas
|
||||
|
||||
### Mapeo de Conceptos Core → Mecánicas
|
||||
|
||||
| Concepto Core | Concepto Mecánicas |
|
||||
|---------------|-------------------|
|
||||
| `sales.sale_orders` | Órdenes de servicio |
|
||||
| `inventory.products` | Refacciones, partes |
|
||||
| `inventory.lots` | Lotes OEM, garantías |
|
||||
| `core.partners` | Clientes con vehículos |
|
||||
| `projects.tasks` | Trabajos de servicio |
|
||||
|
||||
### Extensiones de Entidad
|
||||
|
||||
```sql
|
||||
-- Vehículos de clientes
|
||||
service_management.vehicles (
|
||||
id UUID,
|
||||
partner_id → core.partners,
|
||||
vin VARCHAR(17),
|
||||
marca VARCHAR,
|
||||
modelo VARCHAR,
|
||||
anio INTEGER,
|
||||
motor_tipo VARCHAR,
|
||||
placas VARCHAR
|
||||
)
|
||||
|
||||
-- Órdenes de servicio
|
||||
service_management.service_orders (
|
||||
id UUID,
|
||||
vehicle_id → vehicles,
|
||||
sale_order_id → sales.sale_orders,
|
||||
tipo_servicio ENUM,
|
||||
km_entrada INTEGER,
|
||||
diagnostico TEXT,
|
||||
estado ENUM
|
||||
)
|
||||
|
||||
-- Refacciones con compatibilidad
|
||||
parts_management.parts (
|
||||
product_id → inventory.products,
|
||||
oem_number VARCHAR,
|
||||
aftermarket_number VARCHAR,
|
||||
compatibilidad JSONB
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plan de Implementación
|
||||
|
||||
### Fase 1: Fundamentos (SP: 52)
|
||||
1. SPEC-SISTEMA-SECUENCIAS
|
||||
2. SPEC-SEGURIDAD-API-KEYS-PERMISOS
|
||||
3. SPEC-TWO-FACTOR-AUTHENTICATION
|
||||
|
||||
### Fase 2: Inventario (SP: 55)
|
||||
4. SPEC-VALORACION-INVENTARIO
|
||||
5. SPEC-TRAZABILIDAD-LOTES-SERIES
|
||||
6. SPEC-INVENTARIOS-CICLICOS
|
||||
7. SPEC-PRICING-RULES
|
||||
|
||||
### Fase 3: Operaciones (SP: 34)
|
||||
8. SPEC-MAIL-THREAD-TRACKING
|
||||
9. SPEC-WIZARD-TRANSIENT-MODEL
|
||||
10. SPEC-TAREAS-RECURRENTES
|
||||
|
||||
### Fase 4: Financiero (SP: 65)
|
||||
11. SPEC-REPORTES-FINANCIEROS
|
||||
12. SPEC-CONTABILIDAD-ANALITICA
|
||||
13. SPEC-CONCILIACION-BANCARIA
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- Documento Core: `erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md`
|
||||
- SPECS del Core: `erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/`
|
||||
- Herencia DB: `database/HERENCIA-ERP-CORE.md`
|
||||
- Directivas: `orchestration/directivas/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia de SPECS oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,106 @@
|
||||
# Inventarios - ERP Mecánicas Diesel
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Nivel SIMCO:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Descripción
|
||||
|
||||
Este directorio contiene los inventarios YAML que sirven como **Single Source of Truth (SSOT)** para el proyecto ERP Mecánicas Diesel. Estos archivos son la referencia canónica para métricas, trazabilidad y componentes del sistema.
|
||||
|
||||
---
|
||||
|
||||
## Archivos de Inventario
|
||||
|
||||
| Archivo | Descripción | Estado |
|
||||
|---------|-------------|--------|
|
||||
| [MASTER_INVENTORY.yml](./MASTER_INVENTORY.yml) | Inventario maestro con métricas globales | Completo |
|
||||
| [DATABASE_INVENTORY.yml](./DATABASE_INVENTORY.yml) | Inventario de objetos de base de datos | Completo |
|
||||
| [BACKEND_INVENTORY.yml](./BACKEND_INVENTORY.yml) | Inventario de componentes backend | Planificado |
|
||||
| [FRONTEND_INVENTORY.yml](./FRONTEND_INVENTORY.yml) | Inventario de componentes frontend | Planificado |
|
||||
| [TRACEABILITY_MATRIX.yml](./TRACEABILITY_MATRIX.yml) | Matriz de trazabilidad RF->ET->US | Completo |
|
||||
| [DEPENDENCY_GRAPH.yml](./DEPENDENCY_GRAPH.yml) | Grafo de dependencias entre módulos | Completo |
|
||||
|
||||
---
|
||||
|
||||
## Herencia del Core
|
||||
|
||||
Este proyecto hereda del **ERP Core** (nivel 2B.1):
|
||||
|
||||
| Aspecto | Heredado | Específico |
|
||||
|---------|----------|------------|
|
||||
| **Tablas DB** | 97 | 30+ |
|
||||
| **Schemas** | 8 | 3 |
|
||||
| **Specs** | 5 | - |
|
||||
|
||||
### Specs Heredadas
|
||||
|
||||
1. SPEC-VALORACION-INVENTARIO.md
|
||||
2. SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
3. SPEC-INVENTARIOS-CICLICOS.md
|
||||
4. SPEC-MAIL-THREAD-TRACKING.md
|
||||
5. SPEC-TAREAS-RECURRENTES.md
|
||||
|
||||
### Documento de Herencia
|
||||
|
||||
Ver: [`database/HERENCIA-ERP-CORE.md`](../../database/HERENCIA-ERP-CORE.md)
|
||||
|
||||
---
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
### Métricas del Proyecto
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| **Módulos** | 5 (MD-001 a MD-005) |
|
||||
| **Schemas específicos** | 3 |
|
||||
| **Tablas específicas** | 30+ |
|
||||
| **DDL implementado** | 1,561 líneas |
|
||||
| **Estado** | DDL_IMPLEMENTADO |
|
||||
|
||||
### Schemas Específicos
|
||||
|
||||
| Schema | Propósito | Tablas |
|
||||
|--------|-----------|--------|
|
||||
| `service_management` | Órdenes de servicio | 10+ |
|
||||
| `parts_management` | Inventario refacciones | 12+ |
|
||||
| `vehicle_management` | Gestión de vehículos | 8+ |
|
||||
|
||||
---
|
||||
|
||||
## Configuración de Puertos (Planificado)
|
||||
|
||||
| Servicio | Puerto |
|
||||
|----------|--------|
|
||||
| Backend API | 3200 |
|
||||
| Frontend Web | 5175 |
|
||||
| PostgreSQL | Compartido con Core |
|
||||
| Redis | Compartido con Core |
|
||||
|
||||
---
|
||||
|
||||
## Directivas Específicas
|
||||
|
||||
1. [DIRECTIVA-ORDENES-TRABAJO.md](../directivas/DIRECTIVA-ORDENES-TRABAJO.md)
|
||||
2. [DIRECTIVA-INVENTARIO-REFACCIONES.md](../directivas/DIRECTIVA-INVENTARIO-REFACCIONES.md)
|
||||
|
||||
---
|
||||
|
||||
## Alineación con ERP Core
|
||||
|
||||
Estos inventarios siguen la misma estructura que:
|
||||
- `/erp-core/orchestration/inventarios/` (proyecto padre)
|
||||
- `/verticales/construccion/orchestration/inventarios/` (vertical hermana)
|
||||
|
||||
### Referencias
|
||||
|
||||
- Suite Master: `orchestration/inventarios/SUITE_MASTER_INVENTORY.yml`
|
||||
- Core: `apps/erp-core/orchestration/inventarios/`
|
||||
- Status Global: `orchestration/inventarios/STATUS.yml`
|
||||
|
||||
---
|
||||
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,187 @@
|
||||
# Herencia de Base de Datos - ERP Core -> Retail
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Retail
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## RESUMEN
|
||||
|
||||
La vertical de Retail hereda los schemas base del ERP Core y extiende con schemas específicos del dominio de punto de venta y comercio minorista.
|
||||
|
||||
**Ubicación DDL Core:** `apps/erp-core/database/ddl/`
|
||||
|
||||
---
|
||||
|
||||
## ARQUITECTURA DE HERENCIA
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ERP CORE (Base) │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ auth │ │ core │ │financial│ │inventory│ │ purchase │ │
|
||||
│ │ 26 tbl │ │ 12 tbl │ │ 15 tbl │ │ 15 tbl │ │ 8 tbl │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ sales │ │analytics│ │ system │ │ crm │ │
|
||||
│ │ 6 tbl │ │ 5 tbl │ │ 10 tbl │ │ 5 tbl │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ TOTAL: ~102 tablas heredadas │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HEREDA
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ RETAIL (Extensiones) │
|
||||
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
||||
│ │ pos │ │ stores │ │ pricing │ │
|
||||
│ │ (punto venta) │ │ (sucursales) │ │ (promociones) │ │
|
||||
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
||||
│ EXTENSIONES: ~30 tablas (planificadas) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SCHEMAS HEREDADOS DEL CORE
|
||||
|
||||
| Schema | Tablas | Uso en Retail |
|
||||
|--------|--------|---------------|
|
||||
| `auth` | 26 | Autenticación, usuarios por sucursal |
|
||||
| `core` | 12 | Partners (clientes), catálogos |
|
||||
| `financial` | 15 | Facturas, cuentas, caja |
|
||||
| `inventory` | 15 | Inventario multi-sucursal |
|
||||
| `purchase` | 8 | Compras a proveedores |
|
||||
| `sales` | 6 | Ventas base |
|
||||
| `crm` | 5 | Clientes frecuentes |
|
||||
| `analytics` | 5 | Métricas de venta |
|
||||
| `system` | 10 | Notificaciones |
|
||||
|
||||
**Total heredado:** ~102 tablas
|
||||
|
||||
---
|
||||
|
||||
## SCHEMAS ESPECÍFICOS DE RETAIL (Planificados)
|
||||
|
||||
### 1. Schema `pos` (estimado 12+ tablas)
|
||||
|
||||
**Propósito:** Punto de venta y operaciones de caja
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
pos.cash_registers -- Cajas registradoras
|
||||
pos.cash_sessions -- Sesiones de caja
|
||||
pos.pos_orders -- Tickets/ventas POS
|
||||
pos.pos_order_lines -- Líneas de ticket
|
||||
pos.payment_methods -- Métodos de pago
|
||||
pos.cash_movements -- Movimientos de caja
|
||||
pos.cash_counts -- Cortes de caja
|
||||
pos.receipts -- Recibos
|
||||
```
|
||||
|
||||
### 2. Schema `stores` (estimado 8+ tablas)
|
||||
|
||||
**Propósito:** Gestión de sucursales
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
stores.branches -- Sucursales
|
||||
stores.branch_inventory -- Inventario por sucursal
|
||||
stores.transfers -- Transferencias entre sucursales
|
||||
stores.transfer_lines -- Líneas de transferencia
|
||||
stores.branch_employees -- Empleados por sucursal
|
||||
```
|
||||
|
||||
### 3. Schema `pricing` (estimado 10+ tablas)
|
||||
|
||||
**Propósito:** Precios y promociones
|
||||
|
||||
```sql
|
||||
-- Extiende: sales schema del core
|
||||
pricing.price_lists -- Listas de precios
|
||||
pricing.promotions -- Promociones
|
||||
pricing.discounts -- Descuentos
|
||||
pricing.loyalty_programs -- Programas de lealtad
|
||||
pricing.coupons -- Cupones
|
||||
pricing.price_history -- Historial de precios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SPECS DEL CORE APLICABLES
|
||||
|
||||
**Documento detallado:** `orchestration/00-guidelines/HERENCIA-SPECS-CORE.md`
|
||||
|
||||
### SPECS Obligatorias
|
||||
|
||||
| Spec Core | Aplicación en Retail | SP | Estado |
|
||||
|-----------|---------------------|----:|--------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | Foliado de tickets y facturas | 8 | PENDIENTE |
|
||||
| SPEC-VALORACION-INVENTARIO | Costeo de mercancía | 21 | PENDIENTE |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | Control de acceso por sucursal | 31 | PENDIENTE |
|
||||
| SPEC-PRICING-RULES | Precios y promociones | 8 | PENDIENTE |
|
||||
| SPEC-INVENTARIOS-CICLICOS | Conteos en sucursales | 13 | PENDIENTE |
|
||||
| SPEC-TRAZABILIDAD-LOTES-SERIES | Productos con lote/serie | 13 | PENDIENTE |
|
||||
| SPEC-MAIL-THREAD-TRACKING | Comunicación con clientes | 13 | PENDIENTE |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | Wizards de cierre de caja | 8 | PENDIENTE |
|
||||
|
||||
### SPECS Opcionales
|
||||
|
||||
| Spec Core | Decisión | Razón |
|
||||
|-----------|----------|-------|
|
||||
| SPEC-PORTAL-PROVEEDORES | EVALUAR | Para compras centralizadas |
|
||||
| SPEC-TAREAS-RECURRENTES | EVALUAR | Para reorden automático |
|
||||
|
||||
### SPECS No Aplican
|
||||
|
||||
| Spec Core | Razón |
|
||||
|-----------|-------|
|
||||
| SPEC-INTEGRACION-CALENDAR | No requiere calendario de citas |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | No hay proyectos largos |
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | No aplica para tickets POS |
|
||||
|
||||
---
|
||||
|
||||
## ORDEN DE EJECUCIÓN DDL (Futuro)
|
||||
|
||||
```bash
|
||||
# PASO 1: Cargar ERP Core (base)
|
||||
cd apps/erp-core/database
|
||||
./scripts/reset-database.sh --force
|
||||
|
||||
# PASO 2: Cargar extensiones de Retail
|
||||
cd apps/verticales/retail/database
|
||||
psql $DATABASE_URL -f init/00-extensions.sql
|
||||
psql $DATABASE_URL -f init/01-create-schemas.sql
|
||||
psql $DATABASE_URL -f init/02-pos-tables.sql
|
||||
psql $DATABASE_URL -f init/03-stores-tables.sql
|
||||
psql $DATABASE_URL -f init/04-pricing-tables.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MAPEO DE NOMENCLATURA
|
||||
|
||||
| Core | Retail |
|
||||
|------|--------|
|
||||
| `core.partners` | Clientes, proveedores |
|
||||
| `inventory.products` | Productos de venta |
|
||||
| `inventory.locations` | Almacenes de sucursal |
|
||||
| `sales.sale_orders` | Base para POS orders |
|
||||
| `financial.invoices` | Facturas de venta |
|
||||
|
||||
---
|
||||
|
||||
## REFERENCIAS
|
||||
|
||||
- ERP Core DDL: `apps/erp-core/database/ddl/`
|
||||
- ERP Core README: `apps/erp-core/database/README.md`
|
||||
- Directivas: `orchestration/directivas/`
|
||||
- Inventarios: `orchestration/inventarios/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,97 @@
|
||||
# Visión General - ERP Retail
|
||||
|
||||
**Versión:** 1.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Nivel:** 2B.2 (Vertical)
|
||||
|
||||
---
|
||||
|
||||
## Propósito del Sistema
|
||||
|
||||
Sistema ERP especializado para comercio minorista con punto de venta (POS), gestión de inventario multi-sucursal, control de caja y programas de fidelización. Optimizado para operación en tienda física con capacidad offline.
|
||||
|
||||
---
|
||||
|
||||
## Dominio del Negocio
|
||||
|
||||
### Procesos Principales
|
||||
|
||||
1. **Punto de Venta (POS)**
|
||||
- Venta rápida en mostrador
|
||||
- Múltiples métodos de pago
|
||||
- Facturación CFDI 4.0
|
||||
- Operación offline
|
||||
|
||||
2. **Inventario Multi-Sucursal**
|
||||
- Control de stock por sucursal
|
||||
- Transferencias entre tiendas
|
||||
- Conteos cíclicos
|
||||
- Alertas de reorden
|
||||
|
||||
3. **Compras y Reabastecimiento**
|
||||
- Órdenes de compra centralizadas
|
||||
- Distribución a sucursales
|
||||
- Control de proveedores
|
||||
|
||||
4. **Clientes y Fidelización**
|
||||
- Programa de lealtad
|
||||
- Puntos y recompensas
|
||||
- Historial de compras
|
||||
|
||||
5. **Gestión de Caja**
|
||||
- Apertura y cierre de caja
|
||||
- Arqueos
|
||||
- Control de efectivo
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura de Módulos
|
||||
|
||||
```
|
||||
RT-001 Fundamentos → Auth, Users, Tenants (hereda 100% core)
|
||||
RT-002 POS → Punto de venta (20% core)
|
||||
RT-003 Inventario → Stock multi-sucursal (60% core)
|
||||
RT-004 Compras → Reabastecimiento (80% core)
|
||||
RT-005 Clientes → Programa fidelidad (40% core)
|
||||
RT-006 Precios → Promociones, descuentos (30% core)
|
||||
RT-007 Caja → Arqueos, cortes (10% core)
|
||||
RT-008 Reportes → Dashboard ventas (70% core)
|
||||
RT-009 E-commerce → Tienda online (20% core)
|
||||
RT-010 Facturación → CFDI 4.0 (60% core)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
- **Backend:** NestJS + TypeORM + PostgreSQL
|
||||
- **Frontend POS:** React + PWA (offline-first)
|
||||
- **Base de Datos:** PostgreSQL 15+ (hereda ERP Core)
|
||||
- **Hardware:** Impresora térmica, lector de códigos, cajón
|
||||
|
||||
---
|
||||
|
||||
## Métricas Objetivo
|
||||
|
||||
| Métrica | Valor Objetivo |
|
||||
|---------|----------------|
|
||||
| Módulos | 10 |
|
||||
| Tablas Específicas | ~30 |
|
||||
| Tablas Heredadas | ~102 |
|
||||
| Story Points Est. | ~280 |
|
||||
| Tiempo Venta | < 30 segundos |
|
||||
| Disponibilidad | 99.9% |
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- ERP Core: `apps/erp-core/`
|
||||
- Herencia DB: `database/HERENCIA-ERP-CORE.md`
|
||||
- SPECS del Core: `HERENCIA-SPECS-CORE.md`
|
||||
- Inventarios: `orchestration/inventarios/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de visión oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,116 @@
|
||||
# Índice de Módulos - ERP Retail
|
||||
|
||||
**Versión:** 1.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Total Módulos:** 10
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Código | Nombre | Descripción | Reutilización Core | Estado |
|
||||
|--------|--------|-------------|-------------------|--------|
|
||||
| RT-001 | Fundamentos | Auth, Users, Tenants | 100% | PLANIFICADO |
|
||||
| RT-002 | POS | Punto de venta | 20% | PLANIFICADO |
|
||||
| RT-003 | Inventario | Stock multi-sucursal | 60% | PLANIFICADO |
|
||||
| RT-004 | Compras | Reabastecimiento | 80% | PLANIFICADO |
|
||||
| RT-005 | Clientes | Programa fidelidad | 40% | PLANIFICADO |
|
||||
| RT-006 | Precios | Promociones y descuentos | 30% | PLANIFICADO |
|
||||
| RT-007 | Caja | Arqueos y cortes | 10% | PLANIFICADO |
|
||||
| RT-008 | Reportes | Dashboard de ventas | 70% | PLANIFICADO |
|
||||
| RT-009 | E-commerce | Tienda online | 20% | PLANIFICADO |
|
||||
| RT-010 | Facturación | CFDI 4.0 | 60% | PLANIFICADO |
|
||||
|
||||
---
|
||||
|
||||
## Detalle por Módulo
|
||||
|
||||
### RT-001: Fundamentos
|
||||
**Herencia:** 100% del core
|
||||
- Usuarios por sucursal
|
||||
- Roles: Cajero, Supervisor, Gerente, Admin
|
||||
|
||||
### RT-002: POS
|
||||
**Herencia:** 20%
|
||||
- Venta rápida en mostrador
|
||||
- Múltiples formas de pago
|
||||
- Operación offline (PWA)
|
||||
- Integración con hardware
|
||||
|
||||
### RT-003: Inventario
|
||||
**Herencia:** 60%
|
||||
- Stock por sucursal
|
||||
- Transferencias entre tiendas
|
||||
- Conteos cíclicos
|
||||
- Alertas de mínimos
|
||||
|
||||
### RT-004: Compras
|
||||
**Herencia:** 80%
|
||||
- Órdenes de compra centralizadas
|
||||
- Distribución a sucursales
|
||||
- Control de proveedores
|
||||
|
||||
### RT-005: Clientes
|
||||
**Herencia:** 40%
|
||||
- Programa de lealtad
|
||||
- Puntos y recompensas
|
||||
- Historial de compras
|
||||
- Membresías
|
||||
|
||||
### RT-006: Precios
|
||||
**Herencia:** 30%
|
||||
- Listas de precios
|
||||
- Promociones temporales
|
||||
- Descuentos por volumen
|
||||
- Cupones
|
||||
|
||||
### RT-007: Caja
|
||||
**Herencia:** 10%
|
||||
- Sesiones de caja
|
||||
- Apertura/cierre
|
||||
- Arqueos
|
||||
- Movimientos de efectivo
|
||||
|
||||
### RT-008: Reportes
|
||||
**Herencia:** 70%
|
||||
- Dashboard de ventas
|
||||
- Análisis por sucursal
|
||||
- Top productos
|
||||
- Métricas de cajeros
|
||||
|
||||
### RT-009: E-commerce
|
||||
**Herencia:** 20%
|
||||
- Tienda online
|
||||
- Carrito de compras
|
||||
- Checkout
|
||||
- Sincronización de inventario
|
||||
|
||||
### RT-010: Facturación
|
||||
**Herencia:** 60%
|
||||
- CFDI 4.0
|
||||
- Timbrado automático
|
||||
- Notas de crédito
|
||||
- Reportes fiscales
|
||||
|
||||
---
|
||||
|
||||
## Story Points Estimados
|
||||
|
||||
| Módulo | SP Backend | SP Frontend | SP Total |
|
||||
|--------|-----------|-------------|----------|
|
||||
| RT-001 | 0 | 0 | 0 |
|
||||
| RT-002 | 34 | 21 | 55 |
|
||||
| RT-003 | 21 | 13 | 34 |
|
||||
| RT-004 | 13 | 8 | 21 |
|
||||
| RT-005 | 21 | 13 | 34 |
|
||||
| RT-006 | 21 | 13 | 34 |
|
||||
| RT-007 | 21 | 13 | 34 |
|
||||
| RT-008 | 13 | 13 | 26 |
|
||||
| RT-009 | 34 | 21 | 55 |
|
||||
| RT-010 | 21 | 8 | 29 |
|
||||
| **Total** | **199** | **123** | **322** |
|
||||
|
||||
---
|
||||
|
||||
**Índice de módulos oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,184 @@
|
||||
# Herencia de SPECS del Core - Retail
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Retail (RT)
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| SPECS Aplicables | 24/30 |
|
||||
| SPECS Obligatorias | 21 |
|
||||
| SPECS Opcionales | 3 |
|
||||
| SPECS No Aplican | 6 |
|
||||
| Estado Implementación | 0% |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Obligatorias (Deben Implementarse)
|
||||
|
||||
### P0 - Críticas
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | ir.sequence | 8 | PENDIENTE | RT-001, RT-002, RT-007 |
|
||||
| SPEC-VALORACION-INVENTARIO | FIFO/AVCO | 21 | PENDIENTE | RT-003 |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | API Keys + ACL | 31 | PENDIENTE | RT-001 |
|
||||
| SPEC-REPORTES-FINANCIEROS | Balance/P&L SAT | 13 | PENDIENTE | RT-008, RT-010 |
|
||||
| SPEC-NOMINA-BASICA | hr_payroll | 21 | PENDIENTE | RT-001 |
|
||||
| SPEC-GASTOS-EMPLEADOS | hr_expense | 13 | PENDIENTE | RT-001 |
|
||||
| SPEC-SCHEDULER-REPORTES | ir.cron + mail | 8 | PENDIENTE | RT-008 |
|
||||
|
||||
### P1 - Complementarias
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-CONTABILIDAD-ANALITICA | Centros de costo | 21 | PENDIENTE | RT-008 |
|
||||
| SPEC-CONCILIACION-BANCARIA | Conciliación | 21 | PENDIENTE | RT-007, RT-008 |
|
||||
| SPEC-TWO-FACTOR-AUTHENTICATION | 2FA | 13 | PENDIENTE | RT-001 |
|
||||
| SPEC-TRAZABILIDAD-LOTES-SERIES | Lotes/Series | 13 | PENDIENTE | RT-003 |
|
||||
| SPEC-PRICING-RULES | Reglas precio | 8 | PENDIENTE | RT-006 |
|
||||
| SPEC-BLANKET-ORDERS | Órdenes marco | 13 | PENDIENTE | RT-004 |
|
||||
| SPEC-INVENTARIOS-CICLICOS | Conteo cíclico | 13 | PENDIENTE | RT-003 |
|
||||
| SPEC-IMPUESTOS-AVANZADOS | IVA, ISR | 8 | PENDIENTE | RT-010 |
|
||||
| SPEC-PLANTILLAS-CUENTAS | Plan contable | 8 | PENDIENTE | RT-008 |
|
||||
| SPEC-TASAS-CAMBIO-AUTOMATICAS | Tipos cambio | 5 | PENDIENTE | RT-008 |
|
||||
| SPEC-ALERTAS-PRESUPUESTO | Alertas | 8 | PENDIENTE | RT-008 |
|
||||
| SPEC-RRHH-EVALUACIONES-SKILLS | Evaluaciones | 26 | PENDIENTE | RT-001 |
|
||||
| SPEC-LOCALIZACION-PAISES | Localización | 13 | PENDIENTE | RT-001, RT-010 |
|
||||
|
||||
### Patrones Técnicos
|
||||
|
||||
| SPEC | Patrón | SP | Estado | Aplicación |
|
||||
|------|--------|----:|--------|------------|
|
||||
| SPEC-MAIL-THREAD-TRACKING | mail.thread | 13 | PENDIENTE | Órdenes, Clientes |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | TransientModel | 8 | PENDIENTE | Wizards de cierre, arqueo |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Opcionales
|
||||
|
||||
| SPEC | Descripción | SP | Decisión | Razón |
|
||||
|------|-------------|----:|----------|-------|
|
||||
| SPEC-PORTAL-PROVEEDORES | Portal RFQ | 13 | EVALUAR | Para compras centralizadas |
|
||||
| SPEC-TAREAS-RECURRENTES | Recurrencia | 13 | EVALUAR | Para reorden automático |
|
||||
| SPEC-PRESUPUESTOS-REVISIONES | Aprobación | 8 | DIFERIR | Menos relevante en retail |
|
||||
|
||||
---
|
||||
|
||||
## SPECS No Aplicables
|
||||
|
||||
| SPEC | Razón |
|
||||
|------|-------|
|
||||
| SPEC-INTEGRACION-CALENDAR | No requiere calendario de citas |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | No hay proyectos largos |
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | No aplica para tickets POS |
|
||||
| SPEC-OAUTH2-SOCIAL-LOGIN | El personal usa login tradicional |
|
||||
| SPEC-CONSOLIDACION-FINANCIERA | Generalmente una empresa |
|
||||
|
||||
---
|
||||
|
||||
## Adaptaciones Requeridas
|
||||
|
||||
### Mapeo de Conceptos Core → Retail
|
||||
|
||||
| Concepto Core | Concepto Retail |
|
||||
|---------------|-----------------|
|
||||
| `sales.sale_orders` | Tickets POS |
|
||||
| `inventory.products` | Productos de venta |
|
||||
| `inventory.locations` | Sucursales |
|
||||
| `inventory.stock_moves` | Transferencias entre tiendas |
|
||||
| `core.partners` | Clientes con membresía |
|
||||
| `financial.payments` | Pagos en caja |
|
||||
|
||||
### Extensiones de Entidad
|
||||
|
||||
```sql
|
||||
-- Sucursales
|
||||
stores.branches (
|
||||
id UUID,
|
||||
location_id → inventory.locations,
|
||||
nombre VARCHAR,
|
||||
direccion TEXT,
|
||||
gerente_id → hr.employees,
|
||||
horario JSONB,
|
||||
activa BOOLEAN
|
||||
)
|
||||
|
||||
-- Sesiones de caja
|
||||
pos.cash_sessions (
|
||||
id UUID,
|
||||
branch_id → branches,
|
||||
cajero_id → hr.employees,
|
||||
caja_id → cash_registers,
|
||||
fecha_apertura TIMESTAMPTZ,
|
||||
fecha_cierre TIMESTAMPTZ,
|
||||
saldo_inicial DECIMAL,
|
||||
saldo_final DECIMAL,
|
||||
estado ENUM
|
||||
)
|
||||
|
||||
-- Tickets POS
|
||||
pos.pos_orders (
|
||||
id UUID,
|
||||
session_id → cash_sessions,
|
||||
sale_order_id → sales.sale_orders,
|
||||
numero_ticket VARCHAR,
|
||||
subtotal DECIMAL,
|
||||
descuentos DECIMAL,
|
||||
impuestos DECIMAL,
|
||||
total DECIMAL
|
||||
)
|
||||
|
||||
-- Programa de lealtad
|
||||
pricing.loyalty_programs (
|
||||
id UUID,
|
||||
nombre VARCHAR,
|
||||
tipo ENUM('puntos', 'cashback', 'descuento'),
|
||||
reglas JSONB,
|
||||
activo BOOLEAN
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plan de Implementación
|
||||
|
||||
### Fase 1: Fundamentos (SP: 52)
|
||||
1. SPEC-SISTEMA-SECUENCIAS
|
||||
2. SPEC-SEGURIDAD-API-KEYS-PERMISOS
|
||||
3. SPEC-TWO-FACTOR-AUTHENTICATION
|
||||
|
||||
### Fase 2: Inventario (SP: 55)
|
||||
4. SPEC-VALORACION-INVENTARIO
|
||||
5. SPEC-TRAZABILIDAD-LOTES-SERIES
|
||||
6. SPEC-INVENTARIOS-CICLICOS
|
||||
7. SPEC-PRICING-RULES
|
||||
|
||||
### Fase 3: Operaciones POS (SP: 21)
|
||||
8. SPEC-MAIL-THREAD-TRACKING
|
||||
9. SPEC-WIZARD-TRANSIENT-MODEL
|
||||
|
||||
### Fase 4: Financiero (SP: 65)
|
||||
10. SPEC-REPORTES-FINANCIEROS
|
||||
11. SPEC-CONTABILIDAD-ANALITICA
|
||||
12. SPEC-CONCILIACION-BANCARIA
|
||||
13. SPEC-IMPUESTOS-AVANZADOS
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- Documento Core: `erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md`
|
||||
- SPECS del Core: `erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/`
|
||||
- Herencia DB: `database/HERENCIA-ERP-CORE.md`
|
||||
- Directivas: `orchestration/directivas/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia de SPECS oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,95 @@
|
||||
# Inventarios - ERP Retail
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Nivel SIMCO:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Descripción
|
||||
|
||||
Este directorio contiene los inventarios YAML que sirven como **Single Source of Truth (SSOT)** para el proyecto ERP Retail. Estos archivos son la referencia canónica para métricas, trazabilidad y componentes del sistema.
|
||||
|
||||
---
|
||||
|
||||
## Archivos de Inventario
|
||||
|
||||
| Archivo | Descripción | Estado |
|
||||
|---------|-------------|--------|
|
||||
| [MASTER_INVENTORY.yml](./MASTER_INVENTORY.yml) | Inventario maestro con métricas globales | Completo |
|
||||
| [DATABASE_INVENTORY.yml](./DATABASE_INVENTORY.yml) | Inventario de objetos de base de datos | Planificado |
|
||||
| [BACKEND_INVENTORY.yml](./BACKEND_INVENTORY.yml) | Inventario de componentes backend | Planificado |
|
||||
| [FRONTEND_INVENTORY.yml](./FRONTEND_INVENTORY.yml) | Inventario de componentes frontend | Planificado |
|
||||
| [TRACEABILITY_MATRIX.yml](./TRACEABILITY_MATRIX.yml) | Matriz de trazabilidad RF->ET->US | Completo |
|
||||
| [DEPENDENCY_GRAPH.yml](./DEPENDENCY_GRAPH.yml) | Grafo de dependencias entre módulos | Completo |
|
||||
|
||||
---
|
||||
|
||||
## Herencia del Core
|
||||
|
||||
Este proyecto hereda del **ERP Core** (nivel 2B.1):
|
||||
|
||||
| Aspecto | Heredado | Específico |
|
||||
|---------|----------|------------|
|
||||
| **Tablas DB** | ~100 | Planificado |
|
||||
| **Schemas** | 8+ | Planificado |
|
||||
| **Specs** | 3 | - |
|
||||
|
||||
### Specs Heredadas
|
||||
|
||||
1. SPEC-PRICING-RULES.md
|
||||
2. SPEC-INVENTARIOS-CICLICOS.md
|
||||
3. SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
|
||||
---
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
### Métricas del Proyecto
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| **Módulos** | 5 (RT-001 a RT-005) |
|
||||
| **Estado** | PLANIFICACION_COMPLETA |
|
||||
| **Completitud** | 15% |
|
||||
|
||||
### Dominio del Negocio
|
||||
|
||||
- Punto de venta (POS)
|
||||
- Inventario multi-sucursal
|
||||
- Gestión de precios y promociones
|
||||
- Control de cajas
|
||||
|
||||
---
|
||||
|
||||
## Directivas Específicas
|
||||
|
||||
1. [DIRECTIVA-PUNTO-VENTA.md](../directivas/DIRECTIVA-PUNTO-VENTA.md)
|
||||
2. [DIRECTIVA-INVENTARIO-SUCURSALES.md](../directivas/DIRECTIVA-INVENTARIO-SUCURSALES.md)
|
||||
|
||||
---
|
||||
|
||||
## Configuración de Puertos (Planificado)
|
||||
|
||||
| Servicio | Puerto |
|
||||
|----------|--------|
|
||||
| Backend API | 3400 |
|
||||
| Frontend Web | 5177 |
|
||||
| POS App | 5178 |
|
||||
|
||||
---
|
||||
|
||||
## Alineación con ERP Core
|
||||
|
||||
Estos inventarios siguen la misma estructura que:
|
||||
- `/erp-core/orchestration/inventarios/` (proyecto padre)
|
||||
|
||||
### Referencias
|
||||
|
||||
- Suite Master: `orchestration/inventarios/SUITE_MASTER_INVENTORY.yml`
|
||||
- Core: `apps/erp-core/orchestration/inventarios/`
|
||||
- Status Global: `orchestration/inventarios/STATUS.yml`
|
||||
|
||||
---
|
||||
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,182 @@
|
||||
# Herencia de Base de Datos - ERP Core -> Vidrio Templado
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Vidrio Templado
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## RESUMEN
|
||||
|
||||
La vertical de Vidrio Templado hereda los schemas base del ERP Core y extiende con schemas específicos del dominio de producción de vidrio.
|
||||
|
||||
**Ubicación DDL Core:** `apps/erp-core/database/ddl/`
|
||||
|
||||
---
|
||||
|
||||
## ARQUITECTURA DE HERENCIA
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ERP CORE (Base) │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ auth │ │ core │ │financial│ │inventory│ │ purchase │ │
|
||||
│ │ 26 tbl │ │ 12 tbl │ │ 15 tbl │ │ 15 tbl │ │ 8 tbl │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ sales │ │analytics│ │ system │ │
|
||||
│ │ 6 tbl │ │ 5 tbl │ │ 10 tbl │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ TOTAL: ~97 tablas heredadas │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HEREDA
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ VIDRIO TEMPLADO (Extensiones) │
|
||||
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
||||
│ │ production │ │ quality │ │ glass │ │
|
||||
│ │ management │ │ control │ │ inventory │ │
|
||||
│ │ (hornos) │ │ (inspección) │ │ (lotes) │ │
|
||||
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
||||
│ EXTENSIONES: ~25 tablas (planificadas) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SCHEMAS HEREDADOS DEL CORE
|
||||
|
||||
| Schema | Tablas | Uso en Vidrio Templado |
|
||||
|--------|--------|------------------------|
|
||||
| `auth` | 26 | Autenticación, usuarios, roles, permisos |
|
||||
| `core` | 12 | Partners (clientes), catálogos |
|
||||
| `financial` | 15 | Facturas, cuentas contables |
|
||||
| `inventory` | 15 | Base para materia prima y producto terminado |
|
||||
| `purchase` | 8 | Compras de materiales |
|
||||
| `sales` | 6 | Cotizaciones, órdenes de venta |
|
||||
| `analytics` | 5 | Centros de costo |
|
||||
| `system` | 10 | Mensajes, notificaciones |
|
||||
|
||||
**Total heredado:** ~97 tablas
|
||||
|
||||
---
|
||||
|
||||
## SCHEMAS ESPECÍFICOS DE VIDRIO TEMPLADO (Planificados)
|
||||
|
||||
### 1. Schema `production` (estimado 10+ tablas)
|
||||
|
||||
**Propósito:** Gestión de producción y hornos de templado
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
production.production_orders -- Órdenes de producción
|
||||
production.production_lines -- Líneas de producción (hornos)
|
||||
production.work_orders -- Órdenes de trabajo
|
||||
production.cutting_plans -- Planes de corte
|
||||
production.oven_schedules -- Programación de hornos
|
||||
production.temperature_logs -- Registros de temperatura
|
||||
```
|
||||
|
||||
### 2. Schema `quality` (estimado 8+ tablas)
|
||||
|
||||
**Propósito:** Control de calidad y trazabilidad
|
||||
|
||||
```sql
|
||||
-- Tablas principales planificadas:
|
||||
quality.inspections -- Inspecciones de calidad
|
||||
quality.defect_types -- Catálogo de defectos
|
||||
quality.quality_tests -- Pruebas de calidad
|
||||
quality.certifications -- Certificaciones de producto
|
||||
quality.non_conformities -- No conformidades
|
||||
```
|
||||
|
||||
### 3. Schema `glass` (estimado 7+ tablas)
|
||||
|
||||
**Propósito:** Inventario especializado de vidrio
|
||||
|
||||
```sql
|
||||
-- Extiende: inventory schema del core
|
||||
glass.glass_types -- Tipos de vidrio
|
||||
glass.glass_lots -- Lotes de producción
|
||||
glass.glass_dimensions -- Dimensiones estándar
|
||||
glass.raw_materials -- Materia prima
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SPECS DEL CORE APLICABLES
|
||||
|
||||
**Documento detallado:** `orchestration/00-guidelines/HERENCIA-SPECS-CORE.md`
|
||||
|
||||
### SPECS Obligatorias
|
||||
|
||||
| Spec Core | Aplicación en Vidrio Templado | SP | Estado |
|
||||
|-----------|------------------------------|----:|--------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | Foliado de órdenes y lotes | 8 | PENDIENTE |
|
||||
| SPEC-VALORACION-INVENTARIO | Costeo de materia prima y producto | 21 | PENDIENTE |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | Control de acceso | 31 | PENDIENTE |
|
||||
| SPEC-TRAZABILIDAD-LOTES-SERIES | Lotes de producción de vidrio | 13 | PENDIENTE |
|
||||
| SPEC-PRICING-RULES | Precios por dimensiones y tipo | 8 | PENDIENTE |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | Control de producción | 13 | PENDIENTE |
|
||||
| SPEC-MAIL-THREAD-TRACKING | Historial de órdenes | 13 | PENDIENTE |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | Wizards de corte y templado | 8 | PENDIENTE |
|
||||
|
||||
### SPECS Opcionales
|
||||
|
||||
| Spec Core | Decisión | Razón |
|
||||
|-----------|----------|-------|
|
||||
| SPEC-INVENTARIOS-CICLICOS | EVALUAR | Útil para materia prima |
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | EVALUAR | Certificados de calidad |
|
||||
|
||||
### SPECS No Aplican
|
||||
|
||||
| Spec Core | Razón |
|
||||
|-----------|-------|
|
||||
| SPEC-INTEGRACION-CALENDAR | No requiere calendario externo |
|
||||
| SPEC-CONSOLIDACION-FINANCIERA | Negocio de una sola planta |
|
||||
|
||||
---
|
||||
|
||||
## ORDEN DE EJECUCIÓN DDL (Futuro)
|
||||
|
||||
```bash
|
||||
# PASO 1: Cargar ERP Core (base)
|
||||
cd apps/erp-core/database
|
||||
./scripts/reset-database.sh --force
|
||||
|
||||
# PASO 2: Cargar extensiones de Vidrio Templado
|
||||
cd apps/verticales/vidrio-templado/database
|
||||
psql $DATABASE_URL -f init/00-extensions.sql
|
||||
psql $DATABASE_URL -f init/01-create-schemas.sql
|
||||
psql $DATABASE_URL -f init/02-production-tables.sql
|
||||
psql $DATABASE_URL -f init/03-quality-tables.sql
|
||||
psql $DATABASE_URL -f init/04-glass-inventory.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MAPEO DE NOMENCLATURA
|
||||
|
||||
| Core | Vidrio Templado |
|
||||
|------|-----------------|
|
||||
| `core.partners` | Clientes, proveedores |
|
||||
| `inventory.products` | Producto terminado base |
|
||||
| `inventory.locations` | Almacenes de vidrio |
|
||||
| `sales.sale_orders` | Pedidos de vidrio |
|
||||
| `purchase.purchase_orders` | Compras de materia prima |
|
||||
|
||||
---
|
||||
|
||||
## REFERENCIAS
|
||||
|
||||
- ERP Core DDL: `apps/erp-core/database/ddl/`
|
||||
- ERP Core README: `apps/erp-core/database/README.md`
|
||||
- Directivas: `orchestration/directivas/`
|
||||
- Inventarios: `orchestration/inventarios/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,104 @@
|
||||
# Visión General - ERP Vidrio Templado
|
||||
|
||||
**Versión:** 1.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Nivel:** 2B.2 (Vertical)
|
||||
|
||||
---
|
||||
|
||||
## Propósito del Sistema
|
||||
|
||||
Sistema ERP especializado para empresas de manufactura de vidrio templado, laminado y procesado. Gestiona todo el ciclo desde la cotización hasta el despacho, incluyendo control de producción, optimización de corte, control de hornos de templado y gestión de calidad.
|
||||
|
||||
---
|
||||
|
||||
## Dominio del Negocio
|
||||
|
||||
### Procesos Principales
|
||||
|
||||
1. **Cotización y Ventas**
|
||||
- Cotización por dimensiones, tipo de vidrio y acabados
|
||||
- Cálculo automático de precios por m²
|
||||
- Gestión de clientes y arquitectos
|
||||
|
||||
2. **Producción**
|
||||
- Órdenes de producción
|
||||
- Optimización de corte (nesting)
|
||||
- Control de hornos de templado
|
||||
- Programación de producción
|
||||
|
||||
3. **Inventario**
|
||||
- Control de materia prima (láminas de vidrio)
|
||||
- Trazabilidad de lotes
|
||||
- Gestión de producto terminado
|
||||
|
||||
4. **Control de Calidad**
|
||||
- Inspecciones de producto
|
||||
- Pruebas de fragmentación
|
||||
- Certificaciones
|
||||
|
||||
5. **Despacho**
|
||||
- Logística de entrega
|
||||
- Instalación (opcional)
|
||||
|
||||
---
|
||||
|
||||
## Tipos de Vidrio Manejados
|
||||
|
||||
| Tipo | Descripción |
|
||||
|------|-------------|
|
||||
| Templado | Tratamiento térmico para resistencia |
|
||||
| Laminado | Capas con PVB/EVA |
|
||||
| Insulado | Cámaras de aire |
|
||||
| Curvo | Templado con curvatura |
|
||||
| Esmerilado | Acabado mate |
|
||||
| Serigrafiado | Con diseños impresos |
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura de Módulos
|
||||
|
||||
```
|
||||
VT-001 Fundamentos → Auth, Users, Tenants (hereda 100% core)
|
||||
VT-002 Cotizaciones → Cotizador de vidrio (30% core)
|
||||
VT-003 Producción → Órdenes de producción (20% core)
|
||||
VT-004 Inventario → Stock de vidrio (70% core)
|
||||
VT-005 Corte → Optimización de corte (0% core - nuevo)
|
||||
VT-006 Templado → Control de hornos (0% core - nuevo)
|
||||
VT-007 Calidad → Inspecciones, QC (40% core)
|
||||
VT-008 Despacho → Logística (50% core)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stack Tecnológico
|
||||
|
||||
- **Backend:** NestJS + TypeORM + PostgreSQL
|
||||
- **Frontend:** React + TypeScript + Vite
|
||||
- **Base de Datos:** PostgreSQL 15+ (hereda ERP Core)
|
||||
- **Extensiones:** PostGIS (dimensiones)
|
||||
|
||||
---
|
||||
|
||||
## Métricas Objetivo
|
||||
|
||||
| Métrica | Valor Objetivo |
|
||||
|---------|----------------|
|
||||
| Módulos | 8 |
|
||||
| Tablas Específicas | ~25 |
|
||||
| Tablas Heredadas | ~97 |
|
||||
| Story Points Est. | ~200 |
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- ERP Core: `apps/erp-core/`
|
||||
- Herencia DB: `database/HERENCIA-ERP-CORE.md`
|
||||
- SPECS del Core: `HERENCIA-SPECS-CORE.md`
|
||||
- Inventarios: `orchestration/inventarios/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de visión oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,154 @@
|
||||
# Índice de Módulos - ERP Vidrio Templado
|
||||
|
||||
**Versión:** 1.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Total Módulos:** 8
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Código | Nombre | Descripción | Reutilización Core | Estado |
|
||||
|--------|--------|-------------|-------------------|--------|
|
||||
| VT-001 | Fundamentos | Auth, Users, Tenants | 100% | PLANIFICADO |
|
||||
| VT-002 | Cotizaciones | Cotizador de vidrio | 30% | PLANIFICADO |
|
||||
| VT-003 | Producción | Órdenes de producción | 20% | PLANIFICADO |
|
||||
| VT-004 | Inventario | Stock de vidrio y materia prima | 70% | PLANIFICADO |
|
||||
| VT-005 | Corte | Optimización de corte | 0% | PLANIFICADO |
|
||||
| VT-006 | Templado | Control de hornos | 0% | PLANIFICADO |
|
||||
| VT-007 | Calidad | Control de calidad | 40% | PLANIFICADO |
|
||||
| VT-008 | Despacho | Logística y entregas | 50% | PLANIFICADO |
|
||||
|
||||
---
|
||||
|
||||
## Detalle por Módulo
|
||||
|
||||
### VT-001: Fundamentos
|
||||
|
||||
**Herencia:** 100% del core (MGN-001 a MGN-004)
|
||||
**Propósito:** Autenticación, usuarios, roles, multi-tenancy
|
||||
|
||||
- Usuarios del sistema (operadores, supervisores, gerentes)
|
||||
- Roles y permisos por planta
|
||||
- Multi-tenancy para franquicias
|
||||
|
||||
### VT-002: Cotizaciones
|
||||
|
||||
**Herencia:** 30% del core (sales)
|
||||
**Propósito:** Cotización de productos de vidrio
|
||||
|
||||
- Cotizador por dimensiones (alto × ancho)
|
||||
- Tipos de vidrio y espesores
|
||||
- Cálculo de precios por m²
|
||||
- Acabados y procesamientos adicionales
|
||||
- Generación de PDF para cliente
|
||||
|
||||
### VT-003: Producción
|
||||
|
||||
**Herencia:** 20% del core (projects)
|
||||
**Propósito:** Gestión de órdenes de producción
|
||||
|
||||
- Órdenes de producción
|
||||
- Estados: borrador → programado → corte → templado → QC → terminado
|
||||
- Asignación de recursos
|
||||
- Tiempos de producción
|
||||
|
||||
### VT-004: Inventario
|
||||
|
||||
**Herencia:** 70% del core (inventory)
|
||||
**Propósito:** Control de materia prima y producto terminado
|
||||
|
||||
- Láminas de vidrio (materia prima)
|
||||
- Control de lotes por proveedor
|
||||
- Producto terminado
|
||||
- Merma y desperdicio
|
||||
- Alertas de reorden
|
||||
|
||||
### VT-005: Corte
|
||||
|
||||
**Herencia:** 0% - Módulo nuevo
|
||||
**Propósito:** Optimización de corte de vidrio
|
||||
|
||||
- Algoritmo de nesting para optimizar cortes
|
||||
- Planes de corte
|
||||
- Reducción de desperdicio
|
||||
- Integración con máquinas CNC (futuro)
|
||||
|
||||
### VT-006: Templado
|
||||
|
||||
**Herencia:** 0% - Módulo nuevo
|
||||
**Propósito:** Control de hornos de templado
|
||||
|
||||
- Programación de hornos
|
||||
- Parámetros de templado (temperatura, tiempo, velocidad)
|
||||
- Registro de ciclos de templado
|
||||
- Mantenimiento preventivo de hornos
|
||||
|
||||
### VT-007: Calidad
|
||||
|
||||
**Herencia:** 40% del core (system)
|
||||
**Propósito:** Control de calidad
|
||||
|
||||
- Inspecciones de producto
|
||||
- Pruebas de fragmentación
|
||||
- No conformidades
|
||||
- Certificaciones de producto
|
||||
- Trazabilidad de defectos
|
||||
|
||||
### VT-008: Despacho
|
||||
|
||||
**Herencia:** 50% del core (inventory, sales)
|
||||
**Propósito:** Logística de entregas
|
||||
|
||||
- Programación de entregas
|
||||
- Rutas de entrega
|
||||
- Confirmación de recepción
|
||||
- Instalación (opcional)
|
||||
- Evidencia fotográfica
|
||||
|
||||
---
|
||||
|
||||
## Dependencias entre Módulos
|
||||
|
||||
```
|
||||
VT-001 (Fundamentos)
|
||||
↓
|
||||
VT-002 (Cotizaciones) ←→ VT-004 (Inventario)
|
||||
↓
|
||||
VT-003 (Producción)
|
||||
↓
|
||||
VT-005 (Corte) → VT-006 (Templado)
|
||||
↓
|
||||
VT-007 (Calidad)
|
||||
↓
|
||||
VT-008 (Despacho)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Story Points Estimados
|
||||
|
||||
| Módulo | SP Backend | SP Frontend | SP Total |
|
||||
|--------|-----------|-------------|----------|
|
||||
| VT-001 | 0 (hereda) | 0 (hereda) | 0 |
|
||||
| VT-002 | 21 | 13 | 34 |
|
||||
| VT-003 | 21 | 13 | 34 |
|
||||
| VT-004 | 13 | 8 | 21 |
|
||||
| VT-005 | 34 | 13 | 47 |
|
||||
| VT-006 | 21 | 13 | 34 |
|
||||
| VT-007 | 13 | 8 | 21 |
|
||||
| VT-008 | 13 | 8 | 21 |
|
||||
| **Total** | **136** | **76** | **212** |
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- Documentación por módulo: `VT-XXX-nombre/README.md`
|
||||
- SPECS heredadas: `orchestration/00-guidelines/HERENCIA-SPECS-CORE.md`
|
||||
- Visión: `00-vision-general/VISION-VIDRIO.md`
|
||||
|
||||
---
|
||||
|
||||
**Índice de módulos oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,21 @@
|
||||
# VT-001: Fundamentos
|
||||
|
||||
**Módulo:** Fundamentos
|
||||
**Herencia:** 100% del ERP Core
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
## Descripción
|
||||
Módulo base que hereda completamente del ERP Core. Proporciona autenticación, gestión de usuarios, roles y multi-tenancy.
|
||||
|
||||
## Componentes Heredados
|
||||
- MGN-001: Auth (JWT, OAuth2, 2FA)
|
||||
- MGN-002: Users (CRUD, perfiles)
|
||||
- MGN-003: Roles (RBAC)
|
||||
- MGN-004: Tenants (Multi-tenancy, RLS)
|
||||
|
||||
## Configuración Específica
|
||||
- Roles por defecto: Operador, Supervisor, Gerente, Administrador
|
||||
- Permisos por módulo de vidrio
|
||||
|
||||
## Referencias
|
||||
- ERP Core: `apps/erp-core/`
|
||||
@ -0,0 +1,26 @@
|
||||
# VT-002: Cotizaciones
|
||||
|
||||
**Módulo:** Cotizaciones de Vidrio
|
||||
**Herencia:** 30% del ERP Core (sales)
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
## Descripción
|
||||
Cotizador especializado para productos de vidrio. Calcula precios por dimensiones, tipo de vidrio, espesor y acabados.
|
||||
|
||||
## Funcionalidades
|
||||
- Cotización por dimensiones (alto × ancho × espesor)
|
||||
- Catálogo de tipos de vidrio
|
||||
- Acabados y procesamientos adicionales
|
||||
- Cálculo automático de precios por m²
|
||||
- Generación de PDF
|
||||
- Conversión a orden de producción
|
||||
|
||||
## Entidades
|
||||
- `quotes.quotes` - Cotizaciones
|
||||
- `quotes.quote_lines` - Líneas de cotización
|
||||
- `quotes.glass_types` - Tipos de vidrio
|
||||
- `quotes.finishes` - Acabados disponibles
|
||||
|
||||
## SPECS Aplicables
|
||||
- SPEC-PRICING-RULES
|
||||
- SPEC-MAIL-THREAD-TRACKING
|
||||
@ -0,0 +1,33 @@
|
||||
# VT-003: Producción
|
||||
|
||||
**Módulo:** Órdenes de Producción
|
||||
**Herencia:** 20% del ERP Core (projects)
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
## Descripción
|
||||
Gestión de órdenes de producción desde la cotización aprobada hasta el producto terminado.
|
||||
|
||||
## Funcionalidades
|
||||
- Creación de órdenes desde cotizaciones
|
||||
- Estados de producción
|
||||
- Asignación de recursos
|
||||
- Programación de producción
|
||||
- Seguimiento de tiempos
|
||||
|
||||
## Estados del Flujo
|
||||
1. `draft` - Borrador
|
||||
2. `scheduled` - Programado
|
||||
3. `cutting` - En corte
|
||||
4. `tempering` - En templado
|
||||
5. `quality` - Control de calidad
|
||||
6. `done` - Terminado
|
||||
7. `delivered` - Entregado
|
||||
|
||||
## Entidades
|
||||
- `production.production_orders` - Órdenes de producción
|
||||
- `production.production_lines` - Líneas de horno
|
||||
- `production.work_orders` - Órdenes de trabajo
|
||||
|
||||
## SPECS Aplicables
|
||||
- SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN
|
||||
- SPEC-TAREAS-RECURRENTES
|
||||
@ -0,0 +1,27 @@
|
||||
# VT-004: Inventario
|
||||
|
||||
**Módulo:** Inventario de Vidrio
|
||||
**Herencia:** 70% del ERP Core (inventory)
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
## Descripción
|
||||
Control de materia prima (láminas de vidrio) y producto terminado con trazabilidad de lotes.
|
||||
|
||||
## Funcionalidades
|
||||
- Control de láminas de materia prima
|
||||
- Trazabilidad de lotes por proveedor
|
||||
- Producto terminado
|
||||
- Control de merma y desperdicio
|
||||
- Alertas de reorden
|
||||
- Valorización FIFO/AVCO
|
||||
|
||||
## Entidades
|
||||
- `glass.glass_inventory` - Inventario de vidrio
|
||||
- `glass.glass_lots` - Lotes de materia prima
|
||||
- `glass.raw_materials` - Láminas de vidrio
|
||||
- `glass.finished_products` - Producto terminado
|
||||
|
||||
## SPECS Aplicables
|
||||
- SPEC-VALORACION-INVENTARIO
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES
|
||||
- SPEC-INVENTARIOS-CICLICOS (opcional)
|
||||
@ -0,0 +1,26 @@
|
||||
# VT-005: Corte
|
||||
|
||||
**Módulo:** Optimización de Corte
|
||||
**Herencia:** 0% - Módulo nuevo
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
## Descripción
|
||||
Módulo especializado para optimización de cortes de vidrio (nesting) que minimiza el desperdicio de materia prima.
|
||||
|
||||
## Funcionalidades
|
||||
- Algoritmo de nesting para optimizar cortes
|
||||
- Planes de corte por lámina
|
||||
- Cálculo de desperdicio
|
||||
- Visualización de patrones de corte
|
||||
- Integración con CNC (futuro)
|
||||
- Histórico de eficiencia
|
||||
|
||||
## Entidades
|
||||
- `cutting.cutting_plans` - Planes de corte
|
||||
- `cutting.cutting_patterns` - Patrones optimizados
|
||||
- `cutting.waste_records` - Registro de desperdicio
|
||||
|
||||
## Algoritmos
|
||||
- First Fit Decreasing (FFD)
|
||||
- Guillotine cuts
|
||||
- Optimización por rotación de piezas
|
||||
@ -0,0 +1,28 @@
|
||||
# VT-006: Templado
|
||||
|
||||
**Módulo:** Control de Hornos
|
||||
**Herencia:** 0% - Módulo nuevo
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
## Descripción
|
||||
Control de hornos de templado con registro de parámetros y ciclos de producción.
|
||||
|
||||
## Funcionalidades
|
||||
- Programación de hornos
|
||||
- Registro de parámetros (temperatura, tiempo, velocidad)
|
||||
- Ciclos de templado por tipo de vidrio
|
||||
- Registro de logs de producción
|
||||
- Mantenimiento preventivo
|
||||
- Alertas de parámetros fuera de rango
|
||||
|
||||
## Entidades
|
||||
- `tempering.ovens` - Hornos de templado
|
||||
- `tempering.oven_schedules` - Programación
|
||||
- `tempering.tempering_cycles` - Ciclos de templado
|
||||
- `tempering.temperature_logs` - Logs de temperatura
|
||||
- `tempering.maintenance_records` - Mantenimientos
|
||||
|
||||
## Parámetros de Control
|
||||
- Temperatura de calentamiento: 620-720°C
|
||||
- Tiempo en horno: según espesor
|
||||
- Velocidad de enfriamiento: presión de aire
|
||||
@ -0,0 +1,28 @@
|
||||
# VT-007: Calidad
|
||||
|
||||
**Módulo:** Control de Calidad
|
||||
**Herencia:** 40% del ERP Core (system)
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
## Descripción
|
||||
Control de calidad para producto de vidrio templado, incluyendo inspecciones, pruebas de fragmentación y certificaciones.
|
||||
|
||||
## Funcionalidades
|
||||
- Inspecciones de producto
|
||||
- Pruebas de fragmentación (NMX-R-2-1989)
|
||||
- Registro de no conformidades
|
||||
- Certificaciones de producto
|
||||
- Trazabilidad de defectos
|
||||
- Reportes de calidad
|
||||
|
||||
## Entidades
|
||||
- `quality.inspections` - Inspecciones
|
||||
- `quality.fragmentation_tests` - Pruebas de fragmentación
|
||||
- `quality.non_conformities` - No conformidades
|
||||
- `quality.certifications` - Certificaciones
|
||||
- `quality.defect_types` - Tipos de defectos
|
||||
|
||||
## Criterios de Aceptación
|
||||
- Fragmentación: mín 40 partículas en 5×5cm
|
||||
- Defectos visuales: según tolerancias
|
||||
- Dimensiones: ±2mm
|
||||
@ -0,0 +1,31 @@
|
||||
# VT-008: Despacho
|
||||
|
||||
**Módulo:** Despacho y Logística
|
||||
**Herencia:** 50% del ERP Core (inventory, sales)
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
## Descripción
|
||||
Gestión de entregas de producto terminado, incluyendo rutas, confirmaciones y evidencia.
|
||||
|
||||
## Funcionalidades
|
||||
- Programación de entregas
|
||||
- Asignación de rutas
|
||||
- Confirmación de recepción
|
||||
- Evidencia fotográfica
|
||||
- Instalación (opcional)
|
||||
- Actas de entrega
|
||||
|
||||
## Entidades
|
||||
- `delivery.deliveries` - Entregas
|
||||
- `delivery.delivery_lines` - Líneas de entrega
|
||||
- `delivery.routes` - Rutas
|
||||
- `delivery.confirmations` - Confirmaciones
|
||||
- `delivery.installations` - Instalaciones
|
||||
|
||||
## Flujo de Entrega
|
||||
1. Programación de entrega
|
||||
2. Asignación de ruta y vehículo
|
||||
3. Carga de producto
|
||||
4. Entrega en sitio
|
||||
5. Confirmación con evidencia
|
||||
6. Cierre de entrega
|
||||
@ -0,0 +1,65 @@
|
||||
# Épica: Fundamentos del Sistema
|
||||
|
||||
**Código:** EPIC-VT-001
|
||||
**Módulos:** VT-001 a VT-002
|
||||
**Estado:** PLANIFICADO
|
||||
|
||||
---
|
||||
|
||||
## Descripción
|
||||
|
||||
Implementación de los módulos fundacionales del ERP Vidrio Templado, incluyendo la configuración inicial del sistema heredado del core y el módulo de cotizaciones.
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Configurar el ambiente de desarrollo heredando del ERP Core
|
||||
2. Implementar el cotizador de vidrio con cálculo por m²
|
||||
3. Establecer el catálogo de tipos de vidrio y acabados
|
||||
|
||||
---
|
||||
|
||||
## Módulos Incluidos
|
||||
|
||||
| Módulo | Descripción | SP Estimados |
|
||||
|--------|-------------|--------------|
|
||||
| VT-001 | Fundamentos (hereda core) | 0 |
|
||||
| VT-002 | Cotizaciones | 34 |
|
||||
|
||||
---
|
||||
|
||||
## User Stories Principales
|
||||
|
||||
1. Como usuario, quiero iniciar sesión en el sistema de vidrio templado
|
||||
2. Como vendedor, quiero crear una cotización de vidrio por dimensiones
|
||||
3. Como vendedor, quiero calcular el precio automático por m²
|
||||
4. Como vendedor, quiero generar un PDF de cotización para el cliente
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] Sistema de autenticación funcional (heredado)
|
||||
- [ ] CRUD de cotizaciones implementado
|
||||
- [ ] Cálculo de precios por dimensiones correcto
|
||||
- [ ] Generación de PDF funcional
|
||||
- [ ] Catálogo de tipos de vidrio completo
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
- ERP Core instalado y funcional
|
||||
- Base de datos configurada con schemas heredados
|
||||
|
||||
---
|
||||
|
||||
## Story Points Totales
|
||||
|
||||
**34 SP** (excluyendo fundamentos heredados)
|
||||
|
||||
---
|
||||
|
||||
**Épica fundacional**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,175 @@
|
||||
# Herencia de SPECS del Core - Vidrio Templado
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Versión:** 1.0
|
||||
**Vertical:** Vidrio Templado (VT)
|
||||
**Nivel:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| SPECS Aplicables | 25/30 |
|
||||
| SPECS Obligatorias | 22 |
|
||||
| SPECS Opcionales | 3 |
|
||||
| SPECS No Aplican | 5 |
|
||||
| Estado Implementación | 0% |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Obligatorias (Deben Implementarse)
|
||||
|
||||
### P0 - Críticas
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-SISTEMA-SECUENCIAS | ir.sequence | 8 | PENDIENTE | VT-001, VT-002 |
|
||||
| SPEC-VALORACION-INVENTARIO | FIFO/AVCO | 21 | PENDIENTE | VT-004 |
|
||||
| SPEC-SEGURIDAD-API-KEYS-PERMISOS | API Keys + ACL | 31 | PENDIENTE | VT-001 |
|
||||
| SPEC-REPORTES-FINANCIEROS | Balance/P&L SAT | 13 | PENDIENTE | VT-008 |
|
||||
| SPEC-PORTAL-PROVEEDORES | Portal RFQ | 13 | PENDIENTE | VT-004 |
|
||||
| SPEC-NOMINA-BASICA | hr_payroll | 21 | PENDIENTE | VT-001 |
|
||||
| SPEC-GASTOS-EMPLEADOS | hr_expense | 13 | PENDIENTE | VT-001 |
|
||||
| SPEC-TAREAS-RECURRENTES | project.task.recurrence | 13 | PENDIENTE | VT-003 |
|
||||
| SPEC-SCHEDULER-REPORTES | ir.cron + mail | 8 | PENDIENTE | VT-008 |
|
||||
|
||||
### P1 - Complementarias
|
||||
|
||||
| SPEC | Gap Original | SP | Estado | Módulos Afectados |
|
||||
|------|-------------|----:|--------|-------------------|
|
||||
| SPEC-CONTABILIDAD-ANALITICA | Centros de costo | 21 | PENDIENTE | VT-008 |
|
||||
| SPEC-CONCILIACION-BANCARIA | Conciliación | 21 | PENDIENTE | VT-008 |
|
||||
| SPEC-TWO-FACTOR-AUTHENTICATION | 2FA | 13 | PENDIENTE | VT-001 |
|
||||
| SPEC-TRAZABILIDAD-LOTES-SERIES | Lotes/Series | 13 | PENDIENTE | VT-004, VT-007 |
|
||||
| SPEC-PRICING-RULES | Reglas precio | 8 | PENDIENTE | VT-002 |
|
||||
| SPEC-BLANKET-ORDERS | Órdenes marco | 13 | PENDIENTE | VT-004 |
|
||||
| SPEC-IMPUESTOS-AVANZADOS | IVA, ISR | 8 | PENDIENTE | VT-008 |
|
||||
| SPEC-PLANTILLAS-CUENTAS | Plan contable | 8 | PENDIENTE | VT-008 |
|
||||
| SPEC-TASAS-CAMBIO-AUTOMATICAS | Tipos cambio | 5 | PENDIENTE | VT-008 |
|
||||
| SPEC-ALERTAS-PRESUPUESTO | Alertas | 8 | PENDIENTE | VT-002, VT-003 |
|
||||
| SPEC-PRESUPUESTOS-REVISIONES | Aprobación | 8 | PENDIENTE | VT-002 |
|
||||
| SPEC-RRHH-EVALUACIONES-SKILLS | Evaluaciones | 26 | PENDIENTE | VT-001 |
|
||||
| SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN | Burndown | 13 | PENDIENTE | VT-003 |
|
||||
| SPEC-LOCALIZACION-PAISES | Localización | 13 | PENDIENTE | VT-001 |
|
||||
|
||||
### Patrones Técnicos
|
||||
|
||||
| SPEC | Patrón | SP | Estado | Aplicación |
|
||||
|------|--------|----:|--------|------------|
|
||||
| SPEC-MAIL-THREAD-TRACKING | mail.thread | 13 | PENDIENTE | Órdenes producción, Cotizaciones |
|
||||
| SPEC-WIZARD-TRANSIENT-MODEL | TransientModel | 8 | PENDIENTE | Wizards de corte, templado |
|
||||
|
||||
---
|
||||
|
||||
## SPECS Opcionales
|
||||
|
||||
| SPEC | Descripción | SP | Decisión | Razón |
|
||||
|------|-------------|----:|----------|-------|
|
||||
| SPEC-FIRMA-ELECTRONICA-NOM151 | e.firma | 13 | EVALUAR | Para certificados de calidad |
|
||||
| SPEC-OAUTH2-SOCIAL-LOGIN | OAuth2 | 8 | DIFERIR | No prioritario |
|
||||
| SPEC-INVENTARIOS-CICLICOS | Conteo cíclico | 13 | EVALUAR | Útil para materia prima |
|
||||
|
||||
---
|
||||
|
||||
## SPECS No Aplicables
|
||||
|
||||
| SPEC | Razón |
|
||||
|------|-------|
|
||||
| SPEC-INTEGRACION-CALENDAR | No requiere calendario externo |
|
||||
| SPEC-CONSOLIDACION-FINANCIERA | Negocio de una sola planta |
|
||||
|
||||
---
|
||||
|
||||
## Adaptaciones Requeridas
|
||||
|
||||
### Mapeo de Conceptos Core → Vidrio
|
||||
|
||||
| Concepto Core | Concepto Vidrio |
|
||||
|---------------|-----------------|
|
||||
| `sales.sale_orders` | Pedidos de vidrio |
|
||||
| `inventory.products` | Tipos de vidrio (templado, laminado, etc.) |
|
||||
| `inventory.lots` | Lotes de producción |
|
||||
| `projects.projects` | Órdenes de producción |
|
||||
| `projects.tasks` | Etapas (corte, templado, inspección) |
|
||||
|
||||
### Extensiones de Entidad
|
||||
|
||||
```sql
|
||||
-- Tipos de vidrio
|
||||
glass.glass_types (
|
||||
product_id → inventory.products,
|
||||
tipo ENUM('templado', 'laminado', 'insulado', 'curvo'),
|
||||
espesor_mm DECIMAL,
|
||||
color VARCHAR,
|
||||
propiedades JSONB
|
||||
)
|
||||
|
||||
-- Órdenes de producción
|
||||
production.production_orders (
|
||||
id UUID,
|
||||
sale_order_id → sales.sale_orders,
|
||||
tipo_vidrio_id → glass_types,
|
||||
dimensiones JSONB,
|
||||
cantidad INTEGER,
|
||||
estado ENUM
|
||||
)
|
||||
|
||||
-- Parámetros de horno
|
||||
production.oven_parameters (
|
||||
production_order_id → production_orders,
|
||||
temperatura_c INTEGER,
|
||||
tiempo_minutos INTEGER,
|
||||
velocidad_enfriamiento DECIMAL,
|
||||
fecha_templado TIMESTAMPTZ
|
||||
)
|
||||
|
||||
-- Inspecciones de calidad
|
||||
quality.inspections (
|
||||
id UUID,
|
||||
production_order_id → production_orders,
|
||||
tipo_inspeccion ENUM,
|
||||
resultado ENUM('aprobado', 'rechazado', 'condicional'),
|
||||
observaciones TEXT
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plan de Implementación
|
||||
|
||||
### Fase 1: Fundamentos (SP: 52)
|
||||
1. SPEC-SISTEMA-SECUENCIAS
|
||||
2. SPEC-SEGURIDAD-API-KEYS-PERMISOS
|
||||
3. SPEC-TWO-FACTOR-AUTHENTICATION
|
||||
|
||||
### Fase 2: Producción (SP: 55)
|
||||
4. SPEC-VALORACION-INVENTARIO
|
||||
5. SPEC-TRAZABILIDAD-LOTES-SERIES
|
||||
6. SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN
|
||||
7. SPEC-PRICING-RULES
|
||||
|
||||
### Fase 3: Operaciones (SP: 34)
|
||||
8. SPEC-MAIL-THREAD-TRACKING
|
||||
9. SPEC-WIZARD-TRANSIENT-MODEL
|
||||
10. SPEC-TAREAS-RECURRENTES
|
||||
|
||||
### Fase 4: Financiero (SP: 65)
|
||||
11. SPEC-REPORTES-FINANCIEROS
|
||||
12. SPEC-CONTABILIDAD-ANALITICA
|
||||
13. SPEC-CONCILIACION-BANCARIA
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- Documento Core: `erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md`
|
||||
- SPECS del Core: `erp-core/docs/04-modelado/especificaciones-tecnicas/transversal/`
|
||||
- Herencia DB: `database/HERENCIA-ERP-CORE.md`
|
||||
- Directivas: `orchestration/directivas/`
|
||||
|
||||
---
|
||||
|
||||
**Documento de herencia de SPECS oficial**
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -0,0 +1,94 @@
|
||||
# Inventarios - ERP Vidrio Templado
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Nivel SIMCO:** 2B.2
|
||||
|
||||
---
|
||||
|
||||
## Descripción
|
||||
|
||||
Este directorio contiene los inventarios YAML que sirven como **Single Source of Truth (SSOT)** para el proyecto ERP Vidrio Templado. Estos archivos son la referencia canónica para métricas, trazabilidad y componentes del sistema.
|
||||
|
||||
---
|
||||
|
||||
## Archivos de Inventario
|
||||
|
||||
| Archivo | Descripción | Estado |
|
||||
|---------|-------------|--------|
|
||||
| [MASTER_INVENTORY.yml](./MASTER_INVENTORY.yml) | Inventario maestro con métricas globales | Completo |
|
||||
| [DATABASE_INVENTORY.yml](./DATABASE_INVENTORY.yml) | Inventario de objetos de base de datos | Planificado |
|
||||
| [BACKEND_INVENTORY.yml](./BACKEND_INVENTORY.yml) | Inventario de componentes backend | Planificado |
|
||||
| [FRONTEND_INVENTORY.yml](./FRONTEND_INVENTORY.yml) | Inventario de componentes frontend | Planificado |
|
||||
| [TRACEABILITY_MATRIX.yml](./TRACEABILITY_MATRIX.yml) | Matriz de trazabilidad RF->ET->US | Completo |
|
||||
| [DEPENDENCY_GRAPH.yml](./DEPENDENCY_GRAPH.yml) | Grafo de dependencias entre módulos | Completo |
|
||||
|
||||
---
|
||||
|
||||
## Herencia del Core
|
||||
|
||||
Este proyecto hereda del **ERP Core** (nivel 2B.1):
|
||||
|
||||
| Aspecto | Heredado | Específico |
|
||||
|---------|----------|------------|
|
||||
| **Tablas DB** | ~100 | Planificado |
|
||||
| **Schemas** | 8+ | Planificado |
|
||||
| **Specs** | 3 | - |
|
||||
|
||||
### Specs Heredadas
|
||||
|
||||
1. SPEC-VALORACION-INVENTARIO.md
|
||||
2. SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
3. SPEC-INVENTARIOS-CICLICOS.md
|
||||
|
||||
---
|
||||
|
||||
## Resumen Ejecutivo
|
||||
|
||||
### Métricas del Proyecto
|
||||
|
||||
| Métrica | Valor |
|
||||
|---------|-------|
|
||||
| **Módulos** | 5 (VT-001 a VT-005) |
|
||||
| **Estado** | PLANIFICACION_COMPLETA |
|
||||
| **Completitud** | 15% |
|
||||
|
||||
### Dominio del Negocio
|
||||
|
||||
- Producción de vidrio templado
|
||||
- Control de calidad
|
||||
- Trazabilidad de lotes
|
||||
- Gestión de hornos y procesos
|
||||
|
||||
---
|
||||
|
||||
## Directivas Específicas
|
||||
|
||||
1. [DIRECTIVA-PRODUCCION-VIDRIO.md](../directivas/DIRECTIVA-PRODUCCION-VIDRIO.md)
|
||||
2. [DIRECTIVA-CONTROL-CALIDAD.md](../directivas/DIRECTIVA-CONTROL-CALIDAD.md)
|
||||
|
||||
---
|
||||
|
||||
## Configuración de Puertos (Planificado)
|
||||
|
||||
| Servicio | Puerto |
|
||||
|----------|--------|
|
||||
| Backend API | 3300 |
|
||||
| Frontend Web | 5176 |
|
||||
|
||||
---
|
||||
|
||||
## Alineación con ERP Core
|
||||
|
||||
Estos inventarios siguen la misma estructura que:
|
||||
- `/erp-core/orchestration/inventarios/` (proyecto padre)
|
||||
|
||||
### Referencias
|
||||
|
||||
- Suite Master: `orchestration/inventarios/SUITE_MASTER_INVENTORY.yml`
|
||||
- Core: `apps/erp-core/orchestration/inventarios/`
|
||||
- Status Global: `orchestration/inventarios/STATUS.yml`
|
||||
|
||||
---
|
||||
|
||||
**Última actualización:** 2025-12-08
|
||||
@ -123,39 +123,114 @@ referencias_erp_core:
|
||||
referencias_verticales:
|
||||
construccion:
|
||||
ubicacion_base: apps/verticales/construccion/
|
||||
documentacion: docs/
|
||||
nivel: 2B.2
|
||||
estado: EN_DESARROLLO
|
||||
completitud: 40%
|
||||
documentacion: docs/ (449 archivos)
|
||||
orchestration: orchestration/
|
||||
total_archivos: 403
|
||||
|
||||
vidrio_templado:
|
||||
ubicacion_base: apps/verticales/vidrio-templado/
|
||||
documentacion: docs/
|
||||
orchestration: orchestration/
|
||||
estado: estructura_base
|
||||
inventarios:
|
||||
ubicacion: orchestration/inventarios/
|
||||
archivos: 6
|
||||
readme: true
|
||||
database:
|
||||
ubicacion: database/
|
||||
herencia: database/HERENCIA-ERP-CORE.md
|
||||
ddl_implementado: true
|
||||
tablas_especificas: 33
|
||||
backend:
|
||||
porcentaje: 15%
|
||||
entities: 12
|
||||
services: 2
|
||||
controllers: 2
|
||||
directivas:
|
||||
- directivas/DIRECTIVA-CONTROL-OBRA.md
|
||||
- directivas/DIRECTIVA-ESTIMACIONES.md
|
||||
- directivas/DIRECTIVA-INTEGRACION-INFONAVIT.md
|
||||
|
||||
mecanicas_diesel:
|
||||
ubicacion_base: apps/verticales/mecanicas-diesel/
|
||||
nivel: 2B.2
|
||||
estado: DDL_IMPLEMENTADO
|
||||
completitud: 20%
|
||||
documentacion: docs/ (75 archivos)
|
||||
orchestration: orchestration/
|
||||
inventarios:
|
||||
ubicacion: orchestration/inventarios/
|
||||
archivos: 6
|
||||
readme: true
|
||||
database:
|
||||
ubicacion: database/
|
||||
herencia: database/HERENCIA-ERP-CORE.md
|
||||
ddl_implementado: true
|
||||
lineas_sql: 1561
|
||||
directivas:
|
||||
- directivas/DIRECTIVA-ORDENES-TRABAJO.md
|
||||
- directivas/DIRECTIVA-INVENTARIO-REFACCIONES.md
|
||||
|
||||
vidrio_templado:
|
||||
ubicacion_base: apps/verticales/vidrio-templado/
|
||||
nivel: 2B.2
|
||||
estado: PLANIFICACION_COMPLETA
|
||||
completitud: 15%
|
||||
documentacion: docs/
|
||||
orchestration: orchestration/
|
||||
estado: estructura_base
|
||||
inventarios:
|
||||
ubicacion: orchestration/inventarios/
|
||||
archivos: 6
|
||||
readme: true
|
||||
database:
|
||||
ubicacion: database/
|
||||
herencia: database/HERENCIA-ERP-CORE.md
|
||||
ddl_implementado: false
|
||||
directivas:
|
||||
- directivas/DIRECTIVA-PRODUCCION-VIDRIO.md
|
||||
- directivas/DIRECTIVA-CONTROL-CALIDAD.md
|
||||
|
||||
retail:
|
||||
ubicacion_base: apps/verticales/retail/
|
||||
nivel: 2B.2
|
||||
estado: PLANIFICACION_COMPLETA
|
||||
completitud: 15%
|
||||
documentacion: docs/
|
||||
orchestration: orchestration/
|
||||
estado: estructura_base
|
||||
inventarios:
|
||||
ubicacion: orchestration/inventarios/
|
||||
archivos: 6
|
||||
readme: true
|
||||
database:
|
||||
ubicacion: database/
|
||||
herencia: database/HERENCIA-ERP-CORE.md
|
||||
ddl_implementado: false
|
||||
directivas:
|
||||
- directivas/DIRECTIVA-PUNTO-VENTA.md
|
||||
- directivas/DIRECTIVA-INVENTARIO-SUCURSALES.md
|
||||
|
||||
clinicas:
|
||||
ubicacion_base: apps/verticales/clinicas/
|
||||
nivel: 2B.2
|
||||
estado: PLANIFICACION_COMPLETA
|
||||
completitud: 15%
|
||||
documentacion: docs/
|
||||
orchestration: orchestration/
|
||||
estado: estructura_base
|
||||
inventarios:
|
||||
ubicacion: orchestration/inventarios/
|
||||
archivos: 6
|
||||
readme: true
|
||||
database:
|
||||
ubicacion: database/
|
||||
herencia: database/HERENCIA-ERP-CORE.md
|
||||
ddl_implementado: false
|
||||
directivas:
|
||||
- directivas/DIRECTIVA-EXPEDIENTE-CLINICO.md
|
||||
- directivas/DIRECTIVA-GESTION-CITAS.md
|
||||
|
||||
# ============================================================================
|
||||
# MATRIZ DE HERENCIA (Verticales -> Core)
|
||||
# ============================================================================
|
||||
herencia_verticales:
|
||||
descripcion: "Especificaciones del core que cada vertical debe heredar"
|
||||
fecha_propagacion: 2025-12-08
|
||||
estado_propagacion: COMPLETO
|
||||
|
||||
construccion:
|
||||
specs_heredables:
|
||||
@ -165,32 +240,92 @@ herencia_verticales:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-TAREAS-RECURRENTES.md
|
||||
documentado: false
|
||||
|
||||
vidrio_templado:
|
||||
specs_heredables:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
documentado: false
|
||||
documentado: true
|
||||
documento_herencia: apps/verticales/construccion/database/HERENCIA-ERP-CORE.md
|
||||
tablas_heredadas: 124
|
||||
tablas_especificas: 33
|
||||
|
||||
mecanicas_diesel:
|
||||
specs_heredables:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
documentado: false
|
||||
- SPEC-MAIL-THREAD-TRACKING.md
|
||||
- SPEC-TAREAS-RECURRENTES.md
|
||||
documentado: true
|
||||
documento_herencia: apps/verticales/mecanicas-diesel/database/HERENCIA-ERP-CORE.md
|
||||
tablas_heredadas: 97
|
||||
tablas_especificas: 30+
|
||||
|
||||
vidrio_templado:
|
||||
specs_heredables:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
documentado: true
|
||||
documento_herencia: apps/verticales/vidrio-templado/database/HERENCIA-ERP-CORE.md
|
||||
tablas_heredadas: ~97
|
||||
tablas_especificas: ~25 (planificado)
|
||||
|
||||
retail:
|
||||
specs_heredables:
|
||||
- SPEC-PRICING-RULES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
documentado: false
|
||||
documentado: true
|
||||
documento_herencia: apps/verticales/retail/database/HERENCIA-ERP-CORE.md
|
||||
tablas_heredadas: ~102
|
||||
tablas_especificas: ~30 (planificado)
|
||||
|
||||
clinicas:
|
||||
specs_heredables:
|
||||
- SPEC-RRHH-EVALUACIONES-SKILLS.md
|
||||
- SPEC-INTEGRACION-CALENDAR.md
|
||||
- SPEC-MAIL-THREAD-TRACKING.md
|
||||
documentado: false
|
||||
documentado: true
|
||||
documento_herencia: apps/verticales/clinicas/database/HERENCIA-ERP-CORE.md
|
||||
tablas_heredadas: ~100
|
||||
tablas_especificas: ~35 (planificado)
|
||||
|
||||
# ============================================================================
|
||||
# VALIDACION DE PROPAGACION SIMCO
|
||||
# ============================================================================
|
||||
validacion_propagacion:
|
||||
fecha: 2025-12-08
|
||||
sistema: SIMCO v2.2.0
|
||||
|
||||
niveles:
|
||||
suite_master:
|
||||
nivel: 2B
|
||||
inventarios: [SUITE_MASTER_INVENTORY.yml, STATUS.yml, REFERENCIAS.yml]
|
||||
estado: COMPLETO
|
||||
|
||||
erp_core:
|
||||
nivel: 2B.1
|
||||
inventarios: 6
|
||||
estado: COMPLETO
|
||||
carga_limpia: EXITOSA
|
||||
|
||||
verticales:
|
||||
nivel: 2B.2
|
||||
total: 5
|
||||
inventarios_por_vertical: 6
|
||||
readme_por_vertical: 5/5
|
||||
herencia_documentada: 5/5
|
||||
directivas_por_vertical: 2-3
|
||||
|
||||
checklist:
|
||||
- item: "SUITE_MASTER_INVENTORY actualizado"
|
||||
estado: true
|
||||
- item: "STATUS.yml sincronizado"
|
||||
estado: true
|
||||
- item: "REFERENCIAS.yml completo"
|
||||
estado: true
|
||||
- item: "README.md en todas las verticales"
|
||||
estado: true
|
||||
- item: "HERENCIA-ERP-CORE.md en todas las verticales"
|
||||
estado: true
|
||||
- item: "Directivas específicas por vertical"
|
||||
estado: true
|
||||
- item: "6 inventarios por proyecto"
|
||||
estado: true
|
||||
|
||||
@ -62,7 +62,7 @@ componentes:
|
||||
nivel: "2B.2"
|
||||
ultima_modificacion: "2025-12-08"
|
||||
estado: "EN_DESARROLLO"
|
||||
completitud: "35%"
|
||||
completitud: "40%"
|
||||
capas:
|
||||
documentacion:
|
||||
estado: "AVANZADA"
|
||||
@ -73,10 +73,13 @@ componentes:
|
||||
tablas_especificas: 33
|
||||
schemas_especificos: ["construccion", "hr", "hse"]
|
||||
backend:
|
||||
estado: "INICIAL"
|
||||
archivos_ts: 7
|
||||
entities: 2
|
||||
porcentaje: "5%"
|
||||
estado: "EN_PROGRESO"
|
||||
archivos_ts: 25
|
||||
entities: 12
|
||||
services: 2
|
||||
controllers: 2
|
||||
porcentaje: "15%"
|
||||
modulos_funcionales: ["construction"]
|
||||
frontend:
|
||||
estado: "INICIAL"
|
||||
archivos: 3
|
||||
@ -93,8 +96,9 @@ componentes:
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-TAREAS-RECURRENTES.md
|
||||
gap_analisis:
|
||||
documentacion_vs_codigo: "449 MD vs 10 código"
|
||||
ratio_implementacion: "2%"
|
||||
documentacion_vs_codigo: "449 MD vs 25 código"
|
||||
ratio_implementacion: "5.6%"
|
||||
nota: "Gap corregido - entities base implementadas"
|
||||
|
||||
vidrio_templado:
|
||||
nivel: "2B.2"
|
||||
@ -226,8 +230,8 @@ alertas:
|
||||
fecha: "2025-12-08"
|
||||
|
||||
- componente: "construccion"
|
||||
tipo: "GAP"
|
||||
mensaje: "Gap significativo: 449 docs vs 10 archivos código (2% implementado)"
|
||||
tipo: "EN_PROGRESO"
|
||||
mensaje: "Gap corregido: 12 entities, 2 services, 2 controllers implementados"
|
||||
fecha: "2025-12-08"
|
||||
|
||||
- componente: "mecanicas_diesel"
|
||||
@ -239,6 +243,16 @@ alertas:
|
||||
# HISTORIAL DE CAMBIOS RECIENTES
|
||||
# ========================================
|
||||
historial:
|
||||
- fecha: "2025-12-08"
|
||||
componente: "construccion"
|
||||
cambio: "IMPLEMENTACION GAP FIX - 12 entities, 2 services, 2 controllers creados"
|
||||
agente: "System"
|
||||
detalles:
|
||||
- "Entities: Proyecto, Fraccionamiento, Employee, Puesto, Incidente, Capacitacion"
|
||||
- "Services: ProyectoService, FraccionamientoService"
|
||||
- "Controllers: ProyectoController, FraccionamientoController"
|
||||
- "Backend 15% implementado (25 archivos TS)"
|
||||
|
||||
- fecha: "2025-12-08"
|
||||
componente: "erp_core"
|
||||
cambio: "CARGA LIMPIA EXITOSA - 124 tablas, 12 schemas, 6 seeds"
|
||||
|
||||
@ -1,157 +1,422 @@
|
||||
# Suite Master Inventory - ERP Suite
|
||||
# Ultima actualizacion: 2025-12-08
|
||||
# SSOT para metricas de toda la suite (core + verticales)
|
||||
# Sistema: SIMCO v2.2.0
|
||||
# Nivel: 2B (Suite Master)
|
||||
|
||||
suite:
|
||||
nombre: ERP Suite
|
||||
tipo: Multi-Vertical Suite
|
||||
version: 0.5.0
|
||||
version: 0.6.0
|
||||
nivel: 2B
|
||||
estado: En Desarrollo
|
||||
ultima_modificacion: 2025-12-08
|
||||
|
||||
# Inventarios de este nivel
|
||||
inventarios_suite:
|
||||
- SUITE_MASTER_INVENTORY.yml # Este archivo
|
||||
- STATUS.yml # Estado de componentes
|
||||
- REFERENCIAS.yml # Referencias cruzadas
|
||||
- BACKEND_CONSOLIDATED.yml # Consolidado backend (referencia)
|
||||
- FRONTEND_CONSOLIDATED.yml # Consolidado frontend (referencia)
|
||||
- DEPENDENCY_SUITE.yml # Dependencias inter-proyecto
|
||||
|
||||
# ============================================================================
|
||||
# ERP CORE (Nivel 2B.1)
|
||||
# ERP CORE (Nivel 2B.1) - PROYECTO PADRE
|
||||
# ============================================================================
|
||||
erp_core:
|
||||
path: apps/erp-core/
|
||||
estado: Gap Analysis COMPLETO
|
||||
version: 0.6.0
|
||||
nivel: 2B.1
|
||||
estado: DATABASE_COMPLETO
|
||||
version: 1.1.0
|
||||
ultima_modificacion: 2025-12-08
|
||||
|
||||
# Estado de capas
|
||||
capas:
|
||||
documentacion:
|
||||
estado: COMPLETA
|
||||
archivos: 680+
|
||||
especificaciones: 30
|
||||
workflows: 3
|
||||
|
||||
database:
|
||||
estado: VALIDADO
|
||||
tablas: 124
|
||||
schemas: 12
|
||||
ddl_archivos: 15
|
||||
carga_limpia: EXITOSA
|
||||
fecha_validacion: 2025-12-08
|
||||
|
||||
backend:
|
||||
estado: PENDIENTE
|
||||
endpoints_especificados: 148
|
||||
services_especificados: 45+
|
||||
|
||||
frontend:
|
||||
estado: PENDIENTE
|
||||
componentes_especificados: 80+
|
||||
|
||||
# Inventarios del core (6 archivos estándar)
|
||||
inventarios:
|
||||
- MASTER_INVENTORY.yml
|
||||
- DATABASE_INVENTORY.yml
|
||||
- BACKEND_INVENTORY.yml
|
||||
- FRONTEND_INVENTORY.yml
|
||||
- DEPENDENCY_GRAPH.yml
|
||||
- TRACEABILITY_MATRIX.yml
|
||||
ubicacion: apps/erp-core/orchestration/inventarios/
|
||||
|
||||
# Métricas de documentación
|
||||
metricas:
|
||||
modulos_totales: 15
|
||||
modulos_p0: 4
|
||||
modulos_p1: 6
|
||||
modulos_p2: 5
|
||||
gap_analysis_cobertura: "100%"
|
||||
story_points_cubiertos: 394
|
||||
|
||||
documentacion:
|
||||
total_archivos: 680+
|
||||
especificaciones_transversales: 30
|
||||
workflows: 3
|
||||
requerimientos_funcionales: 46
|
||||
user_stories: 17
|
||||
test_plans: 4
|
||||
|
||||
gap_analysis:
|
||||
gaps_p0_documentados: 18
|
||||
gaps_p1_documentados: 22
|
||||
patrones_tecnicos: 2
|
||||
cobertura: "100%"
|
||||
story_points_cubiertos: 394
|
||||
|
||||
especificaciones_transversales:
|
||||
# Especificaciones que heredan las verticales
|
||||
especificaciones_heredables:
|
||||
ubicacion: docs/04-modelado/especificaciones-tecnicas/transversal/
|
||||
total: 30
|
||||
referencia: apps/erp-core/orchestration/inventarios/MASTER_INVENTORY.yml
|
||||
lista_principales:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
- SPEC-MAIL-THREAD-TRACKING.md
|
||||
- SPEC-TAREAS-RECURRENTES.md
|
||||
- SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN.md
|
||||
- SPEC-INTEGRACION-CALENDAR.md
|
||||
- SPEC-PRICING-RULES.md
|
||||
- SPEC-RRHH-EVALUACIONES-SKILLS.md
|
||||
|
||||
inventario_local: apps/erp-core/orchestration/inventarios/MASTER_INVENTORY.yml
|
||||
analisis_gaps: apps/erp-core/orchestration/01-analisis/ANALISIS-GAPS-CONSOLIDADO.md
|
||||
referencias:
|
||||
inventario_local: apps/erp-core/orchestration/inventarios/MASTER_INVENTORY.yml
|
||||
analisis_gaps: apps/erp-core/orchestration/01-analisis/ANALISIS-GAPS-CONSOLIDADO.md
|
||||
database_readme: apps/erp-core/database/README.md
|
||||
|
||||
# ============================================================================
|
||||
# VERTICALES (Nivel 2B.2)
|
||||
# VERTICALES (Nivel 2B.2) - PROYECTOS HIJOS
|
||||
# ============================================================================
|
||||
verticales:
|
||||
total: 5
|
||||
en_desarrollo: 1
|
||||
planificacion: 4
|
||||
ddl_implementado: 1
|
||||
planificacion: 3
|
||||
|
||||
# Inventarios estándar que debe tener cada vertical
|
||||
inventarios_requeridos:
|
||||
- MASTER_INVENTORY.yml
|
||||
- DATABASE_INVENTORY.yml
|
||||
- BACKEND_INVENTORY.yml
|
||||
- FRONTEND_INVENTORY.yml
|
||||
- DEPENDENCY_GRAPH.yml
|
||||
- TRACEABILITY_MATRIX.yml
|
||||
|
||||
lista:
|
||||
# -------------------------------------------------------------------------
|
||||
# CONSTRUCCION - Vertical más avanzada
|
||||
# -------------------------------------------------------------------------
|
||||
- nombre: construccion
|
||||
path: apps/verticales/construccion/
|
||||
estado: En Desarrollo
|
||||
completitud: 35%
|
||||
documentacion: 403+ archivos
|
||||
modulos: 15 (MAI-001 a MAI-018)
|
||||
epicas_fase2: 3 (MAE-014 a MAE-016)
|
||||
ultima_modificacion: 2025-12-05
|
||||
herencia_core:
|
||||
- SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN.md
|
||||
- SPEC-MAIL-THREAD-TRACKING.md
|
||||
- SPEC-WIZARD-TRANSIENT-MODEL.md
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
nivel: 2B.2
|
||||
estado: EN_DESARROLLO
|
||||
completitud: 40%
|
||||
ultima_modificacion: 2025-12-08
|
||||
|
||||
- nombre: vidrio-templado
|
||||
path: apps/verticales/vidrio-templado/
|
||||
estado: Planificacion
|
||||
completitud: 0%
|
||||
documentacion: Estructura base
|
||||
ultima_modificacion: 2025-12-05
|
||||
herencia_core:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
capas:
|
||||
documentacion:
|
||||
estado: AVANZADA
|
||||
archivos: 449
|
||||
database:
|
||||
estado: DDL_COMPLETO
|
||||
tablas_heredadas: 124
|
||||
tablas_especificas: 33
|
||||
schemas: [construccion, hr, hse]
|
||||
backend:
|
||||
estado: EN_PROGRESO
|
||||
porcentaje: 15%
|
||||
entities: 12
|
||||
services: 2
|
||||
controllers: 2
|
||||
frontend:
|
||||
estado: INICIAL
|
||||
porcentaje: 2%
|
||||
|
||||
herencia_core:
|
||||
specs_heredadas: 6
|
||||
documento: database/HERENCIA-ERP-CORE.md
|
||||
lista:
|
||||
- SPEC-PROYECTOS-DEPENDENCIAS-BURNDOWN.md
|
||||
- SPEC-MAIL-THREAD-TRACKING.md
|
||||
- SPEC-WIZARD-TRANSIENT-MODEL.md
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-TAREAS-RECURRENTES.md
|
||||
|
||||
directivas_especificas:
|
||||
- DIRECTIVA-CONTROL-OBRA.md
|
||||
- DIRECTIVA-ESTIMACIONES.md
|
||||
- DIRECTIVA-INTEGRACION-INFONAVIT.md
|
||||
|
||||
inventarios_ubicacion: orchestration/inventarios/
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# MECANICAS-DIESEL
|
||||
# -------------------------------------------------------------------------
|
||||
- nombre: mecanicas-diesel
|
||||
path: apps/verticales/mecanicas-diesel/
|
||||
estado: Planificacion
|
||||
completitud: 0%
|
||||
documentacion: Estructura base
|
||||
ultima_modificacion: 2025-12-05
|
||||
herencia_core:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
nivel: 2B.2
|
||||
estado: DDL_IMPLEMENTADO
|
||||
completitud: 20%
|
||||
ultima_modificacion: 2025-12-08
|
||||
|
||||
capas:
|
||||
documentacion:
|
||||
estado: COMPLETA
|
||||
archivos: 75
|
||||
database:
|
||||
estado: DDL_DEFINIDO
|
||||
tablas_heredadas: 97
|
||||
tablas_especificas: 30+
|
||||
schemas: [service_management, parts_management, vehicle_management]
|
||||
lineas_sql: 1561
|
||||
backend:
|
||||
estado: PENDIENTE
|
||||
porcentaje: 0%
|
||||
frontend:
|
||||
estado: PENDIENTE
|
||||
porcentaje: 0%
|
||||
|
||||
herencia_core:
|
||||
specs_heredadas: 5
|
||||
documento: database/HERENCIA-ERP-CORE.md
|
||||
lista:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
- SPEC-MAIL-THREAD-TRACKING.md
|
||||
- SPEC-TAREAS-RECURRENTES.md
|
||||
|
||||
directivas_especificas:
|
||||
- DIRECTIVA-ORDENES-TRABAJO.md
|
||||
- DIRECTIVA-INVENTARIO-REFACCIONES.md
|
||||
|
||||
inventarios_ubicacion: orchestration/inventarios/
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# VIDRIO-TEMPLADO
|
||||
# -------------------------------------------------------------------------
|
||||
- nombre: vidrio-templado
|
||||
path: apps/verticales/vidrio-templado/
|
||||
nivel: 2B.2
|
||||
estado: PLANIFICACION_COMPLETA
|
||||
completitud: 15%
|
||||
ultima_modificacion: 2025-12-08
|
||||
|
||||
capas:
|
||||
documentacion:
|
||||
estado: ESTRUCTURA_BASE
|
||||
database:
|
||||
estado: PLANIFICADO
|
||||
backend:
|
||||
estado: PENDIENTE
|
||||
frontend:
|
||||
estado: PENDIENTE
|
||||
|
||||
herencia_core:
|
||||
specs_heredadas: 3
|
||||
lista:
|
||||
- SPEC-VALORACION-INVENTARIO.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
|
||||
directivas_especificas:
|
||||
- DIRECTIVA-PRODUCCION-VIDRIO.md
|
||||
- DIRECTIVA-CONTROL-CALIDAD.md
|
||||
|
||||
inventarios_ubicacion: orchestration/inventarios/
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# RETAIL
|
||||
# -------------------------------------------------------------------------
|
||||
- nombre: retail
|
||||
path: apps/verticales/retail/
|
||||
estado: Planificacion
|
||||
completitud: 0%
|
||||
documentacion: Estructura base
|
||||
ultima_modificacion: 2025-12-05
|
||||
herencia_core:
|
||||
- SPEC-PRICING-RULES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
nivel: 2B.2
|
||||
estado: PLANIFICACION_COMPLETA
|
||||
completitud: 15%
|
||||
ultima_modificacion: 2025-12-08
|
||||
|
||||
capas:
|
||||
documentacion:
|
||||
estado: ESTRUCTURA_BASE
|
||||
database:
|
||||
estado: PLANIFICADO
|
||||
backend:
|
||||
estado: PENDIENTE
|
||||
frontend:
|
||||
estado: PENDIENTE
|
||||
|
||||
herencia_core:
|
||||
specs_heredadas: 3
|
||||
lista:
|
||||
- SPEC-PRICING-RULES.md
|
||||
- SPEC-INVENTARIOS-CICLICOS.md
|
||||
- SPEC-TRAZABILIDAD-LOTES-SERIES.md
|
||||
|
||||
directivas_especificas:
|
||||
- DIRECTIVA-PUNTO-VENTA.md
|
||||
- DIRECTIVA-INVENTARIO-SUCURSALES.md
|
||||
|
||||
inventarios_ubicacion: orchestration/inventarios/
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# CLINICAS
|
||||
# -------------------------------------------------------------------------
|
||||
- nombre: clinicas
|
||||
path: apps/verticales/clinicas/
|
||||
estado: Planificacion
|
||||
completitud: 0%
|
||||
documentacion: Estructura base
|
||||
ultima_modificacion: 2025-12-05
|
||||
nivel: 2B.2
|
||||
estado: PLANIFICACION_COMPLETA
|
||||
completitud: 15%
|
||||
ultima_modificacion: 2025-12-08
|
||||
|
||||
capas:
|
||||
documentacion:
|
||||
estado: ESTRUCTURA_BASE
|
||||
database:
|
||||
estado: PLANIFICADO
|
||||
backend:
|
||||
estado: PENDIENTE
|
||||
frontend:
|
||||
estado: PENDIENTE
|
||||
|
||||
herencia_core:
|
||||
- SPEC-RRHH-EVALUACIONES-SKILLS.md
|
||||
- SPEC-INTEGRACION-CALENDAR.md
|
||||
- SPEC-MAIL-THREAD-TRACKING.md
|
||||
specs_heredadas: 3
|
||||
lista:
|
||||
- SPEC-RRHH-EVALUACIONES-SKILLS.md
|
||||
- SPEC-INTEGRACION-CALENDAR.md
|
||||
- SPEC-MAIL-THREAD-TRACKING.md
|
||||
|
||||
directivas_especificas:
|
||||
- DIRECTIVA-EXPEDIENTE-CLINICO.md
|
||||
- DIRECTIVA-GESTION-CITAS.md
|
||||
|
||||
inventarios_ubicacion: orchestration/inventarios/
|
||||
|
||||
# ============================================================================
|
||||
# SHARED LIBS
|
||||
# SHARED LIBS (Futuro)
|
||||
# ============================================================================
|
||||
shared_libs:
|
||||
path: apps/shared-libs/
|
||||
estado: Planificado
|
||||
documentacion: Pendiente
|
||||
nivel: 2B.3
|
||||
estado: PLANIFICADO
|
||||
documentacion: PENDIENTE
|
||||
proposito: "Componentes compartidos entre verticales"
|
||||
|
||||
# ============================================================================
|
||||
# SAAS LAYER
|
||||
# SAAS LAYER (Futuro)
|
||||
# ============================================================================
|
||||
saas:
|
||||
path: apps/saas/
|
||||
estado: Planificado
|
||||
documentacion: Pendiente
|
||||
nivel: 2B.4
|
||||
estado: PLANIFICADO
|
||||
documentacion: PENDIENTE
|
||||
proposito: "Capa de servicios multi-tenant cloud"
|
||||
|
||||
# ============================================================================
|
||||
# METRICAS CONSOLIDADAS
|
||||
# METRICAS CONSOLIDADAS DE LA SUITE
|
||||
# ============================================================================
|
||||
metricas_suite:
|
||||
total_archivos_docs: 1100+
|
||||
modulos_core: 15
|
||||
verticales: 5
|
||||
story_points_documentados: 734
|
||||
cobertura_gaps_core: 100%
|
||||
fecha_actualizacion: 2025-12-08
|
||||
|
||||
documentacion:
|
||||
total_archivos: 1200+
|
||||
core: 680+
|
||||
verticales: 520+
|
||||
|
||||
database:
|
||||
tablas_core: 124
|
||||
schemas_core: 12
|
||||
verticales_con_ddl: 2
|
||||
tablas_especificas_total: 63+ # construccion(33) + mecanicas(30+)
|
||||
|
||||
backend:
|
||||
core_implementado: 0%
|
||||
construccion_implementado: 15%
|
||||
entities_totales: 12
|
||||
services_totales: 2
|
||||
controllers_totales: 2
|
||||
|
||||
frontend:
|
||||
core_implementado: 0%
|
||||
construccion_implementado: 2%
|
||||
|
||||
cobertura:
|
||||
gap_analysis_core: "100%"
|
||||
story_points_cubiertos: 734
|
||||
specs_transversales: 30
|
||||
workflows: 3
|
||||
|
||||
# ============================================================================
|
||||
# PROXIMA ACCION SUITE
|
||||
# PROPAGACION SIMCO
|
||||
# ============================================================================
|
||||
proxima_accion:
|
||||
prioridad: ALTA
|
||||
descripcion: Implementar modulos P0 del core
|
||||
modulos:
|
||||
- MGN-001 Auth
|
||||
- MGN-002 Users
|
||||
- MGN-003 Roles
|
||||
- MGN-004 Tenants
|
||||
specs_disponibles: 30
|
||||
workflows_disponibles: 3
|
||||
propagacion:
|
||||
sistema: SIMCO v2.2.0
|
||||
niveles:
|
||||
- nivel: 2B
|
||||
nombre: Suite Master
|
||||
ubicacion: orchestration/inventarios/
|
||||
archivos: [SUITE_MASTER_INVENTORY.yml, STATUS.yml, REFERENCIAS.yml]
|
||||
|
||||
- nivel: 2B.1
|
||||
nombre: ERP Core
|
||||
ubicacion: apps/erp-core/orchestration/inventarios/
|
||||
archivos: 6 # Inventarios estándar
|
||||
|
||||
- nivel: 2B.2
|
||||
nombre: Verticales
|
||||
ubicacion: apps/verticales/*/orchestration/inventarios/
|
||||
archivos: 6 # Inventarios estándar por vertical
|
||||
verticales: 5
|
||||
|
||||
herencia:
|
||||
direccion: "Core -> Verticales"
|
||||
documento_base: "HERENCIA-ERP-CORE.md"
|
||||
specs_heredables: 30
|
||||
propagacion_completada: true
|
||||
fecha_propagacion: 2025-12-08
|
||||
|
||||
validacion:
|
||||
inventarios_completos: true
|
||||
directivas_propagadas: true
|
||||
herencia_documentada: true
|
||||
status_sincronizado: true
|
||||
|
||||
# ============================================================================
|
||||
# PROXIMAS ACCIONES SUITE
|
||||
# ============================================================================
|
||||
proximas_acciones:
|
||||
prioridad_1:
|
||||
descripcion: "Completar backend construcción"
|
||||
modulos: [MAI-001, MAI-002, MAI-003]
|
||||
porcentaje_actual: 15%
|
||||
porcentaje_objetivo: 50%
|
||||
|
||||
prioridad_2:
|
||||
descripcion: "Cargar DDL mecanicas-diesel"
|
||||
prerequisito: "Validar DDL contra core"
|
||||
estado: PENDIENTE
|
||||
|
||||
prioridad_3:
|
||||
descripcion: "Iniciar DDL para verticales restantes"
|
||||
verticales: [vidrio-templado, retail, clinicas]
|
||||
|
||||
# ============================================================================
|
||||
# REFERENCIAS CRUZADAS
|
||||
# ============================================================================
|
||||
referencias:
|
||||
status_global: orchestration/inventarios/STATUS.yml
|
||||
referencias_herencia: orchestration/inventarios/REFERENCIAS.yml
|
||||
core_master: apps/erp-core/orchestration/inventarios/MASTER_INVENTORY.yml
|
||||
core_database: apps/erp-core/database/README.md
|
||||
guidelines: orchestration/00-guidelines/
|
||||
|
||||
@ -0,0 +1,967 @@
|
||||
# Arquitectura T茅cnica - Platform Marketing Content
|
||||
|
||||
**Versi贸n:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Propuesta
|
||||
|
||||
---
|
||||
|
||||
## 1. Visi贸n de Arquitectura
|
||||
|
||||
### 1.1 Principios Arquitect贸nicos
|
||||
|
||||
```yaml
|
||||
Principios:
|
||||
1. API-First: Todo servicio expuesto via REST/GraphQL
|
||||
2. Modular: Componentes desacoplados por dominio
|
||||
3. Multi-tenant Ready: Aislamiento de datos por tenant
|
||||
4. Open Source First: Priorizar modelos auto-hosteados
|
||||
5. Escalabilidad Horizontal: Preparado para m煤ltiples GPUs
|
||||
6. Event-Driven: Comunicaci贸n as铆ncrona entre servicios
|
||||
```
|
||||
|
||||
### 1.2 Diagrama de Arquitectura de Alto Nivel
|
||||
|
||||
```
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? CAPA DE PRESENTACI脫N 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁? 鉁? Web App Admin 鉁? 鉁? Portal Cliente 鉁? 鉁?
|
||||
鉁? 鉁? (React + Vite) 鉁? 鉁? (Opcional) 鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁?
|
||||
鈻?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? API GATEWAY 鉁?
|
||||
鉁? (NestJS + JWT Auth) 鉁?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? 鉁? 鉁? 鉁?
|
||||
鈻? 鈻? 鈻? 鈻?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? CAPA DE SERVICIOS 鉁?
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁? 鉁? CRM 鉁? 鉁? Projects 鉁? 鉁? Assets 鉁? 鉁? Auth 鉁? 鉁?
|
||||
鉁? 鉁? Service 鉁? 鉁? Service 鉁? 鉁? Service 鉁? 鉁? Service 鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁? 鉁? Generation 鉁? 鉁? Automation 鉁? 鉁?
|
||||
鉁? 鉁? Service 鉁? 鉁? Service 鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? 鉁? 鉁? 鉁?
|
||||
鈻? 鈻? 鈻? 鈻?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? CAPA DE INFRAESTRUCTURA 鉁?
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁? 鉁? PostgreSQL 鉁? 鉁? Redis 鉁? 鉁? S3/MinIO 鉁? 鉁? ComfyUI 鉁? 鉁?
|
||||
鉁? 鉁? 15+ 鉁? 鉁? (Cache) 鉁? 鉁? (Storage)鉁? 鉁? (GPU) 鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁? 鉁? n8n 鉁? 鉁? Bull/BullMQ 鉁? 鉁?
|
||||
鉁? 鉁? (Workflows) 鉁? 鉁? (Job Queues) 鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Stack Tecnol贸gico Detallado
|
||||
|
||||
### 2.1 Backend
|
||||
|
||||
```yaml
|
||||
Framework: NestJS 10+
|
||||
Runtime: Node.js 20 LTS
|
||||
Lenguaje: TypeScript 5.x
|
||||
|
||||
Dependencias Core:
|
||||
- @nestjs/core
|
||||
- @nestjs/platform-express
|
||||
- @nestjs/typeorm
|
||||
- @nestjs/jwt
|
||||
- @nestjs/passport
|
||||
- @nestjs/bull
|
||||
- class-validator
|
||||
- class-transformer
|
||||
|
||||
ORM/Database:
|
||||
- TypeORM 0.3+
|
||||
- pg (PostgreSQL driver)
|
||||
|
||||
Queues:
|
||||
- bull / bullmq
|
||||
- @nestjs/bull
|
||||
|
||||
Caching:
|
||||
- ioredis
|
||||
- @nestjs/cache-manager
|
||||
|
||||
HTTP Client:
|
||||
- axios (llamadas a ComfyUI)
|
||||
|
||||
File Storage:
|
||||
- @aws-sdk/client-s3 (o minio compatible)
|
||||
|
||||
Logging:
|
||||
- winston
|
||||
- @nestjs/winston
|
||||
```
|
||||
|
||||
### 2.2 Frontend
|
||||
|
||||
```yaml
|
||||
Framework: React 18
|
||||
Build Tool: Vite 5+
|
||||
Lenguaje: TypeScript 5.x
|
||||
|
||||
UI Framework:
|
||||
- TailwindCSS 3.x
|
||||
- Shadcn/UI (componentes)
|
||||
- Radix UI (primitivos)
|
||||
|
||||
State Management:
|
||||
- Zustand (global state)
|
||||
- React Query / TanStack Query (server state)
|
||||
|
||||
Routing:
|
||||
- React Router 6
|
||||
|
||||
Forms:
|
||||
- React Hook Form
|
||||
- Zod (validaci贸n)
|
||||
|
||||
Charts/Visualizaci贸n:
|
||||
- Recharts
|
||||
- Apache ECharts (opcional)
|
||||
|
||||
Utilities:
|
||||
- date-fns
|
||||
- lodash-es
|
||||
```
|
||||
|
||||
### 2.3 Motor de Generaci贸n IA
|
||||
|
||||
```yaml
|
||||
Orquestador Principal: ComfyUI
|
||||
- Interfaz de nodos para workflows
|
||||
- Soporte para m煤ltiples modelos
|
||||
- Custom nodes extensibles
|
||||
|
||||
Exposici贸n API: ComfyDeploy
|
||||
- Convierte workflows en APIs HTTP
|
||||
- Manejo de colas integrado
|
||||
- Webhooks para resultados
|
||||
|
||||
Modelos Base:
|
||||
Text-to-Image:
|
||||
- Stable Diffusion XL 1.0
|
||||
- SD 1.5 / 2.1 (fallback para menos VRAM)
|
||||
|
||||
Checkpoints Especializados:
|
||||
- Realistic Vision (rostros)
|
||||
- Product Photography (e-commerce)
|
||||
- Juggernaut XL (general alta calidad)
|
||||
|
||||
ControlNets:
|
||||
- OpenPose (control de poses)
|
||||
- Canny (bordes)
|
||||
- Depth (profundidad)
|
||||
- Segmentation (m谩scaras)
|
||||
|
||||
Upscalers:
|
||||
- RealESRGAN x4
|
||||
- SwinIR
|
||||
- SDXL refiner
|
||||
|
||||
Inpainting:
|
||||
- SDXL Inpaint model
|
||||
- SD 1.5 Inpaint (fallback)
|
||||
|
||||
Requisitos Hardware:
|
||||
M铆nimo: NVIDIA GPU 12GB VRAM (RTX 3060 12GB)
|
||||
Recomendado: NVIDIA GPU 24GB VRAM (RTX 4090, L4, A5000)
|
||||
脫ptimo: M煤ltiples GPUs para colas paralelas
|
||||
```
|
||||
|
||||
### 2.4 Base de Datos
|
||||
|
||||
```yaml
|
||||
Motor: PostgreSQL 15+
|
||||
|
||||
Schemas:
|
||||
- auth: Usuarios, sesiones, permisos
|
||||
- crm: Clientes, contactos, marcas, productos
|
||||
- projects: Proyectos, campa帽as, briefs
|
||||
- assets: Im谩genes, copys, metadatos
|
||||
- generation: Jobs, workflows, resultados
|
||||
- config: Configuraci贸n por tenant
|
||||
|
||||
Extensiones:
|
||||
- uuid-ossp (UUIDs)
|
||||
- pgcrypto (encriptaci贸n)
|
||||
- pg_trgm (b煤squeda fuzzy)
|
||||
|
||||
Estrategia Multi-tenant:
|
||||
- Discriminador: tenant_id en todas las tablas
|
||||
- Row-Level Security (RLS) por tenant
|
||||
- Contexto de sesi贸n: app.current_tenant_id
|
||||
```
|
||||
|
||||
### 2.5 Automatizaci贸n
|
||||
|
||||
```yaml
|
||||
Orquestador: n8n (self-hosted)
|
||||
|
||||
Triggers Soportados:
|
||||
- Webhooks desde backend
|
||||
- Cron / schedules
|
||||
- Eventos de base de datos
|
||||
|
||||
Integraciones:
|
||||
- HTTP/REST (cualquier API)
|
||||
- PostgreSQL queries
|
||||
- S3/MinIO operations
|
||||
- Email (SMTP)
|
||||
- Slack/Discord (opcional)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Estructura de M贸dulos Backend
|
||||
|
||||
### 3.1 Organizaci贸n de C贸digo
|
||||
|
||||
```
|
||||
apps/backend/src/
|
||||
鉁斺攢鉁? modules/
|
||||
鉁? 鉁斺攢鉁? auth/
|
||||
鉁? 鉁? 鉁斺攢鉁? controllers/
|
||||
鉁? 鉁? 鉁斺攢鉁? services/
|
||||
鉁? 鉁? 鉁斺攢鉁? entities/
|
||||
鉁? 鉁? 鉁斺攢鉁? dto/
|
||||
鉁? 鉁? 鉁斺攢鉁? guards/
|
||||
鉁? 鉁? 鉁斺攢鉁? strategies/
|
||||
鉁? 鉁? 鉁斺攢鉁? auth.module.ts
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁? crm/
|
||||
鉁? 鉁? 鉁斺攢鉁? controllers/
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? clients.controller.ts
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? brands.controller.ts
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? products.controller.ts
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? contacts.controller.ts
|
||||
鉁? 鉁? 鉁斺攢鉁? services/
|
||||
鉁? 鉁? 鉁斺攢鉁? entities/
|
||||
鉁? 鉁? 鉁斺攢鉁? dto/
|
||||
鉁? 鉁? 鉁斺攢鉁? crm.module.ts
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁? projects/
|
||||
鉁? 鉁? 鉁斺攢鉁? controllers/
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? projects.controller.ts
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? campaigns.controller.ts
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? briefs.controller.ts
|
||||
鉁? 鉁? 鉁斺攢鉁? services/
|
||||
鉁? 鉁? 鉁斺攢鉁? entities/
|
||||
鉁? 鉁? 鉁斺攢鉁? dto/
|
||||
鉁? 鉁? 鉁斺攢鉁? projects.module.ts
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁? generation/
|
||||
鉁? 鉁? 鉁斺攢鉁? controllers/
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? generation.controller.ts
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? workflows.controller.ts
|
||||
鉁? 鉁? 鉁斺攢鉁? services/
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? generation.service.ts
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? comfyui.service.ts
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? llm.service.ts
|
||||
鉁? 鉁? 鉁斺攢鉁? processors/
|
||||
鉁? 鉁? 鉁? 鉁斺攢鉁? generation.processor.ts
|
||||
鉁? 鉁? 鉁斺攢鉁? entities/
|
||||
鉁? 鉁? 鉁斺攢鉁? dto/
|
||||
鉁? 鉁? 鉁斺攢鉁? generation.module.ts
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁? assets/
|
||||
鉁? 鉁? 鉁斺攢鉁? controllers/
|
||||
鉁? 鉁? 鉁斺攢鉁? services/
|
||||
鉁? 鉁? 鉁斺攢鉁? entities/
|
||||
鉁? 鉁? 鉁斺攢鉁? dto/
|
||||
鉁? 鉁? 鉁斺攢鉁? assets.module.ts
|
||||
鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁? admin/
|
||||
鉁? 鉁斺攢鉁? controllers/
|
||||
鉁? 鉁斺攢鉁? services/
|
||||
鉁? 鉁斺攢鉁? admin.module.ts
|
||||
鉁?
|
||||
鉁斺攢鉁? shared/
|
||||
鉁? 鉁斺攢鉁? decorators/
|
||||
鉁? 鉁斺攢鉁? guards/
|
||||
鉁? 鉁? 鉁斺攢鉁? tenant.guard.ts
|
||||
鉁? 鉁? 鉁斺攢鉁? roles.guard.ts
|
||||
鉁? 鉁斺攢鉁? interceptors/
|
||||
鉁? 鉁? 鉁斺攢鉁? tenant-context.interceptor.ts
|
||||
鉁? 鉁斺攢鉁? filters/
|
||||
鉁? 鉁斺攢鉁? pipes/
|
||||
鉁? 鉁斺攢鉁? utils/
|
||||
鉁?
|
||||
鉁斺攢鉁? config/
|
||||
鉁? 鉁斺攢鉁? database.config.ts
|
||||
鉁? 鉁斺攢鉁? redis.config.ts
|
||||
鉁? 鉁斺攢鉁? storage.config.ts
|
||||
鉁? 鉁斺攢鉁? comfyui.config.ts
|
||||
鉁?
|
||||
鉁斺攢鉁? app.module.ts
|
||||
鉁斺攢鉁? main.ts
|
||||
```
|
||||
|
||||
### 3.2 API Endpoints (Borrador)
|
||||
|
||||
```yaml
|
||||
Auth:
|
||||
POST /api/v1/auth/login
|
||||
POST /api/v1/auth/register
|
||||
POST /api/v1/auth/refresh
|
||||
POST /api/v1/auth/logout
|
||||
GET /api/v1/auth/me
|
||||
|
||||
CRM - Clients:
|
||||
GET /api/v1/crm/clients
|
||||
POST /api/v1/crm/clients
|
||||
GET /api/v1/crm/clients/:id
|
||||
PATCH /api/v1/crm/clients/:id
|
||||
DELETE /api/v1/crm/clients/:id
|
||||
|
||||
CRM - Brands:
|
||||
GET /api/v1/crm/brands
|
||||
POST /api/v1/crm/brands
|
||||
GET /api/v1/crm/brands/:id
|
||||
PATCH /api/v1/crm/brands/:id
|
||||
GET /api/v1/crm/brands/:id/products
|
||||
|
||||
CRM - Products:
|
||||
GET /api/v1/crm/products
|
||||
POST /api/v1/crm/products
|
||||
GET /api/v1/crm/products/:id
|
||||
PATCH /api/v1/crm/products/:id
|
||||
|
||||
Projects:
|
||||
GET /api/v1/projects
|
||||
POST /api/v1/projects
|
||||
GET /api/v1/projects/:id
|
||||
PATCH /api/v1/projects/:id
|
||||
GET /api/v1/projects/:id/campaigns
|
||||
GET /api/v1/projects/:id/assets
|
||||
|
||||
Campaigns:
|
||||
GET /api/v1/campaigns
|
||||
POST /api/v1/campaigns
|
||||
GET /api/v1/campaigns/:id
|
||||
PATCH /api/v1/campaigns/:id
|
||||
POST /api/v1/campaigns/:id/generate
|
||||
GET /api/v1/campaigns/:id/assets
|
||||
|
||||
Generation:
|
||||
POST /api/v1/generation/image
|
||||
POST /api/v1/generation/copy
|
||||
GET /api/v1/generation/jobs/:id
|
||||
GET /api/v1/generation/workflows
|
||||
POST /api/v1/generation/workflows/:id/execute
|
||||
|
||||
Assets:
|
||||
GET /api/v1/assets
|
||||
GET /api/v1/assets/:id
|
||||
PATCH /api/v1/assets/:id
|
||||
DELETE /api/v1/assets/:id
|
||||
POST /api/v1/assets/:id/approve
|
||||
GET /api/v1/assets/:id/download
|
||||
|
||||
Admin:
|
||||
GET /api/v1/admin/users
|
||||
POST /api/v1/admin/users
|
||||
GET /api/v1/admin/tenants
|
||||
GET /api/v1/admin/metrics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Modelo de Datos (Entidades Principales)
|
||||
|
||||
### 4.1 Schema: auth
|
||||
|
||||
```sql
|
||||
-- Tabla: auth.users
|
||||
CREATE TABLE auth.users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
first_name VARCHAR(100),
|
||||
last_name VARCHAR(100),
|
||||
role VARCHAR(50) NOT NULL DEFAULT 'user',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(tenant_id, email)
|
||||
);
|
||||
|
||||
-- Tabla: auth.sessions
|
||||
CREATE TABLE auth.sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id),
|
||||
refresh_token VARCHAR(500) NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 4.2 Schema: crm
|
||||
|
||||
```sql
|
||||
-- Tabla: crm.clients
|
||||
CREATE TABLE crm.clients (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
industry VARCHAR(100),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla: crm.brands
|
||||
CREATE TABLE crm.brands (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
client_id UUID NOT NULL REFERENCES crm.clients(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
tone_of_voice VARCHAR(100),
|
||||
color_palette JSONB,
|
||||
restrictions JSONB,
|
||||
logo_url VARCHAR(500),
|
||||
lora_model_id UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla: crm.products
|
||||
CREATE TABLE crm.products (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
brand_id UUID NOT NULL REFERENCES crm.brands(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
category VARCHAR(100),
|
||||
reference_images JSONB,
|
||||
lora_model_id UUID,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla: crm.contacts
|
||||
CREATE TABLE crm.contacts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
client_id UUID REFERENCES crm.clients(id),
|
||||
first_name VARCHAR(100) NOT NULL,
|
||||
last_name VARCHAR(100),
|
||||
email VARCHAR(255),
|
||||
phone VARCHAR(50),
|
||||
position VARCHAR(100),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 4.3 Schema: projects
|
||||
|
||||
```sql
|
||||
-- Tabla: projects.projects
|
||||
CREATE TABLE projects.projects (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
client_id UUID REFERENCES crm.clients(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla: projects.campaigns
|
||||
CREATE TABLE projects.campaigns (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
project_id UUID NOT NULL REFERENCES projects.projects(id),
|
||||
brand_id UUID REFERENCES crm.brands(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
||||
channels JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla: projects.briefs
|
||||
CREATE TABLE projects.briefs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
campaign_id UUID NOT NULL REFERENCES projects.campaigns(id),
|
||||
objective TEXT NOT NULL,
|
||||
target_audience TEXT,
|
||||
tone_of_voice VARCHAR(100),
|
||||
key_messages JSONB,
|
||||
restrictions JSONB,
|
||||
reference_images JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 4.4 Schema: assets
|
||||
|
||||
```sql
|
||||
-- Tabla: assets.assets
|
||||
CREATE TABLE assets.assets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
campaign_id UUID REFERENCES projects.campaigns(id),
|
||||
type VARCHAR(20) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'draft',
|
||||
file_url VARCHAR(500),
|
||||
file_size INTEGER,
|
||||
mime_type VARCHAR(100),
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
metadata JSONB,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
parent_id UUID REFERENCES assets.assets(id),
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
approved_by UUID REFERENCES auth.users(id),
|
||||
approved_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla: assets.tags
|
||||
CREATE TABLE assets.tags (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
UNIQUE(tenant_id, name)
|
||||
);
|
||||
|
||||
-- Tabla: assets.asset_tags
|
||||
CREATE TABLE assets.asset_tags (
|
||||
asset_id UUID NOT NULL REFERENCES assets.assets(id),
|
||||
tag_id UUID NOT NULL REFERENCES assets.tags(id),
|
||||
PRIMARY KEY (asset_id, tag_id)
|
||||
);
|
||||
```
|
||||
|
||||
### 4.5 Schema: generation
|
||||
|
||||
```sql
|
||||
-- Tabla: generation.jobs
|
||||
CREATE TABLE generation.jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES config.tenants(id),
|
||||
campaign_id UUID REFERENCES projects.campaigns(id),
|
||||
workflow_id VARCHAR(100) NOT NULL,
|
||||
type VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
input_params JSONB NOT NULL,
|
||||
result JSONB,
|
||||
error_message TEXT,
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla: generation.workflows
|
||||
CREATE TABLE generation.workflows (
|
||||
id VARCHAR(100) PRIMARY KEY,
|
||||
tenant_id UUID REFERENCES config.tenants(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
comfyui_workflow JSONB NOT NULL,
|
||||
input_schema JSONB,
|
||||
is_system BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Tabla: generation.models
|
||||
CREATE TABLE generation.models (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID REFERENCES config.tenants(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
file_path VARCHAR(500),
|
||||
description TEXT,
|
||||
training_images JSONB,
|
||||
is_system BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Flujo de Generaci贸n de Contenido
|
||||
|
||||
### 5.1 Secuencia de Generaci贸n de Imagen
|
||||
|
||||
```
|
||||
1. Usuario solicita generaci贸n
|
||||
鈻?
|
||||
Frontend 鈫? POST /api/v1/generation/image
|
||||
{
|
||||
"campaign_id": "uuid",
|
||||
"workflow_id": "product_photography",
|
||||
"params": {
|
||||
"prompt": "...",
|
||||
"product_id": "uuid",
|
||||
"style": "commercial"
|
||||
}
|
||||
}
|
||||
|
||||
2. Backend crea Job
|
||||
鈻?
|
||||
GenerationService.createJob()
|
||||
鈫? Valida permisos
|
||||
鈫? Crea registro en generation.jobs
|
||||
鈫? Encola job en Bull queue
|
||||
|
||||
3. Worker procesa Job
|
||||
鈻?
|
||||
GenerationProcessor.process()
|
||||
鈫? Obtiene workflow de ComfyUI
|
||||
鈫? Inyecta par谩metros (prompt, LoRA, etc.)
|
||||
鈫? Env铆a a ComfyUI API
|
||||
鈫? Espera resultado (webhook o polling)
|
||||
|
||||
4. ComfyUI ejecuta workflow
|
||||
鈻?
|
||||
鈫? Carga modelo SDXL
|
||||
鈫? Aplica LoRAs (si hay)
|
||||
鈫? Ejecuta pipeline de difusi贸n
|
||||
鈫? Aplica upscaling
|
||||
鈫? Retorna imagen
|
||||
|
||||
5. Worker procesa resultado
|
||||
鈻?
|
||||
鈫? Descarga imagen de ComfyUI
|
||||
鈫? Sube a S3/MinIO
|
||||
鈫? Crea registro en assets.assets
|
||||
鈫? Actualiza job como completado
|
||||
鈫? Emite evento (webhook a n8n)
|
||||
|
||||
6. Frontend recibe notificaci贸n
|
||||
鈻?
|
||||
WebSocket / Polling 鈫? Job completado
|
||||
鈫? Muestra imagen al usuario
|
||||
```
|
||||
|
||||
### 5.2 Integraci贸n con ComfyUI
|
||||
|
||||
```typescript
|
||||
// services/comfyui.service.ts
|
||||
|
||||
interface ComfyUIWorkflowParams {
|
||||
prompt: string;
|
||||
negativePrompt?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
steps?: number;
|
||||
cfg?: number;
|
||||
seed?: number;
|
||||
loraModels?: string[];
|
||||
controlNet?: {
|
||||
type: string;
|
||||
image: string;
|
||||
strength: number;
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ComfyUIService {
|
||||
constructor(
|
||||
private httpService: HttpService,
|
||||
@Inject('COMFYUI_CONFIG') private config: ComfyUIConfig,
|
||||
) {}
|
||||
|
||||
async executeWorkflow(
|
||||
workflowId: string,
|
||||
params: ComfyUIWorkflowParams,
|
||||
): Promise<string> {
|
||||
// 1. Cargar workflow template
|
||||
const workflow = await this.getWorkflowTemplate(workflowId);
|
||||
|
||||
// 2. Inyectar par谩metros
|
||||
const modifiedWorkflow = this.injectParams(workflow, params);
|
||||
|
||||
// 3. Enviar a ComfyUI
|
||||
const response = await this.httpService.post(
|
||||
`${this.config.baseUrl}/prompt`,
|
||||
{ prompt: modifiedWorkflow },
|
||||
).toPromise();
|
||||
|
||||
return response.data.prompt_id;
|
||||
}
|
||||
|
||||
async getResult(promptId: string): Promise<Buffer> {
|
||||
// Polling o webhook para obtener resultado
|
||||
const history = await this.httpService.get(
|
||||
`${this.config.baseUrl}/history/${promptId}`,
|
||||
).toPromise();
|
||||
|
||||
const outputNode = this.findOutputNode(history.data);
|
||||
const imageUrl = `${this.config.baseUrl}/view?filename=${outputNode.filename}`;
|
||||
|
||||
const imageResponse = await this.httpService.get(imageUrl, {
|
||||
responseType: 'arraybuffer',
|
||||
}).toPromise();
|
||||
|
||||
return Buffer.from(imageResponse.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Seguridad
|
||||
|
||||
### 6.1 Autenticaci贸n
|
||||
|
||||
```yaml
|
||||
M茅todo: JWT (JSON Web Tokens)
|
||||
|
||||
Access Token:
|
||||
- Expiraci贸n: 15 minutos
|
||||
- Payload: userId, tenantId, role
|
||||
- Almacenamiento: Memory (frontend)
|
||||
|
||||
Refresh Token:
|
||||
- Expiraci贸n: 7 d铆as
|
||||
- Almacenamiento: HTTP-only cookie
|
||||
- Rotaci贸n en cada uso
|
||||
```
|
||||
|
||||
### 6.2 Autorizaci贸n (RBAC)
|
||||
|
||||
```yaml
|
||||
Roles:
|
||||
super_admin:
|
||||
- Acceso total a todos los tenants
|
||||
- Configuraci贸n global
|
||||
- Gesti贸n de modelos del sistema
|
||||
|
||||
admin:
|
||||
- Gesti贸n de usuarios del tenant
|
||||
- Configuraci贸n del tenant
|
||||
- Aprobaci贸n de workflows
|
||||
|
||||
manager:
|
||||
- CRUD completo de proyectos/campa帽as
|
||||
- Aprobaci贸n de assets
|
||||
- Acceso a anal铆ticas
|
||||
|
||||
creative:
|
||||
- Crear proyectos y campa帽as
|
||||
- Generar contenido
|
||||
- Editar assets propios
|
||||
|
||||
viewer:
|
||||
- Solo lectura
|
||||
- Descargar assets aprobados
|
||||
```
|
||||
|
||||
### 6.3 Multi-tenancy
|
||||
|
||||
```sql
|
||||
-- Pol铆tica RLS ejemplo
|
||||
CREATE POLICY "crm_clients_tenant_isolation" ON crm.clients
|
||||
FOR ALL
|
||||
TO authenticated
|
||||
USING (tenant_id::text = current_setting('app.current_tenant_id', true))
|
||||
WITH CHECK (tenant_id::text = current_setting('app.current_tenant_id', true));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Despliegue
|
||||
|
||||
### 7.1 Docker Compose (Desarrollo/Staging)
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
backend:
|
||||
build: ./apps/backend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://...
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- COMFYUI_URL=http://comfyui:8188
|
||||
- S3_ENDPOINT=http://minio:9000
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
- minio
|
||||
|
||||
frontend:
|
||||
build: ./apps/frontend
|
||||
ports:
|
||||
- "5173:80"
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_DB=pmc
|
||||
- POSTGRES_USER=pmc
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
|
||||
comfyui:
|
||||
image: comfyui/comfyui:latest
|
||||
ports:
|
||||
- "8188:8188"
|
||||
volumes:
|
||||
- comfyui_models:/models
|
||||
- comfyui_output:/output
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: 1
|
||||
capabilities: [gpu]
|
||||
|
||||
n8n:
|
||||
image: n8nio/n8n
|
||||
ports:
|
||||
- "5678:5678"
|
||||
volumes:
|
||||
- n8n_data:/home/node/.n8n
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
minio_data:
|
||||
comfyui_models:
|
||||
comfyui_output:
|
||||
n8n_data:
|
||||
```
|
||||
|
||||
### 7.2 Requisitos de Hardware (Producci贸n)
|
||||
|
||||
```yaml
|
||||
Backend + Frontend:
|
||||
CPU: 4 cores
|
||||
RAM: 8 GB
|
||||
Storage: 50 GB SSD
|
||||
|
||||
PostgreSQL:
|
||||
CPU: 2 cores
|
||||
RAM: 8 GB
|
||||
Storage: 100 GB SSD
|
||||
|
||||
ComfyUI (GPU Server):
|
||||
CPU: 8 cores
|
||||
RAM: 32 GB
|
||||
GPU: NVIDIA RTX 4090 (24GB) o similar
|
||||
Storage: 500 GB NVMe (modelos)
|
||||
|
||||
Storage (S3/MinIO):
|
||||
Storage: 1 TB+ (escalable)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Monitoreo y Observabilidad
|
||||
|
||||
### 8.1 Logs
|
||||
|
||||
```yaml
|
||||
Backend:
|
||||
- Winston con formato JSON estructurado
|
||||
- Niveles: error, warn, info, debug
|
||||
- Rotaci贸n diaria
|
||||
|
||||
Agregaci贸n:
|
||||
- ELK Stack (Elasticsearch, Logstash, Kibana)
|
||||
- O alternativa: Loki + Grafana
|
||||
```
|
||||
|
||||
### 8.2 M茅tricas
|
||||
|
||||
```yaml
|
||||
Application Metrics:
|
||||
- API response times (p50, p95, p99)
|
||||
- Queue depths y processing times
|
||||
- Generation success rate
|
||||
- Error rates por tipo
|
||||
|
||||
Business Metrics:
|
||||
- Assets generados por d铆a
|
||||
- Tiempo promedio de generaci贸n
|
||||
- Assets aprobados vs rechazados
|
||||
- Uso por tenant/usuario
|
||||
|
||||
Infrastructure:
|
||||
- CPU, RAM, disk usage
|
||||
- GPU utilization
|
||||
- Network I/O
|
||||
```
|
||||
|
||||
### 8.3 Alertas
|
||||
|
||||
```yaml
|
||||
Cr铆ticas:
|
||||
- API down > 1 min
|
||||
- GPU unavailable
|
||||
- Queue backlog > 100 jobs
|
||||
- Error rate > 5%
|
||||
|
||||
Advertencias:
|
||||
- Response time p95 > 2s
|
||||
- Disk usage > 80%
|
||||
- Queue processing slow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,199 @@
|
||||
# Glosario - Platform Marketing Content
|
||||
|
||||
**Versi贸n:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## T茅rminos del Dominio
|
||||
|
||||
### A
|
||||
|
||||
**Asset**
|
||||
: Recurso digital generado o almacenado en la plataforma (imagen, copy, video, etc.).
|
||||
|
||||
**Avatar Virtual / Influencer Virtual**
|
||||
: Personaje generado por IA que mantiene consistencia visual a trav茅s de m煤ltiples im谩genes/videos. Ejemplo: Aitana L贸pez.
|
||||
|
||||
### B
|
||||
|
||||
**Brief**
|
||||
: Documento estructurado que define los par谩metros de una campa帽a o generaci贸n: objetivo, p煤blico, tono, canales, restricciones.
|
||||
|
||||
### C
|
||||
|
||||
**Campa帽a**
|
||||
: Iniciativa de marketing con objetivo espec铆fico que agrupa m煤ltiples assets generados para diferentes canales.
|
||||
|
||||
**Checkpoint**
|
||||
: Versi贸n guardada de un modelo de IA entrenado. Puede ser especializado (fotograf铆a de producto, rostros, etc.).
|
||||
|
||||
**ComfyUI**
|
||||
: Interfaz de nodos para construir flujos de generaci贸n con Stable Diffusion y otros modelos. Permite encadenar procesos de forma visual.
|
||||
|
||||
**ComfyDeploy**
|
||||
: Herramienta que permite exponer workflows de ComfyUI como APIs HTTP para consumo desde aplicaciones.
|
||||
|
||||
**Consistencia de Personaje**
|
||||
: Capacidad de mantener los mismos rasgos faciales y caracter铆sticas visuales de un personaje a trav茅s de m煤ltiples generaciones.
|
||||
|
||||
**ControlNet**
|
||||
: Adaptador para modelos de difusi贸n que permite controlar la generaci贸n mediante gu铆as visuales (poses, bordes, profundidad, segmentaci贸n).
|
||||
|
||||
**Copy**
|
||||
: Texto publicitario generado para acompa帽ar im谩genes (t铆tulos, descripciones, hashtags, CTAs).
|
||||
|
||||
**CRM (Customer Relationship Management)**
|
||||
: Sistema de gesti贸n de relaciones con clientes. En esta plataforma, integrado con el motor de generaci贸n.
|
||||
|
||||
### D
|
||||
|
||||
**DAM (Digital Asset Management)**
|
||||
: Sistema de gesti贸n de activos digitales. Repositorio centralizado para almacenar, organizar y buscar contenido generado.
|
||||
|
||||
**Difusi贸n / Diffusion**
|
||||
: T茅cnica de IA generativa que crea im谩genes partiendo de ruido aleatorio y eliminando ruido gradualmente siguiendo indicaciones de texto.
|
||||
|
||||
**DreamBooth**
|
||||
: T茅cnica de fine-tuning que permite personalizar un modelo con pocas im谩genes de un sujeto espec铆fico.
|
||||
|
||||
### F
|
||||
|
||||
**Fine-tuning**
|
||||
: Proceso de re-entrenar un modelo pre-existente con datos espec铆ficos para especializarlo en un dominio o estilo particular.
|
||||
|
||||
**Fotograf铆a Sint茅tica**
|
||||
: Im谩genes generadas por IA que simulan fotograf铆as reales de productos, personas o escenas.
|
||||
|
||||
### G
|
||||
|
||||
**Generaci贸n de Contenido**
|
||||
: Proceso de crear im谩genes, textos u otros medios mediante modelos de IA.
|
||||
|
||||
### I
|
||||
|
||||
**Image Prompt**
|
||||
: Imagen de referencia utilizada como entrada adicional al texto para guiar la generaci贸n hacia un estilo o sujeto espec铆fico.
|
||||
|
||||
**Inpainting**
|
||||
: T茅cnica de edici贸n que permite regenerar solo una porci贸n seleccionada de una imagen manteniendo el resto intacto.
|
||||
|
||||
**IP-Adapter**
|
||||
: Adaptador que permite inyectar caracter铆sticas de una imagen de referencia en la generaci贸n, logrando consistencia visual.
|
||||
|
||||
### L
|
||||
|
||||
**LLM (Large Language Model)**
|
||||
: Modelo de lenguaje de gran escala usado para generar texto (GPT-4, Claude, Llama, etc.).
|
||||
|
||||
**LoRA (Low-Rank Adaptation)**
|
||||
: T茅cnica de fine-tuning eficiente que permite personalizar un modelo con pocos recursos. Crea archivos peque帽os que modifican el comportamiento del modelo base.
|
||||
|
||||
### M
|
||||
|
||||
**Multi-tenant**
|
||||
: Arquitectura donde una sola instancia del software sirve a m煤ltiples clientes (tenants) con datos aislados.
|
||||
|
||||
### N
|
||||
|
||||
**n8n**
|
||||
: Plataforma de automatizaci贸n de workflows que conecta diferentes servicios y APIs mediante flujos visuales.
|
||||
|
||||
### O
|
||||
|
||||
**Orquestador**
|
||||
: Sistema que coordina la ejecuci贸n de m煤ltiples tareas o servicios en un flujo definido.
|
||||
|
||||
### P
|
||||
|
||||
**Plantilla / Template**
|
||||
: Workflow predefinido de generaci贸n que puede ser reutilizado para crear contenido con par谩metros espec铆ficos.
|
||||
|
||||
**Prompt**
|
||||
: Texto de instrucci贸n que gu铆a al modelo de IA sobre qu茅 generar. Puede incluir descripci贸n de la imagen, estilo, elementos a incluir/excluir.
|
||||
|
||||
### R
|
||||
|
||||
**RBAC (Role-Based Access Control)**
|
||||
: Sistema de permisos basado en roles donde los usuarios heredan permisos seg煤n su rol asignado.
|
||||
|
||||
**Rollout**
|
||||
: Despliegue gradual de una funcionalidad a grupos de usuarios.
|
||||
|
||||
### S
|
||||
|
||||
**SaaS (Software as a Service)**
|
||||
: Modelo de distribuci贸n de software donde la aplicaci贸n se aloja en la nube y se accede v铆a web bajo suscripci贸n.
|
||||
|
||||
**SDXL (Stable Diffusion XL)**
|
||||
: Versi贸n avanzada de Stable Diffusion con mayor resoluci贸n y mejor comprensi贸n de prompts.
|
||||
|
||||
**Stable Diffusion**
|
||||
: Modelo de difusi贸n de c贸digo abierto para generaci贸n de im谩genes a partir de texto.
|
||||
|
||||
### T
|
||||
|
||||
**Tenant**
|
||||
: Cliente/organizaci贸n en una arquitectura multi-tenant. Cada tenant tiene sus datos aislados.
|
||||
|
||||
**Text-to-Image (T2I)**
|
||||
: Proceso de generar im谩genes a partir de descripciones textuales.
|
||||
|
||||
**Textual Inversion**
|
||||
: T茅cnica que permite ense帽ar a un modelo un nuevo concepto asociado a una palabra clave espec铆fica.
|
||||
|
||||
### U
|
||||
|
||||
**Upscaling**
|
||||
: Proceso de aumentar la resoluci贸n de una imagen utilizando t茅cnicas de IA para mantener o mejorar la calidad.
|
||||
|
||||
### V
|
||||
|
||||
**VRAM (Video RAM)**
|
||||
: Memoria dedicada de la tarjeta gr谩fica. Los modelos de difusi贸n requieren 8-24GB VRAM para funcionar.
|
||||
|
||||
### W
|
||||
|
||||
**Workflow**
|
||||
: Flujo de trabajo que define una secuencia de pasos para completar un proceso (ej: generaci贸n de im谩genes con postprocesado).
|
||||
|
||||
---
|
||||
|
||||
## Acr贸nimos Comunes
|
||||
|
||||
| Acr贸nimo | Significado |
|
||||
|----------|-------------|
|
||||
| **API** | Application Programming Interface |
|
||||
| **CRM** | Customer Relationship Management |
|
||||
| **DAM** | Digital Asset Management |
|
||||
| **GPU** | Graphics Processing Unit |
|
||||
| **JWT** | JSON Web Token |
|
||||
| **LLM** | Large Language Model |
|
||||
| **LoRA** | Low-Rank Adaptation |
|
||||
| **MVP** | Minimum Viable Product |
|
||||
| **NLP** | Natural Language Processing |
|
||||
| **RBAC** | Role-Based Access Control |
|
||||
| **RLS** | Row-Level Security |
|
||||
| **SaaS** | Software as a Service |
|
||||
| **SDXL** | Stable Diffusion XL |
|
||||
| **T2I** | Text-to-Image |
|
||||
| **VRAM** | Video Random Access Memory |
|
||||
|
||||
---
|
||||
|
||||
## Modelos de IA Relevantes
|
||||
|
||||
| Modelo | Tipo | Descripci贸n |
|
||||
|--------|------|-------------|
|
||||
| **Stable Diffusion XL** | Text-to-Image | Modelo base open-source de alta calidad |
|
||||
| **Fooocus** | Frontend SDXL | Interfaz simplificada para SDXL |
|
||||
| **Gemini 3 Pro Image** | Text-to-Image | Modelo de Google con texto perfecto en im谩genes |
|
||||
| **Seedream 4.0** | Multimodal | Modelo de ByteDance con imagen, video y avatares |
|
||||
| **DeepFloyd IF** | Text-to-Image | Especializado en renderizado de texto |
|
||||
| **GPT-4** | LLM | Modelo de OpenAI para generaci贸n de texto |
|
||||
| **Claude** | LLM | Modelo de Anthropic para generaci贸n de texto |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
Binary file not shown.
@ -0,0 +1,327 @@
|
||||
|
||||
# MVP – Plataforma SaaS de Generación de Contenido y CRM Creativo
|
||||
*(Basada en la investigación de la plataforma tipo Morfeo Academy y tecnologías open source)*
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo General del MVP
|
||||
|
||||
Construir una **plataforma SaaS interna** (modo “SaaS para uso propio”) para una **agencia de publicidad y generación de contenido**, que permita:
|
||||
|
||||
- Generar contenido visual y textual (imágenes, piezas publicitarias, posts para redes, etc.) con IA.
|
||||
- Gestionar campañas, clientes y oportunidades desde un **CRM integrado**.
|
||||
- Automatizar flujos creativos y procesos operativos (desde brief → contenido → aprobación → publicación/entrega).
|
||||
- Operar bajo un **admin con uso ilimitado** + roles internos, con base técnica preparada para futura multi-empresa/multi-tenant.
|
||||
|
||||
La prioridad del MVP es usar **modelos open source auto-hosteados** y, cuando sea estrictamente necesario, **APIs de terceros** para capacidades avanzadas (texto perfecto en imágenes, video/avatares, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 2. Alcance del MVP
|
||||
|
||||
### 2.1 Incluido
|
||||
|
||||
- Núcleo de generación de **imágenes** + **copys** para marketing.
|
||||
- Gestión básica de **clientes, marcas, productos y campañas**.
|
||||
- Módulo para **workflows creativos predefinidos** (plantillas de generación).
|
||||
- **Automatización básica**: triggers desde CRM hacia el motor de generación.
|
||||
- **Gestión de usuarios internos** (equipo de la agencia) con permisos por rol.
|
||||
- Panel de **administración SaaS** para el súper admin (configuración global, límites, monitoreo).
|
||||
|
||||
### 2.2 Fuera de alcance inmediato (para fases posteriores)
|
||||
|
||||
- Generación de video avanzada (SORA, Seedream, etc.) como core.
|
||||
- Entrenamiento auto-servicio 100% self-service de LoRAs por parte del cliente final (en MVP será gestionado por el equipo técnico).
|
||||
- Integraciones profundas con múltiples CRMs externos (inicialmente solo 1 CRM interno y 1 integración externa simple).
|
||||
- Marketplace público multi-tenant con planes comerciales complejos.
|
||||
|
||||
---
|
||||
|
||||
## 3. Perfiles de Usuario
|
||||
|
||||
1. **Súper Admin SaaS (Dueño/CTO)**
|
||||
- Acceso ilimitado a todos los módulos y recursos (sin restricciones de uso).
|
||||
- Configura parámetros globales de infraestructura, colas de tareas y modelos.
|
||||
- Gestiona planes/limitaciones (aunque en MVP sólo se usa el plan interno “ilimitado”).
|
||||
|
||||
2. **Admin de Agencia**
|
||||
- Configura clientes, marcas, equipos y usuarios internos.
|
||||
- Define plantillas y workflows creativos estándar.
|
||||
- Aprueba solicitudes de entrenamiento de modelos personalizados (LoRAs, estilos).
|
||||
|
||||
3. **Creativo/Media Buyer**
|
||||
- Crea proyectos y campañas.
|
||||
- Lanza generaciones de contenido (imágenes y copys) usando plantillas.
|
||||
- Solicita entrenamiento de modelos personalizados (avatar de marca, producto, estilo).
|
||||
- Interactúa con el sistema para iterar versiones.
|
||||
|
||||
4. **Analista/CRM**
|
||||
- Administra leads, contactos, oportunidades y campañas en CRM.
|
||||
- Dispara automatizaciones (por ejemplo, al crear un nuevo producto).
|
||||
- Consulta métricas de campañas y uso de contenido.
|
||||
|
||||
5. **Cliente (externo) – Modo opcional MVP**
|
||||
- Acceso restringido (portal ligero): revisar piezas generadas, aprobar/rechazar, descargar assets.
|
||||
- No lanza generaciones directamente en MVP (esto puede añadirse en una fase posterior).
|
||||
|
||||
---
|
||||
|
||||
## 4. Módulos Funcionales del MVP
|
||||
|
||||
### 4.1 Módulo de Gestión de Cuentas y Tenants
|
||||
|
||||
> Aunque el uso principal es interno, la arquitectura debe soportar **multi-tenant** futuro.
|
||||
|
||||
- **Tenant “Agencia Propia”** como tenant principal.
|
||||
- Estructuras para futuros tenants (clientes/agencias externas) aunque en MVP solo haya uno.
|
||||
- Configuración por tenant:
|
||||
- Nombre, branding (logo, colores base).
|
||||
- Límites de uso (imágenes mensuales, entrenamientos, etc.) – aunque el tenant interno tenga límites desactivados.
|
||||
- Conexiones a CRM interno y/o externo.
|
||||
|
||||
### 4.2 Módulo CRM Integrado
|
||||
|
||||
- **Entidades básicas**:
|
||||
- Contactos (personas).
|
||||
- Empresas / Clientes.
|
||||
- Productos / Servicios.
|
||||
- Oportunidades / Deals.
|
||||
- Campañas de marketing.
|
||||
|
||||
- **Funcionalidades**:
|
||||
- Alta/edición de clientes y contactos.
|
||||
- Registro de oportunidades asociadas a campañas.
|
||||
- Segmentación básica de leads (por industria, tamaño, estado, etc.).
|
||||
- Historial de interacciones (notas, actividades, tareas).
|
||||
- Campos clave para conectar con generación de contenido (ej. tipo de producto, fotos de referencia, identidad de marca, tono de comunicación).
|
||||
|
||||
- **Integración**:
|
||||
- API interna para conectarse con el motor de generación (ej. “nuevo producto → generar pack de 5 imágenes de catálogo y 3 posts de redes”).
|
||||
- Integración mínima con 1 CRM externo (ej. webhooks/REST) para sincronizar datos básicos (opcional en MVP).
|
||||
|
||||
### 4.3 Módulo de Proyectos y Campañas
|
||||
|
||||
- **Proyectos**:
|
||||
- Agrupan campañas y assets por cliente o iniciativa interna.
|
||||
- Estados: Borrador, En curso, En revisión, Cerrado.
|
||||
|
||||
- **Campañas**:
|
||||
- Tipos: redes sociales, performance ads, catálogos, landing pages, etc.
|
||||
- Brief estructurado:
|
||||
- Objetivo de la campaña.
|
||||
- Público objetivo.
|
||||
- Tono de voz.
|
||||
- Canales (IG, FB, TikTok, Google Ads, etc.).
|
||||
- Restricciones (palabras prohibidas, colores de marca, etc.).
|
||||
|
||||
- **Flujos del MVP**:
|
||||
- Crear campaña → seleccionar plantillas de generación (ej. pack de 10 imágenes + 10 copys).
|
||||
- Disparar generación → monitorizar estado → revisión interna → aprobación/entrega.
|
||||
|
||||
### 4.4 Módulo de Motor de Generación de Contenido (IA)
|
||||
|
||||
#### 4.4.1 Núcleo de Modelos y Runtime
|
||||
|
||||
- **Modelos locales (open source, auto-hosteados)**:
|
||||
- Modelo principal **text-to-image** tipo **Stable Diffusion XL** o similar, optimizado para marketing.
|
||||
- Checkpoints especializados:
|
||||
- Fotografía de producto (e-commerce, fondo blanco/contextos comerciales).
|
||||
- Rostros humanos realistas / lifestyle.
|
||||
- Estilos ilustrados (diseño publicitario, vintage, cartoon, etc.).
|
||||
- **ControlNets / Adaptadores**:
|
||||
- Pose humana (OpenPose) para controlar poses de modelos/influencers.
|
||||
- Segmentación (fondo/primer plano).
|
||||
- Otros de utilidad (canny, depth) para composición y ajustes.
|
||||
- Modelos de **upscaling** para entregar imágenes en alta resolución (2x–4x).
|
||||
- Modelos de **inpainting** para correcciones localizadas (ej. arreglar manos, insertar elementos).
|
||||
|
||||
- **Modelos de texto (NLP)**:
|
||||
- Integración con **modelo de lenguaje** (open source o API external) para:
|
||||
- Generar copys, títulos, descripciones, hashtags.
|
||||
- Ajustar tono según el brief.
|
||||
- El MVP puede usar un modelo de OpenAI vía API para texto, dado su bajo costo relativo.
|
||||
|
||||
#### 4.4.2 Workflows de Generación (ComfyUI / Orquestador)
|
||||
|
||||
- Uso de **ComfyUI** como base de construcción de flujos de generación y automatización creativa.
|
||||
- Diseño de varios workflows estándar:
|
||||
1. **Fotografía sintética de producto**
|
||||
- Entrada: fotos de referencia (opcional), descripción de producto, estilo o contexto.
|
||||
- Salida: pack de imágenes listas para catálogo y redes.
|
||||
2. **Post de redes sociales (imagen + copy)**
|
||||
- Entrada: brief corto, producto/servicio, canal objetivo.
|
||||
- Salida: 1–N imágenes + textos sugeridos.
|
||||
3. **Influencer virtual / avatar de marca** (ver más abajo).
|
||||
4. **Variaciones de anuncio**
|
||||
- Generación de múltiples variantes (colores, encuadres, fondos) para pruebas A/B.
|
||||
|
||||
- Exposición de estos workflows como **APIs internas** (ComfyDeploy u otro mecanismo) para que el backend los consuma.
|
||||
|
||||
#### 4.4.3 Personalización de Identidad Visual y Avatares
|
||||
|
||||
- **LoRAs / Fine-tuning**:
|
||||
- Entrenamiento dirigido por el equipo técnico de:
|
||||
- Estilos de marca.
|
||||
- Productos específicos (líneas completas de producto).
|
||||
- Avatares e influencers virtuales (rostros/personajes consistentes).
|
||||
- Interfaz para:
|
||||
- Registrar un “Modelo personalizado” (LoRA/Checkpoint).
|
||||
- Asignarlo a una marca/cliente.
|
||||
- Seleccionarlo en plantillas de generación.
|
||||
|
||||
- **Consistencia de personajes**:
|
||||
- Uso de image prompts, IP-Adapters y nodos tipo Consistent Characters para mantener la misma apariencia a través de múltiples piezas.
|
||||
- Flujos de “reciclaje de referencia”: la última imagen aprobada de un avatar se reutiliza como referencia para nuevas generaciones.
|
||||
|
||||
#### 4.4.4 Generación con Texto Integrado en la Imagen
|
||||
|
||||
- **Modo base (open source)**:
|
||||
- Mejor esfuerzo con SDXL + técnicas complementarias (inpainting, ControlNet, postprocesado).
|
||||
- Alternativa híbrida: generar imagen base y superponer texto con capa vectorial (HTML/CSS/Canvas) desde el front.
|
||||
|
||||
- **Modo premium vía API (opcional)**:
|
||||
- Integración con algún modelo propietario tipo **Gemini 3 Pro Image (Nano Banana Pro)** u otro similar para casos en que se requiera texto perfectamente legible integrado (posters densos, infografías).
|
||||
- Expuesto al usuario como opción “Calidad premium de texto”.
|
||||
|
||||
#### 4.4.5 Contenido Animado y Video (MVP reducido)
|
||||
|
||||
- MVP:
|
||||
- Creación de **GIFs/cinemagraphs** a partir de secuencias de imágenes generadas.
|
||||
- Pipeline básico de “fotogramas clave + interpolación” (usando herramientas IA cuando sea viable).
|
||||
|
||||
- Futuro (fuera del MVP, pero preparado en arquitectura):
|
||||
- Integración con APIs de texto-a-video (Runway, Seedream, SORA, etc.).
|
||||
- Integración con servicios de avatares parlantes (HeyGen u otros).
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Módulo de Automatización Creativa y Flujos (Orquestación)
|
||||
|
||||
- Integración con un orquestador tipo **n8n** o similar para automatizar tareas entre:
|
||||
- CRM interno.
|
||||
- Motor de generación de contenido.
|
||||
- Sistemas externos (por ejemplo, herramientas de email marketing, redes sociales).
|
||||
|
||||
- Flujos MVP:
|
||||
1. **Nuevo producto en CRM → Generar Kit de Assets**
|
||||
- Dispara workflow de fotografía de producto + posts base.
|
||||
2. **Nueva campaña → Generar lote de creatividades**
|
||||
- Crea assets iniciales para revisión interna.
|
||||
3. **Cambio de estado de campaña (Aprobada) → Notificación/Entrega**
|
||||
- Notifica a responsables; genera zip descargable o publica en espacio compartido.
|
||||
|
||||
- Sistema de **colas de tareas**:
|
||||
- Distribución de trabajos en GPU(s).
|
||||
- Manejo de prioridades (trabajos urgentes vs batch).
|
||||
|
||||
---
|
||||
|
||||
### 4.6 Módulo de Biblioteca de Activos (DAM)
|
||||
|
||||
- Repositorio central de:
|
||||
- Imágenes generadas.
|
||||
- Copys, prompts y briefs.
|
||||
- Modelos personalizados (LoRAs, checkpoints, presets de workflows).
|
||||
|
||||
- Funciones:
|
||||
- Búsqueda avanzada (por cliente, campaña, tags, tipo de contenido, fecha).
|
||||
- Versionado de assets (iteraciones sobre una misma pieza).
|
||||
- Estados: borrador, en revisión, aprobado, publicado.
|
||||
- Descarga individual o en paquetes.
|
||||
|
||||
---
|
||||
|
||||
### 4.7 Módulo de Administración SaaS y Configuración
|
||||
|
||||
- Gestión de:
|
||||
- Usuarios, roles y permisos.
|
||||
- Tenants (al menos el tenant “Agencia Propia” en el MVP).
|
||||
- Parámetros globales:
|
||||
- Límites de tamaño/formatos de salida.
|
||||
- Modelos activos/inactivos.
|
||||
- Política de retención de assets.
|
||||
|
||||
- Auditoría básica:
|
||||
- Log de acciones relevantes (generaciones, entrenamientos, cambios de configuración).
|
||||
- Métricas de uso por usuario y campaña (número de imágenes generadas, GPU time estimado, etc.).
|
||||
|
||||
- Preparación para **planes de suscripción** (aunque en MVP no se comercializa externamente):
|
||||
- Estructura para planes (Free, Pro, Enterprise, Interno Ilimitado).
|
||||
- Parámetros por plan: generaciones/mes, entrenamientos, acceso a modo premium de texto, etc.
|
||||
|
||||
---
|
||||
|
||||
### 4.8 Módulo de Analítica y Reporting
|
||||
|
||||
- **Dashboards básicos**:
|
||||
- Volumen de contenido generado por periodo, cliente, campaña.
|
||||
- Tiempo promedio de generación y aprobación.
|
||||
- Uso de modelos personalizados (cuántas campañas usan cada LoRA/estilo).
|
||||
- Relación entre campañas y assets generados (para análisis posterior de performance).
|
||||
|
||||
- **KPI iniciales**:
|
||||
- Nº de campañas activas.
|
||||
- Nº de assets por campaña.
|
||||
- Porcentaje de assets aprobados en primera iteración.
|
||||
|
||||
- Exportación:
|
||||
- Descarga de reportes en CSV/Excel.
|
||||
- API para integración con herramientas de BI.
|
||||
|
||||
---
|
||||
|
||||
### 4.9 Módulo de Seguridad, Permisos y Cumplimiento
|
||||
|
||||
- **Autenticación y autorización**:
|
||||
- Login con email + password (más adelante SSO/OAuth).
|
||||
- Roles y permisos por módulo/acción (RBAC).
|
||||
|
||||
- **Aislamiento lógico por tenant** pese a que inicialmente sólo se use uno.
|
||||
- **Gestión de datos sensibles**:
|
||||
- Cifrado en tránsito (HTTPS).
|
||||
- Buenas prácticas de manejo de imágenes de personas (consentimiento, uso de datos, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 5. Requisitos Técnicos de Alto Nivel
|
||||
|
||||
### 5.1 Infraestructura
|
||||
|
||||
- Servidor con **GPU dedicada** (idealmente 12–24 GB VRAM) para ejecutar los modelos de imagen (SDXL, LoRAs, ControlNets, upscaling).
|
||||
- Arquitectura con:
|
||||
- **Backend monolítico** (API REST/GraphQL) para lógica de negocio, colas y orquestación.
|
||||
- **Servicio(s) de inferencia** (ComfyUI/Deploy, pipelines Diffusers, etc.) comunicados vía HTTP/ gRPC.
|
||||
- **Base de datos** relacional para CRM, proyectos, campañas, usuarios, configuración.
|
||||
- Almacenamiento de archivos (local o S3-compatible) para assets e imágenes generadas.
|
||||
|
||||
- Sistema de **colado y scheduling de tareas**:
|
||||
- Gestión eficiente de jobs de generación y entrenamiento.
|
||||
- Posibilidad de añadir más GPUs/instancias a futuro.
|
||||
|
||||
### 5.2 Integraciones Clave
|
||||
|
||||
- Orquestador (n8n u otro) para flujos entre módulos.
|
||||
- APIs externas **opcionales**:
|
||||
- Modelos de texto avanzados (LLMs).
|
||||
- Modelos de imágenes con texto perfecto (Gemini/Seedream u otros) – sólo para casos premium.
|
||||
- Servicios de video/avatares (etapa futura).
|
||||
|
||||
---
|
||||
|
||||
## 6. Roadmap de Evolución (en alto nivel)
|
||||
|
||||
> No forma parte estricta del MVP, pero orienta el desglose posterior.
|
||||
|
||||
1. **Fase 1 – Core MVP**
|
||||
- Módulos: usuarios/roles, CRM básico, proyectos y campañas, motor de generación de imágenes + copys, DAM básico.
|
||||
- Workflows estándar de generación de producto y posts.
|
||||
|
||||
2. **Fase 2 – Personalización Avanzada**
|
||||
- Entrenamiento recurrente de LoRAs/estilos.
|
||||
- Avatares/influencers virtuales consistentes.
|
||||
- Integración más profunda con CRM e orquestador.
|
||||
|
||||
3. **Fase 3 – Contenido Enriquecido y Multi-tenant Comercial**
|
||||
- Video, avatares parlantes, modos premium con APIs externas.
|
||||
- Apertura a clientes externos (modo SaaS completo).
|
||||
- Planes de suscripción y límites de uso.
|
||||
@ -0,0 +1,466 @@
|
||||
# Platform Marketing Content - Plataforma SaaS de Generaci贸n de Contenido y CRM Creativo
|
||||
|
||||
**Versi贸n:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Fase de An谩lisis y Documentaci贸n
|
||||
**Modelo de Negocio:** SaaS Multi-tenant (uso interno inicial)
|
||||
|
||||
---
|
||||
|
||||
## 1. Resumen Ejecutivo
|
||||
|
||||
### 1.1 Visi贸n del Producto
|
||||
|
||||
**Platform Marketing Content** es una plataforma SaaS interna para **agencias de publicidad y generaci贸n de contenido** que combina:
|
||||
|
||||
1. **Motor de Generaci贸n de Contenido con IA** - Im谩genes, copys, posts para redes sociales
|
||||
2. **CRM Integrado** - Gesti贸n de clientes, marcas, campa帽as y oportunidades
|
||||
3. **Automatizaci贸n Creativa** - Flujos desde brief 鈫? contenido 鈫? aprobaci贸n 鈫? entrega
|
||||
4. **DAM (Digital Asset Management)** - Biblioteca centralizada de activos generados
|
||||
|
||||
### 1.2 Propuesta de Valor
|
||||
|
||||
| Problema Actual | Soluci贸n Propuesta |
|
||||
|-----------------|-------------------|
|
||||
| Creaci贸n manual de contenido visual costosa y lenta | Generaci贸n autom谩tica con IA (Stable Diffusion, ComfyUI) |
|
||||
| Dependencia de fot贸grafos y sets f铆sicos | Fotograf铆a sint茅tica de productos hiperrealista |
|
||||
| Inconsistencia de marca entre campa帽as | LoRAs entrenados por marca/producto |
|
||||
| Gesti贸n fragmentada de clientes y campa帽as | CRM integrado con motor de generaci贸n |
|
||||
| Costos elevados por APIs comerciales (Midjourney, DALL-E) | Modelos open-source auto-hosteados |
|
||||
| Flujos manuales de aprobaci贸n | Automatizaci贸n con n8n + workflows predefinidos |
|
||||
|
||||
### 1.3 Diferenciadores Clave
|
||||
|
||||
```
|
||||
鉁? Modelos Open Source + Auto-hosting = Costos controlados
|
||||
鉁? LoRAs personalizados por marca = Consistencia visual
|
||||
鉁? CRM + Generaci贸n integrados = Flujo end-to-end
|
||||
鉁? ComfyUI como orquestador = Workflows visuales y escalables
|
||||
鉁? Arquitectura preparada para multi-tenant = Escalabilidad futura
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Objetivos del MVP
|
||||
|
||||
### 2.1 Objetivos de Negocio
|
||||
|
||||
| Objetivo | M茅trica de 脡xito | Plazo |
|
||||
|----------|------------------|-------|
|
||||
| Reducir tiempo de creaci贸n de contenido | -70% tiempo vs proceso manual | MVP |
|
||||
| Eliminar dependencia de servicios externos | 100% generaci贸n local | MVP |
|
||||
| Centralizar gesti贸n de clientes y campa帽as | 1 plataforma unificada | MVP |
|
||||
| Habilitar personalizaci贸n por marca | LoRAs entrenados por cliente | Fase 2 |
|
||||
|
||||
### 2.2 Objetivos T茅cnicos
|
||||
|
||||
| Objetivo | Descripci贸n |
|
||||
|----------|-------------|
|
||||
| **Auto-hosting** | Ejecutar modelos en GPU dedicada (12-24GB VRAM) |
|
||||
| **Arquitectura modular** | Separaci贸n clara: CRM, Motor IA, DAM, Automatizaci贸n |
|
||||
| **Multi-tenant ready** | Estructura preparada para m煤ltiples tenants |
|
||||
| **API-first** | Todos los servicios expuestos v铆a REST/GraphQL |
|
||||
| **Escalabilidad** | Colas de tareas, distribuci贸n en GPUs |
|
||||
|
||||
---
|
||||
|
||||
## 3. Alcance del MVP
|
||||
|
||||
### 3.1 Incluido en MVP (Fase 1)
|
||||
|
||||
| M贸dulo | Funcionalidades Core |
|
||||
|--------|---------------------|
|
||||
| **CRM Integrado** | Clientes, marcas, productos, campa帽as, oportunidades |
|
||||
| **Motor de Generaci贸n** | Text-to-image (SDXL), copys con LLM, upscaling |
|
||||
| **Workflows Creativos** | Fotograf铆a de producto, posts para redes, variaciones |
|
||||
| **DAM B谩sico** | Repositorio de im谩genes, b煤squeda, estados (borrador/aprobado) |
|
||||
| **Automatizaci贸n B谩sica** | Triggers CRM 鈫? Generaci贸n |
|
||||
| **Admin SaaS** | Usuarios, roles, configuraci贸n global |
|
||||
|
||||
### 3.2 Fuera de Alcance MVP (Fases Posteriores)
|
||||
|
||||
| Feature | Fase Planificada |
|
||||
|---------|------------------|
|
||||
| Generaci贸n de video avanzada (SORA, Seedream) | Fase 3 |
|
||||
| Entrenamiento self-service de LoRAs | Fase 2 |
|
||||
| Marketplace multi-tenant p煤blico | Fase 4 |
|
||||
| Integraciones profundas con CRMs externos | Fase 2 |
|
||||
| Avatares parlantes (HeyGen, etc.) | Fase 3 |
|
||||
| Texto perfecto en im谩genes (Gemini API) | Fase 2 (opcional) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Perfiles de Usuario
|
||||
|
||||
### 4.1 Usuarios Internos (MVP)
|
||||
|
||||
| Perfil | Descripci贸n | Permisos Principales |
|
||||
|--------|-------------|---------------------|
|
||||
| **S煤per Admin SaaS** | Due帽o/CTO de la plataforma | Acceso ilimitado, configuraci贸n global, modelos |
|
||||
| **Admin de Agencia** | Configura clientes, equipos, plantillas | Gesti贸n de usuarios, workflows, aprobaciones |
|
||||
| **Creativo/Media Buyer** | Lanza generaciones, itera versiones | Crear proyectos, generar contenido, solicitar LoRAs |
|
||||
| **Analista/CRM** | Gestiona leads, campa帽as, m茅tricas | CRM completo, triggers de automatizaci贸n |
|
||||
|
||||
### 4.2 Usuarios Externos (Opcional MVP)
|
||||
|
||||
| Perfil | Descripci贸n | Permisos |
|
||||
|--------|-------------|----------|
|
||||
| **Cliente (portal ligero)** | Revisa piezas, aprueba/rechaza | Solo lectura + aprobaci贸n |
|
||||
|
||||
---
|
||||
|
||||
## 5. M贸dulos Funcionales
|
||||
|
||||
### 5.1 M贸dulo 1: Gesti贸n de Cuentas y Tenants (PMC-001)
|
||||
|
||||
**Objetivo:** Arquitectura multi-tenant preparada para escalar.
|
||||
|
||||
```yaml
|
||||
Funcionalidades:
|
||||
- Tenant principal "Agencia Propia"
|
||||
- Estructura para futuros tenants externos
|
||||
- Configuraci贸n por tenant: branding, l铆mites, conexiones
|
||||
- L铆mites de uso (im谩genes/mes, entrenamientos)
|
||||
```
|
||||
|
||||
### 5.2 M贸dulo 2: CRM Integrado (PMC-002)
|
||||
|
||||
**Objetivo:** Gestionar clientes, marcas y campa帽as con conexi贸n directa al motor de generaci贸n.
|
||||
|
||||
```yaml
|
||||
Entidades:
|
||||
- Contactos (personas)
|
||||
- Empresas/Clientes
|
||||
- Marcas (brand identity, tono, restricciones)
|
||||
- Productos/Servicios
|
||||
- Oportunidades/Deals
|
||||
- Campa帽as de Marketing
|
||||
|
||||
Funcionalidades:
|
||||
- Alta/edici贸n de clientes y contactos
|
||||
- Segmentaci贸n de leads
|
||||
- Historial de interacciones
|
||||
- Campos para generaci贸n: fotos referencia, identidad marca, tono
|
||||
- API interna 鈫? motor de generaci贸n
|
||||
```
|
||||
|
||||
### 5.3 M贸dulo 3: Proyectos y Campa帽as (PMC-003)
|
||||
|
||||
**Objetivo:** Organizar el trabajo creativo por iniciativa.
|
||||
|
||||
```yaml
|
||||
Proyectos:
|
||||
- Agrupan campa帽as y assets por cliente
|
||||
- Estados: Borrador, En curso, En revisi贸n, Cerrado
|
||||
|
||||
Campa帽as:
|
||||
- Tipos: redes sociales, performance ads, cat谩logos, landing pages
|
||||
- Brief estructurado:
|
||||
- Objetivo de campa帽a
|
||||
- P煤blico objetivo
|
||||
- Tono de voz
|
||||
- Canales (IG, FB, TikTok, Google Ads)
|
||||
- Restricciones (palabras prohibidas, colores marca)
|
||||
|
||||
Flujos:
|
||||
- Crear campa帽a 鈫? seleccionar plantillas 鈫? disparar generaci贸n 鈫? revisi贸n 鈫? entrega
|
||||
```
|
||||
|
||||
### 5.4 M贸dulo 4: Motor de Generaci贸n de Contenido IA (PMC-004)
|
||||
|
||||
**Objetivo:** N煤cleo de generaci贸n de im谩genes y copys.
|
||||
|
||||
```yaml
|
||||
4.1 Modelos Locales (Open Source):
|
||||
Imagen:
|
||||
- Stable Diffusion XL (principal)
|
||||
- Checkpoints especializados:
|
||||
- Fotograf铆a de producto (e-commerce)
|
||||
- Rostros humanos realistas
|
||||
- Estilos ilustrados (publicidad, vintage)
|
||||
- ControlNets: OpenPose, segmentaci贸n, canny, depth
|
||||
- Upscaling: 2x-4x
|
||||
- Inpainting: correcciones localizadas
|
||||
|
||||
Texto (NLP):
|
||||
- LLM para copys, t铆tulos, hashtags
|
||||
- Ajuste de tono seg煤n brief
|
||||
- OpenAI API (bajo costo) o modelo local
|
||||
|
||||
4.2 Workflows ComfyUI:
|
||||
Predefinidos:
|
||||
- Fotograf铆a sint茅tica de producto
|
||||
- Post redes sociales (imagen + copy)
|
||||
- Variaciones de anuncio (A/B testing)
|
||||
- Influencer virtual / avatar de marca
|
||||
|
||||
Caracter铆sticas:
|
||||
- Exposici贸n como APIs internas (ComfyDeploy)
|
||||
- Workflows personalizables por usuario
|
||||
|
||||
4.3 Personalizaci贸n (LoRAs):
|
||||
- Entrenamiento dirigido por equipo t茅cnico:
|
||||
- Estilos de marca
|
||||
- Productos espec铆ficos
|
||||
- Avatares/influencers virtuales
|
||||
- Interfaz para registrar y asignar modelos personalizados
|
||||
- Consistencia de personajes (IP-Adapters, Consistent Characters)
|
||||
|
||||
4.4 Texto en Im谩genes:
|
||||
Modo base:
|
||||
- SDXL + t茅cnicas complementarias
|
||||
- Superposici贸n vectorial (HTML/CSS/Canvas)
|
||||
Modo premium (opcional):
|
||||
- API Gemini 3 Pro Image para tipograf铆a perfecta
|
||||
```
|
||||
|
||||
### 5.5 M贸dulo 5: Automatizaci贸n Creativa (PMC-005)
|
||||
|
||||
**Objetivo:** Conectar CRM con motor de generaci贸n mediante flujos automatizados.
|
||||
|
||||
```yaml
|
||||
Orquestador: n8n (o similar)
|
||||
|
||||
Flujos MVP:
|
||||
1. Nuevo producto en CRM 鈫? Generar Kit de Assets
|
||||
- Dispara workflow de fotograf铆a + posts base
|
||||
|
||||
2. Nueva campa帽a 鈫? Generar lote de creatividades
|
||||
- Crea assets iniciales para revisi贸n
|
||||
|
||||
3. Cambio estado campa帽a (Aprobada) 鈫? Notificaci贸n/Entrega
|
||||
- Notifica responsables
|
||||
- Genera zip descargable
|
||||
|
||||
Sistema de Colas:
|
||||
- Distribuci贸n de jobs en GPU(s)
|
||||
- Manejo de prioridades (urgente vs batch)
|
||||
```
|
||||
|
||||
### 5.6 M贸dulo 6: Biblioteca de Activos DAM (PMC-006)
|
||||
|
||||
**Objetivo:** Repositorio central de contenido generado.
|
||||
|
||||
```yaml
|
||||
Contenido:
|
||||
- Im谩genes generadas
|
||||
- Copys, prompts, briefs
|
||||
- Modelos personalizados (LoRAs, checkpoints, presets)
|
||||
|
||||
Funciones:
|
||||
- B煤squeda avanzada (cliente, campa帽a, tags, tipo, fecha)
|
||||
- Versionado de assets
|
||||
- Estados: borrador, en revisi贸n, aprobado, publicado
|
||||
- Descarga individual o en paquetes
|
||||
```
|
||||
|
||||
### 5.7 M贸dulo 7: Administraci贸n SaaS (PMC-007)
|
||||
|
||||
**Objetivo:** Panel de control de la plataforma.
|
||||
|
||||
```yaml
|
||||
Gesti贸n:
|
||||
- Usuarios, roles, permisos (RBAC)
|
||||
- Tenants (al menos "Agencia Propia" en MVP)
|
||||
- Par谩metros globales:
|
||||
- L铆mites de tama帽o/formatos
|
||||
- Modelos activos/inactivos
|
||||
- Pol铆tica de retenci贸n de assets
|
||||
|
||||
Auditor铆a:
|
||||
- Log de acciones (generaciones, entrenamientos, cambios)
|
||||
- M茅tricas de uso por usuario y campa帽a
|
||||
|
||||
Preparaci贸n para planes:
|
||||
- Estructura: Free, Pro, Enterprise, Interno Ilimitado
|
||||
- Par谩metros por plan: generaciones/mes, entrenamientos, etc.
|
||||
```
|
||||
|
||||
### 5.8 M贸dulo 8: Anal铆tica y Reporting (PMC-008)
|
||||
|
||||
**Objetivo:** Visibilidad del rendimiento y uso.
|
||||
|
||||
```yaml
|
||||
Dashboards:
|
||||
- Volumen generado por periodo/cliente/campa帽a
|
||||
- Tiempo promedio de generaci贸n y aprobaci贸n
|
||||
- Uso de modelos personalizados
|
||||
- Relaci贸n campa帽as-assets
|
||||
|
||||
KPIs Iniciales:
|
||||
- N煤mero de campa帽as activas
|
||||
- Assets por campa帽a
|
||||
- % assets aprobados en primera iteraci贸n
|
||||
|
||||
Exportaci贸n:
|
||||
- CSV/Excel
|
||||
- API para herramientas BI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Requisitos T茅cnicos
|
||||
|
||||
### 6.1 Stack Tecnol贸gico
|
||||
|
||||
```yaml
|
||||
Backend:
|
||||
- Node.js 20+ / NestJS + TypeScript
|
||||
- PostgreSQL 15+ (multi-tenant ready)
|
||||
- Redis (caching, colas)
|
||||
- Bull/BullMQ (jobs de generaci贸n)
|
||||
|
||||
Frontend:
|
||||
- React 18 + Vite + TypeScript
|
||||
- TailwindCSS / Shadcn UI
|
||||
- React Query (data fetching)
|
||||
|
||||
Motor IA:
|
||||
- ComfyUI (orquestaci贸n de workflows)
|
||||
- ComfyDeploy (API de inferencia)
|
||||
- Stable Diffusion XL + checkpoints
|
||||
- Python 3.10+ / Diffusers (alternativa)
|
||||
|
||||
Automatizaci贸n:
|
||||
- n8n (flujos entre m贸dulos)
|
||||
|
||||
Almacenamiento:
|
||||
- S3 / MinIO (assets e im谩genes)
|
||||
- PostgreSQL (metadata, CRM)
|
||||
|
||||
Infraestructura:
|
||||
- Docker / Docker Compose
|
||||
- GPU: NVIDIA 12-24GB VRAM (L4, RTX 3090/4090, A5000)
|
||||
```
|
||||
|
||||
### 6.2 Arquitectura de Alto Nivel
|
||||
|
||||
```
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? Frontend (React + Vite) 鉁?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁?
|
||||
鈻?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? Backend API (NestJS) 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁? 鉁? CRM 鉁? 鉁? Projects鉁? 鉁? Assets 鉁? 鉁?
|
||||
鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? 鉁? 鉁?
|
||||
鈻? 鈻? 鈻?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? PostgreSQL 鉁? 鉁? ComfyUI 鉁? 鉁? n8n 鉁?
|
||||
鉁? + Redis 鉁? 鉁? (GPU GPU) 鉁? 鉁? (Workflows) 鉁?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁? 鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁?
|
||||
鈻?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
鉁? S3/MinIO 鉁?
|
||||
鉁? (Assets) 鉁?
|
||||
鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁斺攢鉁?
|
||||
```
|
||||
|
||||
### 6.3 Integraciones Clave
|
||||
|
||||
| Integraci贸n | Prioridad | Descripci贸n |
|
||||
|-------------|-----------|-------------|
|
||||
| ComfyUI / ComfyDeploy | Alta | Motor de workflows de generaci贸n |
|
||||
| n8n | Alta | Orquestaci贸n de automatizaciones |
|
||||
| OpenAI API | Media | LLM para generaci贸n de copys |
|
||||
| Gemini API | Baja | Texto perfecto en im谩genes (opcional) |
|
||||
| CRM Externo | Baja | Sincronizaci贸n b谩sica via webhooks |
|
||||
|
||||
---
|
||||
|
||||
## 7. Roadmap
|
||||
|
||||
### Fase 1: MVP Core (Semanas 1-8)
|
||||
|
||||
```yaml
|
||||
Entregables:
|
||||
- Arquitectura base (backend + frontend)
|
||||
- CRM b谩sico (clientes, marcas, productos)
|
||||
- Motor de generaci贸n con 2-3 workflows
|
||||
- DAM b谩sico
|
||||
- Admin de usuarios y roles
|
||||
|
||||
M茅trica de 脡xito:
|
||||
- Generar 100 im谩genes de producto
|
||||
- 3 campa帽as completas end-to-end
|
||||
```
|
||||
|
||||
### Fase 2: Personalizaci贸n Avanzada (Semanas 9-14)
|
||||
|
||||
```yaml
|
||||
Entregables:
|
||||
- Entrenamiento de LoRAs por marca
|
||||
- Avatares/influencers virtuales consistentes
|
||||
- Integraci贸n profunda CRM 鈫? Generaci贸n
|
||||
- Workflows adicionales
|
||||
|
||||
M茅trica de 脡xito:
|
||||
- 5 LoRAs entrenados por marca
|
||||
- 90% consistencia en personajes
|
||||
```
|
||||
|
||||
### Fase 3: Contenido Enriquecido (Semanas 15-22)
|
||||
|
||||
```yaml
|
||||
Entregables:
|
||||
- GIFs/cinemagraphs
|
||||
- Integraci贸n de video b谩sico
|
||||
- APIs premium opcionales (Gemini para texto)
|
||||
- Portal cliente externo
|
||||
|
||||
M茅trica de 脡xito:
|
||||
- 50% de campa帽as con contenido animado
|
||||
- 3 clientes externos usando portal
|
||||
```
|
||||
|
||||
### Fase 4: Multi-tenant Comercial (Semanas 23+)
|
||||
|
||||
```yaml
|
||||
Entregables:
|
||||
- Apertura a clientes externos (SaaS completo)
|
||||
- Planes de suscripci贸n y l铆mites
|
||||
- Marketplace de extensiones
|
||||
|
||||
M茅trica de 脡xito:
|
||||
- 10 tenants activos
|
||||
- MRR positivo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Riesgos y Mitigaciones
|
||||
|
||||
| Riesgo | Impacto | Probabilidad | Mitigaci贸n |
|
||||
|--------|---------|--------------|------------|
|
||||
| Calidad de generaci贸n insuficiente | Alto | Media | Checkpoints especializados, postprocesado |
|
||||
| Requisitos de GPU elevados | Medio | Alta | Optimizaci贸n de modelos, colas de prioridad |
|
||||
| Texto ilegible en im谩genes | Medio | Alta | Modo h铆brido (IA + superposici贸n) |
|
||||
| Inconsistencia de personajes | Medio | Media | LoRAs + IP-Adapters + image prompts |
|
||||
| Complejidad de ComfyUI | Medio | Media | Workflows pre-construidos, documentaci贸n |
|
||||
|
||||
---
|
||||
|
||||
## 9. Referencias
|
||||
|
||||
### 9.1 Documentaci贸n del Proyecto
|
||||
|
||||
- [MVP Original](./MVP_Plataforma_SaaS_Contenido_CRM.md) - Definici贸n inicial
|
||||
- [Investigaci贸n Morfeo Academy](./Investigaci贸n%20Profunda_%20Plataforma%20de%20Generaci贸n%20de%20Contenido%20de%20Morfeo%20Academy%20y%20Desarrollo%20de%20una%20.pdf) - An谩lisis de referencia
|
||||
|
||||
### 9.2 Tecnolog铆as de Referencia
|
||||
|
||||
- [Morfeo Academy](https://www.morfeoacademy.com/) - Referencia de plataforma similar
|
||||
- [ComfyUI](https://github.com/comfyanonymous/ComfyUI) - Motor de workflows
|
||||
- [ComfyDeploy](https://www.comfydeploy.com/) - API de inferencia
|
||||
- [Stable Diffusion XL](https://stability.ai/) - Modelo base de generaci贸n
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
**Pr贸xima revisi贸n:** Al completar Fase 1
|
||||
@ -0,0 +1,275 @@
|
||||
# PMC-001: Módulo de Tenants
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Definición
|
||||
**Prioridad:** Alta
|
||||
|
||||
---
|
||||
|
||||
## Descripción General
|
||||
|
||||
El módulo de Tenants proporciona la arquitectura multi-tenant que permite aislar datos y configuraciones entre diferentes organizaciones (agencias) que utilicen la plataforma.
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Aislar completamente los datos entre tenants
|
||||
2. Permitir configuración personalizada por tenant
|
||||
3. Soportar branding personalizado (logo, colores)
|
||||
4. Gestionar límites y cuotas por tenant
|
||||
5. Preparar arquitectura para comercialización SaaS futura
|
||||
|
||||
---
|
||||
|
||||
## Entidades del Dominio
|
||||
|
||||
### Tenant
|
||||
|
||||
```yaml
|
||||
Entidad: Tenant
|
||||
Descripción: Organización/agencia que utiliza la plataforma
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- name: string (nombre de la organización)
|
||||
- slug: string (identificador URL-friendly, único)
|
||||
- status: enum [active, suspended, trial, cancelled]
|
||||
- plan_id: UUID (FK a Plan)
|
||||
- settings: JSONB (configuración personalizada)
|
||||
- branding: JSONB (logo_url, primary_color, secondary_color)
|
||||
- limits: JSONB (cuotas y límites)
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
- deleted_at: timestamp (soft delete)
|
||||
|
||||
Relaciones:
|
||||
- 1:N con User
|
||||
- 1:N con Client (CRM)
|
||||
- 1:N con Project
|
||||
- 1:N con Asset
|
||||
- N:1 con Plan
|
||||
```
|
||||
|
||||
### Plan
|
||||
|
||||
```yaml
|
||||
Entidad: Plan
|
||||
Descripción: Plan de suscripción con límites definidos
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- name: string (Free, Pro, Enterprise, Internal)
|
||||
- code: string (identificador único)
|
||||
- features: JSONB (funcionalidades habilitadas)
|
||||
- limits: JSONB
|
||||
- generations_per_month: number
|
||||
- trainings_per_month: number
|
||||
- storage_gb: number
|
||||
- users_max: number
|
||||
- projects_max: number
|
||||
- price_monthly: decimal
|
||||
- price_yearly: decimal
|
||||
- is_active: boolean
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- 1:N con Tenant
|
||||
```
|
||||
|
||||
### TenantSettings
|
||||
|
||||
```yaml
|
||||
Estructura JSONB: settings
|
||||
Campos:
|
||||
- default_language: string (es, en)
|
||||
- timezone: string
|
||||
- date_format: string
|
||||
- currency: string
|
||||
- notifications:
|
||||
email_enabled: boolean
|
||||
slack_webhook: string
|
||||
- integrations:
|
||||
crm_external_url: string
|
||||
n8n_webhook_base: string
|
||||
- generation:
|
||||
default_model: string
|
||||
default_quality: string
|
||||
watermark_enabled: boolean
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### F-001.1: Gestión de Tenants
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-001.1.1 | Crear tenant | Registro de nueva organización | Alta |
|
||||
| F-001.1.2 | Editar tenant | Modificar datos y configuración | Alta |
|
||||
| F-001.1.3 | Suspender tenant | Desactivar acceso temporalmente | Media |
|
||||
| F-001.1.4 | Eliminar tenant | Soft delete con retención de datos | Baja |
|
||||
|
||||
### F-001.2: Configuración por Tenant
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-001.2.1 | Branding | Personalizar logo y colores | Media |
|
||||
| F-001.2.2 | Límites | Configurar cuotas de uso | Alta |
|
||||
| F-001.2.3 | Integraciones | URLs de webhooks y APIs externas | Media |
|
||||
| F-001.2.4 | Preferencias | Idioma, zona horaria, formatos | Baja |
|
||||
|
||||
### F-001.3: Aislamiento de Datos
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-001.3.1 | RLS PostgreSQL | Row-Level Security por tenant_id | Alta |
|
||||
| F-001.3.2 | Middleware | Inyección automática de tenant_id | Alta |
|
||||
| F-001.3.3 | Storage isolation | Prefijos S3 por tenant | Alta |
|
||||
|
||||
---
|
||||
|
||||
## Reglas de Negocio
|
||||
|
||||
```yaml
|
||||
RN-001.1:
|
||||
Descripción: Todo tenant debe tener un plan asignado
|
||||
Validación: plan_id NOT NULL
|
||||
Default: Plan "Free" o "Internal"
|
||||
|
||||
RN-001.2:
|
||||
Descripción: El slug del tenant debe ser único y URL-safe
|
||||
Validación: Regex ^[a-z0-9-]+$, único en BD
|
||||
|
||||
RN-001.3:
|
||||
Descripción: Tenant suspendido no permite login de usuarios
|
||||
Acción: Validar status en middleware de autenticación
|
||||
|
||||
RN-001.4:
|
||||
Descripción: Límites del plan se heredan al tenant
|
||||
Override: Tenant puede tener límites personalizados que sobreescriban el plan
|
||||
|
||||
RN-001.5:
|
||||
Descripción: Soft delete conserva datos 90 días
|
||||
Acción: deleted_at marca fecha, cron job limpia después
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```yaml
|
||||
Base: /api/v1/tenants
|
||||
|
||||
Endpoints:
|
||||
POST /tenants:
|
||||
Descripción: Crear nuevo tenant (Super Admin)
|
||||
Body: { name, slug, plan_id, settings? }
|
||||
Response: 201 Created
|
||||
|
||||
GET /tenants:
|
||||
Descripción: Listar tenants (Super Admin)
|
||||
Query: ?status=active&page=1&limit=20
|
||||
Response: 200 OK (paginado)
|
||||
|
||||
GET /tenants/:id:
|
||||
Descripción: Obtener tenant por ID
|
||||
Response: 200 OK
|
||||
|
||||
PUT /tenants/:id:
|
||||
Descripción: Actualizar tenant
|
||||
Body: { name?, settings?, branding?, limits? }
|
||||
Response: 200 OK
|
||||
|
||||
PATCH /tenants/:id/status:
|
||||
Descripción: Cambiar estado del tenant
|
||||
Body: { status: "suspended" | "active" }
|
||||
Response: 200 OK
|
||||
|
||||
DELETE /tenants/:id:
|
||||
Descripción: Soft delete tenant
|
||||
Response: 204 No Content
|
||||
|
||||
GET /tenants/current:
|
||||
Descripción: Obtener tenant del usuario actual
|
||||
Response: 200 OK
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
```yaml
|
||||
Dependencias del Catálogo:
|
||||
- @CATALOG_TENANT: Patrón base multi-tenancy (adaptar)
|
||||
|
||||
Dependencias de Módulos:
|
||||
- PMC-007 Admin: Gestión de planes y configuración global
|
||||
|
||||
Servicios Externos:
|
||||
- Ninguno requerido
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consideraciones Técnicas
|
||||
|
||||
### Estrategia Multi-Tenant
|
||||
|
||||
```yaml
|
||||
Tipo: Single Database, Shared Schema
|
||||
Aislamiento: Row-Level Security (RLS) en PostgreSQL
|
||||
|
||||
Implementación:
|
||||
1. Columna tenant_id en todas las tablas principales
|
||||
2. Políticas RLS que filtran por tenant_id
|
||||
3. Middleware que extrae tenant del JWT/sesión
|
||||
4. SET app.current_tenant antes de cada query
|
||||
```
|
||||
|
||||
### Ejemplo RLS PostgreSQL
|
||||
|
||||
```sql
|
||||
-- Política para tabla clients
|
||||
CREATE POLICY tenant_isolation_policy ON clients
|
||||
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
||||
|
||||
-- Habilitar RLS
|
||||
ALTER TABLE clients ENABLE ROW LEVEL SECURITY;
|
||||
```
|
||||
|
||||
### Storage Isolation
|
||||
|
||||
```yaml
|
||||
Estructura S3:
|
||||
bucket/
|
||||
├── {tenant_slug}/
|
||||
│ ├── assets/
|
||||
│ ├── generated/
|
||||
│ ├── models/
|
||||
│ └── temp/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] Tenant puede ser creado con datos mínimos (name, slug, plan)
|
||||
- [ ] RLS impide acceso a datos de otros tenants
|
||||
- [ ] Branding se refleja en UI del tenant
|
||||
- [ ] Límites del plan se validan antes de operaciones
|
||||
- [ ] Soft delete funciona correctamente
|
||||
- [ ] API responde correctamente a todos los endpoints
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
- [@CATALOG_TENANT](../../../core/catalog/modules/multi-tenancy/)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,387 @@
|
||||
# PMC-002: Módulo CRM
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Definición
|
||||
**Prioridad:** Alta
|
||||
|
||||
---
|
||||
|
||||
## Descripción General
|
||||
|
||||
El módulo CRM (Customer Relationship Management) gestiona clientes, marcas, productos y la relación entre estos elementos. Está diseñado para integrarse directamente con el motor de generación de contenido IA.
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Gestionar clientes y contactos de la agencia
|
||||
2. Organizar marcas y productos por cliente
|
||||
3. Almacenar identidad visual y lineamientos de marca
|
||||
4. Registrar oportunidades comerciales
|
||||
5. Conectar datos de CRM con generación de contenido
|
||||
|
||||
---
|
||||
|
||||
## Entidades del Dominio
|
||||
|
||||
### Client
|
||||
|
||||
```yaml
|
||||
Entidad: Client
|
||||
Descripción: Empresa/organización cliente de la agencia
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- name: string
|
||||
- legal_name: string
|
||||
- tax_id: string (RFC/NIF)
|
||||
- industry: string
|
||||
- size: enum [micro, small, medium, large, enterprise]
|
||||
- website: string
|
||||
- status: enum [prospect, active, inactive, churned]
|
||||
- notes: text
|
||||
- metadata: JSONB
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- 1:N con Contact
|
||||
- 1:N con Brand
|
||||
- 1:N con Opportunity
|
||||
- 1:N con Project
|
||||
```
|
||||
|
||||
### Contact
|
||||
|
||||
```yaml
|
||||
Entidad: Contact
|
||||
Descripción: Persona de contacto en un cliente
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- client_id: UUID (FK)
|
||||
- first_name: string
|
||||
- last_name: string
|
||||
- email: string
|
||||
- phone: string
|
||||
- position: string
|
||||
- department: string
|
||||
- is_primary: boolean
|
||||
- status: enum [active, inactive]
|
||||
- metadata: JSONB
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Client
|
||||
- N:1 con Tenant
|
||||
```
|
||||
|
||||
### Brand
|
||||
|
||||
```yaml
|
||||
Entidad: Brand
|
||||
Descripción: Marca de un cliente con su identidad visual
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- client_id: UUID (FK)
|
||||
- name: string
|
||||
- description: text
|
||||
- status: enum [active, inactive]
|
||||
- identity: JSONB
|
||||
- logo_url: string
|
||||
- logo_variations: array
|
||||
- primary_color: string
|
||||
- secondary_colors: array
|
||||
- typography: object
|
||||
- tone_of_voice: string (formal, casual, playful, etc.)
|
||||
- keywords: array
|
||||
- forbidden_words: array
|
||||
- visual_style: string
|
||||
- lora_models: array[UUID] (referencias a modelos entrenados)
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Client
|
||||
- N:1 con Tenant
|
||||
- 1:N con Product
|
||||
- 1:N con Campaign
|
||||
- N:N con CustomModel (LoRAs)
|
||||
```
|
||||
|
||||
### Product
|
||||
|
||||
```yaml
|
||||
Entidad: Product
|
||||
Descripción: Producto o servicio de una marca
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- brand_id: UUID (FK)
|
||||
- name: string
|
||||
- sku: string
|
||||
- description: text
|
||||
- category: string
|
||||
- status: enum [active, discontinued, coming_soon]
|
||||
- attributes: JSONB
|
||||
- price: decimal
|
||||
- features: array
|
||||
- target_audience: string
|
||||
- use_cases: array
|
||||
- reference_images: array[string] (URLs)
|
||||
- lora_model_id: UUID (modelo específico del producto)
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Brand
|
||||
- N:1 con Tenant
|
||||
- 1:N con Asset (imágenes generadas)
|
||||
```
|
||||
|
||||
### Opportunity
|
||||
|
||||
```yaml
|
||||
Entidad: Opportunity
|
||||
Descripción: Oportunidad de negocio/deal
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- client_id: UUID (FK)
|
||||
- contact_id: UUID (FK, opcional)
|
||||
- name: string
|
||||
- description: text
|
||||
- value: decimal
|
||||
- currency: string
|
||||
- stage: enum [lead, qualified, proposal, negotiation, won, lost]
|
||||
- probability: integer (0-100)
|
||||
- expected_close_date: date
|
||||
- actual_close_date: date
|
||||
- lost_reason: string
|
||||
- notes: text
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Client
|
||||
- N:1 con Contact
|
||||
- N:1 con Tenant
|
||||
- 1:N con Project (al convertirse)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### F-002.1: Gestión de Clientes
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-002.1.1 | CRUD Clientes | Alta, edición, listado, eliminación | Alta |
|
||||
| F-002.1.2 | Búsqueda y filtros | Por nombre, industria, estado | Alta |
|
||||
| F-002.1.3 | Vista de cliente | Dashboard con marcas, contactos, proyectos | Alta |
|
||||
| F-002.1.4 | Historial | Timeline de actividades y cambios | Media |
|
||||
|
||||
### F-002.2: Gestión de Contactos
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-002.2.1 | CRUD Contactos | Gestión de personas de contacto | Alta |
|
||||
| F-002.2.2 | Contacto primario | Marcar contacto principal por cliente | Media |
|
||||
| F-002.2.3 | Directorio | Vista consolidada de todos los contactos | Media |
|
||||
|
||||
### F-002.3: Gestión de Marcas
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-002.3.1 | CRUD Marcas | Alta y gestión de marcas | Alta |
|
||||
| F-002.3.2 | Identidad visual | Definir colores, tipografía, tono | Alta |
|
||||
| F-002.3.3 | Brand guidelines | Subir y almacenar guías de marca | Media |
|
||||
| F-002.3.4 | Asociar LoRAs | Vincular modelos entrenados a marca | Alta |
|
||||
|
||||
### F-002.4: Gestión de Productos
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-002.4.1 | CRUD Productos | Alta y gestión de productos | Alta |
|
||||
| F-002.4.2 | Imágenes referencia | Subir fotos del producto real | Alta |
|
||||
| F-002.4.3 | Catálogo | Vista de productos por marca | Media |
|
||||
| F-002.4.4 | Trigger generación | Botón "Generar pack de imágenes" | Alta |
|
||||
|
||||
### F-002.5: Pipeline de Ventas
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-002.5.1 | CRUD Oportunidades | Gestión de deals | Media |
|
||||
| F-002.5.2 | Kanban pipeline | Vista drag & drop por etapas | Media |
|
||||
| F-002.5.3 | Convertir a proyecto | Crear proyecto desde oportunidad ganada | Media |
|
||||
|
||||
---
|
||||
|
||||
## Reglas de Negocio
|
||||
|
||||
```yaml
|
||||
RN-002.1:
|
||||
Descripción: Cliente debe tener al menos un contacto
|
||||
Validación: Warning si no hay contactos, no bloquea
|
||||
|
||||
RN-002.2:
|
||||
Descripción: Marca requiere identidad visual mínima
|
||||
Validación: Al menos logo_url o primary_color definido
|
||||
|
||||
RN-002.3:
|
||||
Descripción: Producto hereda identidad de su marca
|
||||
Comportamiento: Si no tiene LoRA propio, usa el de la marca
|
||||
|
||||
RN-002.4:
|
||||
Descripción: Oportunidad ganada puede generar proyecto
|
||||
Acción: Botón "Crear proyecto" disponible en stage=won
|
||||
|
||||
RN-002.5:
|
||||
Descripción: Datos de marca se inyectan en generación
|
||||
Comportamiento: Al generar contenido, se cargan automáticamente colores, tono, keywords
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```yaml
|
||||
Base: /api/v1/crm
|
||||
|
||||
# Clients
|
||||
POST /clients
|
||||
GET /clients
|
||||
GET /clients/:id
|
||||
PUT /clients/:id
|
||||
DELETE /clients/:id
|
||||
GET /clients/:id/brands
|
||||
GET /clients/:id/contacts
|
||||
GET /clients/:id/projects
|
||||
|
||||
# Contacts
|
||||
POST /contacts
|
||||
GET /contacts
|
||||
GET /contacts/:id
|
||||
PUT /contacts/:id
|
||||
DELETE /contacts/:id
|
||||
|
||||
# Brands
|
||||
POST /brands
|
||||
GET /brands
|
||||
GET /brands/:id
|
||||
PUT /brands/:id
|
||||
DELETE /brands/:id
|
||||
GET /brands/:id/products
|
||||
POST /brands/:id/loras # Asociar LoRA
|
||||
DELETE /brands/:id/loras/:loraId # Desasociar LoRA
|
||||
|
||||
# Products
|
||||
POST /products
|
||||
GET /products
|
||||
GET /products/:id
|
||||
PUT /products/:id
|
||||
DELETE /products/:id
|
||||
POST /products/:id/generate # Trigger generación
|
||||
|
||||
# Opportunities
|
||||
POST /opportunities
|
||||
GET /opportunities
|
||||
GET /opportunities/:id
|
||||
PUT /opportunities/:id
|
||||
DELETE /opportunities/:id
|
||||
POST /opportunities/:id/convert # Convertir a proyecto
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integraciones
|
||||
|
||||
### Con Motor de Generación (PMC-004)
|
||||
|
||||
```yaml
|
||||
Trigger: POST /products/:id/generate
|
||||
Payload:
|
||||
product_id: UUID
|
||||
brand_id: UUID
|
||||
workflow_template: string
|
||||
options:
|
||||
quantity: number
|
||||
formats: array
|
||||
|
||||
Comportamiento:
|
||||
1. Cargar datos de producto (nombre, descripción, referencias)
|
||||
2. Cargar identidad de marca (colores, tono, LoRAs)
|
||||
3. Encolar job de generación con parámetros combinados
|
||||
```
|
||||
|
||||
### Con Proyectos (PMC-003)
|
||||
|
||||
```yaml
|
||||
Trigger: POST /opportunities/:id/convert
|
||||
Crea:
|
||||
- Proyecto vinculado al cliente
|
||||
- Campaña inicial (opcional)
|
||||
- Copia brief de la oportunidad
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
```yaml
|
||||
Dependencias de Módulos:
|
||||
- PMC-001 Tenants: Aislamiento de datos
|
||||
- PMC-003 Projects: Conversión de oportunidades
|
||||
- PMC-004 Generation: Trigger de generación
|
||||
- PMC-006 Assets: Almacenamiento de logos y referencias
|
||||
|
||||
Servicios Externos:
|
||||
- Storage (S3/MinIO): Logos, imágenes de referencia
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI/UX Consideraciones
|
||||
|
||||
```yaml
|
||||
Vistas principales:
|
||||
- Lista de clientes con filtros y búsqueda
|
||||
- Ficha de cliente con tabs (info, contactos, marcas, proyectos)
|
||||
- Lista de marcas con preview de identidad
|
||||
- Ficha de producto con galería de referencias
|
||||
- Pipeline kanban de oportunidades
|
||||
|
||||
Acciones rápidas:
|
||||
- Desde producto: "Generar contenido"
|
||||
- Desde cliente: "Nueva marca", "Nuevo proyecto"
|
||||
- Desde oportunidad: "Convertir a proyecto"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] CRUD completo para Clients, Contacts, Brands, Products, Opportunities
|
||||
- [ ] Identidad de marca se almacena y muestra correctamente
|
||||
- [ ] Trigger de generación funciona desde producto
|
||||
- [ ] Pipeline de oportunidades permite drag & drop
|
||||
- [ ] Conversión de oportunidad crea proyecto
|
||||
- [ ] Búsqueda y filtros funcionan en todas las listas
|
||||
- [ ] RLS aísla datos por tenant
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [VISION-GENERAL.md](../00-vision-general/VISION-GENERAL.md)
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,381 @@
|
||||
# PMC-003: Módulo de Projects
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Definición
|
||||
**Prioridad:** Alta
|
||||
|
||||
---
|
||||
|
||||
## Descripción General
|
||||
|
||||
El módulo de Projects gestiona proyectos y campañas de marketing. Cada proyecto agrupa múltiples campañas, y cada campaña contiene un brief estructurado que guía la generación de contenido.
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Organizar el trabajo por proyectos y campañas
|
||||
2. Estructurar briefs creativos para generación de contenido
|
||||
3. Gestionar estados y flujos de aprobación
|
||||
4. Vincular proyectos con clientes y marcas del CRM
|
||||
5. Coordinar entregas de assets generados
|
||||
|
||||
---
|
||||
|
||||
## Entidades del Dominio
|
||||
|
||||
### Project
|
||||
|
||||
```yaml
|
||||
Entidad: Project
|
||||
Descripción: Contenedor de campañas para un cliente/iniciativa
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- client_id: UUID (FK)
|
||||
- name: string
|
||||
- description: text
|
||||
- code: string (identificador corto, ej: "PRJ-2025-001")
|
||||
- status: enum [draft, active, on_hold, completed, cancelled]
|
||||
- start_date: date
|
||||
- end_date: date
|
||||
- budget: decimal
|
||||
- currency: string
|
||||
- owner_id: UUID (FK a User, responsable)
|
||||
- team_members: array[UUID] (usuarios asignados)
|
||||
- settings: JSONB
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con Client
|
||||
- N:1 con User (owner)
|
||||
- 1:N con Campaign
|
||||
- 1:N con Asset (assets del proyecto)
|
||||
```
|
||||
|
||||
### Campaign
|
||||
|
||||
```yaml
|
||||
Entidad: Campaign
|
||||
Descripción: Campaña de marketing con brief y assets
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- project_id: UUID (FK)
|
||||
- brand_id: UUID (FK)
|
||||
- name: string
|
||||
- description: text
|
||||
- type: enum [social_media, performance_ads, catalog, landing, email, other]
|
||||
- status: enum [draft, briefing, in_production, review, approved, published, archived]
|
||||
- brief: JSONB (ver estructura abajo)
|
||||
- channels: array[string] (instagram, facebook, tiktok, google_ads, etc.)
|
||||
- start_date: date
|
||||
- end_date: date
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Project
|
||||
- N:1 con Brand
|
||||
- N:1 con Tenant
|
||||
- 1:N con GenerationJob
|
||||
- 1:N con Asset
|
||||
```
|
||||
|
||||
### Brief (JSONB Structure)
|
||||
|
||||
```yaml
|
||||
Estructura: Campaign.brief
|
||||
Campos:
|
||||
objective:
|
||||
description: string (objetivo de la campaña)
|
||||
kpis: array[string] (métricas de éxito)
|
||||
|
||||
audience:
|
||||
demographics: string
|
||||
psychographics: string
|
||||
pain_points: array[string]
|
||||
desires: array[string]
|
||||
|
||||
messaging:
|
||||
main_message: string
|
||||
tone_of_voice: string (override del brand)
|
||||
call_to_action: string
|
||||
hashtags: array[string]
|
||||
|
||||
visual:
|
||||
style: string (fotográfico, ilustrado, minimalista, etc.)
|
||||
mood: string (energético, sereno, profesional, etc.)
|
||||
color_palette: array[string] (override o adicionales)
|
||||
references: array[string] (URLs de referencias visuales)
|
||||
|
||||
constraints:
|
||||
forbidden_words: array[string]
|
||||
forbidden_elements: array[string]
|
||||
legal_disclaimers: array[string]
|
||||
brand_guidelines_url: string
|
||||
|
||||
deliverables:
|
||||
formats: array[object]
|
||||
- type: string (post, story, banner, etc.)
|
||||
dimensions: string (1080x1080, 1080x1920, etc.)
|
||||
quantity: number
|
||||
total_images: number
|
||||
total_copies: number
|
||||
variations_per_piece: number
|
||||
```
|
||||
|
||||
### CampaignAsset
|
||||
|
||||
```yaml
|
||||
Entidad: CampaignAsset (tabla pivote)
|
||||
Descripción: Relación entre campaña y assets generados
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- campaign_id: UUID (FK)
|
||||
- asset_id: UUID (FK)
|
||||
- status: enum [pending, approved, rejected, revision_requested]
|
||||
- feedback: text
|
||||
- approved_by: UUID (FK a User)
|
||||
- approved_at: timestamp
|
||||
- created_at: timestamp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### F-003.1: Gestión de Proyectos
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-003.1.1 | CRUD Proyectos | Alta, edición, listado, archivado | Alta |
|
||||
| F-003.1.2 | Dashboard proyecto | Vista general con campañas y progreso | Alta |
|
||||
| F-003.1.3 | Asignar equipo | Agregar/quitar miembros al proyecto | Media |
|
||||
| F-003.1.4 | Timeline | Visualización de fechas y milestones | Media |
|
||||
|
||||
### F-003.2: Gestión de Campañas
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-003.2.1 | CRUD Campañas | Alta, edición, listado | Alta |
|
||||
| F-003.2.2 | Editor de brief | Formulario estructurado | Alta |
|
||||
| F-003.2.3 | Plantillas de brief | Briefs predefinidos por tipo | Media |
|
||||
| F-003.2.4 | Duplicar campaña | Clonar con modificaciones | Media |
|
||||
|
||||
### F-003.3: Flujo de Trabajo
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-003.3.1 | Cambio de estado | Transiciones controladas | Alta |
|
||||
| F-003.3.2 | Notificaciones | Alertar cambios de estado | Media |
|
||||
| F-003.3.3 | Aprobación de assets | Aprobar/rechazar contenido | Alta |
|
||||
| F-003.3.4 | Solicitar revisión | Pedir cambios con feedback | Alta |
|
||||
|
||||
### F-003.4: Generación de Contenido
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-003.4.1 | Lanzar generación | Desde brief, iniciar jobs | Alta |
|
||||
| F-003.4.2 | Seleccionar plantillas | Elegir workflows de generación | Alta |
|
||||
| F-003.4.3 | Monitor de progreso | Ver estado de generaciones | Alta |
|
||||
| F-003.4.4 | Regenerar | Volver a generar con ajustes | Media |
|
||||
|
||||
---
|
||||
|
||||
## Máquina de Estados
|
||||
|
||||
### Project Status
|
||||
|
||||
```
|
||||
draft ─────────────────────► active
|
||||
│ │
|
||||
│ ├───► on_hold ───► active
|
||||
│ │
|
||||
│ └───► completed
|
||||
│
|
||||
└────────────────────────────────► cancelled
|
||||
```
|
||||
|
||||
### Campaign Status
|
||||
|
||||
```
|
||||
draft ───► briefing ───► in_production ───► review ───► approved ───► published
|
||||
│ │ │ │ │
|
||||
│ │ └────────────────┘ │
|
||||
│ │ (revision_requested) │
|
||||
│ │ │
|
||||
└───────────┴────────────────────────────────────────────┴───► archived
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reglas de Negocio
|
||||
|
||||
```yaml
|
||||
RN-003.1:
|
||||
Descripción: Proyecto requiere cliente asignado
|
||||
Validación: client_id NOT NULL
|
||||
|
||||
RN-003.2:
|
||||
Descripción: Campaña requiere brief mínimo para pasar a producción
|
||||
Validación: brief.objective y brief.deliverables definidos
|
||||
|
||||
RN-003.3:
|
||||
Descripción: Solo owner o admin puede cambiar estado del proyecto
|
||||
Autorización: Verificar rol del usuario
|
||||
|
||||
RN-003.4:
|
||||
Descripción: Assets aprobados no pueden eliminarse
|
||||
Validación: Bloquear DELETE si status=approved
|
||||
|
||||
RN-003.5:
|
||||
Descripción: Brief hereda identidad de la marca
|
||||
Comportamiento: Cargar colores, tono, etc. de Brand al crear campaña
|
||||
|
||||
RN-003.6:
|
||||
Descripción: Campaña solo puede publicarse si tiene assets aprobados
|
||||
Validación: Al menos 1 asset con status=approved
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```yaml
|
||||
Base: /api/v1/projects
|
||||
|
||||
# Projects
|
||||
POST /projects
|
||||
GET /projects
|
||||
GET /projects/:id
|
||||
PUT /projects/:id
|
||||
DELETE /projects/:id
|
||||
PATCH /projects/:id/status
|
||||
GET /projects/:id/campaigns
|
||||
GET /projects/:id/assets
|
||||
POST /projects/:id/team # Agregar miembro
|
||||
DELETE /projects/:id/team/:userId
|
||||
|
||||
# Campaigns
|
||||
POST /campaigns
|
||||
GET /campaigns
|
||||
GET /campaigns/:id
|
||||
PUT /campaigns/:id
|
||||
DELETE /campaigns/:id
|
||||
PATCH /campaigns/:id/status
|
||||
PUT /campaigns/:id/brief
|
||||
GET /campaigns/:id/assets
|
||||
POST /campaigns/:id/generate # Lanzar generación
|
||||
|
||||
# Campaign Assets
|
||||
POST /campaigns/:id/assets/:assetId/approve
|
||||
POST /campaigns/:id/assets/:assetId/reject
|
||||
POST /campaigns/:id/assets/:assetId/request-revision
|
||||
|
||||
# Brief Templates
|
||||
GET /brief-templates
|
||||
GET /brief-templates/:id
|
||||
POST /brief-templates # Admin only
|
||||
PUT /brief-templates/:id
|
||||
DELETE /brief-templates/:id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integraciones
|
||||
|
||||
### Con CRM (PMC-002)
|
||||
|
||||
```yaml
|
||||
Relación: Proyecto vinculado a Cliente y Marca
|
||||
Datos heredados:
|
||||
- Identidad visual de Brand
|
||||
- Información de productos
|
||||
- Contactos para notificaciones
|
||||
```
|
||||
|
||||
### Con Motor de Generación (PMC-004)
|
||||
|
||||
```yaml
|
||||
Trigger: POST /campaigns/:id/generate
|
||||
Payload:
|
||||
campaign_id: UUID
|
||||
workflow_templates: array[string]
|
||||
options:
|
||||
use_brand_lora: boolean
|
||||
quality: string
|
||||
|
||||
Respuesta:
|
||||
job_ids: array[UUID]
|
||||
estimated_time: number
|
||||
```
|
||||
|
||||
### Con DAM (PMC-006)
|
||||
|
||||
```yaml
|
||||
Assets generados se almacenan en DAM
|
||||
Vinculación automática campaign_id → asset
|
||||
Metadatos del brief copiados al asset
|
||||
```
|
||||
|
||||
### Con Automatización (PMC-005)
|
||||
|
||||
```yaml
|
||||
Eventos disparados:
|
||||
- campaign.status_changed → Notificaciones
|
||||
- campaign.approved → Preparar entrega
|
||||
- assets.all_approved → Trigger siguiente paso
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI/UX Consideraciones
|
||||
|
||||
```yaml
|
||||
Vistas principales:
|
||||
- Lista de proyectos con filtros (cliente, estado, fecha)
|
||||
- Board kanban de campañas por estado
|
||||
- Editor de brief con preview
|
||||
- Galería de assets de campaña con acciones
|
||||
|
||||
Acciones rápidas:
|
||||
- "Nueva campaña" desde proyecto
|
||||
- "Generar contenido" desde campaña
|
||||
- "Aprobar todo" en vista de revisión
|
||||
- "Descargar pack" de assets aprobados
|
||||
|
||||
Componentes:
|
||||
- BriefEditor: Formulario estructurado con secciones colapsables
|
||||
- AssetReviewer: Galería con zoom, comparación, acciones
|
||||
- ProgressTracker: Timeline visual de estado de campaña
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] CRUD completo para Projects y Campaigns
|
||||
- [ ] Editor de brief funcional con todas las secciones
|
||||
- [ ] Transiciones de estado respetan reglas
|
||||
- [ ] Generación se lanza correctamente desde campaña
|
||||
- [ ] Flujo de aprobación de assets funciona
|
||||
- [ ] Assets rechazados permiten regeneración
|
||||
- [ ] Notificaciones se disparan en cambios de estado
|
||||
- [ ] Plantillas de brief funcionan correctamente
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [VISION-GENERAL.md](../00-vision-general/VISION-GENERAL.md)
|
||||
- [PMC-002-CRM.md](./PMC-002-CRM.md)
|
||||
- [PMC-004-GENERATION.md](./PMC-004-GENERATION.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,489 @@
|
||||
# PMC-004: Módulo de Generation
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Definición
|
||||
**Prioridad:** Alta
|
||||
|
||||
---
|
||||
|
||||
## Descripción General
|
||||
|
||||
El módulo de Generation es el núcleo de la plataforma. Gestiona la generación de contenido mediante IA, incluyendo imágenes con Stable Diffusion/ComfyUI y textos con LLMs. Orquesta workflows, colas de tareas, y la integración con modelos personalizados (LoRAs).
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Generar imágenes de alta calidad para marketing
|
||||
2. Generar copys y textos publicitarios
|
||||
3. Ejecutar workflows predefinidos de ComfyUI
|
||||
4. Gestionar modelos personalizados (LoRAs, checkpoints)
|
||||
5. Mantener consistencia de marca en generaciones
|
||||
6. Procesar tareas en cola con prioridades
|
||||
|
||||
---
|
||||
|
||||
## Entidades del Dominio
|
||||
|
||||
### GenerationJob
|
||||
|
||||
```yaml
|
||||
Entidad: GenerationJob
|
||||
Descripción: Tarea de generación en cola
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- campaign_id: UUID (FK, opcional)
|
||||
- product_id: UUID (FK, opcional)
|
||||
- user_id: UUID (FK, quien solicitó)
|
||||
- type: enum [image, text, image_batch, mixed]
|
||||
- status: enum [queued, processing, completed, failed, cancelled]
|
||||
- priority: integer (1-10, mayor = más urgente)
|
||||
- workflow_id: UUID (FK a WorkflowTemplate)
|
||||
- input_params: JSONB (parámetros de entrada)
|
||||
- output_assets: array[UUID] (assets generados)
|
||||
- error_message: text
|
||||
- progress: integer (0-100)
|
||||
- started_at: timestamp
|
||||
- completed_at: timestamp
|
||||
- created_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con Campaign
|
||||
- N:1 con Product
|
||||
- N:1 con User
|
||||
- N:1 con WorkflowTemplate
|
||||
- 1:N con Asset (outputs)
|
||||
```
|
||||
|
||||
### WorkflowTemplate
|
||||
|
||||
```yaml
|
||||
Entidad: WorkflowTemplate
|
||||
Descripción: Plantilla de workflow de generación
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK, null = global)
|
||||
- name: string
|
||||
- description: text
|
||||
- type: enum [product_photo, social_post, banner, avatar, variation, custom]
|
||||
- category: string (para organización)
|
||||
- comfyui_workflow: JSONB (definición del workflow)
|
||||
- input_schema: JSONB (parámetros esperados)
|
||||
- output_config: JSONB
|
||||
- format: string (png, jpg, webp)
|
||||
- dimensions: array[string]
|
||||
- quantity_default: number
|
||||
- models_required: array[string] (checkpoints, LoRAs requeridos)
|
||||
- estimated_time_seconds: integer
|
||||
- is_active: boolean
|
||||
- is_system: boolean (plantilla del sistema vs personalizada)
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant (opcional)
|
||||
- 1:N con GenerationJob
|
||||
```
|
||||
|
||||
### CustomModel
|
||||
|
||||
```yaml
|
||||
Entidad: CustomModel
|
||||
Descripción: Modelo personalizado (LoRA, checkpoint, embedding)
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- brand_id: UUID (FK, opcional)
|
||||
- name: string
|
||||
- type: enum [lora, checkpoint, embedding, controlnet]
|
||||
- purpose: string (product, avatar, style, character)
|
||||
- description: text
|
||||
- file_path: string (ruta en storage)
|
||||
- file_size: bigint
|
||||
- status: enum [training, ready, failed, archived]
|
||||
- training_params: JSONB
|
||||
- base_model: string
|
||||
- steps: number
|
||||
- learning_rate: number
|
||||
- training_images: array[string]
|
||||
- trigger_word: string (palabra para activar en prompt)
|
||||
- preview_images: array[string]
|
||||
- metadata: JSONB
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con Brand (opcional)
|
||||
- N:N con WorkflowTemplate
|
||||
```
|
||||
|
||||
### TextGeneration
|
||||
|
||||
```yaml
|
||||
Entidad: TextGeneration
|
||||
Descripción: Generación de texto/copy
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- job_id: UUID (FK a GenerationJob, opcional)
|
||||
- campaign_id: UUID (FK, opcional)
|
||||
- type: enum [copy, title, description, hashtags, cta, full_post]
|
||||
- prompt: text (instrucción al LLM)
|
||||
- context: JSONB (datos de marca, producto, brief)
|
||||
- output: text
|
||||
- variations: array[text] (si se generaron múltiples)
|
||||
- model_used: string
|
||||
- tokens_used: integer
|
||||
- status: enum [pending, completed, failed]
|
||||
- created_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con GenerationJob
|
||||
- N:1 con Campaign
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### F-004.1: Generación de Imágenes
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-004.1.1 | Text-to-Image | Generar imagen desde prompt | Alta |
|
||||
| F-004.1.2 | Image-to-Image | Transformar imagen existente | Alta |
|
||||
| F-004.1.3 | Inpainting | Editar partes de una imagen | Media |
|
||||
| F-004.1.4 | Upscaling | Aumentar resolución | Alta |
|
||||
| F-004.1.5 | Batch generation | Generar múltiples variaciones | Alta |
|
||||
|
||||
### F-004.2: Generación de Texto
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-004.2.1 | Copy generation | Generar textos publicitarios | Alta |
|
||||
| F-004.2.2 | Hashtag generation | Sugerir hashtags relevantes | Media |
|
||||
| F-004.2.3 | Title generation | Crear títulos/headlines | Alta |
|
||||
| F-004.2.4 | Tone adaptation | Ajustar tono según brief | Alta |
|
||||
|
||||
### F-004.3: Workflows
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-004.3.1 | Plantillas predefinidas | Workflows listos para usar | Alta |
|
||||
| F-004.3.2 | Ejecutar workflow | Correr workflow con parámetros | Alta |
|
||||
| F-004.3.3 | Crear plantillas | Admin puede crear nuevos workflows | Media |
|
||||
| F-004.3.4 | Previsualizar | Ver ejemplo antes de ejecutar | Media |
|
||||
|
||||
### F-004.4: Modelos Personalizados
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-004.4.1 | Registrar LoRA | Subir modelo entrenado | Alta |
|
||||
| F-004.4.2 | Entrenar LoRA | Iniciar entrenamiento (básico) | Media |
|
||||
| F-004.4.3 | Asociar a marca | Vincular modelo con brand | Alta |
|
||||
| F-004.4.4 | Selector de modelos | Elegir LoRAs en generación | Alta |
|
||||
|
||||
### F-004.5: Cola de Tareas
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-004.5.1 | Encolar job | Agregar tarea a la cola | Alta |
|
||||
| F-004.5.2 | Priorizar | Ajustar prioridad de jobs | Media |
|
||||
| F-004.5.3 | Monitor | Ver estado de cola | Alta |
|
||||
| F-004.5.4 | Cancelar | Cancelar job pendiente | Media |
|
||||
| F-004.5.5 | Reintentar | Re-ejecutar job fallido | Media |
|
||||
|
||||
---
|
||||
|
||||
## Workflows Predefinidos (MVP)
|
||||
|
||||
### WF-001: Fotografía Sintética de Producto
|
||||
|
||||
```yaml
|
||||
Nombre: product_photo_synthetic
|
||||
Descripción: Genera fotos de producto en contextos comerciales
|
||||
Inputs:
|
||||
- product_description: string
|
||||
- reference_images: array[string] (opcional)
|
||||
- background: string (white, lifestyle, custom)
|
||||
- style: string (minimalist, premium, casual)
|
||||
- brand_colors: array[string]
|
||||
- lora_id: UUID (opcional)
|
||||
|
||||
Outputs:
|
||||
- 5 variaciones del producto
|
||||
- Formato: PNG 1024x1024
|
||||
- Fondo transparente disponible
|
||||
|
||||
ComfyUI Nodes:
|
||||
- SDXL Base
|
||||
- ControlNet (si hay referencia)
|
||||
- IP-Adapter (consistencia)
|
||||
- Background Removal
|
||||
- Upscaler
|
||||
```
|
||||
|
||||
### WF-002: Post para Redes Sociales
|
||||
|
||||
```yaml
|
||||
Nombre: social_media_post
|
||||
Descripción: Genera imagen + copy para redes
|
||||
Inputs:
|
||||
- brief: object (objetivo, audiencia, tono)
|
||||
- product_id: UUID (opcional)
|
||||
- channel: string (instagram, facebook, linkedin)
|
||||
- format: string (post, story, carousel)
|
||||
- brand_id: UUID
|
||||
|
||||
Outputs:
|
||||
- 3-5 variaciones de imagen
|
||||
- Copy sugerido por variación
|
||||
- Hashtags recomendados
|
||||
|
||||
Proceso:
|
||||
1. Cargar identidad de marca
|
||||
2. Generar imagen base con SDXL + LoRA
|
||||
3. Aplicar composición según formato
|
||||
4. Generar copy con LLM
|
||||
5. Combinar outputs
|
||||
```
|
||||
|
||||
### WF-003: Variaciones de Anuncio
|
||||
|
||||
```yaml
|
||||
Nombre: ad_variations
|
||||
Descripción: Genera múltiples versiones para A/B testing
|
||||
Inputs:
|
||||
- base_image: string (URL o asset_id)
|
||||
- variations_count: number
|
||||
- variation_type: string (color, background, composition)
|
||||
|
||||
Outputs:
|
||||
- N variaciones según configuración
|
||||
- Metadatos de diferencias
|
||||
|
||||
Uso:
|
||||
- Testing de creatividades
|
||||
- Adaptaciones por canal
|
||||
```
|
||||
|
||||
### WF-004: Avatar/Influencer Virtual
|
||||
|
||||
```yaml
|
||||
Nombre: virtual_avatar
|
||||
Descripción: Genera imágenes consistentes de un personaje
|
||||
Inputs:
|
||||
- character_lora_id: UUID
|
||||
- pose: string (standing, sitting, action)
|
||||
- outfit: string
|
||||
- background: string
|
||||
- expression: string
|
||||
|
||||
Outputs:
|
||||
- Imagen del avatar
|
||||
- Consistencia facial garantizada
|
||||
|
||||
Técnicas:
|
||||
- IP-Adapter para consistencia
|
||||
- ControlNet OpenPose para poses
|
||||
- LoRA específico del personaje
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura de Integración ComfyUI
|
||||
|
||||
```yaml
|
||||
Componentes:
|
||||
Backend (NestJS):
|
||||
- GenerationService: Lógica de negocio
|
||||
- QueueService: Gestión de cola (Bull)
|
||||
- ComfyUIClient: Cliente HTTP para ComfyUI
|
||||
|
||||
ComfyUI Server:
|
||||
- API REST nativa o ComfyDeploy
|
||||
- Websocket para progreso
|
||||
- Storage compartido para outputs
|
||||
|
||||
Flujo:
|
||||
1. Backend recibe solicitud de generación
|
||||
2. Valida permisos y límites del tenant
|
||||
3. Construye payload del workflow
|
||||
4. Encola job en Bull/Redis
|
||||
5. Worker toma job y llama a ComfyUI
|
||||
6. ComfyUI ejecuta workflow
|
||||
7. Worker recibe resultado y crea Asset
|
||||
8. Notifica completion via websocket
|
||||
```
|
||||
|
||||
### Ejemplo de Llamada a ComfyUI
|
||||
|
||||
```typescript
|
||||
interface ComfyUIRequest {
|
||||
workflow_id: string;
|
||||
inputs: {
|
||||
positive_prompt: string;
|
||||
negative_prompt: string;
|
||||
seed: number;
|
||||
steps: number;
|
||||
cfg_scale: number;
|
||||
width: number;
|
||||
height: number;
|
||||
lora_name?: string;
|
||||
lora_strength?: number;
|
||||
controlnet_image?: string;
|
||||
};
|
||||
webhook_url: string;
|
||||
}
|
||||
|
||||
// Respuesta
|
||||
interface ComfyUIResponse {
|
||||
job_id: string;
|
||||
status: 'queued' | 'processing' | 'completed' | 'failed';
|
||||
outputs?: {
|
||||
images: string[]; // URLs
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```yaml
|
||||
Base: /api/v1/generation
|
||||
|
||||
# Jobs
|
||||
POST /jobs # Crear job de generación
|
||||
GET /jobs # Listar jobs del usuario
|
||||
GET /jobs/:id # Detalle de job
|
||||
DELETE /jobs/:id # Cancelar job
|
||||
POST /jobs/:id/retry # Reintentar job fallido
|
||||
|
||||
# Quick Generate (shortcuts)
|
||||
POST /generate/image # Generación rápida de imagen
|
||||
POST /generate/text # Generación rápida de texto
|
||||
POST /generate/batch # Batch de imágenes
|
||||
|
||||
# Workflows
|
||||
GET /workflows # Listar plantillas disponibles
|
||||
GET /workflows/:id # Detalle de plantilla
|
||||
POST /workflows # Crear plantilla (admin)
|
||||
PUT /workflows/:id # Editar plantilla
|
||||
POST /workflows/:id/execute # Ejecutar workflow
|
||||
|
||||
# Custom Models
|
||||
GET /models # Listar modelos del tenant
|
||||
GET /models/:id # Detalle de modelo
|
||||
POST /models # Registrar modelo
|
||||
DELETE /models/:id # Eliminar modelo
|
||||
POST /models/train # Iniciar entrenamiento
|
||||
|
||||
# Queue Management (admin)
|
||||
GET /queue/status # Estado de la cola
|
||||
GET /queue/jobs # Jobs en cola
|
||||
POST /queue/jobs/:id/priority # Cambiar prioridad
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reglas de Negocio
|
||||
|
||||
```yaml
|
||||
RN-004.1:
|
||||
Descripción: Generaciones limitadas por plan del tenant
|
||||
Validación: Verificar cuota antes de encolar
|
||||
Acción: Rechazar si límite alcanzado
|
||||
|
||||
RN-004.2:
|
||||
Descripción: LoRA de marca se aplica automáticamente
|
||||
Comportamiento: Si brand tiene LoRA y no se especifica otro, usar el de marca
|
||||
|
||||
RN-004.3:
|
||||
Descripción: Jobs prioritarios para planes premium
|
||||
Cálculo: priority_base + plan_bonus
|
||||
|
||||
RN-004.4:
|
||||
Descripción: Outputs se almacenan 30 días mínimo
|
||||
Política: Assets generados tienen retención mínima
|
||||
|
||||
RN-004.5:
|
||||
Descripción: Entrenamiento requiere mínimo 10 imágenes
|
||||
Validación: Verificar cantidad antes de iniciar
|
||||
|
||||
RN-004.6:
|
||||
Descripción: Negative prompts por defecto
|
||||
Comportamiento: Agregar prompts negativos estándar de calidad
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
```yaml
|
||||
Dependencias de Módulos:
|
||||
- PMC-001 Tenants: Límites y cuotas
|
||||
- PMC-002 CRM: Datos de marca y producto
|
||||
- PMC-003 Projects: Contexto de campaña
|
||||
- PMC-006 Assets: Almacenamiento de outputs
|
||||
|
||||
Servicios Externos:
|
||||
- ComfyUI: Motor de generación de imágenes
|
||||
- OpenAI/Claude API: Generación de texto
|
||||
- Redis: Cola de tareas
|
||||
- S3/MinIO: Almacenamiento de modelos y outputs
|
||||
|
||||
Dependencias del Catálogo:
|
||||
- @CATALOG_RATELIMIT: Rate limiting por tenant
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consideraciones de Performance
|
||||
|
||||
```yaml
|
||||
Optimizaciones:
|
||||
- Cola con workers paralelos según GPUs disponibles
|
||||
- Cache de modelos frecuentes en VRAM
|
||||
- Batch processing cuando sea posible
|
||||
- Compresión de outputs antes de storage
|
||||
|
||||
Monitoreo:
|
||||
- Tiempo promedio por tipo de workflow
|
||||
- Utilización de GPU
|
||||
- Cola depth y wait time
|
||||
- Tasa de errores
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] Generación de imagen funciona con SDXL base
|
||||
- [ ] Workflows predefinidos ejecutan correctamente
|
||||
- [ ] LoRAs se cargan y aplican correctamente
|
||||
- [ ] Cola procesa jobs en orden de prioridad
|
||||
- [ ] Progreso se reporta via websocket
|
||||
- [ ] Jobs fallidos permiten reintento
|
||||
- [ ] Límites de tenant se respetan
|
||||
- [ ] Outputs se almacenan como Assets
|
||||
- [ ] Generación de texto produce copys coherentes
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
- [ComfyUI Documentation](https://github.com/comfyanonymous/ComfyUI)
|
||||
- [ComfyDeploy](https://www.comfydeploy.com/)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,447 @@
|
||||
# PMC-005: Módulo de Automation
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Definición
|
||||
**Prioridad:** Media
|
||||
|
||||
---
|
||||
|
||||
## Descripción General
|
||||
|
||||
El módulo de Automation gestiona flujos automatizados entre los diferentes componentes de la plataforma. Utiliza n8n como orquestador principal para conectar CRM, motor de generación, notificaciones y sistemas externos.
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Automatizar flujos creativos desde brief hasta entrega
|
||||
2. Integrar CRM con motor de generación
|
||||
3. Disparar acciones basadas en eventos del sistema
|
||||
4. Conectar con servicios externos (email, redes, etc.)
|
||||
5. Reducir tareas manuales repetitivas
|
||||
|
||||
---
|
||||
|
||||
## Entidades del Dominio
|
||||
|
||||
### AutomationFlow
|
||||
|
||||
```yaml
|
||||
Entidad: AutomationFlow
|
||||
Descripción: Definición de un flujo automatizado
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- name: string
|
||||
- description: text
|
||||
- type: enum [trigger_based, scheduled, manual]
|
||||
- trigger_event: string (evento que dispara el flujo)
|
||||
- n8n_workflow_id: string (ID del workflow en n8n)
|
||||
- is_active: boolean
|
||||
- last_run: timestamp
|
||||
- run_count: integer
|
||||
- config: JSONB
|
||||
- retry_on_failure: boolean
|
||||
- max_retries: number
|
||||
- timeout_seconds: number
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- 1:N con AutomationRun
|
||||
```
|
||||
|
||||
### AutomationRun
|
||||
|
||||
```yaml
|
||||
Entidad: AutomationRun
|
||||
Descripción: Ejecución de un flujo automatizado
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- flow_id: UUID (FK)
|
||||
- tenant_id: UUID (FK)
|
||||
- status: enum [running, completed, failed, cancelled]
|
||||
- trigger_data: JSONB (datos del evento que disparó)
|
||||
- output_data: JSONB (resultados)
|
||||
- error_message: text
|
||||
- started_at: timestamp
|
||||
- completed_at: timestamp
|
||||
- duration_ms: integer
|
||||
|
||||
Relaciones:
|
||||
- N:1 con AutomationFlow
|
||||
- N:1 con Tenant
|
||||
```
|
||||
|
||||
### WebhookEndpoint
|
||||
|
||||
```yaml
|
||||
Entidad: WebhookEndpoint
|
||||
Descripción: Endpoint para recibir eventos externos
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- name: string
|
||||
- slug: string (parte de la URL)
|
||||
- secret_key: string (para validación)
|
||||
- target_flow_id: UUID (FK)
|
||||
- is_active: boolean
|
||||
- last_called: timestamp
|
||||
- call_count: integer
|
||||
- created_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con AutomationFlow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Eventos del Sistema
|
||||
|
||||
### Eventos Disponibles para Triggers
|
||||
|
||||
```yaml
|
||||
CRM Events:
|
||||
- client.created
|
||||
- client.updated
|
||||
- brand.created
|
||||
- brand.updated
|
||||
- product.created
|
||||
- product.updated
|
||||
- opportunity.stage_changed
|
||||
- opportunity.won
|
||||
- opportunity.lost
|
||||
|
||||
Project Events:
|
||||
- project.created
|
||||
- project.status_changed
|
||||
- campaign.created
|
||||
- campaign.status_changed
|
||||
- campaign.brief_completed
|
||||
- campaign.approved
|
||||
|
||||
Generation Events:
|
||||
- job.completed
|
||||
- job.failed
|
||||
- batch.completed
|
||||
- model.training_completed
|
||||
|
||||
Asset Events:
|
||||
- asset.created
|
||||
- asset.approved
|
||||
- asset.rejected
|
||||
- all_assets.approved (todos los assets de campaña)
|
||||
|
||||
User Events:
|
||||
- user.created
|
||||
- user.invited
|
||||
- user.activated
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flujos Predefinidos (MVP)
|
||||
|
||||
### FLOW-001: Nuevo Producto → Generar Kit de Assets
|
||||
|
||||
```yaml
|
||||
Nombre: product_asset_kit
|
||||
Trigger: product.created
|
||||
Descripción: Al crear producto, genera pack de imágenes automáticamente
|
||||
|
||||
Pasos:
|
||||
1. Recibir evento product.created
|
||||
2. Cargar datos del producto y su marca
|
||||
3. Verificar si tiene imágenes de referencia
|
||||
4. Llamar a GenerationService con workflow "product_photo_synthetic"
|
||||
5. Esperar completación del job
|
||||
6. Notificar al usuario creador
|
||||
|
||||
Configuración:
|
||||
images_count: 5
|
||||
auto_approve: false
|
||||
notify_on_complete: true
|
||||
```
|
||||
|
||||
### FLOW-002: Nueva Campaña → Generar Lote Inicial
|
||||
|
||||
```yaml
|
||||
Nombre: campaign_initial_batch
|
||||
Trigger: campaign.brief_completed
|
||||
Descripción: Al completar brief, genera creatividades iniciales
|
||||
|
||||
Pasos:
|
||||
1. Recibir evento campaign.brief_completed
|
||||
2. Extraer deliverables del brief
|
||||
3. Para cada formato solicitado:
|
||||
- Llamar a GenerationService
|
||||
- Generar imágenes según especificaciones
|
||||
4. Generar copys con LLM
|
||||
5. Vincular assets a la campaña
|
||||
6. Cambiar estado campaña a "in_production"
|
||||
7. Notificar al equipo asignado
|
||||
|
||||
Configuración:
|
||||
parallel_generations: true
|
||||
generate_copies: true
|
||||
auto_link_assets: true
|
||||
```
|
||||
|
||||
### FLOW-003: Campaña Aprobada → Preparar Entrega
|
||||
|
||||
```yaml
|
||||
Nombre: campaign_delivery_prep
|
||||
Trigger: campaign.approved
|
||||
Descripción: Prepara paquete de entrega al aprobar campaña
|
||||
|
||||
Pasos:
|
||||
1. Recibir evento campaign.approved
|
||||
2. Recopilar todos los assets aprobados
|
||||
3. Generar ZIP con estructura organizada
|
||||
4. Crear enlace de descarga temporal
|
||||
5. Si cliente tiene acceso al portal:
|
||||
- Crear notificación en portal
|
||||
6. Enviar email al contacto del cliente
|
||||
7. Registrar entrega en historial
|
||||
|
||||
Configuración:
|
||||
zip_structure: "by_format" | "flat"
|
||||
include_copies: true
|
||||
link_expiry_days: 7
|
||||
notify_client: true
|
||||
```
|
||||
|
||||
### FLOW-004: Job Fallido → Notificar y Reintentar
|
||||
|
||||
```yaml
|
||||
Nombre: job_failure_handler
|
||||
Trigger: job.failed
|
||||
Descripción: Maneja fallos de generación
|
||||
|
||||
Pasos:
|
||||
1. Recibir evento job.failed
|
||||
2. Evaluar tipo de error
|
||||
3. Si error transitorio (GPU, timeout):
|
||||
- Reintentar hasta max_retries
|
||||
4. Si error persistente:
|
||||
- Notificar al usuario
|
||||
- Crear ticket/tarea de revisión
|
||||
5. Registrar en logs de auditoría
|
||||
|
||||
Configuración:
|
||||
max_retries: 3
|
||||
retry_delay_seconds: 60
|
||||
notify_on_permanent_failure: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### F-005.1: Gestión de Flujos
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-005.1.1 | Listar flujos | Ver flujos disponibles y activos | Alta |
|
||||
| F-005.1.2 | Activar/Desactivar | Toggle de flujos | Alta |
|
||||
| F-005.1.3 | Configurar | Ajustar parámetros de flujo | Media |
|
||||
| F-005.1.4 | Ver historial | Ejecuciones pasadas | Media |
|
||||
|
||||
### F-005.2: Ejecución Manual
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-005.2.1 | Ejecutar ahora | Disparar flujo manualmente | Media |
|
||||
| F-005.2.2 | Test run | Ejecutar en modo prueba | Baja |
|
||||
| F-005.2.3 | Cancelar | Detener ejecución en curso | Media |
|
||||
|
||||
### F-005.3: Webhooks
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-005.3.1 | Crear endpoint | Generar URL de webhook | Media |
|
||||
| F-005.3.2 | Validar payload | Verificar firma/secret | Media |
|
||||
| F-005.3.3 | Ver logs | Historial de llamadas | Baja |
|
||||
|
||||
### F-005.4: Integraciones Externas
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-005.4.1 | Email | Envío de notificaciones | Alta |
|
||||
| F-005.4.2 | Slack | Notificaciones a canales | Media |
|
||||
| F-005.4.3 | CRM externo | Sync bidireccional | Baja |
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura de Integración n8n
|
||||
|
||||
```yaml
|
||||
Componentes:
|
||||
Backend (NestJS):
|
||||
- EventEmitter: Emite eventos del sistema
|
||||
- WebhookController: Recibe llamadas de n8n
|
||||
- AutomationService: Gestiona flujos y ejecuciones
|
||||
|
||||
n8n Server:
|
||||
- Workflows definidos
|
||||
- Credenciales de integración
|
||||
- Webhooks de entrada/salida
|
||||
|
||||
Comunicación:
|
||||
Backend → n8n: Webhooks HTTP POST
|
||||
n8n → Backend: API calls con auth token
|
||||
|
||||
Flujo:
|
||||
1. Evento ocurre en Backend (ej: product.created)
|
||||
2. EventEmitter notifica a AutomationService
|
||||
3. AutomationService busca flujos suscritos al evento
|
||||
4. Para cada flujo activo:
|
||||
- POST a webhook de n8n con datos del evento
|
||||
5. n8n ejecuta workflow
|
||||
6. n8n llama a API del Backend para acciones
|
||||
7. n8n reporta resultado via webhook de completion
|
||||
```
|
||||
|
||||
### Ejemplo de Webhook Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "product.created",
|
||||
"timestamp": "2025-12-08T10:30:00Z",
|
||||
"tenant_id": "uuid-tenant",
|
||||
"data": {
|
||||
"product_id": "uuid-product",
|
||||
"brand_id": "uuid-brand",
|
||||
"name": "Producto X",
|
||||
"description": "...",
|
||||
"reference_images": ["url1", "url2"]
|
||||
},
|
||||
"metadata": {
|
||||
"user_id": "uuid-user",
|
||||
"source": "api"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```yaml
|
||||
Base: /api/v1/automation
|
||||
|
||||
# Flows
|
||||
GET /flows # Listar flujos
|
||||
GET /flows/:id # Detalle de flujo
|
||||
POST /flows/:id/activate # Activar flujo
|
||||
POST /flows/:id/deactivate # Desactivar flujo
|
||||
PUT /flows/:id/config # Configurar flujo
|
||||
POST /flows/:id/execute # Ejecutar manualmente
|
||||
|
||||
# Runs
|
||||
GET /runs # Historial de ejecuciones
|
||||
GET /runs/:id # Detalle de ejecución
|
||||
POST /runs/:id/cancel # Cancelar ejecución
|
||||
|
||||
# Webhooks
|
||||
GET /webhooks # Listar endpoints
|
||||
POST /webhooks # Crear endpoint
|
||||
DELETE /webhooks/:id # Eliminar endpoint
|
||||
POST /webhooks/:id/regenerate # Regenerar secret
|
||||
|
||||
# Incoming webhooks (public, con autenticación por secret)
|
||||
POST /hooks/:tenant_slug/:webhook_slug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reglas de Negocio
|
||||
|
||||
```yaml
|
||||
RN-005.1:
|
||||
Descripción: Flujos desactivados no procesan eventos
|
||||
Comportamiento: Eventos ignorados si flow.is_active = false
|
||||
|
||||
RN-005.2:
|
||||
Descripción: Reintentos con backoff exponencial
|
||||
Cálculo: delay = retry_delay * (2 ^ attempt_number)
|
||||
|
||||
RN-005.3:
|
||||
Descripción: Ejecuciones fallidas notifican a admins
|
||||
Acción: Email + notificación in-app a usuarios con rol admin
|
||||
|
||||
RN-005.4:
|
||||
Descripción: Webhooks requieren validación de secret
|
||||
Validación: HMAC-SHA256 del payload con secret key
|
||||
|
||||
RN-005.5:
|
||||
Descripción: Límite de ejecuciones por hora
|
||||
Validación: Rate limit según plan del tenant
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
```yaml
|
||||
Dependencias de Módulos:
|
||||
- PMC-001 Tenants: Configuración y límites
|
||||
- PMC-002 CRM: Eventos de clientes/productos
|
||||
- PMC-003 Projects: Eventos de campañas
|
||||
- PMC-004 Generation: Llamadas a generación
|
||||
- PMC-006 Assets: Gestión de outputs
|
||||
|
||||
Servicios Externos:
|
||||
- n8n: Orquestador de workflows
|
||||
- SMTP/SendGrid: Envío de emails
|
||||
- Slack API: Notificaciones (opcional)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consideraciones de Seguridad
|
||||
|
||||
```yaml
|
||||
Autenticación:
|
||||
- Webhooks internos usan JWT del sistema
|
||||
- Webhooks externos usan HMAC con secret por endpoint
|
||||
- n8n autenticado con API key exclusiva
|
||||
|
||||
Aislamiento:
|
||||
- Flujos aislados por tenant
|
||||
- Eventos solo visible para el tenant que los genera
|
||||
|
||||
Validación:
|
||||
- Payload size máximo: 1MB
|
||||
- Rate limiting por endpoint
|
||||
- Timeout de ejecución: 5 minutos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] Flujos predefinidos funcionan correctamente
|
||||
- [ ] Eventos del sistema disparan flujos suscritos
|
||||
- [ ] n8n recibe y procesa webhooks
|
||||
- [ ] Ejecuciones se registran con estado y resultado
|
||||
- [ ] Reintentos automáticos funcionan en errores transitorios
|
||||
- [ ] Webhooks externos validan correctamente el secret
|
||||
- [ ] Notificaciones se envían según configuración
|
||||
- [ ] Rate limiting funciona según plan
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
- [n8n Documentation](https://docs.n8n.io/)
|
||||
- [PMC-004-GENERATION.md](./PMC-004-GENERATION.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,449 @@
|
||||
# PMC-006: Módulo de Assets (DAM)
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Definición
|
||||
**Prioridad:** Alta
|
||||
|
||||
---
|
||||
|
||||
## Descripción General
|
||||
|
||||
El módulo de Assets implementa un Digital Asset Management (DAM) simplificado para almacenar, organizar y gestionar todos los recursos digitales generados o subidos a la plataforma: imágenes, copys, videos, modelos IA, y documentos.
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Centralizar todos los activos digitales del tenant
|
||||
2. Organizar assets por cliente, campaña, tipo
|
||||
3. Gestionar versiones y estados de aprobación
|
||||
4. Facilitar búsqueda y descubrimiento de assets
|
||||
5. Controlar acceso y permisos por rol
|
||||
|
||||
---
|
||||
|
||||
## Entidades del Dominio
|
||||
|
||||
### Asset
|
||||
|
||||
```yaml
|
||||
Entidad: Asset
|
||||
Descripción: Recurso digital almacenado en la plataforma
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- name: string
|
||||
- description: text
|
||||
- type: enum [image, copy, video, document, model, template]
|
||||
- mime_type: string
|
||||
- file_path: string (ruta en storage)
|
||||
- file_size: bigint (bytes)
|
||||
- dimensions: JSONB (width, height para imágenes/videos)
|
||||
- duration: integer (segundos, para video/audio)
|
||||
- status: enum [draft, pending_review, approved, rejected, archived]
|
||||
- visibility: enum [private, team, client]
|
||||
- source: enum [generated, uploaded, imported]
|
||||
- generation_job_id: UUID (FK, si fue generado)
|
||||
- metadata: JSONB
|
||||
- prompt: string (si fue generado)
|
||||
- model_used: string
|
||||
- seed: number
|
||||
- parameters: object
|
||||
- tags: array[string]
|
||||
- created_by: UUID (FK a User)
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
- deleted_at: timestamp (soft delete)
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con User (creator)
|
||||
- N:1 con GenerationJob
|
||||
- N:N con Campaign
|
||||
- N:N con Collection
|
||||
- 1:N con AssetVersion
|
||||
```
|
||||
|
||||
### AssetVersion
|
||||
|
||||
```yaml
|
||||
Entidad: AssetVersion
|
||||
Descripción: Versión histórica de un asset
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- asset_id: UUID (FK)
|
||||
- version_number: integer
|
||||
- file_path: string
|
||||
- file_size: bigint
|
||||
- changes_description: text
|
||||
- created_by: UUID (FK)
|
||||
- created_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Asset
|
||||
- N:1 con User
|
||||
```
|
||||
|
||||
### Collection
|
||||
|
||||
```yaml
|
||||
Entidad: Collection
|
||||
Descripción: Agrupación lógica de assets
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- name: string
|
||||
- description: text
|
||||
- type: enum [manual, smart, campaign, brand]
|
||||
- smart_filters: JSONB (criterios para smart collections)
|
||||
- cover_asset_id: UUID (FK, opcional)
|
||||
- is_public: boolean
|
||||
- created_by: UUID (FK)
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:N con Asset
|
||||
- N:1 con User
|
||||
```
|
||||
|
||||
### AssetComment
|
||||
|
||||
```yaml
|
||||
Entidad: AssetComment
|
||||
Descripción: Comentario o feedback sobre un asset
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- asset_id: UUID (FK)
|
||||
- user_id: UUID (FK)
|
||||
- content: text
|
||||
- position: JSONB (x, y para comentarios en imagen)
|
||||
- is_resolved: boolean
|
||||
- parent_id: UUID (FK, para respuestas)
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Asset
|
||||
- N:1 con User
|
||||
- Self-referencial (parent/children)
|
||||
```
|
||||
|
||||
### Download
|
||||
|
||||
```yaml
|
||||
Entidad: Download
|
||||
Descripción: Registro de descargas de assets
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- asset_id: UUID (FK, opcional - puede ser colección)
|
||||
- collection_id: UUID (FK, opcional)
|
||||
- user_id: UUID (FK)
|
||||
- download_type: enum [single, batch, collection]
|
||||
- format: string (original, converted)
|
||||
- ip_address: string
|
||||
- user_agent: string
|
||||
- created_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Asset
|
||||
- N:1 con Collection
|
||||
- N:1 con User
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### F-006.1: Gestión de Assets
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-006.1.1 | Upload | Subir archivos manualmente | Alta |
|
||||
| F-006.1.2 | Ver asset | Detalle con preview y metadata | Alta |
|
||||
| F-006.1.3 | Editar metadata | Nombre, descripción, tags | Alta |
|
||||
| F-006.1.4 | Eliminar | Soft delete con papelera | Alta |
|
||||
| F-006.1.5 | Restaurar | Recuperar de papelera | Media |
|
||||
|
||||
### F-006.2: Organización
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-006.2.1 | Colecciones | Agrupar assets manualmente | Alta |
|
||||
| F-006.2.2 | Smart collections | Colecciones automáticas por criterios | Media |
|
||||
| F-006.2.3 | Tags | Etiquetar assets | Alta |
|
||||
| F-006.2.4 | Filtros | Filtrar por tipo, estado, fecha, etc. | Alta |
|
||||
|
||||
### F-006.3: Búsqueda
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-006.3.1 | Búsqueda texto | Por nombre, descripción, tags | Alta |
|
||||
| F-006.3.2 | Filtros avanzados | Combinar múltiples criterios | Alta |
|
||||
| F-006.3.3 | Búsqueda por similar | Encontrar imágenes similares | Baja |
|
||||
|
||||
### F-006.4: Versiones y Aprobación
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-006.4.1 | Versionar | Subir nueva versión de asset | Media |
|
||||
| F-006.4.2 | Comparar versiones | Ver diferencias | Baja |
|
||||
| F-006.4.3 | Aprobar/Rechazar | Cambiar estado de revisión | Alta |
|
||||
| F-006.4.4 | Comentarios | Feedback sobre assets | Alta |
|
||||
|
||||
### F-006.5: Descargas y Exportación
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-006.5.1 | Descargar individual | Bajar un asset | Alta |
|
||||
| F-006.5.2 | Descargar batch | Bajar múltiples como ZIP | Alta |
|
||||
| F-006.5.3 | Convertir formato | Descargar en formato diferente | Media |
|
||||
| F-006.5.4 | Enlace temporal | URL con expiración | Media |
|
||||
|
||||
---
|
||||
|
||||
## Tipos de Assets Soportados
|
||||
|
||||
```yaml
|
||||
Imágenes:
|
||||
Formatos: PNG, JPG, JPEG, WebP, GIF, SVG
|
||||
Max size: 50MB
|
||||
Procesamiento:
|
||||
- Generación de thumbnails
|
||||
- Extracción de dimensiones
|
||||
- Optimización automática
|
||||
|
||||
Copys/Textos:
|
||||
Tipos: Copy publicitario, título, descripción, hashtags
|
||||
Almacenamiento: En BD + archivo .txt opcional
|
||||
Metadata: Tone, language, character count
|
||||
|
||||
Videos:
|
||||
Formatos: MP4, MOV, WebM
|
||||
Max size: 500MB
|
||||
Procesamiento:
|
||||
- Generación de thumbnail
|
||||
- Extracción de duración
|
||||
- Preview de baja resolución
|
||||
|
||||
Documentos:
|
||||
Formatos: PDF, DOC, DOCX
|
||||
Max size: 100MB
|
||||
Uso: Brand guidelines, briefs, contratos
|
||||
|
||||
Modelos IA:
|
||||
Tipos: LoRA (.safetensors), Checkpoint, Embedding
|
||||
Max size: 10GB
|
||||
Metadata: Base model, trigger word, training params
|
||||
|
||||
Templates:
|
||||
Tipos: Workflow ComfyUI, plantillas de brief
|
||||
Formato: JSON
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Storage Structure
|
||||
|
||||
```yaml
|
||||
S3/MinIO Bucket Structure:
|
||||
{bucket}/
|
||||
├── {tenant_slug}/
|
||||
│ ├── assets/
|
||||
│ │ ├── images/
|
||||
│ │ │ ├── {year}/
|
||||
│ │ │ │ ├── {month}/
|
||||
│ │ │ │ │ ├── {asset_id}/
|
||||
│ │ │ │ │ │ ├── original.{ext}
|
||||
│ │ │ │ │ │ ├── thumb_200.jpg
|
||||
│ │ │ │ │ │ ├── thumb_800.jpg
|
||||
│ │ │ │ │ │ └── versions/
|
||||
│ │ │ │ │ │ ├── v1.{ext}
|
||||
│ │ │ │ │ │ └── v2.{ext}
|
||||
│ │ ├── videos/
|
||||
│ │ ├── documents/
|
||||
│ │ └── copies/
|
||||
│ ├── models/
|
||||
│ │ ├── loras/
|
||||
│ │ ├── checkpoints/
|
||||
│ │ └── embeddings/
|
||||
│ └── temp/
|
||||
│ └── (archivos temporales, limpiados periódicamente)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```yaml
|
||||
Base: /api/v1/assets
|
||||
|
||||
# Assets CRUD
|
||||
POST /assets/upload # Subir archivo(s)
|
||||
GET /assets # Listar con filtros y paginación
|
||||
GET /assets/:id # Detalle de asset
|
||||
PUT /assets/:id # Actualizar metadata
|
||||
DELETE /assets/:id # Soft delete
|
||||
|
||||
# Bulk operations
|
||||
POST /assets/bulk/move # Mover a colección
|
||||
POST /assets/bulk/tag # Agregar tags
|
||||
POST /assets/bulk/delete # Eliminar múltiples
|
||||
POST /assets/bulk/status # Cambiar estado
|
||||
|
||||
# Versions
|
||||
GET /assets/:id/versions # Listar versiones
|
||||
POST /assets/:id/versions # Subir nueva versión
|
||||
GET /assets/:id/versions/:v # Obtener versión específica
|
||||
|
||||
# Status & Approval
|
||||
PATCH /assets/:id/status # Cambiar estado
|
||||
POST /assets/:id/approve # Aprobar
|
||||
POST /assets/:id/reject # Rechazar con feedback
|
||||
|
||||
# Comments
|
||||
GET /assets/:id/comments # Listar comentarios
|
||||
POST /assets/:id/comments # Agregar comentario
|
||||
PUT /assets/:id/comments/:cid # Editar comentario
|
||||
DELETE /assets/:id/comments/:cid # Eliminar comentario
|
||||
|
||||
# Downloads
|
||||
GET /assets/:id/download # Descargar asset
|
||||
POST /assets/download/batch # Descargar múltiples (ZIP)
|
||||
POST /assets/:id/share # Generar enlace temporal
|
||||
|
||||
# Collections
|
||||
GET /collections # Listar colecciones
|
||||
POST /collections # Crear colección
|
||||
GET /collections/:id # Detalle de colección
|
||||
PUT /collections/:id # Actualizar colección
|
||||
DELETE /collections/:id # Eliminar colección
|
||||
POST /collections/:id/assets # Agregar assets
|
||||
DELETE /collections/:id/assets # Quitar assets
|
||||
|
||||
# Search
|
||||
POST /assets/search # Búsqueda avanzada
|
||||
GET /assets/tags # Listar tags usados
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reglas de Negocio
|
||||
|
||||
```yaml
|
||||
RN-006.1:
|
||||
Descripción: Assets generados heredan metadata del job
|
||||
Comportamiento: Copiar prompt, modelo, parámetros automáticamente
|
||||
|
||||
RN-006.2:
|
||||
Descripción: Thumbnails se generan automáticamente
|
||||
Tamaños: 200px y 800px de ancho, manteniendo aspect ratio
|
||||
|
||||
RN-006.3:
|
||||
Descripción: Soft delete retiene archivos 30 días
|
||||
Acción: Cron job limpia después del período
|
||||
|
||||
RN-006.4:
|
||||
Descripción: Versionado mantiene historial completo
|
||||
Límite: Máximo 10 versiones por asset
|
||||
|
||||
RN-006.5:
|
||||
Descripción: Links temporales expiran según configuración
|
||||
Default: 7 días, máximo 30 días
|
||||
|
||||
RN-006.6:
|
||||
Descripción: Storage cuenta contra cuota del tenant
|
||||
Validación: Verificar límite antes de upload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI/UX Consideraciones
|
||||
|
||||
```yaml
|
||||
Vistas principales:
|
||||
- Grid view: Thumbnails en cuadrícula
|
||||
- List view: Lista con metadata
|
||||
- Detail view: Asset grande con panel de info
|
||||
|
||||
Componentes:
|
||||
- AssetUploader: Drag & drop, multi-file
|
||||
- AssetPreview: Lightbox con navegación
|
||||
- AssetFilters: Panel de filtros colapsable
|
||||
- CollectionPicker: Modal para agregar a colección
|
||||
- CommentPanel: Sidebar con comentarios
|
||||
|
||||
Acciones rápidas:
|
||||
- Quick preview (spacebar)
|
||||
- Quick download (d)
|
||||
- Quick approve (a)
|
||||
- Add to collection (c)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
```yaml
|
||||
Dependencias de Módulos:
|
||||
- PMC-001 Tenants: Cuotas de storage
|
||||
- PMC-003 Projects: Vinculación con campañas
|
||||
- PMC-004 Generation: Assets generados
|
||||
|
||||
Servicios Externos:
|
||||
- S3/MinIO: Almacenamiento de archivos
|
||||
- Sharp: Procesamiento de imágenes
|
||||
- FFmpeg: Procesamiento de video (opcional)
|
||||
|
||||
Dependencias del Catálogo:
|
||||
- (ninguna directa)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consideraciones de Performance
|
||||
|
||||
```yaml
|
||||
Optimizaciones:
|
||||
- Lazy loading de thumbnails
|
||||
- Paginación con cursor para grandes volúmenes
|
||||
- CDN para servir assets estáticos
|
||||
- Compresión de uploads grandes
|
||||
- Pre-signed URLs para uploads directos a S3
|
||||
|
||||
Índices BD:
|
||||
- tenant_id + type + status
|
||||
- tenant_id + created_at
|
||||
- tenant_id + tags (GIN index)
|
||||
- Full-text search en name, description
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] Upload funciona con drag & drop y file picker
|
||||
- [ ] Thumbnails se generan automáticamente
|
||||
- [ ] Búsqueda y filtros funcionan correctamente
|
||||
- [ ] Colecciones permiten organizar assets
|
||||
- [ ] Versionado mantiene historial
|
||||
- [ ] Flujo de aprobación funciona
|
||||
- [ ] Descargas individuales y batch funcionan
|
||||
- [ ] Links temporales se generan y expiran
|
||||
- [ ] Comentarios se pueden agregar y resolver
|
||||
- [ ] Storage se cuenta contra cuota
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
- [GLOSARIO.md](../00-vision-general/GLOSARIO.md) - Definición de DAM
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,495 @@
|
||||
# PMC-007: Módulo de Admin
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Definición
|
||||
**Prioridad:** Media
|
||||
|
||||
---
|
||||
|
||||
## Descripción General
|
||||
|
||||
El módulo de Admin proporciona las funcionalidades de administración del sistema SaaS: gestión de usuarios, roles, permisos, planes de suscripción, configuración global y herramientas de supervisión.
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Gestionar usuarios y sus roles dentro del tenant
|
||||
2. Controlar permisos de acceso por módulo/acción
|
||||
3. Administrar planes y suscripciones (preparación SaaS)
|
||||
4. Configurar parámetros globales del sistema
|
||||
5. Monitorear uso y salud del sistema
|
||||
|
||||
---
|
||||
|
||||
## Entidades del Dominio
|
||||
|
||||
### User
|
||||
|
||||
```yaml
|
||||
Entidad: User
|
||||
Descripción: Usuario del sistema
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- email: string (único por tenant)
|
||||
- password_hash: string
|
||||
- first_name: string
|
||||
- last_name: string
|
||||
- avatar_url: string
|
||||
- status: enum [pending, active, suspended, deactivated]
|
||||
- role_id: UUID (FK)
|
||||
- preferences: JSONB
|
||||
- language: string
|
||||
- timezone: string
|
||||
- theme: string
|
||||
- notifications: object
|
||||
- last_login_at: timestamp
|
||||
- email_verified_at: timestamp
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con Role
|
||||
- 1:N con Project (como owner)
|
||||
- 1:N con Asset (como creator)
|
||||
- 1:N con GenerationJob
|
||||
```
|
||||
|
||||
### Role
|
||||
|
||||
```yaml
|
||||
Entidad: Role
|
||||
Descripción: Rol con conjunto de permisos
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK, null = rol de sistema)
|
||||
- name: string
|
||||
- description: text
|
||||
- permissions: array[string] (lista de permisos)
|
||||
- is_system: boolean (no editable si true)
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant (opcional)
|
||||
- 1:N con User
|
||||
|
||||
Roles de Sistema:
|
||||
- super_admin: Acceso total, sin límites
|
||||
- tenant_admin: Admin del tenant
|
||||
- creative: Crear contenido, gestionar campañas
|
||||
- analyst: CRM, reportes
|
||||
- viewer: Solo lectura
|
||||
```
|
||||
|
||||
### Permission (definición estática)
|
||||
|
||||
```yaml
|
||||
Permisos del Sistema:
|
||||
# Tenants
|
||||
- tenants.view
|
||||
- tenants.create
|
||||
- tenants.edit
|
||||
- tenants.delete
|
||||
|
||||
# Users
|
||||
- users.view
|
||||
- users.create
|
||||
- users.edit
|
||||
- users.delete
|
||||
- users.invite
|
||||
|
||||
# CRM
|
||||
- clients.view
|
||||
- clients.create
|
||||
- clients.edit
|
||||
- clients.delete
|
||||
- brands.view
|
||||
- brands.create
|
||||
- brands.edit
|
||||
- brands.delete
|
||||
- products.view
|
||||
- products.create
|
||||
- products.edit
|
||||
- products.delete
|
||||
|
||||
# Projects
|
||||
- projects.view
|
||||
- projects.create
|
||||
- projects.edit
|
||||
- projects.delete
|
||||
- campaigns.view
|
||||
- campaigns.create
|
||||
- campaigns.edit
|
||||
- campaigns.delete
|
||||
- campaigns.approve
|
||||
|
||||
# Generation
|
||||
- generation.execute
|
||||
- generation.view_queue
|
||||
- generation.manage_queue
|
||||
- models.view
|
||||
- models.create
|
||||
- models.delete
|
||||
- models.train
|
||||
|
||||
# Assets
|
||||
- assets.view
|
||||
- assets.upload
|
||||
- assets.edit
|
||||
- assets.delete
|
||||
- assets.approve
|
||||
- assets.download
|
||||
|
||||
# Automation
|
||||
- automation.view
|
||||
- automation.configure
|
||||
- automation.execute
|
||||
|
||||
# Admin
|
||||
- admin.users
|
||||
- admin.roles
|
||||
- admin.settings
|
||||
- admin.billing
|
||||
- admin.audit
|
||||
```
|
||||
|
||||
### Invitation
|
||||
|
||||
```yaml
|
||||
Entidad: Invitation
|
||||
Descripción: Invitación pendiente para unirse al tenant
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- email: string
|
||||
- role_id: UUID (FK)
|
||||
- invited_by: UUID (FK a User)
|
||||
- token: string (único)
|
||||
- status: enum [pending, accepted, expired, cancelled]
|
||||
- expires_at: timestamp
|
||||
- accepted_at: timestamp
|
||||
- created_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con Role
|
||||
- N:1 con User (inviter)
|
||||
```
|
||||
|
||||
### AuditLog
|
||||
|
||||
```yaml
|
||||
Entidad: AuditLog
|
||||
Descripción: Registro de acciones importantes
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- user_id: UUID (FK)
|
||||
- action: string (ej: user.created, asset.deleted)
|
||||
- entity_type: string
|
||||
- entity_id: UUID
|
||||
- old_values: JSONB
|
||||
- new_values: JSONB
|
||||
- ip_address: string
|
||||
- user_agent: string
|
||||
- created_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con User
|
||||
```
|
||||
|
||||
### Setting
|
||||
|
||||
```yaml
|
||||
Entidad: Setting
|
||||
Descripción: Configuración del sistema/tenant
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK, null = global)
|
||||
- key: string
|
||||
- value: JSONB
|
||||
- type: enum [string, number, boolean, json]
|
||||
- category: string
|
||||
- is_secret: boolean
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant (opcional)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### F-007.1: Gestión de Usuarios
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-007.1.1 | Listar usuarios | Ver todos los usuarios del tenant | Alta |
|
||||
| F-007.1.2 | Invitar usuario | Enviar invitación por email | Alta |
|
||||
| F-007.1.3 | Editar usuario | Modificar datos y rol | Alta |
|
||||
| F-007.1.4 | Suspender usuario | Bloquear acceso temporalmente | Media |
|
||||
| F-007.1.5 | Eliminar usuario | Desactivar cuenta | Media |
|
||||
|
||||
### F-007.2: Gestión de Roles
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-007.2.1 | Listar roles | Ver roles disponibles | Alta |
|
||||
| F-007.2.2 | Crear rol | Definir rol personalizado | Media |
|
||||
| F-007.2.3 | Editar permisos | Modificar permisos de rol | Media |
|
||||
| F-007.2.4 | Eliminar rol | Solo si no tiene usuarios | Baja |
|
||||
|
||||
### F-007.3: Configuración
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-007.3.1 | Settings generales | Nombre, branding, etc. | Alta |
|
||||
| F-007.3.2 | Settings de generación | Modelos por defecto, calidad | Media |
|
||||
| F-007.3.3 | Integraciones | Configurar n8n, APIs externas | Media |
|
||||
| F-007.3.4 | Notificaciones | Templates de email, webhooks | Media |
|
||||
|
||||
### F-007.4: Auditoría
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-007.4.1 | Ver logs | Historial de acciones | Media |
|
||||
| F-007.4.2 | Filtrar logs | Por usuario, acción, fecha | Media |
|
||||
| F-007.4.3 | Exportar logs | Descargar en CSV | Baja |
|
||||
|
||||
### F-007.5: Monitoreo (Super Admin)
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-007.5.1 | Dashboard sistema | Métricas globales | Media |
|
||||
| F-007.5.2 | Estado de servicios | GPU, queue, storage | Alta |
|
||||
| F-007.5.3 | Uso por tenant | Consumo de recursos | Media |
|
||||
|
||||
---
|
||||
|
||||
## Roles del Sistema
|
||||
|
||||
### Matriz de Permisos por Rol
|
||||
|
||||
```yaml
|
||||
super_admin:
|
||||
description: "Administrador del sistema completo"
|
||||
permissions: ["*"] # Todos los permisos
|
||||
notes: "Solo para el owner/CTO. Sin límites de uso."
|
||||
|
||||
tenant_admin:
|
||||
description: "Administrador del tenant/agencia"
|
||||
permissions:
|
||||
- users.*
|
||||
- roles.view
|
||||
- clients.*
|
||||
- brands.*
|
||||
- products.*
|
||||
- projects.*
|
||||
- campaigns.*
|
||||
- generation.*
|
||||
- assets.*
|
||||
- automation.view
|
||||
- automation.configure
|
||||
- admin.users
|
||||
- admin.settings
|
||||
- admin.audit
|
||||
|
||||
creative:
|
||||
description: "Creativo/Media Buyer"
|
||||
permissions:
|
||||
- clients.view
|
||||
- brands.view
|
||||
- products.view
|
||||
- products.create
|
||||
- projects.view
|
||||
- projects.create
|
||||
- projects.edit
|
||||
- campaigns.*
|
||||
- generation.execute
|
||||
- generation.view_queue
|
||||
- models.view
|
||||
- assets.view
|
||||
- assets.upload
|
||||
- assets.edit
|
||||
|
||||
analyst:
|
||||
description: "Analista/CRM"
|
||||
permissions:
|
||||
- clients.*
|
||||
- brands.view
|
||||
- products.view
|
||||
- projects.view
|
||||
- campaigns.view
|
||||
- assets.view
|
||||
- assets.download
|
||||
- automation.view
|
||||
|
||||
viewer:
|
||||
description: "Solo lectura"
|
||||
permissions:
|
||||
- clients.view
|
||||
- brands.view
|
||||
- products.view
|
||||
- projects.view
|
||||
- campaigns.view
|
||||
- assets.view
|
||||
|
||||
client_portal:
|
||||
description: "Cliente externo (portal)"
|
||||
permissions:
|
||||
- campaigns.view # Solo sus campañas
|
||||
- assets.view # Solo sus assets
|
||||
- assets.download
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```yaml
|
||||
Base: /api/v1/admin
|
||||
|
||||
# Users
|
||||
GET /users # Listar usuarios
|
||||
GET /users/:id # Detalle de usuario
|
||||
POST /users/invite # Invitar usuario
|
||||
PUT /users/:id # Actualizar usuario
|
||||
PATCH /users/:id/status # Cambiar estado
|
||||
DELETE /users/:id # Desactivar usuario
|
||||
|
||||
# Invitations
|
||||
GET /invitations # Listar invitaciones
|
||||
POST /invitations/:id/resend # Reenviar
|
||||
DELETE /invitations/:id # Cancelar
|
||||
|
||||
# Roles
|
||||
GET /roles # Listar roles
|
||||
GET /roles/:id # Detalle de rol
|
||||
POST /roles # Crear rol
|
||||
PUT /roles/:id # Actualizar rol
|
||||
DELETE /roles/:id # Eliminar rol
|
||||
|
||||
# Settings
|
||||
GET /settings # Listar settings
|
||||
GET /settings/:key # Obtener setting
|
||||
PUT /settings/:key # Actualizar setting
|
||||
DELETE /settings/:key # Eliminar setting (custom)
|
||||
|
||||
# Audit
|
||||
GET /audit # Listar logs
|
||||
GET /audit/export # Exportar logs
|
||||
|
||||
# System (Super Admin only)
|
||||
GET /system/status # Estado del sistema
|
||||
GET /system/metrics # Métricas globales
|
||||
GET /system/tenants # Listar todos los tenants
|
||||
GET /system/tenants/:id/usage # Uso de un tenant
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reglas de Negocio
|
||||
|
||||
```yaml
|
||||
RN-007.1:
|
||||
Descripción: Email único por tenant
|
||||
Validación: No puede haber dos usuarios con mismo email en un tenant
|
||||
|
||||
RN-007.2:
|
||||
Descripción: Roles de sistema no editables
|
||||
Validación: is_system = true bloquea edición
|
||||
|
||||
RN-007.3:
|
||||
Descripción: No eliminar rol con usuarios asignados
|
||||
Validación: Verificar user_count = 0 antes de DELETE
|
||||
|
||||
RN-007.4:
|
||||
Descripción: Invitación expira en 7 días
|
||||
Validación: expires_at = created_at + 7 days
|
||||
|
||||
RN-007.5:
|
||||
Descripción: Super admin no puede ser suspendido
|
||||
Validación: Bloquear cambio de status si role = super_admin
|
||||
|
||||
RN-007.6:
|
||||
Descripción: Audit logs son inmutables
|
||||
Validación: Solo INSERT, no UPDATE ni DELETE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
```yaml
|
||||
Dependencias de Módulos:
|
||||
- PMC-001 Tenants: Configuración de tenant
|
||||
- Todos los módulos: Para verificación de permisos
|
||||
|
||||
Dependencias del Catálogo:
|
||||
- @CATALOG_AUTH: Autenticación JWT + OAuth
|
||||
- @CATALOG_SESSION: Gestión de sesiones
|
||||
|
||||
Servicios Externos:
|
||||
- SMTP: Envío de invitaciones
|
||||
- (OAuth providers si se implementa SSO)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flujos de Usuario
|
||||
|
||||
### Invitar Usuario
|
||||
|
||||
```
|
||||
1. Admin navega a Users → Invite
|
||||
2. Ingresa email y selecciona rol
|
||||
3. Sistema genera token único
|
||||
4. Sistema envía email con link
|
||||
5. Usuario hace clic en link
|
||||
6. Usuario completa registro (password, nombre)
|
||||
7. Usuario queda activo en el tenant
|
||||
```
|
||||
|
||||
### Cambiar Rol de Usuario
|
||||
|
||||
```
|
||||
1. Admin navega a Users → [Usuario]
|
||||
2. Selecciona nuevo rol
|
||||
3. Sistema verifica que no sea el único admin
|
||||
4. Sistema actualiza rol
|
||||
5. Permisos se aplican inmediatamente
|
||||
6. Se registra en audit log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] CRUD completo de usuarios funciona
|
||||
- [ ] Sistema de invitaciones por email funciona
|
||||
- [ ] Roles controlan acceso a módulos correctamente
|
||||
- [ ] Permisos se verifican en cada endpoint
|
||||
- [ ] Settings se guardan y cargan correctamente
|
||||
- [ ] Audit logs registran acciones importantes
|
||||
- [ ] Dashboard de sistema muestra métricas
|
||||
- [ ] Super admin tiene acceso a todo
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
- [@CATALOG_AUTH](../../../core/catalog/modules/auth/)
|
||||
- [PMC-001-TENANTS.md](./PMC-001-TENANTS.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,443 @@
|
||||
# PMC-008: Módulo de Analytics
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado:** Definición
|
||||
**Prioridad:** Baja
|
||||
|
||||
---
|
||||
|
||||
## Descripción General
|
||||
|
||||
El módulo de Analytics proporciona dashboards, reportes y métricas sobre el uso de la plataforma, rendimiento de campañas, y consumo de recursos. Permite tomar decisiones basadas en datos.
|
||||
|
||||
---
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Visualizar métricas clave de operación
|
||||
2. Analizar rendimiento de campañas
|
||||
3. Monitorear uso de recursos (generaciones, storage)
|
||||
4. Generar reportes exportables
|
||||
5. Identificar tendencias y oportunidades
|
||||
|
||||
---
|
||||
|
||||
## Dashboards
|
||||
|
||||
### Dashboard Principal (Home)
|
||||
|
||||
```yaml
|
||||
Widgets:
|
||||
- quick_stats:
|
||||
- Campañas activas
|
||||
- Assets generados (mes)
|
||||
- Tasa de aprobación
|
||||
- Jobs en cola
|
||||
|
||||
- recent_activity:
|
||||
- Últimos assets generados
|
||||
- Campañas recién creadas
|
||||
- Jobs completados
|
||||
|
||||
- pending_actions:
|
||||
- Assets pendientes de revisión
|
||||
- Campañas esperando aprobación
|
||||
```
|
||||
|
||||
### Dashboard de Producción
|
||||
|
||||
```yaml
|
||||
Widgets:
|
||||
- generation_volume:
|
||||
Tipo: Line chart
|
||||
Datos: Generaciones por día/semana/mes
|
||||
Filtros: Tipo (imagen/texto), workflow
|
||||
|
||||
- queue_status:
|
||||
Tipo: Real-time gauge
|
||||
Datos: Jobs en cola, procesando, completados
|
||||
|
||||
- model_usage:
|
||||
Tipo: Pie chart
|
||||
Datos: Distribución de uso de workflows/LoRAs
|
||||
|
||||
- error_rate:
|
||||
Tipo: Line chart
|
||||
Datos: % de jobs fallidos por período
|
||||
|
||||
- processing_time:
|
||||
Tipo: Bar chart
|
||||
Datos: Tiempo promedio por tipo de workflow
|
||||
```
|
||||
|
||||
### Dashboard de Campañas
|
||||
|
||||
```yaml
|
||||
Widgets:
|
||||
- campaign_funnel:
|
||||
Tipo: Funnel chart
|
||||
Datos: Campañas por estado
|
||||
|
||||
- approval_metrics:
|
||||
Tipo: Stats cards
|
||||
Datos:
|
||||
- Tasa de aprobación primera iteración
|
||||
- Promedio de revisiones por campaña
|
||||
- Tiempo desde brief hasta aprobación
|
||||
|
||||
- assets_per_campaign:
|
||||
Tipo: Bar chart
|
||||
Datos: Promedio de assets por campaña
|
||||
|
||||
- top_clients:
|
||||
Tipo: Table
|
||||
Datos: Clientes con más campañas/assets
|
||||
```
|
||||
|
||||
### Dashboard de Recursos
|
||||
|
||||
```yaml
|
||||
Widgets:
|
||||
- storage_usage:
|
||||
Tipo: Progress bar + breakdown
|
||||
Datos: GB usados vs cuota, por tipo de archivo
|
||||
|
||||
- generation_quota:
|
||||
Tipo: Progress bar
|
||||
Datos: Generaciones usadas vs límite mensual
|
||||
|
||||
- gpu_utilization:
|
||||
Tipo: Real-time gauge (si aplica)
|
||||
Datos: % de uso de GPU
|
||||
|
||||
- cost_estimate:
|
||||
Tipo: Stats card
|
||||
Datos: Costo estimado de APIs externas (LLM, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reportes
|
||||
|
||||
### Reporte de Actividad Mensual
|
||||
|
||||
```yaml
|
||||
Nombre: monthly_activity_report
|
||||
Período: Mes natural
|
||||
Contenido:
|
||||
- Resumen ejecutivo
|
||||
- Campañas creadas/completadas
|
||||
- Assets generados por tipo
|
||||
- Clientes más activos
|
||||
- Uso de recursos
|
||||
- Comparativa con mes anterior
|
||||
|
||||
Formatos: PDF, Excel
|
||||
Programación: Automático primer día del mes
|
||||
```
|
||||
|
||||
### Reporte de Campaña
|
||||
|
||||
```yaml
|
||||
Nombre: campaign_report
|
||||
Período: Duración de la campaña
|
||||
Contenido:
|
||||
- Datos de la campaña y brief
|
||||
- Assets generados
|
||||
- Historial de revisiones
|
||||
- Tiempo total de producción
|
||||
- Participantes (usuarios)
|
||||
|
||||
Formatos: PDF
|
||||
Generación: Manual o al cerrar campaña
|
||||
```
|
||||
|
||||
### Reporte de Cliente
|
||||
|
||||
```yaml
|
||||
Nombre: client_report
|
||||
Período: Configurable
|
||||
Contenido:
|
||||
- Proyectos y campañas del cliente
|
||||
- Assets entregados
|
||||
- Histórico de actividad
|
||||
- Métricas de satisfacción (si aplica)
|
||||
|
||||
Formatos: PDF, Excel
|
||||
Generación: Manual
|
||||
```
|
||||
|
||||
### Reporte de Uso (Admin)
|
||||
|
||||
```yaml
|
||||
Nombre: usage_report
|
||||
Período: Configurable
|
||||
Contenido:
|
||||
- Generaciones por usuario
|
||||
- Storage consumido
|
||||
- Costo de APIs externas
|
||||
- Comparativa por período
|
||||
|
||||
Formatos: Excel, CSV
|
||||
Audiencia: Admin/Finance
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entidades del Dominio
|
||||
|
||||
### Metric
|
||||
|
||||
```yaml
|
||||
Entidad: Metric (tabla de hechos)
|
||||
Descripción: Registro agregado de métricas
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- metric_type: string (generation_count, storage_used, etc.)
|
||||
- dimension_1: string (ej: workflow_type)
|
||||
- dimension_2: string (ej: user_id)
|
||||
- value: decimal
|
||||
- period_type: enum [hour, day, week, month]
|
||||
- period_start: timestamp
|
||||
- created_at: timestamp
|
||||
|
||||
Índices:
|
||||
- tenant_id + metric_type + period_start
|
||||
- tenant_id + period_type + period_start
|
||||
```
|
||||
|
||||
### Report
|
||||
|
||||
```yaml
|
||||
Entidad: Report
|
||||
Descripción: Reporte generado
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- name: string
|
||||
- type: string (monthly_activity, campaign, client, usage)
|
||||
- parameters: JSONB (filtros aplicados)
|
||||
- file_path: string
|
||||
- file_format: enum [pdf, xlsx, csv]
|
||||
- generated_by: UUID (FK a User)
|
||||
- created_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con User
|
||||
```
|
||||
|
||||
### SavedView
|
||||
|
||||
```yaml
|
||||
Entidad: SavedView
|
||||
Descripción: Vista personalizada guardada
|
||||
Atributos:
|
||||
- id: UUID (PK)
|
||||
- tenant_id: UUID (FK)
|
||||
- user_id: UUID (FK)
|
||||
- name: string
|
||||
- dashboard: string (production, campaigns, resources)
|
||||
- config: JSONB (filtros, widgets visibles, layout)
|
||||
- is_default: boolean
|
||||
- created_at: timestamp
|
||||
- updated_at: timestamp
|
||||
|
||||
Relaciones:
|
||||
- N:1 con Tenant
|
||||
- N:1 con User
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funcionalidades
|
||||
|
||||
### F-008.1: Dashboards
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-008.1.1 | Dashboard home | Vista principal con KPIs | Alta |
|
||||
| F-008.1.2 | Dashboard producción | Métricas de generación | Media |
|
||||
| F-008.1.3 | Dashboard campañas | Métricas de campañas | Media |
|
||||
| F-008.1.4 | Dashboard recursos | Uso de recursos | Media |
|
||||
| F-008.1.5 | Filtros globales | Por fecha, cliente, usuario | Alta |
|
||||
|
||||
### F-008.2: Reportes
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-008.2.1 | Generar reporte | Crear reporte bajo demanda | Media |
|
||||
| F-008.2.2 | Programar reporte | Generación automática | Baja |
|
||||
| F-008.2.3 | Descargar reporte | PDF, Excel, CSV | Media |
|
||||
| F-008.2.4 | Historial reportes | Ver reportes generados | Baja |
|
||||
|
||||
### F-008.3: Personalización
|
||||
|
||||
| ID | Funcionalidad | Descripción | Prioridad |
|
||||
|----|---------------|-------------|-----------|
|
||||
| F-008.3.1 | Guardar vista | Guardar configuración de dashboard | Baja |
|
||||
| F-008.3.2 | Vista por defecto | Establecer vista inicial | Baja |
|
||||
|
||||
---
|
||||
|
||||
## KPIs Principales
|
||||
|
||||
```yaml
|
||||
Operación:
|
||||
- Generaciones totales (día/semana/mes)
|
||||
- Tiempo promedio de generación
|
||||
- Tasa de éxito de jobs (%)
|
||||
- Cola promedio (tiempo de espera)
|
||||
|
||||
Campañas:
|
||||
- Campañas activas
|
||||
- Tiempo promedio brief → aprobación
|
||||
- Tasa de aprobación primera iteración (%)
|
||||
- Assets por campaña (promedio)
|
||||
|
||||
Recursos:
|
||||
- Storage utilizado vs cuota (%)
|
||||
- Generaciones usadas vs límite (%)
|
||||
- Costo estimado de APIs externas
|
||||
|
||||
Usuarios:
|
||||
- Usuarios activos (día/semana/mes)
|
||||
- Generaciones por usuario
|
||||
- Acciones por usuario
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
```yaml
|
||||
Base: /api/v1/analytics
|
||||
|
||||
# Dashboards
|
||||
GET /dashboards/:name # Datos de dashboard
|
||||
GET /dashboards/:name/widgets/:widget # Datos de widget específico
|
||||
|
||||
# Metrics
|
||||
GET /metrics # Query de métricas
|
||||
POST /metrics/aggregate # Agregación personalizada
|
||||
|
||||
# Reports
|
||||
GET /reports # Listar reportes generados
|
||||
POST /reports # Generar nuevo reporte
|
||||
GET /reports/:id # Detalle de reporte
|
||||
GET /reports/:id/download # Descargar archivo
|
||||
DELETE /reports/:id # Eliminar reporte
|
||||
|
||||
# Saved Views
|
||||
GET /views # Listar vistas guardadas
|
||||
POST /views # Crear vista
|
||||
PUT /views/:id # Actualizar vista
|
||||
DELETE /views/:id # Eliminar vista
|
||||
PATCH /views/:id/default # Establecer como default
|
||||
|
||||
# Quick stats (para widgets)
|
||||
GET /stats/overview # Resumen general
|
||||
GET /stats/generations # Stats de generación
|
||||
GET /stats/campaigns # Stats de campañas
|
||||
GET /stats/storage # Stats de storage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura de Datos
|
||||
|
||||
### Pipeline de Métricas
|
||||
|
||||
```yaml
|
||||
Flujo:
|
||||
1. Evento ocurre (generación, campaña creada, etc.)
|
||||
2. EventEmitter emite evento
|
||||
3. MetricsService captura y procesa
|
||||
4. Se inserta en tabla metrics (agregado horario)
|
||||
5. Job nocturno consolida a día/semana/mes
|
||||
|
||||
Retención:
|
||||
- Métricas horarias: 7 días
|
||||
- Métricas diarias: 90 días
|
||||
- Métricas semanales: 1 año
|
||||
- Métricas mensuales: indefinido
|
||||
```
|
||||
|
||||
### Queries Optimizadas
|
||||
|
||||
```yaml
|
||||
Estrategias:
|
||||
- Tablas de métricas pre-agregadas
|
||||
- Índices por tenant + período
|
||||
- Cache en Redis para datos frecuentes
|
||||
- Refresh periódico de materialized views
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias
|
||||
|
||||
```yaml
|
||||
Dependencias de Módulos:
|
||||
- PMC-001 Tenants: Contexto de datos
|
||||
- PMC-003 Projects: Datos de campañas
|
||||
- PMC-004 Generation: Datos de generación
|
||||
- PMC-006 Assets: Datos de almacenamiento
|
||||
|
||||
Servicios Externos:
|
||||
- Redis: Cache de métricas
|
||||
- (Opcional) Chart library frontend
|
||||
|
||||
Dependencias del Catálogo:
|
||||
- (ninguna directa)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI/UX Consideraciones
|
||||
|
||||
```yaml
|
||||
Componentes:
|
||||
- DashboardGrid: Layout responsivo de widgets
|
||||
- ChartWidget: Wrapper para gráficos
|
||||
- FilterBar: Barra de filtros global
|
||||
- DateRangePicker: Selector de período
|
||||
- ExportButton: Descarga de datos/reportes
|
||||
|
||||
Interactividad:
|
||||
- Drill-down en gráficos
|
||||
- Tooltips con detalles
|
||||
- Filtros aplicables a toda la página
|
||||
- Auto-refresh configurable
|
||||
|
||||
Responsividad:
|
||||
- Widgets se reordenan en móvil
|
||||
- Gráficos adaptan tamaño
|
||||
- Tablas con scroll horizontal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Criterios de Aceptación
|
||||
|
||||
- [ ] Dashboard home muestra KPIs correctos
|
||||
- [ ] Filtros de fecha funcionan globalmente
|
||||
- [ ] Gráficos cargan datos correctamente
|
||||
- [ ] Reportes se generan en PDF y Excel
|
||||
- [ ] Métricas se agregan correctamente
|
||||
- [ ] Cache mejora tiempos de carga
|
||||
- [ ] Datos se aíslan por tenant
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [VISION-GENERAL.md](../00-vision-general/VISION-GENERAL.md)
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,148 @@
|
||||
# Índice de Módulos - Platform Marketing Content
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Resumen de Módulos
|
||||
|
||||
| ID | Módulo | Descripción | Prioridad | Estado |
|
||||
|----|--------|-------------|-----------|--------|
|
||||
| PMC-001 | [Tenants](./PMC-001-TENANTS.md) | Arquitectura multi-tenant, planes, configuración | Alta | Definido |
|
||||
| PMC-002 | [CRM](./PMC-002-CRM.md) | Clientes, marcas, productos, oportunidades | Alta | Definido |
|
||||
| PMC-003 | [Projects](./PMC-003-PROJECTS.md) | Proyectos, campañas, briefs, flujos de trabajo | Alta | Definido |
|
||||
| PMC-004 | [Generation](./PMC-004-GENERATION.md) | Motor de IA, workflows ComfyUI, modelos custom | Alta | Definido |
|
||||
| PMC-005 | [Automation](./PMC-005-AUTOMATION.md) | Flujos automatizados con n8n, triggers, webhooks | Media | Definido |
|
||||
| PMC-006 | [Assets](./PMC-006-ASSETS.md) | DAM, biblioteca de activos, versionado | Alta | Definido |
|
||||
| PMC-007 | [Admin](./PMC-007-ADMIN.md) | Usuarios, roles, permisos, configuración SaaS | Media | Definido |
|
||||
| PMC-008 | [Analytics](./PMC-008-ANALYTICS.md) | Dashboards, reportes, métricas | Baja | Definido |
|
||||
|
||||
---
|
||||
|
||||
## Dependencias entre Módulos
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ PMC-001 Tenants │
|
||||
│ (Base de aislamiento) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ PMC-007 │ │ PMC-002 │ │ PMC-006 │
|
||||
│ Admin │ │ CRM │ │ Assets │
|
||||
│ (Users/Roles)│ │ (Clientes) │ │ (DAM) │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
│ │ ▲
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌───────────────┐ │
|
||||
│ │ PMC-003 │ │
|
||||
│ │ Projects │──────────────┤
|
||||
│ │ (Campañas) │ │
|
||||
│ └───────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌───────────────┐ │
|
||||
│ │ PMC-004 │ │
|
||||
│ │ Generation │──────────────┘
|
||||
│ │ (Motor IA) │
|
||||
│ └───────────────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌───────────────┐
|
||||
└───────────►│ PMC-005 │
|
||||
│ Automation │
|
||||
│ (n8n) │
|
||||
└───────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ PMC-008 │
|
||||
│ Analytics │
|
||||
│ (Métricas) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Orden de Implementación Sugerido
|
||||
|
||||
### Fase 1 - Core MVP
|
||||
|
||||
1. **PMC-001 Tenants** - Base de la arquitectura
|
||||
2. **PMC-007 Admin** - Usuarios y autenticación
|
||||
3. **PMC-002 CRM** - Gestión de clientes y marcas
|
||||
4. **PMC-006 Assets** - Almacenamiento de activos
|
||||
5. **PMC-003 Projects** - Proyectos y campañas básicos
|
||||
6. **PMC-004 Generation** - Motor de generación (2-3 workflows)
|
||||
|
||||
### Fase 2 - Automatización
|
||||
|
||||
7. **PMC-005 Automation** - Flujos automatizados
|
||||
|
||||
### Fase 3 - Analytics
|
||||
|
||||
8. **PMC-008 Analytics** - Dashboards y reportes
|
||||
|
||||
---
|
||||
|
||||
## Entidades Compartidas
|
||||
|
||||
| Entidad | Módulo Principal | Módulos que Referencian |
|
||||
|---------|------------------|------------------------|
|
||||
| Tenant | PMC-001 | Todos |
|
||||
| User | PMC-007 | Todos |
|
||||
| Client | PMC-002 | PMC-003, PMC-008 |
|
||||
| Brand | PMC-002 | PMC-003, PMC-004, PMC-006 |
|
||||
| Product | PMC-002 | PMC-004, PMC-006 |
|
||||
| Campaign | PMC-003 | PMC-004, PMC-005, PMC-006, PMC-008 |
|
||||
| Asset | PMC-006 | PMC-002, PMC-003, PMC-004 |
|
||||
| GenerationJob | PMC-004 | PMC-003, PMC-006, PMC-008 |
|
||||
|
||||
---
|
||||
|
||||
## APIs por Módulo
|
||||
|
||||
| Módulo | Base Path | Endpoints Principales |
|
||||
|--------|-----------|----------------------|
|
||||
| Tenants | `/api/v1/tenants` | CRUD tenants, config |
|
||||
| CRM | `/api/v1/crm` | clients, contacts, brands, products, opportunities |
|
||||
| Projects | `/api/v1/projects` | projects, campaigns, briefs |
|
||||
| Generation | `/api/v1/generation` | jobs, workflows, models |
|
||||
| Automation | `/api/v1/automation` | flows, runs, webhooks |
|
||||
| Assets | `/api/v1/assets` | assets, collections, downloads |
|
||||
| Admin | `/api/v1/admin` | users, roles, settings, audit |
|
||||
| Analytics | `/api/v1/analytics` | dashboards, metrics, reports |
|
||||
|
||||
---
|
||||
|
||||
## Conteo de Funcionalidades
|
||||
|
||||
| Módulo | Funcionalidades | Prioridad Alta | Prioridad Media | Prioridad Baja |
|
||||
|--------|-----------------|----------------|-----------------|----------------|
|
||||
| PMC-001 | 10 | 6 | 3 | 1 |
|
||||
| PMC-002 | 18 | 12 | 5 | 1 |
|
||||
| PMC-003 | 16 | 10 | 5 | 1 |
|
||||
| PMC-004 | 20 | 12 | 6 | 2 |
|
||||
| PMC-005 | 12 | 4 | 6 | 2 |
|
||||
| PMC-006 | 18 | 10 | 6 | 2 |
|
||||
| PMC-007 | 14 | 7 | 5 | 2 |
|
||||
| PMC-008 | 10 | 3 | 5 | 2 |
|
||||
| **TOTAL** | **118** | **64** | **41** | **13** |
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [VISION-GENERAL.md](../00-vision-general/VISION-GENERAL.md)
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
- [GLOSARIO.md](../00-vision-general/GLOSARIO.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,512 @@
|
||||
# Requerimientos Funcionales - PMC-001 Tenants
|
||||
|
||||
**Módulo:** Tenants
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-001: Crear Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-001 |
|
||||
| **Nombre** | Crear Tenant |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir crear un nuevo tenant con datos básicos.
|
||||
|
||||
**Precondiciones:**
|
||||
- Usuario autenticado como Super Admin
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string (requerido, 3-100 caracteres)
|
||||
- slug: string (requerido, único, formato URL-safe)
|
||||
- plan_id: UUID (requerido)
|
||||
- settings: object (opcional)
|
||||
- branding: object (opcional)
|
||||
|
||||
**Flujo principal:**
|
||||
1. Super Admin accede a gestión de tenants
|
||||
2. Selecciona "Crear tenant"
|
||||
3. Completa formulario con datos requeridos
|
||||
4. Sistema valida unicidad del slug
|
||||
5. Sistema crea tenant con status "active"
|
||||
6. Sistema crea usuario admin inicial (opcional)
|
||||
7. Sistema retorna tenant creado
|
||||
|
||||
**Postcondiciones:**
|
||||
- Tenant existe en base de datos
|
||||
- Tenant tiene plan asignado
|
||||
- RLS configurado para nuevo tenant
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Validación de slug único funciona
|
||||
- [ ] Tenant se crea con status "active"
|
||||
- [ ] Plan se asocia correctamente
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-002: Editar Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-002 |
|
||||
| **Nombre** | Editar Tenant |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir modificar datos de un tenant existente.
|
||||
|
||||
**Precondiciones:**
|
||||
- Usuario autenticado con permisos de edición
|
||||
- Tenant existe
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string (opcional)
|
||||
- settings: object (opcional)
|
||||
- branding: object (opcional)
|
||||
- limits: object (opcional, solo Super Admin)
|
||||
|
||||
**Flujo principal:**
|
||||
1. Usuario accede a configuración del tenant
|
||||
2. Modifica campos permitidos según rol
|
||||
3. Sistema valida datos
|
||||
4. Sistema actualiza tenant
|
||||
5. Sistema registra cambio en audit log
|
||||
|
||||
**Restricciones:**
|
||||
- Tenant Admin no puede modificar limits ni plan
|
||||
- Slug no es editable después de creación
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Campos se actualizan correctamente
|
||||
- [ ] Permisos por rol se respetan
|
||||
- [ ] Audit log registra cambios
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-003: Suspender Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-003 |
|
||||
| **Nombre** | Suspender Tenant |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir suspender un tenant, bloqueando acceso de usuarios.
|
||||
|
||||
**Precondiciones:**
|
||||
- Usuario autenticado como Super Admin
|
||||
- Tenant existe con status "active"
|
||||
|
||||
**Flujo principal:**
|
||||
1. Super Admin selecciona tenant a suspender
|
||||
2. Sistema solicita confirmación
|
||||
3. Sistema cambia status a "suspended"
|
||||
4. Sistema invalida todas las sesiones del tenant
|
||||
5. Sistema notifica a admins del tenant
|
||||
|
||||
**Postcondiciones:**
|
||||
- Usuarios del tenant no pueden hacer login
|
||||
- Datos permanecen intactos
|
||||
- Jobs pendientes se pausan
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Status cambia a "suspended"
|
||||
- [ ] Login bloqueado para usuarios del tenant
|
||||
- [ ] Sesiones existentes invalidadas
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-004: Reactivar Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-004 |
|
||||
| **Nombre** | Reactivar Tenant |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir reactivar un tenant suspendido.
|
||||
|
||||
**Precondiciones:**
|
||||
- Tenant existe con status "suspended"
|
||||
|
||||
**Flujo principal:**
|
||||
1. Super Admin selecciona tenant suspendido
|
||||
2. Selecciona "Reactivar"
|
||||
3. Sistema cambia status a "active"
|
||||
4. Sistema notifica a admins del tenant
|
||||
|
||||
**Postcondiciones:**
|
||||
- Usuarios pueden hacer login
|
||||
- Jobs pausados se reactivan
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Status cambia a "active"
|
||||
- [ ] Login permitido nuevamente
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-005: Eliminar Tenant (Soft Delete)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-005 |
|
||||
| **Nombre** | Eliminar Tenant |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir eliminar un tenant mediante soft delete.
|
||||
|
||||
**Precondiciones:**
|
||||
- Tenant existe
|
||||
|
||||
**Flujo principal:**
|
||||
1. Super Admin selecciona tenant a eliminar
|
||||
2. Sistema solicita confirmación con texto de verificación
|
||||
3. Sistema marca tenant como eliminado (deleted_at)
|
||||
4. Sistema invalida sesiones
|
||||
5. Sistema programa limpieza de datos (90 días)
|
||||
|
||||
**Postcondiciones:**
|
||||
- Tenant marcado con deleted_at
|
||||
- Datos retenidos por 90 días
|
||||
- Acceso completamente bloqueado
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Soft delete funciona correctamente
|
||||
- [ ] Datos no se eliminan inmediatamente
|
||||
- [ ] Tenant no aparece en listados
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-006: Listar Tenants
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-006 |
|
||||
| **Nombre** | Listar Tenants |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir listar todos los tenants con filtros y paginación.
|
||||
|
||||
**Datos de entrada (query params):**
|
||||
- status: string (filtro por estado)
|
||||
- plan_id: UUID (filtro por plan)
|
||||
- search: string (búsqueda por nombre)
|
||||
- page: number
|
||||
- limit: number (max 100)
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de tenants con datos básicos
|
||||
- Total de registros
|
||||
- Información de paginación
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Paginación funciona correctamente
|
||||
- [ ] Filtros se aplican correctamente
|
||||
- [ ] Búsqueda por nombre funciona
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-007: Ver Detalle de Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-007 |
|
||||
| **Nombre** | Ver Detalle de Tenant |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Super Admin, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe mostrar información detallada de un tenant.
|
||||
|
||||
**Datos de salida:**
|
||||
- Datos básicos del tenant
|
||||
- Plan asociado con límites
|
||||
- Configuración (settings)
|
||||
- Branding
|
||||
- Estadísticas de uso
|
||||
- Usuarios activos (count)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Todos los datos se muestran correctamente
|
||||
- [ ] Tenant Admin solo ve su propio tenant
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-008: Configurar Branding
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-008 |
|
||||
| **Nombre** | Configurar Branding |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir personalizar el branding del tenant.
|
||||
|
||||
**Datos de entrada:**
|
||||
- logo_url: string (URL o upload)
|
||||
- primary_color: string (hex color)
|
||||
- secondary_color: string (hex color)
|
||||
- favicon_url: string (opcional)
|
||||
|
||||
**Flujo principal:**
|
||||
1. Admin accede a configuración de branding
|
||||
2. Sube logo o proporciona URL
|
||||
3. Selecciona colores
|
||||
4. Sistema valida formatos
|
||||
5. Sistema actualiza branding
|
||||
6. Cambios se reflejan en UI
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Logo se almacena/referencia correctamente
|
||||
- [ ] Colores se aplican en UI
|
||||
- [ ] Preview disponible antes de guardar
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-009: Configurar Límites Personalizados
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-009 |
|
||||
| **Nombre** | Configurar Límites Personalizados |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir sobreescribir límites del plan para un tenant específico.
|
||||
|
||||
**Datos de entrada:**
|
||||
- generations_per_month: number (null = usar plan)
|
||||
- storage_gb: number (null = usar plan)
|
||||
- users_max: number (null = usar plan)
|
||||
- custom_limits: object
|
||||
|
||||
**Flujo principal:**
|
||||
1. Super Admin accede a límites del tenant
|
||||
2. Modifica valores específicos
|
||||
3. Sistema valida que valores sean >= 0
|
||||
4. Sistema guarda límites personalizados
|
||||
5. Límites se aplican sobre los del plan
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Límites personalizados sobreescriben plan
|
||||
- [ ] Valores null usan defaults del plan
|
||||
- [ ] Cambios se aplican inmediatamente
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-010: Obtener Tenant Actual
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-010 |
|
||||
| **Nombre** | Obtener Tenant Actual |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Usuario autenticado |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe proporcionar los datos del tenant del usuario actual.
|
||||
|
||||
**Flujo principal:**
|
||||
1. Usuario hace request a /tenants/current
|
||||
2. Sistema extrae tenant_id del JWT
|
||||
3. Sistema retorna datos del tenant
|
||||
|
||||
**Datos de salida:**
|
||||
- Datos básicos del tenant
|
||||
- Plan con límites efectivos
|
||||
- Branding
|
||||
- Settings relevantes para el usuario
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Endpoint retorna tenant correcto
|
||||
- [ ] Límites efectivos calculados correctamente
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-011: Validar Cuota de Uso
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-011 |
|
||||
| **Nombre** | Validar Cuota de Uso |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe validar cuotas antes de operaciones que consumen recursos.
|
||||
|
||||
**Operaciones validadas:**
|
||||
- Generación de imágenes
|
||||
- Entrenamiento de modelos
|
||||
- Subida de archivos (storage)
|
||||
- Creación de usuarios
|
||||
|
||||
**Flujo principal:**
|
||||
1. Usuario solicita operación
|
||||
2. Sistema obtiene límites del tenant
|
||||
3. Sistema obtiene uso actual
|
||||
4. Sistema compara uso vs límite
|
||||
5. Si excede: rechaza con error específico
|
||||
6. Si no excede: permite operación
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Validación ocurre antes de cada operación
|
||||
- [ ] Mensaje de error indica límite y uso actual
|
||||
- [ ] Operaciones no se ejecutan si exceden límite
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-012: Aplicar RLS por Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-012 |
|
||||
| **Nombre** | Aplicar RLS por Tenant |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe garantizar aislamiento de datos entre tenants mediante RLS.
|
||||
|
||||
**Implementación:**
|
||||
1. Todas las tablas principales tienen columna tenant_id
|
||||
2. Políticas RLS filtran por tenant_id
|
||||
3. Middleware inyecta tenant_id en cada request
|
||||
4. SET app.current_tenant ejecutado antes de queries
|
||||
|
||||
**Tablas afectadas:**
|
||||
- clients, contacts, brands, products
|
||||
- projects, campaigns
|
||||
- assets, collections
|
||||
- users, roles
|
||||
- generation_jobs, custom_models
|
||||
- automation_flows, automation_runs
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Queries solo retornan datos del tenant actual
|
||||
- [ ] INSERT automáticamente incluye tenant_id
|
||||
- [ ] No es posible acceder a datos de otro tenant
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-013: Gestionar Planes
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-013 |
|
||||
| **Nombre** | Gestionar Planes |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir crear y gestionar planes de suscripción.
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string
|
||||
- code: string (único)
|
||||
- features: object (funcionalidades habilitadas)
|
||||
- limits: object (cuotas)
|
||||
- price_monthly: decimal
|
||||
- price_yearly: decimal
|
||||
- is_active: boolean
|
||||
|
||||
**Operaciones:**
|
||||
- Crear plan
|
||||
- Editar plan
|
||||
- Activar/desactivar plan
|
||||
- Ver tenants por plan
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] CRUD de planes funciona
|
||||
- [ ] Planes inactivos no asignables a nuevos tenants
|
||||
- [ ] Cambio de plan en tenant actualiza límites
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-014: Cambiar Plan de Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-014 |
|
||||
| **Nombre** | Cambiar Plan de Tenant |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir cambiar el plan de un tenant.
|
||||
|
||||
**Flujo principal:**
|
||||
1. Super Admin selecciona tenant
|
||||
2. Selecciona nuevo plan
|
||||
3. Sistema valida compatibilidad
|
||||
4. Sistema actualiza plan_id
|
||||
5. Nuevos límites se aplican inmediatamente
|
||||
6. Sistema notifica a admins del tenant
|
||||
|
||||
**Validaciones:**
|
||||
- Si downgrade: verificar que uso actual no exceda nuevos límites
|
||||
- Warning si usuarios exceden nuevo límite
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Plan se actualiza correctamente
|
||||
- [ ] Límites se aplican inmediatamente
|
||||
- [ ] Warnings apropiados en downgrade
|
||||
|
||||
---
|
||||
|
||||
## RF-PMC-001-015: Ver Uso del Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-001-015 |
|
||||
| **Nombre** | Ver Uso del Tenant |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe mostrar métricas de uso del tenant.
|
||||
|
||||
**Datos de salida:**
|
||||
- Generaciones: usado/límite
|
||||
- Storage: usado/límite (GB)
|
||||
- Usuarios: activos/límite
|
||||
- Entrenamientos: usado/límite
|
||||
- Período de facturación actual
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Métricas se calculan correctamente
|
||||
- [ ] Porcentajes y gráficos visuales
|
||||
- [ ] Alertas cuando se acerca al límite (>80%)
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Prioridad | Cantidad |
|
||||
|-----------|----------|
|
||||
| P1 | 6 |
|
||||
| P2 | 6 |
|
||||
| P3 | 3 |
|
||||
| **Total** | **15** |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,587 @@
|
||||
# Requerimientos Funcionales - PMC-002 CRM
|
||||
|
||||
**Módulo:** CRM
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Clientes
|
||||
|
||||
### RF-PMC-002-001: Crear Cliente
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-001 |
|
||||
| **Nombre** | Crear Cliente |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Analyst, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir registrar un nuevo cliente de la agencia.
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string (requerido)
|
||||
- legal_name: string (opcional)
|
||||
- tax_id: string (opcional)
|
||||
- industry: string (opcional)
|
||||
- size: enum (opcional)
|
||||
- website: string (opcional)
|
||||
- notes: text (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Cliente se crea con status "prospect"
|
||||
- [ ] tenant_id se asigna automáticamente
|
||||
- [ ] Validación de datos funciona
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-002: Editar Cliente
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-002 |
|
||||
| **Nombre** | Editar Cliente |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Analyst, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir modificar datos de un cliente existente.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Todos los campos editables se actualizan
|
||||
- [ ] Cambio de status registra historial
|
||||
- [ ] Audit log registra modificaciones
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-003: Listar Clientes
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-003 |
|
||||
| **Nombre** | Listar Clientes |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe mostrar lista de clientes con filtros y paginación.
|
||||
|
||||
**Filtros disponibles:**
|
||||
- status: prospect, active, inactive, churned
|
||||
- industry: string
|
||||
- size: enum
|
||||
- search: nombre o legal_name
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Paginación funciona correctamente
|
||||
- [ ] Filtros se combinan con AND
|
||||
- [ ] Ordenamiento por nombre/fecha
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-004: Ver Ficha de Cliente
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-004 |
|
||||
| **Nombre** | Ver Ficha de Cliente |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe mostrar vista detallada del cliente con información relacionada.
|
||||
|
||||
**Datos mostrados:**
|
||||
- Información básica
|
||||
- Contactos asociados
|
||||
- Marcas del cliente
|
||||
- Proyectos activos
|
||||
- Oportunidades abiertas
|
||||
- Historial de actividad
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Tabs organizan información
|
||||
- [ ] Datos relacionados cargan correctamente
|
||||
- [ ] Acciones rápidas disponibles
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-005: Eliminar Cliente
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-005 |
|
||||
| **Nombre** | Eliminar Cliente |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe permitir eliminar un cliente (soft delete).
|
||||
|
||||
**Validaciones:**
|
||||
- No tiene proyectos activos
|
||||
- Confirmación requerida
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Soft delete funciona
|
||||
- [ ] Validaciones impiden eliminación si hay dependencias activas
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Contactos
|
||||
|
||||
### RF-PMC-002-006: Crear Contacto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-006 |
|
||||
| **Nombre** | Crear Contacto |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Analyst, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- client_id: UUID (requerido)
|
||||
- first_name: string (requerido)
|
||||
- last_name: string (requerido)
|
||||
- email: string (requerido)
|
||||
- phone: string (opcional)
|
||||
- position: string (opcional)
|
||||
- is_primary: boolean (default false)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Contacto se asocia al cliente
|
||||
- [ ] Email validado (formato)
|
||||
- [ ] Solo un contacto primario por cliente
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-007: Editar Contacto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-007 |
|
||||
| **Nombre** | Editar Contacto |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Analyst, Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Campos se actualizan correctamente
|
||||
- [ ] Cambio de is_primary actualiza otros contactos
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-008: Listar Contactos
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-008 |
|
||||
| **Nombre** | Listar Contactos |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Descripción:**
|
||||
Listar contactos del tenant o de un cliente específico.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Filtro por cliente funciona
|
||||
- [ ] Búsqueda por nombre/email
|
||||
- [ ] Paginación implementada
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-009: Marcar Contacto Primario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-009 |
|
||||
| **Nombre** | Marcar Contacto Primario |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Analyst, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Designar un contacto como principal del cliente.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Solo un contacto primario por cliente
|
||||
- [ ] Al marcar uno, otros se desmarcan automáticamente
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Marcas
|
||||
|
||||
### RF-PMC-002-010: Crear Marca
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-010 |
|
||||
| **Nombre** | Crear Marca |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- client_id: UUID (requerido)
|
||||
- name: string (requerido)
|
||||
- description: text (opcional)
|
||||
- identity: object (opcional)
|
||||
- logo_url
|
||||
- primary_color
|
||||
- secondary_colors
|
||||
- typography
|
||||
- tone_of_voice
|
||||
- keywords
|
||||
- forbidden_words
|
||||
- visual_style
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Marca se asocia al cliente
|
||||
- [ ] Identity se almacena como JSONB
|
||||
- [ ] Logo se puede subir o referenciar URL
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-011: Editar Marca
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-011 |
|
||||
| **Nombre** | Editar Marca |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Todos los campos de identity editables
|
||||
- [ ] Preview de colores en tiempo real
|
||||
- [ ] Cambios se propagan a nuevas generaciones
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-012: Definir Identidad Visual
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-012 |
|
||||
| **Nombre** | Definir Identidad Visual |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Configurar todos los elementos de identidad visual de la marca.
|
||||
|
||||
**Campos de identidad:**
|
||||
- Logo (principal y variaciones)
|
||||
- Paleta de colores
|
||||
- Tipografías
|
||||
- Tono de voz
|
||||
- Keywords positivas
|
||||
- Palabras prohibidas
|
||||
- Estilo visual preferido
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Formulario estructurado para cada sección
|
||||
- [ ] Upload de logos funciona
|
||||
- [ ] Preview visual de paleta de colores
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-013: Asociar LoRA a Marca
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-013 |
|
||||
| **Nombre** | Asociar LoRA a Marca |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Vincular modelos LoRA entrenados con una marca.
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario accede a configuración de marca
|
||||
2. Selecciona "Modelos IA"
|
||||
3. Elige LoRA(s) del catálogo del tenant
|
||||
4. Sistema vincula LoRA con marca
|
||||
5. LoRA se usa automáticamente en generaciones de la marca
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Múltiples LoRAs pueden asociarse
|
||||
- [ ] LoRA se aplica por defecto en generación
|
||||
- [ ] Desasociación funciona
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-014: Listar Marcas
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-014 |
|
||||
| **Nombre** | Listar Marcas |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Filtro por cliente funciona
|
||||
- [ ] Preview de logo/colores en lista
|
||||
- [ ] Búsqueda por nombre
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Productos
|
||||
|
||||
### RF-PMC-002-015: Crear Producto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-015 |
|
||||
| **Nombre** | Crear Producto |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Analyst |
|
||||
|
||||
**Datos de entrada:**
|
||||
- brand_id: UUID (requerido)
|
||||
- name: string (requerido)
|
||||
- sku: string (opcional)
|
||||
- description: text (opcional)
|
||||
- category: string (opcional)
|
||||
- attributes: object (precio, features, etc.)
|
||||
- reference_images: array[file] (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Producto se asocia a marca
|
||||
- [ ] Imágenes de referencia se almacenan
|
||||
- [ ] Status inicial "active"
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-016: Editar Producto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-016 |
|
||||
| **Nombre** | Editar Producto |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Analyst |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Campos se actualizan correctamente
|
||||
- [ ] Imágenes de referencia pueden agregarse/quitarse
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-017: Subir Imágenes de Referencia
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-017 |
|
||||
| **Nombre** | Subir Imágenes de Referencia |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Analyst |
|
||||
|
||||
**Descripción:**
|
||||
Agregar fotos reales del producto para usar como referencia en generación.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Upload múltiple funciona
|
||||
- [ ] Formatos: JPG, PNG, WebP
|
||||
- [ ] Tamaño máximo: 10MB por imagen
|
||||
- [ ] Thumbnails generados automáticamente
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-018: Trigger Generación desde Producto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-018 |
|
||||
| **Nombre** | Trigger Generación desde Producto |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Descripción:**
|
||||
Iniciar generación de contenido directamente desde la ficha de producto.
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario ve ficha de producto
|
||||
2. Selecciona "Generar contenido"
|
||||
3. Elige workflow template
|
||||
4. Configura opciones (cantidad, estilo)
|
||||
5. Sistema crea job de generación
|
||||
6. Sistema redirige a monitor o muestra progreso
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Datos del producto se cargan al job
|
||||
- [ ] Identidad de marca se incluye
|
||||
- [ ] LoRAs se aplican automáticamente
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-019: Listar Productos
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-019 |
|
||||
| **Nombre** | Listar Productos |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Filtro por marca funciona
|
||||
- [ ] Vista de catálogo con imágenes
|
||||
- [ ] Búsqueda por nombre/SKU
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Oportunidades
|
||||
|
||||
### RF-PMC-002-020: Crear Oportunidad
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-020 |
|
||||
| **Nombre** | Crear Oportunidad |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Analyst, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- client_id: UUID (requerido)
|
||||
- name: string (requerido)
|
||||
- description: text (opcional)
|
||||
- value: decimal (opcional)
|
||||
- currency: string (default: USD)
|
||||
- expected_close_date: date (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Stage inicial "lead"
|
||||
- [ ] Probability calculada por stage
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-021: Mover Oportunidad en Pipeline
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-021 |
|
||||
| **Nombre** | Mover Oportunidad en Pipeline |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Analyst, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Cambiar stage de una oportunidad mediante drag & drop o acción directa.
|
||||
|
||||
**Stages:**
|
||||
- lead → qualified → proposal → negotiation → won/lost
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Drag & drop en vista Kanban
|
||||
- [ ] Probability se actualiza automáticamente
|
||||
- [ ] Registro de historial de cambios
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-022: Cerrar Oportunidad como Ganada
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-022 |
|
||||
| **Nombre** | Cerrar Oportunidad como Ganada |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Analyst, Tenant Admin |
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario mueve a stage "won"
|
||||
2. Sistema solicita valor final confirmado
|
||||
3. Sistema marca como ganada
|
||||
4. Sistema ofrece crear proyecto
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] actual_close_date se registra
|
||||
- [ ] Opción de crear proyecto disponible
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-023: Cerrar Oportunidad como Perdida
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-023 |
|
||||
| **Nombre** | Cerrar Oportunidad como Perdida |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Analyst, Tenant Admin |
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario mueve a stage "lost"
|
||||
2. Sistema solicita motivo de pérdida
|
||||
3. Sistema registra lost_reason
|
||||
4. Sistema marca como perdida
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] lost_reason es requerido
|
||||
- [ ] Oportunidad no editable después de cerrar
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-024: Convertir Oportunidad en Proyecto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-024 |
|
||||
| **Nombre** | Convertir Oportunidad en Proyecto |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Analyst, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Crear proyecto automáticamente desde oportunidad ganada.
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario selecciona "Convertir a proyecto"
|
||||
2. Sistema muestra formulario pre-llenado
|
||||
3. Usuario confirma/modifica datos
|
||||
4. Sistema crea proyecto vinculado al cliente
|
||||
5. Sistema vincula oportunidad con proyecto
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Datos del cliente se heredan
|
||||
- [ ] Descripción de oportunidad se copia a proyecto
|
||||
- [ ] Relación bidireccional establecida
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-002-025: Vista Kanban de Oportunidades
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-002-025 |
|
||||
| **Nombre** | Vista Kanban de Oportunidades |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Analyst, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Mostrar pipeline de oportunidades en formato Kanban.
|
||||
|
||||
**Características:**
|
||||
- Columnas por stage
|
||||
- Cards con info resumida
|
||||
- Drag & drop entre columnas
|
||||
- Filtros por cliente, fecha, valor
|
||||
- Totales por columna
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Drag & drop funciona
|
||||
- [ ] Totales se calculan correctamente
|
||||
- [ ] Filtros se aplican en tiempo real
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Prioridad | Cantidad |
|
||||
|-----------|----------|
|
||||
| P1 | 14 |
|
||||
| P2 | 10 |
|
||||
| P3 | 1 |
|
||||
| **Total** | **25** |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,503 @@
|
||||
# Requerimientos Funcionales - PMC-003 Projects
|
||||
|
||||
**Módulo:** Projects
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Proyectos
|
||||
|
||||
### RF-PMC-003-001: Crear Proyecto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-001 |
|
||||
| **Nombre** | Crear Proyecto |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- client_id: UUID (requerido)
|
||||
- name: string (requerido)
|
||||
- description: text (opcional)
|
||||
- start_date: date (opcional)
|
||||
- end_date: date (opcional)
|
||||
- budget: decimal (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Proyecto se crea con status "draft"
|
||||
- [ ] Código autogenerado (PRJ-YYYY-XXX)
|
||||
- [ ] Owner asignado al creador
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-002: Editar Proyecto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-002 |
|
||||
| **Nombre** | Editar Proyecto |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Project Owner, Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Campos básicos editables
|
||||
- [ ] Cambio de cliente requiere confirmación
|
||||
- [ ] Historial de cambios registrado
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-003: Cambiar Estado de Proyecto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-003 |
|
||||
| **Nombre** | Cambiar Estado de Proyecto |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Project Owner, Tenant Admin |
|
||||
|
||||
**Estados válidos:**
|
||||
- draft → active
|
||||
- active → on_hold, completed
|
||||
- on_hold → active
|
||||
- cualquiera → cancelled
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Transiciones válidas se permiten
|
||||
- [ ] Transiciones inválidas se bloquean
|
||||
- [ ] Notificación al equipo en cambio de estado
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-004: Asignar Equipo a Proyecto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-004 |
|
||||
| **Nombre** | Asignar Equipo a Proyecto |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Project Owner, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Agregar o quitar miembros del equipo de un proyecto.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Usuarios del tenant pueden asignarse
|
||||
- [ ] Owner siempre está en el equipo
|
||||
- [ ] Notificación al agregar miembro
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-005: Listar Proyectos
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-005 |
|
||||
| **Nombre** | Listar Proyectos |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Filtros:**
|
||||
- client_id
|
||||
- status
|
||||
- owner_id
|
||||
- date_range
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Paginación funciona
|
||||
- [ ] Filtros combinables
|
||||
- [ ] Vista lista y tarjetas
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-006: Ver Dashboard de Proyecto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-006 |
|
||||
| **Nombre** | Ver Dashboard de Proyecto |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Miembros del proyecto |
|
||||
|
||||
**Información mostrada:**
|
||||
- Resumen del proyecto
|
||||
- Campañas con estado
|
||||
- Assets recientes
|
||||
- Actividad del equipo
|
||||
- Métricas de progreso
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Dashboard carga correctamente
|
||||
- [ ] Métricas calculadas en tiempo real
|
||||
- [ ] Acciones rápidas disponibles
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Campañas
|
||||
|
||||
### RF-PMC-003-007: Crear Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-007 |
|
||||
| **Nombre** | Crear Campaña |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- project_id: UUID (requerido)
|
||||
- brand_id: UUID (requerido)
|
||||
- name: string (requerido)
|
||||
- type: enum (requerido)
|
||||
- channels: array[string]
|
||||
- start_date: date (opcional)
|
||||
- end_date: date (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Campaña se crea con status "draft"
|
||||
- [ ] Brand se vincula correctamente
|
||||
- [ ] Brief vacío se inicializa
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-008: Editar Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-008 |
|
||||
| **Nombre** | Editar Campaña |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Campos básicos editables en cualquier estado
|
||||
- [ ] Brief editable hasta "in_production"
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-009: Completar Brief de Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-009 |
|
||||
| **Nombre** | Completar Brief de Campaña |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Secciones del Brief:**
|
||||
- Objetivo: descripción, KPIs
|
||||
- Audiencia: demographics, psychographics, pain_points
|
||||
- Mensajes: main_message, tone, CTA, hashtags
|
||||
- Visual: style, mood, color_palette, references
|
||||
- Restricciones: forbidden_words, disclaimers
|
||||
- Entregables: formatos, cantidades
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Formulario con secciones colapsables
|
||||
- [ ] Validación de campos mínimos
|
||||
- [ ] Guardado automático (draft)
|
||||
- [ ] Al completar: estado → "briefing"
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-010: Usar Plantilla de Brief
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-010 |
|
||||
| **Nombre** | Usar Plantilla de Brief |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Descripción:**
|
||||
Cargar brief desde una plantilla predefinida.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Listado de plantillas disponibles
|
||||
- [ ] Preview de plantilla antes de aplicar
|
||||
- [ ] Campos se pre-llenan pero son editables
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-011: Cambiar Estado de Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-011 |
|
||||
| **Nombre** | Cambiar Estado de Campaña |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Estados y transiciones:**
|
||||
```
|
||||
draft → briefing → in_production → review → approved → published
|
||||
↑_______________|
|
||||
(revision_requested)
|
||||
```
|
||||
|
||||
**Validaciones:**
|
||||
- briefing → in_production: brief mínimo completado
|
||||
- review → approved: al menos 1 asset aprobado
|
||||
- approved → published: confirmación requerida
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Transiciones válidas funcionan
|
||||
- [ ] Validaciones se aplican
|
||||
- [ ] Notificaciones en cada cambio
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-012: Listar Campañas
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-012 |
|
||||
| **Nombre** | Listar Campañas |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Vistas:**
|
||||
- Lista con filtros
|
||||
- Kanban por estado
|
||||
- Calendario por fechas
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Filtro por proyecto, brand, status, type
|
||||
- [ ] Vista Kanban con drag & drop
|
||||
- [ ] Búsqueda por nombre
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-013: Ver Detalle de Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-013 |
|
||||
| **Nombre** | Ver Detalle de Campaña |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Miembros del proyecto |
|
||||
|
||||
**Información mostrada:**
|
||||
- Datos de campaña
|
||||
- Brief completo
|
||||
- Assets generados con estados
|
||||
- Historial de actividad
|
||||
- Jobs de generación
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Todas las secciones visibles
|
||||
- [ ] Assets organizados por estado
|
||||
- [ ] Acciones contextuales disponibles
|
||||
|
||||
---
|
||||
|
||||
## Generación de Contenido
|
||||
|
||||
### RF-PMC-003-014: Lanzar Generación desde Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-014 |
|
||||
| **Nombre** | Lanzar Generación desde Campaña |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario abre campaña en status "briefing" o superior
|
||||
2. Selecciona "Generar contenido"
|
||||
3. Elige workflows según deliverables del brief
|
||||
4. Configura opciones adicionales
|
||||
5. Sistema crea jobs de generación
|
||||
6. Sistema cambia status a "in_production" si no lo está
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Brief se usa para configurar generación
|
||||
- [ ] Múltiples workflows ejecutables
|
||||
- [ ] Progreso visible en tiempo real
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-015: Seleccionar Workflows de Generación
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-015 |
|
||||
| **Nombre** | Seleccionar Workflows de Generación |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Descripción:**
|
||||
Elegir qué workflows ejecutar basado en deliverables del brief.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Workflows sugeridos según tipo de campaña
|
||||
- [ ] Preview de cada workflow
|
||||
- [ ] Configuración de cantidad por workflow
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-016: Monitorear Progreso de Generación
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-016 |
|
||||
| **Nombre** | Monitorear Progreso de Generación |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Información mostrada:**
|
||||
- Jobs en cola
|
||||
- Jobs procesando (con %)
|
||||
- Jobs completados
|
||||
- Jobs fallidos
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Actualización en tiempo real (websocket)
|
||||
- [ ] Posibilidad de cancelar jobs pendientes
|
||||
- [ ] Reintentar jobs fallidos
|
||||
|
||||
---
|
||||
|
||||
## Flujo de Aprobación
|
||||
|
||||
### RF-PMC-003-017: Aprobar Asset de Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-017 |
|
||||
| **Nombre** | Aprobar Asset de Campaña |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario revisa asset en vista de revisión
|
||||
2. Selecciona "Aprobar"
|
||||
3. Sistema marca asset como aprobado
|
||||
4. Sistema registra aprobador y timestamp
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Asset cambia a status "approved"
|
||||
- [ ] Metadata de aprobación registrada
|
||||
- [ ] No editable después de aprobar
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-018: Rechazar Asset de Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-018 |
|
||||
| **Nombre** | Rechazar Asset de Campaña |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario revisa asset
|
||||
2. Selecciona "Rechazar"
|
||||
3. Sistema solicita feedback obligatorio
|
||||
4. Sistema marca asset como rechazado
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Feedback es requerido
|
||||
- [ ] Asset puede regenerarse
|
||||
- [ ] Historial de rechazos visible
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-019: Solicitar Revisión de Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-019 |
|
||||
| **Nombre** | Solicitar Revisión de Asset |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Descripción:**
|
||||
Pedir cambios específicos en un asset antes de decidir.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Comentario con posición en imagen
|
||||
- [ ] Asset queda en "revision_requested"
|
||||
- [ ] Notificación a equipo
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-020: Aprobar Todos los Assets
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-020 |
|
||||
| **Nombre** | Aprobar Todos los Assets |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Acción bulk para aprobar todos los assets pendientes.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Confirmación requerida
|
||||
- [ ] Solo assets en "pending_review"
|
||||
- [ ] Registro individual por asset
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-021: Regenerar Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-021 |
|
||||
| **Nombre** | Regenerar Asset |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Descripción:**
|
||||
Crear nueva versión de un asset rechazado o en revisión.
|
||||
|
||||
**Flujo:**
|
||||
1. Usuario selecciona asset rechazado
|
||||
2. Selecciona "Regenerar"
|
||||
3. Opcionalmente ajusta parámetros
|
||||
4. Sistema crea nuevo job
|
||||
5. Nuevo asset se vincula como versión
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Parámetros originales pre-cargados
|
||||
- [ ] Historial de versiones mantenido
|
||||
- [ ] Original no se elimina
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-003-022: Descargar Assets Aprobados
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-003-022 |
|
||||
| **Nombre** | Descargar Assets Aprobados |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Descargar pack de todos los assets aprobados de la campaña.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] ZIP generado con estructura organizada
|
||||
- [ ] Solo assets aprobados incluidos
|
||||
- [ ] Copys incluidos como .txt
|
||||
- [ ] Opción de selección manual
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Prioridad | Cantidad |
|
||||
|-----------|----------|
|
||||
| P1 | 15 |
|
||||
| P2 | 7 |
|
||||
| **Total** | **22** |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,641 @@
|
||||
# Requerimientos Funcionales - PMC-004 Generation
|
||||
|
||||
**Módulo:** Generation (Motor IA)
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Generación de Imágenes
|
||||
|
||||
### RF-PMC-004-001: Generar Imagen Text-to-Image
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-001 |
|
||||
| **Nombre** | Generar Imagen Text-to-Image |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- prompt: string (requerido)
|
||||
- negative_prompt: string (opcional)
|
||||
- width: number (default: 1024)
|
||||
- height: number (default: 1024)
|
||||
- seed: number (opcional, aleatorio si no se especifica)
|
||||
- steps: number (default: 30)
|
||||
- cfg_scale: number (default: 7.5)
|
||||
- lora_id: UUID (opcional)
|
||||
- brand_id: UUID (opcional, para cargar LoRA automáticamente)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Imagen se genera correctamente
|
||||
- [ ] Parámetros se aplican
|
||||
- [ ] LoRA se carga si especificado
|
||||
- [ ] Asset se crea automáticamente
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-002: Generar Imagen Image-to-Image
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-002 |
|
||||
| **Nombre** | Generar Imagen Image-to-Image |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- input_image: file/URL (requerido)
|
||||
- prompt: string (requerido)
|
||||
- strength: number (0.0-1.0, default: 0.75)
|
||||
- Otros parámetros de T2I
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Imagen input se procesa correctamente
|
||||
- [ ] Strength afecta resultado
|
||||
- [ ] Formatos soportados: PNG, JPG, WebP
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-003: Aplicar Inpainting
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-003 |
|
||||
| **Nombre** | Aplicar Inpainting |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- input_image: file/URL (requerido)
|
||||
- mask_image: file (requerido)
|
||||
- prompt: string (requerido)
|
||||
- Otros parámetros de generación
|
||||
|
||||
**Descripción:**
|
||||
Regenerar solo la parte enmascarada de una imagen.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Máscara define área a regenerar
|
||||
- [ ] Resto de imagen preservado
|
||||
- [ ] Editor de máscara en UI
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-004: Aplicar Upscaling
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-004 |
|
||||
| **Nombre** | Aplicar Upscaling |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- input_image: file/URL (requerido)
|
||||
- scale: number (2 o 4)
|
||||
- model: string (default: "RealESRGAN")
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Imagen se escala correctamente
|
||||
- [ ] Calidad mejorada, no solo interpolación
|
||||
- [ ] Formatos de salida preservados
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-005: Generar Batch de Imágenes
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-005 |
|
||||
| **Nombre** | Generar Batch de Imágenes |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- base_params: object (parámetros comunes)
|
||||
- count: number (cantidad a generar)
|
||||
- variation_type: string (seed, prompt_variation)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] N imágenes generadas
|
||||
- [ ] Seeds diferentes por imagen
|
||||
- [ ] Todas vinculadas al mismo job
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-006: Remover Fondo
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-006 |
|
||||
| **Nombre** | Remover Fondo |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- input_image: file/URL (requerido)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Fondo removido correctamente
|
||||
- [ ] Salida PNG con transparencia
|
||||
- [ ] Bordes suaves en objetos
|
||||
|
||||
---
|
||||
|
||||
## Generación de Texto
|
||||
|
||||
### RF-PMC-004-007: Generar Copy Publicitario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-007 |
|
||||
| **Nombre** | Generar Copy Publicitario |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- context: object
|
||||
- product_name: string
|
||||
- product_description: string
|
||||
- brand_tone: string
|
||||
- target_audience: string
|
||||
- objective: string
|
||||
- type: enum (title, description, cta, full_post)
|
||||
- max_length: number (opcional)
|
||||
- variations: number (default: 3)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Texto generado coherente
|
||||
- [ ] Tono respeta brand guidelines
|
||||
- [ ] Múltiples variaciones disponibles
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-008: Generar Hashtags
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-008 |
|
||||
| **Nombre** | Generar Hashtags |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- context: string (descripción del contenido)
|
||||
- count: number (default: 10)
|
||||
- platform: string (instagram, twitter, linkedin)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Hashtags relevantes generados
|
||||
- [ ] Formato correcto (#hashtag)
|
||||
- [ ] Sin espacios ni caracteres inválidos
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-009: Adaptar Tono de Texto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-009 |
|
||||
| **Nombre** | Adaptar Tono de Texto |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- input_text: string (requerido)
|
||||
- target_tone: string (formal, casual, playful, professional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Mensaje preservado, tono cambiado
|
||||
- [ ] Opciones de tono claras
|
||||
|
||||
---
|
||||
|
||||
## Workflows
|
||||
|
||||
### RF-PMC-004-010: Listar Workflow Templates
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-010 |
|
||||
| **Nombre** | Listar Workflow Templates |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de workflows disponibles
|
||||
- Por cada uno: nombre, descripción, tipo, inputs requeridos
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Workflows de sistema incluidos
|
||||
- [ ] Workflows custom del tenant incluidos
|
||||
- [ ] Filtro por tipo/categoría
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-011: Ver Detalle de Workflow
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-011 |
|
||||
| **Nombre** | Ver Detalle de Workflow |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de salida:**
|
||||
- Descripción completa
|
||||
- Inputs requeridos y opcionales
|
||||
- Outputs esperados
|
||||
- Ejemplos de resultado
|
||||
- Tiempo estimado
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Schema de inputs documentado
|
||||
- [ ] Ejemplos visuales disponibles
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-012: Ejecutar Workflow
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-012 |
|
||||
| **Nombre** | Ejecutar Workflow |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- workflow_id: UUID (requerido)
|
||||
- inputs: object (según schema del workflow)
|
||||
- campaign_id: UUID (opcional)
|
||||
- brand_id: UUID (opcional)
|
||||
|
||||
**Flujo:**
|
||||
1. Sistema valida inputs contra schema
|
||||
2. Sistema verifica cuotas del tenant
|
||||
3. Sistema crea GenerationJob
|
||||
4. Sistema encola job
|
||||
5. Sistema retorna job_id
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Validación de inputs funciona
|
||||
- [ ] Job se crea correctamente
|
||||
- [ ] ID retornado para tracking
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-013: Crear Workflow Template (Admin)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-013 |
|
||||
| **Nombre** | Crear Workflow Template |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string
|
||||
- description: text
|
||||
- type: enum
|
||||
- comfyui_workflow: JSON
|
||||
- input_schema: JSON
|
||||
- output_config: object
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Workflow se almacena correctamente
|
||||
- [ ] Schema de inputs validado
|
||||
- [ ] Disponible para usuarios del tenant
|
||||
|
||||
---
|
||||
|
||||
## Modelos Personalizados
|
||||
|
||||
### RF-PMC-004-014: Listar Modelos Custom
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-014 |
|
||||
| **Nombre** | Listar Modelos Custom |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de LoRAs, checkpoints disponibles
|
||||
- Status de cada uno
|
||||
- Brand asociada (si aplica)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Solo modelos del tenant
|
||||
- [ ] Filtro por tipo y brand
|
||||
- [ ] Preview images mostradas
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-015: Registrar Modelo Custom
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-015 |
|
||||
| **Nombre** | Registrar Modelo Custom |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string (requerido)
|
||||
- type: enum (lora, checkpoint, embedding)
|
||||
- file: upload (requerido)
|
||||
- purpose: string
|
||||
- trigger_word: string (para LoRAs)
|
||||
- brand_id: UUID (opcional)
|
||||
- preview_images: array[file]
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Archivo subido a storage
|
||||
- [ ] Registro en BD
|
||||
- [ ] Disponible para generación
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-016: Eliminar Modelo Custom
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-016 |
|
||||
| **Nombre** | Eliminar Modelo Custom |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Confirmación requerida
|
||||
- [ ] Archivo eliminado de storage
|
||||
- [ ] Registro eliminado de BD
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-017: Iniciar Entrenamiento de LoRA
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-017 |
|
||||
| **Nombre** | Iniciar Entrenamiento de LoRA |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string
|
||||
- training_images: array[file] (mínimo 10)
|
||||
- base_model: string
|
||||
- steps: number (default: 1000)
|
||||
- learning_rate: number (default: 0.0001)
|
||||
- trigger_word: string (requerido)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Mínimo 10 imágenes validado
|
||||
- [ ] Job de entrenamiento creado
|
||||
- [ ] Status actualizado durante entrenamiento
|
||||
- [ ] Modelo disponible al completar
|
||||
|
||||
---
|
||||
|
||||
## Cola de Tareas
|
||||
|
||||
### RF-PMC-004-018: Ver Estado de Cola
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-018 |
|
||||
| **Nombre** | Ver Estado de Cola |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Jobs en cola (pending)
|
||||
- Jobs procesando
|
||||
- Jobs completados (recientes)
|
||||
- Jobs fallidos (recientes)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Actualización en tiempo real
|
||||
- [ ] Filtro por usuario/campaña
|
||||
- [ ] Progreso visible para jobs activos
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-019: Ver Detalle de Job
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-019 |
|
||||
| **Nombre** | Ver Detalle de Job |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de salida:**
|
||||
- Parámetros de entrada
|
||||
- Status y progreso
|
||||
- Timestamps
|
||||
- Outputs generados
|
||||
- Error (si falló)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Toda la información visible
|
||||
- [ ] Links a assets generados
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-020: Cancelar Job
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-020 |
|
||||
| **Nombre** | Cancelar Job |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Precondiciones:**
|
||||
- Job en status "queued"
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Job cambia a "cancelled"
|
||||
- [ ] No se ejecuta
|
||||
- [ ] Cuota no consumida
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-021: Reintentar Job Fallido
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-021 |
|
||||
| **Nombre** | Reintentar Job Fallido |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Precondiciones:**
|
||||
- Job en status "failed"
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Nuevo job creado con mismos parámetros
|
||||
- [ ] Original marcado como "retried"
|
||||
- [ ] Hasta 3 reintentos permitidos
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-022: Cambiar Prioridad de Job (Admin)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-022 |
|
||||
| **Nombre** | Cambiar Prioridad de Job |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Precondiciones:**
|
||||
- Job en status "queued"
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Prioridad actualizada
|
||||
- [ ] Posición en cola recalculada
|
||||
|
||||
---
|
||||
|
||||
## Integración ComfyUI
|
||||
|
||||
### RF-PMC-004-023: Enviar Workflow a ComfyUI
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-023 |
|
||||
| **Nombre** | Enviar Workflow a ComfyUI |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe enviar workflows a ComfyUI para ejecución.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Payload correcto enviado
|
||||
- [ ] Respuesta de ComfyUI procesada
|
||||
- [ ] Errores manejados correctamente
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-024: Recibir Resultados de ComfyUI
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-024 |
|
||||
| **Nombre** | Recibir Resultados de ComfyUI |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe procesar callbacks/webhooks de ComfyUI con resultados.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Imágenes descargadas y almacenadas
|
||||
- [ ] Assets creados automáticamente
|
||||
- [ ] Job actualizado a "completed"
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-025: Monitorear Progreso en ComfyUI
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-025 |
|
||||
| **Nombre** | Monitorear Progreso en ComfyUI |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
Tracking de progreso durante ejecución del workflow.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Websocket conectado a ComfyUI
|
||||
- [ ] Progreso actualizado en tiempo real
|
||||
- [ ] Propagado a frontend
|
||||
|
||||
---
|
||||
|
||||
## Validaciones
|
||||
|
||||
### RF-PMC-004-026: Validar Cuota Antes de Generación
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-026 |
|
||||
| **Nombre** | Validar Cuota Antes de Generación |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
Verificar límites del tenant antes de aceptar job.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Generaciones mensuales verificadas
|
||||
- [ ] Storage verificado para outputs
|
||||
- [ ] Error claro si excede límite
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-027: Agregar Negative Prompts Automáticos
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-027 |
|
||||
| **Nombre** | Agregar Negative Prompts Automáticos |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
Añadir negative prompts de calidad estándar.
|
||||
|
||||
**Negative prompts por defecto:**
|
||||
- "blurry, low quality, watermark, signature, bad anatomy, deformed..."
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Negativos agregados automáticamente
|
||||
- [ ] Usuario puede sobreescribir
|
||||
- [ ] Configurables por tenant
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-004-028: Cargar Identidad de Marca Automáticamente
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-004-028 |
|
||||
| **Nombre** | Cargar Identidad de Marca Automáticamente |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
Al generar para una campaña/marca, cargar automáticamente:
|
||||
- LoRAs asociados
|
||||
- Colores de marca (para prompts)
|
||||
- Forbidden words (para negative prompts)
|
||||
- Tono de voz (para texto)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] LoRA se inyecta en workflow
|
||||
- [ ] Colores incluidos en prompt si aplica
|
||||
- [ ] Forbidden words en negative prompt
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Prioridad | Cantidad |
|
||||
|-----------|----------|
|
||||
| P1 | 17 |
|
||||
| P2 | 9 |
|
||||
| P3 | 2 |
|
||||
| **Total** | **28** |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,414 @@
|
||||
# Requerimientos Funcionales - PMC-005 Automation
|
||||
|
||||
**Módulo:** Automation
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Flujos
|
||||
|
||||
### RF-PMC-005-001: Listar Flujos de Automatización
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-001 |
|
||||
| **Nombre** | Listar Flujos de Automatización |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de flujos disponibles
|
||||
- Estado (activo/inactivo)
|
||||
- Tipo (trigger_based, scheduled, manual)
|
||||
- Última ejecución
|
||||
- Conteo de ejecuciones
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Flujos de sistema y custom listados
|
||||
- [ ] Filtro por tipo y estado
|
||||
- [ ] Ordenamiento por nombre/última ejecución
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-002: Ver Detalle de Flujo
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-002 |
|
||||
| **Nombre** | Ver Detalle de Flujo |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Descripción completa
|
||||
- Evento trigger
|
||||
- Configuración
|
||||
- Historial de ejecuciones recientes
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Toda la información visible
|
||||
- [ ] Link a workflow en n8n (admin)
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-003: Activar Flujo
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-003 |
|
||||
| **Nombre** | Activar Flujo |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Habilitar un flujo para que procese eventos.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] is_active cambia a true
|
||||
- [ ] Flujo comienza a procesar eventos
|
||||
- [ ] Registro en audit log
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-004: Desactivar Flujo
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-004 |
|
||||
| **Nombre** | Desactivar Flujo |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Deshabilitar un flujo sin eliminarlo.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] is_active cambia a false
|
||||
- [ ] Eventos son ignorados
|
||||
- [ ] Configuración preservada
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-005: Configurar Flujo
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-005 |
|
||||
| **Nombre** | Configurar Flujo |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- config: object
|
||||
- retry_on_failure: boolean
|
||||
- max_retries: number
|
||||
- timeout_seconds: number
|
||||
- custom_params: object
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Configuración se guarda
|
||||
- [ ] Valores aplicados en ejecuciones
|
||||
- [ ] Validación de rangos
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-006: Ejecutar Flujo Manualmente
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-006 |
|
||||
| **Nombre** | Ejecutar Flujo Manualmente |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Disparar ejecución manual de un flujo con datos de prueba.
|
||||
|
||||
**Datos de entrada:**
|
||||
- flow_id: UUID
|
||||
- test_data: object (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Ejecución inicia inmediatamente
|
||||
- [ ] Datos de prueba inyectados
|
||||
- [ ] Resultado visible al completar
|
||||
|
||||
---
|
||||
|
||||
## Ejecuciones
|
||||
|
||||
### RF-PMC-005-007: Ver Historial de Ejecuciones
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-007 |
|
||||
| **Nombre** | Ver Historial de Ejecuciones |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de ejecuciones con:
|
||||
- Timestamp
|
||||
- Status
|
||||
- Duración
|
||||
- Trigger data (resumen)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Paginación implementada
|
||||
- [ ] Filtro por status y fecha
|
||||
- [ ] Click para ver detalle
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-008: Ver Detalle de Ejecución
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-008 |
|
||||
| **Nombre** | Ver Detalle de Ejecución |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Datos del trigger completos
|
||||
- Output data
|
||||
- Error message (si falló)
|
||||
- Duración
|
||||
- Timestamps
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] JSON viewer para datos complejos
|
||||
- [ ] Error stack visible en fallos
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-009: Cancelar Ejecución en Curso
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-009 |
|
||||
| **Nombre** | Cancelar Ejecución en Curso |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Precondiciones:**
|
||||
- Ejecución en status "running"
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Ejecución marcada como "cancelled"
|
||||
- [ ] n8n notificado para cancelar
|
||||
|
||||
---
|
||||
|
||||
## Webhooks
|
||||
|
||||
### RF-PMC-005-010: Crear Endpoint de Webhook
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-010 |
|
||||
| **Nombre** | Crear Endpoint de Webhook |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string
|
||||
- target_flow_id: UUID
|
||||
|
||||
**Datos de salida:**
|
||||
- slug generado
|
||||
- URL completa
|
||||
- secret_key generado
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] URL única generada
|
||||
- [ ] Secret para validación
|
||||
- [ ] Endpoint activo inmediatamente
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-011: Listar Webhooks
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-011 |
|
||||
| **Nombre** | Listar Webhooks |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Lista de endpoints del tenant
|
||||
- [ ] URL copiable
|
||||
- [ ] Estado y última llamada visible
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-012: Eliminar Webhook
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-012 |
|
||||
| **Nombre** | Eliminar Webhook |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Confirmación requerida
|
||||
- [ ] Endpoint deja de funcionar
|
||||
- [ ] Registro eliminado
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-013: Regenerar Secret de Webhook
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-013 |
|
||||
| **Nombre** | Regenerar Secret de Webhook |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Generar nuevo secret invalidando el anterior.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Nuevo secret generado
|
||||
- [ ] Anterior invalidado
|
||||
- [ ] Integradores deben actualizar
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-014: Recibir Webhook Externo
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-014 |
|
||||
| **Nombre** | Recibir Webhook Externo |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Sistema Externo |
|
||||
|
||||
**Flujo:**
|
||||
1. Sistema externo hace POST a /hooks/{tenant_slug}/{webhook_slug}
|
||||
2. Sistema valida firma HMAC
|
||||
3. Sistema busca flujo asociado
|
||||
4. Sistema crea ejecución
|
||||
5. Sistema responde 202 Accepted
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Validación HMAC funciona
|
||||
- [ ] Payload parseado correctamente
|
||||
- [ ] Ejecución disparada
|
||||
|
||||
---
|
||||
|
||||
## Eventos del Sistema
|
||||
|
||||
### RF-PMC-005-015: Emitir Eventos Internos
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-015 |
|
||||
| **Nombre** | Emitir Eventos Internos |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
El sistema debe emitir eventos cuando ocurren acciones relevantes.
|
||||
|
||||
**Eventos a emitir:**
|
||||
- CRM: client.created, brand.created, product.created
|
||||
- Projects: campaign.created, campaign.status_changed, campaign.approved
|
||||
- Generation: job.completed, job.failed
|
||||
- Assets: asset.approved, all_assets.approved
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Eventos emitidos en cada acción
|
||||
- [ ] Payload incluye datos relevantes
|
||||
- [ ] Flujos suscritos son notificados
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-016: Suscribir Flujo a Evento
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-016 |
|
||||
| **Nombre** | Suscribir Flujo a Evento |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema |
|
||||
|
||||
**Descripción:**
|
||||
Asociar un flujo con un evento específico.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Flujo se activa cuando evento ocurre
|
||||
- [ ] Datos del evento pasados al flujo
|
||||
- [ ] Múltiples flujos pueden suscribirse al mismo evento
|
||||
|
||||
---
|
||||
|
||||
## Notificaciones
|
||||
|
||||
### RF-PMC-005-017: Enviar Notificación por Email
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-017 |
|
||||
| **Nombre** | Enviar Notificación por Email |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Sistema (vía n8n) |
|
||||
|
||||
**Descripción:**
|
||||
Enviar emails como parte de flujos automatizados.
|
||||
|
||||
**Datos de entrada:**
|
||||
- to: string (email)
|
||||
- subject: string
|
||||
- body: string (HTML)
|
||||
- template_id: string (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Email enviado vía SMTP/SendGrid
|
||||
- [ ] Templates soportados
|
||||
- [ ] Variables interpoladas
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-005-018: Enviar Notificación a Slack
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-005-018 |
|
||||
| **Nombre** | Enviar Notificación a Slack |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Sistema (vía n8n) |
|
||||
|
||||
**Descripción:**
|
||||
Enviar mensajes a canales de Slack.
|
||||
|
||||
**Datos de entrada:**
|
||||
- channel: string
|
||||
- message: string
|
||||
- blocks: array (opcional, formato Slack)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Mensaje enviado al canal
|
||||
- [ ] Formato Slack soportado
|
||||
- [ ] Webhook configurable por tenant
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Prioridad | Cantidad |
|
||||
|-----------|----------|
|
||||
| P1 | 7 |
|
||||
| P2 | 7 |
|
||||
| P3 | 4 |
|
||||
| **Total** | **18** |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,511 @@
|
||||
# Requerimientos Funcionales - PMC-006 Assets
|
||||
|
||||
**Módulo:** Assets (DAM)
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Assets
|
||||
|
||||
### RF-PMC-006-001: Subir Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-001 |
|
||||
| **Nombre** | Subir Asset |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- file(s): array[file] (requerido)
|
||||
- name: string (opcional, usa filename si vacío)
|
||||
- description: text (opcional)
|
||||
- tags: array[string] (opcional)
|
||||
- campaign_id: UUID (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Upload múltiple funciona
|
||||
- [ ] Drag & drop soportado
|
||||
- [ ] Thumbnails generados automáticamente
|
||||
- [ ] Progreso visible durante upload
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-002: Ver Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-002 |
|
||||
| **Nombre** | Ver Asset |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Datos de salida:**
|
||||
- Preview/visualización del asset
|
||||
- Metadata completa
|
||||
- Historial de versiones
|
||||
- Comentarios
|
||||
- Estado de aprobación
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Lightbox para imágenes
|
||||
- [ ] Player para videos
|
||||
- [ ] Viewer para documentos
|
||||
- [ ] Zoom disponible
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-003: Editar Metadata de Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-003 |
|
||||
| **Nombre** | Editar Metadata de Asset |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos editables:**
|
||||
- name
|
||||
- description
|
||||
- tags
|
||||
- visibility
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Cambios guardados correctamente
|
||||
- [ ] Historial de cambios registrado
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-004: Eliminar Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-004 |
|
||||
| **Nombre** | Eliminar Asset |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Restricciones:**
|
||||
- Assets aprobados no pueden eliminarse (solo admin)
|
||||
- Soft delete con retención 30 días
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Soft delete funciona
|
||||
- [ ] Asset va a papelera
|
||||
- [ ] Confirmación requerida
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-005: Restaurar Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-005 |
|
||||
| **Nombre** | Restaurar Asset |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Recuperar asset de la papelera.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Asset restaurado a su estado anterior
|
||||
- [ ] deleted_at se limpia
|
||||
- [ ] Disponible dentro de 30 días
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-006: Listar Assets
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-006 |
|
||||
| **Nombre** | Listar Assets |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Vistas:**
|
||||
- Grid (thumbnails)
|
||||
- Lista (tabla con metadata)
|
||||
|
||||
**Filtros:**
|
||||
- type (image, video, document, copy, model)
|
||||
- status (draft, pending_review, approved, rejected)
|
||||
- campaign_id
|
||||
- collection_id
|
||||
- tags
|
||||
- date_range
|
||||
- source (generated, uploaded)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Ambas vistas funcionan
|
||||
- [ ] Filtros combinables
|
||||
- [ ] Paginación con scroll infinito o páginas
|
||||
- [ ] Ordenamiento múltiple
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-007: Búsqueda de Assets
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-007 |
|
||||
| **Nombre** | Búsqueda de Assets |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Campos buscables:**
|
||||
- name
|
||||
- description
|
||||
- tags
|
||||
- prompt (para generados)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Búsqueda de texto funciona
|
||||
- [ ] Resultados relevantes primero
|
||||
- [ ] Highlighting de matches
|
||||
|
||||
---
|
||||
|
||||
## Colecciones
|
||||
|
||||
### RF-PMC-006-008: Crear Colección
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-008 |
|
||||
| **Nombre** | Crear Colección |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string (requerido)
|
||||
- description: text (opcional)
|
||||
- type: enum (manual, smart)
|
||||
- smart_filters: object (si type=smart)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Colección creada correctamente
|
||||
- [ ] Smart collection ejecuta filtros
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-009: Editar Colección
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-009 |
|
||||
| **Nombre** | Editar Colección |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Nombre y descripción editables
|
||||
- [ ] Filtros de smart collection editables
|
||||
- [ ] Cover image seleccionable
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-010: Agregar Assets a Colección
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-010 |
|
||||
| **Nombre** | Agregar Assets a Colección |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Métodos:**
|
||||
- Desde asset detail → "Add to collection"
|
||||
- Desde colección → "Add assets"
|
||||
- Bulk selection → "Add to collection"
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Múltiples métodos funcionan
|
||||
- [ ] Asset puede estar en múltiples colecciones
|
||||
- [ ] No duplicados en misma colección
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-011: Quitar Assets de Colección
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-011 |
|
||||
| **Nombre** | Quitar Assets de Colección |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Asset se desvincula de colección
|
||||
- [ ] Asset NO se elimina
|
||||
- [ ] Bulk removal soportado
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-012: Eliminar Colección
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-012 |
|
||||
| **Nombre** | Eliminar Colección |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Colección eliminada
|
||||
- [ ] Assets NO se eliminan
|
||||
- [ ] Confirmación requerida
|
||||
|
||||
---
|
||||
|
||||
## Versiones
|
||||
|
||||
### RF-PMC-006-013: Subir Nueva Versión de Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-013 |
|
||||
| **Nombre** | Subir Nueva Versión de Asset |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de entrada:**
|
||||
- asset_id: UUID
|
||||
- new_file: file
|
||||
- changes_description: text (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Nueva versión almacenada
|
||||
- [ ] Version number incrementado
|
||||
- [ ] Versión anterior preservada
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-014: Ver Historial de Versiones
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-014 |
|
||||
| **Nombre** | Ver Historial de Versiones |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de versiones con:
|
||||
- Número de versión
|
||||
- Fecha
|
||||
- Usuario
|
||||
- Descripción de cambios
|
||||
- Preview
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Historial completo visible
|
||||
- [ ] Click para ver versión específica
|
||||
- [ ] Opción de restaurar versión anterior
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-015: Restaurar Versión Anterior
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-015 |
|
||||
| **Nombre** | Restaurar Versión Anterior |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Descripción:**
|
||||
Hacer que una versión anterior sea la actual.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Versión seleccionada se convierte en actual
|
||||
- [ ] Nueva versión creada (copia)
|
||||
- [ ] Historial preservado
|
||||
|
||||
---
|
||||
|
||||
## Aprobación
|
||||
|
||||
### RF-PMC-006-016: Aprobar Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-016 |
|
||||
| **Nombre** | Aprobar Asset |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Status cambia a "approved"
|
||||
- [ ] approved_by y approved_at registrados
|
||||
- [ ] Asset no editable después (excepto metadata)
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-017: Rechazar Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-017 |
|
||||
| **Nombre** | Rechazar Asset |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- feedback: text (requerido)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Status cambia a "rejected"
|
||||
- [ ] Feedback almacenado
|
||||
- [ ] Asset puede regenerarse/versionarse
|
||||
|
||||
---
|
||||
|
||||
## Comentarios
|
||||
|
||||
### RF-PMC-006-018: Agregar Comentario a Asset
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-018 |
|
||||
| **Nombre** | Agregar Comentario a Asset |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- content: text (requerido)
|
||||
- position: object (x, y) - opcional, para comentarios en imagen
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Comentario se crea
|
||||
- [ ] Posición en imagen soportada
|
||||
- [ ] Notificación a equipo
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-019: Responder Comentario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-019 |
|
||||
| **Nombre** | Responder Comentario |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Respuesta vinculada al comentario padre
|
||||
- [ ] Thread visible
|
||||
- [ ] Notificación a participantes
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-020: Resolver Comentario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-020 |
|
||||
| **Nombre** | Resolver Comentario |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] is_resolved cambia a true
|
||||
- [ ] Comentario colapsado visualmente
|
||||
- [ ] Puede des-resolverse
|
||||
|
||||
---
|
||||
|
||||
## Descargas
|
||||
|
||||
### RF-PMC-006-021: Descargar Asset Individual
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-021 |
|
||||
| **Nombre** | Descargar Asset Individual |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Opciones:**
|
||||
- Original
|
||||
- Convertido (formato diferente)
|
||||
- Tamaño reducido
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Descarga inicia correctamente
|
||||
- [ ] Registro de descarga creado
|
||||
- [ ] Conversión on-the-fly funciona
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-022: Descargar Múltiples Assets (ZIP)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-022 |
|
||||
| **Nombre** | Descargar Múltiples Assets (ZIP) |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- asset_ids: array[UUID]
|
||||
- include_metadata: boolean (opcional)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] ZIP generado con estructura organizada
|
||||
- [ ] Progreso visible para ZIPs grandes
|
||||
- [ ] Metadata en JSON opcional
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-023: Generar Enlace Temporal
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-023 |
|
||||
| **Nombre** | Generar Enlace Temporal |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- asset_id: UUID
|
||||
- expiry_days: number (default: 7, max: 30)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] URL única generada
|
||||
- [ ] Expira después del tiempo configurado
|
||||
- [ ] No requiere autenticación para descargar
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-006-024: Descargar Colección Completa
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-006-024 |
|
||||
| **Nombre** | Descargar Colección Completa |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Todos los assets de colección en ZIP
|
||||
- [ ] Estructura de carpetas preservada
|
||||
- [ ] Opción de incluir solo aprobados
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Prioridad | Cantidad |
|
||||
|-----------|----------|
|
||||
| P1 | 13 |
|
||||
| P2 | 9 |
|
||||
| P3 | 2 |
|
||||
| **Total** | **24** |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,438 @@
|
||||
# Requerimientos Funcionales - PMC-007 Admin
|
||||
|
||||
**Módulo:** Admin
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Usuarios
|
||||
|
||||
### RF-PMC-007-001: Listar Usuarios
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-001 |
|
||||
| **Nombre** | Listar Usuarios |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de usuarios del tenant
|
||||
- Status, rol, último login
|
||||
- Filtros por status y rol
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Solo usuarios del tenant
|
||||
- [ ] Paginación funciona
|
||||
- [ ] Búsqueda por nombre/email
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-002: Invitar Usuario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-002 |
|
||||
| **Nombre** | Invitar Usuario |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- email: string (requerido)
|
||||
- role_id: UUID (requerido)
|
||||
- message: text (opcional)
|
||||
|
||||
**Flujo:**
|
||||
1. Admin ingresa email y selecciona rol
|
||||
2. Sistema verifica email no existe en tenant
|
||||
3. Sistema crea invitación con token único
|
||||
4. Sistema envía email con link
|
||||
5. Invitación expira en 7 días
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Email de invitación enviado
|
||||
- [ ] Token único generado
|
||||
- [ ] Invitación listada como pendiente
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-003: Aceptar Invitación
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-003 |
|
||||
| **Nombre** | Aceptar Invitación |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Usuario invitado |
|
||||
|
||||
**Datos de entrada:**
|
||||
- token: string (del link)
|
||||
- first_name: string
|
||||
- last_name: string
|
||||
- password: string
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Usuario creado con rol asignado
|
||||
- [ ] Password hasheado
|
||||
- [ ] Invitación marcada como aceptada
|
||||
- [ ] Usuario puede hacer login
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-004: Editar Usuario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-004 |
|
||||
| **Nombre** | Editar Usuario |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos editables:**
|
||||
- first_name, last_name
|
||||
- role_id
|
||||
- status
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Campos se actualizan
|
||||
- [ ] Cambio de rol aplica inmediatamente
|
||||
- [ ] Audit log registra cambios
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-005: Suspender Usuario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-005 |
|
||||
| **Nombre** | Suspender Usuario |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Bloquear acceso de un usuario temporalmente.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Status cambia a "suspended"
|
||||
- [ ] Sesiones invalidadas
|
||||
- [ ] Usuario no puede hacer login
|
||||
- [ ] Admin no puede suspenderse a sí mismo
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-006: Reactivar Usuario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-006 |
|
||||
| **Nombre** | Reactivar Usuario |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Status cambia a "active"
|
||||
- [ ] Usuario puede hacer login
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-007: Eliminar Usuario
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-007 |
|
||||
| **Nombre** | Eliminar Usuario |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Status cambia a "deactivated"
|
||||
- [ ] Soft delete
|
||||
- [ ] Datos preservados (para auditoría)
|
||||
- [ ] No puede ser el último admin
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-008: Reenviar Invitación
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-008 |
|
||||
| **Nombre** | Reenviar Invitación |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Nuevo email enviado
|
||||
- [ ] Token regenerado
|
||||
- [ ] Expiry extendido
|
||||
|
||||
---
|
||||
|
||||
## Gestión de Roles
|
||||
|
||||
### RF-PMC-007-009: Listar Roles
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-009 |
|
||||
| **Nombre** | Listar Roles |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Roles de sistema
|
||||
- Roles custom del tenant
|
||||
- Usuarios por rol
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Todos los roles listados
|
||||
- [ ] Identificación clara de roles de sistema
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-010: Ver Permisos de Rol
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-010 |
|
||||
| **Nombre** | Ver Permisos de Rol |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de permisos agrupados por módulo
|
||||
- Checkbox de cada permiso
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Permisos organizados por módulo
|
||||
- [ ] Visual claro de lo que puede/no puede hacer
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-011: Crear Rol Custom
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-011 |
|
||||
| **Nombre** | Crear Rol Custom |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string
|
||||
- description: text
|
||||
- permissions: array[string]
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Rol creado con is_system=false
|
||||
- [ ] Permisos asignados
|
||||
- [ ] Disponible para asignar a usuarios
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-012: Editar Rol Custom
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-012 |
|
||||
| **Nombre** | Editar Rol Custom |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Restricciones:**
|
||||
- Roles de sistema no editables
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Permisos actualizables
|
||||
- [ ] Cambios aplican inmediatamente a usuarios
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-013: Eliminar Rol Custom
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-013 |
|
||||
| **Nombre** | Eliminar Rol Custom |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Restricciones:**
|
||||
- No puede tener usuarios asignados
|
||||
- Roles de sistema no eliminables
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Validación de usuarios previo a eliminar
|
||||
- [ ] Error si tiene usuarios
|
||||
|
||||
---
|
||||
|
||||
## Configuración
|
||||
|
||||
### RF-PMC-007-014: Ver Configuración del Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-014 |
|
||||
| **Nombre** | Ver Configuración del Tenant |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Secciones:**
|
||||
- General (nombre, timezone, idioma)
|
||||
- Branding
|
||||
- Generación (defaults)
|
||||
- Integraciones
|
||||
- Notificaciones
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Todas las secciones accesibles
|
||||
- [ ] Valores actuales mostrados
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-015: Editar Configuración General
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-015 |
|
||||
| **Nombre** | Editar Configuración General |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos editables:**
|
||||
- Nombre del tenant
|
||||
- Timezone
|
||||
- Idioma por defecto
|
||||
- Formato de fecha
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Cambios guardados
|
||||
- [ ] Aplican a nuevos usuarios
|
||||
- [ ] Usuarios existentes pueden tener preferencia propia
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-016: Configurar Integraciones
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-016 |
|
||||
| **Nombre** | Configurar Integraciones |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Integraciones configurables:**
|
||||
- n8n webhook URL
|
||||
- Slack webhook
|
||||
- SMTP custom (opcional)
|
||||
- CRM externo URL
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] URLs validadas
|
||||
- [ ] Test de conexión disponible
|
||||
- [ ] Secrets seguros
|
||||
|
||||
---
|
||||
|
||||
## Auditoría
|
||||
|
||||
### RF-PMC-007-017: Ver Logs de Auditoría
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-017 |
|
||||
| **Nombre** | Ver Logs de Auditoría |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Filtros:**
|
||||
- Usuario
|
||||
- Acción
|
||||
- Entidad
|
||||
- Fecha
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Logs listados con paginación
|
||||
- [ ] Filtros combinables
|
||||
- [ ] Detalle expandible
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-018: Exportar Logs de Auditoría
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-018 |
|
||||
| **Nombre** | Exportar Logs de Auditoría |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Formatos:**
|
||||
- CSV
|
||||
- JSON
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Filtros aplicados en export
|
||||
- [ ] Archivo descargable generado
|
||||
|
||||
---
|
||||
|
||||
## Sistema (Super Admin)
|
||||
|
||||
### RF-PMC-007-019: Ver Estado del Sistema
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-019 |
|
||||
| **Nombre** | Ver Estado del Sistema |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Métricas:**
|
||||
- Estado de servicios (API, ComfyUI, Redis, DB)
|
||||
- Uso de GPU
|
||||
- Cola de generación
|
||||
- Storage total
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Dashboard de salud
|
||||
- [ ] Alertas en problemas
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-007-020: Ver Uso por Tenant
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-007-020 |
|
||||
| **Nombre** | Ver Uso por Tenant |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Super Admin |
|
||||
|
||||
**Métricas por tenant:**
|
||||
- Generaciones
|
||||
- Storage
|
||||
- Usuarios activos
|
||||
- API calls
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Comparativa entre tenants
|
||||
- [ ] Exportable
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Prioridad | Cantidad |
|
||||
|-----------|----------|
|
||||
| P1 | 8 |
|
||||
| P2 | 10 |
|
||||
| P3 | 2 |
|
||||
| **Total** | **20** |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,356 @@
|
||||
# Requerimientos Funcionales - PMC-008 Analytics
|
||||
|
||||
**Módulo:** Analytics
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Dashboards
|
||||
|
||||
### RF-PMC-008-001: Ver Dashboard Principal
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-001 |
|
||||
| **Nombre** | Ver Dashboard Principal |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Widgets:**
|
||||
- Quick stats (campañas activas, assets mes, tasa aprobación)
|
||||
- Actividad reciente
|
||||
- Acciones pendientes
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Dashboard carga correctamente
|
||||
- [ ] Datos actualizados
|
||||
- [ ] Responsive en diferentes pantallas
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-002: Ver Dashboard de Producción
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-002 |
|
||||
| **Nombre** | Ver Dashboard de Producción |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Widgets:**
|
||||
- Volumen de generación (gráfico de líneas)
|
||||
- Estado de cola (gauge)
|
||||
- Uso de modelos/workflows (pie chart)
|
||||
- Tasa de error
|
||||
- Tiempo promedio de procesamiento
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Gráficos interactivos
|
||||
- [ ] Filtro de período funciona
|
||||
- [ ] Datos en tiempo real para cola
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-003: Ver Dashboard de Campañas
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-003 |
|
||||
| **Nombre** | Ver Dashboard de Campañas |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Analyst, Tenant Admin |
|
||||
|
||||
**Widgets:**
|
||||
- Funnel de campañas por estado
|
||||
- Tasa de aprobación primera iteración
|
||||
- Tiempo promedio brief → aprobación
|
||||
- Assets por campaña
|
||||
- Top clientes
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Métricas calculadas correctamente
|
||||
- [ ] Drill-down en gráficos
|
||||
- [ ] Filtro por cliente/período
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-004: Ver Dashboard de Recursos
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-004 |
|
||||
| **Nombre** | Ver Dashboard de Recursos |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Widgets:**
|
||||
- Storage usado vs cuota
|
||||
- Generaciones mes vs límite
|
||||
- Distribución de storage por tipo
|
||||
- Proyección de uso
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Progress bars claras
|
||||
- [ ] Alertas en >80% uso
|
||||
- [ ] Breakdown por tipo de asset
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-005: Aplicar Filtros Globales
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-005 |
|
||||
| **Nombre** | Aplicar Filtros Globales |
|
||||
| **Prioridad** | P1 |
|
||||
| **Actor** | Todos los roles |
|
||||
|
||||
**Filtros:**
|
||||
- Período (hoy, semana, mes, custom)
|
||||
- Cliente
|
||||
- Usuario
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Filtros aplican a todos los widgets
|
||||
- [ ] Persistencia durante sesión
|
||||
- [ ] Reset disponible
|
||||
|
||||
---
|
||||
|
||||
## Reportes
|
||||
|
||||
### RF-PMC-008-006: Generar Reporte de Actividad Mensual
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-006 |
|
||||
| **Nombre** | Generar Reporte de Actividad Mensual |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Contenido:**
|
||||
- Resumen ejecutivo
|
||||
- Campañas del período
|
||||
- Assets generados
|
||||
- Uso de recursos
|
||||
- Comparativa con período anterior
|
||||
|
||||
**Formatos:**
|
||||
- PDF
|
||||
- Excel
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Reporte generado correctamente
|
||||
- [ ] Datos precisos
|
||||
- [ ] Formato profesional
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-007: Generar Reporte de Campaña
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-007 |
|
||||
| **Nombre** | Generar Reporte de Campaña |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- campaign_id: UUID
|
||||
|
||||
**Contenido:**
|
||||
- Datos de campaña y brief
|
||||
- Assets generados/aprobados
|
||||
- Timeline de actividad
|
||||
- Participantes
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Reporte específico de campaña
|
||||
- [ ] Incluye thumbnails de assets
|
||||
- [ ] PDF descargable
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-008: Generar Reporte de Cliente
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-008 |
|
||||
| **Nombre** | Generar Reporte de Cliente |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Analyst, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- client_id: UUID
|
||||
- date_range: object
|
||||
|
||||
**Contenido:**
|
||||
- Proyectos y campañas
|
||||
- Assets entregados
|
||||
- Histórico de actividad
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Filtro de período funciona
|
||||
- [ ] Exportable en PDF/Excel
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-009: Programar Reporte Automático
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-009 |
|
||||
| **Nombre** | Programar Reporte Automático |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Opciones:**
|
||||
- Frecuencia (semanal, mensual)
|
||||
- Destinatarios (emails)
|
||||
- Tipo de reporte
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Programación guardada
|
||||
- [ ] Email enviado automáticamente
|
||||
- [ ] PDF adjunto
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-010: Ver Historial de Reportes
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-010 |
|
||||
| **Nombre** | Ver Historial de Reportes |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Datos de salida:**
|
||||
- Lista de reportes generados
|
||||
- Fecha, tipo, generador
|
||||
- Link de descarga
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Reportes listados
|
||||
- [ ] Descarga disponible (30 días)
|
||||
- [ ] Filtro por tipo/fecha
|
||||
|
||||
---
|
||||
|
||||
## Métricas
|
||||
|
||||
### RF-PMC-008-011: Consultar Métricas Raw
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-011 |
|
||||
| **Nombre** | Consultar Métricas Raw |
|
||||
| **Prioridad** | P2 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
API para consultar métricas agregadas.
|
||||
|
||||
**Parámetros:**
|
||||
- metric_type
|
||||
- dimensions
|
||||
- period
|
||||
- filters
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] API retorna datos correctos
|
||||
- [ ] Agregaciones funcionan
|
||||
- [ ] Paginación para grandes volúmenes
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-012: Exportar Datos de Métricas
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-012 |
|
||||
| **Nombre** | Exportar Datos de Métricas |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Tenant Admin |
|
||||
|
||||
**Formatos:**
|
||||
- CSV
|
||||
- JSON
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Filtros aplicados
|
||||
- [ ] Formato correcto
|
||||
- [ ] Útil para BI externo
|
||||
|
||||
---
|
||||
|
||||
## Personalización
|
||||
|
||||
### RF-PMC-008-013: Guardar Vista Personalizada
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-013 |
|
||||
| **Nombre** | Guardar Vista Personalizada |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Datos de entrada:**
|
||||
- name: string
|
||||
- dashboard: string
|
||||
- config: object (filtros, widgets visibles)
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Vista guardada
|
||||
- [ ] Cargable posteriormente
|
||||
- [ ] Por usuario
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-014: Establecer Vista por Defecto
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-014 |
|
||||
| **Nombre** | Establecer Vista por Defecto |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Descripción:**
|
||||
Marcar una vista guardada como la que se carga al abrir dashboard.
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Vista se carga automáticamente
|
||||
- [ ] Una sola vista por defecto por dashboard
|
||||
|
||||
---
|
||||
|
||||
### RF-PMC-008-015: Eliminar Vista Guardada
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|-------|
|
||||
| **ID** | RF-PMC-008-015 |
|
||||
| **Nombre** | Eliminar Vista Guardada |
|
||||
| **Prioridad** | P3 |
|
||||
| **Actor** | Creative, Tenant Admin |
|
||||
|
||||
**Criterios de aceptación:**
|
||||
- [ ] Vista eliminada
|
||||
- [ ] Si era default, se usa vista estándar
|
||||
|
||||
---
|
||||
|
||||
## Resumen
|
||||
|
||||
| Prioridad | Cantidad |
|
||||
|-----------|----------|
|
||||
| P1 | 2 |
|
||||
| P2 | 6 |
|
||||
| P3 | 7 |
|
||||
| **Total** | **15** |
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,58 @@
|
||||
# Índice de Requerimientos Funcionales
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Documentos de Requerimientos
|
||||
|
||||
| ID | Módulo | Documento | RFs | Estado |
|
||||
|----|--------|-----------|-----|--------|
|
||||
| RF-001 | Tenants | [RF-PMC-001-TENANTS.md](./RF-PMC-001-TENANTS.md) | 15 | Definido |
|
||||
| RF-002 | CRM | [RF-PMC-002-CRM.md](./RF-PMC-002-CRM.md) | 25 | Definido |
|
||||
| RF-003 | Projects | [RF-PMC-003-PROJECTS.md](./RF-PMC-003-PROJECTS.md) | 22 | Definido |
|
||||
| RF-004 | Generation | [RF-PMC-004-GENERATION.md](./RF-PMC-004-GENERATION.md) | 28 | Definido |
|
||||
| RF-005 | Automation | [RF-PMC-005-AUTOMATION.md](./RF-PMC-005-AUTOMATION.md) | 18 | Definido |
|
||||
| RF-006 | Assets | [RF-PMC-006-ASSETS.md](./RF-PMC-006-ASSETS.md) | 24 | Definido |
|
||||
| RF-007 | Admin | [RF-PMC-007-ADMIN.md](./RF-PMC-007-ADMIN.md) | 20 | Definido |
|
||||
| RF-008 | Analytics | [RF-PMC-008-ANALYTICS.md](./RF-PMC-008-ANALYTICS.md) | 15 | Definido |
|
||||
|
||||
**Total de Requerimientos Funcionales:** 167
|
||||
|
||||
---
|
||||
|
||||
## Nomenclatura
|
||||
|
||||
```
|
||||
RF-PMC-XXX-YYY
|
||||
|
||||
Donde:
|
||||
- RF: Requerimiento Funcional
|
||||
- PMC: Platform Marketing Content
|
||||
- XXX: ID del módulo (001-008)
|
||||
- YYY: Número secuencial del requerimiento
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prioridades
|
||||
|
||||
| Prioridad | Descripción | Criterio |
|
||||
|-----------|-------------|----------|
|
||||
| **P1** | Crítico | Bloquea el MVP, sin esto no funciona |
|
||||
| **P2** | Alto | Necesario para MVP, funcionalidad core |
|
||||
| **P3** | Medio | Deseable para MVP, mejora UX |
|
||||
| **P4** | Bajo | Post-MVP, mejoras futuras |
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [Definición de Módulos](../02-definicion-modulos/_INDEX.md)
|
||||
- [Arquitectura Técnica](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,275 @@
|
||||
# Modelo de Dominio - Platform Marketing Content
|
||||
|
||||
**Versión:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
|
||||
---
|
||||
|
||||
## Diagrama de Entidades
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ CORE ENTITIES │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Tenant │◄────────│ Plan │ │ User │ │
|
||||
│ └────┬─────┘ └──────────┘ └────┬─────┘ │
|
||||
│ │ │ │
|
||||
│ │ 1:N │ N:1 │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Client │ │ Role │ │
|
||||
│ └────┬─────┘ └──────────┘ │
|
||||
│ │ │
|
||||
│ │ 1:N │
|
||||
│ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Contact │ │ Brand │◄────────┐ │
|
||||
│ └──────────┘ └────┬─────┘ │ │
|
||||
│ │ │ N:N │
|
||||
│ │ 1:N │ │
|
||||
│ ▼ │ │
|
||||
│ ┌──────────┐ ┌────┴─────┐ │
|
||||
│ │ Product │ │CustomModel│ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ PROJECT ENTITIES │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ │
|
||||
│ │ Project │◄──────────────────┐ │
|
||||
│ └────┬─────┘ │ │
|
||||
│ │ │ N:1 (Client) │
|
||||
│ │ 1:N │ │
|
||||
│ ▼ │ │
|
||||
│ ┌──────────┐ ┌─────────┴┐ ┌──────────┐ │
|
||||
│ │ Campaign │─────────│Opportunity│ │ BriefTpl │ │
|
||||
│ └────┬─────┘ └──────────┘ └──────────┘ │
|
||||
│ │ │
|
||||
│ │ 1:N │
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │CampaignAsset │──────────┐ │
|
||||
│ └──────────────┘ │ N:1 │
|
||||
│ ▼ │
|
||||
│ ┌──────────┐ │
|
||||
│ │ Asset │ │
|
||||
│ └──────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ GENERATION ENTITIES │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────────┐ ┌────────────────┐ │
|
||||
│ │GenerationJob │───────────────────│WorkflowTemplate│ │
|
||||
│ └───────┬────────┘ └────────────────┘ │
|
||||
│ │ │
|
||||
│ │ 1:N │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ ┌────────────────┐ │
|
||||
│ │ Asset │ │ TextGeneration │ │
|
||||
│ └───────┬────────┘ └────────────────┘ │
|
||||
│ │ │
|
||||
│ │ 1:N │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ ┌────────────────┐ │
|
||||
│ │ AssetVersion │ │ AssetComment │ │
|
||||
│ └────────────────┘ └────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ AUTOMATION ENTITIES │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────────┐ ┌────────────────┐ │
|
||||
│ │AutomationFlow │───────────────────│ AutomationRun │ │
|
||||
│ └───────┬────────┘ └────────────────┘ │
|
||||
│ │ │
|
||||
│ │ N:1 │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │WebhookEndpoint │ │
|
||||
│ └────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ SUPPORT ENTITIES │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │Collection│ │Invitation│ │ AuditLog │ │ Setting │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Download │ │ Metric │ │ Report │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entidades por Módulo
|
||||
|
||||
### PMC-001: Tenants
|
||||
|
||||
| Entidad | Descripción | Relaciones Principales |
|
||||
|---------|-------------|------------------------|
|
||||
| **Tenant** | Organización que usa la plataforma | 1:N User, Client, Project, Asset |
|
||||
| **Plan** | Plan de suscripción con límites | 1:N Tenant |
|
||||
|
||||
### PMC-002: CRM
|
||||
|
||||
| Entidad | Descripción | Relaciones Principales |
|
||||
|---------|-------------|------------------------|
|
||||
| **Client** | Empresa cliente de la agencia | N:1 Tenant, 1:N Contact, Brand, Project |
|
||||
| **Contact** | Persona de contacto | N:1 Client |
|
||||
| **Brand** | Marca con identidad visual | N:1 Client, 1:N Product, Campaign |
|
||||
| **Product** | Producto o servicio | N:1 Brand |
|
||||
| **Opportunity** | Oportunidad comercial | N:1 Client, Contact |
|
||||
|
||||
### PMC-003: Projects
|
||||
|
||||
| Entidad | Descripción | Relaciones Principales |
|
||||
|---------|-------------|------------------------|
|
||||
| **Project** | Contenedor de campañas | N:1 Client, Tenant, 1:N Campaign |
|
||||
| **Campaign** | Campaña de marketing con brief | N:1 Project, Brand, 1:N CampaignAsset |
|
||||
| **CampaignAsset** | Relación campaña-asset con estado | N:1 Campaign, Asset |
|
||||
|
||||
### PMC-004: Generation
|
||||
|
||||
| Entidad | Descripción | Relaciones Principales |
|
||||
|---------|-------------|------------------------|
|
||||
| **GenerationJob** | Tarea de generación | N:1 Tenant, Campaign, WorkflowTemplate |
|
||||
| **WorkflowTemplate** | Plantilla de workflow ComfyUI | 1:N GenerationJob |
|
||||
| **CustomModel** | LoRA/Checkpoint personalizado | N:1 Tenant, Brand |
|
||||
| **TextGeneration** | Generación de texto/copy | N:1 Tenant, GenerationJob |
|
||||
|
||||
### PMC-005: Automation
|
||||
|
||||
| Entidad | Descripción | Relaciones Principales |
|
||||
|---------|-------------|------------------------|
|
||||
| **AutomationFlow** | Definición de flujo automatizado | N:1 Tenant, 1:N AutomationRun |
|
||||
| **AutomationRun** | Ejecución de un flujo | N:1 AutomationFlow |
|
||||
| **WebhookEndpoint** | Endpoint para webhooks externos | N:1 Tenant, AutomationFlow |
|
||||
|
||||
### PMC-006: Assets
|
||||
|
||||
| Entidad | Descripción | Relaciones Principales |
|
||||
|---------|-------------|------------------------|
|
||||
| **Asset** | Recurso digital (imagen, video, etc.) | N:1 Tenant, GenerationJob |
|
||||
| **AssetVersion** | Versión histórica de asset | N:1 Asset |
|
||||
| **Collection** | Agrupación de assets | N:1 Tenant, N:N Asset |
|
||||
| **AssetComment** | Comentario sobre asset | N:1 Asset, User |
|
||||
| **Download** | Registro de descarga | N:1 Asset, User |
|
||||
|
||||
### PMC-007: Admin
|
||||
|
||||
| Entidad | Descripción | Relaciones Principales |
|
||||
|---------|-------------|------------------------|
|
||||
| **User** | Usuario del sistema | N:1 Tenant, Role |
|
||||
| **Role** | Rol con permisos | 1:N User |
|
||||
| **Invitation** | Invitación pendiente | N:1 Tenant, Role |
|
||||
| **AuditLog** | Registro de auditoría | N:1 Tenant, User |
|
||||
| **Setting** | Configuración del sistema | N:1 Tenant (opcional) |
|
||||
|
||||
### PMC-008: Analytics
|
||||
|
||||
| Entidad | Descripción | Relaciones Principales |
|
||||
|---------|-------------|------------------------|
|
||||
| **Metric** | Dato métrico agregado | N:1 Tenant |
|
||||
| **Report** | Reporte generado | N:1 Tenant, User |
|
||||
| **SavedView** | Vista personalizada guardada | N:1 Tenant, User |
|
||||
|
||||
---
|
||||
|
||||
## Matriz de Relaciones
|
||||
|
||||
```
|
||||
│Ten│Pln│Usr│Rol│Cli│Con│Bra│Pro│Opp│Prj│Cam│CAs│Job│Wfl│Mod│Txt│Flo│Run│Whk│Ast│Ver│Col│Com│Dwn│Inv│Aud│Set│Met│Rep│Viw│
|
||||
─────────────┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
Tenant │ │N:1│1:N│ │1:N│ │ │ │ │1:N│ │ │1:N│ │1:N│ │1:N│ │1:N│1:N│ │1:N│ │ │1:N│1:N│1:N│1:N│1:N│1:N│
|
||||
Plan │1:N│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
User │N:1│ │ │N:1│ │ │ │ │ │1:N│ │ │1:N│ │ │ │ │ │ │1:N│ │ │1:N│1:N│ │1:N│ │ │1:N│1:N│
|
||||
Role │ │ │1:N│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │1:N│ │ │ │ │ │
|
||||
Client │N:1│ │ │ │ │1:N│1:N│ │1:N│1:N│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Contact │ │ │ │ │N:1│ │ │ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Brand │ │ │ │ │N:1│ │ │1:N│ │ │1:N│ │ │ │N:N│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Product │ │ │ │ │ │ │N:1│ │ │ │ │ │N:1│ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Opportunity │ │ │ │ │N:1│N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Project │N:1│ │N:1│ │N:1│ │ │ │ │ │1:N│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Campaign │ │ │ │ │ │ │N:1│ │ │N:1│ │1:N│N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
CampaignAsset│ │ │ │ │ │ │ │ │ │ │N:1│ │ │ │ │ │ │ │ │N:1│ │ │ │ │ │ │ │ │ │ │
|
||||
GenJob │N:1│ │N:1│ │ │ │ │N:1│ │ │N:1│ │ │N:1│ │1:N│ │ │ │1:N│ │ │ │ │ │ │ │ │ │ │
|
||||
Workflow │ │ │ │ │ │ │ │ │ │ │ │ │1:N│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
CustomModel │N:1│ │ │ │ │ │N:N│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
TextGen │N:1│ │ │ │ │ │ │ │ │ │N:1│ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
AutoFlow │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │1:N│1:N│ │ │ │ │ │ │ │ │ │ │ │
|
||||
AutoRun │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Webhook │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Asset │N:1│ │N:1│ │ │ │ │ │ │ │ │N:1│N:1│ │ │ │ │ │ │ │1:N│N:N│1:N│1:N│ │ │ │ │ │ │
|
||||
AssetVer │ │ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │N:1│ │ │ │ │ │ │ │ │ │ │
|
||||
Collection │N:1│ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │N:N│ │ │ │ │ │ │ │ │ │ │
|
||||
AssetComment │ │ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │N:1│ │ │1:N│ │ │ │ │ │ │ │
|
||||
Download │N:1│ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │N:1│ │N:1│ │ │ │ │ │ │ │ │
|
||||
Invitation │N:1│ │N:1│N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
AuditLog │N:1│ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Setting │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Metric │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
Report │N:1│ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
SavedView │N:1│ │N:1│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Agregados (DDD)
|
||||
|
||||
### Aggregate: Tenant
|
||||
- **Root:** Tenant
|
||||
- **Entities:** Plan (referencia)
|
||||
- **Value Objects:** Settings, Branding, Limits
|
||||
|
||||
### Aggregate: Client
|
||||
- **Root:** Client
|
||||
- **Entities:** Contact, Brand, Product
|
||||
- **Value Objects:** Identity (en Brand)
|
||||
|
||||
### Aggregate: Project
|
||||
- **Root:** Project
|
||||
- **Entities:** Campaign, CampaignAsset
|
||||
- **Value Objects:** Brief (en Campaign)
|
||||
|
||||
### Aggregate: Asset
|
||||
- **Root:** Asset
|
||||
- **Entities:** AssetVersion, AssetComment
|
||||
- **Value Objects:** Metadata, Dimensions
|
||||
|
||||
### Aggregate: GenerationJob
|
||||
- **Root:** GenerationJob
|
||||
- **Entities:** (outputs referenciados)
|
||||
- **Value Objects:** InputParams, Progress
|
||||
|
||||
### Aggregate: User
|
||||
- **Root:** User
|
||||
- **Entities:** (Role como referencia)
|
||||
- **Value Objects:** Preferences
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
- [Definición de Módulos](../02-definicion-modulos/_INDEX.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,188 @@
|
||||
# Contexto de Proyecto: Platform Marketing Content
|
||||
|
||||
**Versi贸n:** 1.0.0
|
||||
**Fecha:** 2025-12-08
|
||||
**Nivel SIMCO:** NIVEL_2B (Proyecto independiente)
|
||||
|
||||
---
|
||||
|
||||
## Identificaci贸n del Proyecto
|
||||
|
||||
```yaml
|
||||
Proyecto: platform_marketing_content
|
||||
Alias: PMC
|
||||
Tipo: SaaS Platform
|
||||
Dominio: Marketing Digital / Generaci贸n de Contenido IA
|
||||
Estado: An谩lisis y Documentaci贸n
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Descripci贸n
|
||||
|
||||
**Platform Marketing Content (PMC)** es una plataforma SaaS para agencias de publicidad que combina:
|
||||
|
||||
1. **Motor de Generaci贸n de Contenido con IA** - Im谩genes y copys autom谩ticos
|
||||
2. **CRM Integrado** - Gesti贸n de clientes, marcas y campa帽as
|
||||
3. **Automatizaci贸n Creativa** - Flujos desde brief hasta entrega
|
||||
4. **DAM** - Biblioteca de activos digitales
|
||||
|
||||
---
|
||||
|
||||
## Stack Tecnol贸gico
|
||||
|
||||
```yaml
|
||||
Backend:
|
||||
- NestJS + TypeScript
|
||||
- PostgreSQL 15+
|
||||
- Redis
|
||||
- Bull/BullMQ
|
||||
|
||||
Frontend:
|
||||
- React 18 + Vite
|
||||
- TailwindCSS
|
||||
- Shadcn/UI
|
||||
|
||||
Motor IA:
|
||||
- ComfyUI
|
||||
- Stable Diffusion XL
|
||||
- ComfyDeploy
|
||||
|
||||
Automatizaci贸n:
|
||||
- n8n
|
||||
|
||||
Almacenamiento:
|
||||
- S3/MinIO
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Estructura de Documentaci贸n
|
||||
|
||||
```
|
||||
projects/platform_marketing_content/
|
||||
鉁斺攢鉁? docs/
|
||||
鉁? 鉁斺攢鉁? 00-vision-general/ # Visi贸n, arquitectura, glosario
|
||||
鉁? 鉁斺攢鉁? 01-analisis-referencias/ # Investigaci贸n, benchmarks
|
||||
鉁? 鉁斺攢鉁? 02-definicion-modulos/ # Especificaciones por m贸dulo
|
||||
鉁? 鉁斺攢鉁? 03-requerimientos/ # Requerimientos funcionales
|
||||
鉁? 鉁斺攢鉁? 04-modelado/ # Modelos de dominio, DB design
|
||||
鉁? 鉁斺攢鉁? 05-user-stories/ # Historias de usuario
|
||||
鉁? 鉁斺攢鉁? 95-guias-desarrollo/ # Gu铆as y convenciones
|
||||
鉁? 鉁斺攢鉁? 97-adr/ # Decisiones arquitect贸nicas
|
||||
鉁?
|
||||
鉁斺攢鉁? orchestration/
|
||||
鉁? 鉁斺攢鉁? 00-guidelines/ # Contexto, herencias
|
||||
鉁? 鉁斺攢鉁? inventarios/ # Inventarios de implementaci贸n
|
||||
鉁? 鉁斺攢鉁? trazas/ # Trazas de tareas
|
||||
鉁?
|
||||
鉁斺攢鉁? apps/
|
||||
鉁斺攢鉁? backend/ # C贸digo NestJS
|
||||
鉁斺攢鉁? frontend/ # C贸digo React
|
||||
鉁斺攢鉁? comfyui/ # Workflows ComfyUI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## M贸dulos Funcionales
|
||||
|
||||
| ID | M贸dulo | Descripci贸n | Prioridad |
|
||||
|----|--------|-------------|-----------|
|
||||
| PMC-001 | Tenants | Arquitectura multi-tenant | Alta |
|
||||
| PMC-002 | CRM | Clientes, marcas, productos | Alta |
|
||||
| PMC-003 | Projects | Proyectos y campa帽as | Alta |
|
||||
| PMC-004 | Generation | Motor de generaci贸n IA | Alta |
|
||||
| PMC-005 | Automation | Flujos automatizados | Media |
|
||||
| PMC-006 | Assets | DAM - biblioteca de activos | Alta |
|
||||
| PMC-007 | Admin | Administraci贸n SaaS | Media |
|
||||
| PMC-008 | Analytics | Reportes y dashboards | Baja |
|
||||
|
||||
---
|
||||
|
||||
## Aliases del Proyecto
|
||||
|
||||
```yaml
|
||||
# Documentaci贸n
|
||||
@PMC_DOCS: projects/platform_marketing_content/docs/
|
||||
@PMC_VISION: projects/platform_marketing_content/docs/00-vision-general/
|
||||
@PMC_MODULES: projects/platform_marketing_content/docs/02-definicion-modulos/
|
||||
@PMC_REQS: projects/platform_marketing_content/docs/03-requerimientos/
|
||||
@PMC_ADR: projects/platform_marketing_content/docs/97-adr/
|
||||
|
||||
# Orchestration
|
||||
@PMC_ORCH: projects/platform_marketing_content/orchestration/
|
||||
@PMC_INVENTORY: projects/platform_marketing_content/orchestration/inventarios/
|
||||
@PMC_TRAZA: projects/platform_marketing_content/orchestration/trazas/
|
||||
|
||||
# C贸digo
|
||||
@PMC_BACKEND: projects/platform_marketing_content/apps/backend/
|
||||
@PMC_FRONTEND: projects/platform_marketing_content/apps/frontend/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencias del Cat谩logo Core
|
||||
|
||||
Funcionalidades reutilizables del cat谩logo:
|
||||
|
||||
```yaml
|
||||
Requeridas:
|
||||
- @CATALOG_AUTH: Autenticaci贸n JWT + OAuth
|
||||
- @CATALOG_SESSION: Gesti贸n de sesiones
|
||||
- @CATALOG_TENANT: Multi-tenancy (adaptar)
|
||||
- @CATALOG_NOTIFY: Notificaciones
|
||||
|
||||
Opcionales:
|
||||
- @CATALOG_RATELIMIT: Rate limiting
|
||||
- @CATALOG_FLAGS: Feature flags
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Roadmap de Fases
|
||||
|
||||
```yaml
|
||||
Fase 1 - MVP Core (Semanas 1-8):
|
||||
- Arquitectura base
|
||||
- CRM b谩sico
|
||||
- Motor de generaci贸n (2-3 workflows)
|
||||
- DAM b谩sico
|
||||
- Admin usuarios
|
||||
|
||||
Fase 2 - Personalizaci贸n (Semanas 9-14):
|
||||
- LoRAs por marca
|
||||
- Avatares consistentes
|
||||
- Integraci贸n CRM鈫扜eneraci贸n
|
||||
|
||||
Fase 3 - Contenido Enriquecido (Semanas 15-22):
|
||||
- GIFs/cinemagraphs
|
||||
- Video b谩sico
|
||||
- Portal cliente
|
||||
|
||||
Fase 4 - Multi-tenant Comercial (Semanas 23+):
|
||||
- SaaS p煤blico
|
||||
- Planes de suscripci贸n
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contactos y Responsables
|
||||
|
||||
```yaml
|
||||
Product Owner: [Por definir]
|
||||
Tech Lead: [Por definir]
|
||||
Requirements Analyst: Agente IA
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Referencias
|
||||
|
||||
- [VISION-GENERAL.md](../../docs/00-vision-general/VISION-GENERAL.md)
|
||||
- [ARQUITECTURA-TECNICA.md](../../docs/00-vision-general/ARQUITECTURA-TECNICA.md)
|
||||
- [GLOSARIO.md](../../docs/00-vision-general/GLOSARIO.md)
|
||||
|
||||
---
|
||||
|
||||
**Documento generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,161 @@
|
||||
# Próxima Acción - Platform Marketing Content
|
||||
|
||||
**Fecha:** 2025-12-08
|
||||
**Estado Actual:** Documentación Completada
|
||||
|
||||
---
|
||||
|
||||
## Resumen de Progreso
|
||||
|
||||
### Completado
|
||||
|
||||
- [x] Visión general del proyecto consolidada
|
||||
- [x] Glosario de términos del dominio
|
||||
- [x] Arquitectura técnica detallada
|
||||
- [x] Definición de 8 módulos funcionales
|
||||
- [x] Contexto de proyecto para orchestration
|
||||
- [x] Inventario maestro (MASTER_INVENTORY.yml)
|
||||
|
||||
### Estructura de Documentación Creada
|
||||
|
||||
```
|
||||
projects/platform_marketing_content/
|
||||
├── docs/
|
||||
│ ├── 00-vision-general/
|
||||
│ │ ├── VISION-GENERAL.md ✅
|
||||
│ │ ├── ARQUITECTURA-TECNICA.md ✅
|
||||
│ │ ├── GLOSARIO.md ✅
|
||||
│ │ └── MVP_Plataforma_SaaS...md (original)
|
||||
│ ├── 01-analisis-referencias/ (vacío)
|
||||
│ ├── 02-definicion-modulos/
|
||||
│ │ ├── _INDEX.md ✅
|
||||
│ │ ├── PMC-001-TENANTS.md ✅
|
||||
│ │ ├── PMC-002-CRM.md ✅
|
||||
│ │ ├── PMC-003-PROJECTS.md ✅
|
||||
│ │ ├── PMC-004-GENERATION.md ✅
|
||||
│ │ ├── PMC-005-AUTOMATION.md ✅
|
||||
│ │ ├── PMC-006-ASSETS.md ✅
|
||||
│ │ ├── PMC-007-ADMIN.md ✅
|
||||
│ │ └── PMC-008-ANALYTICS.md ✅
|
||||
│ ├── 03-requerimientos/ (pendiente)
|
||||
│ ├── 04-modelado/ (pendiente)
|
||||
│ ├── 05-user-stories/ (pendiente)
|
||||
│ ├── 95-guias-desarrollo/ (pendiente)
|
||||
│ └── 97-adr/ (pendiente)
|
||||
│
|
||||
└── orchestration/
|
||||
├── 00-guidelines/
|
||||
│ └── CONTEXTO-PROYECTO.md ✅
|
||||
├── inventarios/
|
||||
│ └── MASTER_INVENTORY.yml ✅
|
||||
└── trazas/ (vacío)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Próximas Acciones Sugeridas
|
||||
|
||||
### Opción A: Continuar Documentación (Requirements-Analyst)
|
||||
|
||||
```yaml
|
||||
Prioridad: Alta
|
||||
Agente: Requirements-Analyst
|
||||
Tareas:
|
||||
1. Crear requerimientos funcionales detallados:
|
||||
- docs/03-requerimientos/RF-PMC-001-TENANTS.md
|
||||
- docs/03-requerimientos/RF-PMC-002-CRM.md
|
||||
- ... (por cada módulo)
|
||||
|
||||
2. Crear modelo de datos consolidado:
|
||||
- docs/04-modelado/MODELO-DOMINIO.md
|
||||
- docs/04-modelado/ESQUEMA-BD.md
|
||||
|
||||
3. Crear user stories para MVP:
|
||||
- docs/05-user-stories/EPIC-001-SETUP.md
|
||||
- docs/05-user-stories/EPIC-002-CRM.md
|
||||
- ...
|
||||
|
||||
Estimación: 4-6 horas de trabajo de agente
|
||||
```
|
||||
|
||||
### Opción B: Iniciar Implementación (Feature-Developer)
|
||||
|
||||
```yaml
|
||||
Prioridad: Alta
|
||||
Agente: Feature-Developer
|
||||
Prerequisitos:
|
||||
- Documentación actual es suficiente para MVP
|
||||
- Usar docs/02-definicion-modulos/* como referencia
|
||||
|
||||
Tareas:
|
||||
1. Setup del proyecto backend:
|
||||
- Crear proyecto NestJS
|
||||
- Configurar TypeORM + PostgreSQL
|
||||
- Implementar estructura de módulos
|
||||
|
||||
2. Implementar PMC-001-TENANTS:
|
||||
- Entidades: Tenant, Plan
|
||||
- RLS para multi-tenancy
|
||||
- Endpoints básicos
|
||||
|
||||
3. Implementar PMC-007-ADMIN (parcial):
|
||||
- User, Role, permisos
|
||||
- Autenticación JWT
|
||||
|
||||
Estimación: 16-24 horas para setup + 2 módulos base
|
||||
```
|
||||
|
||||
### Opción C: Revisar y Validar (Documentation-Validator)
|
||||
|
||||
```yaml
|
||||
Prioridad: Media
|
||||
Agente: Documentation-Validator
|
||||
Tareas:
|
||||
1. Validar consistencia entre documentos
|
||||
2. Verificar que todas las entidades estén definidas
|
||||
3. Revisar endpoints duplicados o faltantes
|
||||
4. Generar checklist de validación
|
||||
|
||||
Estimación: 2-3 horas
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recomendación
|
||||
|
||||
Para avanzar de manera eficiente, se sugiere:
|
||||
|
||||
1. **Si se quiere documentación exhaustiva**: Continuar con Opción A
|
||||
2. **Si se quiere iniciar desarrollo pronto**: Ir con Opción B usando la documentación actual
|
||||
3. **Enfoque híbrido**: Ejecutar Opción B mientras otro agente avanza en Opción A
|
||||
|
||||
La documentación actual (8 módulos definidos con entidades, funcionalidades, APIs) es suficiente para comenzar implementación del MVP.
|
||||
|
||||
---
|
||||
|
||||
## Comandos de Activación
|
||||
|
||||
```bash
|
||||
# Para continuar documentación
|
||||
@Requirements-Analyst continuar con requerimientos funcionales para PMC
|
||||
|
||||
# Para iniciar implementación
|
||||
@Feature-Developer setup proyecto backend PMC con módulos Tenants y Admin
|
||||
|
||||
# Para validación
|
||||
@Documentation-Validator validar documentación de PMC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notas Adicionales
|
||||
|
||||
- El proyecto usa stack NestJS + React + ComfyUI
|
||||
- Priorizar módulos: PMC-001, PMC-007, PMC-002, PMC-006 (en ese orden)
|
||||
- La integración con ComfyUI (PMC-004) requiere servidor GPU configurado
|
||||
- n8n (PMC-005) puede ejecutarse en contenedor Docker local
|
||||
|
||||
---
|
||||
|
||||
**Generado por:** Requirements-Analyst
|
||||
**Fecha:** 2025-12-08
|
||||
@ -0,0 +1,339 @@
|
||||
# MASTER_INVENTORY.yml - Platform Marketing Content
|
||||
# Inventario maestro de implementación
|
||||
# Versión: 1.0.0
|
||||
# Fecha: 2025-12-08
|
||||
|
||||
project:
|
||||
name: Platform Marketing Content
|
||||
alias: PMC
|
||||
nivel_simco: NIVEL_2B
|
||||
status: documentacion
|
||||
|
||||
# =============================================================================
|
||||
# MÓDULOS
|
||||
# =============================================================================
|
||||
modules:
|
||||
PMC-001-TENANTS:
|
||||
name: Tenants
|
||||
priority: alta
|
||||
status: definido
|
||||
doc_path: docs/02-definicion-modulos/PMC-001-TENANTS.md
|
||||
dependencies:
|
||||
catalog:
|
||||
- "@CATALOG_TENANT"
|
||||
entities:
|
||||
- Tenant
|
||||
- Plan
|
||||
endpoints_count: 8
|
||||
features_count: 10
|
||||
|
||||
PMC-002-CRM:
|
||||
name: CRM
|
||||
priority: alta
|
||||
status: definido
|
||||
doc_path: docs/02-definicion-modulos/PMC-002-CRM.md
|
||||
dependencies:
|
||||
modules:
|
||||
- PMC-001-TENANTS
|
||||
- PMC-003-PROJECTS
|
||||
- PMC-004-GENERATION
|
||||
- PMC-006-ASSETS
|
||||
entities:
|
||||
- Client
|
||||
- Contact
|
||||
- Brand
|
||||
- Product
|
||||
- Opportunity
|
||||
endpoints_count: 25
|
||||
features_count: 18
|
||||
|
||||
PMC-003-PROJECTS:
|
||||
name: Projects
|
||||
priority: alta
|
||||
status: definido
|
||||
doc_path: docs/02-definicion-modulos/PMC-003-PROJECTS.md
|
||||
dependencies:
|
||||
modules:
|
||||
- PMC-001-TENANTS
|
||||
- PMC-002-CRM
|
||||
- PMC-004-GENERATION
|
||||
- PMC-006-ASSETS
|
||||
entities:
|
||||
- Project
|
||||
- Campaign
|
||||
- CampaignAsset
|
||||
endpoints_count: 20
|
||||
features_count: 16
|
||||
|
||||
PMC-004-GENERATION:
|
||||
name: Generation (Motor IA)
|
||||
priority: alta
|
||||
status: definido
|
||||
doc_path: docs/02-definicion-modulos/PMC-004-GENERATION.md
|
||||
dependencies:
|
||||
modules:
|
||||
- PMC-001-TENANTS
|
||||
- PMC-002-CRM
|
||||
- PMC-003-PROJECTS
|
||||
- PMC-006-ASSETS
|
||||
catalog:
|
||||
- "@CATALOG_RATELIMIT"
|
||||
external:
|
||||
- ComfyUI
|
||||
- OpenAI/Claude API
|
||||
- Redis
|
||||
- S3/MinIO
|
||||
entities:
|
||||
- GenerationJob
|
||||
- WorkflowTemplate
|
||||
- CustomModel
|
||||
- TextGeneration
|
||||
endpoints_count: 18
|
||||
features_count: 20
|
||||
workflows_predefinidos:
|
||||
- product_photo_synthetic
|
||||
- social_media_post
|
||||
- ad_variations
|
||||
- virtual_avatar
|
||||
|
||||
PMC-005-AUTOMATION:
|
||||
name: Automation
|
||||
priority: media
|
||||
status: definido
|
||||
doc_path: docs/02-definicion-modulos/PMC-005-AUTOMATION.md
|
||||
dependencies:
|
||||
modules:
|
||||
- PMC-001-TENANTS
|
||||
- PMC-002-CRM
|
||||
- PMC-003-PROJECTS
|
||||
- PMC-004-GENERATION
|
||||
- PMC-006-ASSETS
|
||||
external:
|
||||
- n8n
|
||||
- SMTP/SendGrid
|
||||
entities:
|
||||
- AutomationFlow
|
||||
- AutomationRun
|
||||
- WebhookEndpoint
|
||||
endpoints_count: 15
|
||||
features_count: 12
|
||||
flows_predefinidos:
|
||||
- product_asset_kit
|
||||
- campaign_initial_batch
|
||||
- campaign_delivery_prep
|
||||
- job_failure_handler
|
||||
|
||||
PMC-006-ASSETS:
|
||||
name: Assets (DAM)
|
||||
priority: alta
|
||||
status: definido
|
||||
doc_path: docs/02-definicion-modulos/PMC-006-ASSETS.md
|
||||
dependencies:
|
||||
modules:
|
||||
- PMC-001-TENANTS
|
||||
- PMC-003-PROJECTS
|
||||
- PMC-004-GENERATION
|
||||
external:
|
||||
- S3/MinIO
|
||||
- Sharp
|
||||
entities:
|
||||
- Asset
|
||||
- AssetVersion
|
||||
- Collection
|
||||
- AssetComment
|
||||
- Download
|
||||
endpoints_count: 25
|
||||
features_count: 18
|
||||
|
||||
PMC-007-ADMIN:
|
||||
name: Admin
|
||||
priority: media
|
||||
status: definido
|
||||
doc_path: docs/02-definicion-modulos/PMC-007-ADMIN.md
|
||||
dependencies:
|
||||
modules:
|
||||
- PMC-001-TENANTS
|
||||
catalog:
|
||||
- "@CATALOG_AUTH"
|
||||
- "@CATALOG_SESSION"
|
||||
entities:
|
||||
- User
|
||||
- Role
|
||||
- Invitation
|
||||
- AuditLog
|
||||
- Setting
|
||||
endpoints_count: 20
|
||||
features_count: 14
|
||||
roles_sistema:
|
||||
- super_admin
|
||||
- tenant_admin
|
||||
- creative
|
||||
- analyst
|
||||
- viewer
|
||||
- client_portal
|
||||
|
||||
PMC-008-ANALYTICS:
|
||||
name: Analytics
|
||||
priority: baja
|
||||
status: definido
|
||||
doc_path: docs/02-definicion-modulos/PMC-008-ANALYTICS.md
|
||||
dependencies:
|
||||
modules:
|
||||
- PMC-001-TENANTS
|
||||
- PMC-003-PROJECTS
|
||||
- PMC-004-GENERATION
|
||||
- PMC-006-ASSETS
|
||||
external:
|
||||
- Redis
|
||||
entities:
|
||||
- Metric
|
||||
- Report
|
||||
- SavedView
|
||||
endpoints_count: 12
|
||||
features_count: 10
|
||||
dashboards:
|
||||
- home
|
||||
- production
|
||||
- campaigns
|
||||
- resources
|
||||
|
||||
# =============================================================================
|
||||
# STACK TECNOLÓGICO
|
||||
# =============================================================================
|
||||
tech_stack:
|
||||
backend:
|
||||
framework: NestJS
|
||||
language: TypeScript
|
||||
database: PostgreSQL 15+
|
||||
cache: Redis
|
||||
queue: Bull/BullMQ
|
||||
orm: TypeORM
|
||||
|
||||
frontend:
|
||||
framework: React 18
|
||||
bundler: Vite
|
||||
styling: TailwindCSS
|
||||
components: Shadcn/UI
|
||||
state: Zustand o React Query
|
||||
|
||||
ia_engine:
|
||||
image_generation: ComfyUI + SDXL
|
||||
text_generation: OpenAI API / Claude API
|
||||
deployment: ComfyDeploy
|
||||
|
||||
automation:
|
||||
orchestrator: n8n
|
||||
|
||||
storage:
|
||||
files: S3/MinIO
|
||||
|
||||
infrastructure:
|
||||
containers: Docker + Docker Compose
|
||||
gpu: NVIDIA (12-24GB VRAM)
|
||||
|
||||
# =============================================================================
|
||||
# CATÁLOGO DE DEPENDENCIAS
|
||||
# =============================================================================
|
||||
catalog_dependencies:
|
||||
required:
|
||||
- id: "@CATALOG_AUTH"
|
||||
description: Autenticación JWT + OAuth
|
||||
module: PMC-007-ADMIN
|
||||
|
||||
- id: "@CATALOG_SESSION"
|
||||
description: Gestión de sesiones
|
||||
module: PMC-007-ADMIN
|
||||
|
||||
- id: "@CATALOG_TENANT"
|
||||
description: Multi-tenancy (adaptar)
|
||||
module: PMC-001-TENANTS
|
||||
|
||||
optional:
|
||||
- id: "@CATALOG_RATELIMIT"
|
||||
description: Rate limiting
|
||||
module: PMC-004-GENERATION
|
||||
|
||||
- id: "@CATALOG_NOTIFY"
|
||||
description: Notificaciones
|
||||
module: PMC-005-AUTOMATION
|
||||
|
||||
# =============================================================================
|
||||
# FASES DE IMPLEMENTACIÓN
|
||||
# =============================================================================
|
||||
roadmap:
|
||||
fase_1_mvp_core:
|
||||
duration: "Semanas 1-8"
|
||||
modules:
|
||||
- PMC-001-TENANTS
|
||||
- PMC-007-ADMIN
|
||||
- PMC-002-CRM
|
||||
- PMC-006-ASSETS
|
||||
- PMC-003-PROJECTS
|
||||
- PMC-004-GENERATION
|
||||
deliverables:
|
||||
- Arquitectura base multi-tenant
|
||||
- Autenticación y usuarios
|
||||
- CRM básico (clientes, marcas, productos)
|
||||
- DAM básico
|
||||
- Campañas con brief
|
||||
- Motor de generación (2-3 workflows)
|
||||
|
||||
fase_2_personalizacion:
|
||||
duration: "Semanas 9-14"
|
||||
modules:
|
||||
- PMC-004-GENERATION (ampliación)
|
||||
- PMC-005-AUTOMATION
|
||||
deliverables:
|
||||
- Entrenamiento de LoRAs
|
||||
- Avatares consistentes
|
||||
- Flujos automatizados CRM → Generation
|
||||
|
||||
fase_3_contenido_enriquecido:
|
||||
duration: "Semanas 15-22"
|
||||
modules:
|
||||
- PMC-004-GENERATION (video)
|
||||
- PMC-008-ANALYTICS
|
||||
deliverables:
|
||||
- GIFs/cinemagraphs
|
||||
- Video básico
|
||||
- Dashboards y reportes
|
||||
- Portal cliente
|
||||
|
||||
fase_4_saas_comercial:
|
||||
duration: "Semanas 23+"
|
||||
modules:
|
||||
- PMC-001-TENANTS (planes)
|
||||
- PMC-007-ADMIN (billing)
|
||||
deliverables:
|
||||
- SaaS público
|
||||
- Planes de suscripción
|
||||
- Onboarding automático
|
||||
|
||||
# =============================================================================
|
||||
# MÉTRICAS
|
||||
# =============================================================================
|
||||
metrics:
|
||||
total_modules: 8
|
||||
total_entities: 28
|
||||
total_endpoints: ~143
|
||||
total_features: 118
|
||||
features_priority:
|
||||
alta: 64
|
||||
media: 41
|
||||
baja: 13
|
||||
documentation_status:
|
||||
vision_general: completado
|
||||
glosario: completado
|
||||
arquitectura: completado
|
||||
modulos: completado
|
||||
requerimientos: pendiente
|
||||
user_stories: pendiente
|
||||
|
||||
# =============================================================================
|
||||
# METADATOS
|
||||
# =============================================================================
|
||||
metadata:
|
||||
created_by: Requirements-Analyst
|
||||
created_at: "2025-12-08"
|
||||
last_updated: "2025-12-08"
|
||||
version: "1.0.0"
|
||||
Loading…
Reference in New Issue
Block a user