From 2781837d9e7612debd650041a7063d08f037327d Mon Sep 17 00:00:00 2001 From: rckrdmrd Date: Mon, 8 Dec 2025 11:34:35 -0600 Subject: [PATCH] feat: Add SaaS products architecture and alignment analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- core/orchestration/directivas/simco/_INDEX.md | 23 + .../impactos/IMPACTO-CAMBIOS-API.md | 669 ++++++++++++ .../impactos/IMPACTO-CAMBIOS-BACKEND.md | 498 +++++++++ .../impactos/IMPACTO-CAMBIOS-DDL.md | 428 ++++++++ .../impactos/IMPACTO-CAMBIOS-ENTITY.md | 439 ++++++++ .../impactos/MATRIZ-DEPENDENCIAS.md | 445 ++++++++ core/orchestration/patrones/ANTIPATRONES.md | 714 +++++++++++++ .../patrones/MAPEO-TIPOS-DDL-TYPESCRIPT.md | 499 +++++++++ .../patrones/NOMENCLATURA-UNIFICADA.md | 539 ++++++++++ .../patrones/PATRON-CONFIGURACION.md | 745 ++++++++++++++ .../patrones/PATRON-EXCEPTION-HANDLING.md | 534 ++++++++++ core/orchestration/patrones/PATRON-LOGGING.md | 663 ++++++++++++ .../patrones/PATRON-PERFORMANCE.md | 657 ++++++++++++ .../patrones/PATRON-SEGURIDAD.md | 778 ++++++++++++++ core/orchestration/patrones/PATRON-TESTING.md | 727 +++++++++++++ .../patrones/PATRON-TRANSACCIONES.md | 678 ++++++++++++ .../patrones/PATRON-VALIDACION.md | 619 +++++++++++ .../procesos/ORDEN-IMPLEMENTACION.md | 413 ++++++++ core/orchestration/referencias/ALIASES.yml | 32 + ...NALISIS-ALINEACION-WORKSPACE-2025-12-08.md | 434 ++++++++ orchestration/WORKSPACE-STATUS.md | 27 +- .../04-modelado/MAPEO-SPECS-VERTICALES.md | 211 ++++ .../inventarios/MASTER_INVENTORY.yml | 66 +- .../apps/products/erp-basico/README.md | 191 ++++ .../00-guidelines/CONTEXTO-PROYECTO.md | 238 +++++ .../apps/products/pos-micro/README.md | 139 +++ .../00-guidelines/CONTEXTO-PROYECTO.md | 164 +++ projects/erp-suite/apps/saas/README.md | 198 ++++ .../apps/saas/orchestration/CONTEXTO-SAAS.md | 122 +++ .../clinicas/database/HERENCIA-ERP-CORE.md | 213 ++++ .../00-guidelines/HERENCIA-SPECS-CORE.md | 199 ++++ .../orchestration/inventarios/README.md | 103 ++ .../controllers/fraccionamiento.controller.ts | 157 +++ .../modules/construction/controllers/index.ts | 7 + .../controllers/proyecto.controller.ts | 165 +++ .../services/fraccionamiento.service.ts | 117 +++ .../modules/construction/services/index.ts | 7 + .../construction/services/proyecto.service.ts | 117 +++ .../hse/entities/capacitacion.entity.ts | 74 ++ .../hse/entities/incidente-accion.entity.ts | 71 ++ .../entities/incidente-involucrado.entity.ts | 58 ++ .../modules/hse/entities/incidente.entity.ts | 111 ++ .../backend/src/modules/hse/entities/index.ts | 23 + .../construccion/backend/src/server.ts | 12 +- .../00-guidelines/HERENCIA-SPECS-CORE.md | 159 +++ .../inventarios/BACKEND_INVENTORY.yml | 37 +- .../00-guidelines/HERENCIA-SPECS-CORE.md | 169 +++ .../orchestration/inventarios/README.md | 106 ++ .../retail/database/HERENCIA-ERP-CORE.md | 187 ++++ .../docs/00-vision-general/VISION-RETAIL.md | 97 ++ .../02-definicion-modulos/INDICE-MODULOS.md | 116 +++ .../00-guidelines/HERENCIA-SPECS-CORE.md | 184 ++++ .../orchestration/inventarios/README.md | 95 ++ .../database/HERENCIA-ERP-CORE.md | 182 ++++ .../docs/00-vision-general/VISION-VIDRIO.md | 104 ++ .../02-definicion-modulos/INDICE-MODULOS.md | 154 +++ .../VT-001-fundamentos/README.md | 21 + .../VT-002-cotizaciones/README.md | 26 + .../VT-003-produccion/README.md | 33 + .../VT-004-inventario/README.md | 27 + .../VT-005-corte/README.md | 26 + .../VT-006-templado/README.md | 28 + .../VT-007-calidad/README.md | 28 + .../VT-008-despacho/README.md | 31 + .../docs/08-epicas/EPIC-VT-001-fundamentos.md | 65 ++ .../00-guidelines/HERENCIA-SPECS-CORE.md | 175 ++++ .../orchestration/inventarios/README.md | 94 ++ .../orchestration/inventarios/REFERENCIAS.yml | 179 +++- .../orchestration/inventarios/STATUS.yml | 32 +- .../inventarios/SUITE_MASTER_INVENTORY.yml | 451 ++++++-- .../00-vision-general/ARQUITECTURA-TECNICA.md | 967 ++++++++++++++++++ .../docs/00-vision-general/GLOSARIO.md | 199 ++++ ...o de Morfeo Academy y Desarrollo de una .pdf | Bin 0 -> 886415 bytes .../MVP_Plataforma_SaaS_Contenido_CRM.md | 327 ++++++ .../docs/00-vision-general/VISION-GENERAL.md | 466 +++++++++ .../02-definicion-modulos/PMC-001-TENANTS.md | 275 +++++ .../docs/02-definicion-modulos/PMC-002-CRM.md | 387 +++++++ .../02-definicion-modulos/PMC-003-PROJECTS.md | 381 +++++++ .../PMC-004-GENERATION.md | 489 +++++++++ .../PMC-005-AUTOMATION.md | 447 ++++++++ .../02-definicion-modulos/PMC-006-ASSETS.md | 449 ++++++++ .../02-definicion-modulos/PMC-007-ADMIN.md | 495 +++++++++ .../PMC-008-ANALYTICS.md | 443 ++++++++ .../docs/02-definicion-modulos/_INDEX.md | 148 +++ .../03-requerimientos/RF-PMC-001-TENANTS.md | 512 ++++++++++ .../docs/03-requerimientos/RF-PMC-002-CRM.md | 587 +++++++++++ .../03-requerimientos/RF-PMC-003-PROJECTS.md | 503 +++++++++ .../RF-PMC-004-GENERATION.md | 641 ++++++++++++ .../RF-PMC-005-AUTOMATION.md | 414 ++++++++ .../03-requerimientos/RF-PMC-006-ASSETS.md | 511 +++++++++ .../03-requerimientos/RF-PMC-007-ADMIN.md | 438 ++++++++ .../03-requerimientos/RF-PMC-008-ANALYTICS.md | 356 +++++++ .../docs/03-requerimientos/_INDEX.md | 58 ++ .../docs/04-modelado/MODELO-DOMINIO.md | 275 +++++ .../00-guidelines/CONTEXTO-PROYECTO.md | 188 ++++ .../orchestration/PROXIMA-ACCION.md | 161 +++ .../inventarios/MASTER_INVENTORY.yml | 339 ++++++ 97 files changed, 26437 insertions(+), 149 deletions(-) create mode 100644 core/orchestration/impactos/IMPACTO-CAMBIOS-API.md create mode 100644 core/orchestration/impactos/IMPACTO-CAMBIOS-BACKEND.md create mode 100644 core/orchestration/impactos/IMPACTO-CAMBIOS-DDL.md create mode 100644 core/orchestration/impactos/IMPACTO-CAMBIOS-ENTITY.md create mode 100644 core/orchestration/impactos/MATRIZ-DEPENDENCIAS.md create mode 100644 core/orchestration/patrones/ANTIPATRONES.md create mode 100644 core/orchestration/patrones/MAPEO-TIPOS-DDL-TYPESCRIPT.md create mode 100644 core/orchestration/patrones/NOMENCLATURA-UNIFICADA.md create mode 100644 core/orchestration/patrones/PATRON-CONFIGURACION.md create mode 100644 core/orchestration/patrones/PATRON-EXCEPTION-HANDLING.md create mode 100644 core/orchestration/patrones/PATRON-LOGGING.md create mode 100644 core/orchestration/patrones/PATRON-PERFORMANCE.md create mode 100644 core/orchestration/patrones/PATRON-SEGURIDAD.md create mode 100644 core/orchestration/patrones/PATRON-TESTING.md create mode 100644 core/orchestration/patrones/PATRON-TRANSACCIONES.md create mode 100644 core/orchestration/patrones/PATRON-VALIDACION.md create mode 100644 core/orchestration/procesos/ORDEN-IMPLEMENTACION.md create mode 100644 orchestration/ANALISIS-ALINEACION-WORKSPACE-2025-12-08.md create mode 100644 projects/erp-suite/apps/erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md create mode 100644 projects/erp-suite/apps/products/erp-basico/README.md create mode 100644 projects/erp-suite/apps/products/erp-basico/orchestration/00-guidelines/CONTEXTO-PROYECTO.md create mode 100644 projects/erp-suite/apps/products/pos-micro/README.md create mode 100644 projects/erp-suite/apps/products/pos-micro/orchestration/00-guidelines/CONTEXTO-PROYECTO.md create mode 100644 projects/erp-suite/apps/saas/README.md create mode 100644 projects/erp-suite/apps/saas/orchestration/CONTEXTO-SAAS.md create mode 100644 projects/erp-suite/apps/verticales/clinicas/database/HERENCIA-ERP-CORE.md create mode 100644 projects/erp-suite/apps/verticales/clinicas/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md create mode 100644 projects/erp-suite/apps/verticales/clinicas/orchestration/inventarios/README.md create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/fraccionamiento.controller.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/index.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/proyecto.controller.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/fraccionamiento.service.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/index.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/proyecto.service.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/capacitacion.entity.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente-accion.entity.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente-involucrado.entity.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente.entity.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/index.ts create mode 100644 projects/erp-suite/apps/verticales/construccion/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md create mode 100644 projects/erp-suite/apps/verticales/mecanicas-diesel/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md create mode 100644 projects/erp-suite/apps/verticales/mecanicas-diesel/orchestration/inventarios/README.md create mode 100644 projects/erp-suite/apps/verticales/retail/database/HERENCIA-ERP-CORE.md create mode 100644 projects/erp-suite/apps/verticales/retail/docs/00-vision-general/VISION-RETAIL.md create mode 100644 projects/erp-suite/apps/verticales/retail/docs/02-definicion-modulos/INDICE-MODULOS.md create mode 100644 projects/erp-suite/apps/verticales/retail/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md create mode 100644 projects/erp-suite/apps/verticales/retail/orchestration/inventarios/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/database/HERENCIA-ERP-CORE.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/00-vision-general/VISION-VIDRIO.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/INDICE-MODULOS.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-001-fundamentos/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-002-cotizaciones/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-003-produccion/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-004-inventario/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-005-corte/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-006-templado/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-007-calidad/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-008-despacho/README.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/docs/08-epicas/EPIC-VT-001-fundamentos.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md create mode 100644 projects/erp-suite/apps/verticales/vidrio-templado/orchestration/inventarios/README.md create mode 100644 projects/platform_marketing_content/docs/00-vision-general/ARQUITECTURA-TECNICA.md create mode 100644 projects/platform_marketing_content/docs/00-vision-general/GLOSARIO.md create mode 100644 projects/platform_marketing_content/docs/00-vision-general/InvestigaciΓ³n Profunda_ Plataforma de GeneraciΓ³n de Contenido de Morfeo Academy y Desarrollo de una .pdf create mode 100644 projects/platform_marketing_content/docs/00-vision-general/MVP_Plataforma_SaaS_Contenido_CRM.md create mode 100644 projects/platform_marketing_content/docs/00-vision-general/VISION-GENERAL.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/PMC-001-TENANTS.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/PMC-002-CRM.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/PMC-003-PROJECTS.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/PMC-004-GENERATION.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/PMC-005-AUTOMATION.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/PMC-006-ASSETS.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/PMC-007-ADMIN.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/PMC-008-ANALYTICS.md create mode 100644 projects/platform_marketing_content/docs/02-definicion-modulos/_INDEX.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-001-TENANTS.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-002-CRM.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-003-PROJECTS.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-004-GENERATION.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-005-AUTOMATION.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-006-ASSETS.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-007-ADMIN.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-008-ANALYTICS.md create mode 100644 projects/platform_marketing_content/docs/03-requerimientos/_INDEX.md create mode 100644 projects/platform_marketing_content/docs/04-modelado/MODELO-DOMINIO.md create mode 100644 projects/platform_marketing_content/orchestration/00-guidelines/CONTEXTO-PROYECTO.md create mode 100644 projects/platform_marketing_content/orchestration/PROXIMA-ACCION.md create mode 100644 projects/platform_marketing_content/orchestration/inventarios/MASTER_INVENTORY.yml diff --git a/core/orchestration/directivas/simco/_INDEX.md b/core/orchestration/directivas/simco/_INDEX.md index 4287568..d9c4e58 100644 --- a/core/orchestration/directivas/simco/_INDEX.md +++ b/core/orchestration/directivas/simco/_INDEX.md @@ -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) β”‚ diff --git a/core/orchestration/impactos/IMPACTO-CAMBIOS-API.md b/core/orchestration/impactos/IMPACTO-CAMBIOS-API.md new file mode 100644 index 0000000..a80b116 --- /dev/null +++ b/core/orchestration/impactos/IMPACTO-CAMBIOS-API.md @@ -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
{user.legacyCode}
; // ← 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 { + return this.users; +} +// Response: [{ id: 1 }, { id: 2 }] + +// DESPUES: Objeto paginado +@Get() +async findAll(): Promise> { + 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('/users'), + + // Si cambia estructura response + getAllPaginated: async (page: number) => { + const response = await api.get>('/users', { params: { page } }); + return response.data; // Extraer data aqui + }, + + // Si cambia metodo HTTP + update: (id: string, data: UpdateUserDto) => + api.put(`/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 diff --git a/core/orchestration/impactos/IMPACTO-CAMBIOS-BACKEND.md b/core/orchestration/impactos/IMPACTO-CAMBIOS-BACKEND.md new file mode 100644 index 0000000..77d8063 --- /dev/null +++ b/core/orchestration/impactos/IMPACTO-CAMBIOS-BACKEND.md @@ -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 { + // ... +} +``` + +**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 { + // 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 { + // ... +} +``` + +**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 diff --git a/core/orchestration/impactos/IMPACTO-CAMBIOS-DDL.md b/core/orchestration/impactos/IMPACTO-CAMBIOS-DDL.md new file mode 100644 index 0000000..14b425b --- /dev/null +++ b/core/orchestration/impactos/IMPACTO-CAMBIOS-DDL.md @@ -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 + +``` + +--- + +## 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 diff --git a/core/orchestration/impactos/IMPACTO-CAMBIOS-ENTITY.md b/core/orchestration/impactos/IMPACTO-CAMBIOS-ENTITY.md new file mode 100644 index 0000000..7c7974a --- /dev/null +++ b/core/orchestration/impactos/IMPACTO-CAMBIOS-ENTITY.md @@ -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 diff --git a/core/orchestration/impactos/MATRIZ-DEPENDENCIAS.md b/core/orchestration/impactos/MATRIZ-DEPENDENCIAS.md new file mode 100644 index 0000000..4b3830f --- /dev/null +++ b/core/orchestration/impactos/MATRIZ-DEPENDENCIAS.md @@ -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 diff --git a/core/orchestration/patrones/ANTIPATRONES.md b/core/orchestration/patrones/ANTIPATRONES.md new file mode 100644 index 0000000..686630b --- /dev/null +++ b/core/orchestration/patrones/ANTIPATRONES.md @@ -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, + ) {} + + 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
{user?.name}
; +}; + +// βœ… 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 ; + if (error) return ; + + return
{user.name}
; +}; +``` + +**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 + + + + + // Finalmente se usa + + + + + +// βœ… CORRECTO - Context para datos compartidos +const UserContext = createContext(null); +const useUser = () => useContext(UserContext); + + + + + + + // Usa useUser() internamente + + + + + +``` + +**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 diff --git a/core/orchestration/patrones/MAPEO-TIPOS-DDL-TYPESCRIPT.md b/core/orchestration/patrones/MAPEO-TIPOS-DDL-TYPESCRIPT.md new file mode 100644 index 0000000..209be33 --- /dev/null +++ b/core/orchestration/patrones/MAPEO-TIPOS-DDL-TYPESCRIPT.md @@ -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; +``` + +--- + +### 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 diff --git a/core/orchestration/patrones/NOMENCLATURA-UNIFICADA.md b/core/orchestration/patrones/NOMENCLATURA-UNIFICADA.md new file mode 100644 index 0000000..e3c552f --- /dev/null +++ b/core/orchestration/patrones/NOMENCLATURA-UNIFICADA.md @@ -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 { ... } + async findAll(query: UserQueryDto): Promise { ... } + async findById(id: string): Promise { ... } + async findByEmail(email: string): Promise { ... } + async update(id: string, dto: UpdateUserDto): Promise { ... } + async remove(id: string): Promise { ... } + + // MΓ©todos adicionales + async activate(id: string): Promise { ... } + async deactivate(id: string): Promise { ... } + async assignRole(userId: string, roleId: string): Promise { ... } + async hasPermission(userId: string, permission: string): Promise { ... } +} + +// 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(); +const roleById = {}; // Record +``` + +### 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 = ({ + 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 diff --git a/core/orchestration/patrones/PATRON-CONFIGURACION.md b/core/orchestration/patrones/PATRON-CONFIGURACION.md new file mode 100644 index 0000000..63ad0fe --- /dev/null +++ b/core/orchestration/patrones/PATRON-CONFIGURACION.md @@ -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('jwt'); + } + + async generateToken(userId: string): Promise { + 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 = { + 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 { + 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 diff --git a/core/orchestration/patrones/PATRON-EXCEPTION-HANDLING.md b/core/orchestration/patrones/PATRON-EXCEPTION-HANDLING.md new file mode 100644 index 0000000..9ed9cee --- /dev/null +++ b/core/orchestration/patrones/PATRON-EXCEPTION-HANDLING.md @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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(); + const request = ctx.getRequest(); + + 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 { + 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 diff --git a/core/orchestration/patrones/PATRON-LOGGING.md b/core/orchestration/patrones/PATRON-LOGGING.md new file mode 100644 index 0000000..d5fc9c5 --- /dev/null +++ b/core/orchestration/patrones/PATRON-LOGGING.md @@ -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) { + this.logger.info(message, { + context: this.context, + correlationId: this.correlationId, + ...data, + }); + } + + error(message: string, trace?: string, data?: Record) { + this.logger.error(message, { + context: this.context, + correlationId: this.correlationId, + stack: trace, + ...data, + }); + } + + warn(message: string, data?: Record) { + this.logger.warn(message, { + context: this.context, + correlationId: this.correlationId, + ...data, + }); + } + + debug(message: string, data?: Record) { + 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, + ) { + this.logger.setContext(UserService.name); + } + + async create(dto: CreateUserDto): Promise { + 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 { + 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 { + 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 { + 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; + 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) { + 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) { + this.log('debug', message, context, data); + } + + info(message: string, context?: string, data?: Record) { + this.log('info', message, context, data); + } + + warn(message: string, context?: string, data?: Record) { + this.log('warn', message, context, data); + } + + error(message: string, context?: string, data?: Record) { + 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 { + 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 ||
Something went wrong
; + } + 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): Record { + 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 diff --git a/core/orchestration/patrones/PATRON-PERFORMANCE.md b/core/orchestration/patrones/PATRON-PERFORMANCE.md new file mode 100644 index 0000000..4ed84f4 --- /dev/null +++ b/core/orchestration/patrones/PATRON-PERFORMANCE.md @@ -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 { + 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 { + return this.userRepository.find({ + relations: ['orders'], // TypeORM hace JOIN + }); +} + +// βœ… CORRECTO: QueryBuilder con control +async findAllWithOrders(): Promise { + 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(key: string): Promise { + return this.cacheManager.get(key); + } + + async set(key: string, value: T, ttlSeconds: number): Promise { + await this.cacheManager.set(key, value, ttlSeconds * 1000); + } + + async del(key: string): Promise { + await this.cacheManager.del(key); + } + + async delByPattern(pattern: string): Promise { + 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> { + const cacheKey = this.reflector.get(CACHE_KEY, context.getHandler()); + if (!cacheKey) { + return next.handle(); + } + + const cacheTtl = this.reflector.get(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, + private readonly cacheService: CacheService, + ) {} + + async findAll(query: ProductQueryDto): Promise { + const cacheKey = `products:list:${JSON.stringify(query)}`; + + // Intentar obtener de cache + const cached = await this.cacheService.get(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 { + 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 +}> + + +``` + +### 4.2 Memoizacion + +```typescript +// React.memo para componentes puros +const UserCard = memo(({ user }: { user: User }) => { + return ( +
+

{user.name}

+

{user.email}

+
+ ); +}); + +// 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
    {filteredItems.map(/* ... */)}
; +}; + +// useCallback para funciones estables +const ParentComponent = () => { + const [count, setCount] = useState(0); + + const handleClick = useCallback(() => { + console.log('clicked'); + }, []); // Funcion estable + + return ; +}; +``` + +### 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(null); + + const virtualizer = useVirtualizer({ + count: items.length, + getScrollElement: () => parentRef.current, + estimateSize: () => 50, // Altura estimada de cada item + }); + + return ( +
+
+ {virtualizer.getVirtualItems().map(virtualItem => ( +
+ +
+ ))} +
+
+ ); +}; +``` + +### 4.4 Optimizacion de Imagenes + +```typescript +// Componente de imagen optimizada +const OptimizedImage = ({ + src, + alt, + width, + height, +}: ImageProps) => { + return ( + {alt} + ); +}; + +// Con srcset para responsive +const ResponsiveImage = ({ src, alt }: Props) => { + return ( + {alt} + ); +}; +``` + +### 4.5 Debounce y Throttle + +```typescript +// src/shared/hooks/useDebounce.ts +import { useState, useEffect } from 'react'; + +export function useDebounce(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 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[]> { + 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 { + 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 { + // 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 { + 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 { + 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 diff --git a/core/orchestration/patrones/PATRON-SEGURIDAD.md b/core/orchestration/patrones/PATRON-SEGURIDAD.md new file mode 100644 index 0000000..e7d99be --- /dev/null +++ b/core/orchestration/patrones/PATRON-SEGURIDAD.md @@ -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 { + return bcrypt.hash(password, SALT_ROUNDS); +} + +export async function verifyPassword( + password: string, + hash: string, +): Promise { + 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 { + 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 { + 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 { + 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 { + // 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(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.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('roles', [ + context.getHandler(), + context.getClass(), + ]); + + const requiredPermissions = this.reflector.getAllAndOverride( + '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(); + 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 { + 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 +
+ +// βœ… CORRECTO: Sanitizar primero +import DOMPurify from 'dompurify'; + +
+ +// βœ… MEJOR: Evitar dangerouslySetInnerHTML cuando sea posible +
{userInput}
// 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 diff --git a/core/orchestration/patrones/PATRON-TESTING.md b/core/orchestration/patrones/PATRON-TESTING.md new file mode 100644 index 0000000..84812a6 --- /dev/null +++ b/core/orchestration/patrones/PATRON-TESTING.md @@ -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>; + + // 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); + 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; + + 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); + 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(); + + expect(screen.getByText('Test User')).toBeInTheDocument(); + expect(screen.getByText('test@example.com')).toBeInTheDocument(); + }); + + it('should call onEdit when edit button clicked', () => { + render( + + ); + + fireEvent.click(screen.getByRole('button', { name: /edit/i })); + + expect(mockOnEdit).toHaveBeenCalledWith(mockUser); + }); + + it('should call onDelete when delete button clicked', () => { + render( + + ); + + fireEvent.click(screen.getByRole('button', { name: /delete/i })); + + expect(mockOnDelete).toHaveBeenCalledWith(mockUser.id); + }); + + it('should show loading state', () => { + render(); + + expect(screen.getByTestId('loading-skeleton')).toBeInTheDocument(); + }); + + it('should hide actions when not provided', () => { + render(); + + 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; + +describe('useUsers', () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + + const wrapper = ({ children }) => ( + + {children} + + ); + + 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 diff --git a/core/orchestration/patrones/PATRON-TRANSACCIONES.md b/core/orchestration/patrones/PATRON-TRANSACCIONES.md new file mode 100644 index 0000000..b2838ba --- /dev/null +++ b/core/orchestration/patrones/PATRON-TRANSACCIONES.md @@ -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 { + // 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 { + 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, + ) {} + + private manager: EntityManager; + + @Transactional() + async createOrder(dto: CreateOrderDto): Promise { + // 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 { + 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 { + 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 { + 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 { + this.queryRunner = this.dataSource.createQueryRunner(); + await this.queryRunner.connect(); + await this.queryRunner.startTransaction(); + } + + getRepository(entity: EntityTarget): Repository { + return this.queryRunner.manager.getRepository(entity); + } + + async commit(): Promise { + await this.queryRunner.commitTransaction(); + await this.queryRunner.release(); + } + + async rollback(): Promise { + await this.queryRunner.rollbackTransaction(); + await this.queryRunner.release(); + } +} + +// Uso +@Injectable() +export class OrderService { + constructor(private readonly uow: UnitOfWork) {} + + async createOrder(dto: CreateOrderDto): Promise { + 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; + compensate(): Promise; // Revertir si falla +} + +class CreateOrderSaga { + private executedSteps: SagaStep[] = []; + + async execute(steps: SagaStep[]): Promise { + 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( + operation: () => Promise, + maxRetries: number = 3, + baseDelay: number = 100, +): Promise { + 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 { + 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 { + // 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 { + 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 { + 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 { + 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 { + 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 diff --git a/core/orchestration/patrones/PATRON-VALIDACION.md b/core/orchestration/patrones/PATRON-VALIDACION.md new file mode 100644 index 0000000..9a5f9e8 --- /dev/null +++ b/core/orchestration/patrones/PATRON-VALIDACION.md @@ -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; + +// 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 { + // 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; + +// Uso en componente +const UserForm = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(createUserSchema), + }); + + const onSubmit = async (data: CreateUserForm) => { + // data ya estΓ‘ validado + await userService.create(data); + }; + + return ( +
+ + {errors.email && {errors.email.message}} + {/* ... */} +
+ ); +}; +``` + +--- + +## 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 diff --git a/core/orchestration/procesos/ORDEN-IMPLEMENTACION.md b/core/orchestration/procesos/ORDEN-IMPLEMENTACION.md new file mode 100644 index 0000000..108e436 --- /dev/null +++ b/core/orchestration/procesos/ORDEN-IMPLEMENTACION.md @@ -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 diff --git a/core/orchestration/referencias/ALIASES.yml b/core/orchestration/referencias/ALIASES.yml index 3d85cc1..65295a5 100644 --- a/core/orchestration/referencias/ALIASES.yml +++ b/core/orchestration/referencias/ALIASES.yml @@ -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) # ───────────────────────────────────────────────────────────────────────────────── diff --git a/orchestration/ANALISIS-ALINEACION-WORKSPACE-2025-12-08.md b/orchestration/ANALISIS-ALINEACION-WORKSPACE-2025-12-08.md new file mode 100644 index 0000000..b092de4 --- /dev/null +++ b/orchestration/ANALISIS-ALINEACION-WORKSPACE-2025-12-08.md @@ -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* diff --git a/orchestration/WORKSPACE-STATUS.md b/orchestration/WORKSPACE-STATUS.md index 4ea5eed..8b5c82c 100644 --- a/orchestration/WORKSPACE-STATUS.md +++ b/orchestration/WORKSPACE-STATUS.md @@ -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) diff --git a/projects/erp-suite/apps/erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md b/projects/erp-suite/apps/erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md new file mode 100644 index 0000000..4ab69f5 --- /dev/null +++ b/projects/erp-suite/apps/erp-core/docs/04-modelado/MAPEO-SPECS-VERTICALES.md @@ -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 diff --git a/projects/erp-suite/apps/erp-core/orchestration/inventarios/MASTER_INVENTORY.yml b/projects/erp-suite/apps/erp-core/orchestration/inventarios/MASTER_INVENTORY.yml index 7ced484..1c57ea6 100644 --- a/projects/erp-suite/apps/erp-core/orchestration/inventarios/MASTER_INVENTORY.yml +++ b/projects/erp-suite/apps/erp-core/orchestration/inventarios/MASTER_INVENTORY.yml @@ -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 diff --git a/projects/erp-suite/apps/products/erp-basico/README.md b/projects/erp-suite/apps/products/erp-basico/README.md new file mode 100644 index 0000000..c235527 --- /dev/null +++ b/projects/erp-suite/apps/products/erp-basico/README.md @@ -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* diff --git a/projects/erp-suite/apps/products/erp-basico/orchestration/00-guidelines/CONTEXTO-PROYECTO.md b/projects/erp-suite/apps/products/erp-basico/orchestration/00-guidelines/CONTEXTO-PROYECTO.md new file mode 100644 index 0000000..1f30589 --- /dev/null +++ b/projects/erp-suite/apps/products/erp-basico/orchestration/00-guidelines/CONTEXTO-PROYECTO.md @@ -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* diff --git a/projects/erp-suite/apps/products/pos-micro/README.md b/projects/erp-suite/apps/products/pos-micro/README.md new file mode 100644 index 0000000..8dfcf7b --- /dev/null +++ b/projects/erp-suite/apps/products/pos-micro/README.md @@ -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* diff --git a/projects/erp-suite/apps/products/pos-micro/orchestration/00-guidelines/CONTEXTO-PROYECTO.md b/projects/erp-suite/apps/products/pos-micro/orchestration/00-guidelines/CONTEXTO-PROYECTO.md new file mode 100644 index 0000000..abb6715 --- /dev/null +++ b/projects/erp-suite/apps/products/pos-micro/orchestration/00-guidelines/CONTEXTO-PROYECTO.md @@ -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* diff --git a/projects/erp-suite/apps/saas/README.md b/projects/erp-suite/apps/saas/README.md new file mode 100644 index 0000000..7169ff8 --- /dev/null +++ b/projects/erp-suite/apps/saas/README.md @@ -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* diff --git a/projects/erp-suite/apps/saas/orchestration/CONTEXTO-SAAS.md b/projects/erp-suite/apps/saas/orchestration/CONTEXTO-SAAS.md new file mode 100644 index 0000000..3857af0 --- /dev/null +++ b/projects/erp-suite/apps/saas/orchestration/CONTEXTO-SAAS.md @@ -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* diff --git a/projects/erp-suite/apps/verticales/clinicas/database/HERENCIA-ERP-CORE.md b/projects/erp-suite/apps/verticales/clinicas/database/HERENCIA-ERP-CORE.md new file mode 100644 index 0000000..5030bd2 --- /dev/null +++ b/projects/erp-suite/apps/verticales/clinicas/database/HERENCIA-ERP-CORE.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/clinicas/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md b/projects/erp-suite/apps/verticales/clinicas/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md new file mode 100644 index 0000000..f3bc55d --- /dev/null +++ b/projects/erp-suite/apps/verticales/clinicas/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/clinicas/orchestration/inventarios/README.md b/projects/erp-suite/apps/verticales/clinicas/orchestration/inventarios/README.md new file mode 100644 index 0000000..8020d92 --- /dev/null +++ b/projects/erp-suite/apps/verticales/clinicas/orchestration/inventarios/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/fraccionamiento.controller.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/fraccionamiento.controller.ts new file mode 100644 index 0000000..fd05a6c --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/fraccionamiento.controller.ts @@ -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; diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/index.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/index.ts new file mode 100644 index 0000000..543d60c --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/index.ts @@ -0,0 +1,7 @@ +/** + * Construction Controllers Index + * @module Construction + */ + +export { default as proyectoController } from './proyecto.controller'; +export { default as fraccionamientoController } from './fraccionamiento.controller'; diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/proyecto.controller.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/proyecto.controller.ts new file mode 100644 index 0000000..2a3eb11 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/controllers/proyecto.controller.ts @@ -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; diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/fraccionamiento.service.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/fraccionamiento.service.ts new file mode 100644 index 0000000..604b234 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/fraccionamiento.service.ts @@ -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; + + constructor() { + this.repository = AppDataSource.getRepository(Fraccionamiento); + } + + async findAll(filters: FraccionamientoFilters): Promise { + const where: FindOptionsWhere = { + 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 { + return this.repository.findOne({ + where: { id, tenantId }, + relations: ['proyecto', 'createdBy'], + }); + } + + async findByCodigo(codigo: string, tenantId: string): Promise { + return this.repository.findOne({ + where: { codigo, tenantId }, + }); + } + + async findByProyecto(proyectoId: string, tenantId: string): Promise { + return this.repository.find({ + where: { proyectoId, tenantId }, + order: { codigo: 'ASC' }, + }); + } + + async create(data: CreateFraccionamientoDto): Promise { + const fraccionamiento = this.repository.create(data); + return this.repository.save(fraccionamiento); + } + + async update( + id: string, + tenantId: string, + data: UpdateFraccionamientoDto + ): Promise { + 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 { + const result = await this.repository.delete({ id, tenantId }); + return result.affected ? result.affected > 0 : false; + } + + async countByProyecto(proyectoId: string, tenantId: string): Promise { + return this.repository.count({ + where: { proyectoId, tenantId }, + }); + } +} diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/index.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/index.ts new file mode 100644 index 0000000..e78e8c4 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/index.ts @@ -0,0 +1,7 @@ +/** + * Construction Services Index + * @module Construction + */ + +export * from './proyecto.service'; +export * from './fraccionamiento.service'; diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/proyecto.service.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/proyecto.service.ts new file mode 100644 index 0000000..ae55f2d --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/construction/services/proyecto.service.ts @@ -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; + + constructor() { + this.repository = AppDataSource.getRepository(Proyecto); + } + + async findAll(filters: ProyectoFilters): Promise { + const where: FindOptionsWhere = { + 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 { + return this.repository.findOne({ + where: { id, tenantId }, + relations: ['fraccionamientos', 'createdBy'], + }); + } + + async findByCodigo(codigo: string, tenantId: string): Promise { + return this.repository.findOne({ + where: { codigo, tenantId }, + }); + } + + async create(data: CreateProyectoDto): Promise { + const proyecto = this.repository.create(data); + return this.repository.save(proyecto); + } + + async update(id: string, tenantId: string, data: UpdateProyectoDto): Promise { + 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 { + 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, + }; + } +} diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/capacitacion.entity.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/capacitacion.entity.ts new file mode 100644 index 0000000..ecbd699 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/capacitacion.entity.ts @@ -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; +} diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente-accion.entity.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente-accion.entity.ts new file mode 100644 index 0000000..4fae042 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente-accion.entity.ts @@ -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; +} diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente-involucrado.entity.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente-involucrado.entity.ts new file mode 100644 index 0000000..b92eb67 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente-involucrado.entity.ts @@ -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; +} diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente.entity.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente.entity.ts new file mode 100644 index 0000000..249c5a6 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/incidente.entity.ts @@ -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[]; +} diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/index.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/index.ts new file mode 100644 index 0000000..00cea16 --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/modules/hse/entities/index.ts @@ -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 diff --git a/projects/erp-suite/apps/verticales/construccion/backend/src/server.ts b/projects/erp-suite/apps/verticales/construccion/backend/src/server.ts index 571dcb4..9797f95 100644 --- a/projects/erp-suite/apps/verticales/construccion/backend/src/server.ts +++ b/projects/erp-suite/apps/verticales/construccion/backend/src/server.ts @@ -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 */ diff --git a/projects/erp-suite/apps/verticales/construccion/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md b/projects/erp-suite/apps/verticales/construccion/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md new file mode 100644 index 0000000..c55197b --- /dev/null +++ b/projects/erp-suite/apps/verticales/construccion/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/construccion/orchestration/inventarios/BACKEND_INVENTORY.yml b/projects/erp-suite/apps/verticales/construccion/orchestration/inventarios/BACKEND_INVENTORY.yml index a85da10..debaf97 100644 --- a/projects/erp-suite/apps/verticales/construccion/orchestration/inventarios/BACKEND_INVENTORY.yml +++ b/projects/erp-suite/apps/verticales/construccion/orchestration/inventarios/BACKEND_INVENTORY.yml @@ -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" diff --git a/projects/erp-suite/apps/verticales/mecanicas-diesel/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md b/projects/erp-suite/apps/verticales/mecanicas-diesel/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md new file mode 100644 index 0000000..bf5dae8 --- /dev/null +++ b/projects/erp-suite/apps/verticales/mecanicas-diesel/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/mecanicas-diesel/orchestration/inventarios/README.md b/projects/erp-suite/apps/verticales/mecanicas-diesel/orchestration/inventarios/README.md new file mode 100644 index 0000000..bdff046 --- /dev/null +++ b/projects/erp-suite/apps/verticales/mecanicas-diesel/orchestration/inventarios/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/retail/database/HERENCIA-ERP-CORE.md b/projects/erp-suite/apps/verticales/retail/database/HERENCIA-ERP-CORE.md new file mode 100644 index 0000000..62bf581 --- /dev/null +++ b/projects/erp-suite/apps/verticales/retail/database/HERENCIA-ERP-CORE.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/retail/docs/00-vision-general/VISION-RETAIL.md b/projects/erp-suite/apps/verticales/retail/docs/00-vision-general/VISION-RETAIL.md new file mode 100644 index 0000000..300a88d --- /dev/null +++ b/projects/erp-suite/apps/verticales/retail/docs/00-vision-general/VISION-RETAIL.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/retail/docs/02-definicion-modulos/INDICE-MODULOS.md b/projects/erp-suite/apps/verticales/retail/docs/02-definicion-modulos/INDICE-MODULOS.md new file mode 100644 index 0000000..ada07c9 --- /dev/null +++ b/projects/erp-suite/apps/verticales/retail/docs/02-definicion-modulos/INDICE-MODULOS.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/retail/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md b/projects/erp-suite/apps/verticales/retail/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md new file mode 100644 index 0000000..58041b5 --- /dev/null +++ b/projects/erp-suite/apps/verticales/retail/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/retail/orchestration/inventarios/README.md b/projects/erp-suite/apps/verticales/retail/orchestration/inventarios/README.md new file mode 100644 index 0000000..20a7ca9 --- /dev/null +++ b/projects/erp-suite/apps/verticales/retail/orchestration/inventarios/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/database/HERENCIA-ERP-CORE.md b/projects/erp-suite/apps/verticales/vidrio-templado/database/HERENCIA-ERP-CORE.md new file mode 100644 index 0000000..a817e75 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/database/HERENCIA-ERP-CORE.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/00-vision-general/VISION-VIDRIO.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/00-vision-general/VISION-VIDRIO.md new file mode 100644 index 0000000..4ab1200 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/00-vision-general/VISION-VIDRIO.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/INDICE-MODULOS.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/INDICE-MODULOS.md new file mode 100644 index 0000000..eaa3914 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/INDICE-MODULOS.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-001-fundamentos/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-001-fundamentos/README.md new file mode 100644 index 0000000..16fe998 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-001-fundamentos/README.md @@ -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/` diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-002-cotizaciones/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-002-cotizaciones/README.md new file mode 100644 index 0000000..d827ed3 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-002-cotizaciones/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-003-produccion/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-003-produccion/README.md new file mode 100644 index 0000000..c1134fb --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-003-produccion/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-004-inventario/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-004-inventario/README.md new file mode 100644 index 0000000..3972aaa --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-004-inventario/README.md @@ -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) diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-005-corte/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-005-corte/README.md new file mode 100644 index 0000000..a6714bc --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-005-corte/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-006-templado/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-006-templado/README.md new file mode 100644 index 0000000..a12e964 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-006-templado/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-007-calidad/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-007-calidad/README.md new file mode 100644 index 0000000..cb69ed4 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-007-calidad/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-008-despacho/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-008-despacho/README.md new file mode 100644 index 0000000..9199f4d --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/02-definicion-modulos/VT-008-despacho/README.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/docs/08-epicas/EPIC-VT-001-fundamentos.md b/projects/erp-suite/apps/verticales/vidrio-templado/docs/08-epicas/EPIC-VT-001-fundamentos.md new file mode 100644 index 0000000..b2700f7 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/docs/08-epicas/EPIC-VT-001-fundamentos.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md b/projects/erp-suite/apps/verticales/vidrio-templado/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md new file mode 100644 index 0000000..e5e5943 --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/orchestration/00-guidelines/HERENCIA-SPECS-CORE.md @@ -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 diff --git a/projects/erp-suite/apps/verticales/vidrio-templado/orchestration/inventarios/README.md b/projects/erp-suite/apps/verticales/vidrio-templado/orchestration/inventarios/README.md new file mode 100644 index 0000000..e52b12e --- /dev/null +++ b/projects/erp-suite/apps/verticales/vidrio-templado/orchestration/inventarios/README.md @@ -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 diff --git a/projects/erp-suite/orchestration/inventarios/REFERENCIAS.yml b/projects/erp-suite/orchestration/inventarios/REFERENCIAS.yml index 97d96ac..61e7ad5 100644 --- a/projects/erp-suite/orchestration/inventarios/REFERENCIAS.yml +++ b/projects/erp-suite/orchestration/inventarios/REFERENCIAS.yml @@ -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 diff --git a/projects/erp-suite/orchestration/inventarios/STATUS.yml b/projects/erp-suite/orchestration/inventarios/STATUS.yml index 9266f22..818fb6c 100644 --- a/projects/erp-suite/orchestration/inventarios/STATUS.yml +++ b/projects/erp-suite/orchestration/inventarios/STATUS.yml @@ -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" diff --git a/projects/erp-suite/orchestration/inventarios/SUITE_MASTER_INVENTORY.yml b/projects/erp-suite/orchestration/inventarios/SUITE_MASTER_INVENTORY.yml index 79ec180..8830cf5 100644 --- a/projects/erp-suite/orchestration/inventarios/SUITE_MASTER_INVENTORY.yml +++ b/projects/erp-suite/orchestration/inventarios/SUITE_MASTER_INVENTORY.yml @@ -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/ diff --git a/projects/platform_marketing_content/docs/00-vision-general/ARQUITECTURA-TECNICA.md b/projects/platform_marketing_content/docs/00-vision-general/ARQUITECTURA-TECNICA.md new file mode 100644 index 0000000..b32f604 --- /dev/null +++ b/projects/platform_marketing_content/docs/00-vision-general/ARQUITECTURA-TECNICA.md @@ -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 { + // 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 { + // 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 diff --git a/projects/platform_marketing_content/docs/00-vision-general/GLOSARIO.md b/projects/platform_marketing_content/docs/00-vision-general/GLOSARIO.md new file mode 100644 index 0000000..57aab86 --- /dev/null +++ b/projects/platform_marketing_content/docs/00-vision-general/GLOSARIO.md @@ -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 diff --git a/projects/platform_marketing_content/docs/00-vision-general/InvestigaciΓ³n Profunda_ Plataforma de GeneraciΓ³n de Contenido de Morfeo Academy y Desarrollo de una .pdf b/projects/platform_marketing_content/docs/00-vision-general/InvestigaciΓ³n Profunda_ Plataforma de GeneraciΓ³n de Contenido de Morfeo Academy y Desarrollo de una .pdf new file mode 100644 index 0000000000000000000000000000000000000000..ad26fe88f0c2d7dc860a2a66eebc22e59c71ba81 GIT binary patch literal 886415 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 mZ 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�XGD+S&DfyS&~N|_!7+J8@4z#9eP&tMx{l$53(g7{l!7&VBD(?0ULJh`t14ggP+f4GbXBBFzqd;u5EJ{B@4Px z&l*<+@*WV{wpGzh*RI>mrtda=|Mc|yv(FxG*J~oGs@ex|wJ%b-h~%9oCNl+eU`Whd zRqTUrW(hFoyk0$9pPinqx2sLJZhLghJ}9CBcUjD^azU~HA`vm+d@)}xm#nu>F>f?V z9s>L@F1?PBVA4n&LXCwbiCzAY!4sR3R91aZw3GQu4tg zP?E@wRkV<$N|Kb0#h%PeR7)UBM*(ZGOHAIK_#=$N^5zLKn2x_T=G^WLOjo?|fGeoU zi*D$;`FeHJ2fHtE2Zq^Mt*%C$c44Y3zRQ*Cj=|-txB9t;=!WzBLdQ=xdF8s(rb?92 zx!bSZx%c`TufO@$;`ZA~Ddx0Zoj?5K$>#hlZMP{WGs`)f0huipi{i%sKplL}$su~i z?fTpRmiq@OcZlSi6Ui}(_r6@WIPpYO_5gKV=bTfDMChC=bpGU6SapzdmONBZ1TI4gXa$c7wthARv({Ao6^^ zm)J=ZGXQpb)!uS(aBwfBB<#Rd_44rc{_Xpl&DPfI-1WKd5CkQA&-J_}Kn4Oq0@vqW zRDGyY-+6SFV?-u)Xr)T4A)*hyszM0CIS<|^m7jd@;o;t1_12r*psMuSHnn}~x}36c zl~sJMUFF3rb$u3Db=%X;>b&26_U!pbpFL~)Q~}f;0{}A@v41dQCN(uR_6`7Y&Z@~w zmwU~urfq+|ZQJd$&z?Pgyl&<2_};y{Z*pCsha}b|Ip1uFT;KpajGPo>j4|gdss+1N zQyR-a4&jEZDi>)@WrtSsl?aI#nW@*?C!48o>Fg z3PjL%eb;rnyOgrKEg3i7dqgPcI*z2S9aC^aB>6@|@r8Clzs~m8eOJH0`M;>o@e8+? zpV=l$cUe5}ibFOTSOid!s+sNIeen9*?<|jRZL@s5P4M%dt5|_FwwrBEJuo7$sTTOW z=m>-mL?q?x9WjQOQtG!|*Xev~4@+We0C21)Wq?AN(BWDW#K>lV6Q1QzCjJ zwR4BuVgLg{dS@FjYC7(ON2jR>0ARWsU?4Nzab*Ai6i4CR0g@>ZrQMU!#fO(pKE{s* zGR<(aS9Pgvlx`hvW8~uK;1Z1V`HOY!3wPOHd%GHL1^^t^%Q5@Qps4$bR9O$)?DpGl zefQ0Ge`~pazt^evivZU+5s|$ML<`5k`Oa8xlh zb-ie+&^Vgci+Qzh)Hn*{M8}#zj8w=Hh!jFNI6OQ!KI&E0s|}%xcD`9ZPiJRs*LCv# z;oaB2^7U^W9UfIP?h#C4O!1RXK0}gOJ*%DfZ-|$+@;*~eWfXy_1o=Fe)hrP-oE9GmQx?4kFo3eKB2b^#wqCrimEvvuG{U|cJtw* z&pvr_+6j09F+lQ;E62{UXYY|6B5t?as+q-PWQ<@4X1R@9?ECYl&!2TEruB-6Uwd#b z)O(B^V{DoRGy~ycxmc~Y>+NQ?SU4Au$$JgXZP)9EpMKh{p7)z=&K>M*=*`R{gQ?lD zU=;l%9h@A5wgb=ExY8Hzn5R0gp1P{z?%0NSan}NZ33#Mu@_S3j2 z*;1-ng6ml{OExnkA~oBwp?}|g!Dg2hp}%?U!fxPbL9e)?xZ}d8sv%W%^Y)j%^zK*R zTU*`c{ORd>)u&D0*USuHDrgY9PIC5D@WF`)v6{hZwWhHFm`Gv*?>#t|2+d@(*);Wx zYcvpMoHg^9Qi{=sAUTV3#nmp(kg65811>v-#hWQmWy!3ju~b3bIylgBJH;A z`@Te1opU0^Z4wb7q!5Cr6js?)SEj)JJ0I8;SL5pOIuT4pbjuWXofjhjLV%+CVm4+f zO|T;Bco+i^8H^$>g||z3DGFwQ82|=&0Kj(2qmFNO;uQ-3qWNJcjF3R_8W9aZz*wc4 z1_S^g`5`H#-0;Ay8T=RkBq|wCLuwyGk$L<4cIBnMNWS{&?%^i*3U@uJtJ^HR9CR~A zMI$grfC!Qa1<8=jxKB2#Z-4n~|MHEm|8CEBRx3Mys_QL$`2Ht9`Qe9;KYeok{9NN> zu;&9QxOBHCKmzoO<>8&X_rLtDuYT=o@7*~%c=~L8dUCRV?B9Op@VEc*zc@NL{{Q@| z|4ZLLW7t@c{Wb+n9>Q#Xu();i{@pjL)+@!Q;MzF(t29V2&<6}*V zl9dgHV|DYft0&R3VYL`1n_IiW-`3ky#VJX2LQF-9fUOr2O&&*J*zZ2hFUeG4saPEY*oAXKxu znQsi*HtIIdu{BJRAxchM^_^Af>Uvhq;v)A)N|>k*aJE|g=)?EF{Lb4ma{acIlqIGZ zBN+l?lxToSvsx6B=j-mnCnq00IZwdGss@3a=itcKA%xjXd?VnPy>ldx4Sb(EiM{5i z-Fme;MdwzVjS(*Pj@eZd>UEB(ZI$53<~g!wU$L*ktiicM3^6-KlHA4hnb(N1jhl_6 zj6mpsiAM|uMABrloO1wNgzNx-nAkgH0`|^(S5>~A8L=2j1~%u>F$8k8sAe$9nzf7C zCsQCQuyv`KSJLMDKIWWsrz8_Oj+#Zq2+f9#xHD2!9_XAG)}-(c3UoAB*FhO;8oC3- zxT~r%B%$mmHeCq-T^qSq(C&$AINF`d-78CYg}qqPWU3WZ~1w8mg!;b z@~#B|sL&n8FiojDyz9T-cmpp%P+k$Xc~vxbj5!c8T!sn89ULP8nVKo88uQ_ud#``# zEBD|2%JIF|A3r;7&sV(}b6pX*Kuo;ZYydE&C<^34-7JYP#$;wGC9?=XM6Tc~DRntV z$(phNhxMk*dDDiird(AOv+Kd-ELBx?Z5xZ0#Ev>tsVP^~91Cft?^{)Anx=4m3)Cti ztJNxm0AK`~B@-y4IRx|}a;)pR_%Ysxvg|Swvof2iI0sl^jPX}b`!yL@Fc-Vtoz4dx zvYpLrtWaLS+9BK723KY*?r5Y}y{PfX0an1Wj14aukuE|`W`ImXz-SZ|7_zK)K$;Q7 z88dopI36!f15>8%r4jYFp_Td!W*Bt{?=hN-QY#<7{oZeW^Ez))HV5Fr780qDAaw0`!V-~ZVkeg8kc{d@oHUwr%9 z-!$hQKR@sKC+Y3u+jqb5Tfg(){!f4S`(6H+(JHPE_YS`F)|VbUc>B)12YW|{xAu;f zToFLh#L;kb+Mhkr_PJ`p#j(HjMh^4LE~czqPjj`HD-LhH{`PCPU;Fxd-}uqb{^Box z^rw%WeG0Ax5C8>`q1fjy*Yt8ZT!bM9>MDSD!xsn4>rxE<^Foj|l!h@gMRY_=rM@y! zQ7AzmW*2;@dQnD%LQc7Uf)K%ZMO;zRK zW1l2vfj)S@TrLo?t{XDlq&|d-Ks*SVW>EsnBHFcx*r9mGvsrU`@_cPZV8+Lqtj(uvMJbk{uT;4g@FO{p4N)~a>l4Qx6RFaB<_BsCilg}QW ztc%~rfSz6LLsK_R(^PfE&Z(MbMg&vQ6e+Oglyhv?tK4m(DJnoZX` zAT{j!)(BLSC{*NJaE6+55}}G*+igX1Q`gJovR$7`j0UCVj!vuBRA{z)Z3TN>x`)@Cmd}F-guMB1tVfhef;>5xozHlrxvoG(&jn zRk6d*Z#W6-{TC8h(VYnW-?x{xt37lPrA7n*MgqWu7y)lRxcAOizJ73gpQ@Rg?{C$g zwX)uJOjK2MN?An#pri#L5~4Zh3qVrWb*U9_aBwI&0bmMCVvpCFhj#X0wSg z)^%Oi4cLk~%;wEzy{YS(y=%fer_}cyjoX09xT$K4J%G_Ap*%*!7^8@&W=U<=wE$28 zQiVxxrZL9x&Y4K@qNHmqI-=hDUp-f5WXp`6rH(GbNaKaErq_665$Bx>1rXXrl>jrk zj9uUmR5G?fKZXEl%+Un^ROE8?Vsiis$#=R%1l$EJCgL#KNa6w5#X=CS(sBNVf|0vS zluLgw0*Xdikqv^WkEvN6zxR#b_}?7e|89cW)2I2P_dorkKm6nOfB1erUsJnQi$Dn) z@_Wb4;$S{In9t{R(>P{8>SEe#&)1vhoAqgISCINAAN}?cv3%q8cfbE&kmSrs-PFVrr1Z%!oOJU}jlF@*rs%Lhznes}icPs;*L9=hQ8i%fowb9vmJNhH2Ne zvE2|-)716;LgU`*{B(785|cS3uM*oWNCt$n(=*M{Kr)gFfWoW^aOdcgvlC8Aj%z== z*UT1si)YW9^=h?QuOB{oQhT1)O;$;gvk8KMY06p9#B9^Y4?lVMdHB%B4T*7+?&nkpaPQ4PPSxfImW(a(5jp3XDd${7II?8eDTO@^wksp*I+#Cw zZkLaN>kju08TQ6ue95f8a=S8RZoK&`v*~lekH3yq7Nr+G+l{QFU!c9HhhM4D^xa|` zzzh{>et7)O+i%}{^G(3|*(y~)9yFdme|EB(`%sqnx}F2jcDunbA!teqDw#xP=DkO? zQgpgtDykMjJp#{WrgdGF+U(oycDrr8cXeI&@tm0rDCc&&Rn>u2hDbynz)CXm7)Tll znH5*2bVZpsW&`297(M4)2%K{&x-|tvR25ZCDV4932=8#VSL^&&`g?K0CYZ6juPgaM z000WAfP*Ms`HxJ|Y+$j_lwM?HFsPF3zS0Y+x+0Nc2sov*0H7-!Ur2VQz8o-C0fi#V zIhN@eJQcfspD=0V@$@(VJ4L--t$psm{gq4-c(E&k1`1|C+2%=>iicl%@3+43-G46h z`l_A%?1S@v|8M^6D;!J^b{^hwnfB8gsEy4mGfLv z8(WavgxUS$x0>HRTr3a%===Y^?@!n(l3oNice(~HVdj^5{< z9FO0-0>xiHKTM^$i-{>-c+Oe1HzN*VD7iC+56T{;)a7yPhLwKwfeMJgL{(k6!0mQj z%B*wA*fT>b;}Ndk*#9;jyHyU9V-kL4fC7H=oa& zx(3DEcincSij{8^4Q5qyeDM6E&w{$p)S(Vl9jeN&7t772+iXtP?Y6FKU=p)r;?NKv zgZ3#!$)7ztdvd-l!YS;iB3`4GlU{G z1xlvjQ4IPp&=7V<^^Fnd3&(}PWarnNH1g?N<@~$4{3Cxkxudsy4NqS$f6iz_9|rFL@`g=*rw^$q~^I)f5cOl$iJK+#eFPr9NdGAQ0zrPO7QxqNsZ3k+LDwghWUTDMtdYd9X-ju!T@JP2Kl>xo+u- zGTurl1IC7EsMpc9tr?0~jM01Fb=`C-RjuoKu~@{|iR3ave_2#!R}6X>xK=!g$(6an z{6N&A=`{KJgX3aK0BDLsVha!e89>>u?ht#ifH#+8Lok#Z2IN8jGAq>JoQ;TSELLJh zh?f$V>^fz|5CD*-IMnV4#*$?9OZMBZvmpqe8TgbJb@P??e(QgAeE)0PT(uke_y7KX z{-Zzq^vhx z*Gud+>vLweIzQcRn7|kffMTB|3OYwkIJ$S2lAb<&)~8el^1&S(?9XS*%_eR(vn*%X z6p4&b)Uqjo>}V!!SpS2M@Rj@VPpevXy*!3D?OD_BZ#wwUi99E4DbNZ+-fQE~*$ z?Ygf*==+46=ArJoZoO&I;kMnLoUMWVrtPW_D(9HU`w+!)j5MRVt`_qplAf(hMN~A# zgqn(AV{sEh2<2VV7;m01v*O0!5Y|TTTnQ}|&5UAjMI?j)vH>t6Wao_#RVozGlv5m8 zmS863#T7$3;`6@}Q)7t4gk#FZ%g#wujOwtyzlhTJ_wCo*3@obxzkx4^ zn|bfsZ4$A)Wy8)PV%Ke)U^%|7>zq>;SDVdRB#A_|D2nVtRRtpQ&iPQ8xV9YvMnvqr zFB>obC;&`xou~331*##U;_mc)@0>4XUkf~0xd_-KKeR5p9H_f81Fak&{YOEE@9b6|}(^c)$QFWr9Y-q9!Ty!j{J z-~aRf_^1ElfB1|4{eSl_347Hf#*a_n4}b8()YCtHcd@jb+q{sy1CV7{_wvlsG1mud zDFb!vu%4;K4Ex$|xm`iG3W9n{()jxLLDe+Ro;}7DF7F=I4i`0c(VneWclHi{^XuQ9 zSNQM#UuV-*Xs=+vfAcs6+<;&?g7Jgvr4Pn^s~jmZAA1;i@W}doHIE@M4&Ja zfw|eVs~Gz}iLKAfw5exJv-G|K!LDyp>dTG`5&OPNDU}nJ1Z*>4azvC;R>`1cgI2I- z>{G8fR#kVfod5hWlu3o36;wO zrbx)^Ze$Wipao0mpW$UrEB56nvs*U+z`y{Ri&p{yHe^th4K7N zjdP(2vryGDo7KK)>be*v#~0<1%hgOwMN9=$%)}5BEURP@1yfZt@XifomXVHb-EQW~ z)q4AbKmYT+gTwb`OW*8+LCU@LZI@$88B{DMO}%$CujfaH$Lq}+kqF6$(5H0z>^T`& zmac7mUSQQ2(em&hrqpd$B8d!96-@$D7LH;$WmQvyKK96@redOIP3>#+=olTl$`g`P z6jLz)q-HU{b?feY|A4TRtjpW3&8hDtBhj{x=(%cY--H-rAdieG$8;VGoe;p)o1n&eM@_Tz^?|A>Gb;p?xz!SqJET|ayFc^|#ubPE z#%goJe}h#U@|g^3g9Ssgk{MxRmXQHkv5Z*Y=`jb>&~`9EyyP?t02BZ`whTX2jj`+6 zzIaT|Rb|z4>_la|?KYdXh)-5k$XQQL&(6+Puf6sfJC-OS;=MNjBHuK%s>K)q5^?ap zcA;w9^?H5Y_dS^9Bz>2c%l!}_#l93vsH&<2wn|Ty@&zK2bE>LJ%?OE@5rG{rlMfgp za?&hGwKFSu-G)>OJcIMXUKrKX5F_H}b7ii!$<|L`%;-oesF_*TDUSmI0A%C zQ@wCshSLrI6u`&8P<4`rxjaCI#pnX=9i?AP^@5g<2r8w#N}0T)>tuA{@9vJFzjah4>wtq> z*86Y1_MTDo$NE1!{_IBz8)h)nUp^S9YK5?M5n%elgNAl~AOct93&tR;BPK^Ra}&b6 zsh5lTU@>2YPXj%V}vVsEc* zD%GTB2C{9pmZe~5p{kmuF#-}hd-~jYFCv@uI;Lmj{S@*IEO@t1jUURnNo$_ zGQE_6(K1FPu}qaw*DL4DOo)tx&O2{DLrQ{EVsBUqc(l6`+bGqUPwop=4a!kp{Lqq^A z7iA_SaBg6U#lDYi?7AL|`o0B#5b6^B@!l6aW-902bsaGRTGwyQY_V8YRV7K8kr~y1 z9e4v%5y?d4*db-a%xogru{t&bnRzBe=U*YfbPXOvlj0Vjs*)ipsVNSrNeEcjWW&TD z9k#qfvBrXk*sg;zxizo@w2dDQB;OrY*#H=hw`7#9Y*_#aK?ffT5fo9xwCtF1BF7B; zVjARP;YFz6E-_+v8tYj1_?IM1Oy|53jed#VO!>-XkrZ%v$uk?UCQt!N*0gbUaObza z`J4Z{-tHUopZ?jO{@dUGcm1`>pT1`}J?Vy?0d4J=bIeeO+^MoRScmY0hRv z(6o><$~siE>A(n4&CoG9=SWEa-hBP&%kRE%Cchu&fW})x4@^-Z$O_PZUO?%*3B40j9FEVFR%w8bzXlfM$>ck}O5bQF1gb>fkIQ z+wHd7ZdWJIjYB3{EGtbNTlSU5#d4*ZVdLvs67M&SNpg&gc5-rJW;y2&Le5!CLDkf> zXh<-uH{00tq$ZkyET(K(&5%?2Pt%RZ2EA#iZPS5+09ubi)|x=q?| zw(FCp5ANR0+fBFKpjwXIlSdCfdwx|;?t9rb3eQcwr?Ye!e6g#NEzc7h?1E*s7Z{9`mkh^iM?eECz{@I){8)vnQZ zuFUT7TSKwG8?E&9le|iMQH<>a)L^>5yP``!7`5s*KBwnp>Iy}g*V zU2g#sk^w;5wq>A~jHD1k+qM%q9RNxa5HpA1i=y=I6AdzqBMp4muQC@hO7TU?#Gjm- z5=aFgGez`OXhH}%<_)hmn@!HiECGhekFgzkZ~3|zursrgtH9nfq5_B+lrm@{QKgf( z>AJ3}>#ANLf%ne);QYXQ1H_zCOv&_>YcO4i0VqvzQZ+zDR0KsEV>gPRiWA#+VyNt> z3D|B>>4FO~{5-}Ci)XXz@sxj!;GzT);285XGXgZ5B2=gfs--?5Dh+2KBrpMcl@Zo3A&;E!1<-h!AzwzGj{Z;zW`_I1r^YxvBdjGo*j`vR2AARWkoR2H? z&Qyb=&V}CZH3vl5(%H|0Y&dSx(~q;!?A|-#W`SJ|-Rj8?WBV9pdwTeme))R`Z@kk5 zJewW-#<&0JpZ%c!^wD1eWdNniAj+?u$%v&KCL$3zM0Qj&2j>_7RLy&0X76Sp%$s^| z)+}d!(fD~C=2e(^9}omlca8D|QKqB~Y8FX3$1F)CnkLW;Dk?F@9)`-SL(QNPURcZ; zuIjVZ=IP@t_-Zy^-o1A(IoB-qklpMxKX2;TZMW;QpZwJi&mTQ(D(_uEsv|p>B{L4} zV`lE+rrm5nO=1EybR4MJp06|~O9~<=COJ#aYATZZt`k*X1;N742T-wWS+fxs5MrpR zs*rrUuFc9m_?)wWHcb)9+x-n1=>Y8FuevnBq9;>^*qdK zz@gk%j;Y(WQGo#G%`9%u_m>MzUG5`;Fd;$KcKyL${UtlTbLY-G@4lOIe)8l=+qT|$ zwala1@01rhc{asY6Ujw7CZjMbu$EnE*L5LOIQkg|rYb4rlyjC`UK0YT>5a?qFFO`5 z@4>DZy^A#-k&t!|m>lTA`?+A%lri;-gxdeUU2kTlB74i_Ti^N)HB07Z)k^z#BMPf_ zZ3@kLowK&vRy1Fyh=dUC+_^J(3}q+|TPy-*Oeq&NhpuZ2y{P~*FnTs6|GAKDOA20r zQo632T=vm~5&$|5RTX0F7y>OFBDU?O?be|R06@fulyfO18SA>PstN$S_o6Z7wg}$= zs+s|6Rzb5E+qPYM>?#M%{(OIbe>R(W9~_60R~Td5uD3B4prLO8 zr!j0gO8Ap0st!rm08mJ1idG1_!xYfbw*V}B;S1SC8(1B%d%ZCw8_^A3%TQLi%qt}0 z>2?6XMm`-tG4?2m19~QEX6OLvg2OAI076vUNdy2=EaYRGT&O7|bt6}1BHNS)_#)Qk zRlFsxA*w1QWheTq|DJW{*TQsGAOPPCADJ<){ zm>L*WYzN1CnvLxN`kUo_)N@Lbu<6Y9CaX*3IGtJ^OVVE zP4)ETG%Fzcx}GhTN5s|svT6Ii3YBU8@Z*oy=O;%8d#FkS|J0A{8#SWX(CJ)FUElB6DC2YAKp$$Hc@87_y`eJU~B?OMOd?2MMjY$PF; z5nj;39mc@b%+KL^zvzzf)wi0L!PE;hA20p&g599w>hz)$;!9~jS3S=0;jgY8H<%g$ zKtn7{9x+vgX0d$dEARcIfAUYHUVQ%te|37gX+n=CeTDku)3dIR&M_e| zb4fxp73X}@G{s#gehduy)R0m{1n>QPJ~xB&^Yba96l0k>ImQ@cZw6IWmn^4A#Y#kC zOaM@bI7FP%;LwtnG?VNX|`25wkVz8^iKX@0aWf+<&Sb_o0 zEatQ*_YLyTs-Oa62x)j(mqJ5>$1w6Q4Gjd0aG;wRDx)$0F*yKdXbJ*Apsb3ZfJUZc z?HT|;vN60Os)gc2WBCO{ppv#RjiHi>1EwlPu%U#cvFHe@+8B4b7`u2$-;5@e`{DOj z^=52T++=8m_y4SY zE*#)Df9JdZ^!I*i-cZx@o|7XDJcQd!5r7>UnB-()OwKz-v|Jv#q|XsD5ve3177=om zRYa7~c>+M49V`r0#X7}D=k3q_@+V(?>)^ZJ`Hhq3aB^;^t7nVl{M|Q?KK<#FAAWHD z#=*UB9=+oq{wMqSUy735{h9;dHb8& z(@*sI$5s5ypFDj2AAiCRzVXgie)q$!zkT?|*T3<5tMk}y9)a5;B0wR|U@6>l=@nlI zAzj{p8cec^Lrn}D!D~=3LQ+MV99#fWKm}kcd3k_nMntHD$bp+C%xAM@@O20??*bS) za#g59m{p+;uCDyt`M}PzV?aY-Fg6nv%^5%iESrdlfC`$aW)p!dn1zx+?p0cqs5zOM zN>sipD6Vj+LX--AB57^f(n+X_zM#%|445L6aB7w??kO)A* z#H9Sj!H2psvaHEe**VLq#>AAmnEQl?96TT@$ZB=woKpY;R#PPORV6CUSM$Yk@93b7 z{Wj&>M@M(>yz$xN$6f5nQ8w#IV0(Ukc0$_r?RjilNy&S!fIy5024Y}Ek%5_s;N;n( z?dtUC_~`3j`|{H#&reRyBx4nVa~_ev2&o{!ma`IBPLerAL`P)8Xbz2tykw1S#|Gq` zN2<^V42+$b4cf;DzcyX+n%(={_KHXD>_Ftww?NIimx5z$NJkFU5gS8VwB>y<62;njjRmkJdV2%-R>qRWN~uvqThd*iJ)-hGdn zy%SipC+g^6e}CzHK(qyxGO<`ybLT5`tv4JBni5IMs^(n@zX~F**X!rcp98?b!9gL% zq?7?5B~3|mD6!7oSIgyoaeB7fZAxjo-G)$y5YErfma(D#xPBRVrMW%L067?IEuG7z!5fh#i=$e3_ND14KW98?gO!rN5^6a&Sv7!9hz zM2RWyRb}*k$2cQ^A&`yyn8MpNqrw&D(N$J67;-Vju_34iHi!Y*Q11rKa5!WF++jz) z>~Bk(X5b~q>kBtD)Ck&HeeaF$+`03$nCq~d|LgzZ|M$_ykI>^czy8(V`@P?8<}Ns8 zP!nM;Sr-UEB{q+yq8ejfx9cpnT+ZjUpR_c;NS@0cEYAaSyh#DF2-mEu5Oq( zr6eGQq0)6-_xLe+zkj&@^y$-$-}ovRh5%uA;%2*&nV&Zft#{7*$`PR|))t&|^Jc~XNaj5ufvTonQ%qfqIb{*) zRFGWY+Vi|}t^koKCRJ5a$)cn{N(N>lDl-j7)hT!~`~}r;OaekhEyoN%H=q)Z+0Isy z+ytj^Dgb~DreVi78p$jJ24{o}#g#Ec9Mii89G|oR^)}Q@Ln)vp0T3c83{v0awATrq z7fyN^Ks#i&>GO%NhcAX1cTRGBS8?-KcCQ7~Zs2n4`9{v#?k6{%{l$)&8B){$v)AAH z+CR&(JXq}g;D;am$)EoWkncXY{d>RnTXmHQ5~w;y-Z6ujS*bW$E?SW7y?W+CdUkT! zcU|q!MnN`2oJ!Xft1T%if)7nL%{ey&`-?h7Id5&%)5%HukG}iWKW;z$=w~0i_U2*t z+1Wd_Ri7mW_E9H?<};Z>CumoWPayOB@;qqR;~r>SyQ>i z;`EcBq_fAfev_VjMAZy#zt+6{8`hr3k6P}Y&#bLr6QBKM%5wM1|3ds7KRcxNzWc|2 zwOwz|2VACrx;MSCE3e>{ZM02eYVEoBfzH7pa>Fhz|? zysPiMemjI_vsr)q(Z_%O=YM{-dR{fv+03n1cYwTt9vvO8&reHD+Vy&Ues)&o8>xZ! z&8#W5aoIc_aiN3eF<~YZTGG140UJvZn@sjx%UhRmr=d z4?xV4+qNxf6m8oo0x=U&(=;NIWvIM3Bzfi>Y62uecfMIaefD%Qn+Fa?;6kWpO=ucs z?l#-5>(-l%Czyp$IMElca_~kjO<@?yF52VAkNds&=9_PJUH9#EK z95QmATqa_|%uItOSix72z4J}oWQk_Ng2|$x!N?_7)f<6d9FfTc7v0zw za@Teq{0fRb+(TKgvRD)fb@Te`Z@v5FuL8TyXcNUSgsRR-sxY{gX4W)~bDn50!bG%K zENrYV4FJx$x~gK|6`kC(v$Nuql+aS&_vI-jR`2e}*>1Pn?Y3N~OtBEcVzH>}>e(}Q zeturazoGKpQ;dCaiEM~`^Co`a- zs1uqJAR%8JYK4zo-uLcIyEL_I@Q&ccQ|?up*^S=M=T1@hyh(D!m3hVcRCQ7Izx3`u z@r$>$51Qlu_#b|_>A9LW?|tK|^EokhP3;kkNYIRFsAY;%8V(FTmhc6J!#6^3{mGN<&wtF4!|m64q6ETKoshRsHOtrc z-l(_yeth~Fc3t}H=g3uc_f35LYtK#}ZTdCL7U+0&dg?Zh(trAcd#`gfv3f|Y=&1$!phvlqZc&?qR91#7d*4(*s4*);@ z^x4zL@83Q?c=*Xj+@2p*o~ux^M|13Ei^anFZoAzU=;HkR90}{H>Nf4B+eoN#>Qn3r zEL3XIA)?rjTV)8Qnqw+`ky1ngbm+Wi$D$n&7F4DbUu0%ci6VlQ)dZA)7={d-EK+#2 zI_iX$RuZlIzOSk(rIa)w4hY9K=hgZ7db9OSRW%KOtkx?s2u(d_NC_#hbF;#24Bp3n zn+trB^XL-{;o8ZWz^O1+q17XAx9v}U@{>2;eDkfh-g@@zY3YRNCd(RAs>2MzrYR8> z-nTi2NRu0ZKKQvcXqgH6+;y3gozj(`cWmmVukUR6rqIw;k7xR6 z_ba<6-}(DRO=-I31ztJ+73bP7@5+2`!=3D{8~44xs4H{%6T5ihIj4GlaQn@#y!OVM zv&H_l>o;xhszsOu@+s666XleN5fQ!jrRqgWd2+amPgACV_ul*97#&j~>Tb8&zV9bz zMOEv1;J%d4%aEQF!^^J2OiK{}5vi&Qz;55Z-84M7p;Zj*pjzhYcYT85qQVs58*V-m+>|LPuCi%?D)SU@i=~Z)MaLYM^L} zDW=qAk(^Ub8Ne8g46(=r+vNaZ*}Yx70|TD&RSiH33WBCE1e?r2(X?bm00@{~RrVj0 zVn#AC4k^|Ir4rVyQOa9@X|LOTB?|0`v2kY*> z{@v%#AGW6lF&@7ENqc8+??3(dkG}VhzVlT1#~+`3X?d{3>Q=pfa3^d&dcS-0p@ODR z31bLVvpDXt-iTW)?=9b~dq3|!{UD#6ME+nopH)Zqj=u66k5U&JuJ;c4;ITgY`QqeL z`RVf@-<&=8(p&f5T(4Fie*9ClmhgfW0B}JD{8he;5<5Y1E>u-r`B{ats%k=LysPVa zws6bJ&lo~*m3Ossz^sITm=FwibXo@@+929gfK@eB7&Ea;oCzdL7Rh3!rc%DNp{rqv>oGzT1^R8LmI=BVuoSU&hd* z(DoHv_o-$!Bw15wR6FBbEoqt}VUX4^e}_N4Fo zWvB@yZD>~t!AI%PKHoXgKSXAvbLW@M(m@4K!u6X&?9s{Mn# z5d6t$>bf-|h7dqS4JW;clFOh;3CuZU=lAxGLpUaDQ%dWz6GZI$2png#eeWD%V8S-F zplslfE203v!~<^x5eUg3#n|^FkWY00g~w`=jmPIJjvki^BLgdCASjnM`mlwTKh~$jl8&`I zrLS&BZfw_m`Im1mrsH+u%IpUEuB8xIecgq>7FdV#a~-I}wbSNk>7BPe1--|KRY>om<8AE<~AP^Q80@k%#~= zgHyvk=DtgZM|)@8dGF^RJpAKYI*i?n?7N(!ux;W-}**<|EtSL=bwDY(k$=ZeXK`5bW4ldk3TqEKiPZmUA+J9vu%9( zu6)_`EV~)VwOVhJyoCQukOxtdmPK9w2)YBlvg*&Hs-^ds;OwsDpIDWiAs)YSuLwXHJLmK zHEWt?&a~;<(`P4DXim@8n(}LJ+&MivIqg=`1TepUaNH~oo}4_Z4v!l)(Ohf^ICgbt z!u;XGM}`c@NwOm*7hK?~s%(UJRRS9PWrhT|C!I;4uIwL!vyv@Ci7COrWF=EF{Z|y@R*E@{NPLuhomg z9=O|R+h$P>E(AgVR1qQ))s(Vx4uNu30BGA7<1i!2J(mD3jGzkuh?=W{8oE%|pgC&aN!R`V+56L9TaqL{5VOVIBhGTSx4+Dndsfz7)zvI^vq=s)Lv4_T zBT*l;K+*^OI|2kqA0$8vg!v+2AP|HG13@z!&S*GnlFepUS5+^y=KeDC<=gIdmRQ`~ z%=Ez{;+}iodzn?;O|UtlXCm|7bK}H`6X9-d_OqY;tjiKp>Q&8v91mG(c0BVHjTr+c zc}T<#)v7Q+Mx*1h>&leJRHE?$W-92A02(?w?nnRcWKw8rM_zy6r=rh^#=z|qyA(cL950%Zap z{H(6#vwB`!8AdK?Z)PsV{Wy9;<-Mk9mqcYbgXB`|TwEDYF_ocWQ1gYzjA?<3W8W7M zh_36-PEXg(I#e@N-EP+R?!9`pK0P@;X6LH9S}vAt+dcT~)8*kc$tiY?3sp&tm{~oW z<=6vKUC$WVlk1vxR@clr5{`4zG?LOT4>WaMiphHqC1i}T{8feogdhSFj_#qHhT)*D7 zZAuB$a*VOi=@Cn5D3Js9m^1tAnE=d0mDKxygvewlI<%WfQMwU3dEN}g(H!D#;mp;8 z%e@5v)QbjWR}R?yQZP11xg3aHa&ATug55~#%fZcy{Pn*Un(!EF3>WaP7G1wcVH=u2 z5o8VD`0j7M{mpME`X{UA{?lVHCM6>D&NC1(1G0|1PqWmveF#JQzmfogF~L`?aWNoM zb}WPdfGOv4hZl=wRlBCymQ-wvv28o1RF;5q4henmL?|kV;5-8wGcrRKbp>@mO1W<~ zje!IY^Em+G>U`V9IGfLAb-h?FX7yq|+w(q1ioK<_>j9yvYUgV;*xTC^k**gph+V&0 zcSMf4@-Bqn##VJUEGBeB$j%Tg=h%0BjGagnNr37-J6GDmGBYtd18rm1c3tdyr-HUq zVl?Uy!06vhuFUk9E)5uifh%+WR~gePx;%We(}sV|eo2rfa+sJ^Y!+7vHF|+Ma#=MW0)61vbmT371&kt@rPVn^Dm1R+|j7 zdHw3^_q=B$>$=9zYZ%gS#W7U|7+}Ml%k#k>9#&TBEk`m<`Rf!J3#BJ# z;iQkP;3&~0noyJ*qiV3;c55q z)2APQaO1Vt=XYQBH*T)pyT5wy$Ub;_=e1jVZ~bO1?#a79IQ{U)Z@ltGc>As2-DuN2 zCMS#tS{io!+Ee$LXGVa8KxKaeGcW}6U=AT5cm!hd-c=#Yt5A7zZqikwi#a;+49sNI z=>S#5oU8=3W@ZX1CK**wwMgOFP~bHDUsRm;X13jK`@T<-4S<E-QayE;3s zyq_-?j@{YWY0h%z=8a9CK6?L`Z@%Mi+`MhxIqwm%ZCg!oHmiH+o6RPt6hbgfr>AEH zm1&wL=j@pgv3O2p?=N1(G^YKsFl>etjB5GWOs5H!MG(zkpm3G9)bm;Kw#O$W-DX|a zbvcijfmz|$78Bnz>#Qlp=E3LvE3drr^1YWHJ$W)ozjSSPc7Bea#I(1!2a=m@bK#&A zt3fn94xC;`ES}I zOz6QXH4({4`d)igixEZR)X(LLLAbEPmrVq|SO;atj9jv26GHPWIx;T&=bmH)iU220 z>lc6SvjDQ?q8xadns|5Lo=zN&ZDTLW# zk-M%>U0;TCZg06qNTw-6HUnZKVg?0_SuN!j36iAL$F|#c-9}YIFjJh*=ZiasA%wlX zJ@z#b=LAjDoSmO)211AEh^U(Fi{vimD6rjhT{Klns%8wvgpSY`2zFma_Gb5)-x=>X| zi^Z$2-V333-W)j_;{4296f~|ri<~ncfU4x;E)SnOZ8lIy!Ol77a>}Tt8Ic0I<>{*J ztp4!B#~=RTk6e4=whugIMy`$T9E8WGpWnK7qdD8GKL1bxqP;O&9KL`5^Y47~_51Ih zJ=wtNte)@R;AWLmL#T`bn1TvyPQvUk0QXN%0(skW#vFmr)&BbJ`K&&9`0(RTKHYDc z8+Y$s|JGNQH*c>0_=DAd{qsl5SX~ddUw#EYdvAOH7jN8oJHGMCKl|C^l$9K-jkS4y zCgg3}+YlWBF?%Xt3lM|%eiq!Ks+Lu?tin9_nkf)EB3ERcrh&+&!yGU(Qvr}&IbT+> zoXb4d;>xJlfWj!KjA4aMZduN`@B4wxjWnOn+orvC?Rwj_PmhnmKdeJO<}|*L9()N_~*35|W4%`BCqE7C|KE z-PGzuB;`!ZAQJE2|KR5BJ9qEiJ$`yzo|pGtB)3fyyRO}=7PES>SafYyijT~!I3O2( z7!l&duR1F65A3n=dzIpmQVPpVM7UEeFIh6@OgZzw3ChK_QZoi1Km>5avk)X#QD%u0 zk=VJEx|I5C3C1{$ktfSMUCf17x07^$XZHRb-{q1W8v{ArJ#yD_?qVvF7d6?9E3OO? zfepSO?(l_Qo)cWL(mUhINxs;*x$ySJ*nIrMbIvn;*5rtP!I{6nl^HyB12J13 zU4P^4Zy(%!<%3V3o~+JOjH(dO5kOT}g|x1!UEe#oa3w_qM>Xw&f0t6YvhfqqP#H%w zEiUi9_g=(kTIob$HX`oJ8JjeYP4gaBxwrhV5m?G{lr zrPOz3;#pnA2M6=Lr4J!6vx>yF@7FntnGvBw^GGZPXviG;1YMWbO>BG3Ij3A${xc#+ z;1Hb+R2xI2(si|}D(`DE%>r52F=vNnSk@R=&MBKg0u#{`V;}q6r}bvNUT@mAaf1No z&NRB*@&YdFQv>s_w{_!X>O8`Q9`^3kxjafk1SBYgVt`@c5g6)aHW`k9Tk%g#$69hT z8-SKyNu}P#tpk5u1>b+&qdXr{MdzZ;=Cd0$Ij=oiy9j*y`hg=4AnZ^r6B~f#oQYwl z$05MwvTy`M!r`7n>Ck#H2@o-au-R-w@L<4-U7Hqr*X}=AZ!`Yzryu>|r@!>;M|E>j zyB1{YZ7JK+d)Hq&Tb(zjkM0~DeRBNi-8bIqR_E`0>(-C|@cm9+i%{Kv(!9B(I|q9j z0#qJzOt~kbhQ^pL9**Tp*&u!-LKFN|P)Wi}gB|PIU!X@w1tkZMWMgG#ud;E2TZ<3qBillq(b! zutB|+nME{<0SpPF`C_4Fo6Uxq3CVd^x{MT%#)iC-7THrobj~4TmL8%yPYC$r>0=~! z>-Md_?@vxn9zJ|n)%9YrU=%Xhtj>cE^Vz&miLX2nGh-q^D8)$A=3NMwfEUHmW&apG z09NG*h02mK52y_1oO4d4(Ihg0L(B+Bgbd)2D(8a_lp><5LPZE>Dl&xN#|(JPDbwV2tHkMO`bj8#vpKfUKi=1=}%wsxg!(hi$RzF1`pAEfU_G+)p{i!0Vm;<8!Qpbbbk40-tIcN1iVhjjRK+<~k(|1mW7BS8Y`y2ew12ce zn^&`0aGoS5%emWhW;UD6M6~ZM2@sNVp3tdCR3U`a#y+X47`P-TisY)Qs)C<^ktwEZ zDn`W44ZSLzbIcB)v|N>xbILiT*!NA}w>kGodP|8_%ew1hAG_YRUDtMf->ZnTRJJP{ z`U%j~y1>kg2C>r7;|8!?QZ5W?1`6sx2~~^ODH=|a9p!&~1r!CtLdYr7fH)LsPySBf z4ImN#1ELaU85AT-gC)gSEJcZA%E3BTONw6fcpKIs8V$<77g3AtNYUWv>!2YIodGUI z1JimQU(xAW%iAtb;HeALl+V8K7gp)(h_$+}N)^YaZM@JYw@7lqGC#UO|m*;)jS2OfNbwVIRkBVSPTTF~CG>;m8>yF#^k_;V@B|vAs3P;y( z&*tmTAKm}?yEpD#_jg|Muid?S=kEIB&yL^w+3{(AWAFMsI{WzjAAa@D+w|I-|M>og zy`w!EjS;=iSqXk5Y;^t{tw|>#px$DEO?a-~$=vXi~Ea)qLT7-RrhFJ0~-TWSZyK zm+gAveI;T|(|A`o@3Po-yG>b{S=3m`aVSohnK7etluZQGOtGvqg{+wi#2`fg6A6*f z2^1@Xh*)%V6pe$=IfE(%CnC-bRjD~=AA%>II`=X&0hvMWVh)+OpPhTA>(`F%UcbFMJNx|MXPeEYD884fRJamoiCPvjDS7(RrPKzVROweVK*c(; z%BCSAl9dER)g(iXmN**_phH$9Ac&GnR~I!^QB-gM9w`>Xj2J>F-*+%#3Mv;BUeB72 zT}rPNLDCT&8IKrpX9GFV3j7SJgMGV<$~==%nyBT)Hjb#w#h(;+39~rD z{-K5SP(GxnncIbr1yIrn&Y&W>1P>x(^lnzfVJQSyBq?^&@7iL!`OTFkkw;^BPHlcuN!;wwLJtf;We$kdP_Wkoa}N~=yX zQ!*=nmfFzZBIg2#A+s~Y*<#s8I$QTJKfHgQpPbtLkB@)!d;bzQPhJ0j?Nd)HRAQ`% zGh0rp^IHek?r%Rk->hcW-r7b;+kF3nPhWZMrO!V<{_eM5-99VJ%0*;4VvT% z1Vreat14I5uBzFGQZa}G1}0evm@C-b1`McFPZv>`EFdx^FH{vJnIsW05f!nVb4nu7 zK+LjQ&VWPbLdiL0Nonw4%nFbI2Fag=B`z z&Ic#b$0W?oObh^2jI3x-6yER9O%2Hmy$e7RV=Td?nGti$*?BJ_rJ|`22oSO0D9l_{ z)fBlhP#^o4lOUjTs8-cg*Z227`Q*;syXWWUpM3C9+q54XpWeN3`^NR_cW&$X>a=ti zp3UcyBJ8A7!A!E09^l0w7F$JFDjT5yLerSKWM4oyW?Yy|BATN_WTeys0fH%*SYUSE zdk>1BW?4{_)Cj;iMykMkNv>H@O4K|>LAaxp2hU5;?8Zwks>iX5m~uRLCr1xsKj2-r zY1h}8I>%kH!?0uJXlx@td?H-MdUr4T&p!-7iz_qW^pnoW7>O23KCys}uAJe@^?$J| zGvHnYAT>BVy8imNzjJi^<?it7{A~#saG#A^@89h=!O~tMjhi z9voaNe0D@s^-$!yU}!r~s;mq7w`BARy{5RQXyhDO;njg5SWHn&h`>;gRVAf9s^yrP zlsm_`oLBpMhrw4N%!98|!quirxmV4Ih`u(>^X1`cwcYj>!o230h-47SS)kPZAe6qY zs;25Vly;JgPBW z#~nsvIMc5`h^Ik%|&x*S4Z*Z*Nh~LFMc~c>lq9 zcYJ~m-(Uaa2S1Y4``mwqdF^TKBr-T82P(`-(mKH2zORm-KB?b$3lKo<;}0JE?Z5l| z)6>UYCn;JRc}p%Yszh#o@4$ftUrE!ThyrRx16|B{wOP;h+PdD`o*d)Mhl2wkR5UVb+*U#l{jpRXsgEW#-EH)6-MUvREwozHi$W z#w|@nPR~xaZ4C8nd2roS5viWldwYAb*>>AiOsVa*A=QwRWmP5&ZWcmHDO5A*diB15 z1R;bZT~=|So%PFY> zvt7}8Deo<-IHlBgy%Laj-nm&_J0gde`u@FN{_@t18y54YpL~L9pFQ}jX|_IKRfQt= zmtv&L7NFe89n$gO5ES^OyrN^jq%kdc^%Uh*KorrGl0o!-=qoNFWIR1_LUP0)YO0zN zk~wCv3p&drz6YbF)g6Vm-2~8NSIJ<)jds7nvl*h%*Fgi^^*lyFuNNHuE2Z)On>^64 zbo^7G#d7b>uYY|uKk7SiRA+;|y#+GQ7xU8ODCeAW+&1g3>i|GRm}x$*MWyR{VySBq z$$j5qNgklIEms*GfhrLPA9}#ng7Q<(`O1RuL02&{R<4atF%%EWR2d zAUkjY5fLn_swN->2MB1SqDq<|YSPROL{r~&tMm1Cb-LZ0=f08D<&-q5q-OSWb5Jq?K%QALr8q(ParVsB?<5QbKYz=6KlTfx-O+I<&=S(Z=0s? z`zkOY)^%Oi^?Wh!Hr009#Z5yZMvwtOlnmxot*SOsn7#K%s0hknju?Z084>%c?|bKi z<=pkXA{k-K3V=R1&rV104H1b6^RVw)p~5hsAOI9j001CDDemyz8vqg)8bQ(9AVf7K za-ph12$c^G8Pp`^zUkgSe(&pF`?{p`^wDEW(lpJ{Y;Uz%iOAvMVW_Iqcakry&}kX& z`U=Gs!Dy7G&K^X>JaB#stzmF*L<)waYN9lK9Wx^XQbSM%@I>=a_4N!)5HwjV)b7IQ zEOohPMu@s|J-8!_0%HyxCMa>|u-$1q&FzWkIv7$bw)r{o!BYXFj)fNm`yiw#xdtWC zKAy0gd<=~)Wu10*rVA@KUEvkz=(%H(cgM^16Vu%ozv-n~`rXw%e%$lV{~{dg$^kw4~dRZ2`o{=TTD>6%kYSz?TRIa{cFT`g5fBpYNP zlWZt%Hk*}u?!6~+1^*kpe95_2HD*9kvz&5Q=G&kdt&(#DG(_kNq#(_|?B%*&esOJl(Xf-8cZ1?eWR$cW?dT{j-!( zeX#E|F$j^RltS>TQ``?UaP%fhAgZF8qM?AbF|Jmt#bVL5?R>V7m^+c^W~(0WKREl7 zAO85k&wd!1PhI!G(+UXP*_B#m&7sZ*zw+_UqQ8u1@3W)7bZR z@N$(|v~xylo0?TL~liPm=g>6r)unit(Xd_tRccI%q2JR=xt zWF!Nu!~QF;N^^7b(P#4Hr`KM73HJBkjo+$of31G_(dMVW-~IBBUatVd{1>an&Fgw^ zd9vAb5{-U6<%uFX!b4j@SNnQCo6qa{yq=}89Zpj2?w{S zSwYSz#7-Nhfg!A+B6jNY#esY#_a?EJ3*=&lpG!)T@ zIPdrN_Q=)q<&ub-rtym!5ZUo;RyS3(T<$@en$=pn2n2+bw@ushrP7BhA7fP0()TW# zA`>u`bPzH*4t>r*EU4K)0MwxAqxarnDPl)vQdQ@;p!D837#mR`;FN4~j*DtkxdqO- z&33B@2B0D_B>?m6naDX8raDg~QHfn=BJH+$_uY5je(SAvv(=Q_O|x394i5L$>-F*R zaaC2z#eBA0#<)vu)?JVG$}YR>Pn4atL;_Q5Nnl2iLHjra=RMM}0L!45&;fsWLe~YH5`10f@ zRYe+X$e+g={~JBf2n_}CrPp3Nx^=rZh`nu_{`_oRWL-kFpivG00G;Qma-}e(>-zb8 z?wl{pAX7>zWCkG+^ITP9iZ%O`E$6&kE>(50SRfYf6POtRMU!e)Q$$iz$uXr(?EAj& zQ_kLdPZT_R&&afx@68rf*R{>ITcwWB0}wEpl{V8J42T%XB&W7d+qQF#`Yr*G_d&8D zGVJ)!DaMjl3L%6L01-?bIYkiA zn9%@%O`$Mx3mG?qL2;3Fhs6N^08Y|{)h;q?#g!lEyG0I_Koxjk1P`p3;u+!4rvPA} z>lR)OPCm?Nya)43NN6~~LkNUGJmNtHNK{ge!?ILU%ZB6}6QL;rVAg?8YlEXT`f9_c zJeRq02`IWUCPQ#>z(9ea4!_*k9oU7pddjJ2m}vys*)N7=adfdjvv2BN)Zi z=qT`_&+mTt+4tW#bk1*^==S7$-+JS{cOP`KF!RJ7(Tpj8k>qHm#ME_>BGj`=dLcvt z2Sx)lW-5j$s^dx(>-oMY^u(?@Xtw$XzxU&x{OK>_qt7UQ;`GxBPYKX$Rvu_l4GyBE z)B_QF<|wgkdRHG@zjgPlSph}^{Kd~7pPw^2NXZ=AX7#jh+`;u*H}2kEfB3Oqx4geM zyRpA{{J77t3NsX`2p!tf&pwTsZohn&*lD&pY15oj2)wMtK?W3IB67>>+E?eB2OmEC z`5zw~-Cf>$JJz?|8#i9MedqA2U*Ej{-g33M>AWZOwSU?k|NN8p9<|#+sBxH9&z)C8 z7&g>;QQ=E?H~j0E9WlGghq`j!kJ%-FGGPsh(f}CfPT@5mG7(OP3}P`lRv~cOV^@hzp`{aJ^yEoo={TDy|8Jev(=jUgu<#L&G zjwzj;omI{`=F)Y*%m8cx)rQ4rphZRx006ltbr$?XRRoEIh(r`o6r!3Dl8KrqI#bPN zl5^^NR24Ht1eTJ81dkM$IwS%R$uV}(5|}Ze$bcH!XdFgHi_t)Cy*y0^9QwkpT!Ck% zvE$azCCfCzVCBnA=fM$Bry?q-Lk0Wrt|{gn=X}{cih%WG?rgGr&oz~ZFGNeG%l_g& zKYQR%mhtTSuDtRkKG{e_D19``_C`lHZrpk074^L7`?l%N&e!YJnq8=Tg$PI#f+s4p ze`JRUkaM2RIE2cS+`Nu4;v9)7#ja`E*=)90EcW*HQqt`bvuba)H=oa(C)I4G&9*-~+nOPfIfveP<_fXo1W?t~LmRkz{El-3D@J0m(wn8hdG0tL@qOdcEGR zH?iw-PB}-Bq?%MCnxPV@5fBtrGX(?$g|V+SOew8Pu8gIfc}Zvribf1bMx`bRxL}Qi z+B#xjfyZO;aE8JfwTV=$~=Tty(E8dW1NF9hk2);{Q2#DxJ#5A>8lREZG zHR^ja$6e(={Mo}F{gXfW?7csg{vpdlhaHg$ID+V4OTfV)=A==`J5YouNYb-DHL=5c zo)@xx0!&e7XKPEoQdc>mIl%h#r0Q=rsg2iClLV9(*KW|k!kxFQJrTG4ii64KJanC3 z%va5->#PbjXf)ME#2qd0Xo+Z8 zAJ|vURi(qytnyXu+3&`_Y*>=KU|5>b6QUGUOhH9-RD~}BG6srdQ2Z84U}$P$r4_bl z5=lLPAvhvJkbk zA01vNS7|k;*#cAV_7_!bO*)Or;DH^grJPNzJ#SL%Q_9k&DBY^-l_|{XP22W4R-qPE zVm8Jo`Owqpb}K0f5+b%;TeF{6^%3~4>*n*hLO$JW=CiqHCIti_<@59HY;NB9rb(3# zq`6tO&bh6iCgr4nssPSWky7U1$vdtB`BIymV(yyl`TFc^e{XN$-De+t=uBUI_2myf z_<&vL+B};yedBojP;%D z(il0?rQ@%R>Gk0UyIK`hG@8qf2>@`>f@a8*D%hnGk0A~mB;bv(fNOv_wr`_y-oes| zP6lu~2VK_s+RYzb5FM$8xHG0DTm~zaox+L( z$$WJC?rUH9idFSCOB72fXYH$bRfXAnz9bi7%s}L;P%g>&kW$Jj&Q*pCpor*PvFq50 zno{hw@6PA*MOD?aI*3L@73nscjjI;Sem~Xo7MT|{A{z?G!mnYDOUnOgqA_Ft&veT7zV&k@ZLE|NRPCd~OCK3`5 z?RLJvi#Sz90BkChBcf7~ijy_DVBwS`EI5CdZZL`$jy&}R_I%+3@9eI^$Znldvz`1r{W{@@Sa|LJ?_`~>we z+atsdK|m8|Ym~qkz=1F^=PY6r)f!__cP?;}nnauS=_iuaEsk7O3A4={&uqDmlA69f zqdl(=PM-esJ?Da5KlDcjtMxhq)^(Ll(bsLM!n%3jt{wWL{aNherdh4lGY+|r@zXV( z&iu85aCpGJBKCqVD%Ce%zU`~W@BZoPPyTs*|4(mx@B54SjnAJROU&Q;-d{ieU&YPw zC#NUf(~lpJ?slB~)gBfAj9h6GMFEH8$pv!0bR`+L77-~0QpGN35gcTEtq|Eninf+b zJYQ598p&i4l~M?kQsjI#kPp z!+JJTQH!bDHnCoYFstfno|hmAlR!$6(^fVB#?FsM~w*#`q&e3+jc$0PSQqV+qQLGYnIq|%lV>meinjcNv3_@si<=<_ECLK z-G$5sj=-5AkmFF-z8*xk>Z&TlucmEN*PB^N@%;RJzFe%=>-+cbhY((R>F)iHA8d28 z*=!CDmIm6lZHd|^yTLE8G!ylhh+ufQQ^+_B)uByHxms0KFh>`ZK^uupWry`XAQ`9y z=WFk~9K-~`)Z|ME=~p`G;#huxV@+PUq}`0zu6db;8Nklg06XaQKzSbNS^s}KZ17{S z^hWyHH^24zTVHK*dUC$nv^}}N&Mg*ulEJwULhVB3y_Q8fq}TN}pg6DV2sQ*#vUVSz_BYIY$5}(l5mw#8KtM%oNx;W>=Lob?!8$KE-wF z&)eoS_v^elNzK+`zcD>_t|Q*GecN;~O3sGHU_?8r0e_z1cGew~c}`}aCT7IQV-rUJ zGDWrGxehQxS-mjoa}Q|eu1`%v_Vig-W@MG(k@1!vaGN~HsgpvdI2@7%Z--ofdZ%dU@IdHn^4efbzXmGK&;x8vn>#^u+G zYKRT6O4-xAA~(y-d~p5xSHHRKo-~i|Kl=T%m%i~^*JytKe7$%3 z&foaE|G{z&zx+r4$)o#^=@t_WogH2@H+KDXnw`j6}{?i@p8D{(c{Gx86V-7k=i-G0s)9dFuOE$FMIm zuw;}rEr69b&wK#Oy{LI_IfL^6#HcaFlrkHrCLCJ(0GZ_&$)IrbRRsV9w3D2A6_s|q zA~g@tZZ;4qh)J?2f`MtuDT}EJ0J=di8HQDaiblcL^SP@+=^R;1FcI~AT(8&bvvY@3 zRduu7*7I3Ct2gV-{rmUddFP#5w{CuX|C1Q|&1SRQo97foG7LLP@%b(hX0<2+@33O+ zqW%aCfG%UeFj1o>2*HsXdvTAPX|ln=RWd>YhveDU&Syzk(v=Xgj48V`nY0%eqN`aU zxNH@tZ?ThSzZ6PM-{MM$HE?lof>bWfwzDgjkGnX}mD7H~E4y3WuF1U&u8*_e@|cdl zVyj;?b|2`M4^$xPMeF^;uYUX6FTMQAs*7jqrt8%6%#6&eu4f3GGvs7y(RM1`3a6@^ ziJ4>HYnI7SFmq{^lu{~!Nmc|Y#IlN-%;%Nk98=qFld8s)a_)TK{eyZoo7Z(+*E7vw zyWM61Rfk4(<(VCrVG+>d8o>jAq7o_~7G(#K*vGbQH)_H>$WBgod%8c)JFY6n%%G5C zihW*p{dU`IpKLde`tDTY*7betqNYrM1h_>_R-k4ipi&-xW%})oi7_(0f+L~^V}f;h zqf?o7N`P{+5J3${z{reDQHv@@VX=T+vhVoJ*bz!9MgXSdJt5i^`2j5bBZ&~00Z@l) zEq?-LE~K@R@j*ldEessfA#6azX^o88T!1N}!W~ZW4#@wd6ZOiH+j$)TUZhj}YmDIx z!$bKMjx0Z$g)Vy0JM;Rlef!(L@zr-yoHW_$MDhfnsZ>Tt2B>V>Zm07McyaEv9xgqVv7 z$G$$=qs3BU&gUJUp>ifU_PsO>wXtox*_)i$x_q;$qeCqG)d(na?X;IY0{iflSwkk6YgI0 zwUB*Mld0E1X`DM2Dx0c;liOt~1)O3*LDUSU)`h;KQ4@xT->!)w7sVQqzD? zXNg^(WA;FSoT&&SdUHSyD7oZg4y=)xk)ot1l61&El#GWBasjLlaSn)=oweE8Kd*T*MWkg;7Y>YImeC-KqNcoMCAPZv|`W9+wHcl z>t@?@UH9pypS=3Y8+*&W)jG%6w=Gpwm1J!HjyOHT%VzdM0C#uG#4&!`-2f&L;gPjH zxV{7DcXUT^Y^Fnm1gzj{@JXwfV|1K_g=E?bcPF9QRW;scX6fXhz;q>7u*>dG4uRbdi6d!;mnl9Nf71)!+Dj z*gJT5zBPrc%sfY85j7J+7yPWQTuNC%2B8rm9D$jM$IZ?K=Xq?KTDL&1SQMy?LnjLsb!Z5s+-2_t#C#$<_6;^ky3>QdP}>i4X~}NGuwH zh)9f*QWsMv5)qY&N?N8oyHYG%h`saKEL4?x(v)($?KkUowO$=R+^&w><}~#y&8_9g zVmXN^Au<3eA)1jP6N9lJ$n)AqOg~I>{MjcsPRff{+zvcZ*f3>{0wWJE8W4cuG{rA+ zvBgguLF%&PFH~8kXI8RB!#W@TI+W+xMS8MOWG*PY;~*kc$RdPD%ws178hePK>GNmm zXt=LRJWQv3?z{fAj|*RJBEyaRV^bs5muAcFedX=H^__3Od3XTZHEkbKLc>`bmi+p+ zXRmzaYk%eGdyjwm^k*O5|H<*=r>Ecg_Se4k^?Un=Gbd@Y*5lKykuW2mA}S8rKgBK} z5n<(#eHY{7hY#O>@8kO)e0=ugv)nvIISshQ&?H?Gsext%)erzdvZ$H!4j8RR%uob# ziGiSv+12y&&a~cJT)Xknhd-SymsSU&0|Nt5CvxXt;k(tzgO6wP+v^YR*RS16*JjDn z0f%L^#06Be&jii+YO~o8uudDnL_M+TdvBu(3%Z#3qL*%T^A4pE$Uj+1kQ(UUkLX6|ColG!wgfQgxzqMs@WF@Ryf=dw z`?hIg-|y}1rMBO$R+hqY}Pc*YPEjydW)&=E6$9a}f);}vG-6^CP>+7J^lj2i9E4Yf|pJoXI)0(9)qp^0V4Nm7KIb50lD z+b|~j$d_!}L*H^~}Jwxof*^cYeA(IXQducyoSV;#uw^ zh=2+hBtYWqfqfPzW**@1EPg$UW|~LDeTB60MJgx3dZnj z_k_P*D1cy&hGG>kG^ivALF*8RSkVxT$wjFZZ!Y$K^PPA8##^txdF#mcjh%mHKn_3^ z8`?W~gn@-Kh!CqH@ftyf;Td;R4**XsQ_ z`oWGFfQg|Bfq_Wh#|IhcCaReX1AxO#JHgYv&*Q z>_E3S7hTq57<{$omdlh{2p&Q$)>G=Oi?nGmZc}jXV83Eym1?fnHMJO%x!Q$_ zQQOClYtI#SNTJzorQI%kZa@DZJ$-QM{mrkubNw6Nee~W>PygV*zH|GH-}`_6-}|5a zX9uzEV@)%kAQzpAAh<&tn4<0S=oySm*fUq`7h%4v=F4ia4D&^p*M3H!BKCle0A=9s z763*~Omq}FGb=?plTwUj5Cs#J96=Qrl&x@ul>r6FP_>XSoEQMeBn8XF2#T6iB*(T0 zm>{~GdZbc#phc8)iZlw?sv?%-rrEY#@5A!iwF6*SuUA}Ej)4%Eogo`KsOF1XH!@=L z^qA35r9xDOWB{HtVdcGxL)lvd6RMjk!5P*qH0wWN6a0msyEtNn3Gzzrf zz~&jU*igbJE1r&k0Ae+(U0u20!7+NK%3~F{4(u4rw2RvI&FML_JJ{Qkl(tP{2>bi{ zh*-+4PtT5h;F~vZJb3T`5&AC8>k0=ZX<3?9TuC!S8k?VA&i_tDY7-sBgQev&%cs#r4Uv>m zp>o+sghsofSs)5vM1!R&o5BRuOs>hK+VTvFGo-^tnHeZ8QLi9Im))c3YGL;i2ZV}8 zScnHYFg`CvouIhMXupsbd{xf)D%i6Nj_w{aTt6C&J$A-zN)V8Nz!-q8z44WAeDANc zDfdZ`ohd+8RY5dzC8_KBzIW`Z+974gn)*aU-Z=zJeHK+Lo;iAE2K0!4Xrj66wklTq zdglt_kQI8-oO1-~uHRf7%$G!toXY~6wneKv%>j@A90wI8;*nHMh$@(IFD7djc8xg@u z(tqIf48=f*N(2T1AT+iWM6_a1a8P*}oS_MJDHlIXfJ-oo4Pd9)&J|xwuV*Lk2nM+n@i&7me`(|IB0J$Nd`b8L)RSgzQ)ek+3*rE`l??y;%O{8?XP( z?|l2-wIkQ9?Cdc@MsQIn;X>WqUw=Cvy!C(n`+smYzy9@CUzYXyZ{1nOC!hSI^&hlw zdwpMi^y$z4q@K;@i^aTN?k)EB_e$&IoC{HX+ih3d^V7}xtm&UXTtn_4w}3j|ulEm^ zr^ioIZb34cFe3wr2@cO$0SwTRCPGFuFk>*a%#xYr4b^8o9=>|7YqzmE@#c_R8}s48 z^-oWvUc$1{g>3)ycmLD>{wt}HmG^ttZ7t@eO9WQWyr4qccPGbwvtEED({6Ph2s}A~ z92xs8TeVQn!eYVXK@2o+*RgM!-~*dDQU@xlv+FZ|)}EYx_UL$Z|K2-a`O5cy`_uRC zzyJUIKds;W{eSz7Am)>b?1LIa|)c ztoD_6o|%aqI%h^EW@<$kq;%vdtsu*6!4aVu{wh*7&T)yT85G2(csppuh*G*w02+W~ zncnWb@B5yKB8ib0EJ7&a@m5tBk{8v9u-rH7q@O&?9*XZ)^9G?k3;9kp=h4~ zv804(BrT0TWeFWcH6tLCL!g?yRkN%yrCvcbHEC>M`Nbw>_%GV7g_-t(FU@QJD;U3O zboZ+t`>%M|#chOv8=(NEcJJORFTeA(ll8Wb3D`G%quD$$4kQ>MCPGXpuGYCuz4yNF zGZ<8Lr6q1rh*|AI$VWlV3IhX`2zhAMhNW)+fPrZUZaxq7{Aga+(nHs!B$gEoD#l90 z%(S$oH?X2LK4?cN5}QFzDdm*2NX}hMZQu800tX+ccnDTZjv+hmeW?cb-lLKUTUIsc z`ewB{JNfjJv!{~qe;G`ijTxnBVn1hw+l&7+ zR4`%jM2Z`aiVBFR3X{(cs8rgfjs2F8Y&0oaFfc+bzCY}2K+*?BeP!7d-)D z;T1435r~W=o1J5d<5bF^MB%y4gQ?l`@&)$7zTAJVv1{`FZ0Aod5TO^rn4IX#i)+9A z_BX%(^|xL-nlGe_Cl8{=K!yex&}bH`>rmgA-TKN;9=HF+4?g<-|LE%<9iM8h4h;X- zzkR3wy&wM3+0E?sTJz1e+Mb40%wU34ip~wRXlEsW4zvLjGc!BYu}RGs$`q+q1QH`tB|tG_N9QnK-hSCTSf4)j+JRC|G(URD%o_qi_l-N$ zef0bO@gINXH{QW^y{P&aH=EO^^=w%$_Q4W#Yiic<{^XCl7qS>&#;5@Au3DLPRFd&NDhhUxlh>x2WdJ+1{dB&cn<(2Y@1KITv>nbkOu1 z1`P9lAhM{!l{A6s*r5c=aaaUSvS`jGSxO)UpsE8O#!8+lmx6`C31GIA%rt47KF-XH zN>c1%ehxhWvQ$;&eT`tTZ4USLJX7#g_Vuc&7K;S{#Ga+Yda-wS%g&!X?$>M9tR>ij z;$RTaKm{No_JL(kcq`mwk{qha@z4_TO4b7r1umr95YezSbwjnWH6jr()pb?PX5`2b z10g!+Lnu`tIEuS(w_6=tM9DeVb!}!%(`+^y=bZPxR1dA!8zQdjy6gMCZDNeG*{m=X zaA<#h5xWx)O}EO$PtweVdjwEK4p2Z4(J>@zsUI+5wu;a8M`iR&wKA?$c~6BCRFa4t_0G>gCREOv9!u9 z>cV~BHceB6_*65J?SNZiQt~F~y$>OTdR}Ci96QZ9_9{K**!SJ$?DY8QX`&n33f zcaVEfBV^)T#Ni5^>xFYhhs--Xm-O)~P6?+n_zj9MM4@J-D-{ji%;=XE=!Kw&!{E>m z0LPS#!H~M9vYvOlw%VvFjfPA}qb@!Gq%0*LMM$b(s)pbk8Z^mmT*fB5#| z(eJ;@2X}g`&$HV^TJalw^5p>l0dUs(!0^18jjnG_sN@aTWZ`nFo=0etN7~d|l_& zxy$jo18&#aLFWTomS*dG<$-}BYyas-AGYs5cohzB+4stf^$_ss@-fJ7PX(v{M>VJj`&KD18i6sSt`U-YGdH$q92VberKMrYaz-xe;F} zFRIyIJ$K#>V3G;o1$t@)Yuy>jXb5Ux21tlRp{iUJICx|(O(UH5wmf1MGXYZOy@XM~zW9hbr~^YzMht4%6C!&CCGg&RbTsp!cD_aO)>EFHU~sQ-tVk*eqtOsu^kAZ$Hjv{omvTq0lo#z9L)*zo?dE{4aDArt zPFGS6CPYBkMZ8`#Mk?#o(DaLW(9`i>YNdw@HXz_^Sk30QUU~JjX}3(inh~(1EPYBb zb!OY9AtLX6&bbu9l{FYb2)=IHp$DpSuBvCDt`P}9a_)Wbq2iPzrEI1#W@Zkds_S}c zQyODluU8_;``KpGG1kE^s@c3x8bwF&X83B;L{rVhr;lmdb#2#meczXRz@z6404Zk& z6hf$~YBrlKmrEaN6>Gb8y*cmuK6QQE_U&f1J~`f;ousw_S*s*af#M$+*)Y3G((q!d zy)p;NL(yk$y^bYsQ{YE~?&Z|2==l%J%-oPEzfg{OQJaYo95p2}EUkXYCjX{nvdS8Q zF{6oy4oP?UhNh|pu(O1SaJPRpf@%ro!tSLK5fl||o5GT7@Zw}1PMJ2$Ou;rvrzP||1-0uux=v@oyleg$s45ly z1;uG6M(PNZY|yk4(42bbSPQQRLB?(@A~K{rL`4&tTP~L|ov%Lo+&fkzMT*3(K4>G( zXE^8dzx*G36CeCOef9@m`}$tZYSlr)d8qeh`}^(5V+7&UcgIhC+eo)D&I0BI7S&$Y zG)CN~zWZd|Hm#{5Q9YY!cMi?k=dCu!&C}1&`s8}uoIL&TL&m`2fV|fAa(VANHxIA( zXAiHx_U7$>=YRa^AO0uJ=IOzV?(Xqt>vfpjh$_$3EkYn7N5lk<$Om7~ouAi>c{QJf zI=G5=wqcPX0|P3OWdH|qN-5<@HfC^T%uG#Ha?UEzas*Q{(Huc?@l;|;DJ99tB<(JH z(I}E*>|>1Ow1^mEEWehN`Q$m7nGz0dgD-cLFOCHvIihB}YE!KC_UHQtsh&4&TUB8& z(_W&hfL#Q0i~03%xAAoP^w?q^*rldA0#VZ~E7rB#ff6SmfOn1nGnB?EATrn=+2QIV zfM799Mod*zIqx}mc8uNuGZL}^^U#e35KARj*EA_5iE+Y9`o3SU*L7VNSB9N4Gw*%h zM>FlZuC#jCKR7r$ewtFM>zas4#rW>9k$X4o1hg}dE{(#E!Ys_$JLj04^OIoR^jr`D z$w0&)1EM1l00u)QhlmE=c_#AAo?THMg3G+!e>=WlOfJ-)?T(?DX1spmhFdPTT~x>7 zStM!KSvC|UKt(Vce8CWcbD_+WQgvVSk5mN#kjy}GP5>sFMYDl=av`|+VoxL*XjN6S zdZwx=^=7QJQ6_ak&aaC{R6)lobRjf#psGh8dv|0jZ)Hq9f$-){O->2B?6>uJ~xm zTy!c6YGMGQIus~?0TB?A3hDqH17a>t8z2==35-mkVCaVC9U(d}Kqdr~feoXhn{!EY za!Ly8q<`={e(v;Qq4SMV>TuxB9kZ*)KjYN@TCSBLl%hu(=|&;tHzEDEkKF5WLZ>PM zXllgSut#QB1*pN0xX!Dy=H!oQeZ~+hy8Y{S-hAo$*=amH22JM$ z@8|bFZerRe-kWiIt^VY6?T85pB?W+)BlNE8MU`TLfE|$mkQxYz0A>iFT71;vLaL|{ zA%J9M=gji-g~ZIrXEQ=tEcathxos;j6inzd&w`r))_vB6m%sVl8{dBe{`3F+Klq&+ z_1%MO=xX$qpx;~6PGtS$fp*&&W=_3IMvd$c2`Hw1Em$8FLmLkP}tSIjSU+h$zK6bA@E(TWBh zAShY3(rL2nmjg;#=ByEz8G>n1f*m|jGh@$y6mxp%&K*V4ZhM}j@A_M}Zv(Oc5V7-Y zV$ON07ZG^x@CK4Ue|i$zEhCa?QRxL0Q(@B#MvP#pAWUWmWXgshMy3ux#|rIoc?`_n zDOeU!FaaygvAy@9s(cmD4WtufA|i+6yl3y37|dAV{Pg_v5(@Ph$^*U5G+O zB})C2#dSt(Z#d6%=mc9`TroVyUJow*7+w#Xmf@S<_|B_uzIFfehY8SE zbtw>3L+`zJRShJ7i0IfMp>xhVD)LcPRjCT1BjVCDploF(rYVUf#?;9+IbJLldwT~> ztC9tj+eV6oAXdVxYE_sqxxVdHI3_tcKZ`NNEP%*_ge8ljh3}f8#?<$H*Y{mY5sGF8 z025o0lXT9J^K-YiSk(0lhz-<4f@4$xjk#H!J$dlt;ls1D^SD{LxXrOA5wg-R2LQ3C z|C(JCM@)4^!}j~kgC3qB!n3dMKG4aJxp@5rTPq?u15yMg1yZ1iz6$^_bP~l<(KGlr zg~1I-R*d`DqhOp5W#*Wf5Rsut79IdFs?=(XkP-lx4dm0SUvjq;dl&q?^7C{x$M0`n zf8}~jsR_4!7H|C1cYpA=zIN-4SMUAkqx0)W``gv}&aK0fvnPtoL{1gKSQT6)tQP8w z$#jJGRYAaksUN4tu&k<(RATI%TIoy)zC!YKU2nR? zM~8LI-+aaYNB@Wa%}dSw@4oZ;{`_cb6-mcS=;Nk;@>$ii0V3qaG$8$S<5jx%8-H{EweOBBuN7||@bFRn~+Zq!sInIzC`A}VOfL?Yx2 znot$gQc5G*uQ}zEa_Mvch6u>So-I>0&0tv2TrAzWjYJF#keJ9Jk_}V|L&VbTu<81v zqoZcK`Rub#tE%3=`7)DRot`cC57`CcH* zDF~=!QehB9P!k>1@yP&f$Z-oI0Xkwtj5%i&a_oa=?;JZbGq7x;SqafO$B0D8-aGHT zb3`m6-L}!T_du2e5hcZ@%ijCo98@8tl={xZ72n8t-(yn(?-ct*U9HZ}u3fuU*L7*^ zpsFIn`z&RB(Urp z0{|g};9T}h&QuA&zzj)DirWaVW35J;wChG(GCSqqfgAuRm(AChka~e}yJS?y$lMBn zeT-?Ryj7_J+kNlov3}XOIQcI5@|hd{rRRakb$Y?az{N%~97Rw%E1{?%(O19u?W0%U z`S8)xsJ@;rMGZ0ru3W{%buH2<)0ALl5KEA}FP^R^@XmX7fM{xYvs(F@02E1`OGFTA z7b@5^=k2!7S)shWaw+n9vb1j*JVD9An;W)-kna zVjy6MO3Z}B!F%U?6{@1C9mpYp66PHH*miAH$<6BI(Sy$(K76>{Zo9tcoLtVdJH3bI zMNk^iPu}y`L*w$zf5w$jvqJOR<$hc_a`zE`@nd$xMg#q&1Ub6P*@mGC(uj-*h>_R^ zah<}OdBMi5<0aad_ZkZjrUMK?RflXnAes)JSUhq72BT-Gyi-1vkm1FmGXDk*1VDq* z9)eVFklpYqr^nXUx_&})mucp}131S)jB-*UcFc$lk+~vQ9k7Mdv&Z*t-GN^mi^Acc8I3 z`SAP~pY=~Z+t=228|gPOZ`!nt+1jm~JX~!XQFb4{|H1iBKU&nwzxqFWQ*M9d@H_v( zlkL{GPwo8U+yCyr-=zP^C;#Q@rk&oNxmI;Sl}Sc4L%lK!RaH>|22oBQ!XRWab{s5) zeFJu)R$AIl`JLht!WhsSIdEo!R;&RjN*>Tfz${Z0aOv==Ly~sP4Nc5h8zYLs2(}$& z27&Ej0Hi9aZWPQFxM-V)Ff`1@F;n&C;a?0GxM8 zq8XBN`v-?9hSpZv=NS7Ec^ZHR63MKF3ZRgg3_+%JxeYr_A@>_W(yR*Pcs`#Ylkt%J zH#6@&I#z&^?HZ);xG*R&rPTMmR@vLygODFIW} z^?H4DbaZlZ0+*BYIHZrJ+VD%mM)neooel>5o-ykLQx8-%ilMUq=68PM#+`fjA3x2iE(9?EVjn6W zLhydv=>PyGo)!`17z6sr8!M760LYB9YG&3kW_FH<>e@T++RnD?n8m=DIWSdKs7mKZ zW@bmK+O}=mwjza|Ul9&$q_UP_G|Og|QrvFWUEg3yk`Mv_GZk~?n0=U4^@5oZIf^hK zr2g#q$?@YyXU9)+?E3TbcC+dG&di)ysT6;XQ~YPX!=u+?<3Rj1`^D@G2c@gWkYFmT zUP7|*(Uc^Q?H2XHAXx~=3cz1LbjMNDZlEX*ida;BE2h}YR7yOC0BBZ9O`QoEAUi@( z8Hx`Paa2Aid+n%u`~_IZgsV?{w{ntVbZE!Ej9>QRjWA|d$M+(Dy?_TijOp?0!pqMc zgDVOE3IxE0s73(Sh;V&UQC`+Wu~FYyr~rtmGOX3WIYmH1Cjy8J)jaGU?JIUocWajJ zU7xKs$$Q^OTfP3BcR&5fKmFv#zxl1VUw`S=`;Skq&0gxxw}*9owmt*oBHe%>ph`@d zdNnc=6(lM+Fo-!S`L4&cP-cgf56#($ojj>22Z_X`fh;LGQ39ZzU0hsSRBjeX zn$zQ_kK)IFO!0K#)1j>8?8Le)Blb;vx^7Ol?fH6BAy)f)XPfx+!RLS+7V}Sj@DE>F zTs!*8_i8ym`{92q&66h|zkmH3fAixX|BLkbKe>6RYv!lGo75_1U^gvb)za``WRD?X zkvwxeJ#8d%G_*icRRAjn?n2iU)pS6!a?S`r$>EST0s{jP$-PMeVNfv13Ky7sK?S_` zHdX>pBIv1*X+~jR7~|S$woko_r&HNj5;QFnVm7nYY87L2&WYr{-5wqtdV=lheD455 zRbdFZkC7;uF|&A=HM+`MPMR?+=IWEQC@~7EMsa|H5|Lv7BP0RK<2GD4p1EWv0SGBn zwIgKjx$;1Se9;<^nFqFSL39hU;v6F8ocq3i{P?j)n$PFuR{)sLX1(c%nAPp%wqBcbZb zp9%!K3vXW;I5z7Ue=8~|`MFJl{C5ZqtImaVU+`ym;6*Cuqy0FC5G6AVU+r{}j zRKXX%VT=u?nY??ecnnQ|4XODvwuZIM?CEArMSA-IwaevwT1zbDbkx0(mpWc@wuJ% zc2Gww&0`0*YfRMdl96q=I2%GuBt)-hg^vSRRCJ~W-&R_nhzgV}-T3wweeExHDSnml z7jly44jjN3ATvS=M00a#+n(i)kB?tI%9h1B1~4!ptOx-i0bog1s$lGloKzlfRg}8b z%SZdaJZ%s5<|#coym2$$`|1zg{ki|(^z}F2`je**>#nO1s{=RRtIyi46w4#CJ(0FKtY5hP9syNcHCH58*R#EYOu0M$^zoyQ^XY>M z;-PFv&zmQYq;EB9jD6Fl&!6^>PP@~s99(nzN6XVqJns~;ELf^A1$ng9%ZXhK)is zamcNz?@X3`KQ6(xZ4MehmDH1V*A){ew-eg^c zv }hu()MDQU8@m4T_@ujmip<($#K9aqNWW|>H+XK#PwyS%*i@U-#O-oi6Ej~uwF zQ&tkeVLGv?fep{vIj5$jb?d}YD{4nw-x7iM#LjzXIW^l&N95iv}Gl)P8jvSIX&tN8&K~#(okTm7k#IA2vo6Ty~ zZrj+zcC+c*El46X8Z(8HTmF3E{L5onUtd1WzVz?Ej+(;;c=<^$QUg6Z$i^Ch=`^^te zfBM6Ze)?DW)(8p4DQ|840Bph5neC_quKl$V&H%!&yus&>$Kl|j~@|THJlw9R29S z^fz9go2<28BlVVvy=TXYRTpzssRNPa8B^b!o_zkx{N!EEn^{`TYy~~ZJv+K{_qEqQ z{P<6nue^Etvr85sHc!0;Ckz>vh3bn8{SSwk5(U1Qm&W>+4x*!}RRB9`umL zpNAVFf{5GA=HTGad!M>~7nqrRq3K^lLSjHFbX6oqW+YMrG-E(hODU@wA(?~83=%tH zM^scQkf`v@G^(m%?TV&>B3ep`$XCwQ&iCH;DHaY_VF$>c&kou6Z~0a>?P25Ox03P1 zoj+XNi>ABr7cn4v|GtN^R;ov!Qe* z#n|_~G-tRyD^=E-a_Up=*+kd`K|u|Ol#15ea7Ulp>*qyC-iz+BP4nsE!Ca79c8BgD zH_tw%E2HRzU!2$w6%Yx=RjEc&`t$%A5db?RG}NO01UU41A3vguq+ z48Gb>rPjBYa{}yCQ+C1m1LL`6NCwP_p+^K%^4J;9J=bJ_@%+~HX6{zoJ|E#?sjz(Y z9$$au$;rtN??0W-_Rrl~G|xK^edl2j`%ayrWdjxgBUXc~MGgwej$Qh7Tp+ig8HI=n z!N<@LP{l4v?zPm$XpyGAaqXH9{@q`G&~!6b-<{8{L;vw7AN|Z#P|a`c-FfpQFaF{C zn>Sz8*K+5zN=uilQ=-hlx#|GP9Q1kHKfZ6<6X@11ZfChM%V4pQRwYp+73eZ;ns|J+ zK3&BOeC^J)z3aD*&zp}QpKm*f9k5sk)`NzA)sUmpC+?5`KWF#<6z<%Xys7i%(eM4o z*Z;l$?%vUBoBdPw@hQIKSsG({^KGpWz9>-t`cPP~DMiD;3}9roaC zswmlrd{L4Bv!d;9G|+Ca)aRL*iGgu(Ze$?l+eoibRYyo*RTX02SKe(m=TA>gl32ai z%em{@hLA%DK&-4n<_W-iQOIJQNYY%*mPIW$07?=t119w-S%T+D`6jq3r365T>H6&K;NYN9iz$`ki$jS(U<81KOvH+0hM=X?iNKH*ZDG*j0WNm8%PY5v6^pn?7TeoDyz{lM_Ivx= zNaU+USU6uZJ2N9d2dZX>YK0t#3IGI-DG;K75>i?3#j$245fN*GbkCN+aeP%pU+`T>yH`I;eo5FpeY%EGAOe$Xi!72X!*R^ zNa~u{G)SA&3Vkv*AM!6(1qrJWBw{PEt^~bP2r4%`}Y?_qBkcoT<;2>pZ7RfP4sp%qP64e+R zHMQ;frEBx|&R2`Q`L^p1!vP-L%?FRp&-+z#lB?_IsZAiN;)CmiS)>BWhS@NyAvv(3 zJ!^2$_0uMX0U?zsR0=I}PNGu^wHOgB9i6>%dwY8#eEeV~5L`XqKXAn74?p|3uEG1o z(ZNfO4v@pc_34LC?WLQCY&kd#F!SD%N5D;NHrsX;`>pls$~rHdN?*D~8;DxY5~a7k z%jfH+O*t#vynStXaM%gF|G|e3o^BKYfP%x|Y(68+8PvsWE8VgrUM1A0i`o2IRo#E^ z-ber6uJ7ZGTDQ&Sy9KCkBUdS3hBn3#&@d@jy+ zPO(0eP<)DGQPL|ZNlrqpRR@LBr&LOGfi?+P~cKq2#3FF0Zd35cqcfRHh56@GsXLaS5j%NTQl0-D6C{paB zl)}JLhNd87i0BYCr3eVlAt3nRh)6Z7=w=h!wkLE|H4mYl&+6H1A=#K+Rn;NXMZD3> zQc7*xwe7ZPniykMRrC3LKA$slNge@!s9ef&g5?6PN<>ovg{*41+n%qFpPrweo_A+m z*ILS!RV8Co8k86iE<++$s7P1b{TJTA7vzKNf=TmdXZ3#RaL7PtLpLQdK!veMNU3Zp zEMUYz-WNtZsKh7Fb}Jgvc3@~|P+ z256SY1V0YBcLlSOM89+)1Jrc!;)PQ)J)tjnHqU*E_+@0VFBmVF7KKNQ0IbT3#kIqG z-{^n-&qDVJBU#R94&4Gc06775&Q*2QHO+K~idL4AAPM(fK6(85wU>YSWV3(m`p55o z{`9nggFBBU%&LRn04;92&pPRlfU{AkEUFkOpdg7MAi~%lsO)NLpm0I@VU6hc_EO>UYN`Cz_W){7bB)6MF$#nKan+qYhwg=><63ts2f-hJBs z#w+t0KV@yns{Zszl8suMa)a8}ps10hlYR@PNs6KY_IB&!A7tY^!YmU#C0F9AV4zBHS!Pd*LJ z>+c-h`swHQi2#i}`Lfs4ZnmiA%X+z}<}*JFj0_5*Dw1;&Lp4k>rW8|3l1-IVt=J${ z1!fxKdnx`(7A*q6B9gPEUQ#v@$SN_#9Lug>$f83JplP{{14dG(ToI)dn4r@!88{@f zbVieF^Y-CIgvmIRQRHq0A)MfytBBp? z$`ErwJ8Wd?7@)}GcCasvQeX6-hcg2h(EhcXU-|lX<_FhWk$H{Y zRYX2#>AN)m3#zVB6KgfvFfNpkGk*ltlXny_i?W3yc!pFBA^ zdD6DqP8-#PY7B@5Y)s&WZ5(U_>g?G^ygMb}#e3iG8|@j+Z+Z0!lra5*xTC1~XBv(s zdM_ysEL$p7ONf9MiQ(PP7%1oi`|dekN!5U%V2}zxMK_6%i4crLlyH#b8d1QZk*SsK z8IXunbe!QdQYwudnU%UbiJ-zFD0$A{xA39xUtm z0ys2XziFFIADcdLa4`dWb_A76i(nNA8;RsDi6K_r_G(Od)1qh1ix4_%Q%Xtw;k>?) zx5xM2efv9iK6hbbQu$E!p*qYKVY!?w7PIB7ngl+d7< z`w~aCO;f6%OJT+TkG(%_)@8}g!>~1ER=vX>&Uo)VckFv_ccTY%H>L&w0u%&-;2;hn z9i~E*LDRH?vO}TJH|Y;J!nQ&RhD3(GNOm~F4hLzG3_2`{L7SpUks?e81Azt_J)p-s zo^#JRdwPee%FMNvKdh{J-@W%ar*Ai!q!?17aqix=YskvVVdb-)^{mNGGBc{d{LCDr zq*4k;4^N$W_wyPO*Qn+gV~o*l|5A#Fhm>5vr0;taoyBH7Bt@CJt0RlkEUaS?4ebfu zPhRHzK{~mW_ct(C2D0zRkjN^1ibDv*L@|a~Z5KZ+jKxEkWSlg#a-AThWMF267-nrl zrfC?GNNK2zQKcg4X4Q_JqOtaEi0ePps*dkIb{N1hj(0q6Mv&EcJ`AcjRm z0|3TAIlsVR$ipzCl!$O&%n1rG2M^1FkvrpiYcnvArUehBPATQmBhtaa(PD8JLmT6) zX(BNahPGJ*ZhVsupq^f1v)OcAFCv~vU?S;HV_ZaX8B_~Em17u&e!IEox0_+RPW=|r zz}vp>wkdZx^`#7m%D@B|kjQ`l-S<4U-nf`u{%Y4->}j2CLOS z4AgXTJ^xyVcp83yh+*^`nMW!aA@Z2@n(!&+*HcTChcHk81w)VXtW^i8A@3CfQZ>^8 zsEF+LlNlnBAp@YOm=e;|nj=XD)l4UhW;FD6$>*lVHS;V{*IMIp!pr{w6#stA)GQar zC5z+~2$KeBjx-(`MxdyMsU$Xwg3O$<<&pyvD=~9I5lL(uwZCzb7V~hL^NrhgPu8ag z%W&h~y_5bu*+L!?st`fLGb0g629VvzQw0-HBX(coXjwzGCyQ&MIR65mGKS1R3V;G2 zBM=i1NGV#(tXV7$+u2g0oS!`~HRdp%FAwIoBHtifw#{O`AdB*7olowc#q+Pc`un5{N#k`#XG50BL`t`-8>#}6Uq*xRT z7@1n4SuAa%4I&gMSry1=CSov{a#?M<&@?w@v!mtwukwOg3qFpR!2g})VIXhT1izYPexx#YxWYkGi4FSPaK#Ca|jza?g0HliAjZ9t@ z%O#iMi<7BHI&XvLa$R>@ zJyxv8>LTjK})u&BV zF*A*+umJ!}36+QpjF^}~wZzzRAn6McH1qjlanv?5qR7-B5GZgAF1UzHBEyjSuIt>X zWMFOEwr$HJLw|BH#~9VL45{ljJ_JfB-FCZOoe$ehzg?%kqf(Z+xXq)I0s>kvBQhW& zu3P;p1LIO>+BZ#}*l4x~y?mHPc7t~xAebJ6&-9Rdm=Zj*j=PY60IS58G7}LHDWHzt z`O4o#8eJO(a@7*9dA$vw00II75i%kgI^{wT2+#w=R22xTrUHT)lwyth4h#T-dH&a$ z1I4u$O@UCL0D^)#>kiC}T*nPf$7R;1jO!|X)g%J&xy*$)t->*Q*@80z9H~iHwgPnv zJK|FAax}b_c>*sV&`Pcw&qqZ<1yC2;(1#Dcgxv+qW@afiOcaFx8B5O+q6iS(nT6JW^A@- z+Z(gx3vKgUpal`;(r-_OEd9Y^u{@lAZS(xsSMu`F*4B^n&{OV3r5GbZWLAdl&^6+Hcf1h2Q8k%s_T1a3wcO(g#idrtC**m1y71( zRkw40<2dc()zk&A#}Bki-!qylKy1wJlov2Sij#xD_s=jor_2ltL49gMVDedJ-0NPl|2A(p7Nel zjNz5YkI5xgf4Ce!Z-54bHGc!B$ibcG7q@P8QfS5`*mY~``&_aUPfbh7l^kPE-ohGj zPo8ScgaU@ZZh|9%frJo5tYmANgSI&eaSq6py{f=OA_kB|6cL7D>voK)Hcf11O`J8M zrmQajR0xrP(7mZ|J^y4xH#@+2cCc~ zaPJekoR5EtUEGIZ?S=?|u6@sqsTEPx3;=;+(=)WFrfqT^!uHdHdydcGHZ_ zG^MT|v~Aj3$9LupW6^cG{nh(tpFh4ua>Alo3aC+R5N)$wom`wh^k#m+hK@t(ac&| zue$-32lK;4d-C|bgOd+lzj^${)kzGY<@utWFPhmbHZia#p3ON+DU)2cnTe_cD5{XM z6jeed1VZM(qN$8ayK{D-`q!Hff`MgCl8b0om7Eir5><6IB69MuJ8PVxJ3;q|IAx@t zA_Dw7fzLrAYA{J@jmwx7k+AFJSa$<{TL3j9=Gkl}BHQhz8#+^U#>#57icNERaw4VN zymhPVdkze$FuEj~wpD@v0AQQ-Moc0HKub9>6EOlp)@*`t7Q{-7A&Z3QLUEoQ01XVx zaw($P_dTjfF5PAm`G^deh+_0a*8MQd=W|!aGc#sJA~V5pt|pwqORp9j-E&X;>vYbj z-wPgOqeh@W6!u|u1B9FbAdHlqk~1J=lh8D=Z9GoJb!89{2tpv7HIhO}!;l8%vY5}? zwn5FMx+YhGoomuKw1~V} zcOtNu&)arM6l+3dvWc1EV?5>5cb%$)z)dqdI5=$Q3*xHU#mvZ@MAFa=!?xdUwyX8U z!-tO^ytiFnXh{T$rAQvUpHubbOaVW2AfpEjnS$@BKO9?p_vz~%cP~Ku@@acRp#G@} zlNKfPx|+XvMvKDnVe+7D+_K(`vaL zW1>dpDUhm3ZK}q+XKteaIyz%a^L;2ZhwrBZ;g#CK?S>q?e%7`ecrlNq-*!X4JUEzb*6Vk^`r>DP z^g$O_$!I|_hDF=9F$UsF0?c`T&+2<@)hfg=o6QZKKK0DqsPNA#26jwZQIuGkijCuFbvd$zz9qnW79Sw0!qLLilP!i zXxrAz+NPx#eXr&#eb;wg*98uUn6}+4Hf`Gu!?0ejiO3(iSS(x%)fbzco*v>fm_JPM zpzA08Y5H(uz_6K`I<7hbHltt3bWBx&h=Bqz8Cc2LHH0igZ&hzzL?n(aM~Fe&CgrVZ z5s{X8K3|kPnC8CknVT3|LXuPzH6R7DfLIXA|Kw@-`APs) z$wUZ%A|>Z6QeupUhljJ-Y`I)Ax20H0spYn9n{29(2C?(g^Udn~{PDxbZ+&ZX{un{A zWJahgV86M22us$UvN^w2#x)(`Ds*@Geq$Jcjr`Gl$n&4JCp4%U5f~UDj4tK9$KsWk z9jBOGQvRM^1HjFI9hnZGS|!swYtA~XjSdE=9tVXeE(C7XR@^!22-E}-Jv0=NRSSSu zTkt!6iSdsUhBj_t>pS9QSNfIr0=TMJGu>@!mI~Tc#j~qSXx~u6)9P8p1C^1%;Ns$Z z(RZ`90S*?J=7-rrF;ol~+t{>Lq@>PF%rr2Ys)3+d>JlI@Arda6d-TPB_{V$b6K$~gPs-9grsZ+Qb^PO8;KV8tJOu$DpE()>4@$jFcB3& zLa-1*XxbJLQ_7`evtq_zF~(WjENAoEAuc3Sh%{Two7oHz0d=!k4XGz?$k38PoIMUV zzOl`pe(_dWZPpiO+s)becB`t3MO+*l0X0L`^RsnUDk6Q!NsF3cK@}@%2Ec@7M(Dgw z-~dg-^M;#1r7vP0uNMG>dE{TCiD@zDQ(1SzaydU-#M8mf*Q=Y$!&`HE|M55Z@x9k> zz4FbDn$Uz8*f0{40jg>#wl|D@7GoDbQB<|2Z4FUGrD#r?b3w8RQ&CkD!BM4`kQCHN zrDRuhCT6oTejgK&G$bv%>;z`Ja3Sx&V-l8pWhXuLKoJ3`>CYT?9zkqZhZ}QfkM0#` z&cgVbHr}LZn!f8&&bN+lOUkRW3so72K=fd_bQL}#ifsc{jEI}a5WyhKT4V;WKn(x{ ztC}TE(~MkxGO(RMMbWhF6Tou0B!i(JVhH}?ZndbYCzo*|u;)>#y;=l!lUsGhIhp0k zz6&OglBamy6P}sOlszsm?C3aS1KUUiV5X^8OU1u#Kao~`02S5R^;ZRfxsA`^O?uj%xF{~Xk*mw6_au|)Y zo6%o0`Us|Y_-icb{l88$-YW(7988yaov?dO?PgamoGRn%2R%~@WcpHP5OE;N5D^h=IvHgQrI>z<#wDYIqIMR6iqfD;M(J}P| z2KW9}@Hh|4YS%aSpcd$ChR$$uKI{CU;fW~%;83J_jOq|`jE@&gOYnNh?oGN>NHQ2+7ylpRR#k*P_U?tIs{xJG8#n8DT~&S zcry+>54;Gx2sDd)xLn>mIJg1a8geOFP28wWDRHX-bv|F{Y`&Gl|MrW&{zLfCPTtXU zMl`gG*?bn7z(bbR>SEi;x+}dxF;g@p1SIGB02>pssga>ERY@cSbngs$)>r})akY0C zGc$luFt9$Ab=Suj4;Ep*oSm@2*oJ1i z-Ikb5)E#6o#+qwpOxiF#x@nq}lZQ!uFGF!OeI|TsvS6+kw;f?jO6>LyRLkEjS}QsC z)<VKAKXOEAM2+290l}L zB-OWuU}T7<21W+{FMyd=7A;Lw6`P(2Sh+g;?d7`@#VJCS_bDQFnJa^19`o_)9bP?s zrYj!ev4JYtz(%3_TF=WEouL8>D=OgP@Ww2Kd1!9kIo>=lZElw4uY*tRGd?$;uOEE-{foc(FTMJEe)(}MdY~`8_LlPF)BC*<&yU-? zFH3{z{2}xLnGw;~WHSTbz~F8hX8=|NxhCZbG$tYjEG4JXnSs+$ef%_Si`W`86z7Y> zo6F@<2yrL_88SCkIff=Vw~?E9t5W)YbAJAK=s#G=qyJXE`TJhM8;32#IX7*A@^H0Y zt>wf{3NEJrNW{)JGZX*>4tqOkbVGP=i)J*E-}kplFnU;=+@AVDRbz@O+%3>pjl)l2o#ik zvMHvS7$b9Ea!Ex1!Gyp_Q49fje_9%M%p>`A2b18H6Q12FS2oR0+|Xa@DwnUiexK>U z_?4b~{?jk4<>@uC%Rg5Zm?;vX8n(;jD{p+9n}Z}2VxVR&s-|V8t!lwB+)Ve?K@C(j zml8slFJ>W-)*>NB!szk}IcGDCO;|1$H*VdS&6fR8(nUs7Bvu6V+#@1P7y{)y3`5Bo zh9TvYOcV?(ZMN&Plhel+7iUDkrZEo&imHr2DA}ZXi4b8Y`Zg}CprfenetYcq>Sqz5 z>Ivf-=WF!1VqVHCd>xgsdT5L~9;0feXV_lZzr(Z3)I)a(3ie6>%;1~Vnmwe3O1nWa zz3qeojoCnJ(Mz0l0Dxj<20AV1dy*BtO47t^2Qw&U;I4ceO`1vmOP!L7du?q7G?@wj zZVDK{C_W2&B$wJ~BP?mVM40tIb!bmyn#sL(xdA;v%{YhJY_yJd z0M_NZqXOojaX;xpgl#FWU!L-XGQvH^VlN9$OG-8-p2auppjt^!xmd9t0Pc2i! zac+6@;GNsAenM{Dl+ZFY&Yly?Svs?9QawH*qM{`g5i5qkA*IfM5r`<7fR-#J?=~f7 z#1xyk1NfzsB-t?rMhta-OceKDw=!7Ly} z^7II7re$)Uc>KZ0hQu*{KW+AO;km@-=L88kI09)SsY+~MrIOW4!|WD3uw7!c7^ ztrR87M3{OYXxrHW!K}|iuVO})lS$Tru?ZLuqzN%_Y`AipESnNwU;;uw5CB9_5<^8) zQbGeT%k`62FX|+oQ0sWjRDH53Gnuf{272-Z?5{xMc69d$M%rJluH10{z+}o?KY8Cj z^=wn-2`Aw0LIZU{1W-a?FpBN`h1WkGmp2N;5E`HWj2!A`^^vHf+ky~@*or&axNSTc zJEYW=A~8nKX&|blh=?+W7(>(W;oTc*Xx|1jQ4HAUobpg4OUWu(axX)_-ELN^ z?a&R9GN@T8kh97#q%D^LPyhr-5zxqr84$2cq4)%QDyDS-wH--gY==u8I@}$9vL#_w z$uVYzM5Z-G&oo}1ZOW+GZvWt9ylXV!=?m`r6Q5SB0w$oN_h%o@c^1xm<*D_-bg9SN zlQOdI`@Kd{9I!D778(WS2?sG$&zFIOqQ#(+B@rsJ)5Ye6JGX}Iy8?1_<8aml0wfHC zfntjsRe{K?h>cdHa~GygV5R~EInTKK;LFe7*X1gwc$e7&hBgKlJxrc!eokT%4Spp03wt+ioBV00UE5v@w#s(>HJB zw$E_7J{$T0z=|OeQDkH@a|CXb@CVoB01#ABKwyPfJCrRJZ9AKlGL%7(0)UDNGKLUZ z4u!x}9O45)0~F9CdDCs%wux;U+h)^k=Ld%oYPUXNVc7b z3zGz#U6z@uL7{|bAY1ym+GV7*>XigqFr&2q{I z%Ox6=oU03I98P`Td)~*UX_}@nvtbxq3C3^hO%Y@CrWk$g<{Sc-OfWV7>k~)S=;t9K zG6g3fJ!2GJMqHkJ9~@n-lTCB|=~nZypvy~4=7IxbF@|x<7R|lnp$t799X7F%wlNvd zgc*s@6uPeKwX~5z8f_ZG5X`*8s4CHuUI)RmBkv1_j&u`x;xza_wqgiI$i#PFeBs8e zn+j|ekRxEgNHDqc%-q)l;33nJhCxLnlb+=U8wge2q(hH_O z6gg?q2ZgqZoGNhE!U?rE-_nX!F;;an4iMmGv6lHfb_vQt@TmoJ$&j1;pvQfKgOkJ_ja5QBQNADG*62K@K?4OQ_3lu;D zWGCdhLU%Q(_aAp<#u?|Jv2{knWoK#P7*jgxxm1I0Vr5@?!Knqt%oK(rNCs6dUq_df z0RS4CJ+Wo3mT8UIYi&Rz)c{5zgyq3p!fF}f(5*wuZ5y^{Cqaq1;m{O;QZfKWt~Ed; zfZYzs$&W+`j20laR?6nho5#1_{M1{Iw*7lw`0cO#*Q;;BW4@*BO*NUR5GI|W;2eZ9 zB`AXC0X-EnP*pKiso5nEkwXkkgzDo!t8uQ(6p=#{+oqjy6U`{La{xjJIT;Z^L;#Bs zSLct;PS4ISPR$B{peZ8FTb{*6OD|$yS@GZh(!-y4EfftQT2QOmz@j_sd`VU!^l&wG zu%Ce200^A50TKWuyGWZrIkXL6z^oqn1_WT5At?$r07hsiHRBy*)g)y*^he8s#nHjp zcrdW6Eiay0g)Mo*Z>9-QFf*-v_Cpr5NxyAOjZ;C zs0so)M{dle$1+xLZz$n7$?V3Lj;X+(x_7@!h4B`ycj{bwGCf6W=%jfS`3@%;x zwBzv13HH+GzV@NLXDEOo8sfptqr*G5iV4Rias$+;5l3obC`EkUb3H?cXUvQPA1vD# z8zKY{W{hzjLR8h969W2{NUTS0m82M)td-}>3A%pA3_s-b0o}Y7N zV{_$8O~uDMQB?uDZuea23|D@5T77T~(7C>|0)P-a+Xs((4nrVyVm{VnOtm5RUhUF- zuH4!#rvjLMoQA!^M7XpXuc2n+XW{C^ftnHGT09War5By{kmDe(sEm!y!#%a%oh7mN zHhT`H%kPI395TFw1L5A=*;wTdWy)N-1)^i#Mu-qR+wXdH9!p}4soTyM&3w^rw%f>u zM9~aIltT-EM3Plet&O3iq^dY>2kRFslo2I@K-zZOS-X7WrNdwNaou8%LtrMN<4FC`=MT>~inED&XEEWXl&Hw-i9BkCxFjGWg4k0#8oCR*1 zrZoUHM2s=CNyyY9waev#1LV>_K6#KfLrFO{Ax4Zb#)j0gmeg&!)OQA$0ufQcx-^6#v~AnYXQ6FU>Uh4G2RPrZ9)J5=uN>a!6-<(r?7&RUTElad7wyI|8auE8%wMrCx0F(Lw(6j9414@2q(Ey>JrY$n&0DcJyAm1GKB znwIu`j|q6zX@qI5MZ|)(pB4ZSk&(tkl1xm2_q+7It>DMqL*zwkRv-faUu)4|-n6T; zi|uwZYumV(d-CeGZKq^-zUugeQ))^7VZvf}V-4%#T08mPSe|M%RR;HhCZ;L#X@m6o zwCK0P(X8A5J)dZcz%#em-?2>JR1w0wJ-Tyyn+GyHh_faJ!@x*s=(n2<063An zX&M+?W4T-|m-8lqC-bV~6dDx}QKlBb@y-f`7LigCNM3E${puW2S~QVt+x4r{i}Q!4 zC)=(Q1ye#025k|TF=J(B_zH+v2-GoqFg62)Cw!4B&AT&n_MD#euW(6F>AOXECi`{& zaaVr0R1I)v?h`UAJ?#~~;~IKpwC*1d9Gqug=Qx+f^z99@l1uzflEYBvls!5MRRhuL zj(K#u065|{I;8wV7?_#3)2{05kcx=vh!vZSneBGdXRWT^>1xbgHC85%R(E zj})W{d#&E0Q9kvFPFy#AC;i}R9Pin~yC*)i_4jrx1gK&nMu<&pfFP@g0uriON@+og zx$8FT+soFhq@iz?#~jTZzv_KJ6;(}B-AZaLPJ;vo5 zZ{B<3vv;=qv*-7}^(+728(%+s{P3M_b$Wl({p!Q@7Gj6(MmQJ2%lpz&M5&c;1G^koxt>d7n1S5DAxy7@{+EW$3m;+JLEv zXvsjK;PPY}-+R=(_I!w92B5ylF+meD03--7+PnY(mB=tK6Pg+pWsZmQ_Ha3XAy~UO z#325M$V^w|(D%S1=lm`NW9m^gx_)5j-2}_oiefL!3TG zM9QqCWcL!bZKP;z+YoWeDd*05IXFCw2M0M&$%ES>CR$9g6y|7VDGkgt62=99n2@Vv zkb!}z5fQ4H$}}Nf5eeH{MC$P!(l(*X27tE1tA7_Z#P_jG%ML#M$TNx?GemrNbc7*z z>cJtUtXt6`Wf51vBLc)GMgSn9*=*)L)67<@6%xf5!626mp2Wof&|++`re6g@$|yIHdeS>76aYPan=g=3chOh7=QEn;QlT+^ z{zJF%lXvw~10te~srPmd9?Q6A1G6!6O0}x5B|3c8TRgEo^{-FAiv@1lo$D77dD8jy z%pf>TBv&uheFu&1*%DUkWM<%Wv#P%VsNkX`Ze0R^2^;ZP0RS*iMI$89LPQAcAtuua zxMz%vkM}LwSTU>8Y!fwT&!+Lm?)LKld((YAgIwNuQi`)`b1@hJknYPHAh>u4)Py-a z*>TgSU{Q%Eq^L?spTG5yPk-ho-uTF?H;)fN%A>Q>FMa*XU;g5+t%MtyFXVI~+sHUu z+{R+YEbSa-tt<|#72T|&7R|sE2}w&LBx+CrMJ)goHH}uN&_-_mubgdPzQ@5bnTQD?A{v0GnuoR-u>cVT4A|kpw;zcryn6TG_#k16*0hBK zBF|&POhk$lgro-x#hLx4Y`}<$xH8McJoHGF`keSos5GwVjrXAR)qvGfTGTR^HfCf^jWjOv1#!*kILVMy- zE@MJ6c3J$7xP0Z#2mnQ=!Q~;l2sLvB5}Y-OjMG_X!Auoo6X!TPB~F%m|4`! zjF}}!kpY1k3Lz3`EE$N9!5RzYVzYYZ+w0ZY&~MZVqO-)&04M~iq5B44Vjg=*D89Qe zLl6Wo1`FeRI4Co&ts^`$y`ZXkci!IyxH`u4MM!C%%nna59C*Cjr3clMJuyWf9ly2r z$Z6LkV57W+j!{NPm$Qmq;f&j!vl4b|_q4!Qg4Z~74ab$p2eEmOqJf!(823yWqU!$v z@U{FIvcIEC$jqE~GFmc@k&GM{bFUvNDgf#=PA*)d<=TkX=-5ux+8t4vOYJ+MGSk&w z0mMCv$60VTwL8GHp$7!J#_FyAolqI`q)~g>D|dwukkP@C>)u=gus~+013h>1xu5^3 zpZ&d`|Jj#b_{h8sl6x&lM1ST-e&Va2`{BRy-+$rp>4lPkbf~479~46lG0taUv4Ge} zw<03uU}9jv%&JASG{!-LiiX*oLUX=ewP9IWie(y`a z(Y^l-Sf63pTHcc7S)k_+$MoX_X8+03m~&lH4+ z07k@%gF|BKhqM_61Z0NUY~eB_ecug|)R77V5mohsCJbO|fC$Vur1;>xY|6m_HwX!l z6g|x-)$ipxNW|2%Lpb`)ubw^X7p15&zy3mVJDLzPMI%NIW+ipoVReBZ#%QQomA$K+ zF)30cRfQPTCk<7FLa95P#o2ORR)vNQr}d4aqxZi3jTi3T1&)$)PFZsn!JJC9LUJY` zAO_^3&}T_S%-E?hL@0Z_GF3H0RTC{*m4Azl%HPZ(28tnoNlrt~Rl<%#Koc=@uRhog z!Hh9Z)KnYY+cvUlDwIMLiB)a-6EUbl(Q>KlPSl*eBR+}aUe*O1`NfmxD1XN% z{e#SbOeYY+0czU$n&A2+gosSeSi*6MnwkgztQ1VxG>xkV8CV=s9G6nSEa$S_Zq3@* z5HlH|lwuYGhvjlfrX}S;)TAaAYaC6a9Z5GVR(nMgcufXvLGN4SFJN@Ay&5G5G@3FKJNPO!sEPqrE9*+%^P0I^0t|rn`(NdmDF4*wfBKL8+`szq z*FO;pmhz6w}1A<|N8I$-SXi5!v-i)m@Ny0*v^qiOv-vAeToqU zp_od}Y9LZf3L}HDaRc+4yzj*jbpiUr-9Hk2M0z>fc?q+?(8I_M5u0eyS$Mrs*sZb z2{izPAS#-Qh{ni0mx!5XLHjg=l+x*~=HA(Qb^89>$Isuhq1RkWmQuit7*}kQEMf>m zhAfH#h(Lij#K^H#Kr~ZD5f#;(#SB~s1`%TjxfCMerXfRAEv2BzMAGzC+_fuw#~(s) zcG5KNChWw_+yZdP85sA+?unR;m65=`Bj`;_{&cJT-QEahXb8cvcQW@S-RK+#|73gZ z+a;Ahq%dx2swd)lnJ!UPAPm4(lgKd=xw7ZDNk6L`(nniiS4`?L=JJbFr;p?Rk0A- zrfF5purzIZaBy&ZbOeZ55&$rVF~}~Zp)^fvDy0-ep2r1KXaa%cei+slC*ArKq=1#x z+35%O-yMc6Gja@OGA>|MPfy1ngZ;OdhVXds62QOT)Mn#?cezQY`&=0jPiwT=beCzo z*H5SicR1Hm?`BV_@Doon5Cjc@JcSnlB3Hj2RZlk{fmb|pY?_YN<`%I@#xW=yT7pnuyTs}_05TPcJ zBPK9piGSp0{?H%%$=`o#aa$f;*pOtqPTkfdSxI@w)F+YEd^UT%;rF}q&|kDoXxjOf zXHC-<#=h&yMhSV))GPxvQB8n=n<&LpgL2FiS4ug~4qtf%@7y_v+%D%(3Vd+-TmRs{ zdGz*I%Gq0Yv?h7b>rj#E)JQ7a`6f!I~#ib%zN#~tICX%A4*Oc8;Z4Fo`x78mL0{T2Vf zy<>#|P}#M`P#pL{3`%Uog#gf=d+zpE-hFGmK5H6!{-qabwla#)G#uN~_n_G%BPfHZ zs+a-*1#+-cL_Ib@0?wARTQ}~UKR&U(=e8{(XKB^G@}e}x=Y4Cdygm8I?Uz>%-aS4( zMk$($WF7iE45?-zFdy<5LIe(C6p;|4_%cJqGjZgsIhA23rDzBdv5psynhqg=nH5#l zl3lc>_;ExmIqN7N=SN`dmm@X?6IwvbE*XgV3h^cLu?K=hu*=Wb0pTAnIoOcYPfigKJY(&&2%xS+elbJ-0UW3Gs=6i3UPCPO8q5A=3E4j zn^q}i-8P8xHlhlckfDHLKqNo~RbxX2ECd>9fRa$cq8TJ*C>SZmxH)c)pL=om!Z$-2 zaC1)Wjc(m-PEVknm6TG-UDtcUBVW}*8=$6};H+3gOM#MepA&_`Arev=O5Y74Koq11 zQf11yo(d7ENGU~9Dk+;}Gi7E#5Y$N ziYQXWX?zvDGMD*iov@kurX&B?t%1qtaT?7O$cmnSO7s~FL_gVOJOF$r<#K=G-OLcd zb-mGzfSM}OtF(`@G)=SJZm-l_!3+tny%$bsztMiayX)~vR~$^X7nrK5DoQc9eeYmZ z2%$*L07}e(SyWQa&Lv_dMj|5BqNb|opS~m^m3iAl!VtoIK1Xs+R92Cc(tJMmR!s;T zq6HiS97gdQ#VgAj(Iqo(uLYi!(rEXlb)ov+JRLjA-9N{IhzAD;?eVR`GYXMon?H3r$Sc=iH~*#@qLvd;Q~lbgWp)`J?SyZ?7M`yL#tq@$B)9 zVV%;MN!NFqZo5rM5eF?Dsx<@xhF}foON;W_LH=5nH6Ni?b1blXC8(6q5J4p3s3}7PF4ni!BnNB8g^SL7E!e>fJL-6l5?n^WbVCl2Sw@OU_ah z6bT3!n2ktOi;ea-LQxEFco02Q*Fb+b(g7V-Cd{$jbmls^lE7dlCT*Owo!j zUlkp(-3(#|fX?PO0|PW0UD8vT7J%shqUJmh`M#>M%6&;d#6XbxEt;T>Y*Iv3F)Pf0 z$P_IgdOA7;1tf$D6!}P01XT-x2Pu6{G6@#bM^cNkY(4G81rUK#ib^k(h`}k`z&>$z2c;sX8^(_lo1hK1Ouz zRwlXTho~W?Qvd)=z|Rgx*r)y4^s~>`V}$rVy7L4A(}ynA(LYYl2F_0~aK__4W>B9( zTnxf|4$DJc9*58ZARzbMc1StpGG89ux^-*0JP0AAT!?tNJPIM0>1y3=`ymj_n$W~B zTP~6X(PElGW$3!|M~^PfPPXfl)%k<<#be1`6S!>}BDC?%5fRLTd?zvbUHe`^Wp=Gp z|1)J@Q~)0|SFu1^}RAIC8B-{cHWd zUAvFrcUuvp!MbZj5iCveeI5hbu90g#bUjHPt>{L%e~ zap>Q8{+7YEnI9QPq(E`e#<>);K#Cwmkwb22$eD50+`2Vu+Qq@a{MH?8X5HftEUSEJo=~EeooH{^7QZUt0P%|Pj+7YK$j_BJDH_+ZR0T5~0 z1`Lo1j42>Y9zZ}X00d1Fn`VZH0O}cYswI*|3;4`VHJa5o`eS46a+3S^PyuD8T+(yT z-TCZiKeJdiU;ORge)ntdY!g3Pm)kcF$drN*0nO%Nc3=aijPF10-+P=MU#t|cS@f#I z%Xjbnz{h}T+epf(7;*%#Jap@`R0afN22@1X9EgDfV+dvfrYHsCyg9rVj&CZ>z?vp5 zh%8G7fnj#%;Kguq|JM&fD%}ZS`}Oy~ee=zaA)w0zSD{cML+FRTWHBV5$gwq~7-MW_ zjRM6MkOUMW643y|kg^mYYCN$sB9>A-WY?Kx#Uvt7U=D#o(4588)CegAHiIJK5pe=6kGX~-)Sq^9_vHD4 zahh(LSV|JfT(j?a$Z0iMX0zGQ_rx6C0M@DI^?!&!gvf|GIeK=waCKOksi^_?eXp*w z0Kz~$zwWd1*a(QcH?x^13`0b6X(#X6W5D-hlkMgi9Bnm1Fy9^SV4GqJML0wbC?cYU z2*^lEUWZbO<4lB9a>yBPc%oiqqV@Z>CJ&bevQ#nKFBqSBDpT z-^$+1yw43V+goCHXCopKXElh;!L8fP(GfMX5N0C9#ikMI*6q8;$H$(GJ?G5a9v&W1 zm2=ch-wQwtfg&5okh-Bj>bo*@so$>8&(0q`Sf8Er+cPa4lPuddhQta(49svZ zW5AP5nX9k0dkwq%Vxz5QHY%O$Oc~#hkEdY`Ny90wWi<6JHL8vM!bL|exA1h=r>wra zIG6n|Qc+t#A|v83I*bUa<^vg^Iu8Z_k*uamA_D>>BQiuq&jCb$=%A_#2<}6$7OB9v zQwbr&{bssi$xIz|a(RxGwsDQW3Z1FyxZb-#Q+%HX=u!v1pHoB~y$#d%xY{b;r(%~r z-!6&@0o7*%cjX%z0O`)p?82H=)?)KlPbA^Mm2xqqL>%2X^-M zx7VkerKeI+D1ea|)N&b4HxC}Z|LD2fHxA~6jO}8v#V9XvstaqN%7! zAb`LCaYl0Q=HmD~Z6E{Bkd2UtnFxu3Du|RC;;Y)sLX5F#+iu&HA|k~psH5~50P>!Y zE0PnYRI7x~7!Hc$&|+XNDRr;D_R7EgZ~t4*J$EM%{M1kV-v99b@R!yfoIbdKPqxRT z=NJ<i8y{ zn$(XSi8N(05LG1NK!k>jp^4l!)W)2%maa4#I+!nB{6TGBN9+JXQ3g=t90RRU7dLrv z`*wf-L1Q{^hPbuu>FIoN{CK@ZRss`8N5rfcDq%nl>E0l{EYsB;dt0gfzY zBJv!!goFVBKuuAh6mjN7&SgjgSq4;QKvIo7ixB~dq3=t{nF&DEZ1BprZDU1K>YeWG z$X4JsG^ohgd&Qo7^L`nb06RkjF5}ow_G|2!F4Jx8yATNZ=U<8f$M)`vFCE^wV-&!G zje(YjhldA;N9}xx07V1&NG}Yp%CXWJ{krC|z`vbygYHM(gDo#7Ft60|v;9w2SRV}_H zyM!o>u*$eCGgmbO@RWxNsNiWlYdo`>RhgM7pL%gBf{^Vj^ z8j*_`p{p4ZG9WRBwuM>DWYjJtBQV+GMhXWKj+quFo*PR66JzPp*_rjryEoSFe!G#< z!o^X%`QA6b_3w+c5y(;;@lpjKMP#^?g4KLm>7!$dRxRI827O_y2Ju?hWc4g%$va$;}D3IJ3&c zQFSAxx_w9bi=oPF~%4}DK-oPpoV~en$lo1na^fK96}KF*cmpnVHldG2_ZC1%(%?9|$Lnf6=N46kB2 z-?znn`o2>QCsvS|0TMT}doRCouw^AOA|=j2^f zRRe&P(rq`n@AA;4ZkxC3i?h@7lSf0hkvz1Gw(}SW8Pu_W)fMmZ_!o)y^vKb zc5k(w&fNd^>7}aWx))!@{eYN>OKaohl$K+rq*fX8pU zpVg{t6oRK3bMN{Xilx^oV0-@pm;my4XIFV62K+rg{G;V!+dn*QT0DRJfpmFu|9&%M zn9nd!3QZ(7kpPr(*R4;xi_PJp(cB#!J)gxi#QE|#VT;GN+vPmY=CNHUkTSO|TTXIu zGMt^Q@88e&-)**!@cbRl7b2zP0Z9nOicpamgu%9i!YFD5p_pY*2na}o=BhZ{fZUnE z8!y~=`#suV01OBy>brAva}W_lB$uQnDWzc;a?YYk7;)!f+Z9!DYK`t(mjs5kb0T`e z22{y`8)j-}0W8jE?QehUYmd**aCY;(vy)Y##jFtuaq;3W|K|4p`%4f1_@DjvfBN_S zEAPE`^7sD6fARMFU%qv4^V2{6BQJjB6Ysu%wq4CWxW9h!Wzb?E>d{23h6F^Ugar)M zNYTI`kTJl#jdZ-=1<uk5bgUu^)=iH>Rik&w>ITqs!YSC@#UNe zRmFjEGb=e4SEs2g77M}MLv=RyNy>vSYE01hb;hYs5y`hZ%xr?7f{3{kBy3}=miK2` zM(5jB=$H_9>>0vw>a}U9b8D<=no>&N_i!yOl+S%T3BJ+FnKB&No|gv+J>El^6Cgz z1Ml}P^sYsAEw%E$5CsuUm6>kceeU+1yV$fDoxY~?ImZ}Xz*kjeBvlfTsHWWeUL=>Y z-E3s&HT9|Ax<_fdS*;CRJu-1_aed6eY zhLPB72I`W)qd_%3x0*0G;xVJic*T;yv6lV>0@szPM2^wK+!>6xi=W0zw~%VSp!>fJ zPyy?vyR3sU@1jXQXK#?kx40Fg+L7r5!EEb5RefApu{%3 zvAFrmk5>aVQh*UaD_;UphhZops!~eH{Z_Q<)^pXYGXMmt5~&D){$Y>+1F$D{B|rv1 zL*BVFJhYL5Vf&B%(Les!$6x>GM?doR+i(46|EK@!b~Vg7KD;RJK3L!Tcx#65+?QYc z)d&CD|LWiSp`ZM@x8FYfkN>0p-J^H^(dR$=LOP%QjsNWbc`nDWmV=lJZP)nQpNsedpJIKtaXdTJA3>W?S{yf0qaJf3! zNEwcr^>I9U_uYqY{N(R>`2JUQdk!WbrT~MKoJ!aCeac0YLfg*tfCCz(VdyuTei#6# zX&NF{vA*j?dhheD#N`N$Ljp*sszcwqRthnL8FLNBBvT}EjtA@r>F&QHH7z2On)YtP zr-D2ti6jcZ20(y7rm6_Y97j8;CZ8A8DnN{k2qw-_KtwVE^Qws8h;RZxDW&8Llapx5 zUg-mu$kF}~f>!aH-~r({c8_W|?5K3p95qR#`}|;NHMpsAvc}i}a#bX^Xu0Bo-WMyE zvHFZ^wp4g#-UgT^bOgrg5JW^Wiy?$SDR~??nkQHqwO#}hGso!ou}aCNCMqG!B4J8{ zYU%ndl7&D3q>3uwu?X93-!=^+Frzc9RRPRIij90DA_5Tu&^j8g?(E0eaS{d_X&7LW z8CNIyYL}|nXr9stbtztSitT5m8JR&_ew``7B+YP(A>H~EteUhWLw zOmr{+0id?IdH3#}mtKN)1{}eFDMU6#WMX1M!ePimlE9&D+Q8A&Oe_tl@4NMC)%RQL zHrB0GOTSsK*XMn=F4{A~EXG+I0d(+r)PfnQs~+Q(*w`m@pC|tOVUa3T8{_!uml{nO zx4-Ki(N{p*kt8nH37tqw1XQ=6;LEQu?M`!V!#MoFSK_+1)@&F|E)4CW;Rr;;<~&~_ zKsJnwsD$cMzJv_Uj6nrdz*0>aRHw@rd*(-TuR5tAXq9fLuViC|0RVXqE(%xD8cc@8 zh(?a`)!uaKalzh13M8N~@@LhX6sk*T%y>}g$KW%cK{n4Tf#`0C+Pb5mclCRt;T2eg z$8?_F{$yi2U%GHIfbeoYNxxz|LB=m*jtBz)BKx9?MgWXvKpB8d5rDwJEW619#sG+( z(A(I?%mxdJpLyXU_U_~L`w#n4!i_kjyjpKsz@%#Z5QPg2h{Q}qrAx3{U1cVkqGe$Dv47koYD_Pqx+O){B?ayx=9^&#K_kEkcac}vF-}=g1Z~mb_dHan|-T&%u z(y#)tl%yMmeew`lLBx_v&O_=lW(iE8i7~XQnA6a8y(+YA>w(iAd^Cxa5K<}0#pPUw zfno^6;I5o8en1PTq6iQnF{(*X(PD^hS?q>65s9fypmu%_;K|9?E5L;df0Qtf+~%)qDp`` z-fb6bY(Pi`MnpRek8xUN9y61m&+??EVCL8q@{UPUgUBv}96m!`cdtxaC{<5Tz;3%( zQ4GOAsu2kWSVDBoWl%#kXYgPMK~*HDlJk09VvI3HmtQagCMQTKx6sZSn2;WjoO8*U zDM%@5=DPC5Oiaa;#vSJ9))}YTu_ugT8x!C@!OZpi%KNqIP}FLejRv1HM(gJoUGlnH zP6W&^*=xh`_InkXtS;WgqF#oJt}P2!t+eqIfLT_x81B6E(w%!RLNgbRCSb;8D6`pY zHfz-k0fwAc>#bokpD&v@gQBS)w!^mDt~VFwso#{8hKq-#J0DWtb!*Wa0?*nSV3+{` z*v)IhU4^g2KTq`>m$nM#NCKFN2S5Q( z=fN15k<)?Q)-erL!Lr%|!3Vy0P=XJez4o*{ok0vE$}+W=jvvNP+DlGill4q>AlN=O z^BoG2Z-i*#`F>ba^q)?dzRbv zCAV4YT4E{)$T4UYM+smNuy72;1?Gom@Bh|s{nDr2xb^9mn|S)ArS^+(a{Gnja8F`+l+w9GL|{}xU<2Y7 zW8joK|AI(H0EDJiL=DYC+hWrsjU@pzN3(XJs=$VbDrmy7IpBk1k&Du#_Kx-MZrR$y z!?^nP{da!n{g1r)L+^g=8(BNZeadNSD~B0?SXI~SbxOsQR22gPxX-9;x4nos?H3W9 z*b5_ZgouV=$bCQYJcusYC0`-G{8NXjW-aJQpBT1C`UJ%nVh#hy`~=wR}UM$f+R zJKXJix%zuANDyOle0W5&#dG&w25tnXq=FC-A#l^gWef{I82X{#CZxq|c}QWl>H78g zMYmZ?UQ13xx9(OK+s$fo{s_vTR)}G-n1?_B{On%(&!$YncG6A*06;Ay0tWyCWT>VLqEGft*JY?OFjO`D zYu2x-g#?OVjH8gu7^>i@ADvoxId9UGC2{Iw`#sbdnmhN4!Cq1^0Kk3HknO^?jGbPn zIwc~gaP!oWM=aO9Fjz?^h&2!BWKEB#%yikM+D~WfRpRpb@F9EGWW3|vuQ0Jw$63!j zTDPVWZw5Sx3oKrz90gVWdm{?PhRQe(Sq(-I~XIS#;{lm>BoP*GjhWQ{v z*KaJ5X*=<+udF(VXh256&^s?H1;mKVfGo&>q)vdK3ZS7PfC(rD&$^DPh=Bmbv>;;B z@X!C;&;Bca_z&KD{_gz;Kkr@@jLSA0M_zVGEcR%o!h~cD z5O3gOz1G$IR?GcDEaGgq`$G7w-}ureKJ_P`f9=!X{K79n>5wSI;QkI@$+|vmHXAi} zaWFF*TFJS9rc?lIK3~jcb2mO5&T*Cu)ws~=h4IE05itujoxAxk6LIEiEu|Dhn|LRx zI-Nfb=ej$bl>K~3HZug|NJJrzBpWkJqj5Gu&AT6}fjd!L$4mCjm&qiW&KbvkUHGqC z03(^P7Ed%fU2c01nGBTO?zWb3f>sZm*fV8(9=KeGE4A89IJE|#d-6GfgY40YIRxOH zImKf-PaC<$E^O%fc#gf*Z8EunV$q!Ad_KLkh)PPt9AjK87G^4vhGB>?x_Rn6XQ#6` z6WaE8qcCpPuiI+h@yOnIQL@k!dRQ<>t_p8ed*mwzvtTmO89aBzI^#>YQV zuz_Z-z(Wy51!JbBZD+Ig5HYM*t8Ld4wnxi(7I}F8ez!UA*6TcMA#d`q-L5aT>kE_2 zXrT=uL}DP%$zk6?6z6Gw-5YdIa4PMOl;2PT45UX zTR;HKT#=1{*sU2Kv54c^J3V(Y$EOl(aybcf?Yn2myUhs`&`FT_FBh(RT!O-zJ&)sbb@f@281`PKHY9!f9)PqzH#erArdx4 zTpZnjV(FcCnh)Mt%xJTI*la(D<Wodq)Eal*MW#ZT>L$bf9b zKmjorMdJV=8U_GTL?!kCrJf`V2(0)ZBxXrDyuI4IWz-5aJ%!AQAPCKRm`5hG!9W3v zLP1i*ASP9(8QsO;8wEsS%ia2eZ>QNT-gu#HUL>BGv5U%@D$^XOC|a1^B_{134PhH! zoVTzHv-af|=D+;b`8$t>H$L(Cx4-tK;bKEmULHh56e%eUr4Vt5F%pKHODRPGn5c=w zN+Un}$k^-?$6!2ud6l(2qQ;f`>^|J}$A5?CelMns*J?JO9ULEBY}U=mQ36dPR}4UaufCVlx-YqH0zQ9R5|+BARpQ zbLJ49yL;nsadC9C?5#j-fePjA_8Y&G-+lYAf2ip($OfR#Wu2_A@+=5i6hu%p5Q14? zW&+hBxl8?aJM>@$EF)+P7&tfyLy9B=V4g3Q&1`XgdUk%Yy1yA%huC76*_$slUw;3c z0%YL^T6-zh{7JAeQZQm91`db;h>ZeJsK3@R=s_Rjp5CfsyzjS7;2-?V$4<{r-hS&_ zFTV8rzxVI|ySJWOq~cfn`Ki86se)2YIev?!@SJHLyXekA{^q~#nG#?7r*eOum91X{rtUGKKhkk ze;aMkoSDM76e))|j|~D7g_3m`hE4_!F|-XJf?7&Br7AFph)vVXW;0(Vy=RJusi;8V zT&LAft3%#^h}6{7kSTE0{mVIX3?W7j!YWdTc9EE4HBf8LNSw`L;F@I<5J<47=5FXZ zAp#DF217rTAZ^>uW-TEy)>~Eo5f~`$b`+PrKUU@HccNuec*~WtzrmfB(kGp;FYE?m zqkQMGi~jv*fQ2FLvXR&S2~1{4E~Rz4CTAaVU`9bVuJ7~k-VIZK8O009-P5E!um z`9mvgCnliTtc-StA{&4rnTu^JnCHMAX}lG1P^|;@X?Tb1*(>47N1A}0>A+;ljA6-& z=r$OQRFVja$Y@s7iW>x0H}dMEd^PLLOWzoVbFXp{%>}_2$9( zx*t@+EOI*+Fw2`;j4$5`4<05aY@{fla5NiL$xx9501=pwfdYmZume}d&=^ETR$##( zAs9IrgoY4^vGtSQ|F?hP7k>CtpY4bKFaM>#{MOrVKYH(-!}gV?4S-6B z%__@h+8nTlVR1h4-m!7L_PL^kVFK*h>aP2KZ2wPpdQpA!U%{U1S;ye1#EK7lnZVvE*D#0iDl;YS!n(D&q>`c{~at5SwTj z(gv}eFKP@h62%B4B^&nc<82!tHc%DoyQBj1c@vv3*=MB`2IfFuq@tx1ATqh#nh-##g0du9ZF0_Ilj)C1?%6m!2nVk)I7MOec&g7<-fQB~s>7A(>spChN_s zLqMWbw4Ke5UwHoT=AC(a9O6tgP-sX&JI(#j_1$W9w%)9{nI9b;Lm5`*tCPo%`t#GW zJ{|gP${nJ%ZH!HHnyN?lTXi9z>9hz=+x2P1y}rym?ceFD+E!|wxp~i&v`b!K(~+)Z z27q87m+^vW=y^1L;FhT|r?wy>(hQ=}GG$tES zBLxZsN+$k{LIfxXh0zeilUM^P64YD(MrJ_h@iSH|`Ku5C7$|~6heQOfsBk%-yP<1! zP3i^)d%e_!!X~urlBUatDMX&D2+feV?pH=LYR?q#@8|iV!S?=o-NW-wr{J}JufPi4 zN$@HVwO6LmH4Qo3={uvR3IlUL1z3RQBllk83?~oX*PMt6iSy87*#IzEHY;d?88Aa0 zq|cf}*I90S{Nw5Qg?;4Ji=(4;r%c>xEa#_<^h@kf1&~k$W#~5@2PQ))nF#`!ks+mF z*rH0xeYaVeCbLQdG|j3?T1-K5G4<-wP;|BFF4p~!m6~S0oKfIDrzBcb#5OP9x$(uv zse^ueaRX0$He zy_^5$zxA*Gz(-$^v|cWjpZn|&|JE0N=P&)m|NKWk|LH&fzy8-bHjY5N`SI6p+&J29 zdo>R?Ly_(ipZeq<{1bonAN;@mlcqg*^OeswjXgT;f9W?*A8v2{YyZxF@HhYJfAY2W zzxrdJxs^;@iLRz7ysgB_WPTluN(ecB)visToowz5u`& zD8M0xCRPI_rL^rjkH-cV9H<*53>-LM(?k&|&JZny6_}`K@ok)#5SYtMTEnaQl`0j88T+l_&GUTah}RVIQEtZ76Yv; z-g@qNYg^+v&u=c42dcE$^y~8r*>(^})mVvwVdAGheJ9%_*`dCsIH6vMU zIL0PM9^dzRh49jIpZqQ#=HKh(*?+xWflK9*tP2rV*n707iI@Wr0V6O26V@r2+^^(O z(UT^O3sKhiAsA`54(Ol&D3BrjxT-;NTaeV z)HmFn=J&j`Gx*(dn0+Mleha5ETz*i|3B-4(HopyUIf%Vs2)u z^_KF`a6nZx%{hZGQhWEKufP5SAA9`=K7M%n=DpWnyK(n$emFOpEj!TF{p|nVvdu$3bQ{oYq5!0-QVKDFi54kY3_}(hQhD6T zde9_=LbE)WA;(P`w%w3Qmdb_FTT8sTNU2*H92qizV2xt;?(OukfEW;&7zN69v&q>O z^IMDg!N^KLk7Z#NMgto*tJ51t%Rl`K|MQQ1^v(a{|NcMvSO3Hx`H|24(0cXA`AJ{- z@;49;B+q~CSHAc!{mk2+_|$76NmX9Bck9KM?%sd@gP~K71fYy2s(F|&@LK;D5=R4j$S?n@f_e%N+X#%Ur7NND?W zmfLtzN)Z)PH>NAK0(>KhSD}$9*jPlIxjsegyGhWhzzF4zQ|1?vZw{Ty_0! z6LEMlCZ_x2-sEt7M4Q?4%XBr21e7OEAh>tF|EF^E_sSG*JylZ2bmG%YhxJZGIHppn zC!n}Vhd!C_GK2$ws^pRpjfi86h}8F6&qpzv%?u#*eS^@15JJc~i-=h%MJ5BGL1yuLW^))yvu7UOK*G6RtrsF_omo?z8|`0^nyd^Uph;h#Q@yIrT1cagmKI-R^9SIfP!d5N=gKZCSamu2ncE*il8_mBh$EgExAHc(a~0^XfXhdbjSMpxb-r#s|alsUAf#|`#+ee z2W?GWTjw1Tkr}B~I|kkT1PWM2;h<@@a)xZZ=frdWZmpT?lh=23dA;hH7a}5{feBQ? zh=UV#>fHgS$RMupV!I9H2Y&!km!dhyY*&)wb!mv2Fb8z?N&DVc@`@wc$nWZl!Az2$hRf-H~BBe;` zr6kiKr>#n1?X6@J01Bj9B;}-{mL(Q#wxyhJhfSZi#kh&fn>YF_>r{r^E_M558r$A*Z=B&`tjHKBd;`?R>)E?Mgn9PQ8fU-7!aZoV_@O{^CRSz z+SYhUbW_3|2n|peVO~{L1lbO2jxja|Ashp?MFrv^Hpi(Sp1*r*cCh#-U;gGF`0-Ca z_wvUdeDzn!vZ{$lC(;i?N(lg&xu{7I45X^VFaQ7%4%vzz0C)sb8U_<>Vl9ZsfPt!R zo~jzCNR8SBgb+eQ0nF18C;(7R)==$?2^4`5n^6%2oC1kG9^3Y0B=$|8X~FY2WD$Wq zO6+71_=K_#!}tQfUx)pXHD!D>vvEWDgyHOW+Y#BuseZIKcKjGWp`tGnW?v=MN!9c+ zO~h}sA8q9J4Kt50IPFdg?D6St?hw*GR!^fOpwn_as}!IS$-a1unGM4bo5lcg&R`Z} z9G^m}JEPD$9x{7~SqOmwh^otym6Amw2PR^vZ>i3w5>J_=N~0hA$_#(yD?HKofXRyA zoxeNniRZ&;3+|4?6{hf$O_}ewc<(Cd$uyvTf;)F_Klk!SATBWBt;0n-J4CRH)5j0r zd++Sg2dmT5yxsyBk_{U{gaVdJ(wqX?d>)#}$OxcTwbRr_|IRZi<0m|E$@uVA$Vi!b zvLSf={?l#u;{=HaU{8VtU_?}qh(chZ08AJdL%={37{}Ot$70>*ihI$|O1nNyzEW1*uzmd1IShf(oP|~rY0{ER zvgH9JTPZpelLR7A#8j2kRLR7wXtCAMoUFTb${R6@fun}N+aayGA&ZF`0(q)%MM5xA z3A%mt`8!{IusRu#LI5mgcK6P$*feKn7bOv41SBvb;us*M?H~UWfAkOhfj|81Z@vG^ zzy9Uvtsj2y zc-AgLK(icp_S;|loz=y5z9axs7(VwyKls=G+TZ$X|Ha>Y?e)KK`}W+#*m5bISR2~- z@=GrO&kM9~f9rStFaPOZgw;R!V}J6c1;4$RZ8SuIC6lO$jU%8pC5|B;QFGX|Hy|ER zdq7QNyu^4{Y)NQV8Lyy-0hvidLjsl|7%Yen!g7XT*=NF%DCF1P_{eX5?Y$rW+^0YJ znIC)qE5D|t5OYe&R8z@;10so-s(LyIM^1@IL`wm~IHeX>v*_CPQ(7a@Dv4GDq*YD8 zwP%oU+&=--m~GHPpoDcncME8Lszn1D(*(NR;YQLl6+V%}i->jUB^0Z6RVl^Iit506 z(rj*{cWHmA{+;IbBKr#?de?PdXDYM+lUI{C0!ZjKy;}! zeCO*|xf{p@fkea&U}q~;)n+1B9&iqCV1`mAL1zFcMa-%{QA7}wiNk19=A2EG2$&(p zM%-~s6hc5?wE_leGKCcAPFdzVCm-0SgimGCm3i9tn$G`@_y0bXIu9cPGrjiOYoGhE zA00TH490w9TiBehAHMh2`)_^w^!<0sW(B3>LS~AjNERc9CNwh&^I0HcaH&&;Df2OW z*R0Kd)@95iG2t%mFP*Fz8h`e1edb`X!N45;|LpyDyd}wX9tfUuA|mseXUg05)wa4D z=mr{%h6f1(q7gueBt&_Rq!E{z9qn(W`OQirGcvof!<8dNN}^^YN|dOfM57@=2Y?{q z8m7?>?R|BZ>9Vihd)H)U#5rgGIFWhty{hVN7)q41@#|mpty}lz&5VqU_~JX?`Hllm zM<@TK*n6Q*38X&P$q|Zgjh3TLEpJCW|(8;>09yf0!lA_ z5?fzERiJE~O$5MJZwFLFlPY8077*lW*mgV?#a7VNx@hOaZXx%sM7pSo<%hNBWq@rv z#>GVxYx{uMo)5nDo45$Sb?@6drh2B_C#t42DtM^qh13O1aRwr_&^$7_!PU1Mx$~~K z+;GcnWjRPu>Z)0r&YF!2Y3=;{#QF0lPlvL+=9ZhsyANt2p44twh;UH9ap63A+~dly z=lac!llA&5OXI>pYGzf65wHPCs0mV)s##=-DM4Zqk;tkn#F43yFt5YKNp*a4y-GS- zDh8tvMXD-Io1~f|3m^fZq7XXQ-g9(XitNVfx*alkbv{o6ar&MQe9O0f@LQKgqo*E! z`e**_f2`~1&;b#U6A3!vAHIxm`&CJU}>})ER{yJ2=4sZi_bs%%H40hCGsXSA3C)6=+T=V zdHAUx{Rcn&2Y>(f-gIQY549ko^XliQ%jeF|?*H5;f9+>~(rq07=yzOy)4@%Ol|oQR zh!qO~p%1~MK?bwS(^-I7Xh3aSj6R9HEe9d=ZReIGZ0oL_qyms4um~$<7x2Yy)wl~_Q5HLiUTml}#8d35 z^0x1{Os_p)IT~nEoj`W5$}$c_Sh$#=W|P1kVX*ii*t&r>+NNV@G6!JDNYQ({ z73f-xq+Ow>5jXVNldhBg$@#fdhdg+ zP$~jyMm7Z?A$kx3248vvN-7`#f+VPbAOiWmz1C)OTSQ5>&wH0ngFUGVqMf{&;$e}_ z-)kz^$s(Xz04}9Hy4CGcNYur@6c7~#sH~}3-Zi@8El00Ca-BGT^30iYr#DW#a`Dw; zubesk!shyVK=26a*r5_(QTU=PoI}K1WLYG&x?X{Hd24kMuiq}kb}f%}r3>tuw2F{y zqkL)1Zp#akHY}3t7As>%N%na`<6F>DjJyNy$U6$2yr5S zs;Q<1ID#^bJJNpyzS20zx(^X2ZAez?s@yYpZ)ZgpMCZP3V{d#kwr)2rCob= z3e>mUcEg~g)uFrlE%&_Z-S=H}`1-&0*Z%hWr2f(`eC(B%j{W{W@Zsehg$RMlwbvdx zcyQ0DlT%NFEVz^G&m+=EN)LVIiM#GT>IlfGE8N@P`R2zSd-AcbKKr+x|M5HDbk)(j zuGzEiYEd|Q;i*$EZa((tBd1S2eET)>;qSiwh6B?;O@d(^S#^dA-j4yu6#)R6rg08^ zu><@n(@qWh$d{^3On^8b@`#Q_RS7Tv0y!m!Ko0!ampianqOc;m1PYiFeNiOs7`p2Z zA9(bsXWn+>k)wCN`|O!x0u7KjDh9>K4?HLZFDVKOFtCDJaY3*Z)GdrewXM)w6oqp> z1dr%Ml)MX$h{zF2Y?a-rF-A!V6+L(~X=J-Mcg|ABL|X-M9?`9sq|@P=9j>Z!lNejd zL2RF|DJJs{&Ib^2M1mxUL{2pSUd92SAZiix92Lhc52<~mM9^=N~((FN+0UFW{E0nDuwpkurrf&-gA@~(|lf8S!`8cO^`IDFwg;^<0z?4 zIsgD@r3(s1Dv8a%Z*+!jdxM~x@w$PsrL|BaZ=Ku!@(;;&ci>ztc>AHYt@OR`Tz+QG zHxUrPJ7w?jz`oUTD9%Nc`r^swpMK=|r=L7ES(|vMy!W9job%2XA()nlY_aO}L@cny zf<*Bfrq>@k*#*{P))|w)4loXYl)J!Rq+01P-mG?WWsk`i7yGjLl+RQy*JQD#}CKrjT6dm#? zMK|2L@15^?&sA4l>CE}{&CSiF;5Eg1Hm_Jl<$#9EvxwImebZ0^`N8bsW~wJq zx#CE}!OE`H;}1W|(@C*=XLLAlIEt~9d^9d zJZl<{Ayqt`&YW{Z&K{&#*LNMgd;h*YoYKKV2ma&_{)wA!x_!^yooCOS`SfQVn9Sl} zv@)4BzxIjWy5Z()zU@Qzi6+oA9uIE6a;bNM4!9IfYWB6r@Hf#NeQ$Fbp&T$AHKfght>73Im4@ z&=mOuN(c^`bjOivAAk9Ur(ZsG$I*LtKK%K~`ticC!!i_OB8ibj*ioqK7*lLwW6>gJ zR?NEbmY7%1r2qAP)rv=J{Ad?0Q|A&d9heO{g} zd$p6gS>DJeYJI&g{XqZZiO73wcg|bFDA<}GN^7KL-{r*WAe#~cKuhpsnJ|$OS>1E1 zWl>jE6KiwBv(7IH=Nzf3b1sBXg9=CUq#+_iU}jK_G1g5D04eHlG#I3$d4S}@sQ12Xv!@CEoWfM1FJ#+Tr(~mv!_*b8qPGDG$$K_I4 z7Cqa0LDa8nRQ}Frn(xk)?nqk=*8Z2-O2`kU@ig9h@4WXRxDY~7I_JCcC$@y+!b=xH znilLO1yzX|&7@jWVy#%`si`G3z)?~o&AM>IoJ3;c1^}6J9bBu1A}#%4Ew!-M5-k;a zwG!+Mwv-+K!R~MNkgILg7Q7TK)^4vc0kj+~v_e-fP|Vz!U%WXch~zr`1bEwDR$0h^~U;Svbg~&9%Op)0@sbC zgrXrO7>>hmWxjE)o^GyhtZ8gyzCNwy5w%zzZ@!8rpLk^FaCmXLK11;2c7ErWbFFtqn)T!P34^G!-zx;pw^6}%xLnr}26=H05?p*!whrh!I zWQj#l-1XKwKmWOh9((NRXP$n3V`EZ;(ho+S!T|8|UwrUg?|ZvPHo57W-gNYnAOAe$ zSmfdkk}FbdPM$pT!VAaV^0u3ja*WOL^7#M$KmOnY4}A6K|LsrTbM4Z5?m0>ToV0t=Sm+D&nmE|F}OmhR4gfsf?qDmB}J<0T0p(?!U>W(Ec|c}g&hK85ke{m zfUBRcxsS z)l~d~FDzZD4Nk z`K`;+HctGF9Pl;h3RHC}L3!l-j(}w|Idk^>qmRGz{9{jZ6?d$xjz?pUW*utGoc8)) zOWQWIzV3FZp887k3@%pmO#XEq(A^jV077?J?9z$Ypf_BdbI@|DU0E%#Rr= zCrqMd?nna2x}~c%*P@2@#%y+?8P(@fYnRG^k$pMo5P6Pe6j&?wg+JcvDa{zwvh-H^r z1aN>(1+W?3u>ZCXz4xQHTzi*Gexq7%=Jjl{vAK30IBJT`q%ub`O}rV;?7jN>i_@9T zCmW|u>gF^}H+a6;aKb_12IXcoot}J&R#)QYXcz`XQ956FcVYhlUsBjOKW*lds!5`l z0IMW1={$}uPSfd&)AMt!0KzB?yd$C}MxL{n%|%WiMKw0*a<@QL3CTI9o}G|aRjLo~ z8tmWcpMUo0AN$cC*?Z{PvnS4tP%v)5@@6} zZm0zB$Qzf4AmYIh6+VmsN6bz^h*Z@B6hsA50p$)PPvDRO04gU0L~tZ##Q=<5Au0qV z5bs0+*Eb(I@+)6@>G`wuwYR?Ixvze@+B`RDWLk+#C#qN$L!!{ssc9N=1TH`1gTbH} zj4fA%ob!%Im{W?GEXi}GeYj~F71k83tfO-jLQyymRw&!}fOZ)%<_5DJlr9!^Lk#Vn z%s$o!AF8>4sP`U;+8@_s^|o7r!P0*?B@0A#${Y83vV#tAbgxZ^hAlVTFL;#ux%;lk z)-JbfT)H@E+q&O<-4g@ID`ETFEzh50@!3!ZRa*U!2T*Pq6liOk6Os3UIWz^R^*#Uqr-UvbfOF1!mxOHr@2}z8R@^H= z8GKEjo^AaJZU20OXy9vUNa6=HTn@$V(THZV3&&5Ne(`vk>e686Xn6%tazUN{R44Sh zt=Fa7$7;7X*!q4J`d@aGcF@23S87H2l-2*_$dMxo!G{n+$gE5eLRnDoJpv}5Q-T%> zsQJ*d3ahecVs3;ZbE7Frsx>u9>L^o=H8-_#Es2pWZJ9a8X+V`!#8AFkO&bF)8Mx=L zuw*+V+6rZKn{HLx2qjS)rl#B#7GR>@=e?LYdY38Pj#P2Z;H~6(JspHZRaWITkxCT= zK+LaCloLk#E0U_RAe-M7fMkt05h!5R&o0=R?Sn8ALceVzGgr4JLZ69M001EHh`2a4 zbfBR(k>&_7fs;gq8@}zX_kHlbkF1n?c~cbXrm5zW$$YYrrW=rIiA^($sZJ_fPv;K6 zyYl?Trsw9&$rt&;8qQ|Oi4nALQ25R1q*^=gImN*|4ac5*l#8dXHK7a_0*b&K?vhnGgW0kKOQgr;Q#Fhuesss*=$mjmbu3J4(z(| z#%rH`@@zR+7D=KtVgPceI-Oj6>d9wsy7pZnQB+6c(cN#k^Vmxt_kIKls#*CYpbE0I zJUVpv5F!o2>VdnjyC@`X+7bu7Z6o%+`0)T=jf~rt9%QQ8Hg=)Z2 zw%$iVMVGjtW_Eb*uH}{U4?X|VjqkkS+UswA=yOkQR?X&ATxz@zh@mLF^S*#mR25JW z2ZO=V%Chr=A|>NIm{V+;lz3}uiA7J62n#cCQU%+@BO;5aNYgX`;JpvdcPPO*=N&l+ zoH%i^4T zhx~f+ti>jq$YNz;`@tBaL7A?SHnegkZHcIq>Z+=wt@4ei3WA!;`67S|9Y#V-If<`tf4Oc zQ~<%U9C~oGjcPh;&YiqCzZeS~7K0T466Kz}(HK0u%+VBJp*?9|baeYSwPEW!-+zRm z&Gp;zQCr|!)9m*9TaR`LzUgp`QKW=K4t(KUVNj-U-o!Evkw8>iMY~yy*n^qqnwmQSb#|cRU8Ybk{RcUpokS%Ac2K{F#-r@Vznjk zu+zK&0;qDQi^@uv?x8l~5y?^e0Yp%Aom&+6WC144Kxq-2&Qx%gTlc_tvM6z$87QgN zTW*S3RR#i7Rbdn$B=Y7X0jv@QfSfN-6OiRiDgY}mf?^i(2!dqvhOQsiztRS)otm@V zPItoY(!i4ffKx#Phv)&kSvp&v66W$)Mh7CgYzNkjA0blqLWlPpdH;RC`-a1Jr3$N5 zX-cVy&3u00>{)5*wKJ!u7uQOnLGVpgHS;P~bwSQUNR3YB>l^E9x_QCZH79O_g}@Dp z$!wOYIQ5NFbSV``%k-(YBSrH_QYZ6j^F1GU-v>VQtuG#X_Vb^5=qq1%?9{1K zY6ctVW3%+qasReEuJ!1h#73Gp+D+p_o^~PaW^liNe~%00V^Uv!9X_C z3SdIdepD1oq$NU0uIzN_npbd8U{-S?&@J*(B60*IGvp-IlIL5mS^do?U%qwi(9L(g z;|rht)i|H8(69~x3DE~|B)|;BDk2byVKE#bxu&j-QxTE6uA8Q2mZB(}BVi#GRAo+0 zlbG3|bKt!9gyhJBDofIBv`RZ(z4x9R0dQ(mLf<|VVx z5o=cUvP9bm#TZ#F(<)~~MoEk+mf3`A_I>>+xc@>mi|^DZFWZMHZ6(_~w_i{Y1aSSW zm#XV+Rr4jZ)>YtMr$|z!7hH--S(g6(f4oIxW%;{QsJ+`2M0YJCvR;i_FyU7-vYlCz|1b|JpFh=sJ ziHp9rpnW&d*!rIBoV(x70~7#R1&IwYNsIjv5x@y6Ad4`gl3y@IhzLO55u$KT^`tgx zV;ge>=tz;*w%-2wnjN1I9ic!zvddNvE_D)tGB&FO{7Y|?COxVjNeNdz3q z1glsV3iDVyR21JJxalS(753*gpjcVL(fG{y^G%Y;yo#zWpd+6YQ+5C&AOu>-oAR83 z*{2r(z~w?r&Jj6d(G>~EBY1}nNSy~l59B?%c;n&XuA7!W`*^HtI&|Ic@A@O(_okzF zjRq^9{oH4tdHUsF`o&KoszUSRllQ#q-EV19Ei8B6b=SWApIcv_l*1t*nY5e`hvo3) zW5-XPc=d)G4*;f=;e$-49NoRN8YB%yKyCnTJsE`W)p%9_-gl%0UTY3?Zds$AM;1_znBihEg`s?2_kxf>i_PCE?G|ciWm~g%Pm#o@}$0|H;Fkl!#V&!tPN~6e-27lP&YcAg1k@NvYjKTVO&}HKpj- zV{nKh5>@oFy>Z2EYOfIhunjwBfP#5*yRGMYd)h1An%|bD3Q7Ih3zLb5i<5c8G>;sH zB?ZsW^Yr+ysEG*Z7O7LGW<$grJaWz+E9V?JB04fw^|FT(Ei`n ze9iCk0?y{^N0;^NrtKEZ1$2qfOH9~n%Gp~A30rf;w55Y_QRK=Lvsts*A#g%8xFaN@ za%j=!qQW_z`9= zi0uhIvRJKyphal=n1Q*TMDfm33c~`5 zK!ZXZxM4hU&EDU7aP2+s{+93gqu)6=yo_~CuDSj8+n#*v*y_?wz&NkgA9>`lx8HX+ zqE|_~_w2sw?xVl{>t8H~1tJMVLlP>uu|9kJ@n>#0a!3S_zy%lp7S7#hXoO+sDvtUb|lHbz_$d((B+YAL!6&#;>;H z+d6r69S%JxbA^_7rVq}ruIxZpOAwbkzdZQbN?Tvn8L+TPJgG5Hg zTvtii`H`am zs&`aq&?eU6TqwuBSW*b+3MCQ+*5262O8aC{&iu4rVY1#fMKj2tq#-!BKZ;!&EB3l#fDSL6vAjU8Vm+0rM9dAscMQz zn2mIC&IK0=Uv%pVbCSf+O8~}PNu_0~om0um8_Uf^O?|CNH|s$U!F31#Tf#CUsY5=jWmK>NvAA+{$Ohn_+peVw8KCkOq zWKlDVnVE%DEmgC-9mdbAF6Q5MPH!3a_G$@{2t*gu6u?%C4uIa`vFO`Nr7V~2|81YS z!b+^&nSI@@e`Ez^pNg?39L;7kRw%qnE^p7!w!B**%at0w-p4u|f9Yv#`^?wZu(yVN zt=~dZzj>o%Xxj+41rBZ7nh`~&MMT7eR??~BEOR>R^O!jaSHelTQG+s(+gP(Ov#?@I zx_~+mNdO|bg13rQt`*dq@CFsIQ`IXh4Y-&wGP3?&ur~cUHk+>E0O~uqB6Pge~M3%=p-u9+n&tRGzLQ&Nmfc_uk^bco+&_*YoLQ3J88!AS6M{Qn4*^8!PM70|r@$ z^3V_F2%~^X4jeLpfQL#WQUS&A*!t+X$2Tud8!2~8rI}xNh5hokeBb`(Pdst`^>-4F z9jORhb=9G3uRZj_3&+R9T?AoceRAxjSN83DQ{AvCTz~!5H{N*dlTW@f4kI805$2?d zG4ZjNj!!1D1Is&D8t1~f^XDFU?5VGO>CtDOI5yvqL9ryf2`V5So$onv+F+!B;JEaD z*K^OEK7V1l>#(Z<)Hy(+awse)=r}d?Or=H=LeOGV43^Q2Sb~BFNwlb|W$d)U<)q3A zmyG#KM^W}O$V(a-)B*xwtY+7bmu@|@@|jbo&R=`$Tkm_{<1c>NN3wZ@h>Ef(1|tQW zOeQAO4IvmSJQxfHgMqDo7WA%ej zFyx$T)gz(`=6bg+uGEn|-QKQ?(04e7+Eg9N2BX*k9Tx5$qK*rZp41T$31|zHu?c~) z_doO|?n{BA$rvT$2FC2l3EMu_on-%n+dZOm@}MlKQ9wc>ry4JN$F&!6>z|ma5($8M z?^GA1whNl;~h-&rYuOA)-$hsU=HRD^0C8-!fN$rNpf=8941RRy4a0Je}7h@Kc0o93Q3rtlI zKruIOMVn^YijZB`^~adDfA+(H*$L(H*w=zK-N)1xwO(FWmVqJwdS-BHP~JIm;K-hm zjD0{f2bemNCQ}!=q7A8P1VlCc3918GfDBMpCR9XX2IojYGGff8`G}yxtN>uTU<|4V zz&1fFh%rs745+G>`eaGnmYV>CWWsDB@{+5;br+1eF9_8+DiK4QolFkMY3O&gDBD*w ztKkS#lVb#e#$A2jx?N#++L$*@T;Eu$t6AMt%8Uw-xTzbBkz-TOt5i#jG@n(5X!BHP zVvCgGbOMN=h%8>!T#-d2ZxyS0zFrl@%E6;=zV+xGyOx(WFPxCgWAXe`*v!`ExM%mM znj{2ApmmhuWTW>e!XQ}kR`-AU&`jLO?o3Z+`j`J92-a|3Q@BHw0?%T7E)E|H4{AWM= z`TOsG@XWa>h&u2N9dknvKvg&g9zmE;kx&o;l@Wj(t6yKQo_O-qbq8-@*>ngBAnX7F zh-#`3Jr={H9^HW4D&PpzJ{75jP_0uG<`4>W)K+{`M66M|Lxl8@|#8!RddpgC5blI1pjuvaLsuH#~gTv8UFXJ}LPZ8-rjBEZ(byzTBQ&|wQ)7HESe z(4+qebG%e!F%O8yVyE5tw&}u1>2h7t`6=5_w?A{i3L@&jW;e`0wurr`*Hmp3gN+$L z08tcm3c!je3Zf*c&{8i-_PB}~8>5_{g)-(W1?E8|B7!O&BLkc1B1sn7I8f>;R)8(W zs-j>^yIsIG)ZF$b_6c3g;K58myDQfg)?j}CUou@yMCCxO}jSh2*xwfnE< zD#a$%)qFCUY;JBgO@)#O&;!k;vwAv7^Qx+4;c`Ush}iD2!ui!9DL~c*<&Z?-|^8OddIhXH-(W> zP;_IU4}QlVc<4({PG5a)5a2g{^<#Iu`7R<<5Jb8A?l=AFFMlS*#)q;DgJ+(8?&8|U zj-4Zxq!QoymfL>MN51Xjzy7%<%8ng7zUy~?|F8VYudJ-DeCUH8diB(qU;WjOf8h&X z-dNxCVF{%`gU!k+qF`~*>}ljwv#UFyV#ctnq(YB=^@VSF*PF;k5F#p+5CoYp0fH~d zk3$#|4V)VRV9vY)o20uff<0?}BJ}cYK`MZvvh8Y(wO17d5F$oM4D&|(V0qWhz4t%& z;(M;UcFXY#pEk$3ZIKATBOH8ZM0S(Zc;iZB?Ah&;5UNJ`u^O_vUn$rvKr-%lqK zPSJsSZ&FnB-sK!!VKeu{EP7Ki%?*Hg$ab54G-UxIkyBLR1sBq_?ysFJrh6f(EcPgq zZcXIAw9~Fgnr1HCKwDn+7IL4hL8$)n)2%dCAc?Xe-Xa`hh{E2_vuBr9-J@!{vrw=& zdcIcM$^vFNPE4XI)K-`D-dn{9W;V=`FTWKMX`A+it=n%|4mhT!s=9f#X&MGqmEmw` zr3spbVyv;vCNq2^*}YKEutmoS0Rn*cUL85_d{L-4*Ca&Dnf7X_B3qEbw)>DN$xCjF zUP%hGh{m=rzKLWA!0SREZ1<$E2cg;**neZOyX`g!m*HK|uI@_jZ@XaKI*q(;ci@)K zESO8Dx+r(r;g~={dvRVvGxikQC(Y$9!4g@u{5 z<#BTuGzqY%&y)q;}*G^SM_XK&1Y3r*#JtMAYo^GlsB$oy_CZNv2L1q zg)C!V?Ax{H#;XrsclfI1;f~4L=E;{&>~wzUTvhDygZ;bj{`Sd3x>Wxx}X`i5PFgb7_NS3djI&2#CdAN=$G(^WTL z|JhGG^od{jrR%TV_xAhVF<9CA>~pWmbiPFMH{W{QgU>y6{NZyXvZ| z_P+GOX&*}G!@08;UwrYz-8*jqjDSL({@@?}J@0(?ee-5^?X^c9edNgtXU^BN^pAh! zpS}3va~tau02&TgQZ#S?l;dqdGvZ*}JC=nwvzs zOG6O>Ra^W*2;Dm&grX=iry_zDk2HK{uQ`|6w#OQKO6UH3nxRZ5{zX(o>8dYnrB%9V z9)ch8d%I}eHhw)3wm=J_Lzx$w3b&^RRm<= z?u)hwR#j%6&*w!^bm!cmPb0Ss=-5}vvW!(VpU=nRF%iYs009xj7+Z)*L?yNP@I+LW zWlG5^Y3ET7s47Sv2`zPhI2@uQ8{B4el$B-LrTBIie)++?r4s=9H@&+NR|KQ~Ww!2e zE(2x$bF}Um=?{KtwPjsww}a;0sP0IPZ3na|0u)pvS;blr(M+dw5haZgQj-k6TmofO zRm~DkTVGsV)C9f!g=FWgZ+>&_mh3f-?)xF+e#`S0QLdWU8A!JMmWOz+ES$ZqYUT&d z>YJ(9N3%T+Vo*_Z;E1dwQ+GcQl}JUYh0_`6pxL7X05||8Tab}dlPDl~?6@eU)&_=b zQbnXL<640bJfU0MdIeD=&0c5rCm{fKu$SBPTXb3sVL7A$m?|d5x;9s70%^SSh%k1x^4h*c`)3+W7mPz-Mf~S9j9}zUWlu>bGW*5 zc}0*?Ofd=*t*^!LaIY>OT0i~psB}?9NL9pv0-+)WZ{4IoilBf(D4pGckwd`fP?Hn& zh+*u?eZTqW+L}M|C;rUe+ba%<`x?^?k z?u$=8cmB!89zAmFEhz~AFD(z>{*Jp|eBqZ6Km}98uRi?rTi$XfLJiP32O{x?BUcRu zqpv>t)KC4(f9=UPv-H51A9YR%gDNFPa~wmH?TKpM2Y?YVfZ%{2M<7wfLWKkb;KGH? z`YVr|{H`0`PCWLakc`LO^33-ieraPl_lf{~xb4~#`v7*=liA03nVcxQLx|Z$?Rn=1#1X>)VEXrVsMK%v2nRcqX z*x)@w&I7k~T6!5knQ`uc8zLf5TTQzKep|=(ITh$FjoqQ_S9-ZxSsL5{f<-Xvaw3)f zs^3lS9kS`lPudC~y%l=frW`Hss?jrL7n__qgQ-f;cP{P8!2NV+~$*%A`#kD2}vE9n z5(`U;$jR(3Etd&c%t{L^mbP_Mp^Di)<#L+}0DIuJlu^6%@qdGD+kBFT?j@kD|8)4* zK4KT50?4K$IS>1_PODR#(SnA7d>lleTm{0akWiNP7$c(C3ViIjk{o?P029}MwpFR^ zyY^=RN!=n*}lC-m3_d%JJ}Z+nKdN2Ox!L3P01OR1Hwq&{=@%*MvX z=H}*XJ{OLf8cv+5nq%S^qb92;YSRvYS3m)bDa9C@CR!T5CDsm>mImYTN;zB}E~&!$ z#f@>{mzQ_!*uQ_zRfks(9w^6y`NqXr(*)AVOV5O10qz86d@$H)EZG9}Wjg zpZ)9?-~Nub-tnd*EE7Zla>DTGPk#9)|K(3zyf7_`F(@f|R1hIm6EFb@695|G6M>}8 zk@rDV=JPp69+pECP)9%nfPkbR41_p(@X=%6^5OR^jfW8f5~&galQN1rRi^?9&1=pv z{k{8ZKW}6bGol*Y*^8SOCXM6S4TaR`9WRS)QMAzgyE z_AV_14=mg&JX|2O9ZJWAAcAwkY|3!hy3BNxgK^HDwo?DisH81WqwcSs2c!*_TMdJx0Yxfft+B3{S z?Tx^mACGa_-j~cgo6RIEe{)YFktrtuz+}HgGx9m!H@M~R!{wrvUKL1a6;i_u6 zhkXb|TP5usbF+kVUd)W6ARm)BEp=Q zmDv_(ZJM~bxzQvZltu7!w+DBNNdHGZm zr?KALvv&`s=KKrK;>t=f3PigTjg&7ENJ0>@DmH>9gmef>S&J%4XyHF2wV?4VpdPRx zJu~Oudi=use&1g>bluI$^{XdenoT#}{N~j?{!Affpuj^bn@YiP<*TGJx^C~zXC8a* z{Q0#52lt7@l;rBG4<9{x^h;lO)Q12nvEhII6F>RUkAC>bk?WY#E3cgV@|PZd;QmLM zwe%xR0KlHSh@gUK0s!pnrv*6?!9e^ZNdm#TnI5`k_jQMFdEkqWAvj6m0)Vi9B4`1m z>`%RN?$n9(TX*ghVKI#+8q88vQBboy>Y%IU5>yKrydq^=B!kk9m^?`el&60}5n&;( zzO19nE11?eX$Ccxb)pzCkZ;tT-E1x}T|m!3f`yO_Ms8Mx-+1=ad#^om%dKyD__N2j zB!W;n@L66@b)8}pQ(IBi7Wb5r+1Oi%fe^j(Mii*3C&*?{&KV2Q?VmW;rxXN0L<@gd zUHZfc0SO_Am}9Tz*ov|#sfrTJPSGPtQfU((968Kdm^QAwg}6eiLfS>{2~ifi$&MVb zQ_L}c=N85=V4qz$K^QmF@;&)T_Ns#jwn6QPAH<%wC!&dm7OR5IDDCza5f}Rvv$aJ9 zN5W!zT_1c%FrUvmyk!Q1-3fGCb4#0djzkw(vfO6Q+Z{g0`Dev?Ku%FAlKFs zKYcEz|6psCH*~DG=$#b~om`_xn!} zGAak7azID}Ulir?XlaR4ax1uFW!KWmZgS%b=hiQty;!&qMwp6bK6TZ``r6uBh^gFc z23iBS;2|Uu6myex3Jy@sLYhDk6ckZO7v3SN(D+0`j){YN=JXT}9C_>e-l?KPDmOP~ zbCBW44ZR?^nnKXpNfIUR12P}lQLWI)r=NQK(BVTS$Uu~D`M^7$eDblT-YCm)2;=ES z^;7@irx8Qlr1`W~1&@IMIJG6;TRU%51rSt3vG50z3n_6GH^!so*=#ba);{pNe)rm0 zj>4o8f(K;~1r#p?H3MQbnLYBr<42F)q!K}uz=72=MPvj;Boq{YRv#y#-IiTVgkXb3 z6a`eJs_P12W#AEn2qF-Oh*H90eX3_Ssw$E2ux6LY%@fa?IF}|ysI-`hPJo#cIM9Mh z;u+!#6a9^s*T3(cZ+rgfFR$_XKtf5xk~nddY7rKem?9!_FNesMPm5}2*AXkS0$`zS ztNj^@qAW`^@0qQEm`-StqaJ2ef|!Jr#XCpNX{X)la}~~kia=t`RU6EbOA%5^eO_Hv z5dfVd0!ijoqd?i(xSbU8!~?ysk(qpTm7=q+3xH*B8SAPFB1sj@iK>M%-VSW($7ETL z*z+|;T>Ne?q}5>&Yl|w*j)ZgC4-krIwg#BC{OjU|Ui$uJ|JuoF+tHwHBCdUSdj+KJ4cpp*mw=dU z80$){y{Ih*eXSqF{9B#Ewk7nsJ?)loM*r8_*V3NwVnnxx!Y$(0{4HmKNNfqhZcI|= z0svWD)yb+BKi9Dbi~E8GVnp&P)d!mPEUE&>oOB#nQq$z^jU~ypP)1Zy*mgM?&Gb0D z*XYLAXc=)MVy|xHwn<5)4=Ui2JNP=s7g+?;yMD93`tDPgUP*@v-^kB&5oIC*G{L`D zt4>Z1fUPXC&Bu8)%b}vSa6!I6x@1K5!LqGGi|Z%wfD6wY025PqMDH*V1puo&U_}He z|7gBC0LU413f&)Dp|t`kDhd!z>e-XeKQ(Y}KCkLJrD!2kt0S64;KCxw=8Luuc6+RTs5zxSwtE;?kV9pz+XK##1drzVk{IM!9XQ>%VO%K5=_L?SRkQ6R=*iaVY^IlJX8 z@7uj=H>U9Qq{!G(c2FH zzy9YRdhDyu{?@0y%*iwB=Ei1Z*YoBgS{&QLQ3Q#7n_rMy{o0p$6{1n%$T=#)|RHd zFhnOw$y812d1Ftl$w6$L?*A6H^LVh1!y7kT<4w$MV7Z8lVoTU{8H$f9)YcDmFv>+8k?g{y1c0iVizZ{yIts^r#ommF z!?ILW^K_<5=6V%HPu}|u>vW2lvMB4e&Yht^0FyL`9o(DO2#E7Ogy317a|A+;KrewH zm$Yr6y8qVN0{*@>i}ba(%W84H@wOfD000=hN6wL|a!MBcsOs2Nb`xonu&s!JidEf` z1(>{a|EE?4dy~z`@yW<|HSi4XU>fWE0rpOQcB5@D+1nl{k5lFIkSG@e2PI> z2*6PR(TU_+sv=~TL#hfw4pqb<#@IA*!a5%gmc6Hq$(g_WSO41aSB^jP%#(le2mj<} zKJ%%)dk($y^2@V$ao6tg+S(eyGEzZpG{LmhCyt+a`l%Q0z3&d;E!GrhJ91EfE$IN= zXEJ*a04_9I&h`(e4sd@f-f zr2?tcgAah{1CgZQy%c&e`eU>B`E#4^fAhPJJ^Y1wcGeYM1Wj1FNJT;PZKKYv9zeI2 z3jhERChshSL2U@5;~tR60d|s?ZcViwYnlcDy>qJCO>-T4AtGqiXL~Ge%VsU~#r@4t z_MvI}wjYJ*zSTWx-Bt8pWtP@zuRH?uSf$SArt6k%Q&{&~UE4O%8+yVv(JB9{3G^1) z&n?%>yfCeSHYplfVU0mdqm+uGD2hUrt9l^@vLC9dD))(M(=?^`9m*jjY`un=xvp#P zivoRr2IhqmF?sLF(l8u(Z$|ZtsDz!82Pukzs)mT1^T5%vJu_12*ygRRzpd|9waa|& z%3u6i*FRlM*Wb(q{u^(XUWmQZ>Cm^mey zssLGd-e+;z=MK>2UzA+tQ&SUDO3snk-kJqjNU{P$MOeO`o7e$^4y$D)9JT?TUJ7K# zkEp8fqBk-f{F*=8jj=ovwvR^ljPyV3w|%pNHGR^lYedw~V1i!Y%cORyDuRGH865x# zb706aPe4iYe*$LZ0F-mMasj3Vlxbhsi0K{x*L$6*zWzl3AYjHZpdo(%bN%ra?m*A% zGMoNU>+GcqK32hGI>?NQUi~OE7w1WlsAvg2R^Zf{K&PZllDdg?Rn6wJq9_mS+q--B zp246*Byv;+SCaGON*^R8WE2r{Zs*RO7iV=ft17KxQ$w;^T1uPB7r79iV$q>32l^Ybpq`@i*`J^Ob)`PlJ?A9{RcW%-UfkN)B> z{=E|?UioAH{U1Dh)&9-RX-x6Y|D`|kwzt3O(MO*6TYux9RW%o$JvsnYQAAf&>C0bw z)kIc%|I%GlOygD>Q2@UL#HZ&q9@MR5Y;R4&f>;(Q}2ZA6%>yC_g8Hi&cZ z8!S~!;sP|V-oW`3HdSZLpt@r?9y%x}dQF4ihRa2&>y79ic=gN&@4ex+qxXL93m+Re zM!{~oZ|YI(1%vDX%scpYXKx=4Ap~FgcFKq`HgzpA0f3284SMvix0^d`_4UCcY?<|3 zo8KfN04gjF1Yird(E^=z9@x@UMe;|9pf~w;b6GdTbT7J9)4rpq5W-+EBnQ^L272s8 zcJawhJW>>ew4p<;o9l(QxbJ{7r8jbNsC>qu6#^_w$I^)|UXJXS7GHg?7sf zFNzFk&LXqD7j}Mjst$p2Igm~#g;t{0V1x855fM6x%$BwgY+mk)6xE|TM3-%26abux zQ_cR5*-HjHScb4DQUzIXV`xUzEKlr51+`o%WmHZ#ro;kjKf!>8VD1`K8S}-|>Ub$H&IleEYY5 zL{oYCHL;R|29|0}0oK8I{*$f-G`2r}o`4FE`FQ3f>94pC7=Vw&yP?f?2;|4X5S z!`B=b4-ftD5C8Zt{M>)oT(3U*(eHib_)CvI`bbq*YisL&^auXXa1?&{hyTvs`kO!e ziC_ED{hxUlaxSY^Q#@;U{L!b*pFO|tz>4Z3Oi$TQ2p9lJ6fHrU5E1|=QdC^uoX;3) zH@G;HL<8|<5_HN{oC~vaUY(mHzF1Wa`U-I_JZ0U8I*Cd?q=e_^X)T3{h=~YvV_ref z5$ftF3<3@V*5j~#kzd}JAK&0N-~FyHJbnLsV{>TW-;n}x_S@BN<+cLXor}NyQ@5%S zky&4vlU|H5Albj#^O~bbM$!O4k5_2DXi-589#LDXhzLc1IjV?~sseRhb0QLx0;?*K zvonIMmoQ#d9fB!p%pMmBvD>$`%8C}*EW#^Y*a)m@<2I40RsC(tO!qf}`Cq-quFfcA z*3Y#$T-{pHhhLIYisE9cwwO&6i9nDDr2`5rIItbCQS`RnSXxtAmR1s^1D)8b0iI7) zHO7doR49ZXM9kbYHRQDIG@H#sNJSC65BWu+nwy&J%<8NTC{yrt_+b zi72}QE@teWbo86EW+CKxJGVEwO63J@;c|M#Kfl4&9YovTeFNXP^xMB(Evp;0!<|k$ zyx4V%NM3L|ErcqlndN8wFCir5E=b1Av2LuO9E-X9Dln_4wuI0(Zz%kyZCAK6-lN&L zD}wC>7lu?V4pdSiw8Rg^ws=a*j5*9qSe8UFHmbQy1Cb+Qzz!La3RrSFqM;eJA}V%E z2%U)Jgn9%K%VN%7&KI%}?4kYQmW{Zg`QJMvLLgw3LdgMSUdBlv@T8;}lPWpxk6}co zK!Sph^`Zn+NC^OG$aaB*GuD!Sb-eHo&!rIiSn$@!yYsB6A_Z&QQaR2`OqtV!f zrNTQ0IuDyF2~#S_A%G+nbP6#w>zivek4D9)m?!2<*_=(A`5Z+(xzZsjfq*nPR}oMQ zRV?aRrNPl=t)rd0og3kF129PmLJlIJvQt1v2ofC%iJ4oA2to)>g*Jx+(h^QzSoi3h zBu7+>9KPwMKk+~O#h?2>{_el|%nR4-9~H&m*jlshcfR)z{r5{NH=X^3Pu_a%;g_C! z?iYUgV^09@KoGw@@yNOJ=YjVXx@6!0%{xc{L_7d$1Wl^Q>dABB%~@RwhhiAKA5?Yx zyMN^`*Af1QANq4|d&fG z&z}0g2j6}F=Rf`67aol3)c~5HbHbIJ`=G!{Jrw6ooqOV|Pwd@ypNR#wRY!&sW8?j;eD2hXN#85J{tveRQQHrQqfeHewZlD&>TzJ~V;WV%nTn zLS+rZIXe&_j730nsaBfQI`+I$(uhFE#mQ;2)`TyesPDS{rfcuG?a9x5-X}n5lHy#v ziu$(xyvZ+Y2{hQzJ#L0$RJE!qiH!x7Y*}^Qb@sB-78NSW0v*|s$gKxV#yW%|A_z%^ z1OO#CB63KqDG3Op^TC77phU!;z*%u~Q|zY{RRPd}qs+oH0HAY)?Ey{OZ&pg?nxSeH zOpVg0JAy}5xrHX$cMV1aMDm`T^SvBYwRzd}F;=6>mhC*pSjIlsVe|H-7s;}}kaN(+ zQ-}x@*y_q4fF}@;wrn`HKivf=JATN}#(X~ao(6-WD2gnxG%xvnyootBO$KdXc?pg^pbC zCfpVgg0^r;D-n1-ADP!_rmer?zXFiFQE+p~k!==H%Yo_^`)xFaVPfb7fLcy&u|R7} zQ9#b;Sje;bjGZ-d(2|tIBB-Rg2<7y*_x- zv2~;duqw)4vj2=f?d>5G~0`H?86(UVcLtuwsLhQL_5Cd4PLlHFQR0j zm~8HHDM8gO?!EU_&GET&+yU>p`{=vPip^PHgUe6*8`^Wow*6UqlL#!8X#q1WK~1$| zbx_;Xr0%@2_ur-QqfDwT-c;FY&I^_bkO%rD*JJ*`0v+#-TaSO~JKElT6$KHc;Fnfc zHqNaJYZQ(gl~YPGpT(w*yY}uqc-?h-_w89-S+&Fmk>C(DHNd6AqHwOB&8zuLViI8h zZJN5StJ!2S9F2VN|dag>xPNPTQN{V7N60jn8m%O;( zDFjIkN;0JqI1;rP8Ju&BjG7R2Wl&tZr+oIYugt#lLxIX*j>S#9=`HX2(?|a4BM$QbPI6oMe;iWPu> zLcBgXx4J9b`>uOL_J5j!_c<5js>?P#HqOT7m>%FE|Kc_pmrLDxa;YhtHon$t&-C(|cdI|KV9& zFDwIGv!R(uBCMR`w#Y~y%2*7@ob>Di&N*l`C7p6&FdPg9166IB#&8S(^a%!=fI(k&T0%&*3+@o2BM;KrVB{sG%>!Rd3kwzHVg_$Fk+N+=*pGvBrwVQ4 z3%2c-4c_%E{w}MbfvsQB{`8I6bD?GLmB@L*euS(0qWy%K%5-SbIX4^*?Jh8LjL8RA zID%HwV_)gqbVPHdxt#QbFbIAy7%aeT7?jLpHCg!nM4=K9bN@ zw!*@~LT$;K3}wFIOZc7KJX-z8F37yS_bf1!TmN58H@Cfk+4Y*$E~pS#Ii{?sQBus+ zPIA=7j^EaG)fUlcw`G@XyFKwE4Z z^JRgGD5D61>S%dsHkrp%D<~(9O_LHVt*jos=K9_H_ATvP=>h?coH98?Q51)p7uPES z;ndW%q?DQn!p2(1l;XyQ_r7o*6nt4oOb$UrlbETVAb=MHnWG9w9F{{CId|?vxm=ES zFAtXnObjB`=0pLNHOJ6MK}4)bq4VG@X}K7Ae%q1VKmDm+d-|D=+;QuhlPCcM5NW34 z@%TL-`tJKa@L|Pr5QgVp-S`*(p(G!`8@9Fh)6$KKtzV|adJb|THbaPrI# zUVrzS-}3H<9{gOPb!wVGqyhzMD2i^Sgw~P{Th*sMqaEUr)=Ac$SWZb=oouV#7??U0 z0IalV`+T<(WxKTsA*i&LYVB;hRkv%aFxmmJtzOn00k_sJ$PZ0cq-74gx0|$#O5FN{ zZG{?JXMAn*o1ndlZl>zKCA~D9OXsU@y5Hu~+x@F5n0=0_W?T36Lb`siRBC9G%ZR9# zuVlB27CY?r$RYw3QEH*2^&u0Q@r>aZqth%1*fcQ&7eY>5#X=m!Pm`b`Cs^8jf%r1f2)u7UBSEn|=5OnoOHNzCN zBEZ^8YZV9tC7XV#F^e5X)3JzM)5X{#v}%>@zqFtCnw|Y@6S)Yks{*MKZetf+*VSN| zx*2A59FCe$RTV{mg&op(d3m#uR53UYPH#SPD8{63dQ8L=k0Hjviq& z@SBrOL||z+MbjmqVos0~i7__LfdVAf#H|Bk5>!D^h4qc;QqhPXvf~;cnPngg5=C<3 z;R?hV6;sQ*GjRxEen=pvcL`E~;DgTIbj|K34$uG3fAY8X{@K5F)pfU_mddG=seq;i zN5udWKmNcY|MGwT-_N}Gx!?Eg*S_WE%2RX45&hyL=g1#Mb?u5NQHBW$b{Pfz=!?fTfGSY z2&pW~vBE}GA>wE-lq3nG1B`$Tr?fQ?@Y;(?zqh}p<>wn;tJ*bJx0$JZbFo){jCI4q z{wgB<;LbK=+zq_$^X;L`TIPz;mOj?UtpcY0Ni9CGHIZI>ZoiN-p;#3Qz8{-J|t;^6PHeCNW9x7I<(3Wc5b?V4;T1 zPzD!ZvO7vQb7?MJ*1{MwY{Cu$S#DCsB0X|N+bkpj)m-pMIg=%1HZzcG%J;_<005A4 zhG!7aN;E^8uLIB_r>#+{*J@o5stajd_AA={DGZ>h#c(iQ;-{PXFHb zANl9MeEf(1%AftnANtofNKXWE>eelr1i<{MKR(U_LBn}hAIUGFn&|}~E-S1ynw)`Fx)P~cfsz!d< zIl3^3DaP>$B4lX0y1}mf8?<<$A{i| z=dId(UDviR?=q`x+ub?Y&*yU+B&aehi@|W{+NeLdh2U2$t*L43a=iD0a^PAdlv1*4 zLEH9TExW1RvfV{-MHIF|0d0L{x?SMn@iZMt`w&snyr^Gk9m1}t zQrmc&(BTTaeWz>LcI0i7FSezvu?=idn~G&|Gz%6R6Bc8f&1SuL9{{LA5|!a_Xz(J& zXbEWA7PBits8tQ?R4LYVLIfl-SD3B$0u6=QgXNqn%Qp4B)ov3x=e%3ge?THCi(<)# zrK+A*H7CU}&gb(6YEP~}tzvAN=Iao7{5RD)z>VgMx-|vw8*Hx&wuoFxF=O{O2AgRZ zwS<-SJoBk8;McaLS>^TomcPc+Mnr8-U<=0jx|BoT^yZy+*ssLaO&8tNfY7BR3jiW; z&iMin70mwufuNP_>cZZj%mkQ?TNGMT4*}?WWpWgVa#1%$GoJ@lbK1_Ot|=G9@-AQi zi*1QG@Q5Osx>?RbL@Geeng4GNLuFa9#l2eClfThA?6n9Fx~QUNEr8))RRF~xE~zG> zVtJ*U)JJc=^XQ#-d+%M@P&upTRg6_)0V|Ej3F-<`isU>(J+DwuqRgvWY+X?VA;V9i z8ly-w-L$F#s3ze$HPv)3b>oqi2BVQ*s?#pNa?SYA?%{YaSWVHTqVU19qpGOZ6Hro1 z^v9%%;8>LiwICn>WWbrCM^vYiJ;PUi@VgKG#+T0j+>igoU-);o9lGJ@cxh=qo4tDc z*u`_tEf1^rzGcU|?^@ZlYh!6N=gOndEeBV2E}h-jbc|!fbxlK6LKGyan$5vT|Ja}U zz2E=)KRTPtKK`4(k}A0ErlZJp06z}9PaHdO{P>Fpuey3;b9(IMb2r{{_|CT<`uMMX z{??=4vwmj&@E0E{TmqdD(PReG`PpJHp3K+oz2^hui+}a6f9m}o_yA$^hu{6SfAshN zRjeq409ZB3D8BT=m!CiN;!Cf-`K}wesVmk7!z*VlD!LtIOp%-D(GMpR7=?le8^L*$ zI%z~=2T__?s#Arrp_%$xa8g69l)&SlxHzp}-JD%$q(YC*tFRz~Q*=Z?1C3*Xv7-U$ zfOMDFm4HhjloTB|PJk%v9HwicJl~9d_4w?aH;<0qdG9k{zF*^Hqp1-oBsQ`{M39^) zg%HZYa5x%=5MtA?Bn5{63YIJ8fqcxB;A9YrVL1SVrfFgmxv4GXr!32&D4h4Al2{vM z1q}{EQQ9M_kZaKaIB_=aiJ>E*3d(Q-joUeMBeAp0Tx|a z*qLMXbAv1oYZZw2W z11K=YlrTnzXyzXl@-zJ(vk~+eup&5%fL!T!1-EQcdqK;bKig%`wPLa@?8~KqW-G5FtbfAfUOP=1dlV z);cfyVPlJFRof_4mKtv5V0x$5j=M{5Fy@h?c6bE9B~c3d;uapPdefx+?s$8#SVh6?1uyt=^Erm`xlReTg?08%xtB~(&E1xSi2 zN(30(#_qEI$hRCmdUWsO$Id=~>OY=7Q4K@5ZSUxYw=Q3M&5r%6seseP0EXk`#xqHI z;NZ$tS2r&{RwJz_nujtGIY6Gxrr-DbKKj9Lf8XlP(goPDdv)#Ne8TR=U;q&|DoN}+2$>`-G0+;H(z_?$WQ#(-@oIwtM9nu_J<#MQ3cQ;pbI{fA#6-H zzw*H2NAEg%W^?Vr#w<~>ITP}(s^;WFWSq?^=fdt05y&)3m9SAtq75^X%msvsO~hlY zr`I>9wa{RxQO5anHk&ty8cG6EQ6L3U^5j8-$THz7QK>RQT`r1cM(91dfQq74#>DyP(W2cx5uBr?a8hzJ~Y$=+S|hwbGMK~$ZJ)qFGu zLVKDhQ?M!`U@rcd$i*#BkA0pfr#k@xT#kZ9L~=#}D=48ev395P?da&m>*t~*B&M7H6Y^C6cCf2d)QJ^e&f7@xIwUsJ&r(}_!5JFKDz7F>yq#s zK~-4DQB_rD1~c%!FxO_nYCeY+33Z%SHxEW;iQ>_UyosuM?~B5B7S(nW3!k&Wg{7$* z=L+i<@4cmhizH6bhqClxJQxg$VskpJn}$`$JLg?zpWif%k#^f|$kxA0H-G1e@{*+- z7RrIe5xN!YYi+Mrn`g_)a)p0=9r{GKu1#AGhrKd`uUWuh@r_F`Qx^!m(igXdSF}xV zvo0e?oHCr%t^Hx0Dv%S60f2@3;tgASQ4j+vM%xb$``Wq&g+F(o{9d=j*Oicuc z7MFF7RID8E z!(a+l8bdSez)X!GB9iNNMV%JXzSX`)0CX0p!fs6W{Au#*?)|T+QI_;*H~c$Nig)kb zKOPT*C+}TyFxi}{MCHh_7KsLBAVq0nQ#IT)l9(f>)~t+~0T4uEOa8|uN=&hdF~%GU z1ptJyD96QcX*9m(@Kx7bd;RXcyNkgJ>!4`@dI_ZmnSxF?)?P^$P7h*KU|?__LkMa6 ztYtmm9g?c!q^grRf9j?^yKX!*Qa7GAV?r2};N1n(W8jD2mWc|WC0`Dzc@ig$zjMniH(z!5@KaB`eCpNXZyOxF<<@Jz z`1vPUU}J5)p3Uc*^)LSXuU|NG;XUtr+jP45lRx%PUw-aX&@o|1(9G)!30d{Pp@Sd# z&<8&AnNQzz+a2T4c>nI*&JE6-o=i4pp>QcNsgdi{#0rVN^5w7I_5OEmLLC_uAi>aM zxh|=iGy*i4M_cA-yCs9%W z0&+weREDa{L_339AsP^_gfKuIpae$=&fXoU-^@F6^L6s?e+4l$5cz!8JWlznTO97!VN=N3JYN69Tt$Js>!H zgsr*3U^E=sRg{B)9kc6UA_RsOwG=5OiAh^CWpik?VXdtpji+F^N<`|q7HL!mU1wZD|(Ky?h^;ER|BOtb6qqa$w)V2cbPQ)hyz=lE)Ep-CB^bJsF5vN@H#br#> z!A52a$pc{dA$j4n)r(pBeFNvz{kqB84^yHoH{E zoNWP_b9W-hNmH$fT{*xZ&NNLN_^}@drUacI3tzKKb#leD1Sf{_sb>?OQ+aj-UB=zgks%%Z;}` z`oL3br>84}Jzx0rL!bWCr&6kRtsd|+Zt7IU>E5gM?>fAD-@$`#z3%XZ^|cF|8&5v{ zWH}5VGTWSd&qu!Zh38J5IR0uf1%{F^fK$~(wsqhXAW;Amk)$HzNSz~%1Hzp| zs|ZUZBL^dgV-HKg7aqJPKoDRdk3gj8ftHG}th{?%oV?&a|HA3-z2lz4H{EvPxu;lJ zkbDt>+2DsT2n7*Y?TfmuJBL}b#Y5Cavd=6cdgnq2P1DTh^DIFviqtgHX2f6)?@~@p z0x-_c#`^-n1;NgTAw@Ty7J)E)<8hz(P$td zWmzI3bFz#oD*}~L0&tL$)(!b$FdU8=URhpFoYG{oiIGJ(rGAQB$8v4md2fdxue&aE z=(8It+Uim-J=$g8`C8lMG*w&1$+pw`TJdonv@K`Qjk-RphwbqrMCxiKZu#{~;eOZc zTi!-@fsreax6hB<7RgqF;- znJyQOG0_~UH~EXG0xAKbqn3urGh2_90acJ};N6Xb_GYvv&=Hj)qJk}!vOr}we1V)> zSzT3*O-!@dY(AT9tY6@mG$q!gnh+($CN`<5>sZ$k6E`tMPO94O(qiJ-yqV4Bh`73A zb=U4)!?5Ii8G>J3UEQ%`$DkZYN?bKV@*Ze?ZN1P5qSx`tyeOeg8|lTO;Mg21(yPmH zx{NeOU6`!4O;nWCDG@mY62a7HVpVYEVBpHgE&(Cx;kf& zu!K0?v-{eEdoE5d&hwp81t0(ifCvF$_}F8Q{{i=V@3`akVfkyn^{EFQeDINb@4fGb z|K4An%{IrQ9e?}({S&~0*reg09EV-I_8xrcg;Pmqw;sLzn(GcsH{{#D{X_r9PyXba z-g@WRwX@e;f7Skj2algP{^q;x{0~3#@9TL42>|FE2`eHYl1Oq65QtOw>Q`S}x$&09 zg^LZZYhvd~bRG#@ASl+S8!@_K=x34EruD25!8j_?p0e~VToAaZC6QAR1tA1fh~NYr zLIM&;s)XpAM;Lk;gDe9K3s;t|KqX`%@I)RYiZ+r&kjM>z970eTIao&TM*ALp;q33a zcF(PEecSUdK7)Y6qA1HU6v2@bPIX<+W;0v-j2kG+GFQnKQQ@-84m2X-h*C;1Hq0DC z@Xk>vbTFu!hB+aC_l`lmbKGwB0e~Dibj&F>nN00i2WA1xbBAeNw>_lY%(CEad7Fxs zh)4ibg;`Y95%p|^$rZtnS(fE~V**75u=q}wZPa0}g+-W%;SBH1;v887QX(R9M1T&E z9GOdUxA*Ok7PJ(X;TxZl58kRjLXO-^w_T1gYK)xHU@$0(qMIS>x;7@JED8WNU9jg}QOajxkWFEzkji^CD`i>^=vlV z*jS%!tgGhqV3S319aB9|b)8~NkyA=3CP~t8n%DJgK2OY}!P22ahxYE>v;V+>q9_LC zs`n+R4$Hxg)g9hbs;i9)YxVkOoXt2!iM8`7P)PI52x&GuvxXbv@|?6IV(-+p4_qN;duP}S3=(dgh+*UcLLZ6AF9Q_no|tsnZpU-^rF|Bk!gj6oQ9ZEd=5 zkKTLNyME#S{8*|}=}QzRU~C_fLqsG56;S6KkXJnY+)F18;)It9S)V)S z#!G`{%JaJ1n5Q(GvLXYiD%yJMEBCsTW>qYd0h03a@9p0^= z79|e~C;&-O5Mt5<%Ako+3DrUE8uWUu*n}4 zKu_KT{sP-aJ9tc=AOiO9&FT!$+JPZV0E3?X^vy$eVl0Yt^2 zln#T#;JwTxu@GXHS7qh3`&)KwwTqcZ$2>7!3Ce8cBme-^IB{uVka6&>JVUh&{dX1j zzR9hN#zQNh+lau+89`wiW}pnp45TOyu|$;F^up#;RGOxlPS#U17gh9*6g06W zPIbf8L}Fyt$U1MLG+a%q&G`(1Tiv^N&+a|f?7w<-Wyk93%F@!3_dX0(gD<#A8dC^f zm?_Z8SQtHx$B@{8N2hf)natMLCugVC+R!!Q(LCx@3LpY6iF{B(A;XERx%LrB#)U$} z;1nr1=NJ+tEW`_WRizM+y<*|q7+nEwcPOsGu#a(BD5!%dD@wzC`}Vl-Y)t09oRHz?jtwf z{N}g5`NXRyZocKZfBjSc>fP`9mVJA#e)5SYKKO0#{nF>Y5Zun6{%1eul}F>HK&%Xe z0M5XujqYe>gv^47B`}{q|MIbyPmgzQ$N-e*b#mfKWqn!^jpuO?DY7P2AqR*N85P2M z!@Ph#Y#FX-`y(sFsZc;+R zq9{W#C_%ViGz(|CheZ*jO_@MM5Nyd*BEmeqkuz6Xa%9h}H6j9X z?SdzWfJmyWDqS2IdR)D**x?DuYK{nk^Bx^VX3J?O47Q6wL>3SgBt#?>D!_orOmQ^C6s;Z=|jfHLb@s1L=2}mV-#^gRhQiLdi%;dl+l+Ky7rLJoL2t^h@ zrIf0w3L%j9gYmGgYGoEyMCgK3h#=&PA`ns2G#eWm9E)<-DgmMbh$<+dMoMEN<~@s$bi54S?2R3QaHo-8z)P9%xs& zsmCaME$y;j(Zwz{FF&ALiY1dGX#exjmB7i1YwzDPDF4Rb)|I->=mTLk?jXBnYu+(* z_!1G7+BhH~06L}auJw=EuIsrP!eYm<70tXZ!PW(m+H*D;q=_{0?i?U*_BAI(S(>md zkSRauCJJT%RzShFs<0*!(RL@&t?4=A2NA7wcJF-JGjC~%{w>k|2M|5FMXbyGt-q$R zWxJ?MO{=JAS9$~c{B!?yDgjE%glR3?3!k4hNY)Zob~{sKKqYb(sI}ZhXx)SW zdfjjL_4RINxB1JRZS`byk+t8QUtKpRPMqYX0nB2Tx~>5cRSe2VOx1kO^Cm{Fnl!1J zs%{i;G#=l2%M4u<31s|U)q6-80FLL%!>>gl|xDr8(5tQNynufp}bspeeI zr_+sTJ#988EOislt&U~cSHl9)V6l0DU>2~8h$2WUTS82L45&&0{lE>D0GcRs)sWEp zVn;DJh>QXO-B6SUqeJE3P?E9YvQnu;j6RH)4_$wFd3Aj5T;+0ckNk)-&th}-gTL#& zJC}DKf9dpRe)EBkf9$iRU-rv{3LI0TkzrET(;xT~f3OI}=Rbe{?p>qJjq?{TZ0y@} z_~grHZoc-q-};S@pM2qkS5KWysT`Jta1_W~jP^2SF>6H^ZUTTv4WD}YdAarG%`p}U zLZc~=FGG`5B~=XAZgMOUx8od4RHCw&&223a1?C9k9O}ryo-*uqIzU_|9uqG)Kk%hP zLQ$uPK*?bQh{BFY6j($NiNGm%zyRXXd^B`Ls@B#ghwr%esVBaA{OLz_4oBsvtc9Hm z!{s5UZftBoD+QfSr%lt8Wmygep2$1jMnELSm{Q!bEcK$@pqgSN2R0?C?cSUdk=QgA zoMMh5wD5@$1l4Z3W^oZW;~#b_t^x&Xws_)&juhZf58K42@IUi$gpCQaz; zuG7DBnr>BlU}rt>pWmG2@-5GnTUjlImb2&(o3XG4Wh7S)%teeGXc+v8>5 zSo4{_ZX+{@c{#*wHsl>n-2MnCKwEK55eZNhlD*3fFS0l_C|giS4TCIVv)LKle$)Kv zxAd1RtkWfz40WK$EJ^@ApLQ<{?7B-Zp;Bk5ZpZ7BG2N9yXF4QiVIv)i>~P_1Haj&t z=>WS8X45oCgzOxWCXVwe##q-po7Jp1Ufy}|>Rmhc?%sEB-^%iG2nB+w(qK?}9}qxo zb@56Hsc9ru94oF=X3bPN#Tqq9YMR;Rbh@!Vxv0>nM&W5{7$iDS?*lp)d6p8Of}kp) zv#pSTqIe$=1woO5FIW9wZ!z3i@p@{eA`R1OF*pqV+E5J84bTk|BNe-n+mSFN7=vH+ z!!eMArR9BBFCDmg|JiddiE0SW=5s_;X?E{i{^0w+B3DAXUjoLwZs*s=lg`j-)C{CE=qgRcSv6l@K7b2^a#w zw6>9L*=ns`?@6z{-D&n-Yt1>oKi1y+o_pVuEFh^QV`LAFr+d%cXE&>vYp&n?&EI_2 zJKnx~{rdTHxBc)B{hRH&nR0Xa`Rkr4k6uiT37tzZ5|NsYvifvWA)W%i@4a}nx^^A! z@=>A>l z(4j-b3}R|xnh+Jq5M@A46&x9}8JY!6kd}_!+GM?LJ2-dt4e$QYm5Uc$1#NC~wyK^q zRcPBK0F=5vs@ip(bFOI`=bWURa%!+Ci}$Dkyqd{8{?K(%jh(|00%2w|D9XZRV0T?~ zjtYwc0Fba`&GeANAb7(=wPzwS#5`QE2Y81qL|Obt33e*Xqro6BGZIRvI&M%d*i-S3 z7?;DX=jseCu^whO&eq~yz<}8>Gclpjc(J0q;(Q;~Dp!C(?6?q;RLya(gGWR$r%?^B zKqT5C*|FpbHRJ%!_hc;R5*~1)vW6}TG8vnpXJ zo?BB@6*B|aa=F~w+cUF?P3llPW;2#7t}kdn+)FI>iijhXXKd2ZRT?b$k>i;4evQ7u za0??G_kOQ-+0XlV|9JRnU$ouYo$2e#aZ{f5`xV4Hv0E8=*8Afv6c;B%r zSAT%jF|g7^24H$v%gq@R79mY5)J-I?ktjFbV0=|w0bTm$JDlAGP(G~ia&t44loLQI zx>~(_M0OA6Jd%{l^NXOV0$P@?>)K^|(54Ib-gp0lZ#r@Mj1M(AA~wqkSySvP zVwabsEg))k$UIlltRR}Zm}5$d?qJcjaW0w-B*$o7TrOu>7R=hk#l%O?P&sTuAokh7 z`?|oVW@1`614VwxAfsnw*RY#Z^_hBdS7^4gT+{i?G*36~;_5=1H(j;ncmiA_v(TDY z5F`wfdU{5%F;)V%eiHAvaQh>lc^-|*s#mOb<);TXcK_m^`A!w9>Uv|slH>0FRp;GT ze9c$i|G<5l8(V+J(Hm+`447jk4LVi?EB@|qks{K{ zL}lo$dP_C?wE!m_o-{pSG6AiD0uUn-kf=t<$do~oWl>`GOnoh016DH;6A&_WnsYRA z>T-nKDe~*z`tHv?^s$$p{baBO(X*5G>u!Do3~0g`7xzaY`AH^9*d3_9O)S41VTb4=f;FY zVP@ypdq0>sm28BJ11eK?EXB(i9}ete;tYSpV`?w>$EX21A4k000~ofERnUO|lZYQm~#0 zjVpc=zzXkXP$(xT1wnfJkkp8%cLK^y2R{M<3>b%Ejs^&(0C}}hsKn=yR14LvM~n^` zPl!cuh58Z!$h_*JmO=z*3P7l0DmG|I$PhbJxT#o%tw1k*Hw-D$V`y(!0D3P62$D+I zMaN9el}#IaN7=HPrrfq|+r{^M@Pl9Y&UZJ{b!6YBB(XD1m{q$7V$v;~wZz=DZMnK& z!xS{9xa@L_-Fz|I-&=O`tVt~8oNSrZ)CB;HB8#mxTbov!8-7}605+xsQ4tiNlay5$ zQPB{g)SW?KripVWLVa63xf8=0C-lvpTF2EXaHoi;f-Z{#^GE@?5@bOLp)wyte4%Sr zljin2&rR1S`_~V~)OP?Ns2X?HSQTX1?x!?c->iSv@BVF%KKaCKx%1OM`;%Y)b>Fme zWADcG{U;uK_KmOq;%R;I^2NP>`j7sF_pUHhvKk;GI3QCpBS@CXu_Qp###bOeSJUKk`sS%4IfKv4uuOa!4dHC0dK5Y@CN4k(x@i!z#16AosQW2-4hid&~Q z-}1J1TzuxCB$?6TGA8WWwjD=Z*_xNMTLcVM)l4R%Qj6rAVsy@x1e})Hxv{ViFMp z5HaUnT{lG3b)Be+TCrv^r>tTI-g`%(G++s?6*v$|X+~9Esmiosnh}wl^Cj#)*1&@C z9RQ>^+3Z|#qXD3#i75as7E4vlS-c~%9+k;iymz%{=X-e`W)99V_jMISvg9n13<8rY z_K=w?@2jeALLGRyM3P&}_2R%&qTbN{#@9t#>9V00Uo+J4o2I7x=Xk8X^lLy=5JtE3 zkc9Laj$3rhtDJ0Byyu?oW`M6010L4(-me%la|&?^&8U|FFVEaB{0P{~==aJu1^g)~ z9Hp9fztj%wkXt_gu>s>euh4!0fI!KL1vUaOL$xvDqpHS5_S5uAp5a1@c|l&l#qK z0OY+Vbh+PFq8XHQB%(eydz@J#_|)5apn;$>h~uqbd#VPirFvTgkgYmj>q5>5CaQo) zIWeFqrkIyeKlGJf`vvd$;;L!Nj$g7mGBN^(T?ZQ5c0M{-G3O*XW{r|N(xv$c7&En@x^eOZY;QG_jkUE25oEQLOp}4KW2=*Cwx}^82$zT@ zgN2Ek+~p>(x5;_*wZ{N;oAp4eHADl;hz#HjYJ@eRx?>LY8r54-(LlWtqEVnzXScV{ ztnJ>|BPdu?S$hcplo$KU>kqu?^&j}a2Y>$O9-3}#|G{tjz2E*P|D2-@_V^_q{IcsW zbx%I@{Ij2UexnK%lo4e0Gdj8zqSJo@R{cMUh5#$K!Tqwy7rbyrflCnDI5D83+a*^gZttg;)gwxFH zU{3P{tqzQ?ROmFcUWq>@Q9v=w25P}bTNR9g{VoB?%(7<7fv5(LwTQH0*)r>*l|BEu z+s>TW+}^w1S_VyB2lKXT&CEH6gpRo=n*l)4&M)-OLLe2@E=8ApV|_6JH7yEwCB?=$ zM`UAZCsj=G9akbjy&Hjmu21|nfFuG;U_{Ot zV@z4Hnjrue>I`9z1dlFJnQP3fYFV;^sHrg)yCeu%)d+zwn=e#%7qzQmZnFYrV z5LAS{H=@|(m`#9!tBhS%>#{}xMMq2-%p`+=_ueyyDwI^}K4TogihN7kil~~B^$|T$ zMIwh4^bj>6C=ojcr44h=fT$$_N)-?TqH}dlsh~b!WJbYPDP>dYx&-Px6CxF`Sp>nm z%GV);5S;huT^Nj( z;R?uMgQxjg55(24kB@ZoQw@W>u;XtL!8pbvu|5RhtVBGtWEIgW(Uh1`4UUj8iWxn; z0v`1SUa7?nyI4UK1XNX=bL5Wx6+l_aR$oPm_P&}KX)$;v3JL^RlnTla=~d{^Kta^d zu$MUP8=}-gL1I=_8j_2P(g6%5po&p71mqPG8@nUKz25UE5UrIg5gT6-0GP;%3o^VF zLBGsEzsGW8^Yu6f9Shu;_U(uSO2#^PZO59lJdX_+J1QED&{M$}4yh7H$2Y24j0yU9 zunrn`hpe>m)MKs;7>yQrXx>{#e2svDcXgd+OHctbLMpnWl5MeQA9($P?|Ro4k@u)# zs-!}a5fo7*i{%a^x665#7HzvM7@L`qdKD{$-%^UpHg{1~vszRW1Ilg#IWTsZmuI)b z9VhARR#>lbTCqdRDv6vD3-N?qo2301^Q;Byt8o#3c%Y z0O%MAKn0;xsWMelN-;uY zcI*&c24Dt8W&;S?nnmn(G`z&48knf&lu}OAQ+xY$w82_6jbnI>93hdYifZANjMUm;BKIPAg`zP|Z~#C9 zJ&b+yg8F<|TM?M4RJa5H>2s6{_<}K|oU%&K+8H@X$7la4lDl&BB5y07#FVnCBUfHF zi!eJXTQAe1h}!dAiu9jz#609|mqmgLyfLcx0sxkRjf0M#ArvHrhzdVxG#w%t0g>2y zhlrqBRh2guNdqn;Fmkz^6Oo+7ygBwFno?#q1PGxDp(>KZj>$U;A&jO;RAb7(5CV&! zk`fM;cTGgIno|?WnZ02D{IEWqrmStuA_@yw#;%86`sA3y=l<+jSLE|Q&Ks^`ZN(U zB&5RbF*CD13uJ{ER4UJp?@vR@;;Mgoq$9BRh`H1vw^*18Bcm#SY1Wj~d;i||zBdHlE*I=5 z=L`nYb!K9s6610{Th0$=`+FJ}#R?YV~Tm+-z~R!92+tfNB@eNo$k-~NyO{`a0abL!H?i_5+C-09mNPERK@$KgnoxMBN%MKq1j5F$zT znIRKI!tBHvo0>sZP$X5DER_&j)JO=P=gsnuJP#HN$PSZ404C;}YNKI9f0T~z)F_|b3Vm1OKDAG#` zPOO0xykpQL9UxSB-tE1#f92`RyDxLwacHP%-u%FW0(zP9uWaG z$kN4F{M`}(@y-`)g^(CY2LmS}DMi3u5T)z7vJBfmlJ(A+^ehlnRZ-}RFdlM#OU))2 zoO?QwvDwdb8no#EfSAY1k&-hx#z9xN2wWEqP4Rw4_Ur1DT=j28#Y`L$h|&NNl_j#E_?Mpv9aU?7tdiN9ohiAjU=1##rt>&85n6%M}S_6699(I5%(ESWzw$* zZ}tYDEckuR>9O!4Afhua4?SW{1f~WAU`P(kkhI9cn;FC5y3=DJz0o6t!mpz-Ew326 zQSbbidAb?rSZTx&-%~R!(YA5-w(3ua2;dQ|VqHn87;WnzRdB^48J}d_ah4BPiZTrM zI{LuJ#sN2#Tc5<)0s%q#@5dPLz4!jJXU~}#n|25D6jRDM=T!8AQ`;?Ov*qj{$96fN zk0}fysz$0kp+O{z6)LzQsG)d5#7p-(Tkr@Z7PrCYiHND$#BW22l#_L>plO|~!xG~-c^#&bCybmKChlLBw3~Z!z_Y=LxIfirtW(F+MMtwHZ;bjNc=dfi~MCf^?EEFPo z@6D{l>|jVl8gqPPCFyCjEu{gSbKbdj*_J6>93ysBRRw@vUwG`P@@gs?E#{oVkfEJY z27sb{2YrY^M0CumFo4{bJdrHewk;F;K+ZWNNGUCsO9f&>a>1n(LgO5pTGKQe8yj8f z159Zy`SWoqZ~50(Tp(9Iy5;$+XRf+3H-%9?&y(2Una7|1=1*?%@b-Cdm*1>Ew6eM# zdn#PXQCe{<%16!3pKtj!Z zt?Zk>UWz}Lk|E;-6`PJT-9yI>djeZUEq_9Hs z>D5!P2x6=_Xw>I2A7V$PC>sn~4tP|Sr=*gOhSFAarEWQN%uI`#?@_J7F{U`Sa!(G* z%1q-bxZ3)+9NBiOXwi{By=IZb-twA-X(K6Hmtkxv(^IETEf(|ry;)5*pB<>`a=DCM zLR>+vN65J=e(trA20;)6(9yoYiv}5%b3ZNyPe$~fTx+KY6L{ao{+(*iX}x28x&3i za%?QFl&>9c`G=y;Owo@lhp16E*XT?Z)FgKlSe%Jr^@4xR$-V^ie@kd^W zU7Adq^=88$UA=ULpjILF4Cn#CA?7TIW0OkKY+bg5z_ioU*PAt%Y)HLE%{pyt+U5qW zZSZ6h!kW1NwPlAwP7RG1kQfj|4b%uovUDIBMGy=LNlkOkEIu zHG;EDq|P!cOd20h8V4Q_q~th3DXMIWB`b=U(XdwKoTVr$0#;3p&Rd>SzOi%hkry8S zDVH`RV&~XVkQ8&yIZFr*08)&(;*yyKU@=A@SG!PJ zBj-$9Hm+Dz)nqcMn`YT{p{k_okZ>%E`(KX#ypD1Gasx~CH9BE96ze(kevXO^tuWPI z4MQq(5UHqo9NxM(+~ef6LEUW(RV(yzLqvoMeW~x^%TTC~IE0VT5RF#eOotrgva11r zQN#`aob@3C7;=&Ypg`q)Y=z5!$FWq=@a2MSK(BTM2!%??bd-E!NJa`e;3&E5i4hgi z02NIPrPm7t1T{xg3N4iYPD!~KcX*?D>Khcuv=syq>Rhm#c3!>+3NtN=TQ)nGsj7nJ zoMIQ3i$&M9#gFN_4iMBdNfIDaB=T1H7?1#x8doH@ZFKja)7NizC$_4!HLP7~JgEy< zO~{iXh$q+7;sSC_zEJ|R%z=Vm!|5r=^-@{k1XZWOO^`yN8Hx~A)yC=dZgzbhmy@s| z5K4+Q7R5Ls11mMR3;{3$7IH>+=Ir|E^HX>mT0WPm*y013%ZO+_+zaFv+{%}lAnE=R@G7?ERZ9{iW+KUHGC0>Qg7@7h^#5u0|vPF;g^kc&O== zQ_MN4CL~5eCcptV?ePN>B1C3n%PAMMsMwm~Z~$l)H4wFI=6HynjQ(ggB?A~3Y1o6= zhhT|fQShX~ZxS`NQk}jOci^l7CQMA;Va_A;O+>_GFerj?*}y44UnfGXPnj6!cyDT7 z0X#P|6$M2?_QVXB^AWJBkc#T2F@ThFmh8|ucA^Rh?3kDhu#}?(WD)a$oeRudv~h_U zS=B6w0U9YdDw&@wIZMuF3W%W!#EgU~^Gi77%Fcd zF}BP|jA)7gL^v!AM1;l+hLTM(lB28;ldyLTkk#toRl%~5WL5z&ViYk`@W^$j)~9P- z*LA7OIg1Gl)lzSL;0iIJ70)8}xnv_N(}D zVCooTVmHO+jy>6+&ksWbVyTI{^2$f~q&LxiSG(pI_5&q2R9u|m3Mg0}^OFDokgbU9 zmgQVcjkT!cnVFIlPX!BtB!Z?mZdAy~5G>m;%@vj1ka<%cRgL=v-SDO{=?1;jVN?P1 zS<0my4Ti|o*j`7*%&|Vin|oCe2v85{IWUxB7#txTyU_YD2AB;^3B6l^D=JS#PNSC} z?IXTLcfrc%T1v32NI07eXOs8@{1Bb!N%c^~^8r&gPv+LKPq(R916<8MT!% zezX%*K@t^11x#jS2JA^JBN!1HDTq4n$rQmlo@N5bVg~5efl_7d)NB)NlgxXp9hJg1 z%#MjzwIMe#fOaaFIs&@3)(bh{h-FKwl3jqaH)v*aRjpejj0!WuU9 z0`=YZ(Umisk6p=+U%v7D?k-jn=NgLGXmfC5f3~;x;Op-H_@x^muyX-GRE-F$st!H? zfU0&~mnD@N5u?bH4YGk{P>Lxcs!5iVC6!FVf<}}>O#n(yX_i!KSrx#bpJ+A+T_IW- z?foP`0O+e;16YwZ2SNiiG(aj8T2M91QVQbPScZD!3>qOQ5)nEC1Tr)!#gfzzkPgeu zAfka$pRe7g9}^J_V7nG?16-dbCMqH@Za>U&&Lu5eR1Ps7L_~Dn_xQhn88AENyz>E( z*m-6~)voK1sBR_&(UGj8F|ezBaO|0U&Zz{BMHLC_rtv{?&RtA~6ud9Ruo3zYVF*E) zVbB{KMBgz4mvhdNx(Eaq*fICMIMX*20!C#r4i0N9SFXCE_rhaTkT<;2>mC0!XRH@lvzKd-LVf`fp^ z;P$QrkjlTf@^cxJtLyYDvRYpWv^e%gzuvDOmU%jS(p$$FUg5T*qt<&MH-Gz;aFJVn z2&+p~|Gz`3%F!FXdS|}kQJk2uXR%jt8=bJSR6|iC?H6z8^Pvq(@)h)J0cvsO$7=(C z>KG#fPyp)FkeJaFNCtuz0@%1H^-ttR#_r(C^eUM&pfp9#Y4|=IFY9m=;y^&sqk`52 z&l#gwy^!7D%G}&oNUC`d`L&X-W`jC-FC?$Js&z3uxtaD=3;N^~JkpW2LPNe~KOO1y zS3HiWm;(SIl7S0>wl+j$c`!!=BV5Mr#@?=`1o<#Vj)=>yO(L=DM6^&Kvsf0D5(hUh zX758*K#)$;{@zSv`MPuU8}FIC@g8^A30QX_OdB_?6PtNd0QLyXhDgpS1#%nYHw;6T zm<<%X_nY2tbJ`9#0X5A+9I}`h_^R=18x*FbZsIopM6@mXX0O@~eG4%2gKxP1`_ z0dzWqb?DZ;G#-7AyB1A8YD@xh1{PpS6j(!=;oc>8`Lq7=!?*G5gk9m;Zq?4HJ7ChR zZO)x@UPPQ@&)!EP1bglxH3xMum%rAu$T_l-}T zd^|2^RzzIToO6!)ph?$tF~*|EkBEa@FPKvBp?6bMRmFktHER1p>^izK0MNxY#Rv$9 zlyf$aQWNR5^f;{46BrL7f|bg>3jJvB<>mlJT1kmGm(xaqRJnV3oifKtsqKCfuVAQv zhj%9;!$YdF84#hGnM(hTayaQk#CZ=#`FS>QiH38`IbU>jkCAnP_o52SRaKSvQH(i? zIHJl`0D$P3DW*O&R+zsf{FGBFvOSJNN3dKjN6BEKUX9nxy4WF*_kp=*)gY3MjLv=& ze+Z1JYC*obF6NZ0$_F2qV65oWOeR^P_udD}F+)~jWG3gG_o1!?2y<$4wp-54|0j>% z@Ur?}j{npSwW1OOBhGcKYnk~F7&1dT<~~5-^I-WZLfJ=wno-GYCA2}if7Odc#4&Mt z<ltLlHzosHI%wZaYILEXO zScwFUB;OInEQGuALkV~fhR116Ce`5E?6`h)9YaMV)oN-F@3#)r05HeEPX-PhNWQ!8=cEHSWom zue_X^7}kIhAQKsxI)rKP&M|;CIeQiFt387-fAQXJc>CRJ7fzpf`nmaMp1$Op?I}w| zz-X+dCf2{ENsI>!!X#jgEjQAv+P_g<{#@95`doZ&BR;!Li@4myF5xn&iUy|uSi)Ygefr@T zBcl0G*mD_>Q%Z%#Q5N5^3vy0OVI2P=Qn0g;+rh*=I5CtBhHV5frwz39ldiw{Te({ZB{QxgKT+W(xjA97bV426M=(UHWGk_2rv+lA+9d% z#H`Y1EEa4jrQ}1Ob;ZnJhm1uyiv0J_JUro;fL&ZG=UfP($ZHlkA5~2$ndm44Tr5(} z^)VK9IU(oly$2-keT;F;kaA`?u#X)(H7TNbJ^1<; z!Pj+N+9Y-RZg6Jg)X842JsV>yDQt zLKx*m`f-JD)B$}h$9N-nHOy({w9j{}QpH!FU-ifv9d4@aaAdBYKi=)wQaHZgag=D~ z;)6v6xTyil@GJRrH@EHKydxqVLlcLAq|f`-u#&s8+LZ={BDne_$4V+5Z{bzzfmq^Q z<$^s=*ZLR`ni(=dQTD_lDT8R(yCY_+Zi<-++=R;91S1|ty>O7!D;>Z(MjT5+A2NwX zU+32~5(l)#tPi&p)VVhh#g!Qq$KmMkD*ZjWGUH$>9~NBaWc%ZuDw5>Ib`KGM}9UvmPV2XwwI0!aso?H#I0eG9)1h6Jnu-0gjY!hL_%o~~_ z3I=n|H500vgaI{JiB-DHxz%6KM$OEcNqGA^-twy-eY~3sBb7KD^GXsn-2n5Ce$l7k|jjwn1_=U=rCCAvgI8Lx7k z(qil!no7=Q=A2twTSLOd!CX~KI#^jFh@olfabA?YQ;f05)c`oczCf7wUPZKvE1R)H zl&J3rK1}ASs;X&f(42E>+qPtSn^{>(m+dm9w)2j**4M%s9wve%)uiSCiFurPJzwHA zuHygYc;&-zkW(8qmf(=og?Jd&v2h76K{Fbb+F`N#%^oB6`5)-`4KI;5JFnwz1XjJt zfh7VkmfHpxwj75KP=Z2O^leunl>j(qFb%5=tOlTl_gHD8B05}tBm)3a;KHvdE~*uE zKN~IJAhI|9_lohq0XGse1(2~0PRV6o1#$|?GGZ{}=X$hWTK^|A>vJV^Y#r;DgwI2R zI`;W+<)aTh#F7m$n@%>?Ev19&J249>q->~Z7~5c~xg?|+l`3cKgAgS|1V`!=3{aVbDQD^RBv)SYc$1Wp=0$H(K<o44U=cAu| z+OZ=b9d_R3tC_$ndLaN1g6tVYYsf9o%&kqo_)FsZ-uH67y;ypUJ)}bFFe#!bECoIA z1OXj(=jz38dFz?)|BHX-t3U9?AN;y+c=^{J{?wzN{FTMd@Ba(`)0e;T>@y#~q*PN{ zG*tO&q6#cFLD`1oxdz^F{xntY6Hi?H;GO4x4E?oYF z=dKDIBp)>fNgcWk6qj-WJv+vtzAv2z42ZLNla^^`H_RsQ+CKB%x7>RbPdxO(bDMWRc-QOh zfArDM5M=A(JR?eSpef}+MoGt^j=ki!0a%u_T+VaO9$3+p#TuMLKz78rL|26n9J8?l zK+U2h&vK>KLCMx0JDZu&pwv(4J%gh`_ufQPVXm?EQ?JK(5Q<&=nNyO<`MQARR5tkNjYaVZFCtyn)^)?olgX3-+vQS3#8kz45-{?lnRKxfj_9l9iO6g= zBZRsR%r1lq5mSl^T6SwmweXE|7Bi@-I_KQAEe-`ML_|a#Q>d$4He<}*J236KXyBO8 z)Ryx$_^N4YW&%)90m-OR1#clVRh})DQYxqe63_@z4MxvKMlHahu2y9gzcsL#@F9MKRSP^*$~}#f?TaL@g;4#T9~59uW=9&i+GgIJ)(SvzSQV`0-&R{?X8@0Ht@!1u9GyHi86qDaY4W`%8?id#MB7qDK67e zi35hIlT*ICYh&%)TC*P3h_ZQgl{uIwcy*1s27I0UmWC~WHKa|Wb#NhxxjaFu#6hA3 z196zRFokL@lLrd~EM}-f_8RuH$%7LN5u#cy!j90Jm?N{^v@ahG2t?Kzg{{qd-}?H; z9(kIwnSt^lT1HH!8Gyhjp>+h#$ceTR_s*OTAN;M0H+A2_$(9-O-4-bY_LIP>IlpM2=y`|p2XV|$&K%X@r&-L#ocry7yH??5Ha z&T)R*`He5Q` zvU44S(SoX+a1wORIVaaJS+cab*t`4UFW$fR@$K|H=Id>|krcduEIUfsEL$m)#^%_0 zA5uyvrIc4YE8d);wyv5k%q&2YKczMFh553`wZr|R1a{2t}JMMh;r7N)W zyvvJD*FiMsLexsIxeWDj|BZ-dBAPU%m|8P)+Q&2jK(indq9GXq7zhvrc1T5iA3%=O zZyT`+8j`VTNO*=`8?h8u=@necqZpy}m7)PeYyhEEHL3!!^Fe_WNDT}b$dZbcx@e9) z5Qr3C%52c+EizbUfM6v?34nl-MTU*d7$UJjB-1&kraY7rFP{*SW|5Rllsz%BXkVO! z5P^s*Pma|Lm_R{A4IMi!6%T-12%&A;<#Jj2)Q2FdmQufFA)2D7h7cS>lxQk|hlR~W za>`}B>`OzU?^G@4#Kg$R=1mNYm^^xxBq=XT^R{i9bE+!GOfLA8QdSA%RJ7~5lv1cF zCZCcn7cC-8r;R}d5sOiY8L1*SZNhBf>fpML7#u@R!xjZPae$v(a4liHc$WpbA9sBkcwmd z3R!eav|o>)5HbkSnUPvHQbbQ!H({f$*TdR+(S8(BGX<=GDx!WLUWl#bv3i*UiJ+pY zMFiTi5wKb|%W6emA*R%cCe?&wY=DIety+2pkOwKb(m^_;)q)xz@G3E&FLJcnIzxMX zUI=G(>kdczdKFVBHI;1Tfe=U$5K#(*f>_Epq5=YB=(B|fXJcTK_YPWbe3(aH5PMmr zF|vlM-@Kq?wUv2MQaXc@;F9Sf1Z_Jf08-`bSf5}33IWAr5_ z)J(N-NugIpCnh5@0tQ@k?ZLr;q=ZD|9C`*{p$}o=-Gq%4L(@`?e$9n-S8W+gm1^Lc zcnxt1xMt8OpphCoLZ@WY_0#Cr3|vMh7_tEmY=m35*hK7{LzQ@h%(Qx*fr(+)rk(x$ zb9bEYvOIU`dQ8>!o%d{>JGXrPWgdle&0>t6+-7u3FZ)drHa4slac1@6zIRj~_`u@M z+d5mg7EQb)-v3^Jli6xq3zL#&7-YU;9Hp@Do4y{U80(FMZ#e-g(a>mk+v~T?9xasvir!=Ngql%jNq z7`)e-%~BFkz`bi%uYK;*_df7e^z}3Dw(aec7cW24q=h$4)-U`3FcRKjh~kwZ%sHoV zUX*x|jRX;71nkB82PfY7e&UZ)X&m!oq-p#U9kxx#Un?7l5KIS=|1oSdAn*V%>XgE;sVzKK9@dFW7{&*1;iR>-wNV79M2LD$ zk*H(??vpV^HJxsT=^8Q3^d#=o~Xv#Up z*v4+ZT`qF&vc!}lWkW(R711o22-z~JjHmo zr$dg|XqRsFt^RQ3PVfp&_v#be`Vu`N#GneqC8}hqfP{pl3PN9Gs%+q1Azf|tC?E6y zvP#yT(6p+10fP z@&+<%1xye9Ff#vxNN3IfP^4NJlo-YM^o(-ZG6<}d9>nd8N)`+G||SaiI7a^vm`dlxS^W&pTih*{YwEj-C2+GOc3uh;Pl zzj*S_ui2e!=XODmvW1KlcmnVUS-@G^YuwH|&hhVh+XH7pyS7zzk!-*H#UJ>AFTDTt zZ+q{%Kk`#Qv$?r(`t+%v{HdRM%bVZePHkP=-|4!m>#lzARJi~AR@YiB`K3oc`&a+F zzkKa#XsG({|F=JP?$p_A?X@cxoloEP-+arZi@;$#cJ1aL!es0x$y^ zl^e}uRLv(ODEJKkFfoH;>yl)HiZbk7sV_hN#uL5^-I?w6m%^l-U-i@`Lp3ow)NLEH zrpM_s5-(syFgNGi$7HBSakNim>Zj7kDjVJRG8o1HZgD zNLa0Kq5&~@@0=@d=Dk-F$+Ei7zWMmPJNQ>Rb40IKP0g!(eH@A_SG&aq`;{#47+N{J z!tvA4fDov+!e|Jr2A~W;YN#xTNaO(=lXI>z4or?IR8>>eYd)+KGPAG3q-iGHG~^sJ z<(zZMs-~J_?9@oCPz!olAT!M3N^uhxd_Xj__32jar-0UVi!LqO<-A?qaP=alHs?+> zaYj@u{_$eDkgi2p7U{}wQ*2-g@x< zXCC@gUUW3Ho@19ylScvgD2bBGkwTAJtFQawS9*3C{=@cuWR z`11SDz2ijn*N^Lx1r1y#95se<;9}z3ZQQ`q5@Wk3IJ3@A&Wji~*$S z@={LooDmXAZoPAWmPAD|0WxC2{9=lLN~DP719xo_Q5eE8M3izWCdY=+ zsM0D37;3xZgX@#o%WpU*mky>cp1JMv!8OaVw0m!2%u3EJ5fuTqlDwcp$qrR5GO}fh zFDfW7)T4*NztQph@fhvEkqNS5aSoZe)-xQfDDvg3==yo9eZ{n4dLa?v%C-Rz3hWF; zEe3F8C6C_5YS?I;r>ler0~@MCs;U~mC?y4j{;XLCjCQokD^~_2Mue{GQqJt1^0W{gmS^QZB0!fm8kb*GO4PnsCbo^8i5?d5gUV?4Zu{<47xUf zQ&25Pt*C-oIkRjqh!DV3rENt-rjrTQVU#`WO{Jl#iDJ$v=gbWgVX2ZJD!qC$k%*+6 zQc8g4ogY$K^rp1f6|?ZFE8pimUTG8fc~5(#4dmy&)GbdyWB@b+1O{|~kl7Oi#L7|4 zuErXDsG54iyS3ma=opBI92ygOB!*!QGCO8Q?*$6Pg^CQArH>)U7#CgaG-;u<7YGp9 zO{P0&w8>{7%|fvOFMUO9k72O7ZFzOJgO>$<(Yy>2-}=^Q&E>bh<= zpIL4TTq!CB;6Kl*f4r$*soiX4&p)1TE2YFLpi6x_`KrEwCZEQ zkyZFGU~*FiQy-8%{)*GJLfejphPyE-()n~Hbpp^$@=lqDF_ z6Z$BX6-o`Hl*Y1`15XG>ho-Fm<@FHhNE@w`)`P*ksu?2^q8x2MW;O#=K`gQphZ^cf zXUOPij&IZl{7THNSiVyI)9`T9P>c>^f0`kP$o|3p{(NR?M1(3}C5~g+T$lZ#tBlvf z+DUO|MbDs4MMyzRg-OlI-a-Ynp00(-7NU1dYGNFK0)mkVn381#GhjVBOiLhvi^Viz z03>wGOq62m6t3;;RCP6-PJz&XcJ>Zh!LYV{Wv=_+UYIR+bDo5HdUA99&U1?=o^fy} zxu$9!vq#!9-nr+(=W0f50p08<4fW{$DT*_f-b3`>l$RM~8rrcaVL7-Jl5^K}f*p^QA9{lR%~tL|m!mxMD=x8SV2I4#xj+=EF!6PR zRpYpJZsK|BTw{TWy!UH$Sg)&%;2nSfkSc;<*4*X;(X5iQNmh|A_U>=LLS%)QA$GD5 zi0FU-Kt)n6HIcy-{W>@_Ggo`xgc#*uIbXE1*=#ST$Ur$m7B&bzaP6Co>4tNT+cqlI z(+$rwZ6-(eu(&tK1KFjQaw_M-{3MJ(%(#4XV;Q^1;h_#Xl;%05H^|<)_MEV1g!SLPd7O6a-96 zvS~I%V^9#Wm@$(92&svxsEL`NWi>6{ON@CmNz4vR!9aQ`;bNu{O7tBO3zLT!sa#Nr zkdTX#PXYNw4Y7pD)y$mpHi$hF6%fhHdgz||R8<=TMn#Nu=oJG1GIT}`jSyV$xr8$D zFcFM_M*RTf$i)Cvi(I%Q^p?1*KcNEnpuUC#;aU0pCLD(NK>2jGcK$xyth7`s^Q zX%>Z9wD}?$Rx@h^z&u@*<&z7zl2H;>0_#3(-goc(+2>6W1x(C9Jxde2OOuV|o8RN# z{bkFobDGW6p{%c`m%sWg{B;kWIkP-Cf7hAE&o1hk&z`Cu{lv#V_vo{y&fW2=pL{gu z=rT0b+NnG4I1tH|ecONg|84-EPM2PK@%lDz(_{_z^2dJe@!$F_?|t#$$=r3-`rY69 z2Y#pF^06E6*o*t`^Vjaa|0IHc=J6LbFNpN{XP^I_zw;aZ_@DZws;WQs3orcO5B|*l z{_=;u|EKP|_pYyc{aemXfcVO>3AyJwcvecXp!=7!7ijat9nUR3(pz9D;%xTQrD{a!Ozs+ray|-9EV1 zy!`OV+aEl6?)=%aCtrB>nn*%p0tCREQjUp;z^ue&tkjQHGtmO0TS<%~CPFnRx>95K z6^}-yMkKi$>>%LL@TPf1iW8Zx5y zP8CHgwmG|HDxL~42zjmou_tCGV*$5I;+0rq?bSTt=ubRY!$7Jw_xb0RRSI03geNq`=5NR5djl_1acbZ7@!PpSaL~ zYnYkIITx5+N!zTdx~`k5s+ns;&N=2ByKdRF%VkP!H;ZYmx@5W4i@{fv}{c?NHWQ0B2=a}VA)-pXCzbr z(ek?;4a**L2F4jQZW!Pu=fegz2mn|~1Tmme#M?}e06@{eNC617A2n)ff~%#0hT*e1 zHUMRy1r;ZxtW>M~T{n)pBS}C2pksYaJd&h+_l;vc# zB5PS@h87$Cgi0(5QBe%Ll*KfrSXUJQWnkz0xihCD$;b>uK=Rf!d|NHqn&wb&0 zzG!Dr|Ki780IY`}jkD`EPvN1Apz^-}?Lh z{Qi~8C%3n9T;6teGj})cz5haR{>2wAc1ulBUVQfQ`@Z;HU7KRpe$V&*!v`PS_?oZ! z`uBhFTkp90)b8Hp=N|d^r#|zst1mtGq3!D2dfV(hmGZI$URKR6hKoyhy0yN963L$|8xy(cCe2*swx%s7lC=Q$_IS(se`8Vea{6q?gsnzoPEl!sWQ zwU#hS$=)dukeD+A;GjSQ0BU)p&WuBPWwrvqVLlELFcJ(}!>hsN!J*MX;Xz>phD%Eh zZ6Y~~$*0xkwDY7t(i`n3Ws1>2}PB1D@7I1hak&nCIu2J zaj#A_i=>nmi$zM&c`EH}V4(_{Swv$@F-Fzs2pP%xx^fV^n1WOBC4kt)Zn*?PMsg0B zm{3Usd&FvbHf< zTMx~4)ofON%G5B`NF=5D0ik0Cz??;-Uo;9E*T6(_Ow0LVZ+~{M?7A42Sr!T%GXzp| zV(QDfAX!DgOqm=ZJ9b2VYyCtBA;vhL&yiWcf^P~<0*w$s#lWzsCU@R(*V&V2Vr-pv zawV%tDeFVbre=r=;Hnzf3o`q9vJT{{rU{cOXpXb}-AgY#H{07uu{DXBVop&s6)wG* zA)ul{DbOTJWupFO&>H~LUa_eR7C{-iq|b-zz2YOiHGKKC-sI@dM;>@}bvso19b+-p z+q+wHXvSY}^5Cq*ny{p5mOC3jsl7ZNN?f>GzRrL6`G9+P<%s z44o}mZHsXl7(-Q^I=ONC9kXk@W+|aA*)KLvxet7kz3%>&r`SeQWFKU;gpOpLk(;K-=4A|IJT)VsrDK{n_vM{nIHu_tcATdDGc* z(@2x%zVqw&@^Wo`a{c<1`F{MwCmwnJ`HO$=?|#?8;%olvKm2?D=pX;q6Q@p3x83f} zjeq;`fA^C={WFg|{>a|^;s(#({HFJ5x#fN)#}1 z01TRvWTF(P6PETyb^VdkaR1)cx%IW%y7pP-!yU}X{4GDInOiBaTG$a|#iBB_#@_FL z#p9^P;Q$`uu;TxaIsu~ zfry%l_NBM}J$Nw3;Feqd6$U!x&3`>UPL5JUS1ztS4gk#L$$M~2cN zJe$pkU}J5w4z;Sb*qQ(UhN>YB>_d_^RMSvxO`2wXQWNOv)$P>mcI8Bs(Zj%dOm&Vb!^*TUXZf@w1x;z-v5Q zd5$uliKv*2(LXr$8mqq^`yBc_>QQ{NJQJDhrz<(7D~AH~r4LlqEEl2ua@jEoSUw>lk+e$1$ALn)>hz31^!qum z0!*=YJs)RvZV$=y#oX6*1=#QKR27Me?^M1LW1P3kOV@6^`11bw^U!QL@?H{}FesX` z0lW1Lw|-L8sd*p-6f`%!^{T#(4N%r-7t9PyK@%ERRpZ4)Lr3DsiJ}O!36{FdonVGK zOgTytywSy3q=TfUwB`JoTbzgeO>`0tpZ-_B`hlOl>%tx9Zh!p~pL_ZF=dS+gKl(qOJbCW_`tJX}X(p=O$Mii6I7Isk6=`PlnAHVzjJ&z^1?fjiLZafFD zLLghoYSxuP?qyJkDh%m4$7blNNm<>BlojF#fOm9k$CyGLR%h8N=Ip&k=;ieeO~H!0 z!%U)DFpNVdU{cUg#nmf(qr*29DVs4E&KW6pqmdblq5wcykTgXclF7#)R9)A_XL8O}A*77sj>~yp zg)o^+%6>*P=N!j8wYF{7>ZY#i5vCH!Ip+{UxoMG~D9$;vaO;a=pQ%Y@@ z$T1=tbn^uuN^pq5=Zv07M8xvA)|e@CDZM~-T~8*H&5f-)&tBNt+)~wz$;NzR?y6t_ zd%L@P`}<8(udS_%NagCywXNx7GHup~ohdjMHaEBHI;>4L2sot}LbJZU?Y;M*uBw^{ zox-GD4Qq z1FSgi$Doa|kNTOoLbn>ptm6r2$EqCk4Yx8v4B!Y(e6x>n>{va)t>Uibsb75rucF*H zU(~<|%v5_MM!|?cNwMhm_7!ohXw?!a;TX7qaf=9&b27_WqN%ZYfWp+aW4F8I4=^C` zBjSRDpoanY#9Z2m$?(gE^p?1uIlcDb|YJ? zJk+(`b@G`>xV~J}54^R0-`8K?z5s1v6Js(!G-|6Q$=;oM@Q1(h4Ien?{SE(f`+Mha zJM%+7`2E4@ANc*h>(tp3zxa#4{B*bYzz5&Gwn2}+{PK%C`xnmM{*izAvw!ce|HIR3 zn|FWr-CyvhQNAAa}GRNJ*9e*DQR&pvkJ z^cR1@!4>;gKlo#d_TYE?&Tlz;NBx)o`d>JE@(ht&d;q3ES-PLU<2QFTH zahcm`!1vs9>J3|G-+XTUt~0l>UJsXkH7h-ns~>r`{nVva)-KFqTCdW^iS=kOvwUW2 zYj3`nHCy1ygXjGc*zeHAGQ| zKt^Vejg}GGy%_MBJ0765jp~$aZf~vc?ClamNrhM?f0Rztf%!Bz5LPx!W67Rjkta$uG$}^i*5$G_Q;D{_*v*oc%PzQTI$85Bn1UHj znrT(lRnv4yRRzg~+PSJi#`$8VshiXxrFI#+Sr-qwwvEZqI|JmZs%rp1K#^QGlgVV$ zvR2;5MLXNM)*b8|+X?)~J<4?WydKOg57)C*`z?k62$+EpLII*ttuG!l8jkYKWA`g3 z0D+nnimNFYA}RqB4)mZBBGls35C9Q6LLovjKotWum6Bgt*uEf8C`E(5Cs3(qkBDXW zrfP_V>|y|9$OT?1lr|7yV;kt$C2}v)@4x`SId3Z{8LpK0l3w+k2NVMlIp^+}7|-fx zz=Crai5fDwYO)|U%cy`r8IAhfc|#=ag-s7v9WWp;B6AD`YO38b&KK>AS6_PS`KL~t zJ#}Gvftz(vOkJ17D#yT$VW8rE8RJp>f9#-@p9^hC!Hf*hfRW6MR7iz1s&Xb;q`ZjQ zE=-X2TQPMT%_IpyJ3GDmLUZHB?n^Jq!PQ`kPA^7GI~>wv-LIW&QhjQ3_reLg^N!^U z*FJi6`P43*T2AkpyR%^Ap)*}TT)yZ2b@=SV5C6)+o8IsSU)#A0>)-l^f7izL_QqBn z7u|=x{7cr>)+THI;9zHVFazl(#Lql@waFLuuEziMuYb?~@_+q>_g*;l^FQ&UU;EWx zzr9&M{Me(v{}29-{pFJ1`|$xw@c z5CKt&s|39_iEb~5t)#k&uVCzmu(dS6OOVpW`-bRHWM>< zIpx_wQR)K#BJ!?6pcqpZyPOrW#;z;4j(1)V3aik&K+HMEwpC_gCU#8Zk%J)tBB70POfds651_;yu?C#_K*SP) ztLurY>kyi{UazJBSj!7!Nik&+MbId7T}sLpLNi@k_w@B82@)r@-{S(PivJ zqzcB)CDqx%)vH%u2FqYFpY6ACv9ot$89NF-AudG_+PI8`+!jLcp>e)(%uQXVm|Roc zcuCsD(o{_h!I%`(z>Io|0`)+c8T6$~j&$NGe*??%UyO_y7$_i=t&spCuo0q_{2=Q~ zE%ngfiubG|gNNuQ;TF`sW=di60>WwrlZuX8G$JUu$7+P2L}WwA@Kr|eu~Dc5(7%#` zp%Fl_jyO~(C~txYgg^*}NW@H_qyS=|2C60^NCtok8GCMhd4WSPs{Byg36WIN{h<(6 zOot+lebj8gB>Vl`NCqrreMeToO`8&gLke)&7Vs)5a@7q205gTYN&|{wXr}f0`oS`W zBFosTvlOgc)f5oHuy6)dwd*>^NQ9pIK9(eld3R&?#&Z{+yW{+YbEoc{Zf^y@W-*ee z55X{KHlkDp-@tdeMNi>Me4*&~02-2u!a}|k+nX<=h^nZsTaPyuouYHq27rUO*Is>k z_R{CN7aw=mA3rDaH*&Yl%gUBFU}sL&Nj2Z)JZrk_Kk&MPFF3R1<45*ovfrM1=HS%h zeBtrwgFB}l$ZK1w8{q2$*8AUZ`=|fq58SwZ<#+$yZ&7SdoIR;nT|2nGTjy#2Rbx$@+OVQZ7%`eJ`= zYfEENbUDZA*4k{oP$F_(8_kW}=nj_4B@qFkqy?!ll4%BnOxCdZWLsQK-9}7$us8GO zoa4A$0Q_g%fQb7Eu7`C8%AqfRm zB?(rCfgu_~UlOJVAbUWi7uX~;1hqn2K_XT)$!Y{5fP^l1&n{!`7y98wIP!T1vAcqc z6p-VP3v4=gFxUsRfp}1}1Ozn`P~<*}q=GgSuB8Fm(X!o9GX*kac7$A#URR;-(d?F4 zUr3i`z4o@29B|?h=qXS~Nj@6U*$Y9Q*cQbtHC2Nc)9}i` z?2>2&*u_{(O$ckujXBep)3VE5l$86Vc9AF<;W(M;*s&U{f0ff-5j|pp zP;01Dz$H;#5)fFS)?$JfT@eigW1k}zJ~xltRWbABys?%QPjlB z+D}AefXYlDlH1t1_B!ol!{8gDfJi_FM98ivj>Md^K~^<{{pI3dxkzczwVh+C>w4O( zSCtn@DT|t=EHTdy7E5ME1kAZ@m)-2fa=x2#Oli4n=UumCqEn|%0%Gi9&e3w5FS}8n zvvy4ts9+Fr%-46%&>MiUzI^HBmtJ@Oa&r5HI(Uky>skd+BPe0C*IK{S5Y!07 z2oS`$V|V>ver?`%=#~i_>2kK*%@{FopK%lXSc zxHo(G>%Z~6Cr{OX>3{hC|Ksof=sVtZ_nY4O{%#Q;yzhaZ`smMG+dY_0=-dC?|BK!3 zqfb3U8+l`G;~l4YV|k^0{NWd#f9%SOpZm}qxmkvveEQ;vlhs>qJMk^=f7?Bc>hf7z zyo^~$bM6*3tFs(w`pkay#ErBI&M~y}10-_J`4ChjcX4qrGc!-T)-(%WE%s;gy_u@0 z8Z!pxfQo((C4$c2#KHc%YWL;m&z#M>H=f_CLbbKI)ukmkTrOJfG7>P6+gBU=TA?4Ba9YdvJ!&$4ozv8v?=tCTdT z@e>=wg$<1fj^<3^n2WS}!I1)7o}m!bhzLoD-N?V8!EYENP$jZ9YSJJAn86@kjIl8KMZ`>F*WsWb?7er4F?Qu-GYdZC!Oo~^hRoLY%XB);Ia`inlQvD`Js%us z+qPZTF|%Vg^S{PUg3Sg>PBqNgL4rM8>b}p!xO3(LVA~ZrXV)NiV z0Ol0i0Jf%aoEV7K?*LGBUAjW}>pUyE~uH+QqIUnKV-Z@vJ$= zGCp~b$*4mqims9qfGrlYWxH6m^R}IfBqG|_*qlrzIp;ZG*Df@tqG8>SF`8AO?z&~y zEqw?nwdh^I1}ZIeZd24i}IZc`k{E2@gb zhJY)opd(N`MqG|vaYQTt01%i7ZB-?mN?Nn#TzEpg1BSpv%1$|zGIoe4+ULZ>%KQ

hi73K`xx#oUo{K3IH7v5}DYb>{i2;Piy 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

p$f#e!9*ssEYFHQV0yqSk6w&-s^YYx@jl- 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

  • jY)iU-Cd5Kz~3 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 z z-`U(WtIIzig#|VFd`9JwSk1aLGkWyJ5^GDUTr^(=YO!f_}R~%!Vf=ktmkjv zE8l!@|LEXgwpeJb4iBoMMXj+!P*I}wu6KR3jy)%r?FfQe1yV|D5P_6nB*MYA!5$nO zbX~jIT$W{?&*z)2Yp++OuS#v&G?@0zwS90LB89-#GBYY=5VcZ9Yhz5NO;+VamSsg% z*2`tNI5gF4zBtVD%qRj8&CB&WZ-4yhXQ&ha71g}mZZuH{A=f%0DnN{4P@zBwG5Qc~ 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

    Mr)veZgxs>fGGfGKMxfHFYHmMkE0cS=wFPkzj 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�r8srcffK8wP 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 literal 0 HcmV?d00001 diff --git a/projects/platform_marketing_content/docs/00-vision-general/MVP_Plataforma_SaaS_Contenido_CRM.md b/projects/platform_marketing_content/docs/00-vision-general/MVP_Plataforma_SaaS_Contenido_CRM.md new file mode 100644 index 0000000..1c2e7b6 --- /dev/null +++ b/projects/platform_marketing_content/docs/00-vision-general/MVP_Plataforma_SaaS_Contenido_CRM.md @@ -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. diff --git a/projects/platform_marketing_content/docs/00-vision-general/VISION-GENERAL.md b/projects/platform_marketing_content/docs/00-vision-general/VISION-GENERAL.md new file mode 100644 index 0000000..33ce7df --- /dev/null +++ b/projects/platform_marketing_content/docs/00-vision-general/VISION-GENERAL.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-001-TENANTS.md b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-001-TENANTS.md new file mode 100644 index 0000000..986b4c9 --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-001-TENANTS.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-002-CRM.md b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-002-CRM.md new file mode 100644 index 0000000..ed3ce07 --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-002-CRM.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-003-PROJECTS.md b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-003-PROJECTS.md new file mode 100644 index 0000000..16d9ab8 --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-003-PROJECTS.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-004-GENERATION.md b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-004-GENERATION.md new file mode 100644 index 0000000..f8f3c8b --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-004-GENERATION.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-005-AUTOMATION.md b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-005-AUTOMATION.md new file mode 100644 index 0000000..c171cd7 --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-005-AUTOMATION.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-006-ASSETS.md b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-006-ASSETS.md new file mode 100644 index 0000000..557bb5d --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-006-ASSETS.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-007-ADMIN.md b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-007-ADMIN.md new file mode 100644 index 0000000..3e67898 --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-007-ADMIN.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-008-ANALYTICS.md b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-008-ANALYTICS.md new file mode 100644 index 0000000..fabafbd --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/PMC-008-ANALYTICS.md @@ -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 diff --git a/projects/platform_marketing_content/docs/02-definicion-modulos/_INDEX.md b/projects/platform_marketing_content/docs/02-definicion-modulos/_INDEX.md new file mode 100644 index 0000000..c524def --- /dev/null +++ b/projects/platform_marketing_content/docs/02-definicion-modulos/_INDEX.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-001-TENANTS.md b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-001-TENANTS.md new file mode 100644 index 0000000..a2c74df --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-001-TENANTS.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-002-CRM.md b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-002-CRM.md new file mode 100644 index 0000000..bf78881 --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-002-CRM.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-003-PROJECTS.md b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-003-PROJECTS.md new file mode 100644 index 0000000..459253a --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-003-PROJECTS.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-004-GENERATION.md b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-004-GENERATION.md new file mode 100644 index 0000000..b36aa46 --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-004-GENERATION.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-005-AUTOMATION.md b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-005-AUTOMATION.md new file mode 100644 index 0000000..d1e394f --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-005-AUTOMATION.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-006-ASSETS.md b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-006-ASSETS.md new file mode 100644 index 0000000..8beb65b --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-006-ASSETS.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-007-ADMIN.md b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-007-ADMIN.md new file mode 100644 index 0000000..7f3785a --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-007-ADMIN.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-008-ANALYTICS.md b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-008-ANALYTICS.md new file mode 100644 index 0000000..6069127 --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/RF-PMC-008-ANALYTICS.md @@ -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 diff --git a/projects/platform_marketing_content/docs/03-requerimientos/_INDEX.md b/projects/platform_marketing_content/docs/03-requerimientos/_INDEX.md new file mode 100644 index 0000000..fc9f3f1 --- /dev/null +++ b/projects/platform_marketing_content/docs/03-requerimientos/_INDEX.md @@ -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 diff --git a/projects/platform_marketing_content/docs/04-modelado/MODELO-DOMINIO.md b/projects/platform_marketing_content/docs/04-modelado/MODELO-DOMINIO.md new file mode 100644 index 0000000..5cad5f7 --- /dev/null +++ b/projects/platform_marketing_content/docs/04-modelado/MODELO-DOMINIO.md @@ -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 diff --git a/projects/platform_marketing_content/orchestration/00-guidelines/CONTEXTO-PROYECTO.md b/projects/platform_marketing_content/orchestration/00-guidelines/CONTEXTO-PROYECTO.md new file mode 100644 index 0000000..a308700 --- /dev/null +++ b/projects/platform_marketing_content/orchestration/00-guidelines/CONTEXTO-PROYECTO.md @@ -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 diff --git a/projects/platform_marketing_content/orchestration/PROXIMA-ACCION.md b/projects/platform_marketing_content/orchestration/PROXIMA-ACCION.md new file mode 100644 index 0000000..1deab0c --- /dev/null +++ b/projects/platform_marketing_content/orchestration/PROXIMA-ACCION.md @@ -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 diff --git a/projects/platform_marketing_content/orchestration/inventarios/MASTER_INVENTORY.yml b/projects/platform_marketing_content/orchestration/inventarios/MASTER_INVENTORY.yml new file mode 100644 index 0000000..cfdeb31 --- /dev/null +++ b/projects/platform_marketing_content/orchestration/inventarios/MASTER_INVENTORY.yml @@ -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"